This commit is contained in:
2026-10-30 11:54:49 +08:00
parent 0bd25de906
commit e1bfcfc8f5
15 changed files with 173 additions and 19 deletions
@@ -36,6 +36,7 @@ class ProcurementQueryServiceTest {
var platform=query.platform("9","CG-1",Set.of("platform:overseas-business:purchase:buy"));
assertThat(platform.customerView().budget().totalAmount()).isEqualByComparingTo("150");
assertThat(platform.customerView().order().actualWarehouseName()).isEqualTo("洛杉矶仓");
assertThat(platform.customerView().order().remark()).isEqualTo("internal note");
assertThat(platform.customerView().boxes()).isEmpty();assertThat(platform.customerView().shipments()).isEmpty();
assertThat(platform.actions()).isEmpty();assertThat(platform.operations()).isEmpty();
assertThat(platform.files()).hasSize(3);assertThat(platform.internalRemark()).isEqualTo("internal note");
@@ -49,11 +50,13 @@ class ProcurementQueryServiceTest {
assertThat(query.customer("9","CG-1").order().actualWarehouseName()).isEqualTo("洛杉矶仓");
}
@Test void customerCannotReadAnotherCustomersOrderOrInternalDiagnostics() throws Exception {
fixture.jdbc.update("UPDATE ll_overseas_procurement_order SET remark='purchase note' WHERE id=11");
assertThatThrownBy(()->query.customer("10","CG-1")).hasMessageContaining("当前客户");
verifyNoInteractions(fees);
var fee=fee("9",11L,"RECEIVABLE");fee.setWalletErrorMessage("private error");fee.setReviewReason("private reason");
when(fees.procurementFees("9",11L)).thenReturn(List.of(fee,fee("10",11L,"RECEIVABLE"),fee("9",12L,"RECEIVABLE"),fee("9",11L,"PAYABLE")));
var result=query.customer("9","CG-1");
assertThat(result.order().remark()).isEqualTo("purchase note");
assertThat(result.fees()).singleElement().satisfies(row->{assertThat(row.walletStatus()).isEqualTo("FREEZE_PENDING");assertThat(row.sourceLinked()).isFalse();});
assertThat(new ObjectMapper().findAndRegisterModules().writeValueAsString(result)).doesNotContain("private", "walletErrorMessage", "reviewReason", "internalRemark");
}
@@ -51,7 +51,7 @@ class ProcurementWorkflowPersistenceTest {
file(101L,11L,"9","image/jpeg");file(102L,11L,"9","application/pdf");file(103L,12L,"10","image/jpeg");file(104L,11L,"9","image/png");
}
/** 驳回后必须全批换新照片,返工不能重复增加库存。 */
/** 驳回后可选择换图,换图仍校验附件归属且不能重复增加库存。 */
@Test void rejectionRequiresNewPhotosWithoutChangingStock() {
tx.execute(t->service.receive("9","CG-1","7",new Receipt(0,"receipt",10,List.of(101L))));
tx.execute(t->service.requestConfirmation("9","CG-1","7",new Command(1,"ask")));
@@ -59,16 +59,17 @@ class ProcurementWorkflowPersistenceTest {
assertThatThrownBy(()->tx.execute(t->service.reject("10","CG-1","8",new Rejection(2,"other","破损")))).hasMessageContaining("不属于");
var request=new Rejection(2,"reject","照片不清晰");
var rejected=tx.execute(t->service.reject("9","CG-1","8",request));
assertThat(rejected.status()).isEqualTo("RECEIVING");assertThat(rejected.rejectionReason()).isEqualTo("照片不清晰");assertThat(rejected.photosNeedUpdate()).isTrue();
assertThat(rejected.status()).isEqualTo("RECEIVING");assertThat(rejected.rejectionReason()).isEqualTo("照片不清晰");assertThat(rejected.photosNeedUpdate()).isFalse();
assertThat(tx.execute(t->service.reject("9","CG-1","8",request)).version()).isEqualTo(3);
assertThatThrownBy(()->tx.execute(t->service.requestConfirmation("9","CG-1","7",new Command(3,"early")))).hasMessageContaining("重新上传");
long receipt=rejected.receipts().get(0).id();
assertThatThrownBy(()->tx.execute(t->service.replacePhotos("9","CG-1","7",new ReceiptPhotos(3,"old",receipt,List.of(101L))))).hasMessageContaining("不能复用");
assertThatThrownBy(()->tx.execute(t->service.replacePhotos("9","CG-1","7",new ReceiptPhotos(3,"old-upload",receipt,List.of(104L))))).hasMessageContaining("重新上传");
file(105L,11L,"9","image/png");
var current=tx.execute(t->service.replacePhotos("9","CG-1","7",new ReceiptPhotos(3,"new",receipt,List.of(105L))));
assertThat(current.photosNeedUpdate()).isFalse();assertThat(current.stockQuantity()).isEqualTo(10);assertThat(current.receipts()).hasSize(1);
tx.execute(t->service.requestConfirmation("9","CG-1","7",new Command(4,"again")));
var resubmitted=tx.execute(t->service.requestConfirmation("9","CG-1","7",new Command(4,"again")));
assertThat(resubmitted.rejectionReason()).isNull();
assertThat(resubmitted.photosNeedUpdate()).isFalse();
assertThat(tx.execute(t->service.confirm("9","CG-1","8",new Command(5,"confirm"))).status()).isEqualTo("CONFIRMED");
}
@@ -99,6 +100,42 @@ class ProcurementWorkflowPersistenceTest {
assertThat(current.files()).hasSize(3);
}
/** 多批次驳回后仅整改需要换图的批次,即可再次提交,其他批次保留。 */
@Test void replacingOneRejectedBatchAllowsResubmissionWithoutReplacingEveryBatch() {
tx.execute(s -> service.receive("9", "CG-1", "7", new Receipt(0, "multi-r1", 4, List.of(101L))));
tx.execute(s -> service.receive("9", "CG-1", "7", new Receipt(1, "multi-r2", 6, List.of(104L))));
tx.execute(s -> service.requestConfirmation("9", "CG-1", "7", new Command(2, "multi-ask")));
var rejected = tx.execute(s -> service.reject("9", "CG-1", "8", new Rejection(3, "multi-reject", "两批照片均不清晰")));
assertThat(rejected.photosNeedUpdate()).isFalse();
long first = rejected.receipts().get(0).id();
file(106L, 11L, "9", "image/png");
var firstReplaced = tx.execute(s -> service.replacePhotos("9", "CG-1", "7", new ReceiptPhotos(4, "multi-r1-new", first, List.of(106L))));
assertThat(firstReplaced.photosNeedUpdate()).isFalse();
assertThat(firstReplaced.receipts().get(1).fileIds()).containsExactly(104L);
assertThat(firstReplaced.receivedQuantity()).isEqualTo(10);
var resubmitted = tx.execute(s -> service.requestConfirmation("9", "CG-1", "7", new Command(5, "multi-again")));
assertThat(resubmitted.status()).isEqualTo("WAIT_CUSTOMER_CONFIRM");
assertThat(resubmitted.rejectionReason()).isNull();
assertThat(resubmitted.photosNeedUpdate()).isFalse();
}
/** 多批次均保留原照片也可重新提交,成功后清除上一轮原因,重放不改变库存。 */
@Test void rejectedPurchaseCanResubmitWithoutUploadingPhotos() {
tx.execute(s -> service.receive("9", "CG-1", "7", new Receipt(0, "keep-r1", 4, List.of(101L))));
tx.execute(s -> service.receive("9", "CG-1", "7", new Receipt(1, "keep-r2", 6, List.of(104L))));
tx.execute(s -> service.requestConfirmation("9", "CG-1", "7", new Command(2, "keep-ask")));
tx.execute(s -> service.reject("9", "CG-1", "8", new Rejection(3, "keep-reject", "请核对")));
var command = new Command(4, "keep-again");
var resubmitted = tx.execute(s -> service.requestConfirmation("9", "CG-1", "7", command));
assertThat(resubmitted.status()).isEqualTo("WAIT_CUSTOMER_CONFIRM");
assertThat(resubmitted.rejectionReason()).isNull();
assertThat(resubmitted.photosNeedUpdate()).isFalse();
assertThat(resubmitted.receipts().get(0).fileIds()).containsExactly(101L);
assertThat(resubmitted.receipts().get(1).fileIds()).containsExactly(104L);
assertThat(resubmitted.stockQuantity()).isEqualTo(10);
assertThat(tx.execute(s -> service.requestConfirmation("9", "CG-1", "7", command)).version()).isEqualTo(5);
}
@Test void partialPurchaseCanFinishButRemainingStockRequiresMoreOutbounds() {
tx.execute(s->service.receive("9","CG-1","7",new Receipt(0,"r",6,List.of(101L))));
tx.execute(s->service.requestConfirmation("9","CG-1","7",new Command(1,"ask")));
@@ -54,6 +54,15 @@ class OrderBudgetServiceTest {
assertThat(service.read("9","procurement","key").usable()).isFalse();
assertThat(service.read("9","procurement","key").message()).contains("过期");
}
@Test void reviewReleaseImmediatelyReleasesFrozenBudget() {
row.setOrderNo("CG-1"); row.setStatus("FROZEN");
when(repository.lockOrder("9","procurement","CG-1")).thenReturn(row);
when(wallet.publish(eq("RELEASE"),eq(9L),eq(OrderBudgetService.BUSINESS_TYPE),eq("11"),eq(new BigDecimal("800")),anyString(),eq(2L))).thenReturn("release");
service.releaseAfterReview("9","procurement","CG-1");
assertThat(row.getReleaseRequested()).isTrue();
assertThat(row.getStatus()).isEqualTo("RELEASE_PENDING");
verify(repository).save(row);
}
private WalletCommandCompletedV1 result(String event,String operation,String outcome,String status,String error) {
return new WalletCommandCompletedV1(event,operation,9L,OrderBudgetService.BUSINESS_TYPE,"11",outcome,status,new BigDecimal("800"),null,error,Instant.now(),"hold",null);
}