优化
This commit is contained in:
+7
-4
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -27,17 +28,19 @@ public class InternalFinanceFeeSourceController {
|
||||
@GetMapping("/inbounds/{inboundId}/fees/{feeId}/source-snapshot")
|
||||
public ApiResponse<FinanceFeeSourceSnapshot> inbound(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long inboundId, @PathVariable long feeId) {
|
||||
@PathVariable long inboundId, @PathVariable long feeId,
|
||||
@RequestParam @jakarta.validation.constraints.Min(1) int sourceVersion) {
|
||||
authenticate(token);
|
||||
return ApiResponse.success(service.inbound(inboundId, feeId));
|
||||
return ApiResponse.success(service.inbound(inboundId, feeId, sourceVersion));
|
||||
}
|
||||
|
||||
@GetMapping("/containers/{containerId}/fees/{feeId}/source-snapshot")
|
||||
public ApiResponse<FinanceFeeSourceSnapshot> container(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long containerId, @PathVariable long feeId) {
|
||||
@PathVariable long containerId, @PathVariable long feeId,
|
||||
@RequestParam @jakarta.validation.constraints.Min(1) int sourceVersion) {
|
||||
authenticate(token);
|
||||
return ApiResponse.success(service.container(containerId, feeId));
|
||||
return ApiResponse.success(service.container(containerId, feeId, sourceVersion));
|
||||
}
|
||||
|
||||
private void authenticate(String token) {
|
||||
|
||||
@@ -20,9 +20,7 @@ public final class ContainerFeeDetailDtos {
|
||||
@NotNull LocalDate businessDate,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||
@NotNull Boolean settlementIncluded,
|
||||
Long partnerId,
|
||||
@Size(max = 200) String partnerName,
|
||||
Long supplierId,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public final class FeeItemDtos {
|
||||
@NotBlank @Size(max = 64) String category,
|
||||
@NotBlank @Pattern(regexp = "DOMESTIC|INTERNATIONAL") String tradeScope,
|
||||
@Size(max = 50) @Pattern(regexp = "[A-Za-z0-9_-]{1,50}") String feeCode,
|
||||
@Pattern(regexp = "BOOKING|CUSTOMS|TRUCKING|CERTIFICATE") String supplierCategory,
|
||||
@NotBlank @Size(max = 128) String feeTypeName,
|
||||
@NotNull Boolean pickupPayable,
|
||||
@NotNull Boolean pickupReceivable,
|
||||
@@ -22,12 +23,22 @@ public final class FeeItemDtos {
|
||||
@NotNull Boolean allocationFee,
|
||||
@Size(max = 500) String description
|
||||
) {
|
||||
public SaveRequest(String category, String tradeScope, String feeCode,
|
||||
String feeTypeName, Boolean pickupPayable,
|
||||
Boolean pickupReceivable, Boolean warehousePayable,
|
||||
Boolean warehouseReceivable, Boolean allocationFee,
|
||||
String description) {
|
||||
this(category, tradeScope, feeCode, null, feeTypeName, pickupPayable,
|
||||
pickupReceivable, warehousePayable, warehouseReceivable,
|
||||
allocationFee, description);
|
||||
}
|
||||
|
||||
/** Backward-compatible constructor for callers predating the stable fee code. */
|
||||
public SaveRequest(String category, String tradeScope, String feeTypeName,
|
||||
Boolean pickupPayable, Boolean pickupReceivable,
|
||||
Boolean warehousePayable, Boolean warehouseReceivable,
|
||||
Boolean allocationFee, String description) {
|
||||
this(category, tradeScope, null, feeTypeName, pickupPayable, pickupReceivable,
|
||||
this(category, tradeScope, null, null, feeTypeName, pickupPayable, pickupReceivable,
|
||||
warehousePayable, warehouseReceivable, allocationFee, description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ public record FinanceFeeSourceSnapshot(
|
||||
String containerNo,
|
||||
long customerId,
|
||||
String customerName,
|
||||
Long partnerId,
|
||||
String partnerName,
|
||||
Long supplierId,
|
||||
String supplierName,
|
||||
String supplierCategory,
|
||||
Long feeItemId,
|
||||
String feeItemCode,
|
||||
String feeItemName,
|
||||
@@ -29,7 +30,6 @@ public record FinanceFeeSourceSnapshot(
|
||||
BigDecimal totalAmount,
|
||||
String reviewStatus,
|
||||
String publicationStatus,
|
||||
boolean settlementIncluded,
|
||||
LocalDateTime publishedAt,
|
||||
BigDecimal chargeableQuantity,
|
||||
String chargeUnit,
|
||||
|
||||
@@ -21,15 +21,13 @@ public final class InboundFeeDetailDtos {
|
||||
@NotNull @DecimalMin("0.00") BigDecimal unitPrice,
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||
@NotNull Boolean settlementIncluded,
|
||||
Long partnerId,
|
||||
@Size(max = 200) String partnerName,
|
||||
Long supplierId,
|
||||
@Size(max = 500) String remark
|
||||
) {
|
||||
public SaveRequest(Long feeItemId, String direction, LocalDate businessDate,
|
||||
BigDecimal unitPrice, String currency, BigDecimal amount,
|
||||
String remark) {
|
||||
this(feeItemId, direction, businessDate, unitPrice, currency, amount, false, null, null, remark);
|
||||
this(feeItemId, direction, businessDate, unitPrice, currency, amount, null, remark);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public record MerchantInboundBillingResponse(InboundOrderDetail inbound,
|
||||
BigDecimal chargeableQuantity, String chargeUnit,
|
||||
String feeTypeName, BigDecimal unitPrice, String currency, BigDecimal amount,
|
||||
String reviewStatus, String publicationStatus,
|
||||
LocalDateTime publishedAt, LocalDateTime inboundedAt, Boolean settlementIncluded
|
||||
LocalDateTime publishedAt, LocalDateTime inboundedAt
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ public class ContainerFeeDetailEntity {
|
||||
private String feeItemCodeSnapshot;
|
||||
private String feeItemNameSnapshot;
|
||||
private String direction;
|
||||
private Long partnerId;
|
||||
private String partnerName;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private Boolean settlementIncluded;
|
||||
private String reviewStatus;
|
||||
private String sourceStatus;
|
||||
private Integer sourceVersion;
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 柜次费用审核发布时固化的不可变版本快照。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_container_fee_source_snapshot")
|
||||
public class ContainerFeeSourceSnapshotEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long sourceFeeId;
|
||||
private Integer sourceVersion;
|
||||
private Long tenantId;
|
||||
private Long loadingPlanId;
|
||||
private Long containerBookingId;
|
||||
private String containerNo;
|
||||
private Long feeItemId;
|
||||
private String feeItemCode;
|
||||
private String feeItemName;
|
||||
private String direction;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private String reviewStatus;
|
||||
private LocalDateTime reviewedAt;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class FeeItemEntity extends BaseEntity {
|
||||
private String category;
|
||||
private String tradeScope;
|
||||
private String feeCode;
|
||||
private String supplierCategory;
|
||||
private String feeTypeName;
|
||||
private Boolean pickupPayable;
|
||||
private Boolean pickupReceivable;
|
||||
@@ -28,4 +29,5 @@ public class FeeItemEntity extends BaseEntity {
|
||||
private String status;
|
||||
private String createdByName;
|
||||
private String updatedByName;
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ public class InboundFeeDetailEntity extends BaseEntity {
|
||||
private String sourceFeeNo;
|
||||
private String inboundCode;
|
||||
private String direction;
|
||||
private Long partnerId;
|
||||
private String partnerName;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private String supplierCategory;
|
||||
private LocalDate businessDate;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
@@ -62,5 +63,4 @@ public class InboundFeeDetailEntity extends BaseEntity {
|
||||
private String publishedByName;
|
||||
private LocalDateTime publishedAt;
|
||||
private LocalDateTime inboundedAt;
|
||||
private Boolean settlementIncluded;
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeSourceSnapshotEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ContainerFeeSourceSnapshotMapper extends BaseMapper<ContainerFeeSourceSnapshotEntity> {
|
||||
}
|
||||
+3
-1
@@ -15,6 +15,7 @@ import com.baiyue.firstleg.repository.PriceBookQueryRepository;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelTaxModeRow;
|
||||
import com.baiyue.firstleg.service.WarehouseSupplierSnapshotService;
|
||||
import com.baiyue.firstleg.service.FirstlegDocumentNumberGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -42,6 +43,7 @@ public class OceanBookingService {
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private WarehouseSupplierSnapshotService suppliers;
|
||||
@Resource private WarehouseMasterDataClient warehouses;
|
||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@Value("${baiyue.warehouse.public-tenant-id:0}") private long publicWarehouseTenantId;
|
||||
|
||||
@@ -80,7 +82,7 @@ public class OceanBookingService {
|
||||
ResolvedDraft resolved = resolveDraft(request.data());
|
||||
OceanBookingEntity value = new OceanBookingEntity();
|
||||
value.setId(IdWorker.getId());
|
||||
value.setBookingNo("OB" + value.getId());
|
||||
value.setBookingNo(documentNumbers.next("OB"));
|
||||
value.setStatus(OceanBookingStatus.DRAFT.name());
|
||||
copy(value, request.data(), resolved);
|
||||
value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
|
||||
+16
@@ -42,6 +42,22 @@ public class ContainerFeeDetailRepository {
|
||||
mapper.insert(value);
|
||||
}
|
||||
|
||||
public int updateSupplierSnapshot(long tenantId, long loadingPlanId, long feeId,
|
||||
Long supplierId, String supplierName,
|
||||
String supplierCategory, long operatorId) {
|
||||
return mapper.update(null, new LambdaUpdateWrapper<ContainerFeeDetailEntity>()
|
||||
.eq(ContainerFeeDetailEntity::getTenantId, tenantId)
|
||||
.eq(ContainerFeeDetailEntity::getLoadingPlanId, loadingPlanId)
|
||||
.eq(ContainerFeeDetailEntity::getId, feeId)
|
||||
.eq(ContainerFeeDetailEntity::getReviewStatus, "PENDING")
|
||||
.eq(ContainerFeeDetailEntity::getSourceStatus, "ACTIVE")
|
||||
.set(ContainerFeeDetailEntity::getSupplierId, supplierId)
|
||||
.set(ContainerFeeDetailEntity::getSupplierName, supplierName)
|
||||
.set(ContainerFeeDetailEntity::getSupplierCategory, supplierCategory)
|
||||
.set(ContainerFeeDetailEntity::getUpdatedBy, operatorId)
|
||||
.set(ContainerFeeDetailEntity::getUpdatedAt, LocalDateTime.now()));
|
||||
}
|
||||
|
||||
public int review(long tenantId, long loadingPlanId, long feeId, String status,
|
||||
long reviewerId, String reviewerName, String reason) {
|
||||
return mapper.update(null, new LambdaUpdateWrapper<ContainerFeeDetailEntity>()
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeSourceSnapshotEntity;
|
||||
import com.baiyue.firstleg.mapper.ContainerFeeSourceSnapshotMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class ContainerFeeSourceSnapshotRepository {
|
||||
private final ContainerFeeSourceSnapshotMapper mapper;
|
||||
|
||||
public void saveApproved(ContainerFeeDetailEntity source) {
|
||||
ContainerFeeSourceSnapshotEntity value = new ContainerFeeSourceSnapshotEntity();
|
||||
value.setSourceFeeId(source.getId());
|
||||
value.setSourceVersion(source.getSourceVersion());
|
||||
value.setTenantId(source.getTenantId());
|
||||
value.setLoadingPlanId(source.getLoadingPlanId());
|
||||
value.setContainerBookingId(source.getContainerBookingId());
|
||||
value.setContainerNo(source.getContainerNo());
|
||||
value.setFeeItemId(source.getFeeItemId());
|
||||
value.setFeeItemCode(source.getFeeItemCodeSnapshot());
|
||||
value.setFeeItemName(source.getFeeItemNameSnapshot());
|
||||
value.setDirection(source.getDirection());
|
||||
value.setSupplierId(source.getSupplierId());
|
||||
value.setSupplierName(source.getSupplierName());
|
||||
value.setSupplierCategory(source.getSupplierCategory());
|
||||
value.setBusinessDate(source.getBusinessDate());
|
||||
value.setAmount(source.getAmount());
|
||||
value.setCurrency(source.getCurrency());
|
||||
value.setReviewStatus(source.getReviewStatus());
|
||||
value.setReviewedAt(source.getReviewedAt());
|
||||
value.setCreatedAt(LocalDateTime.now());
|
||||
mapper.insert(value);
|
||||
}
|
||||
|
||||
public ContainerFeeSourceSnapshotEntity find(long loadingPlanId, long sourceFeeId,
|
||||
int sourceVersion) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<ContainerFeeSourceSnapshotEntity>()
|
||||
.eq(ContainerFeeSourceSnapshotEntity::getLoadingPlanId, loadingPlanId)
|
||||
.eq(ContainerFeeSourceSnapshotEntity::getSourceFeeId, sourceFeeId)
|
||||
.eq(ContainerFeeSourceSnapshotEntity::getSourceVersion, sourceVersion));
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,15 @@ public class FeeItemRepository {
|
||||
.eq(InboundFeeDetailEntity::getId, detailId));
|
||||
}
|
||||
|
||||
public InboundFeeDetailEntity findSourceVersion(long inboundId, long sourceFeeId,
|
||||
int sourceVersion) {
|
||||
return feeDetails.selectOne(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
|
||||
.eq(InboundFeeDetailEntity::getSourceFeeVersion, sourceVersion)
|
||||
.and(scope -> scope.eq(InboundFeeDetailEntity::getId, sourceFeeId)
|
||||
.or().eq(InboundFeeDetailEntity::getSourceFeeId, sourceFeeId)));
|
||||
}
|
||||
|
||||
public InboundFeeDetailEntity findBySourceFee(String sourceType, long sourceFeeId) {
|
||||
return feeDetails.selectOne(new LambdaQueryWrapper<InboundFeeDetailEntity>()
|
||||
.eq(InboundFeeDetailEntity::getSourceType, sourceType)
|
||||
@@ -166,7 +175,6 @@ public class FeeItemRepository {
|
||||
.in(InboundFeeDetailEntity::getPublicationStatus, List.of("DRAFT", "PUBLISHED"))
|
||||
.ne(InboundFeeDetailEntity::getSourceFeeId, retainedSourceFeeId)
|
||||
.set(InboundFeeDetailEntity::getPublicationStatus, "SUPERSEDED")
|
||||
.set(InboundFeeDetailEntity::getSettlementIncluded, false)
|
||||
.set(InboundFeeDetailEntity::getUpdatedBy, operatorId)
|
||||
.set(InboundFeeDetailEntity::getUpdatedAt, now));
|
||||
}
|
||||
@@ -186,7 +194,6 @@ public class FeeItemRepository {
|
||||
.set(InboundFeeDetailEntity::getPublishedBy, operatorId)
|
||||
.set(InboundFeeDetailEntity::getPublishedByName, operatorName)
|
||||
.set(InboundFeeDetailEntity::getPublishedAt, now)
|
||||
.set(InboundFeeDetailEntity::getSettlementIncluded, true)
|
||||
.set(InboundFeeDetailEntity::getUpdatedBy, operatorId)
|
||||
.set(InboundFeeDetailEntity::getUpdatedAt, now));
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import com.baiyue.firstleg.entity.ContainerFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
import com.baiyue.firstleg.repository.ContainerFeeDetailRepository;
|
||||
import com.baiyue.firstleg.repository.ContainerFeeSourceSnapshotRepository;
|
||||
import com.baiyue.firstleg.repository.FeeItemRepository;
|
||||
import com.baiyue.firstleg.repository.SmartLoadingRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -27,6 +27,8 @@ public class ContainerFeeDetailService {
|
||||
private final SmartLoadingRepository loading;
|
||||
private final FeeItemRepository feeItems;
|
||||
private final FinanceFeeSourceOutboxService financeOutbox;
|
||||
private final WarehouseSupplierSnapshotService suppliers;
|
||||
private final ContainerFeeSourceSnapshotRepository sourceSnapshots;
|
||||
|
||||
public List<ContainerFeeDetailEntity> list(long tenantId, long loadingPlanId) {
|
||||
requireOwnedPlan(tenantId, loadingPlanId);
|
||||
@@ -56,18 +58,19 @@ public class ContainerFeeDetailService {
|
||||
@Transactional
|
||||
public void approve(long tenantId, long loadingPlanId, long feeId,
|
||||
long reviewerId, String reviewerName) {
|
||||
required(tenantId, loadingPlanId, feeId);
|
||||
ContainerFeeDetailEntity pending = required(tenantId, loadingPlanId, feeId);
|
||||
FeeItemEntity item = requireItem(pending.getFeeItemId(), pending.getDirection());
|
||||
refreshSupplierSnapshot(pending, item, reviewerId);
|
||||
if (repository.review(tenantId, loadingPlanId, feeId, "APPROVED",
|
||||
reviewerId, reviewerName, null) != 1) {
|
||||
throw conflict("只有待审核柜次费用可以审核通过");
|
||||
}
|
||||
ContainerFeeDetailEntity approved = required(tenantId, loadingPlanId, feeId);
|
||||
if (Boolean.TRUE.equals(approved.getSettlementIncluded())) {
|
||||
String eventType = approved.getSourceVersion() > 1
|
||||
? FinanceFeeSourceOutboxService.ADJUSTED : FinanceFeeSourceOutboxService.PUBLISHED;
|
||||
financeOutbox.publishContainer(eventType, tenantId, loadingPlanId, feeId,
|
||||
approved.getSourceVersion(), LocalDateTime.now());
|
||||
}
|
||||
sourceSnapshots.saveApproved(approved);
|
||||
String eventType = approved.getSourceVersion() > 1
|
||||
? FinanceFeeSourceOutboxService.ADJUSTED : FinanceFeeSourceOutboxService.PUBLISHED;
|
||||
financeOutbox.publishContainer(eventType, tenantId, loadingPlanId, feeId,
|
||||
approved.getSourceVersion(), LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -111,10 +114,8 @@ public class ContainerFeeDetailService {
|
||||
if (repository.voidApproved(tenantId, loadingPlanId, feeId, expectedVersion, operatorId) != 1) {
|
||||
throw conflict("只有当前版本的已审核柜次费用可以作废");
|
||||
}
|
||||
if (Boolean.TRUE.equals(current.getSettlementIncluded())) {
|
||||
financeOutbox.publishContainer(FinanceFeeSourceOutboxService.VOIDED, tenantId,
|
||||
loadingPlanId, feeId, expectedVersion, LocalDateTime.now());
|
||||
}
|
||||
financeOutbox.publishContainer(FinanceFeeSourceOutboxService.VOIDED, tenantId,
|
||||
loadingPlanId, feeId, expectedVersion, LocalDateTime.now());
|
||||
}
|
||||
|
||||
private PlanContext requireOwnedPlan(long tenantId, long loadingPlanId) {
|
||||
@@ -144,28 +145,49 @@ public class ContainerFeeDetailService {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void apply(ContainerFeeDetailEntity value, FeeItemEntity item,
|
||||
SaveRequest request, long operatorId) {
|
||||
if ("PAYABLE".equals(request.direction())
|
||||
&& (request.partnerId() == null || request.partnerId() <= 0
|
||||
|| !StringUtils.hasText(request.partnerName()))) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "应付柜次费用必须选择有效往来单位");
|
||||
}
|
||||
private void apply(ContainerFeeDetailEntity value, FeeItemEntity item,
|
||||
SaveRequest request, long operatorId) {
|
||||
value.setFeeItemId(item.getId());
|
||||
value.setFeeItemCodeSnapshot(item.getFeeCode());
|
||||
value.setFeeItemNameSnapshot(item.getFeeTypeName());
|
||||
value.setDirection(request.direction());
|
||||
value.setPartnerId("PAYABLE".equals(request.direction()) ? request.partnerId() : null);
|
||||
value.setPartnerName("PAYABLE".equals(request.direction()) ? request.partnerName().trim() : null);
|
||||
applySupplierSnapshot(value, item, request.direction(), request.supplierId());
|
||||
value.setBusinessDate(request.businessDate());
|
||||
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
||||
value.setCurrency(request.currency());
|
||||
value.setSettlementIncluded(Boolean.TRUE.equals(request.settlementIncluded()));
|
||||
value.setRemark(request.remark() == null ? null : request.remark().trim());
|
||||
value.setUpdatedBy(operatorId);
|
||||
value.setUpdatedAt(LocalDateTime.now());
|
||||
}
|
||||
|
||||
private void applySupplierSnapshot(ContainerFeeDetailEntity value, FeeItemEntity item,
|
||||
String direction, Long supplierId) {
|
||||
if (!"PAYABLE".equals(direction)) {
|
||||
value.setSupplierId(null);
|
||||
value.setSupplierName(null);
|
||||
value.setSupplierCategory(null);
|
||||
return;
|
||||
}
|
||||
if (supplierId == null || supplierId <= 0) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "应付柜次费用必须选择有效供应商");
|
||||
}
|
||||
String category = FeeItemSupplierCategory.requirePayable(item.getSupplierCategory());
|
||||
var snapshot = suppliers.fee(supplierId, category);
|
||||
value.setSupplierId(snapshot.supplierId());
|
||||
value.setSupplierName(snapshot.name());
|
||||
value.setSupplierCategory(snapshot.category());
|
||||
}
|
||||
|
||||
private void refreshSupplierSnapshot(ContainerFeeDetailEntity value, FeeItemEntity item,
|
||||
long operatorId) {
|
||||
applySupplierSnapshot(value, item, value.getDirection(), value.getSupplierId());
|
||||
if (repository.updateSupplierSnapshot(value.getTenantId(), value.getLoadingPlanId(),
|
||||
value.getId(), value.getSupplierId(), value.getSupplierName(),
|
||||
value.getSupplierCategory(), operatorId) != 1) {
|
||||
throw conflict("柜次费用供应商快照已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public class ContainerService {
|
||||
@Resource private PriceBookQueryRepository priceBooks;
|
||||
@Resource private OceanBookingRepository oceanBookings;
|
||||
@Resource private WarehouseSupplierSnapshotService suppliers;
|
||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||
@Autowired(required = false) private ContainerTransportHistoryMapper transportHistories;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@Value("${baiyue.warehouse.public-tenant-id:0}") private long publicWarehouseTenantId;
|
||||
@@ -66,7 +67,7 @@ public class ContainerService {
|
||||
.filter(row -> request.channelCode().trim().equals(row.getChannelCode())).findFirst()
|
||||
.orElseThrow(() -> conflict("所选物流渠道已失效,请重新选择"));
|
||||
ContainerBookingEntity value = new ContainerBookingEntity(); value.setId(IdWorker.getId());
|
||||
value.setBookingNo("CB" + value.getId()); value.setCabinetNo("CAB-" + value.getId());
|
||||
value.setBookingNo(documentNumbers.next("CB")); value.setCabinetNo(documentNumbers.next("CAB"));
|
||||
value.setSourceType("SIMULATION"); value.setRecordStatus("ACTIVE");
|
||||
value.setChannelCode(channel.getChannelCode()); value.setChannelName(channel.getChannelName());
|
||||
value.setWarehouseId(warehouse.id()); value.setWarehouseName(warehouse.warehouseName());
|
||||
|
||||
@@ -102,10 +102,19 @@ public class FeeItemService {
|
||||
}
|
||||
|
||||
private static void copy(FeeItemEntity value, SaveRequest request) {
|
||||
boolean payable = Boolean.TRUE.equals(request.pickupPayable())
|
||||
|| Boolean.TRUE.equals(request.warehousePayable());
|
||||
if (payable && (request.supplierCategory() == null
|
||||
|| request.supplierCategory().isBlank())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"启用应付方向时必须配置供应商类别");
|
||||
}
|
||||
value.setCategory(request.category().trim());
|
||||
value.setTradeScope(request.tradeScope());
|
||||
value.setFeeCode(request.feeCode() == null || request.feeCode().isBlank()
|
||||
? value.getFeeCode() : request.feeCode().trim().toUpperCase());
|
||||
value.setSupplierCategory(payable
|
||||
? FeeItemSupplierCategory.normalize(request.supplierCategory()) : null);
|
||||
value.setFeeTypeName(request.feeTypeName().trim());
|
||||
value.setPickupPayable(request.pickupPayable());
|
||||
value.setPickupReceivable(request.pickupReceivable());
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/** 费用项目显式供应商类别校验;未配置的应付费用失败关闭。 */
|
||||
public final class FeeItemSupplierCategory {
|
||||
private static final Set<String> ALLOWED = Set.of(
|
||||
"BOOKING", "CUSTOMS", "TRUCKING", "CERTIFICATE");
|
||||
|
||||
private FeeItemSupplierCategory() {
|
||||
}
|
||||
|
||||
public static String normalize(String supplierCategory) {
|
||||
if (supplierCategory == null || supplierCategory.isBlank()) return null;
|
||||
String normalized = supplierCategory.trim().toUpperCase(Locale.ROOT);
|
||||
if (!ALLOWED.contains(normalized)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "供应商类别无效");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static String requirePayable(String supplierCategory) {
|
||||
String category = normalize(supplierCategory);
|
||||
if (category == null) {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"费用项目未配置供应商类别,不能用于应付费用");
|
||||
}
|
||||
return category;
|
||||
}
|
||||
}
|
||||
+23
-23
@@ -3,11 +3,11 @@ package com.baiyue.firstleg.service;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FinanceFeeSourceSnapshot;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeSourceSnapshotEntity;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.ContainerFeeDetailRepository;
|
||||
import com.baiyue.firstleg.repository.ContainerFeeSourceSnapshotRepository;
|
||||
import com.baiyue.firstleg.repository.FeeItemRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -21,11 +21,10 @@ import java.util.HexFormat;
|
||||
@RequiredArgsConstructor
|
||||
public class FinanceFeeSourceSnapshotService {
|
||||
private final FeeItemRepository inboundFees;
|
||||
private final ContainerFeeDetailRepository containerFees;
|
||||
private final ContainerFeeSourceSnapshotRepository containerSnapshots;
|
||||
|
||||
public FinanceFeeSourceSnapshot inbound(long inboundId, long feeId) {
|
||||
InboundFeeDetailEntity detail = inboundFees.findDetail(inboundId, feeId);
|
||||
if (detail == null) detail = inboundFees.findBySourceFee("FBA_PRICING", feeId);
|
||||
public FinanceFeeSourceSnapshot inbound(long inboundId, long feeId, int sourceVersion) {
|
||||
InboundFeeDetailEntity detail = inboundFees.findSourceVersion(inboundId, feeId, sourceVersion);
|
||||
if (detail == null || !Long.valueOf(inboundId).equals(detail.getInboundId())) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库费用来源不存在");
|
||||
}
|
||||
@@ -34,43 +33,44 @@ public class FinanceFeeSourceSnapshotService {
|
||||
if (inbound == null || item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库费用来源关联快照不完整");
|
||||
}
|
||||
long sourceFeeId = "FBA_PRICING".equals(detail.getSourceType())
|
||||
? detail.getSourceFeeId() : detail.getId();
|
||||
long sourceFeeId = detail.getSourceFeeId() == null ? detail.getId() : detail.getSourceFeeId();
|
||||
int version = detail.getSourceFeeVersion() == null ? 1 : detail.getSourceFeeVersion();
|
||||
String canonical = String.join("|", "INBOUND_FEE", String.valueOf(sourceFeeId),
|
||||
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(inboundId),
|
||||
String.valueOf(inbound.getOrderId()), nullSafe(item.getFeeCode()),
|
||||
nullSafe(detail.getDirection()), detail.getAmount().toPlainString(),
|
||||
nullSafe(detail.getCurrency()), String.valueOf(detail.getSettlementIncluded()));
|
||||
nullSafe(detail.getDirection()), nullSafe(detail.getSupplierId()),
|
||||
nullSafe(detail.getSupplierName()), nullSafe(detail.getSupplierCategory()),
|
||||
detail.getAmount().toPlainString(),
|
||||
nullSafe(detail.getCurrency()));
|
||||
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "INBOUND_FEE", sourceFeeId,
|
||||
version, inboundId, inbound.getInboundCode(), inbound.getOrderId(), null, null, null,
|
||||
detail.getTenantId(), inbound.getCustomerName(), detail.getPartnerId(), detail.getPartnerName(), item.getId(), item.getFeeCode(),
|
||||
detail.getTenantId(), inbound.getCustomerName(), detail.getSupplierId(),
|
||||
detail.getSupplierName(), detail.getSupplierCategory(), item.getId(), item.getFeeCode(),
|
||||
detail.getFeeTypeNameSnapshot(), detail.getDirection(), detail.getBusinessDate(),
|
||||
detail.getCurrency(), detail.getAmount(), detail.getReviewStatus(),
|
||||
detail.getPublicationStatus(), Boolean.TRUE.equals(detail.getSettlementIncluded()),
|
||||
detail.getPublicationStatus(),
|
||||
detail.getPublishedAt(), detail.getChargeableQuantity(), detail.getChargeUnit(),
|
||||
detail.getActualGrossWeightKg(), detail.getActualVolumeCbm(), sha256(canonical), 1);
|
||||
}
|
||||
|
||||
public FinanceFeeSourceSnapshot container(long containerId, long feeId) {
|
||||
ContainerFeeDetailEntity detail = containerFees.find(containerId, feeId);
|
||||
public FinanceFeeSourceSnapshot container(long containerId, long feeId, int sourceVersion) {
|
||||
ContainerFeeSourceSnapshotEntity detail = containerSnapshots.find(containerId, feeId, sourceVersion);
|
||||
if (detail == null) throw new BizException(ResultCode.NOT_FOUND, "柜次费用来源不存在");
|
||||
int version = detail.getSourceVersion();
|
||||
String publicationStatus = "VOIDED".equals(detail.getSourceStatus()) ? "VOIDED"
|
||||
: "APPROVED".equals(detail.getReviewStatus()) ? "PUBLISHED" : "DRAFT";
|
||||
String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId),
|
||||
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerId),
|
||||
nullSafe(detail.getFeeItemCodeSnapshot()), nullSafe(detail.getDirection()),
|
||||
nullSafe(detail.getPartnerId()), nullSafe(detail.getPartnerName()),
|
||||
nullSafe(detail.getFeeItemCode()), nullSafe(detail.getDirection()),
|
||||
nullSafe(detail.getSupplierId()), nullSafe(detail.getSupplierName()),
|
||||
nullSafe(detail.getSupplierCategory()),
|
||||
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
||||
String.valueOf(detail.getSettlementIncluded()));
|
||||
nullSafe(detail.getReviewStatus()));
|
||||
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
||||
version, null, null, null, containerId, detail.getContainerBookingId(),
|
||||
detail.getContainerNo(), detail.getTenantId(), null, detail.getPartnerId(),
|
||||
detail.getPartnerName(), detail.getFeeItemId(),
|
||||
detail.getFeeItemCodeSnapshot(), detail.getFeeItemNameSnapshot(), detail.getDirection(),
|
||||
detail.getContainerNo(), detail.getTenantId(), null, detail.getSupplierId(),
|
||||
detail.getSupplierName(), detail.getSupplierCategory(), detail.getFeeItemId(),
|
||||
detail.getFeeItemCode(), detail.getFeeItemName(), detail.getDirection(),
|
||||
detail.getBusinessDate(), detail.getCurrency(), detail.getAmount(), detail.getReviewStatus(),
|
||||
publicationStatus, Boolean.TRUE.equals(detail.getSettlementIncluded()),
|
||||
"PUBLISHED",
|
||||
detail.getReviewedAt(), null, null, null, null, sha256(canonical), 1);
|
||||
}
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class FirstlegDocumentNumberGenerator {
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd", Locale.ROOT);
|
||||
private static final Set<String> SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "CAB", "LP");
|
||||
private static final long MAX_DAILY_SEQUENCE = 9_999L;
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
private final Clock clock;
|
||||
|
||||
public FirstlegDocumentNumberGenerator(JdbcTemplate jdbc) {
|
||||
this(jdbc, Clock.system(BUSINESS_ZONE));
|
||||
}
|
||||
|
||||
FirstlegDocumentNumberGenerator(JdbcTemplate jdbc, Clock clock) {
|
||||
this.jdbc = jdbc;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.MANDATORY)
|
||||
public String next(String prefix) {
|
||||
if (!SUPPORTED_PREFIXES.contains(prefix)) {
|
||||
throw new IllegalArgumentException("unsupported firstleg document prefix: " + prefix);
|
||||
}
|
||||
LocalDate sequenceDate = LocalDate.now(clock);
|
||||
String sequenceKey = prefix + ":" + DateTimeFormatter.BASIC_ISO_DATE.format(sequenceDate);
|
||||
jdbc.update(
|
||||
"INSERT INTO fl_document_sequence(sequence_key,current_value) " +
|
||||
"VALUES(?,LAST_INSERT_ID(1)) ON DUPLICATE KEY UPDATE " +
|
||||
"current_value=LAST_INSERT_ID(current_value+1)",
|
||||
sequenceKey);
|
||||
Long sequence = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
|
||||
if (sequence == null || sequence <= 0 || sequence > MAX_DAILY_SEQUENCE) {
|
||||
throw new BizException(ResultCode.CONFLICT, "当日" + prefix + "单据编号已用尽");
|
||||
}
|
||||
return prefix + "-" + DATE_FORMAT.format(sequenceDate) + "-" +
|
||||
String.format(Locale.ROOT, "%04d", sequence);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ public class InboundFeeDetailService {
|
||||
private FbaPricingRepository pricingRepository;
|
||||
@Resource
|
||||
private FinanceFeeSourceOutboxService financeOutbox;
|
||||
@Resource
|
||||
private WarehouseSupplierSnapshotService suppliers;
|
||||
|
||||
@Transactional
|
||||
public List<InboundFeeDetailEntity> list(long tenantId, long inboundId, boolean includeHistory) {
|
||||
@@ -130,7 +132,6 @@ public class InboundFeeDetailService {
|
||||
detail.setPublishedByName(operatorName);
|
||||
detail.setPublishedAt(now);
|
||||
detail.setInboundedAt(inbound.getInboundedAt());
|
||||
detail.setSettlementIncluded(true);
|
||||
detail.setCreatedBy(operatorId);
|
||||
detail.setUpdatedBy(operatorId);
|
||||
try {
|
||||
@@ -199,7 +200,7 @@ public class InboundFeeDetailService {
|
||||
detail.setRemark("来源费用单:" + fee.getFeeNo());
|
||||
detail.setReviewStatus(published ? "APPROVED" : "PENDING");
|
||||
detail.setPublicationStatus(published ? "PUBLISHED" : "DRAFT");
|
||||
detail.setSettlementIncluded(published); detail.setCreatedBy(operatorId); detail.setUpdatedBy(operatorId);
|
||||
detail.setCreatedBy(operatorId); detail.setUpdatedBy(operatorId);
|
||||
if (published) {
|
||||
detail.setReviewerId(operatorId); detail.setReviewerName(operatorName); detail.setReviewedAt(now);
|
||||
detail.setPublishedBy(operatorId); detail.setPublishedByName(operatorName); detail.setPublishedAt(now);
|
||||
@@ -252,7 +253,9 @@ public class InboundFeeDetailService {
|
||||
@Transactional
|
||||
public void approve(long tenantId, long inboundId, long detailId,
|
||||
long reviewerId, String reviewerName) {
|
||||
requiredManual(tenantId, inboundId, detailId);
|
||||
InboundFeeDetailEntity pending = requiredManual(tenantId, inboundId, detailId);
|
||||
FeeItemEntity item = requiredActiveItem(pending.getFeeItemId());
|
||||
refreshSupplierSnapshot(pending, item, reviewerId);
|
||||
if (repository.review(tenantId, inboundId, detailId, "APPROVED",
|
||||
reviewerId, reviewerName, null) != 1) {
|
||||
throw conflict("只有待审核费用明细可以审核通过");
|
||||
@@ -301,10 +304,8 @@ public class InboundFeeDetailService {
|
||||
if (repository.voidApproved(tenantId, inboundId, detailId, expectedSourceVersion, operatorId) != 1) {
|
||||
throw conflict("只有当前版本的已审核费用明细可以作废");
|
||||
}
|
||||
if (Boolean.TRUE.equals(current.getSettlementIncluded())) {
|
||||
current.setPublicationStatus("VOIDED");
|
||||
publishFinanceSource(current, "VOIDED", LocalDateTime.now());
|
||||
}
|
||||
current.setPublicationStatus("VOIDED");
|
||||
publishFinanceSource(current, "VOIDED", LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -373,14 +374,13 @@ public class InboundFeeDetailService {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void apply(InboundFeeDetailEntity value, InboundOrderEntity inbound,
|
||||
FeeItemEntity item, SaveRequest request, long operatorId) {
|
||||
private void apply(InboundFeeDetailEntity value, InboundOrderEntity inbound,
|
||||
FeeItemEntity item, SaveRequest request, long operatorId) {
|
||||
ensureWarehouseApplicable(item, request.direction());
|
||||
value.setFeeItemId(item.getId());
|
||||
value.setInboundCode(inbound.getInboundCode());
|
||||
value.setDirection(request.direction());
|
||||
value.setPartnerId("PAYABLE".equals(request.direction()) ? request.partnerId() : null);
|
||||
value.setPartnerName("PAYABLE".equals(request.direction()) ? request.partnerName() : null);
|
||||
applySupplierSnapshot(value, item, request.direction(), request.supplierId());
|
||||
value.setBusinessDate(request.businessDate());
|
||||
value.setFeeCategorySnapshot(item.getCategory());
|
||||
value.setTradeScopeSnapshot(item.getTradeScope());
|
||||
@@ -399,18 +399,43 @@ public class InboundFeeDetailService {
|
||||
value.setUnitPrice(request.unitPrice().setScale(4, RoundingMode.HALF_UP));
|
||||
value.setCurrency(request.currency());
|
||||
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
||||
value.setSettlementIncluded(Boolean.TRUE.equals(request.settlementIncluded()));
|
||||
value.setRemark(request.remark() == null ? null : request.remark().trim());
|
||||
value.setUpdatedBy(operatorId);
|
||||
}
|
||||
|
||||
private void applySupplierSnapshot(InboundFeeDetailEntity value, FeeItemEntity item,
|
||||
String direction, Long supplierId) {
|
||||
if (!"PAYABLE".equals(direction)) {
|
||||
value.setSupplierId(null);
|
||||
value.setSupplierName(null);
|
||||
value.setSupplierCategory(null);
|
||||
return;
|
||||
}
|
||||
if (supplierId == null || supplierId <= 0) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "应付入库费用必须选择有效供应商");
|
||||
}
|
||||
String category = FeeItemSupplierCategory.requirePayable(item.getSupplierCategory());
|
||||
var snapshot = suppliers.fee(supplierId, category);
|
||||
value.setSupplierId(snapshot.supplierId());
|
||||
value.setSupplierName(snapshot.name());
|
||||
value.setSupplierCategory(snapshot.category());
|
||||
}
|
||||
|
||||
private void refreshSupplierSnapshot(InboundFeeDetailEntity value, FeeItemEntity item,
|
||||
long operatorId) {
|
||||
applySupplierSnapshot(value, item, value.getDirection(), value.getSupplierId());
|
||||
value.setUpdatedBy(operatorId);
|
||||
if (repository.updateDetail(value) != 1) {
|
||||
throw conflict("费用明细供应商快照已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
|
||||
private void publishFinanceSource(InboundFeeDetailEntity detail, String status,
|
||||
LocalDateTime occurredAt) {
|
||||
if (!Boolean.TRUE.equals(detail.getSettlementIncluded())) return;
|
||||
if ("FBA_PRICING".equals(detail.getSourceType())) return;
|
||||
int version = detail.getSourceFeeVersion() == null ? 1 : detail.getSourceFeeVersion();
|
||||
String eventType = "VOIDED".equals(status) ? FinanceFeeSourceOutboxService.VOIDED
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ public class MerchantInboundBillingService {
|
||||
detail.getChargeableVolumeCbm(), detail.getChargeableQuantity(), detail.getChargeUnit(),
|
||||
detail.getFeeTypeNameSnapshot(), detail.getUnitPrice(), detail.getCurrency(), detail.getAmount(),
|
||||
detail.getReviewStatus(), detail.getPublicationStatus(), detail.getPublishedAt(),
|
||||
detail.getInboundedAt(), detail.getSettlementIncluded());
|
||||
detail.getInboundedAt());
|
||||
}
|
||||
|
||||
public record ImageDownload(byte[] content, String contentType) {
|
||||
|
||||
@@ -39,6 +39,7 @@ public class SmartLoadingService {
|
||||
@Resource private LoadingPickService loadingPickService;
|
||||
@Resource private LoadingModelBridgeService loadingModelBridge;
|
||||
@Resource private SplitPreloadService splitPreloadService;
|
||||
@Resource private FirstlegDocumentNumberGenerator documentNumbers;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
public SmartLoadingService() { }
|
||||
@@ -77,7 +78,7 @@ public class SmartLoadingService {
|
||||
LoadingPlanEntity existing = repository.planByBooking(bookingId);
|
||||
if (existing != null) return draftView(existing, booking);
|
||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||
plan.setPlanNo("LP-" + UUID.randomUUID().toString().replace("-", "").substring(0, 20));
|
||||
plan.setPlanNo(documentNumbers.next("LP"));
|
||||
plan.setContainerBookingId(bookingId); plan.setPlanStatus("EDITING"); plan.setRemark(trim(remark));
|
||||
plan.setCreatedBy(operatorId); plan.setCreatedByName(operatorName); plan.setUpdatedBy(operatorId);
|
||||
plan.setUpdatedByName(operatorName); plan.setVersion(0);
|
||||
|
||||
@@ -40,6 +40,7 @@ public class TruckAppointmentService {
|
||||
@Resource TruckAppointmentFileClient files;
|
||||
@Resource WarehouseSupplierSnapshotService suppliers;
|
||||
@Resource IamCustomerSnapshotService customers;
|
||||
@Resource FirstlegDocumentNumberGenerator documentNumbers;
|
||||
@Resource ObjectMapper objectMapper;
|
||||
@Value("${baiyue.security.internal-service-token}") String internalToken;
|
||||
public PageResult<TruckAppointmentEntity> page(Query q){return repo.page(q);} public TruckAppointmentEntity detail(long id){return enrich(required(id));}
|
||||
@@ -47,7 +48,7 @@ public class TruckAppointmentService {
|
||||
@Transactional public TruckAppointmentAttachmentEntity upload(long id,String type,MultipartFile file,long uid){String attachmentType=attachmentType(type);TruckAppointmentEntity appointment=required(id);if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法上传附件");try{var response=files.upload(internalToken,uid,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),file);if(response==null||!response.successful()||response.data()==null)throw bad("附件上传失败");var m=response.data();TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(m.fileId());a.setAttachmentType(attachmentType);a.setFileName(m.fileName());a.setFileHash(m.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");}}
|
||||
@Transactional public TruckAppointmentAttachmentEntity attach(long id,AttachmentRequest r,long uid){String attachmentType=attachmentType(r.attachmentType());TruckAppointmentEntity appointment=required(id); if(appointment.getCustomerId()==null||appointment.getCustomerId()<=0)throw bad("预约缺少客户归属,无法引用附件"); TruckAppointmentFileClient.Metadata metadata;try{var response=files.metadata(internalToken,appointment.getCustomerId(),"TRUCK_APPOINTMENT_ATTACHMENT",Long.toString(id),r.fileId());if(response==null||!response.successful()||response.data()==null)throw bad("附件不存在或归属不匹配");metadata=response.data();}catch(FeignException ex){throw new BizException(ResultCode.SERVICE_UNAVAILABLE,"文件服务暂不可用");} TruckAppointmentAttachmentEntity a=new TruckAppointmentAttachmentEntity();a.setId(IdWorker.getId());a.setAppointmentId(id);a.setFileId(metadata.fileId());a.setAttachmentType(attachmentType);a.setFileName(metadata.fileName());a.setFileHash(metadata.sha256());a.setVisibility("PLATFORM");a.setUploadedBy(uid);a.setUploadedAt(LocalDateTime.now());repo.attachment(a);return a;}
|
||||
@Transactional public void deleteAttachment(long id,long attachmentId){required(id);if(repo.deleteAttachment(id,attachmentId)!=1)throw new BizException(ResultCode.NOT_FOUND,"附件引用不存在");}
|
||||
@Transactional public TruckAppointmentEntity create(long uid,String name,SaveRequest r){if(r.expectedVersion()==null||r.expectedVersion()!=0)throw bad("新增 expectedVersion 必须为0");validateRequired(r); var customer=customers.active(r.customerId(),r.customerName()); AssociationContext links=validateAssociations(r,customer.id()); var carrier=suppliers.trucking(r.carrierId(),r.carrierName()); TruckAppointmentEntity e=new TruckAppointmentEntity(); e.setId(IdWorker.getId()); e.setAppointmentNo("TA"+e.getId()); e.setStatus(TruckAppointmentStatus.DRAFT); e.setRevision(0); e.setVersion(0); e.setCreatedAt(LocalDateTime.now()); e.setUpdatedAt(e.getCreatedAt()); copy(e,r,links,customer.tenantName(),carrier.name()); repo.insert(e); record(e,null,e.getStatus().name(),"CREATE",null,uid,name); return e;}
|
||||
@Transactional public TruckAppointmentEntity create(long uid,String name,SaveRequest r){if(r.expectedVersion()==null||r.expectedVersion()!=0)throw bad("新增 expectedVersion 必须为0");validateRequired(r); var customer=customers.active(r.customerId(),r.customerName()); AssociationContext links=validateAssociations(r,customer.id()); var carrier=suppliers.trucking(r.carrierId(),r.carrierName()); TruckAppointmentEntity e=new TruckAppointmentEntity(); e.setId(IdWorker.getId()); e.setAppointmentNo(documentNumbers.next("TA")); e.setStatus(TruckAppointmentStatus.DRAFT); e.setRevision(0); e.setVersion(0); e.setCreatedAt(LocalDateTime.now()); e.setUpdatedAt(e.getCreatedAt()); copy(e,r,links,customer.tenantName(),carrier.name()); repo.insert(e); record(e,null,e.getStatus().name(),"CREATE",null,uid,name); return e;}
|
||||
@Transactional public TruckAppointmentEntity edit(long id,long uid,String name,SaveRequest r){validateRequired(r);TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if(!(e.getStatus()==TruckAppointmentStatus.DRAFT||e.getStatus()==TruckAppointmentStatus.PENDING_DISPATCH||e.getStatus()==TruckAppointmentStatus.RESCHEDULED))throw conflict("当前状态不可编辑"); var customer=customers.active(r.customerId(),r.customerName()); AssociationContext links=validateAssociations(r,customer.id()); var carrier=suppliers.trucking(r.carrierId(),r.carrierName()); copy(e,r,links,customer.tenantName(),carrier.name()); if(repo.updateEdit(e,r.expectedVersion())!=1)throw conflict("版本已变化,请刷新后重试"); record(e,e.getStatus().name(),e.getStatus().name(),"EDIT",null,uid,name); return required(id);}
|
||||
@Transactional public TruckAppointmentEntity action(long id,String action,ActionRequest r,String idem,String clientHash,long uid,String name){String requestHash=hash(action,id,r);verifyClientHash(clientHash,requestHash);TruckAppointmentEntity replay=claimOrReplay(id,action,idem,requestHash);if(replay!=null)return replay;TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if("RESCHEDULE".equals(action))throw conflict("改约请使用专用接口"); TruckAppointmentStatus from=e.getStatus(),to=next(from,action); String dispatchBill="DISPATCH".equals(action)?requireDispatchReady(e):null; int changed="DISPATCH".equals(action)?repo.dispatch(id,from.name(),dispatchBill,e.getVersion()):repo.transition(id,from.name(),to.name(),e.getVersion()); if(changed!=1)throw conflict("状态或版本已变化,请刷新后重试"); TruckAppointmentEntity after=required(id);record(after,from.name(),to.name(),action,r.reason(),uid,name);return completeOperation(id,action,idem,after);}
|
||||
@Transactional public TruckAppointmentEntity reschedule(long id,LocalDateTime at,ActionRequest r,String idem,String clientHash,long uid,String name){String action="RESCHEDULE";String requestHash=hash(action,id,new ReschedulePayload(at,r));verifyClientHash(clientHash,requestHash);TruckAppointmentEntity replay=claimOrReplay(id,action,idem,requestHash);if(replay!=null)return replay;TruckAppointmentEntity e=required(id);checkVersion(e,r.expectedVersion()); if(e.getStatus()==TruckAppointmentStatus.COMPLETED||e.getStatus()==TruckAppointmentStatus.CANCELLED)throw conflict("当前状态不可改约"); String from=e.getStatus().name(); if(repo.reschedule(id,at,r.reason(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试");TruckAppointmentEntity after=required(id);record(after,from,"RESCHEDULED",action,r.reason(),uid,name);return completeOperation(id,action,idem,after);}
|
||||
|
||||
+8
@@ -26,6 +26,14 @@ public class WarehouseSupplierSnapshotService {
|
||||
return resolve(supplierId, suppliedName, "CUSTOMS");
|
||||
}
|
||||
|
||||
/** 费用来源只提交供应商 ID,名称与类别均以 Warehouse 返回快照为准。 */
|
||||
public WarehouseSupplierClient.Snapshot fee(Long supplierId, String category) {
|
||||
if (!StringUtils.hasText(category)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "费用项目未配置供应商类别");
|
||||
}
|
||||
return resolve(supplierId, null, category);
|
||||
}
|
||||
|
||||
private WarehouseSupplierClient.Snapshot resolve(Long supplierId, String suppliedName,
|
||||
String category) {
|
||||
if (supplierId == null || supplierId <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user