优化
This commit is contained in:
+3
-1
@@ -41,8 +41,10 @@ public class PlatformInboundFeeDetailController {
|
||||
public ApiResponse<List<InboundFeeDetailEntity>> list(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long accountId,
|
||||
@RequestParam(defaultValue = "false") boolean includeHistory,
|
||||
@PathVariable long inboundId) {
|
||||
return ApiResponse.success(service.list(FirstlegScopeGuard.platform(user, accountId), inboundId));
|
||||
return ApiResponse.success(service.list(
|
||||
FirstlegScopeGuard.platform(user, accountId), inboundId, includeHistory));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
||||
@@ -12,9 +12,19 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_SPLIT', measure_review_status = 'APPROVED',
|
||||
actual_gross_weight_kg = (SELECT COALESCE(SUM(item.total_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
actual_gross_weight_kg = CASE
|
||||
WHEN EXISTS (SELECT 1
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED')
|
||||
THEN (SELECT COALESCE(SUM(weight.net_weight_kg), 0)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
AND weight.deleted = 0 AND weight.recognition_status = 'CONFIRMED')
|
||||
ELSE (SELECT COALESCE(SUM(item.total_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId})
|
||||
END,
|
||||
actual_volume_cbm = (SELECT COALESCE(SUM(item.total_volume_cbm), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface LoadingCompletedEventMapper {
|
||||
JOIN fl_inbound_order o ON o.id = i.inbound_id AND o.tenant_id = i.account_id AND o.deleted = 0
|
||||
LEFT JOIN fl_inbound_order root_order ON root_order.id = COALESCE(o.root_inbound_id, o.id)
|
||||
WHERE p.id = #{planId} AND p.plan_status = 'COMPLETED'
|
||||
AND o.process_status = 'LOADED'
|
||||
AND i.package_count > 0
|
||||
ORDER BY i.account_id, i.inbound_id, i.inbound_detail_id, i.id
|
||||
""")
|
||||
|
||||
@@ -98,11 +98,13 @@ public class FeeItemRepository {
|
||||
.eq(InboundOrderEntity::getId, inboundId));
|
||||
}
|
||||
|
||||
public List<InboundFeeDetailEntity> listDetails(long tenantId, long inboundId) {
|
||||
public List<InboundFeeDetailEntity> listDetails(long tenantId, long inboundId, boolean includeHistory) {
|
||||
return feeDetails.selectList(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.ne(!includeHistory, InboundFeeDetailEntity::getPublicationStatus, "SUPERSEDED")
|
||||
.orderByDesc(InboundFeeDetailEntity::getBusinessDate)
|
||||
.orderByDesc(InboundFeeDetailEntity::getSourceFeeVersion)
|
||||
.orderByDesc(InboundFeeDetailEntity::getId));
|
||||
}
|
||||
|
||||
@@ -223,13 +225,20 @@ public class FeeItemRepository {
|
||||
.set(InboundFeeDetailEntity::getUpdatedBy, reviewerId));
|
||||
}
|
||||
|
||||
public int adjustApproved(InboundFeeDetailEntity detail, int expectedSourceVersion) {
|
||||
return feeDetails.update(detail, new LambdaUpdateWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, detail.getTenantId())
|
||||
.eq(InboundFeeDetailEntity::getInboundId, detail.getInboundId())
|
||||
.eq(InboundFeeDetailEntity::getId, detail.getId())
|
||||
public int supersedeApprovedManual(long tenantId, long inboundId, long detailId,
|
||||
int expectedSourceVersion, long operatorId,
|
||||
LocalDateTime now) {
|
||||
return feeDetails.update(null, new LambdaUpdateWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.eq(InboundFeeDetailEntity::getId, detailId)
|
||||
.eq(InboundFeeDetailEntity::getSourceType, "MANUAL")
|
||||
.eq(InboundFeeDetailEntity::getReviewStatus, "APPROVED")
|
||||
.eq(InboundFeeDetailEntity::getSourceFeeVersion, expectedSourceVersion));
|
||||
.eq(InboundFeeDetailEntity::getSourceFeeVersion, expectedSourceVersion)
|
||||
.ne(InboundFeeDetailEntity::getPublicationStatus, "SUPERSEDED")
|
||||
.set(InboundFeeDetailEntity::getPublicationStatus, "SUPERSEDED")
|
||||
.set(InboundFeeDetailEntity::getUpdatedBy, operatorId)
|
||||
.set(InboundFeeDetailEntity::getUpdatedAt, now));
|
||||
}
|
||||
|
||||
public int voidApproved(long tenantId, long inboundId, long detailId,
|
||||
|
||||
@@ -30,9 +30,24 @@ public class InboundFeeDetailService {
|
||||
@Resource
|
||||
private FinanceFeeSourceOutboxService financeOutbox;
|
||||
|
||||
public List<InboundFeeDetailEntity> list(long tenantId, long inboundId) {
|
||||
@Transactional
|
||||
public List<InboundFeeDetailEntity> list(long tenantId, long inboundId, boolean includeHistory) {
|
||||
requiredInbound(tenantId, inboundId);
|
||||
return repository.listDetails(tenantId, inboundId);
|
||||
materializeLatestFreightIfMissing(tenantId, inboundId);
|
||||
return repository.listDetails(tenantId, inboundId, includeHistory);
|
||||
}
|
||||
|
||||
private void materializeLatestFreightIfMissing(long tenantId, long inboundId) {
|
||||
InboundFeeEntity latest = pricingRepository.findLatestFee(tenantId, inboundId);
|
||||
if (latest == null || repository.findBySourceFee(
|
||||
tenantId, "FBA_PRICING", latest.getId()) != null) return;
|
||||
LocalDateTime occurredAt = latest.getSentAt() != null ? latest.getSentAt()
|
||||
: latest.getCalculatedAt();
|
||||
if ("SENT".equals(latest.getFeeStatus())) {
|
||||
publishFreight(tenantId, inboundId, latest.getId(), 0L, "SYSTEM", occurredAt);
|
||||
} else if ("CALCULATED".equals(latest.getFeeStatus())) {
|
||||
materializeCalculatedFreight(tenantId, inboundId, latest.getId(), 0L, occurredAt);
|
||||
}
|
||||
}
|
||||
|
||||
public InboundFeeDetailEntity publishedFreight(long tenantId, long inboundId) {
|
||||
@@ -147,8 +162,15 @@ public class InboundFeeDetailService {
|
||||
repository.supersedeFreight(tenantId, inboundId, feeId, operatorId, now);
|
||||
InboundFeeDetailEntity detail = freightDetail(
|
||||
tenantId, inboundId, operatorId, null, now, inbound, fee, requiredFreightItem(), false);
|
||||
repository.insertDetail(detail);
|
||||
return detail;
|
||||
try {
|
||||
repository.insertDetail(detail);
|
||||
return detail;
|
||||
} catch (DuplicateKeyException exception) {
|
||||
InboundFeeDetailEntity concurrent = repository.findBySourceFee(
|
||||
tenantId, "FBA_PRICING", feeId);
|
||||
if (concurrent != null) return concurrent;
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private InboundFeeDetailEntity freightDetail(long tenantId, long inboundId, long operatorId,
|
||||
@@ -230,7 +252,7 @@ public class InboundFeeDetailService {
|
||||
@Transactional
|
||||
public void approve(long tenantId, long inboundId, long detailId,
|
||||
long reviewerId, String reviewerName) {
|
||||
required(tenantId, inboundId, detailId);
|
||||
requiredManual(tenantId, inboundId, detailId);
|
||||
if (repository.review(tenantId, inboundId, detailId, "APPROVED",
|
||||
reviewerId, reviewerName, null) != 1) {
|
||||
throw conflict("只有待审核费用明细可以审核通过");
|
||||
@@ -243,7 +265,7 @@ public class InboundFeeDetailService {
|
||||
public InboundFeeDetailEntity adjust(long tenantId, long inboundId, long detailId,
|
||||
int expectedSourceVersion, long operatorId,
|
||||
SaveRequest request) {
|
||||
InboundFeeDetailEntity value = required(tenantId, inboundId, detailId);
|
||||
InboundFeeDetailEntity value = requiredManual(tenantId, inboundId, detailId);
|
||||
if (!"APPROVED".equals(value.getReviewStatus()) || "VOIDED".equals(value.getPublicationStatus())) {
|
||||
throw conflict("只有审核通过且未作废的费用明细可以调整");
|
||||
}
|
||||
@@ -252,25 +274,30 @@ public class InboundFeeDetailService {
|
||||
}
|
||||
InboundOrderEntity inbound = requiredInbound(tenantId, inboundId);
|
||||
FeeItemEntity item = requiredActiveItem(request.feeItemId());
|
||||
apply(value, inbound, item, request, operatorId);
|
||||
value.setSourceType("MANUAL");
|
||||
value.setSourceFeeVersion(expectedSourceVersion + 1);
|
||||
value.setReviewStatus("PENDING");
|
||||
value.setReviewerId(null);
|
||||
value.setReviewerName(null);
|
||||
value.setReviewedAt(null);
|
||||
value.setReviewReason(null);
|
||||
value.setPublicationStatus("DRAFT");
|
||||
if (repository.adjustApproved(value, expectedSourceVersion) != 1) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (repository.supersedeApprovedManual(tenantId, inboundId, detailId,
|
||||
expectedSourceVersion, operatorId, now) != 1) {
|
||||
throw conflict("费用明细已被其他人调整,请刷新后重试");
|
||||
}
|
||||
return required(tenantId, inboundId, detailId);
|
||||
InboundFeeDetailEntity next = new InboundFeeDetailEntity();
|
||||
next.setTenantId(tenantId);
|
||||
next.setInboundId(inboundId);
|
||||
next.setSourceType("MANUAL");
|
||||
next.setSourceFeeId(value.getSourceFeeId() == null ? value.getId() : value.getSourceFeeId());
|
||||
next.setSourceFeeVersion(expectedSourceVersion + 1);
|
||||
next.setSourceFeeNo(value.getSourceFeeNo());
|
||||
next.setCreatedBy(operatorId);
|
||||
apply(next, inbound, item, request, operatorId);
|
||||
next.setReviewStatus("PENDING");
|
||||
next.setPublicationStatus("DRAFT");
|
||||
repository.insertDetail(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void voidApproved(long tenantId, long inboundId, long detailId,
|
||||
int expectedSourceVersion, long operatorId) {
|
||||
InboundFeeDetailEntity current = required(tenantId, inboundId, detailId);
|
||||
InboundFeeDetailEntity current = requiredManual(tenantId, inboundId, detailId);
|
||||
if (repository.voidApproved(tenantId, inboundId, detailId, expectedSourceVersion, operatorId) != 1) {
|
||||
throw conflict("只有当前版本的已审核费用明细可以作废");
|
||||
}
|
||||
@@ -283,7 +310,7 @@ public class InboundFeeDetailService {
|
||||
@Transactional
|
||||
public void reject(long tenantId, long inboundId, long detailId, long reviewerId,
|
||||
String reviewerName, String reason) {
|
||||
required(tenantId, inboundId, detailId);
|
||||
requiredManual(tenantId, inboundId, detailId);
|
||||
if (repository.review(tenantId, inboundId, detailId, "REJECTED",
|
||||
reviewerId, reviewerName, reason.trim()) != 1) {
|
||||
throw conflict("只有待审核费用明细可以驳回");
|
||||
@@ -333,11 +360,19 @@ public class InboundFeeDetailService {
|
||||
}
|
||||
|
||||
private InboundFeeDetailEntity requiredEditable(long tenantId, long inboundId, long detailId) {
|
||||
InboundFeeDetailEntity value = required(tenantId, inboundId, detailId);
|
||||
InboundFeeDetailEntity value = requiredManual(tenantId, inboundId, detailId);
|
||||
if ("APPROVED".equals(value.getReviewStatus())) throw conflict("审核通过的费用明细不能修改或删除");
|
||||
return value;
|
||||
}
|
||||
|
||||
private InboundFeeDetailEntity requiredManual(long tenantId, long inboundId, long detailId) {
|
||||
InboundFeeDetailEntity value = required(tenantId, inboundId, detailId);
|
||||
if ("FBA_PRICING".equals(value.getSourceType())) {
|
||||
throw conflict("自动头程运费明细为只读派生,请在头程费用中调整或发布");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void apply(InboundFeeDetailEntity value, InboundOrderEntity inbound,
|
||||
FeeItemEntity item, SaveRequest request, long operatorId) {
|
||||
ensureWarehouseApplicable(item, request.direction());
|
||||
@@ -380,7 +415,8 @@ public class InboundFeeDetailService {
|
||||
String eventType = "VOIDED".equals(status) ? FinanceFeeSourceOutboxService.VOIDED
|
||||
: version > 1 ? FinanceFeeSourceOutboxService.ADJUSTED
|
||||
: FinanceFeeSourceOutboxService.PUBLISHED;
|
||||
long sourceId = detail.getSourceFeeId() == null ? detail.getId() : detail.getSourceFeeId();
|
||||
financeOutbox.publishInbound(eventType, detail.getTenantId(), detail.getInboundId(),
|
||||
detail.getId(), version, occurredAt);
|
||||
sourceId, version, occurredAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +508,14 @@ public class LoadingListService {
|
||||
json(values("source", "PDA", "pickTaskId", pickTaskId, "status", "COMPLETED",
|
||||
"version", before.getVersion() + 1)), operatorId, trim(operatorName),
|
||||
"PDA已人工确认装柜完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the cross-region handover only after every inbound projection has reached LOADED.
|
||||
* The PDA completion orchestrator invokes this after finalizing all fulfillment slices.
|
||||
*/
|
||||
@Transactional
|
||||
public void publishCompletedEvent(long planId) {
|
||||
loadingCompletedOutboxService.publish(planId);
|
||||
}
|
||||
|
||||
|
||||
@@ -472,6 +472,7 @@ public class LoadingPickService {
|
||||
loadingDeliveryService.completeAfterPdaSettlement(owner.tenantId(), owner.inboundId(), operatorId,
|
||||
operatorName, "PDA-" + operationId + '-' + owner.inboundId(), remark);
|
||||
}
|
||||
loadingListService.publishCompletedEvent(task.getLoadingPlanId());
|
||||
return view(requiredTask(taskId), null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user