feat(procurement): implement sample fulfillment and settled fee workflow

This commit is contained in:
2026-10-10 02:19:38 +08:00
parent 040f3bbd93
commit a973be2103
104 changed files with 9823 additions and 714 deletions
@@ -27,12 +27,41 @@ public class WalletCommandOutboxService {
public String publish(String operation, long customerId, String businessType, String businessNo,
BigDecimal amount, String remark, long aggregateVersion) {
String eventKey = "WALLET_COMMAND:" + businessType + ':' + businessNo + ':' + operation + ':' + aggregateVersion;
if ("REFUND".equals(operation)) throw new IllegalArgumentException("退款必须关联原扣款流水");
return publishCommand(operation, customerId, businessType, businessNo, amount, remark, aggregateVersion, null);
}
public String publishRefund(long customerId, String businessType, String refundNo, BigDecimal amount,
String originalLedgerId, String remark, long aggregateVersion) {
if (originalLedgerId == null || !originalLedgerId.matches("[1-9][0-9]*")) {
throw new IllegalArgumentException("退款原扣款流水无效");
}
return publishCommand("REFUND", customerId, businessType, refundNo, amount, remark, aggregateVersion, originalLedgerId);
}
private String publishCommand(String operation, long customerId, String businessType, String businessNo,
BigDecimal amount, String remark, long aggregateVersion, String originalLedgerId) {
String eventKey = "WALLET_COMMAND:" + customerId + ':' + businessType + ':' + businessNo + ':' + operation + ':' + aggregateVersion;
LastlegEventOutbox existing = mapper.findByEventKey(eventKey);
if (existing != null) return eventId(existing.getPayloadJson());
String idempotencyKey = businessType + ':' + businessNo + ':' + operation + ':' + aggregateVersion;
if (existing != null) {
try {
EventEnvelope saved = objectMapper.readValue(existing.getPayloadJson(), EventEnvelope.class);
WalletCommandRequestedV1 command = objectMapper.treeToValue(saved.payload(), WalletCommandRequestedV1.class);
if (command.customerId() != customerId || !operation.equals(command.operation())
|| !businessType.equals(command.businessType()) || !businessNo.equals(command.businessNo())
|| !java.util.Objects.equals(remark, command.remark())
|| !java.util.Objects.equals(originalLedgerId, command.originalLedgerId())
|| (amount == null ? command.amount() != null : command.amount() == null || amount.compareTo(command.amount()) != 0)) {
throw new IllegalStateException("同一资金请求对应不同费用事实");
}
return saved.eventId();
} catch (java.io.IOException exception) {
throw new IllegalStateException("资金指令事件快照损坏", exception);
}
}
String idempotencyKey = customerId + ":" + businessType + ':' + businessNo + ':' + operation + ':' + aggregateVersion;
WalletCommandRequestedV1 value = new WalletCommandRequestedV1(operation, customerId, businessType,
businessNo, amount, remark, idempotencyKey, Instant.now());
businessNo, amount, remark, idempotencyKey, Instant.now(), originalLedgerId);
JsonNode payload = objectMapper.valueToTree(value);
EventEnvelope envelope = new EventEnvelope(EventIdentifiers.newUuid(), EventTypes.WALLET_COMMAND_REQUESTED,
WalletCommandRequestedV1.VERSION, Instant.now(),
@@ -49,10 +78,6 @@ public class WalletCommandOutboxService {
return envelope.eventId();
}
private String eventId(String json) {
try { return objectMapper.readValue(json, EventEnvelope.class).eventId(); }
catch (Exception exception) { throw new IllegalStateException("资金指令事件快照损坏", exception); }
}
private String json(Object value) {
try { return objectMapper.writeValueAsString(value); }
catch (Exception exception) { throw new IllegalStateException("资金指令事件序列化失败", exception); }