diff --git a/finance/src/main/java/com/baiyue/finance/service/TripFinanceQueryService.java b/finance/src/main/java/com/baiyue/finance/service/TripFinanceQueryService.java index 197d70a00..4ebf610ea 100644 --- a/finance/src/main/java/com/baiyue/finance/service/TripFinanceQueryService.java +++ b/finance/src/main/java/com/baiyue/finance/service/TripFinanceQueryService.java @@ -28,7 +28,7 @@ public class TripFinanceQueryService { SELECT f.id,f.customer_id,f.business_no,f.source_no,f.fee_item_name,f.source_fee_version,f.currency_code,f.amount,f.status FROM fin_fee_source f WHERE f.source_system='LASTLEG' AND f.business_type='DELIVERY_ORDER' AND f.source_type='OVERSEAS_DELIVERY_FEE' AND f.direction='RECEIVABLE' - AND f.review_status='APPROVED' AND f.status IN ('AVAILABLE','SETTLED') + AND f.review_status='APPROVED' AND f.status IN ('AVAILABLE','BILLED','SETTLED') AND NOT EXISTS (SELECT 1 FROM fin_fee_source newer WHERE newer.source_system=f.source_system AND newer.source_type=f.source_type AND newer.source_fee_id=f.source_fee_id AND newer.source_fee_version>f.source_fee_version) diff --git a/finance/src/main/resources/openapi/finance-trip-receivables-v1.yaml b/finance/src/main/resources/openapi/finance-trip-receivables-v1.yaml index 5ff65b8c9..cb41ba568 100644 --- a/finance/src/main/resources/openapi/finance-trip-receivables-v1.yaml +++ b/finance/src/main/resources/openapi/finance-trip-receivables-v1.yaml @@ -3,7 +3,7 @@ "info": { "title": "车次Finance权威应收与实收只读来源", "version": "1.0.0", - "description": "Lastleg以不可变DEPART验证后的客户/派送业务号读取。仅本Finance库;最新已审核有效LASTLEG OVERSEAS_DELIVERY_FEE应收,按币种。实收只取fin_receipt_bill已确认分配;整张已出账账单(ISSUED、PARTIALLY_PAID、PAID)全部来源均明确归属请求车次时才计一次。混合账单、无分配、无有效来源为待分配,不按账单总额或比例估算。最多200业务、2000费用,批量查询。现行应收结算单收款沿关联账单,fin_settlement_payment仅用于应付,不重复叠加。" + "description": "Lastleg以不可变DEPART验证后的客户/派送业务号读取。仅本Finance库;最新已审核有效LASTLEG OVERSEAS_DELIVERY_FEE应收(AVAILABLE、正式出账后的BILLED、SETTLED;LOCKED、VOIDED、SUPERSEDED排除),按币种。实收只取fin_receipt_bill已确认分配;整张已出账账单(ISSUED、PARTIALLY_PAID、PAID)全部来源均明确归属请求车次时才计一次。混合账单、无分配、无有效来源为待分配,不按账单总额或比例估算。最多200业务、2000费用,批量查询。现行应收结算单收款沿关联账单,fin_settlement_payment仅用于应付,不重复叠加。" }, "paths": { "/internal/finance/v1/trip-receivables/query": { diff --git a/finance/src/test/java/com/baiyue/finance/service/TripFinanceQueryServiceTest.java b/finance/src/test/java/com/baiyue/finance/service/TripFinanceQueryServiceTest.java index 47e4c6ea3..c0fe30027 100644 --- a/finance/src/test/java/com/baiyue/finance/service/TripFinanceQueryServiceTest.java +++ b/finance/src/test/java/com/baiyue/finance/service/TripFinanceQueryServiceTest.java @@ -24,6 +24,8 @@ class TripFinanceQueryServiceTest { void bill(long source,int version) { jdbc.update("INSERT INTO fin_bill VALUES(1,1,'BILL-A','CNY','ISSUED')"); jdbc.update("INSERT INTO fin_bill_line VALUES(1,1,?,?,'CNY')",source,version); + // Fixture models the real ISSUE source state; this does not call the write service. + jdbc.update("UPDATE fin_fee_source SET status='BILLED' WHERE id=? AND status='AVAILABLE'",source); } void receipt(String amount) { jdbc.update("INSERT INTO fin_receipt VALUES(1,1,'CONFIRMED','CNY')"); @@ -35,6 +37,33 @@ class TripFinanceQueryServiceTest { assertThat(result.totals().get(0).receivableAmount()).isEqualByComparingTo("20"); assertThat(result.documents().get(0).allocationStatus()).isEqualTo("ATTRIBUTED"); } + @Test void legalSourceAndBillStatesKeepReceivableAndRecordedReceiptAcrossLifecycle() { + for(String sourceStatus:List.of("AVAILABLE","BILLED","SETTLED"))for(String billStatus:List.of("ISSUED","PARTIALLY_PAID","PAID")) { + setup();fee(1,1,"DL-A",1,"CNY","20",sourceStatus);bill(1,1); + jdbc.update("UPDATE fin_fee_source SET status=?",sourceStatus); + jdbc.update("UPDATE fin_bill SET status=?",billStatus); + if(!"ISSUED".equals(billStatus))receipt("PAID".equals(billStatus)?"20":"3.5"); + var result=service.query(request(true)); + assertThat(result.sources()).as(sourceStatus+"/"+billStatus).hasSize(1); + assertThat(result.sources().get(0).status()).isEqualTo(sourceStatus); + assertThat(result.totals().get(0).receivableAmount()).isEqualByComparingTo("20"); + assertThat(result.documents()).hasSize(1); + if("ISSUED".equals(billStatus)) { + assertThat(result.totals().get(0).receivedAmount()).isNull(); + assertThat(result.documents().get(0).allocationStatus()).isEqualTo("PENDING_ALLOCATION"); + } else { + assertThat(result.totals().get(0).receivedAmount()).isEqualByComparingTo("PAID".equals(billStatus)?"20":"3.5"); + assertThat(result.documents().get(0).allocationStatus()).isEqualTo("ATTRIBUTED"); + } + } + } + @Test void lockedVoidedAndSupersededSourcesRemainExcluded() { + for(String status:List.of("LOCKED","VOIDED","SUPERSEDED")) { + setup();fee(1,1,"DL-A",1,"CNY","20",status);bill(1,1);receipt("20"); + var result=service.query(request(true));assertThat(result.sources()).as(status).isEmpty(); + assertThat(result.totals()).isEmpty();assertThat(result.documents()).isEmpty(); + } + } @Test void issuedPartialAndPaidBillsKeepTheirConfirmedAllocation() { for(String status:List.of("ISSUED","PARTIALLY_PAID","PAID")) { setup();fee(1,1,"DL-A",1,"CNY","20","AVAILABLE");bill(1,1); @@ -78,8 +107,8 @@ class TripFinanceQueryServiceTest { assertThat(service.query(request(true)).totals().get(0).receivableAmount()).isEqualByComparingTo("0"); } @Test void latestVoidedDoesNotFallBackAndOtherCustomerExcluded() { - fee(1,1,"DL-A",1,"CNY","20","AVAILABLE");fee(2,1,"DL-A",2,"CNY","20","VOIDED"); - fee(3,2,"DL-A",1,"CNY","999","AVAILABLE"); + fee(1,1,"DL-A",1,"CNY","20","BILLED");fee(2,1,"DL-A",2,"CNY","20","VOIDED"); + fee(3,2,"DL-A",1,"CNY","999","BILLED"); var result=service.query(request(true));assertThat(result.sources()).isEmpty();assertThat(result.status()).isEqualTo("PENDING_ALLOCATION"); } @Test void currenciesSeparateAndNoReceiptPermissionMasksDocuments() {