fix(finance): key container finance by booking
This commit is contained in:
+2
-2
@@ -27,7 +27,7 @@ public class PlatformFinanceContainerSettlementController {
|
||||
+ "hasAuthority('platform:finance:settlement-ap:list')")
|
||||
@Operation(summary = "按客户和柜次批量查询已结算柜费及成本分摊")
|
||||
public ApiResponse<ContainerSettlementDetails> details(@RequestParam long customerId,
|
||||
@RequestParam long containerId) {
|
||||
return FinanceResponses.success(service.details(customerId, containerId));
|
||||
@RequestParam long containerBookingId) {
|
||||
return FinanceResponses.success(service.details(customerId, containerBookingId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ public class PlatformFinanceController {
|
||||
@BusinessAudit(event = "FINANCE_COST_CREATE", resourceType = "FINANCE_COST")
|
||||
public ApiResponse<?> cost(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CostRequest request) {
|
||||
if (request.currency() != null && !"CNY".equals(request.currency())) throw new IllegalArgumentException("首期仅支持CNY");
|
||||
var internal = new FinanceService.CostRequest(null, request.costType(), request.orderId(), null, request.containerId(), null, null, "MANUAL", request.expenseName(), request.amount(), request.voucherFileIds(), request.remark());
|
||||
java.util.List<FinanceService.ContainerOrderVolume> volumes = java.util.List.of();
|
||||
Long containerBookingId = null;
|
||||
if ("CONTAINER_SHARED".equals(request.costType())) {
|
||||
if (request.containerId() == null) throw new IllegalArgumentException("整柜成本必须指定柜次");
|
||||
var response = costSnapshotClient.loadingPlan(fileToken, request.customerId(), request.containerId());
|
||||
@@ -180,9 +180,13 @@ public class PlatformFinanceController {
|
||||
|| snapshot.orders() == null || snapshot.orders().isEmpty()) {
|
||||
throw new IllegalArgumentException("Firstleg未返回有效柜内订单体积快照");
|
||||
}
|
||||
containerBookingId = snapshot.containerBookingId();
|
||||
volumes = snapshot.orders().stream().map(v -> new FinanceService.ContainerOrderVolume(v.orderId(),
|
||||
v.inboundId(), snapshot.loadingPlanId(), snapshot.customerId(), v.volumeCbm())).toList();
|
||||
v.inboundId(), snapshot.containerBookingId(), snapshot.customerId(), v.volumeCbm())).toList();
|
||||
}
|
||||
var internal = new FinanceService.CostRequest(null, request.costType(), request.orderId(), null,
|
||||
containerBookingId, null, null, "MANUAL", request.expenseName(), request.amount(),
|
||||
request.voucherFileIds(), request.remark());
|
||||
return FinanceResponses.success(resourceView(service.createCost(request.customerId(), user.userId(), internal, volumes, key)));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public final class FinanceContainerSettlementDtos {
|
||||
|
||||
public record ContainerSettlementDetails(
|
||||
long customerId,
|
||||
long containerId,
|
||||
long containerBookingId,
|
||||
List<Long> settledSourceFeeIds,
|
||||
List<ContainerCostDetail> costs) {}
|
||||
|
||||
@@ -20,7 +20,7 @@ public final class FinanceContainerSettlementDtos {
|
||||
String costOrigin,
|
||||
Long sourceFeeId,
|
||||
Integer sourceFeeVersion,
|
||||
long containerId,
|
||||
long containerBookingId,
|
||||
String supplierName,
|
||||
String feeItemName,
|
||||
BigDecimal amount,
|
||||
|
||||
+14
-13
@@ -13,14 +13,15 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
@Select("""
|
||||
SELECT COUNT(*) AS customerLinkCount
|
||||
FROM (
|
||||
SELECT customer_id, container_id FROM fin_fee_source
|
||||
WHERE customer_id = #{customerId} AND container_id = #{containerId}
|
||||
SELECT customer_id, container_booking_id AS container_id FROM fin_fee_source
|
||||
WHERE customer_id = #{customerId} AND container_booking_id = #{containerBookingId}
|
||||
UNION ALL
|
||||
SELECT tenant_id AS customer_id, container_id FROM fin_cost
|
||||
WHERE tenant_id = #{customerId} AND container_id = #{containerId}
|
||||
WHERE tenant_id = #{customerId} AND container_id = #{containerBookingId}
|
||||
) links
|
||||
""")
|
||||
OwnershipRow ownership(@Param("customerId") long customerId, @Param("containerId") long containerId);
|
||||
OwnershipRow ownership(@Param("customerId") long customerId,
|
||||
@Param("containerBookingId") long containerBookingId);
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT settled.source_fee_id
|
||||
@@ -32,14 +33,14 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
WHERE s.tenant_id = #{customerId}
|
||||
AND s.status = 'PAID'
|
||||
AND f.customer_id = #{customerId}
|
||||
AND f.container_id = #{containerId}
|
||||
AND f.container_booking_id = #{containerBookingId}
|
||||
AND f.source_system = 'FIRSTLEG'
|
||||
AND f.source_type = 'CONTAINER_FEE'
|
||||
UNION ALL
|
||||
SELECT c.source_fee_id
|
||||
FROM fin_cost c
|
||||
WHERE c.tenant_id = #{customerId}
|
||||
AND c.container_id = #{containerId}
|
||||
AND c.container_id = #{containerBookingId}
|
||||
AND c.cost_origin = 'FIRSTLEG_DIRECT_PAID'
|
||||
AND c.source_system = 'FIRSTLEG'
|
||||
AND c.source_type = 'CONTAINER_FEE'
|
||||
@@ -49,12 +50,12 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
ORDER BY settled.source_fee_id
|
||||
""")
|
||||
List<Long> settledSourceFeeIds(@Param("customerId") long customerId,
|
||||
@Param("containerId") long containerId);
|
||||
@Param("containerBookingId") long containerBookingId);
|
||||
|
||||
@Select("""
|
||||
SELECT c.id AS costId, c.cost_no AS costNo, c.cost_type AS costType, c.cost_origin AS costOrigin,
|
||||
c.source_fee_id AS sourceFeeId, c.source_fee_version AS sourceFeeVersion,
|
||||
c.container_id AS containerId, c.supplier_name_snapshot AS supplierName,
|
||||
c.container_id AS containerBookingId, c.supplier_name_snapshot AS supplierName,
|
||||
c.fee_item_name AS feeItemName, c.cost_amount AS amount, c.currency_code AS currency,
|
||||
c.status,
|
||||
a.id AS allocationId, a.order_id AS orderId, a.inbound_id AS inboundId,
|
||||
@@ -63,11 +64,11 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
a.rounding_adjustment AS roundingAdjustment
|
||||
FROM fin_cost c
|
||||
LEFT JOIN fin_cost_allocation a ON a.tenant_id = c.tenant_id AND a.cost_id = c.id
|
||||
WHERE c.tenant_id = #{customerId} AND c.container_id = #{containerId}
|
||||
WHERE c.tenant_id = #{customerId} AND c.container_id = #{containerBookingId}
|
||||
ORDER BY c.created_at DESC, c.id DESC, a.id
|
||||
""")
|
||||
List<CostAllocationRow> costs(@Param("customerId") long customerId,
|
||||
@Param("containerId") long containerId);
|
||||
@Param("containerBookingId") long containerBookingId);
|
||||
|
||||
final class OwnershipRow {
|
||||
private long customerLinkCount;
|
||||
@@ -83,7 +84,7 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
private String costOrigin;
|
||||
private Long sourceFeeId;
|
||||
private Integer sourceFeeVersion;
|
||||
private long containerId;
|
||||
private long containerBookingId;
|
||||
private String supplierName;
|
||||
private String feeItemName;
|
||||
private BigDecimal amount;
|
||||
@@ -110,8 +111,8 @@ public interface FinanceContainerSettlementQueryMapper {
|
||||
public void setSourceFeeId(Long value) { this.sourceFeeId = value; }
|
||||
public Integer getSourceFeeVersion() { return sourceFeeVersion; }
|
||||
public void setSourceFeeVersion(Integer value) { this.sourceFeeVersion = value; }
|
||||
public long getContainerId() { return containerId; }
|
||||
public void setContainerId(long value) { this.containerId = value; }
|
||||
public long getContainerBookingId() { return containerBookingId; }
|
||||
public void setContainerBookingId(long value) { this.containerBookingId = value; }
|
||||
public String getSupplierName() { return supplierName; }
|
||||
public void setSupplierName(String value) { this.supplierName = value; }
|
||||
public String getFeeItemName() { return feeItemName; }
|
||||
|
||||
+7
-7
@@ -22,21 +22,21 @@ public class FinanceContainerSettlementQueryService {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public ContainerSettlementDetails details(long customerId, long containerId) {
|
||||
public ContainerSettlementDetails details(long customerId, long containerBookingId) {
|
||||
if (customerId <= 0) throw new IllegalArgumentException("客户范围无效");
|
||||
if (containerId <= 0) throw new IllegalArgumentException("柜次范围无效");
|
||||
if (containerBookingId <= 0) throw new IllegalArgumentException("货柜范围无效");
|
||||
|
||||
var ownership = mapper.ownership(customerId, containerId);
|
||||
var ownership = mapper.ownership(customerId, containerBookingId);
|
||||
if (ownership == null || ownership.getCustomerLinkCount() == 0) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "柜次不存在或不属于客户");
|
||||
}
|
||||
|
||||
List<Long> settledSourceFeeIds = mapper.settledSourceFeeIds(customerId, containerId).stream()
|
||||
List<Long> settledSourceFeeIds = mapper.settledSourceFeeIds(customerId, containerBookingId).stream()
|
||||
.filter(value -> value != null && value > 0)
|
||||
.distinct()
|
||||
.toList();
|
||||
List<ContainerCostDetail> costs = groupCosts(mapper.costs(customerId, containerId));
|
||||
return new ContainerSettlementDetails(customerId, containerId, settledSourceFeeIds, costs);
|
||||
List<ContainerCostDetail> costs = groupCosts(mapper.costs(customerId, containerBookingId));
|
||||
return new ContainerSettlementDetails(customerId, containerBookingId, settledSourceFeeIds, costs);
|
||||
}
|
||||
|
||||
private static List<ContainerCostDetail> groupCosts(List<CostAllocationRow> rows) {
|
||||
@@ -60,7 +60,7 @@ public class FinanceContainerSettlementQueryService {
|
||||
|
||||
private ContainerCostDetail view() {
|
||||
return new ContainerCostDetail(cost.getCostId(), cost.getCostNo(), cost.getCostType(),
|
||||
cost.getCostOrigin(), cost.getSourceFeeId(), cost.getSourceFeeVersion(), cost.getContainerId(),
|
||||
cost.getCostOrigin(), cost.getSourceFeeId(), cost.getSourceFeeVersion(), cost.getContainerBookingId(),
|
||||
cost.getSupplierName(), cost.getFeeItemName(), cost.getAmount(), cost.getCurrency(),
|
||||
cost.getStatus(), List.copyOf(allocations));
|
||||
}
|
||||
|
||||
@@ -86,10 +86,11 @@ public class FinanceFeeSourceEventService {
|
||||
snapshot.sourceType(), snapshot.feeId());
|
||||
jdbc.update("UPDATE fin_cost SET status='VOIDED',voided_at=CURRENT_TIMESTAMP(3),void_reason='来源版本已替代',version=version+1,updated_by=0 WHERE cost_origin='FIRSTLEG_DIRECT_PAID' AND source_system='FIRSTLEG' AND source_type=? AND source_fee_id=? AND status='CONFIRMED'",
|
||||
snapshot.sourceType(), snapshot.feeId());
|
||||
jdbc.update("INSERT INTO fin_fee_source(source_system,source_fee_id,source_fee_version,customer_id,source_type,source_no,direction,order_id,inbound_id,container_id,fee_item_code,fee_item_name,amount,currency_code,settlement_mode,paid_at,payment_method,payment_reference_no,payment_voucher_file_ids,source_hash,source_snapshot,status) VALUES ('FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'AVAILABLE')",
|
||||
jdbc.update("INSERT INTO fin_fee_source(source_system,source_fee_id,source_fee_version,customer_id,source_type,source_no,direction,order_id,inbound_id,container_id,container_booking_id,fee_item_code,fee_item_name,amount,currency_code,settlement_mode,paid_at,payment_method,payment_reference_no,payment_voucher_file_ids,source_hash,source_snapshot,status) VALUES ('FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'AVAILABLE')",
|
||||
snapshot.feeId(), snapshot.feeVersion(), snapshot.customerId(), snapshot.sourceType(),
|
||||
snapshot.inboundCode() == null || snapshot.inboundCode().isBlank() ? snapshot.containerNo() : snapshot.inboundCode(),
|
||||
snapshot.direction(), snapshot.orderId(), snapshot.inboundId(), snapshot.containerId(),
|
||||
financeContainerBookingId(snapshot),
|
||||
snapshot.feeItemCode(), snapshot.feeItemName(), snapshot.totalAmount(), snapshot.currency(),
|
||||
snapshot.settlementMode(), snapshot.paidAt(), snapshot.paymentMethod(),
|
||||
snapshot.paymentReferenceNo(), voucherJson(snapshot.paymentVoucherFileIds()),
|
||||
@@ -116,7 +117,7 @@ public class FinanceFeeSourceEventService {
|
||||
String costNo = "DIRECT-" + snapshot.sourceType() + "-" + snapshot.feeId() + "-V" + snapshot.feeVersion();
|
||||
jdbc.update("INSERT INTO fin_cost(tenant_id,cost_no,cost_type,cost_origin,source_system,source_type,source_fee_id,source_fee_version,source_hash,order_id,inbound_id,container_id,supplier_id,supplier_name_snapshot,fee_item_code,fee_item_name,currency_code,cost_amount,voucher_file_ids,paid_at,payment_method,payment_reference_no,status,confirmed_at,created_by,created_by_name,updated_by) VALUES(?,?,'SOURCE_DIRECT_PAID','FIRSTLEG_DIRECT_PAID','FIRSTLEG',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'CONFIRMED',CURRENT_TIMESTAMP(3),0,'系统',0)",
|
||||
snapshot.customerId(), costNo, snapshot.sourceType(), snapshot.feeId(), snapshot.feeVersion(),
|
||||
snapshot.sourceHash(), snapshot.orderId(), snapshot.inboundId(), snapshot.containerId(),
|
||||
snapshot.sourceHash(), snapshot.orderId(), snapshot.inboundId(), financeContainerBookingId(snapshot),
|
||||
snapshot.supplierId(), snapshot.supplierName(), snapshot.feeItemCode(), snapshot.feeItemName(),
|
||||
snapshot.currency(), snapshot.totalAmount(), voucherJsonOrEmpty(snapshot.paymentVoucherFileIds()),
|
||||
snapshot.paidAt(), snapshot.paymentMethod(), snapshot.paymentReferenceNo());
|
||||
@@ -184,7 +185,11 @@ public class FinanceFeeSourceEventService {
|
||||
throw error("FIN-EVT-012", "应收费用不得携带结算方式或已付快照");
|
||||
}
|
||||
if ("INBOUND_FEE".equals(value.sourceType()) && (value.inboundId() == null || !String.valueOf(value.inboundId()).equals(event.payload().inboundId()))) throw error("FIN-EVT-013", "入库单归属不一致");
|
||||
if ("CONTAINER_FEE".equals(value.sourceType()) && (value.containerId() == null || !String.valueOf(value.containerId()).equals(event.payload().containerId()))) throw error("FIN-EVT-014", "柜子归属不一致");
|
||||
if ("CONTAINER_FEE".equals(value.sourceType()) && (value.containerId() == null
|
||||
|| !String.valueOf(value.containerId()).equals(event.payload().containerId())
|
||||
|| value.containerBookingId() == null || value.containerBookingId() <= 0)) {
|
||||
throw error("FIN-EVT-014", "柜子归属不一致");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePayableSettlement(FirstlegFeeSourceClient.Snapshot value) {
|
||||
@@ -216,6 +221,10 @@ public class FinanceFeeSourceEventService {
|
||||
return values == null || values.isEmpty() ? "[]" : json(values);
|
||||
}
|
||||
|
||||
private static Long financeContainerBookingId(FirstlegFeeSourceClient.Snapshot snapshot) {
|
||||
return "CONTAINER_FEE".equals(snapshot.sourceType()) ? snapshot.containerBookingId() : null;
|
||||
}
|
||||
|
||||
private String json(Object value) {
|
||||
try { return objectMapper.writeValueAsString(value); }
|
||||
catch (JsonProcessingException e) { throw error("FIN-EVT-001", "事件序列化失败"); }
|
||||
|
||||
Reference in New Issue
Block a user