fix(firstleg): publish fees by container booking
This commit is contained in:
+2
-2
@@ -40,9 +40,9 @@ public class ContainerFeeDetailRepository {
|
|||||||
.eq(ContainerFeeDetailEntity::getId, feeId));
|
.eq(ContainerFeeDetailEntity::getId, feeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContainerFeeDetailEntity find(long loadingPlanId, long feeId) {
|
public ContainerFeeDetailEntity findByContainerBookingId(long containerBookingId, long feeId) {
|
||||||
return mapper.selectOne(new LambdaQueryWrapper<ContainerFeeDetailEntity>()
|
return mapper.selectOne(new LambdaQueryWrapper<ContainerFeeDetailEntity>()
|
||||||
.eq(ContainerFeeDetailEntity::getLoadingPlanId, loadingPlanId)
|
.eq(ContainerFeeDetailEntity::getContainerBookingId, containerBookingId)
|
||||||
.eq(ContainerFeeDetailEntity::getId, feeId));
|
.eq(ContainerFeeDetailEntity::getId, feeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -49,10 +49,10 @@ public class ContainerFeeSourceSnapshotRepository {
|
|||||||
mapper.insert(value);
|
mapper.insert(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContainerFeeSourceSnapshotEntity find(long loadingPlanId, long sourceFeeId,
|
public ContainerFeeSourceSnapshotEntity findByContainerBookingId(long containerBookingId, long sourceFeeId,
|
||||||
int sourceVersion) {
|
int sourceVersion) {
|
||||||
return mapper.selectOne(new LambdaQueryWrapper<ContainerFeeSourceSnapshotEntity>()
|
return mapper.selectOne(new LambdaQueryWrapper<ContainerFeeSourceSnapshotEntity>()
|
||||||
.eq(ContainerFeeSourceSnapshotEntity::getLoadingPlanId, loadingPlanId)
|
.eq(ContainerFeeSourceSnapshotEntity::getContainerBookingId, containerBookingId)
|
||||||
.eq(ContainerFeeSourceSnapshotEntity::getSourceFeeId, sourceFeeId)
|
.eq(ContainerFeeSourceSnapshotEntity::getSourceFeeId, sourceFeeId)
|
||||||
.eq(ContainerFeeSourceSnapshotEntity::getSourceVersion, sourceVersion));
|
.eq(ContainerFeeSourceSnapshotEntity::getSourceVersion, sourceVersion));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ public class ContainerFeeDetailService {
|
|||||||
sourceSnapshots.saveApproved(approved);
|
sourceSnapshots.saveApproved(approved);
|
||||||
String eventType = approved.getSourceVersion() > 1
|
String eventType = approved.getSourceVersion() > 1
|
||||||
? FinanceFeeSourceOutboxService.ADJUSTED : FinanceFeeSourceOutboxService.PUBLISHED;
|
? FinanceFeeSourceOutboxService.ADJUSTED : FinanceFeeSourceOutboxService.PUBLISHED;
|
||||||
financeOutbox.publishContainer(eventType, tenantId, loadingPlanId, feeId,
|
financeOutbox.publishContainer(eventType, tenantId, containerBookingId(approved), feeId,
|
||||||
approved.getSourceVersion(), LocalDateTime.now());
|
approved.getSourceVersion(), LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ public class ContainerFeeDetailService {
|
|||||||
throw conflict("只有当前版本的已审核柜次费用可以作废");
|
throw conflict("只有当前版本的已审核柜次费用可以作废");
|
||||||
}
|
}
|
||||||
financeOutbox.publishContainer(FinanceFeeSourceOutboxService.VOIDED, tenantId,
|
financeOutbox.publishContainer(FinanceFeeSourceOutboxService.VOIDED, tenantId,
|
||||||
loadingPlanId, feeId, expectedVersion, LocalDateTime.now());
|
containerBookingId(current), feeId, expectedVersion, LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlanContext requireOwnedPlan(long tenantId, long loadingPlanId) {
|
private PlanContext requireOwnedPlan(long tenantId, long loadingPlanId) {
|
||||||
@@ -372,6 +372,13 @@ public class ContainerFeeDetailService {
|
|||||||
return new BizException(ResultCode.CONFLICT, message);
|
return new BizException(ResultCode.CONFLICT, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long containerBookingId(ContainerFeeDetailEntity value) {
|
||||||
|
if (value.getContainerBookingId() == null || value.getContainerBookingId() <= 0) {
|
||||||
|
throw conflict("柜次费用缺少真实货柜关联");
|
||||||
|
}
|
||||||
|
return value.getContainerBookingId();
|
||||||
|
}
|
||||||
|
|
||||||
private record PlanContext(LoadingPlanEntity plan, ContainerBookingEntity booking) {
|
private record PlanContext(LoadingPlanEntity plan, ContainerBookingEntity booking) {
|
||||||
}
|
}
|
||||||
private static String text(String value) { return value == null || value.isBlank() ? null : value.trim(); }
|
private static String text(String value) { return value == null || value.isBlank() ? null : value.trim(); }
|
||||||
|
|||||||
+4
-3
@@ -123,9 +123,10 @@ public class FinanceFeeSourceCommandService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void voidContainer(VoidRequest request) {
|
private void voidContainer(VoidRequest request) {
|
||||||
long containerId = positive(request.containerId(), "柜次ID");
|
long containerBookingId = positive(request.containerId(), "柜次ID");
|
||||||
if (request.inboundId() != null) throw bad("柜次费用不能携带入库单ID");
|
if (request.inboundId() != null) throw bad("柜次费用不能携带入库单ID");
|
||||||
ContainerFeeDetailEntity detail = containerFees.find(containerId, request.sourceFeeId());
|
ContainerFeeDetailEntity detail = containerFees.findByContainerBookingId(
|
||||||
|
containerBookingId, request.sourceFeeId());
|
||||||
if (detail == null || !Long.valueOf(request.customerId()).equals(detail.getTenantId())) {
|
if (detail == null || !Long.valueOf(request.customerId()).equals(detail.getTenantId())) {
|
||||||
throw new BizException(ResultCode.NOT_FOUND, "指定客户的柜次费用来源版本不存在");
|
throw new BizException(ResultCode.NOT_FOUND, "指定客户的柜次费用来源版本不存在");
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ public class FinanceFeeSourceCommandService {
|
|||||||
throw conflict("柜次费用来源版本已变化");
|
throw conflict("柜次费用来源版本已变化");
|
||||||
}
|
}
|
||||||
if ("VOIDED".equals(detail.getSourceStatus())) return;
|
if ("VOIDED".equals(detail.getSourceStatus())) return;
|
||||||
containerFeeService.voidApproved(request.customerId(), containerId,
|
containerFeeService.voidApproved(request.customerId(), detail.getLoadingPlanId(),
|
||||||
request.sourceFeeId(), request.sourceVersion(), request.operatorId());
|
request.sourceFeeId(), request.sourceVersion(), request.operatorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -36,11 +36,11 @@ public class FinanceFeeSourceOutboxService {
|
|||||||
payload, occurredAt));
|
payload, occurredAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publishContainer(String eventType, long tenantId, long containerId,
|
public void publishContainer(String eventType, long tenantId, long containerBookingId,
|
||||||
long feeId, int feeVersion, LocalDateTime occurredAt) {
|
long feeId, int feeVersion, LocalDateTime occurredAt) {
|
||||||
FinanceFeeSourceEvent.Payload payload = new FinanceFeeSourceEvent.Payload(
|
FinanceFeeSourceEvent.Payload payload = new FinanceFeeSourceEvent.Payload(
|
||||||
null, String.valueOf(containerId), String.valueOf(feeId), feeVersion);
|
null, String.valueOf(containerBookingId), String.valueOf(feeId), feeVersion);
|
||||||
publish(event(eventType, tenantId, "CONTAINER", containerId, feeId, feeVersion,
|
publish(event(eventType, tenantId, "CONTAINER", containerBookingId, feeId, feeVersion,
|
||||||
payload, occurredAt));
|
payload, occurredAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -58,12 +58,13 @@ public class FinanceFeeSourceSnapshotService {
|
|||||||
detail.getActualGrossWeightKg(), detail.getActualVolumeCbm(), sha256(canonical), 1);
|
detail.getActualGrossWeightKg(), detail.getActualVolumeCbm(), sha256(canonical), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FinanceFeeSourceSnapshot container(long containerId, long feeId, int sourceVersion) {
|
public FinanceFeeSourceSnapshot container(long containerBookingId, long feeId, int sourceVersion) {
|
||||||
ContainerFeeSourceSnapshotEntity detail = containerSnapshots.find(containerId, feeId, sourceVersion);
|
ContainerFeeSourceSnapshotEntity detail = containerSnapshots.findByContainerBookingId(
|
||||||
|
containerBookingId, feeId, sourceVersion);
|
||||||
if (detail == null) throw new BizException(ResultCode.NOT_FOUND, "柜次费用来源不存在");
|
if (detail == null) throw new BizException(ResultCode.NOT_FOUND, "柜次费用来源不存在");
|
||||||
int version = detail.getSourceVersion();
|
int version = detail.getSourceVersion();
|
||||||
String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId),
|
String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId),
|
||||||
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerId),
|
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerBookingId),
|
||||||
nullSafe(detail.getFeeItemCode()), nullSafe(detail.getDirection()),
|
nullSafe(detail.getFeeItemCode()), nullSafe(detail.getDirection()),
|
||||||
nullSafe(detail.getSupplierId()), nullSafe(detail.getSupplierName()),
|
nullSafe(detail.getSupplierId()), nullSafe(detail.getSupplierName()),
|
||||||
nullSafe(detail.getSupplierCategory()),
|
nullSafe(detail.getSupplierCategory()),
|
||||||
@@ -76,7 +77,8 @@ public class FinanceFeeSourceSnapshotService {
|
|||||||
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
||||||
nullSafe(detail.getReviewStatus()));
|
nullSafe(detail.getReviewStatus()));
|
||||||
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
||||||
version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), detail.getAllocationOrderId(), containerId, detail.getContainerBookingId(),
|
version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), detail.getAllocationOrderId(),
|
||||||
|
containerBookingId, containerBookingId,
|
||||||
detail.getContainerNo(), detail.getTenantId(), null, detail.getSupplierId(),
|
detail.getContainerNo(), detail.getTenantId(), null, detail.getSupplierId(),
|
||||||
detail.getSupplierName(), detail.getSupplierCategory(), detail.getSettlementMode(),
|
detail.getSupplierName(), detail.getSupplierCategory(), detail.getSettlementMode(),
|
||||||
detail.getPaidAt(), detail.getPaymentMethod(), detail.getPaymentReferenceNo(),
|
detail.getPaidAt(), detail.getPaymentMethod(), detail.getPaymentReferenceNo(),
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ paths:
|
|||||||
/internal/firstleg/v1/containers/{containerId}/fees/{feeId}/source-snapshot:
|
/internal/firstleg/v1/containers/{containerId}/fees/{feeId}/source-snapshot:
|
||||||
get:
|
get:
|
||||||
summary: Finance 按柜次费用来源版本精确回查不可变快照
|
summary: Finance 按柜次费用来源版本精确回查不可变快照
|
||||||
description: sourceVersion 必填;只返回 sourceFeeId 与该版本精确匹配的已发布费用,不回退到当前可变版本。
|
description: sourceVersion 必填;containerId 固定为真实 containerBookingId,只返回 sourceFeeId 与该版本精确匹配的已发布费用,不接受 loadingPlanId 回退。
|
||||||
security:
|
security:
|
||||||
- internalServiceToken: []
|
- internalServiceToken: []
|
||||||
parameters:
|
parameters:
|
||||||
@@ -2649,8 +2649,8 @@ components:
|
|||||||
inboundId: {type: integer, format: int64, nullable: true}
|
inboundId: {type: integer, format: int64, nullable: true}
|
||||||
inboundCode: {type: string, nullable: true}
|
inboundCode: {type: string, nullable: true}
|
||||||
orderId: {type: integer, format: int64, nullable: true}
|
orderId: {type: integer, format: int64, nullable: true}
|
||||||
containerId: {type: integer, format: int64, nullable: true}
|
containerId: {type: integer, format: int64, nullable: true, description: CONTAINER_FEE时固定为真实containerBookingId}
|
||||||
containerBookingId: {type: integer, format: int64, nullable: true}
|
containerBookingId: {type: integer, format: int64, nullable: true, description: 真实containerBookingId;CONTAINER_FEE时与containerId一致}
|
||||||
containerNo: {type: string, nullable: true}
|
containerNo: {type: string, nullable: true}
|
||||||
customerId: {type: integer, format: int64}
|
customerId: {type: integer, format: int64}
|
||||||
customerName: {type: string}
|
customerName: {type: string}
|
||||||
|
|||||||
+4
-1
@@ -75,6 +75,7 @@ class ContainerFeeDetailServiceTest {
|
|||||||
approved.setId(51L);
|
approved.setId(51L);
|
||||||
approved.setTenantId(10001L);
|
approved.setTenantId(10001L);
|
||||||
approved.setLoadingPlanId(41L);
|
approved.setLoadingPlanId(41L);
|
||||||
|
approved.setContainerBookingId(61L);
|
||||||
approved.setFeeItemId(11L);
|
approved.setFeeItemId(11L);
|
||||||
approved.setDirection("RECEIVABLE");
|
approved.setDirection("RECEIVABLE");
|
||||||
approved.setSourceVersion(1);
|
approved.setSourceVersion(1);
|
||||||
@@ -87,7 +88,7 @@ class ContainerFeeDetailServiceTest {
|
|||||||
service.approve(10001L, 41L, 51L, 9L, "admin");
|
service.approve(10001L, 41L, 51L, 9L, "admin");
|
||||||
|
|
||||||
verify(outbox).publishContainer(eq(FinanceFeeSourceOutboxService.PUBLISHED),
|
verify(outbox).publishContainer(eq(FinanceFeeSourceOutboxService.PUBLISHED),
|
||||||
eq(10001L), eq(41L), eq(51L), eq(1), any(LocalDateTime.class));
|
eq(10001L), eq(61L), eq(51L), eq(1), any(LocalDateTime.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -284,6 +285,7 @@ class ContainerFeeDetailServiceTest {
|
|||||||
private void ownedPlan() {
|
private void ownedPlan() {
|
||||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||||
plan.setId(41L);
|
plan.setId(41L);
|
||||||
|
plan.setContainerBookingId(61L);
|
||||||
when(loading.plan(41L)).thenReturn(plan);
|
when(loading.plan(41L)).thenReturn(plan);
|
||||||
LoadingPlanItemEntity item = new LoadingPlanItemEntity();
|
LoadingPlanItemEntity item = new LoadingPlanItemEntity();
|
||||||
item.setAccountId(10001L);
|
item.setAccountId(10001L);
|
||||||
@@ -316,6 +318,7 @@ class ContainerFeeDetailServiceTest {
|
|||||||
value.setId(51L);
|
value.setId(51L);
|
||||||
value.setTenantId(10001L);
|
value.setTenantId(10001L);
|
||||||
value.setLoadingPlanId(41L);
|
value.setLoadingPlanId(41L);
|
||||||
|
value.setContainerBookingId(61L);
|
||||||
value.setFeeItemId(11L);
|
value.setFeeItemId(11L);
|
||||||
value.setFeeItemCodeSnapshot("TRUCKING");
|
value.setFeeItemCodeSnapshot("TRUCKING");
|
||||||
value.setFeeItemNameSnapshot("拖车费");
|
value.setFeeItemNameSnapshot("拖车费");
|
||||||
|
|||||||
+3
-2
@@ -96,11 +96,12 @@ class FinanceFeeSourceCommandServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void routesContainerVoidThroughExistingBusinessService() throws Exception {
|
void routesContainerVoidThroughExistingBusinessService() throws Exception {
|
||||||
VoidRequest request = new VoidRequest("CONTAINER_FEE", 800L, 4, 9L,
|
VoidRequest request = new VoidRequest("CONTAINER_FEE", 800L, 4, 9L,
|
||||||
null, 200L, 71L, "finance", "柜次费用作废");
|
null, 61L, 71L, "finance", "柜次费用作废");
|
||||||
ContainerFeeDetailEntity detail = new ContainerFeeDetailEntity();
|
ContainerFeeDetailEntity detail = new ContainerFeeDetailEntity();
|
||||||
detail.setId(800L); detail.setTenantId(9L); detail.setLoadingPlanId(200L);
|
detail.setId(800L); detail.setTenantId(9L); detail.setLoadingPlanId(200L);
|
||||||
|
detail.setContainerBookingId(61L);
|
||||||
detail.setSourceVersion(4); detail.setSourceStatus("ACTIVE");
|
detail.setSourceVersion(4); detail.setSourceStatus("ACTIVE");
|
||||||
when(containerFees.find(200L, 800L)).thenReturn(detail);
|
when(containerFees.findByContainerBookingId(61L, 800L)).thenReturn(detail);
|
||||||
|
|
||||||
service.voidSource("void-container", hash(request), request);
|
service.voidSource("void-container", hash(request), request);
|
||||||
|
|
||||||
|
|||||||
+14
@@ -49,4 +49,18 @@ class FinanceFeeSourceOutboxServiceTest {
|
|||||||
assertThatCode(() -> service.publishContainer(FinanceFeeSourceOutboxService.VOIDED,
|
assertThatCode(() -> service.publishContainer(FinanceFeeSourceOutboxService.VOIDED,
|
||||||
10001L, 41L, 51L, 2, LocalDateTime.now())).doesNotThrowAnyException();
|
10001L, 41L, 51L, 2, LocalDateTime.now())).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containerEventUsesTheRealContainerBookingIdAsAggregateAndPayloadIdentity() {
|
||||||
|
service.publishContainer(FinanceFeeSourceOutboxService.PUBLISHED,
|
||||||
|
10001L, 61L, 51L, 2, LocalDateTime.of(2026, 8, 11, 12, 0));
|
||||||
|
|
||||||
|
ArgumentCaptor<InboundOutboxEntity> captor = ArgumentCaptor.forClass(InboundOutboxEntity.class);
|
||||||
|
verify(mapper).insert(captor.capture());
|
||||||
|
InboundOutboxEntity event = captor.getValue();
|
||||||
|
assertThat(event.getAggregateType()).isEqualTo("CONTAINER");
|
||||||
|
assertThat(event.getAggregateId()).isEqualTo(61L);
|
||||||
|
assertThat(event.getPayloadJson()).contains("\"containerId\":\"61\"")
|
||||||
|
.doesNotContain("\"containerId\":\"41\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -90,10 +90,12 @@ class FinanceFeeSourceSnapshotServiceTest {
|
|||||||
detail.setCurrency("CNY");
|
detail.setCurrency("CNY");
|
||||||
detail.setReviewStatus("APPROVED");
|
detail.setReviewStatus("APPROVED");
|
||||||
detail.setSourceVersion(2);
|
detail.setSourceVersion(2);
|
||||||
when(containerFees.find(41L, 51L, 2)).thenReturn(detail);
|
when(containerFees.findByContainerBookingId(61L, 51L, 2)).thenReturn(detail);
|
||||||
|
|
||||||
FinanceFeeSourceSnapshot snapshot = service.container(41L, 51L, 2);
|
FinanceFeeSourceSnapshot snapshot = service.container(61L, 51L, 2);
|
||||||
|
|
||||||
|
assertThat(snapshot.containerId()).isEqualTo(61L);
|
||||||
|
assertThat(snapshot.containerBookingId()).isEqualTo(61L);
|
||||||
assertThat(snapshot.supplierId()).isEqualTo(71L);
|
assertThat(snapshot.supplierId()).isEqualTo(71L);
|
||||||
assertThat(snapshot.supplierName()).isEqualTo("现有供应商");
|
assertThat(snapshot.supplierName()).isEqualTo("现有供应商");
|
||||||
assertThat(snapshot.supplierCategory()).isEqualTo("TRUCKING");
|
assertThat(snapshot.supplierCategory()).isEqualTo("TRUCKING");
|
||||||
|
|||||||
Reference in New Issue
Block a user