优化
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
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.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
/** Validates that a referenced File object belongs to the exact loading business resource. */
|
||||
@FeignClient(name = "file-service", contextId = "firstlegLoadingDocumentFileClient",
|
||||
url = "${baiyue.clients.file.url:}", path = "/internal/file/v1/business-files")
|
||||
public interface LoadingDocumentFileClient {
|
||||
@GetMapping("/{tenantId}/{businessType}/{businessId}/{fileId}")
|
||||
ApiResponse<Metadata> metadata(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@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) {}
|
||||
}
|
||||
+38
@@ -11,6 +11,10 @@ import com.baiyue.firstleg.dto.ContainerDtos.TransportUpdateRequest;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.service.ContainerService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.LoadingListService;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.BusinessDocument;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentAttachRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentCompleteRequest;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -25,6 +29,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
@RequestMapping("/api/platform/v1/containers")
|
||||
public class PlatformContainerController {
|
||||
@Resource private ContainerService service;
|
||||
@Resource private LoadingListService loadingLists;
|
||||
@GetMapping @PreAuthorize("hasAuthority('platform:firstleg:container:list')")
|
||||
public ApiResponse<PageResult<ContainerBookingEntity>> page(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid Query query) {
|
||||
@@ -60,4 +65,37 @@ public class PlatformContainerController {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.updateTransport(id, user.userId(), user.username(), request));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/clearance")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:container:view')")
|
||||
@Operation(summary = "查询货柜目的港清关资料")
|
||||
public ApiResponse<BusinessDocument> clearance(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long id) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(loadingLists.containerClearance(id));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/clearance/document")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
@Operation(summary = "上传或替换货柜目的港清关资料")
|
||||
public ApiResponse<BusinessDocument> attachClearance(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long id,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody DocumentAttachRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(loadingLists.attachContainerClearance(
|
||||
id, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/clearance/complete")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:container:edit')")
|
||||
@Operation(summary = "确认货柜目的港清关完成")
|
||||
public ApiResponse<BusinessDocument> completeClearance(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long id,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody DocumentCompleteRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(loadingLists.completeContainerClearance(
|
||||
id, user.userId(), user.username(), key, request));
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -10,6 +10,9 @@ import com.baiyue.firstleg.dto.LoadingListDtos.PreloadDetail;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadQuery;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadSummary;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.TransportUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.BusinessDocument;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentAttachRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentCompleteRequest;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.LoadingListService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -22,6 +25,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
@@ -89,4 +93,38 @@ public class PlatformLoadingListController {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.confirmedDetail(planId, warehouseId));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询装柜清单出口报关文档")
|
||||
@GetMapping("/loading-lists/{planId}/customs")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:loading-list:view')")
|
||||
public ApiResponse<BusinessDocument> declaration(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long planId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.loadingDeclaration(planId, warehouseId));
|
||||
}
|
||||
|
||||
@Operation(summary = "上传或替换装柜清单出口报关文档")
|
||||
@PostMapping("/loading-lists/{planId}/customs/document")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
public ApiResponse<BusinessDocument> attachDeclaration(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long planId,
|
||||
@RequestParam @Positive long warehouseId, @RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody DocumentAttachRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.attachLoadingDeclaration(
|
||||
planId, warehouseId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认装柜清单出口报关完成")
|
||||
@PostMapping("/loading-lists/{planId}/customs/complete")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
public ApiResponse<BusinessDocument> completeDeclaration(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long planId,
|
||||
@RequestParam @Positive long warehouseId, @RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody DocumentCompleteRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.completeLoadingDeclaration(
|
||||
planId, warehouseId, user.userId(), user.username(), key, request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.dto;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -57,6 +58,26 @@ public final class LoadingListDtos {
|
||||
) {
|
||||
}
|
||||
|
||||
public record DocumentAttachRequest(
|
||||
@NotNull @Positive Long fileId,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@Size(max = 500) String operationReason
|
||||
) {
|
||||
}
|
||||
|
||||
public record DocumentCompleteRequest(
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@Size(max = 500) String operationReason
|
||||
) {
|
||||
}
|
||||
|
||||
public record BusinessDocument(
|
||||
Long id, String documentType, Long fileId, String fileName, String fileHash,
|
||||
Long uploadedBy, String uploadedByName, LocalDateTime uploadedAt,
|
||||
Long completedBy, String completedByName, LocalDateTime completedAt, Integer version
|
||||
) {
|
||||
}
|
||||
|
||||
public record Customer(Long accountId, String customerName) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ContainerDocumentEntity {
|
||||
private Long id;
|
||||
private Long containerBookingId;
|
||||
private String documentType;
|
||||
private Long fileId;
|
||||
private String fileName;
|
||||
private String fileHash;
|
||||
private Long uploadedBy;
|
||||
private String uploadedByName;
|
||||
private LocalDateTime createdAt;
|
||||
private Long completedBy;
|
||||
private String completedByName;
|
||||
private LocalDateTime completedAt;
|
||||
private Integer version;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.mapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.ConfirmedQuery;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadQuery;
|
||||
import com.baiyue.firstleg.entity.ContainerDocumentEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
@@ -99,6 +100,59 @@ public interface LoadingListQueryMapper {
|
||||
""" + AGGREGATE_GROUP)
|
||||
AggregateRow selectAggregate(@Param("planId") long planId);
|
||||
|
||||
@Select("""
|
||||
SELECT
|
||||
""" + AGGREGATE_COLUMNS + """
|
||||
FROM fl_loading_plan p
|
||||
JOIN fl_container_booking b ON b.id = p.container_booking_id
|
||||
LEFT JOIN fl_loading_plan_item i ON i.loading_plan_id = p.id AND i.deleted = 0
|
||||
WHERE p.container_booking_id = #{bookingId}
|
||||
GROUP BY
|
||||
""" + AGGREGATE_GROUP)
|
||||
AggregateRow selectAggregateByBooking(@Param("bookingId") long bookingId);
|
||||
|
||||
@Select("""
|
||||
SELECT id, container_booking_id, document_type, file_id, file_name, file_hash,
|
||||
uploaded_by, uploaded_by_name, created_at, completed_by, completed_by_name,
|
||||
completed_at, version
|
||||
FROM fl_container_document
|
||||
WHERE container_booking_id = #{bookingId} AND document_type = #{documentType}
|
||||
""")
|
||||
ContainerDocumentEntity selectDocument(@Param("bookingId") long bookingId,
|
||||
@Param("documentType") String documentType);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_container_document
|
||||
(id, container_booking_id, document_type, file_id, file_name, file_hash,
|
||||
uploaded_by, uploaded_by_name, created_at, updated_at, version)
|
||||
VALUES
|
||||
(#{id}, #{containerBookingId}, #{documentType}, #{fileId}, #{fileName}, #{fileHash},
|
||||
#{uploadedBy}, #{uploadedByName}, CURRENT_TIMESTAMP(3), CURRENT_TIMESTAMP(3), 0)
|
||||
""")
|
||||
int insertDocument(ContainerDocumentEntity value);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_container_document
|
||||
SET file_id = #{fileId}, file_name = #{fileName}, file_hash = #{fileHash},
|
||||
uploaded_by = #{uploadedBy}, uploaded_by_name = #{uploadedByName},
|
||||
completed_by = NULL, completed_by_name = NULL, completed_at = NULL,
|
||||
updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||
WHERE id = #{id} AND version = #{version}
|
||||
""")
|
||||
int replaceDocument(ContainerDocumentEntity value);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_container_document
|
||||
SET completed_by = #{operatorId}, completed_by_name = #{operatorName},
|
||||
completed_at = CURRENT_TIMESTAMP(3), updated_at = CURRENT_TIMESTAMP(3),
|
||||
version = version + 1
|
||||
WHERE id = #{documentId} AND version = #{expectedVersion} AND completed_at IS NULL
|
||||
""")
|
||||
int completeDocument(@Param("documentId") long documentId,
|
||||
@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT id AS item_id, account_id, customer_name, inbound_id, inbound_detail_id,
|
||||
inbound_code, sku_code, destination_warehouse_name, product_name_zh,
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.baiyue.firstleg.dto.LoadingListDtos.Item;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadDetail;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadQuery;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadSummary;
|
||||
import com.baiyue.firstleg.entity.ContainerDocumentEntity;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.AggregateRow;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.CustomerRow;
|
||||
@@ -50,6 +51,14 @@ public class LoadingListRepository {
|
||||
return mapper.selectAggregate(planId);
|
||||
}
|
||||
|
||||
public AggregateRow aggregateByBooking(long bookingId) { return mapper.selectAggregateByBooking(bookingId); }
|
||||
public ContainerDocumentEntity document(long bookingId, String type) { return mapper.selectDocument(bookingId, type); }
|
||||
public int createDocument(ContainerDocumentEntity value) { return mapper.insertDocument(value); }
|
||||
public int replaceDocument(ContainerDocumentEntity value) { return mapper.replaceDocument(value); }
|
||||
public int completeDocument(long id, int version, long operatorId, String operatorName) {
|
||||
return mapper.completeDocument(id, version, operatorId, operatorName);
|
||||
}
|
||||
|
||||
public PreloadDetail preloadDetail(AggregateRow row) {
|
||||
List<ItemRow> items = mapper.selectItems(row.getPlanId());
|
||||
List<Customer> customers = items.stream()
|
||||
|
||||
@@ -11,6 +11,11 @@ import com.baiyue.firstleg.dto.LoadingListDtos.PreloadDetail;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadQuery;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.PreloadSummary;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.TransportUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.BusinessDocument;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentAttachRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingListDtos.DocumentCompleteRequest;
|
||||
import com.baiyue.firstleg.client.LoadingDocumentFileClient;
|
||||
import com.baiyue.firstleg.entity.ContainerDocumentEntity;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.AggregateRow;
|
||||
import com.baiyue.firstleg.mapper.LoadingListQueryMapper.HistoryRow;
|
||||
import com.baiyue.firstleg.repository.LoadingListRepository;
|
||||
@@ -18,6 +23,10 @@ import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -33,12 +42,20 @@ import java.util.Set;
|
||||
@Service
|
||||
public class LoadingListService {
|
||||
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";
|
||||
private static final String CLEARANCE_FILE_TYPE = "CONTAINER_CLEARANCE_DOCUMENT";
|
||||
private static final String DECLARATION_FILE_TYPE = "LOADING_CUSTOMS_DOCUMENT";
|
||||
@Resource
|
||||
private LoadingListRepository repository;
|
||||
@Resource
|
||||
private LoadingPickRepository pickRepository;
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
@Resource
|
||||
private LoadingDocumentFileClient documentFiles;
|
||||
@Value("${baiyue.security.internal-service-token:}")
|
||||
private String internalToken;
|
||||
|
||||
public PageResult<PreloadSummary> preloadPage(PreloadQuery query) {
|
||||
validateRange(query.createdFrom(), query.createdTo());
|
||||
@@ -74,6 +91,182 @@ public class LoadingListService {
|
||||
return repository.confirmedDetail(row);
|
||||
}
|
||||
|
||||
public BusinessDocument containerClearance(long bookingId) {
|
||||
requireBookingPlan(bookingId);
|
||||
return document(repository.document(bookingId, DESTINATION_CLEARANCE));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "CONTAINER_CLEARANCE_DOCUMENT_ATTACH", resourceType = "CONTAINER",
|
||||
resourceId = "#bookingId", description = "上传目的港清关资料")
|
||||
public BusinessDocument attachContainerClearance(long bookingId, long operatorId, String operatorName,
|
||||
String key, DocumentAttachRequest request) {
|
||||
AggregateRow plan = requireBookingPlan(bookingId);
|
||||
return attachDocument(plan, DESTINATION_CLEARANCE, CLEARANCE_FILE_TYPE,
|
||||
Long.toString(bookingId), operatorId, operatorName, key, request);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "CONTAINER_CLEARANCE_COMPLETE", resourceType = "CONTAINER",
|
||||
resourceId = "#bookingId", description = "确认目的港清关完成")
|
||||
public BusinessDocument completeContainerClearance(long bookingId, long operatorId, String operatorName,
|
||||
String key, DocumentCompleteRequest request) {
|
||||
return completeDocument(requireBookingPlan(bookingId), DESTINATION_CLEARANCE,
|
||||
operatorId, operatorName, key, request);
|
||||
}
|
||||
|
||||
public BusinessDocument loadingDeclaration(long planId, long warehouseId) {
|
||||
AggregateRow plan = requirePlan(planId);
|
||||
requireWarehouse(plan, warehouseId);
|
||||
requireCompletedPlan(plan);
|
||||
return document(repository.document(plan.getContainerBookingId(), CUSTOMS_DECLARATION));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "LOADING_CUSTOMS_DOCUMENT_ATTACH", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "上传出口报关文档")
|
||||
public BusinessDocument attachLoadingDeclaration(long planId, long warehouseId, long operatorId,
|
||||
String operatorName, String key,
|
||||
DocumentAttachRequest request) {
|
||||
AggregateRow plan = requirePlan(planId);
|
||||
requireWarehouse(plan, warehouseId);
|
||||
requireCompletedPlan(plan);
|
||||
return attachDocument(plan, CUSTOMS_DECLARATION, DECLARATION_FILE_TYPE,
|
||||
Long.toString(planId), operatorId, operatorName, key, request);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "LOADING_CUSTOMS_COMPLETE", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "确认出口报关完成")
|
||||
public BusinessDocument completeLoadingDeclaration(long planId, long warehouseId, long operatorId,
|
||||
String operatorName, String key,
|
||||
DocumentCompleteRequest request) {
|
||||
AggregateRow plan = requirePlan(planId);
|
||||
requireWarehouse(plan, warehouseId);
|
||||
requireCompletedPlan(plan);
|
||||
return completeDocument(plan, CUSTOMS_DECLARATION, operatorId, operatorName, key, request);
|
||||
}
|
||||
|
||||
private BusinessDocument attachDocument(AggregateRow plan, String documentType, String businessType,
|
||||
String businessId, long operatorId, String operatorName,
|
||||
String key, DocumentAttachRequest request) {
|
||||
String requestHash = sha256("ATTACH|" + documentType + '|' + plan.getPlanId() + '|'
|
||||
+ request.fileId() + '|' + request.expectedVersion() + '|' + trim(request.operationReason()));
|
||||
String operationId = operationId("ATTACH_" + documentType, plan.getPlanId(), key);
|
||||
HistoryRow replay = repository.history(operationId);
|
||||
if (replay != null) {
|
||||
requireSameRequest(replay, requestHash);
|
||||
return requireDocument(plan.getContainerBookingId(), documentType);
|
||||
}
|
||||
ContainerDocumentEntity current = repository.document(plan.getContainerBookingId(), documentType);
|
||||
if (current == null && request.expectedVersion() != 0) throw conflict("资料版本已变化,请刷新后重试");
|
||||
if (current != null && !java.util.Objects.equals(current.getVersion(), request.expectedVersion())) {
|
||||
throw conflict("资料版本已变化,请刷新后重试");
|
||||
}
|
||||
if (current != null && current.getCompletedAt() != null) throw conflict("已完成的资料不可替换");
|
||||
LoadingDocumentFileClient.Metadata metadata = validateDocumentFile(
|
||||
businessType, businessId, request.fileId());
|
||||
Map<String, Object> beforeSnapshot = documentSnapshot(current);
|
||||
ContainerDocumentEntity value = current == null ? new ContainerDocumentEntity() : current;
|
||||
if (current == null) value.setId(IdWorker.getId());
|
||||
value.setContainerBookingId(plan.getContainerBookingId()); value.setDocumentType(documentType);
|
||||
value.setFileId(metadata.fileId()); value.setFileName(metadata.fileName()); value.setFileHash(metadata.sha256());
|
||||
value.setUploadedBy(operatorId); value.setUploadedByName(trim(operatorName));
|
||||
try {
|
||||
int changed = current == null ? repository.createDocument(value) : repository.replaceDocument(value);
|
||||
if (changed != 1) throw conflict("资料版本已变化,请刷新后重试");
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("资料已被其他操作上传,请刷新后重试");
|
||||
}
|
||||
ContainerDocumentEntity saved = requireDocumentEntity(plan.getContainerBookingId(), documentType);
|
||||
repository.appendHistory(plan.getPlanId(), operationId, "ATTACH_" + documentType,
|
||||
plan.getPlanStatus(), plan.getPlanStatus(), json(beforeSnapshot),
|
||||
json(values("requestHash", requestHash, "document", documentSnapshot(saved))),
|
||||
operatorId, trim(operatorName), trim(request.operationReason()));
|
||||
return document(saved);
|
||||
}
|
||||
|
||||
private BusinessDocument completeDocument(AggregateRow plan, String documentType, long operatorId,
|
||||
String operatorName, String key,
|
||||
DocumentCompleteRequest request) {
|
||||
String requestHash = sha256("COMPLETE|" + documentType + '|' + plan.getPlanId() + '|'
|
||||
+ request.expectedVersion() + '|' + trim(request.operationReason()));
|
||||
String operationId = operationId("COMPLETE_" + documentType, plan.getPlanId(), key);
|
||||
HistoryRow replay = repository.history(operationId);
|
||||
if (replay != null) {
|
||||
requireSameRequest(replay, requestHash);
|
||||
return requireDocument(plan.getContainerBookingId(), documentType);
|
||||
}
|
||||
ContainerDocumentEntity current = requireDocumentEntity(plan.getContainerBookingId(), documentType);
|
||||
if (current.getCompletedAt() != null) {
|
||||
if (!java.util.Objects.equals(current.getVersion(), request.expectedVersion())) {
|
||||
throw conflict("资料版本已变化,请刷新后重试");
|
||||
}
|
||||
return document(current);
|
||||
}
|
||||
if (repository.completeDocument(current.getId(), request.expectedVersion(), operatorId,
|
||||
trim(operatorName)) != 1) throw conflict("资料版本已变化,请刷新后重试");
|
||||
ContainerDocumentEntity saved = requireDocumentEntity(plan.getContainerBookingId(), documentType);
|
||||
repository.appendHistory(plan.getPlanId(), operationId, "COMPLETE_" + documentType,
|
||||
plan.getPlanStatus(), plan.getPlanStatus(), json(documentSnapshot(current)),
|
||||
json(values("requestHash", requestHash, "document", documentSnapshot(saved))),
|
||||
operatorId, trim(operatorName), trim(request.operationReason()));
|
||||
return document(saved);
|
||||
}
|
||||
|
||||
private LoadingDocumentFileClient.Metadata validateDocumentFile(String businessType, String businessId,
|
||||
long fileId) {
|
||||
try {
|
||||
var response = documentFiles.metadata(internalToken, 0, businessType, businessId, fileId);
|
||||
if (response == null || !response.successful() || response.data() == null
|
||||
|| !Long.valueOf(fileId).equals(response.data().fileId())
|
||||
|| !Long.valueOf(0).equals(response.data().tenantId())
|
||||
|| !businessType.equals(response.data().businessType())
|
||||
|| !businessId.equals(response.data().businessId())
|
||||
|| !StringUtils.hasText(response.data().fileName())
|
||||
|| !StringUtils.hasText(response.data().sha256())) {
|
||||
throw badRequest("附件不存在、归属不匹配或文件元数据不完整");
|
||||
}
|
||||
return response.data();
|
||||
} catch (FeignException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "文件服务暂不可用");
|
||||
}
|
||||
}
|
||||
|
||||
private AggregateRow requireBookingPlan(long bookingId) {
|
||||
AggregateRow row = repository.aggregateByBooking(bookingId);
|
||||
if (row == null) throw notFound("货柜或关联装柜计划不存在");
|
||||
return row;
|
||||
}
|
||||
|
||||
private static void requireCompletedPlan(AggregateRow plan) {
|
||||
if (!"COMPLETED".equals(plan.getPlanStatus())) throw conflict("仅完成装柜清单可办理报关");
|
||||
}
|
||||
|
||||
private ContainerDocumentEntity requireDocumentEntity(long bookingId, String documentType) {
|
||||
ContainerDocumentEntity value = repository.document(bookingId, documentType);
|
||||
if (value == null) throw conflict("请先上传资料");
|
||||
return value;
|
||||
}
|
||||
|
||||
private BusinessDocument requireDocument(long bookingId, String documentType) {
|
||||
return document(requireDocumentEntity(bookingId, documentType));
|
||||
}
|
||||
|
||||
private static BusinessDocument document(ContainerDocumentEntity value) {
|
||||
return value == null ? null : new BusinessDocument(value.getId(), value.getDocumentType(), value.getFileId(),
|
||||
value.getFileName(), value.getFileHash(), value.getUploadedBy(), value.getUploadedByName(),
|
||||
value.getCreatedAt(), value.getCompletedBy(), value.getCompletedByName(), value.getCompletedAt(),
|
||||
value.getVersion());
|
||||
}
|
||||
|
||||
private static Map<String, Object> documentSnapshot(ContainerDocumentEntity value) {
|
||||
return value == null ? Map.of() : values("id", value.getId(), "type", value.getDocumentType(),
|
||||
"fileId", value.getFileId(), "fileName", value.getFileName(), "fileHash", value.getFileHash(),
|
||||
"completedAt", value.getCompletedAt() == null ? null : value.getCompletedAt().toString(),
|
||||
"version", value.getVersion());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PRELOAD_TRANSPORT_UPDATE", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "修改预装集装箱号与航次")
|
||||
|
||||
Reference in New Issue
Block a user