This commit is contained in:
2026-08-26 13:58:33 +08:00
parent fc0a6ea7ca
commit 4398e8b347
78 changed files with 920 additions and 1549 deletions
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.client;
import com.baiyue.common.core.api.ApiResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@@ -29,6 +30,12 @@ public interface LoadingDocumentFileClient {
ResponseEntity<byte[]> download(@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable long tenantId,@PathVariable String businessType,@PathVariable String businessId,@PathVariable long fileId);
@DeleteMapping("/{tenantId}/{businessType}/{businessId}/{fileId}")
ApiResponse<Void> delete(@RequestHeader("X-Internal-Service-Token") String token,
@RequestHeader("X-Operator-Id") long operatorId,
@PathVariable long tenantId, @PathVariable String businessType,
@PathVariable String businessId, @PathVariable long fileId);
record Metadata(Long fileId, Long tenantId, String businessType, String businessId,
String fileName, String sha256, String contentType, Long sizeBytes) {}
}
@@ -2,4 +2,10 @@ package com.baiyue.firstleg.customs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.firstleg.customs.entity.CustomsCaseEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface CustomsCaseMapper extends BaseMapper<CustomsCaseEntity> {}
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper public interface CustomsCaseMapper extends BaseMapper<CustomsCaseEntity> {
@Select("SELECT COUNT(*) FROM fl_container_document WHERE container_booking_id=#{containerBookingId} "
+ "AND document_type='CUSTOMS_DECLARATION' AND completed_at IS NOT NULL")
int completedLoadingDeclaration(@Param("containerBookingId") long containerBookingId);
}
@@ -81,11 +81,8 @@ public class CustomsRepository {
.eq(CustomsCaseEntity::getContainerBookingId, containerBookingId)
.orderByDesc(CustomsCaseEntity::getUpdatedAt).last("LIMIT 1"));
}
public CustomsCaseEntity clearedContainerCase(long containerBookingId) {
return cases.selectOne(new LambdaQueryWrapper<CustomsCaseEntity>()
.eq(CustomsCaseEntity::getContainerBookingId, containerBookingId)
.eq(CustomsCaseEntity::getStatus, "CLEARANCE_COMPLETED")
.orderByDesc(CustomsCaseEntity::getUpdatedAt).last("LIMIT 1"));
public boolean loadingDeclarationCompleted(long containerBookingId) {
return cases.completedLoadingDeclaration(containerBookingId) == 1;
}
public boolean containerCarriesOrder(long containerId, long tenantId, long orderId) {
LoadingPlanEntity plan = loadingPlans.selectOne(new LambdaQueryWrapper<LoadingPlanEntity>()
@@ -1,6 +1,5 @@
package com.baiyue.firstleg.customs.service;
import com.baiyue.firstleg.customs.entity.CustomsCaseEntity;
import com.baiyue.firstleg.customs.repository.CustomsRepository;
import com.baiyue.firstleg.outbound.dispatch.dto.OutboundDispatchDtos.GateItem;
import com.baiyue.firstleg.outbound.dispatch.gate.OutboundDispatchGateContributor;
@@ -18,12 +17,11 @@ public class CustomsDispatchGateContributor implements OutboundDispatchGateContr
if (context.tenantId() <= 0 || context.customerId() <= 0) {
return gate(false, "INVALID_CUSTOMER_SCOPE");
}
CustomsCaseEntity value = repository.clearedContainerCase(context.containerBookingId());
if (value == null) return gate(false, "MISSING");
return gate("CLEARANCE_COMPLETED".equals(value.getStatus()), value.getStatus());
boolean completed = repository.loadingDeclarationCompleted(context.containerBookingId());
return gate(completed, completed ? "COMPLETED" : "MISSING");
}
private static GateItem gate(boolean passed, String actual) {
return new GateItem("CUSTOMS_CLEARANCE", "报关完成", passed, actual, "CLEARANCE_COMPLETED");
return new GateItem("CUSTOMS_CLEARANCE", "报关完成", passed, actual, "COMPLETED");
}
}
@@ -106,9 +106,13 @@ public class LoadingListRepository {
String fromStatus, String toStatus, String beforeSnapshot,
String afterSnapshot, long operatorId, String operatorName,
String operationReason) {
mapper.insertHistory(new HistoryWrite(IdWorker.getId(), planId, operationId, actionType,
int changed = mapper.insertHistory(new HistoryWrite(IdWorker.getId(), planId, operationId, actionType,
fromStatus, toStatus, beforeSnapshot, afterSnapshot, operatorId, operatorName,
operationReason));
if (changed != 1) {
throw new com.baiyue.common.core.exception.BizException(
com.baiyue.common.core.api.ResultCode.CONFLICT, "装柜历史已变化,请刷新后重试");
}
}
private Map<Long, List<Customer>> customerMap(List<AggregateRow> rows) {
@@ -31,12 +31,15 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import feign.FeignException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -49,6 +52,7 @@ import java.util.Set;
@Service
public class LoadingListService {
private static final Logger log = LoggerFactory.getLogger(LoadingListService.class);
private static final Set<String> PRELOAD_STATUSES = Set.of("EDITING", "LOADING");
public static final String DESTINATION_CLEARANCE = "DESTINATION_CLEARANCE";
public static final String CUSTOMS_DECLARATION = "CUSTOMS_DECLARATION";
@@ -317,8 +321,16 @@ public class LoadingListService {
requireReplaceableDocument(current, expectedVersion);
LoadingDocumentFileClient.Metadata metadata = uploadDocumentFile(
businessType, businessId, operatorId, file, fileHash);
return persistDocument(plan, documentType, current, metadata, operatorId, operatorName,
operationId, "UPLOAD_" + documentType, requestHash, operationReason);
try {
return persistDocument(plan, documentType, current, metadata, operatorId, operatorName,
operationId, "UPLOAD_" + documentType, requestHash, operationReason);
} catch (RuntimeException exception) {
compensateUploadedFile(businessType, businessId, metadata.fileId(), operatorId);
if (exception instanceof DataIntegrityViolationException) {
throw conflict("资料状态或数据库约束已变化,请刷新后重试");
}
throw exception;
}
}
private BusinessDocument persistDocument(AggregateRow plan, String documentType,
@@ -337,12 +349,18 @@ public class LoadingListService {
if (changed != 1) throw conflict("资料版本已变化,请刷新后重试");
} catch (DuplicateKeyException exception) {
throw conflict("资料已被其他操作上传,请刷新后重试");
} catch (DataIntegrityViolationException exception) {
throw conflict("资料状态或数据库约束已变化,请刷新后重试");
}
ContainerDocumentEntity saved = requireDocumentEntity(plan.getContainerBookingId(), documentType);
repository.appendHistory(plan.getPlanId(), operationId, actionType,
plan.getPlanStatus(), plan.getPlanStatus(), json(beforeSnapshot),
json(values("requestHash", requestHash, "document", documentSnapshot(saved))),
operatorId, trim(operatorName), trim(operationReason));
try {
repository.appendHistory(plan.getPlanId(), operationId, actionType,
plan.getPlanStatus(), plan.getPlanStatus(), json(beforeSnapshot),
json(values("requestHash", requestHash, "document", documentSnapshot(saved))),
operatorId, trim(operatorName), trim(operationReason));
} catch (DataIntegrityViolationException exception) {
throw conflict("资料历史约束已变化,请刷新后重试");
}
return document(saved);
}
@@ -414,6 +432,19 @@ public class LoadingListService {
}
}
private void compensateUploadedFile(String businessType, String businessId, long fileId, long operatorId) {
try {
var response = documentFiles.delete(internalToken, operatorId, 0, businessType, businessId, fileId);
if (response == null || !response.successful()) {
log.error("Business file compensation failed: type={}, businessId={}, fileId={}",
businessType, businessId, fileId);
}
} catch (RuntimeException exception) {
log.error("Business file compensation request failed: type={}, businessId={}, fileId={}",
businessType, businessId, fileId, exception);
}
}
private static void requireReplaceableDocument(ContainerDocumentEntity current, int expectedVersion) {
if (current == null && expectedVersion != 0) throw conflict("资料版本已变化,请刷新后重试");
if (current != null && !java.util.Objects.equals(current.getVersion(), expectedVersion)) {