统一财务费用来源入池链路
This commit is contained in:
+5
-3
@@ -10,7 +10,7 @@ import com.baiyue.lastleg.dto.LastlegOrderSummary;
|
||||
import com.baiyue.lastleg.dto.LastlegOrderCommandDtos;
|
||||
import com.baiyue.lastleg.dto.ContainerPlanProgressDetail;
|
||||
import com.baiyue.lastleg.dto.ContainerPlanProgressSummary;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderFee;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.security.InternalServiceTokenGuard;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import com.baiyue.lastleg.service.LastlegOrderService;
|
||||
@@ -132,12 +132,14 @@ public class LastlegOrderController {
|
||||
@PostMapping("/api/platform/v1/last-leg/orders/{id}/fees")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')")
|
||||
@Operation(summary = "Add a fee to one tail-leg order", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT)
|
||||
public ApiResponse<LastlegOrderFee> createFee(
|
||||
public ApiResponse<OverseasFeeSource> createFee(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId,
|
||||
@PathVariable long id,
|
||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||
@Valid @RequestBody LastlegOrderRequests.CreateFee request) {
|
||||
return ApiResponse.success(orderService.createFee(platformScope(user, customerId), id, request, operatorName(user)));
|
||||
return ApiResponse.success(orderService.createFee(platformScope(user, customerId), id, request,
|
||||
operatorName(user), idempotencyKey));
|
||||
}
|
||||
|
||||
@PutMapping("/api/platform/v1/last-leg/orders/{id}/hold")
|
||||
|
||||
+18
-2
@@ -9,6 +9,7 @@ import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public final class DeliveryRequests {
|
||||
private DeliveryRequests() {}
|
||||
@@ -31,8 +32,23 @@ public final class DeliveryRequests {
|
||||
@NotNull @Valid @Size(min = 1, max = 20) List<FeeItem> fees) {}
|
||||
|
||||
public record FeeItem(@NotBlank @Size(max = 64) String feeType,
|
||||
@NotBlank @Size(max = 8) String currency,
|
||||
@NotNull @Min(0) BigDecimal amount,
|
||||
@NotBlank @Size(max = 128) String feeName,
|
||||
@NotBlank @jakarta.validation.constraints.Pattern(regexp = "RECEIVABLE|PAYABLE") String direction,
|
||||
@NotBlank @jakarta.validation.constraints.Pattern(regexp = "CNY|USD") String currency,
|
||||
@NotNull @jakarta.validation.constraints.DecimalMin(value = "0.00", inclusive = false) BigDecimal amount,
|
||||
@Min(1) Long supplierId,
|
||||
@Size(max = 200) String supplierName,
|
||||
@Size(max = 32) String supplierCategory,
|
||||
@jakarta.validation.constraints.Pattern(regexp = "ON_ACCOUNT|DIRECT_PAID") String settlementMode,
|
||||
LocalDateTime paidAt,
|
||||
@jakarta.validation.constraints.Pattern(regexp = "ONLINE|BANK_TRANSFER|CASH|OTHER") String paymentMethod,
|
||||
@Size(max = 128) String paymentReferenceNo,
|
||||
@Size(max = 20) List<@NotBlank String> paymentVoucherFileIds,
|
||||
@jakarta.validation.constraints.DecimalMin(value = "0.00", inclusive = false) BigDecimal chargeableQuantity,
|
||||
@Size(max = 32) String quantityUnit,
|
||||
@jakarta.validation.constraints.DecimalMin(value = "0.00", inclusive = false) BigDecimal unitPrice,
|
||||
LocalDate periodStart,
|
||||
LocalDate periodEnd,
|
||||
@Size(max = 500) String remark) {}
|
||||
|
||||
public record Query(@Min(1) long page, @Min(1) @Max(200) long size,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.baiyue.lastleg.delivery.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("ll_delivery_fee")
|
||||
public class DeliveryFee {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String customerId;
|
||||
private Long deliveryOrderId;
|
||||
private String feeDirection;
|
||||
private String feeType;
|
||||
private String currency;
|
||||
private BigDecimal amount;
|
||||
private String remark;
|
||||
private String createdBy;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.baiyue.lastleg.delivery.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.delivery.entity.DeliveryFee;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface DeliveryFeeMapper extends BaseMapper<DeliveryFee> {
|
||||
@Select("SELECT * FROM ll_delivery_fee WHERE customer_id=#{customerId} AND delivery_order_id=#{deliveryOrderId} ORDER BY id")
|
||||
List<DeliveryFee> findByDelivery(@Param("customerId") String customerId,
|
||||
@Param("deliveryOrderId") Long deliveryOrderId);
|
||||
}
|
||||
+31
-10
@@ -7,13 +7,14 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.DeliveryFee;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.delivery.entity.DeliveryOperation;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.model.DeliveryStatusPolicy;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryFeeMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
@@ -56,6 +57,7 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import java.math.BigDecimal;
|
||||
@@ -68,7 +70,7 @@ public class DeliveryTransferService {
|
||||
@Resource private OutboundOrderMapper outboundOrders;
|
||||
@Resource private OutboundPalletItemMapper outboundPallets;
|
||||
@Resource private DeliveryOrderMapper deliveryOrders;
|
||||
@Resource private DeliveryFeeMapper deliveryFees;
|
||||
@Resource private OverseasFeeSourceService feeSources;
|
||||
@Resource private DeliveryOperationMapper deliveryOperations;
|
||||
@Resource private OutboundPdaTaskMapper pdaTasks;
|
||||
@Resource private OutboundPdaPickMapper pdaPicks;
|
||||
@@ -184,9 +186,18 @@ public class DeliveryTransferService {
|
||||
delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery);
|
||||
List<DeliveryRequests.FeeItem> feeInputs = request.fees() == null ? List.of() : request.fees();
|
||||
for (DeliveryRequests.FeeItem input : feeInputs) {
|
||||
DeliveryFee fee = new DeliveryFee(); fee.setCustomerId(customerId); fee.setDeliveryOrderId(delivery.getId());
|
||||
fee.setFeeDirection("PAYABLE"); fee.setFeeType(input.feeType().trim()); fee.setCurrency(input.currency().trim().toUpperCase(Locale.ROOT));
|
||||
fee.setAmount(input.amount()); fee.setRemark(trim(input.remark())); fee.setCreatedBy(operatorId); fee.setCreatedAt(now); deliveryFees.insert(fee);
|
||||
feeSources.publishApproved(operatorId, childKey(request.idempotencyKey(), input.feeType()),
|
||||
new OverseasFeeSourceDtos.SaveRequest(customerId, delivery.getCustomerName(),
|
||||
"OVERSEAS_DELIVERY_FEE", "DELIVERY_ORDER", delivery.getId(), delivery.getDeliveryNo(),
|
||||
firstOrder == null ? null : firstOrder.getId(), null,
|
||||
firstOrder == null ? null : firstOrder.getInboundCode(), null,
|
||||
firstOrder == null ? null : parseLong(firstOrder.getSourceContainerBookingId()),
|
||||
firstOrder == null ? null : firstOrder.getContainerNo(), parseLong(delivery.getOverseasWarehouseId()),
|
||||
input.feeType().trim(), input.feeName().trim(), input.direction(), input.supplierId(),
|
||||
input.supplierName(), input.supplierCategory(), input.settlementMode(), input.paidAt(),
|
||||
input.paymentMethod(), input.paymentReferenceNo(), input.paymentVoucherFileIds(), LocalDate.now(),
|
||||
input.currency(), input.amount(), input.chargeableQuantity(), input.quantityUnit(), input.unitPrice(),
|
||||
input.periodStart(), input.periodEnd()));
|
||||
}
|
||||
recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "CREATED", now);
|
||||
bindTailOrders(customerId, items, outbound.getOutboundNo(), delivery.getDeliveryNo(), request.remark(), now);
|
||||
@@ -202,8 +213,9 @@ public class DeliveryTransferService {
|
||||
String feeType = input.feeType().trim().toUpperCase(Locale.ROOT);
|
||||
if (!allowed.contains(feeType)) throw conflict("Unsupported delivery fee type: " + feeType);
|
||||
if (!seen.add(feeType)) throw conflict("Duplicate delivery fee type: " + feeType);
|
||||
if (!"USD".equalsIgnoreCase(input.currency().trim())) throw conflict("Delivery fees must use USD");
|
||||
if (input.amount().signum() < 0) throw conflict("Delivery fee amount cannot be negative");
|
||||
if (!Set.of("CNY", "USD").contains(input.currency().trim().toUpperCase(Locale.ROOT)))
|
||||
throw conflict("Delivery fees only support CNY or USD");
|
||||
if (input.amount().signum() <= 0) throw conflict("Delivery fee amount must be greater than zero");
|
||||
}
|
||||
if (!seen.contains("DELIVERY_FEE")) throw conflict("Delivery fee is required");
|
||||
if (Set.of("FLOOR", "MIXED").contains(request.loadingType().trim().toUpperCase(Locale.ROOT))
|
||||
@@ -559,7 +571,7 @@ public class DeliveryTransferService {
|
||||
new LambdaQueryWrapper<LastlegOrderDetail>().eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, orderIds));
|
||||
List<DeliveryResponses.DeliverySku> skus = details.stream().map(detail -> sku(detail, ordersById.get(detail.getOrderId()))).toList();
|
||||
return new DeliveryResponses.DeliveryDetail(deliveryRow(delivery), skus,
|
||||
deliveryFees.findByDelivery(customerId, delivery.getId()).stream().map(this::feeRow).toList(),
|
||||
feeSources.listForBusiness(customerId, "DELIVERY_ORDER", delivery.getId()).stream().map(this::feeRow).toList(),
|
||||
deliveryOperations.findByDelivery(customerId, delivery.getId()).stream().map(this::operationRow).toList());
|
||||
}
|
||||
|
||||
@@ -616,7 +628,7 @@ public class DeliveryTransferService {
|
||||
}
|
||||
private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt(), value(d.getVersion()), DeliveryStatusPolicy.allowedActions(d.getStatus()), d.getBolNo(), com.baiyue.lastleg.delivery.model.DeliveryDocumentSnapshot.summary(d.getDocumentsJson())); }
|
||||
private DeliveryResponses.DeliverySku sku(LastlegOrderDetail d, LastlegOrder order) { return new DeliveryResponses.DeliverySku(order == null ? null : order.getInboundCode(), order == null ? null : order.getCustomerName(), d.getDestinationWarehouseCode(), null, d.getShipmentNo(), d.getFbaTrackingNo(), d.getSkuCode(), firstText(d.getProductNameZh(), d.getProductNameEn()), value(d.getPackageCount()), d.getGrossWeightKg(), d.getVolumeCbm()); }
|
||||
private DeliveryResponses.DeliveryFeeRow feeRow(DeliveryFee f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getFeeDirection(), f.getFeeType(), f.getCurrency(), f.getAmount(), f.getRemark()); }
|
||||
private DeliveryResponses.DeliveryFeeRow feeRow(OverseasFeeSource f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getDirection(), f.getFeeItemCode(), f.getOriginalCurrency(), f.getOriginalAmount(), f.getReviewReason()); }
|
||||
private DeliveryResponses.DeliveryOperationRow operationRow(DeliveryOperation o) { return new DeliveryResponses.DeliveryOperationRow(o.getId(), o.getCreatedAt(), o.getOperationNode(), firstText(o.getOperatorName(), o.getOperatorId()), o.getResult()); }
|
||||
private void recordOperation(String customerId, Long deliveryId, String operatorId, String node, String result, LocalDateTime now) { DeliveryOperation operation = new DeliveryOperation(); operation.setCustomerId(customerId); operation.setDeliveryOrderId(deliveryId); operation.setOperationNode(node); operation.setOperatorId(operatorId); operation.setOperatorName(operatorId); operation.setResult(result); operation.setCreatedAt(now); deliveryOperations.insert(operation); }
|
||||
private LastlegOrder firstTailOrder(String customerId, List<DispatchItem> items) { return items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).map(tailOrders::selectById).filter(Objects::nonNull).filter(order -> customerId.equals(order.getCustomerId())).findFirst().orElse(null); }
|
||||
@@ -632,6 +644,15 @@ public class DeliveryTransferService {
|
||||
}
|
||||
private BigDecimal totalWeight(String customerId, List<DispatchItem> items) { List<Long> ids = items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList(); if (ids.isEmpty()) return BigDecimal.ZERO; return tailOrderDetails.selectList(new LambdaQueryWrapper<LastlegOrderDetail>().eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, ids)).stream().map(LastlegOrderDetail::getGrossWeightKg).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
|
||||
private static Long parseLong(String value) {
|
||||
try { return StringUtils.hasText(value) ? Long.parseLong(value.trim()) : null; }
|
||||
catch (NumberFormatException ignored) { return null; }
|
||||
}
|
||||
private static String childKey(String key, String suffix) {
|
||||
String prefix = key == null ? "" : key.trim();
|
||||
int maxPrefix = Math.max(0, 128 - suffix.length() - 1);
|
||||
return prefix.substring(0, Math.min(prefix.length(), maxPrefix)) + ':' + suffix;
|
||||
}
|
||||
private static String required(String value, String message) {
|
||||
String normalized = trim(value);
|
||||
if (normalized == null) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.baiyue.lastleg.dto;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderFee;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -12,7 +12,7 @@ public record LastlegOrderDetailResponse(
|
||||
List<LastlegOrderCommandDtos.ManualNodeView> manualNodes,
|
||||
List<LastlegOrderDetail> cargo,
|
||||
List<History> statusHistory,
|
||||
List<LastlegOrderFee> fees,
|
||||
List<OverseasFeeSource> fees,
|
||||
List<Operation> operationHistory
|
||||
) {
|
||||
public record History(Long id, String fromStatus, String toStatus, String remark, LocalDateTime createdAt) {}
|
||||
|
||||
@@ -11,6 +11,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public final class LastlegOrderRequests {
|
||||
@@ -81,11 +82,25 @@ public final class LastlegOrderRequests {
|
||||
}
|
||||
|
||||
public record CreateFee(
|
||||
@NotBlank @Size(max = 128) String feeItem,
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
||||
@NotBlank @Size(max = 64) String feeItemCode,
|
||||
@NotBlank @Size(max = 200) String feeItemName,
|
||||
@NotBlank @Pattern(regexp = "RECEIVABLE|PAYABLE") String direction,
|
||||
@NotBlank @Pattern(regexp = "CNY|USD") String currency,
|
||||
@NotNull @DecimalMin(value = "0.01") BigDecimal amount,
|
||||
@NotBlank @Pattern(regexp = "PENDING_REVIEW|APPROVED|REJECTED") String status,
|
||||
@Size(max = 500) String supplierDescription
|
||||
@NotNull LocalDate businessDate,
|
||||
@Min(1) Long supplierId,
|
||||
@Size(max = 200) String supplierName,
|
||||
@Size(max = 32) String supplierCategory,
|
||||
@Pattern(regexp = "ON_ACCOUNT|DIRECT_PAID") String settlementMode,
|
||||
LocalDateTime paidAt,
|
||||
@Pattern(regexp = "ONLINE|BANK_TRANSFER|CASH|OTHER") String paymentMethod,
|
||||
@Size(max = 128) String paymentReferenceNo,
|
||||
@Size(max = 20) List<@NotBlank String> paymentVoucherFileIds,
|
||||
@DecimalMin(value = "0.00", inclusive = false) BigDecimal chargeableQuantity,
|
||||
@Size(max = 32) String quantityUnit,
|
||||
@DecimalMin(value = "0.00", inclusive = false) BigDecimal unitPrice,
|
||||
LocalDate periodStart,
|
||||
LocalDate periodEnd
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.baiyue.lastleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("ll_order_fee")
|
||||
public class LastlegOrderFee {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private String customerId;
|
||||
private Long orderId;
|
||||
private String feeItem;
|
||||
private String currency;
|
||||
private BigDecimal amount;
|
||||
private String status;
|
||||
private String supplierDescription;
|
||||
private String createdBy;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+2
-2
@@ -20,8 +20,8 @@ public final class OverseasFeeSourceDtos {
|
||||
public record SaveRequest(
|
||||
@NotBlank @Size(max = 128) String customerId,
|
||||
@Size(max = 200) String customerName,
|
||||
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND_FEE|OVERSEAS_PROCUREMENT_FEE|OVERSEAS_TRUCK_FEE|OVERSEAS_LOOSEBOARD_FEE|OVERSEAS_WAREHOUSE_FEE|OVERSEAS_CONTAINER_FEE") String sourceType,
|
||||
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND|OVERSEAS_PROCUREMENT|US_TRUCK|LOCAL_LOOSEBOARD|WAREHOUSE_SERVICE|OVERSEAS_CONTAINER") String businessType,
|
||||
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND_FEE|OVERSEAS_PROCUREMENT_FEE|OVERSEAS_TRUCK_FEE|OVERSEAS_LOOSEBOARD_FEE|OVERSEAS_WAREHOUSE_FEE|OVERSEAS_CONTAINER_FEE|OVERSEAS_ORDER_FEE|OVERSEAS_DELIVERY_FEE|OVERSEAS_RELABEL_FEE") String sourceType,
|
||||
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND|OVERSEAS_PROCUREMENT|PROCUREMENT_FEE|US_TRUCK|LOCAL_LOOSEBOARD|WAREHOUSE_SERVICE|OVERSEAS_CONTAINER|LASTLEG_ORDER|DELIVERY_ORDER|RELABEL_ORDER") String businessType,
|
||||
@Min(1) Long businessId,
|
||||
@NotBlank @Size(max = 128) String businessNo,
|
||||
@Min(1) Long orderId,
|
||||
|
||||
+26
@@ -2,7 +2,11 @@ package com.baiyue.lastleg.financefee.service;
|
||||
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos.SaveRequest;
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos.ContainerCustomerOption;
|
||||
import com.baiyue.lastleg.client.DomesticWarehouseDirectoryClient;
|
||||
import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -14,6 +18,8 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class OverseasFeeBusinessGuard {
|
||||
private final JdbcTemplate jdbc;
|
||||
@Resource private DomesticWarehouseDirectoryClient warehouses;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
public List<ContainerCustomerOption> listContainerCustomers(long containerBookingId) {
|
||||
List<ContainerCustomerOption> customers = jdbc.query(
|
||||
@@ -37,13 +43,18 @@ public class OverseasFeeBusinessGuard {
|
||||
throw bad("业务记录ID不能为空");
|
||||
if (fee.warehouseId() == null)
|
||||
throw bad("海外仓服务费必须关联海外仓;入仓记录和入仓码按实际业务选填");
|
||||
requireActiveOverseasWarehouse(fee.warehouseId());
|
||||
return;
|
||||
}
|
||||
Table table = switch (fee.businessType()) {
|
||||
case "OVERSEAS_INBOUND" -> new Table("ll_inbound_plan", "plan_no");
|
||||
case "OVERSEAS_PROCUREMENT" -> new Table("ll_overseas_procurement_order", "order_no");
|
||||
case "PROCUREMENT_FEE" -> new Table("ll_overseas_procurement_fee", "fee_no");
|
||||
case "US_TRUCK" -> new Table("ll_us_truck_order", "order_no");
|
||||
case "LOCAL_LOOSEBOARD" -> new Table("ll_local_looseboard_order", "order_no");
|
||||
case "LASTLEG_ORDER" -> new Table("ll_order", "order_no");
|
||||
case "DELIVERY_ORDER" -> new Table("ll_delivery_order", "delivery_no");
|
||||
case "RELABEL_ORDER" -> new Table("ll_relabel_application", "relabel_no");
|
||||
default -> throw bad("业务类型与业务记录ID不匹配");
|
||||
};
|
||||
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM " + table.name
|
||||
@@ -83,6 +94,21 @@ public class OverseasFeeBusinessGuard {
|
||||
if (owned == null || owned < 1) throw bad("该客户不属于费用关联的真实货柜");
|
||||
}
|
||||
|
||||
private void requireActiveOverseasWarehouse(long warehouseId) {
|
||||
try {
|
||||
var response = warehouses.activeWarehouse(warehouseId, internalToken);
|
||||
var warehouse = response == null || response.data() == null ? null : response.data().warehouse();
|
||||
if (warehouse == null || !response.successful() || !Long.valueOf(warehouseId).equals(warehouse.id())
|
||||
|| !Long.valueOf(0L).equals(warehouse.tenantId()) || !"ACTIVE".equals(warehouse.status())
|
||||
|| !List.of("OVERSEAS", "OVERSEAS_PARTNER").contains(warehouse.warehouseType())) {
|
||||
throw bad("海外仓不存在、已停用或类型不正确");
|
||||
}
|
||||
} catch (FeignException exception) {
|
||||
if (exception.status() >= 400 && exception.status() < 500) throw bad("海外仓不存在、已停用或类型不正确");
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "仓库主数据暂不可用", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean blank(String value) { return value == null || value.isBlank(); }
|
||||
private static ResponseStatusException bad(String message) {
|
||||
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
|
||||
|
||||
+35
@@ -53,6 +53,15 @@ public class OverseasFeeSourceService {
|
||||
.orderByDesc(OverseasFeeSource::getCreatedAt).orderByDesc(OverseasFeeSource::getId));
|
||||
}
|
||||
|
||||
public List<OverseasFeeSource> listForBusiness(String customerId, String businessType, long businessId) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
.eq(OverseasFeeSource::getCustomerId, customerId)
|
||||
.eq(OverseasFeeSource::getBusinessType, businessType)
|
||||
.eq(OverseasFeeSource::getBusinessId, businessId)
|
||||
.inSql(OverseasFeeSource::getId, "SELECT MAX(v.id) FROM ll_finance_fee_source v GROUP BY v.source_fee_id")
|
||||
.orderByAsc(OverseasFeeSource::getCreatedAt).orderByAsc(OverseasFeeSource::getId));
|
||||
}
|
||||
|
||||
public List<ContainerCustomerOption> listContainerCustomers(long containerBookingId) {
|
||||
return businessGuard.listContainerCustomers(containerBookingId);
|
||||
}
|
||||
@@ -70,6 +79,22 @@ public class OverseasFeeSourceService {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OverseasFeeSource publishApproved(String operatorId, String key, SaveRequest request) {
|
||||
OverseasFeeSource value = create(operatorId, key, request);
|
||||
if ("DRAFT".equals(value.getReviewStatus()) || "REJECTED".equals(value.getReviewStatus())) {
|
||||
value = submit(value.getSourceFeeId(), operatorId, childKey(key, "submit"));
|
||||
}
|
||||
if ("PENDING".equals(value.getReviewStatus())) {
|
||||
value = review(value.getSourceFeeId(), operatorId,
|
||||
new ReviewRequest("APPROVE", "业务完成自动确认费用", childKey(key, "approve")));
|
||||
}
|
||||
if (!"APPROVED".equals(value.getReviewStatus()) || !"PUBLISHED".equals(value.getPublicationStatus())) {
|
||||
throw conflict("业务费用未能审核发布");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OverseasFeeSource submit(long sourceFeeId, String operatorId, String key) {
|
||||
OverseasFeeCommand replay = replay(operatorId, "SUBMIT", key, sourceFeeId);
|
||||
@@ -168,10 +193,14 @@ public class OverseasFeeSourceService {
|
||||
String expected = switch (value.businessType()) {
|
||||
case "OVERSEAS_INBOUND" -> "OVERSEAS_INBOUND_FEE";
|
||||
case "OVERSEAS_PROCUREMENT" -> "OVERSEAS_PROCUREMENT_FEE";
|
||||
case "PROCUREMENT_FEE" -> "OVERSEAS_PROCUREMENT_FEE";
|
||||
case "US_TRUCK" -> "OVERSEAS_TRUCK_FEE";
|
||||
case "LOCAL_LOOSEBOARD" -> "OVERSEAS_LOOSEBOARD_FEE";
|
||||
case "WAREHOUSE_SERVICE" -> "OVERSEAS_WAREHOUSE_FEE";
|
||||
case "OVERSEAS_CONTAINER" -> "OVERSEAS_CONTAINER_FEE";
|
||||
case "LASTLEG_ORDER" -> "OVERSEAS_ORDER_FEE";
|
||||
case "DELIVERY_ORDER" -> "OVERSEAS_DELIVERY_FEE";
|
||||
case "RELABEL_ORDER" -> "OVERSEAS_RELABEL_FEE";
|
||||
default -> "";
|
||||
};
|
||||
if (!expected.equals(value.sourceType())) throw bad("来源类型与业务类型不匹配");
|
||||
@@ -212,6 +241,12 @@ public class OverseasFeeSourceService {
|
||||
if (existing != null && !existing.getRequestHash().equals(hash(request))) throw conflict("幂等键已被不同请求使用");
|
||||
return existing;
|
||||
}
|
||||
|
||||
private static String childKey(String key, String suffix) {
|
||||
String prefix = key == null ? "" : key.trim();
|
||||
int maxPrefix = Math.max(0, 128 - suffix.length() - 1);
|
||||
return prefix.substring(0, Math.min(prefix.length(), maxPrefix)) + ':' + suffix;
|
||||
}
|
||||
private void command(String operator, String action, String key, Object request, long sourceFeeId, int version) {
|
||||
OverseasFeeCommand value = new OverseasFeeCommand(); value.setId(IdWorker.getId()); value.setOperatorId(operator);
|
||||
value.setAction(action); value.setIdempotencyKey(key.trim()); value.setRequestHash(hash(request)); value.setSourceFeeId(sourceFeeId);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.baiyue.lastleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderFee;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LastlegOrderFeeMapper extends BaseMapper<LastlegOrderFee> {
|
||||
}
|
||||
+21
@@ -16,6 +16,8 @@ import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFeeMapper;
|
||||
import com.baiyue.lastleg.procurement.gateway.ProcurementFileGateway;
|
||||
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
|
||||
import com.baiyue.lastleg.wallet.WalletResultHandler;
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.common.event.WalletCommandCompletedV1;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOrderMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementStatusHistoryMapper;
|
||||
@@ -33,7 +35,9 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -62,6 +66,7 @@ public class OverseasProcurementService implements WalletResultHandler {
|
||||
@Resource private OverseasProcurementFeeMapper procurementFees;
|
||||
@Resource private ProcurementFileGateway fileGateway;
|
||||
@Resource private WalletCommandOutboxService walletCommands;
|
||||
@Resource private OverseasFeeSourceService feeSources;
|
||||
|
||||
@Transactional
|
||||
public OverseasProcurementResponses.Detail create(String customerId, String operatorId,
|
||||
@@ -220,6 +225,7 @@ public class OverseasProcurementService implements WalletResultHandler {
|
||||
if ("FREEZE".equals(result.operation()) && "SUCCEEDED".equals(result.result())) {
|
||||
OverseasProcurementOrder order = orders.selectById(row.getOrderId());
|
||||
if (order != null && order.getInboundQuantity() >= order.getPurchaseQuantity()) {
|
||||
publishFeeSource(order, row);
|
||||
walletCommands.publish("SETTLE", result.customerId(), "PROCUREMENT_FEE", row.getFeeNo(), null,
|
||||
"海外采购完成入库,结算费用", 2L);
|
||||
row.setWalletStatus("SETTLE_PENDING"); procurementFees.updateById(row);
|
||||
@@ -313,6 +319,7 @@ public class OverseasProcurementService implements WalletResultHandler {
|
||||
if (order.getInboundQuantity() >= order.getPurchaseQuantity()) {
|
||||
for (OverseasProcurementFee fee : procurementFees.selectList(new LambdaQueryWrapper<OverseasProcurementFee>()
|
||||
.eq(OverseasProcurementFee::getOrderId, order.getId()).eq(OverseasProcurementFee::getWalletStatus, "FROZEN"))) {
|
||||
publishFeeSource(order, fee);
|
||||
walletCommands.publish("SETTLE", Long.parseLong(customerId), "PROCUREMENT_FEE", fee.getFeeNo(), null,
|
||||
"海外采购完成入库,结算费用", 2L);
|
||||
fee.setWalletStatus("SETTLE_PENDING"); fee.setUpdatedAt(now); procurementFees.updateById(fee);
|
||||
@@ -321,6 +328,16 @@ public class OverseasProcurementService implements WalletResultHandler {
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
private void publishFeeSource(OverseasProcurementOrder order, OverseasProcurementFee fee) {
|
||||
feeSources.publishApproved(StringUtils.hasText(fee.getCreatedBy()) ? fee.getCreatedBy() : "SYSTEM", "PROCUREMENT:" + fee.getFeeNo(),
|
||||
new OverseasFeeSourceDtos.SaveRequest(order.getCustomerId(), order.getCustomerName(),
|
||||
"OVERSEAS_PROCUREMENT_FEE", "PROCUREMENT_FEE", fee.getId(), fee.getFeeNo(),
|
||||
order.getId(), null, null, null, null, null, parseLong(order.getPurchaseWarehouseId()),
|
||||
fee.getFeeItemCode(), fee.getFeeItemName(), "RECEIVABLE", null, null, null, null,
|
||||
null, null, null, List.of(), LocalDate.now(), fee.getCurrency(), fee.getAmount(),
|
||||
BigDecimal.ONE, "ITEM", fee.getAmount(), null, null));
|
||||
}
|
||||
|
||||
private OverseasProcurementResponses.Detail replay(String customerId, OverseasProcurementOrder order, String hash) {
|
||||
if (!hash.equals(order.getRequestHash())) {
|
||||
throw conflict("Idempotency key was reused with different procurement data");
|
||||
@@ -491,6 +508,10 @@ public class OverseasProcurementService implements WalletResultHandler {
|
||||
}
|
||||
|
||||
private static long value(Long value) { return value == null ? 0 : value; }
|
||||
private static Long parseLong(String value) {
|
||||
try { return StringUtils.hasText(value) ? Long.parseLong(value.trim()) : null; }
|
||||
catch (NumberFormatException ignored) { return null; }
|
||||
}
|
||||
private static String trim(String value) { return value == null || value.isBlank() ? null : value.trim(); }
|
||||
private static String defaultCurrency(String value) { return trim(value) == null ? "USD" : value.trim().toUpperCase(); }
|
||||
private static void setIfText(String value, java.util.function.Consumer<String> setter) {
|
||||
|
||||
+22
-2
@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
|
||||
@@ -28,9 +30,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -53,6 +57,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
@Resource private LastlegOrderNoGenerator orderNoGenerator;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private WalletCommandOutboxService walletCommands;
|
||||
@Resource private OverseasFeeSourceService feeSources;
|
||||
|
||||
@Transactional
|
||||
public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) {
|
||||
@@ -184,7 +189,20 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
application.setStatus(next);
|
||||
application.setUpdatedAt(now);
|
||||
applications.updateById(application);
|
||||
List<LastlegOrder> linkedOrders = tailOrders(application);
|
||||
if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing");
|
||||
if ("COMPLETED".equals(next)) {
|
||||
BigDecimal quantity = BigDecimal.valueOf(Objects.requireNonNullElse(application.getTotalPackageCount(), 0));
|
||||
feeSources.publishApproved(operatorId, "RELABEL:" + relabelNo,
|
||||
new OverseasFeeSourceDtos.SaveRequest(customerId, linkedOrders.get(0).getCustomerName(),
|
||||
"OVERSEAS_RELABEL_FEE", "RELABEL_ORDER", application.getId(), relabelNo,
|
||||
application.getLastlegOrderId(), null, null, null, null, null,
|
||||
parseLong(application.getOverseasWarehouseId()), "RELABEL_SERVICE", "换标费", "RECEIVABLE",
|
||||
null, null, null, null, null, null, null, List.of(), LocalDate.now(),
|
||||
application.getChargeCurrency(), application.getChargeAmount(),
|
||||
quantity.signum() > 0 ? quantity : null, quantity.signum() > 0 ? "BOX" : null,
|
||||
quantity.signum() > 0 ? application.getChargeAmount().divide(quantity, 6, java.math.RoundingMode.HALF_UP) : null,
|
||||
null, null));
|
||||
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
|
||||
"换标完成,结算费用", 2L);
|
||||
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
|
||||
@@ -193,8 +211,6 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
"换标取消,释放冻结", 2L);
|
||||
application.setWalletStatus("RELEASE_PENDING"); applications.updateById(application);
|
||||
}
|
||||
List<LastlegOrder> linkedOrders = tailOrders(application);
|
||||
if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing");
|
||||
for (LastlegOrder order : linkedOrders) {
|
||||
String orderBefore = order.getCurrentStatus();
|
||||
String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore;
|
||||
@@ -351,6 +367,10 @@ public class RelabelApplicationService implements WalletResultHandler {
|
||||
.digest(objectMapper.copy().findAndRegisterModules().writeValueAsString(value).getBytes(StandardCharsets.UTF_8))); }
|
||||
catch (Exception exception) { throw new IllegalStateException(exception); }
|
||||
}
|
||||
private static Long parseLong(String value) {
|
||||
try { return StringUtils.hasText(value) ? Long.parseLong(value.trim()) : null; }
|
||||
catch (NumberFormatException ignored) { return null; }
|
||||
}
|
||||
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
|
||||
private static ResponseStatusException badRequest(String message) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); }
|
||||
private static ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.baiyue.lastleg.dto.ContainerPlanProgressSummary;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderFee;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderOperationLog;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderManualNode;
|
||||
import com.baiyue.lastleg.entity.ReceivingWarehouseOperation;
|
||||
@@ -25,7 +24,9 @@ import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderFeeMapper;
|
||||
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeMapper;
|
||||
import com.baiyue.lastleg.mapper.ReceivingWarehouseOperationMapper;
|
||||
@@ -64,7 +65,7 @@ public class LastlegOrderService {
|
||||
@Resource private LastlegOrderMapper orderMapper;
|
||||
@Resource private LastlegOrderDetailMapper detailMapper;
|
||||
@Resource private LastlegOrderStatusHistoryMapper historyMapper;
|
||||
@Resource private LastlegOrderFeeMapper feeMapper;
|
||||
@Resource private OverseasFeeSourceService feeSources;
|
||||
@Resource private LastlegOrderOperationLogMapper operationLogMapper;
|
||||
@Resource private LastlegOrderManualNodeMapper manualNodeMapper;
|
||||
@Resource private InboundPlanCartonMapper cartonMapper;
|
||||
@@ -246,9 +247,7 @@ public class LastlegOrderService {
|
||||
.eq(LastlegOrderStatusHistory::getCustomerId, order.getCustomerId())
|
||||
.eq(LastlegOrderStatusHistory::getOrderId, orderId)
|
||||
.orderByAsc(LastlegOrderStatusHistory::getCreatedAt));
|
||||
List<LastlegOrderFee> fees = feeMapper.selectList(new LambdaQueryWrapper<LastlegOrderFee>()
|
||||
.eq(LastlegOrderFee::getCustomerId, order.getCustomerId())
|
||||
.eq(LastlegOrderFee::getOrderId, orderId).orderByAsc(LastlegOrderFee::getCreatedAt));
|
||||
List<OverseasFeeSource> fees = feeSources.listForBusiness(order.getCustomerId(), "LASTLEG_ORDER", orderId);
|
||||
List<LastlegOrderOperationLog> operations = operationLogMapper.selectList(
|
||||
new LambdaQueryWrapper<LastlegOrderOperationLog>()
|
||||
.eq(LastlegOrderOperationLog::getCustomerId, order.getCustomerId())
|
||||
@@ -270,24 +269,21 @@ public class LastlegOrderService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LastlegOrderFee createFee(String customerId, long orderId, LastlegOrderRequests.CreateFee request,
|
||||
String operatorName) {
|
||||
public OverseasFeeSource createFee(String customerId, long orderId, LastlegOrderRequests.CreateFee request,
|
||||
String operatorName, String idempotencyKey) {
|
||||
LastlegOrder order = findOwned(customerId, orderId);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LastlegOrderFee fee = new LastlegOrderFee();
|
||||
fee.setId(IdWorker.getId());
|
||||
fee.setCustomerId(order.getCustomerId());
|
||||
fee.setOrderId(orderId);
|
||||
fee.setFeeItem(request.feeItem().trim());
|
||||
fee.setCurrency(request.currency());
|
||||
fee.setAmount(request.amount());
|
||||
fee.setStatus(request.status());
|
||||
fee.setSupplierDescription(StringUtils.hasText(request.supplierDescription())
|
||||
? request.supplierDescription().trim() : null);
|
||||
fee.setCreatedBy(operatorName);
|
||||
fee.setCreatedAt(now);
|
||||
fee.setUpdatedAt(now);
|
||||
feeMapper.insert(fee);
|
||||
OverseasFeeSource fee = feeSources.create(operatorName, idempotencyKey,
|
||||
new OverseasFeeSourceDtos.SaveRequest(order.getCustomerId(), order.getCustomerName(),
|
||||
"OVERSEAS_ORDER_FEE", "LASTLEG_ORDER", orderId, order.getOrderNo(), orderId,
|
||||
null, order.getInboundCode(), null, parseLong(order.getSourceContainerBookingId()),
|
||||
order.getContainerNo(), parseLong(order.getOverseasWarehouseId()), request.feeItemCode(),
|
||||
request.feeItemName(), request.direction(), request.supplierId(), request.supplierName(),
|
||||
request.supplierCategory(), request.settlementMode(), request.paidAt(), request.paymentMethod(),
|
||||
request.paymentReferenceNo(), request.paymentVoucherFileIds(), request.businessDate(),
|
||||
request.currency(), request.amount(), request.chargeableQuantity(), request.quantityUnit(),
|
||||
request.unitPrice(), request.periodStart(), request.periodEnd()));
|
||||
fee = feeSources.submit(fee.getSourceFeeId(), operatorName, childKey(idempotencyKey, "submit"));
|
||||
|
||||
LastlegOrderOperationLog operation = new LastlegOrderOperationLog();
|
||||
operation.setId(IdWorker.getId());
|
||||
@@ -295,7 +291,8 @@ public class LastlegOrderService {
|
||||
operation.setOrderId(orderId);
|
||||
operation.setOperationNode("新增费用");
|
||||
operation.setOperatorName(operatorName);
|
||||
operation.setResult("新增" + fee.getFeeItem() + ":" + fee.getCurrency() + " " + fee.getAmount().toPlainString());
|
||||
operation.setResult("新增" + fee.getFeeItemName() + ":" + fee.getOriginalCurrency() + " "
|
||||
+ fee.getOriginalAmount().toPlainString() + ",已提交财务审核");
|
||||
operation.setCreatedAt(now);
|
||||
operationLogMapper.insert(operation);
|
||||
return fee;
|
||||
@@ -585,6 +582,17 @@ public class LastlegOrderService {
|
||||
? null : order.getInboundCode();
|
||||
}
|
||||
|
||||
private static Long parseLong(String value) {
|
||||
try { return StringUtils.hasText(value) ? Long.parseLong(value.trim()) : null; }
|
||||
catch (NumberFormatException ignored) { return null; }
|
||||
}
|
||||
|
||||
private static String childKey(String key, String suffix) {
|
||||
String prefix = key == null ? "" : key.trim();
|
||||
int maxPrefix = Math.max(0, 128 - suffix.length() - 1);
|
||||
return prefix.substring(0, Math.min(prefix.length(), maxPrefix)) + ':' + suffix;
|
||||
}
|
||||
|
||||
private static String csv(Object value) {
|
||||
if (value == null) return "";
|
||||
String text = String.valueOf(value);
|
||||
|
||||
@@ -163,22 +163,6 @@ CREATE TABLE IF NOT EXISTS ll_loading_order_status_history (
|
||||
KEY idx_ll_loading_history_customer_order(customer_id,order_id,created_at)
|
||||
) ENGINE=InnoDB COMMENT='尾程订单状态变更历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_order_fee (
|
||||
id BIGINT NOT NULL COMMENT '费用明细主键',
|
||||
customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键',
|
||||
order_id BIGINT NOT NULL COMMENT '尾程订单ID',
|
||||
fee_item VARCHAR(128) NOT NULL COMMENT '费用项目',
|
||||
currency VARCHAR(3) NOT NULL COMMENT '币种代码',
|
||||
amount DECIMAL(18,2) NOT NULL COMMENT '费用金额',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW' COMMENT '费用状态:PENDING_REVIEW待审核、APPROVED审核通过、REJECTED已驳回',
|
||||
supplier_description VARCHAR(500) NULL COMMENT '供应商或费用说明',
|
||||
created_by VARCHAR(128) NOT NULL COMMENT '创建操作人',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
PRIMARY KEY(id),
|
||||
KEY idx_ll_order_fee_customer_order(customer_id,order_id,created_at)
|
||||
) ENGINE=InnoDB COMMENT='尾程订单费用明细';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ll_order_operation_log (
|
||||
id BIGINT NOT NULL COMMENT '操作记录主键',
|
||||
customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键',
|
||||
@@ -302,12 +286,6 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
|
||||
PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='total_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注'' AFTER pallet_count'));
|
||||
PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt;
|
||||
CREATE TABLE IF NOT EXISTS ll_delivery_fee (
|
||||
id BIGINT NOT NULL COMMENT '派送费用主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID',
|
||||
fee_direction VARCHAR(16) NOT NULL DEFAULT 'PAYABLE' COMMENT '应收应付方向', fee_type VARCHAR(64) NOT NULL COMMENT '费用类型', currency VARCHAR(8) NOT NULL COMMENT '币种',
|
||||
amount DECIMAL(18,2) NOT NULL COMMENT '费用金额', remark VARCHAR(500) NULL COMMENT '费用备注', created_by VARCHAR(128) NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_delivery_fee_order(customer_id,delivery_order_id,id)
|
||||
) ENGINE=InnoDB COMMENT='派送单费用明细';
|
||||
CREATE TABLE IF NOT EXISTS ll_delivery_document_revision (
|
||||
id BIGINT NOT NULL COMMENT '派送资料历史ID',
|
||||
customer_id VARCHAR(128) NOT NULL COMMENT '派送所属客户隔离键',
|
||||
|
||||
@@ -194,11 +194,12 @@ paths:
|
||||
/api/platform/v1/last-leg/orders/{id}/fees:
|
||||
post:
|
||||
summary: 新增尾程订单费用
|
||||
description: 权限 platform:overseas-warehouse:last-mile-order:edit;在订单客户范围内新增费用并记录操作人和处理结果。
|
||||
description: 权限 platform:overseas-warehouse:last-mile-order:edit;在订单客户范围内创建统一费用来源并提交财务审核。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||
- { name: customerId, in: query, required: false, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
|
||||
- { name: Idempotency-Key, in: header, required: true, schema: { type: string, minLength: 1, maxLength: 128 } }
|
||||
requestBody:
|
||||
required: true
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/LastMileFeeCreate' } } }
|
||||
@@ -1331,28 +1332,44 @@ components:
|
||||
plannedArrivalAt: { type: string, format: date-time, nullable: true, description: ll_order.estimated_warehouse_at }
|
||||
LastMileFeeCreate:
|
||||
type: object
|
||||
required: [feeItem, currency, amount, status]
|
||||
required: [feeItemCode, feeItemName, direction, currency, amount, businessDate]
|
||||
properties:
|
||||
feeItem: { type: string, minLength: 1, maxLength: 128 }
|
||||
currency: { type: string, pattern: '^[A-Z]{3}$', example: USD }
|
||||
feeItemCode: { type: string, minLength: 1, maxLength: 64 }
|
||||
feeItemName: { type: string, minLength: 1, maxLength: 128 }
|
||||
direction: { type: string, enum: [RECEIVABLE, PAYABLE] }
|
||||
currency: { type: string, enum: [CNY, USD] }
|
||||
amount: { type: number, format: decimal, minimum: 0.01 }
|
||||
status: { type: string, enum: [PENDING_REVIEW, APPROVED, REJECTED] }
|
||||
supplierDescription: { type: string, maxLength: 500 }
|
||||
businessDate: { type: string, format: date }
|
||||
supplierId: { type: string, pattern: '^[1-9][0-9]*$' }
|
||||
supplierName: { type: string, maxLength: 200 }
|
||||
supplierCategory: { type: string, maxLength: 32 }
|
||||
settlementMode: { type: string, enum: [ON_ACCOUNT, DIRECT_PAID] }
|
||||
chargeableQuantity: { type: number, exclusiveMinimum: 0 }
|
||||
quantityUnit: { type: string, maxLength: 32 }
|
||||
unitPrice: { type: number, exclusiveMinimum: 0 }
|
||||
periodStart: { type: string, format: date }
|
||||
periodEnd: { type: string, format: date }
|
||||
LastMileFee:
|
||||
type: object
|
||||
required: [id, customerId, orderId, feeItem, currency, amount, status, createdBy, createdAt, updatedAt]
|
||||
required: [id, sourceFeeId, sourceVersion, customerId, feeItemCode, feeItemName, direction, currency, amount, originalCurrency, originalAmount, reviewStatus, publicationStatus, sourceStatus]
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
id: { type: string, pattern: '^[1-9][0-9]*$' }
|
||||
sourceFeeId: { type: string, pattern: '^[1-9][0-9]*$' }
|
||||
sourceVersion: { type: integer, minimum: 1 }
|
||||
customerId: { type: string }
|
||||
orderId: { type: integer, format: int64 }
|
||||
feeItem: { type: string }
|
||||
currency: { type: string }
|
||||
orderId: { type: string, pattern: '^[1-9][0-9]*$' }
|
||||
feeItemCode: { type: string }
|
||||
feeItemName: { type: string }
|
||||
direction: { type: string, enum: [RECEIVABLE, PAYABLE] }
|
||||
currency: { type: string, enum: [CNY] }
|
||||
amount: { type: number, format: decimal }
|
||||
status: { type: string }
|
||||
supplierDescription: { type: string, nullable: true }
|
||||
createdBy: { type: string }
|
||||
createdAt: { type: string, format: date-time }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
originalCurrency: { type: string, enum: [CNY, USD] }
|
||||
originalAmount: { type: number, format: decimal }
|
||||
exchangeRate: { type: number, format: decimal }
|
||||
exchangeRateMonth: { type: string, pattern: '^\\d{4}-\\d{2}$' }
|
||||
reviewStatus: { type: string, enum: [DRAFT, PENDING, APPROVED, REJECTED] }
|
||||
publicationStatus: { type: string }
|
||||
sourceStatus: { type: string }
|
||||
LastMileFeeApiResponse:
|
||||
type: object
|
||||
required: [code, message, data]
|
||||
@@ -1842,11 +1859,22 @@ components:
|
||||
enum: [START_PROCESSING, COMPLETE_EXCEPTION, CANCEL_EXCEPTION]
|
||||
DeliveryFeeInput:
|
||||
type: object
|
||||
required: [feeType, currency, amount]
|
||||
required: [feeType, feeName, direction, currency, amount]
|
||||
properties:
|
||||
feeType: { type: string, maxLength: 64 }
|
||||
currency: { type: string, maxLength: 8 }
|
||||
amount: { type: number, minimum: 0 }
|
||||
feeName: { type: string, maxLength: 128 }
|
||||
direction: { type: string, enum: [RECEIVABLE, PAYABLE] }
|
||||
currency: { type: string, enum: [CNY, USD] }
|
||||
amount: { type: number, exclusiveMinimum: 0 }
|
||||
supplierId: { type: string, pattern: '^[1-9][0-9]*$' }
|
||||
supplierName: { type: string, maxLength: 200 }
|
||||
supplierCategory: { type: string, maxLength: 32 }
|
||||
settlementMode: { type: string, enum: [ON_ACCOUNT, DIRECT_PAID] }
|
||||
chargeableQuantity: { type: number, exclusiveMinimum: 0 }
|
||||
quantityUnit: { type: string, maxLength: 32 }
|
||||
unitPrice: { type: number, exclusiveMinimum: 0 }
|
||||
periodStart: { type: string, format: date }
|
||||
periodEnd: { type: string, format: date }
|
||||
remark: { type: string, nullable: true, maxLength: 500 }
|
||||
OneClickOutboundCreate:
|
||||
type: object
|
||||
|
||||
@@ -42,7 +42,7 @@ paths:
|
||||
responses: { '200': { description: 当前费用来源版本 }, '409': { description: 状态、版本或幂等冲突 } }
|
||||
components:
|
||||
schemas:
|
||||
BusinessType: { type: string, enum: [OVERSEAS_INBOUND, OVERSEAS_PROCUREMENT, US_TRUCK, LOCAL_LOOSEBOARD, WAREHOUSE_SERVICE, OVERSEAS_CONTAINER] }
|
||||
BusinessType: { type: string, enum: [OVERSEAS_INBOUND, OVERSEAS_PROCUREMENT, PROCUREMENT_FEE, US_TRUCK, LOCAL_LOOSEBOARD, WAREHOUSE_SERVICE, OVERSEAS_CONTAINER, LASTLEG_ORDER, DELIVERY_ORDER, RELABEL_ORDER] }
|
||||
OverseasContainerFeeItem:
|
||||
type: string
|
||||
description: 海外阶段柜级费用项目:港口费、堆存费、修箱费、吊机费、码头拖车费、其他。
|
||||
@@ -70,7 +70,7 @@ components:
|
||||
required: [customerId, sourceType, businessType, businessNo, feeItemCode, feeItemName, direction, businessDate, currency, amount]
|
||||
properties:
|
||||
customerId: { type: string }
|
||||
sourceType: { type: string, enum: [OVERSEAS_INBOUND_FEE, OVERSEAS_PROCUREMENT_FEE, OVERSEAS_TRUCK_FEE, OVERSEAS_LOOSEBOARD_FEE, OVERSEAS_WAREHOUSE_FEE, OVERSEAS_CONTAINER_FEE] }
|
||||
sourceType: { type: string, enum: [OVERSEAS_INBOUND_FEE, OVERSEAS_PROCUREMENT_FEE, OVERSEAS_TRUCK_FEE, OVERSEAS_LOOSEBOARD_FEE, OVERSEAS_WAREHOUSE_FEE, OVERSEAS_CONTAINER_FEE, OVERSEAS_ORDER_FEE, OVERSEAS_DELIVERY_FEE, OVERSEAS_RELABEL_FEE] }
|
||||
businessType: { $ref: '#/components/schemas/BusinessType' }
|
||||
businessId: { type: string, pattern: '^[1-9][0-9]*$', nullable: true }
|
||||
businessNo: { type: string, maxLength: 128 }
|
||||
|
||||
+3
-1
@@ -134,6 +134,8 @@ class MerchantDeliveryControllerTest {
|
||||
private static DeliveryRequests.Transfer transferRequest() {
|
||||
return new DeliveryRequests.Transfer("merchant-key", LocalDateTime.now(), "SELF_DELIVERY", null, null,
|
||||
"ONE_PICKUP_ONE_DROP", "PALLET", null, null, null, null, null, null, null, null,
|
||||
List.of(new DeliveryRequests.FeeItem("DELIVERY_FEE", "USD", BigDecimal.ZERO, null)));
|
||||
List.of(new DeliveryRequests.FeeItem("DELIVERY_FEE", "派送费", "RECEIVABLE", "CNY",
|
||||
new BigDecimal("10.00"), null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null)));
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -8,12 +8,12 @@ import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPdaTask;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryFeeMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaTaskMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaPickMapper;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
|
||||
@@ -67,7 +67,7 @@ class DeliveryTransferServiceTest {
|
||||
@Mock OutboundOrderMapper outboundOrders;
|
||||
@Mock OutboundPalletItemMapper outboundPallets;
|
||||
@Mock DeliveryOrderMapper deliveryOrders;
|
||||
@Mock DeliveryFeeMapper deliveryFees;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
@Mock DeliveryOperationMapper deliveryOperations;
|
||||
@Mock InboundPlanPalletAllocationMapper planAllocations;
|
||||
@Mock InboundPlanLineMapper planLines;
|
||||
@@ -92,7 +92,7 @@ class DeliveryTransferServiceTest {
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "outboundPallets", outboundPallets);
|
||||
ReflectionTestUtils.setField(service, "deliveryOrders", deliveryOrders);
|
||||
ReflectionTestUtils.setField(service, "deliveryFees", deliveryFees);
|
||||
ReflectionTestUtils.setField(service, "feeSources", feeSources);
|
||||
ReflectionTestUtils.setField(service, "deliveryOperations", deliveryOperations);
|
||||
ReflectionTestUtils.setField(service, "planAllocations", planAllocations);
|
||||
ReflectionTestUtils.setField(service, "planLines", planLines);
|
||||
@@ -641,7 +641,13 @@ class DeliveryTransferServiceTest {
|
||||
private static DeliveryRequests.Transfer transferRequest(String idempotencyKey, String remark) {
|
||||
return new DeliveryRequests.Transfer(idempotencyKey, LocalDateTime.now(), "SELF_DELIVERY", null, null,
|
||||
"ONE_PICKUP_ONE_DROP", "PALLET", null, null, null, null, null, null, null, remark,
|
||||
List.of(new DeliveryRequests.FeeItem("DELIVERY_FEE", "USD", BigDecimal.ZERO, null)));
|
||||
List.of(receivableDeliveryFee()));
|
||||
}
|
||||
|
||||
private static DeliveryRequests.FeeItem receivableDeliveryFee() {
|
||||
return new DeliveryRequests.FeeItem("DELIVERY_FEE", "派送费", "RECEIVABLE", "CNY",
|
||||
new BigDecimal("10.00"), null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private static DispatchItem item(Long id, Long warehousePalletId, String palletNo, String destinationCode,
|
||||
|
||||
+5
@@ -10,6 +10,8 @@ import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFileMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFeeMapper;
|
||||
import com.baiyue.lastleg.procurement.gateway.ProcurementFileGateway;
|
||||
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.masterdata.quote.service.PartnerWarehouseDirectory;
|
||||
import com.baiyue.lastleg.masterdata.quote.dto.OverseasQuoteResponses;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -46,6 +48,7 @@ class OverseasProcurementServiceTest {
|
||||
@Mock OverseasProcurementFeeMapper procurementFees;
|
||||
@Mock ProcurementFileGateway fileGateway;
|
||||
@Mock WalletCommandOutboxService walletCommands;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
private OverseasProcurementService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -60,6 +63,8 @@ class OverseasProcurementServiceTest {
|
||||
ReflectionTestUtils.setField(service, "procurementFees", procurementFees);
|
||||
ReflectionTestUtils.setField(service, "fileGateway", fileGateway);
|
||||
ReflectionTestUtils.setField(service, "walletCommands", walletCommands);
|
||||
ReflectionTestUtils.setField(service, "feeSources", feeSources);
|
||||
lenient().when(feeSources.publishApproved(anyString(), anyString(), any())).thenReturn(new OverseasFeeSource());
|
||||
lenient().when(orderNoGenerator.next(anyString(), any())).thenReturn("ACME202609050001");
|
||||
lenient().when(procurementFiles.selectList(any())).thenReturn(List.of());
|
||||
lenient().when(procurementFees.selectList(any())).thenReturn(List.of());
|
||||
|
||||
+5
@@ -16,6 +16,8 @@ import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -51,6 +53,7 @@ class RelabelApplicationServiceTest {
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderStatusHistoryMapper orderHistories;
|
||||
@Mock WalletCommandOutboxService walletCommands;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
private RelabelApplicationService service;
|
||||
@@ -65,8 +68,10 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderHistories", orderHistories);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "walletCommands", walletCommands);
|
||||
ReflectionTestUtils.setField(service, "feeSources", feeSources);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", mapper());
|
||||
lenient().when(walletCommands.publish(any(), any(Long.class), any(), any(), any(), any(), any(Long.class))).thenReturn("wallet-event-1");
|
||||
lenient().when(feeSources.publishApproved(any(), any(), any())).thenReturn(new OverseasFeeSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ class Phase5OverseasUpgradeSqlContractTest {
|
||||
private static final List<String> PHASE5_TABLES = List.of(
|
||||
"ll_dispatch_pda_task", "ll_dispatch_pda_pick", "ll_dispatch_pda_operation",
|
||||
"ll_dispatch_pda_pick_order", "ll_outbound_order", "ll_outbound_pallet_item",
|
||||
"ll_outbound_pda_task", "ll_outbound_pda_pick", "ll_delivery_order", "ll_delivery_fee",
|
||||
"ll_outbound_pda_task", "ll_outbound_pda_pick", "ll_delivery_order",
|
||||
"ll_delivery_operation", "ll_dropship_order_profile", "ll_dropship_carton",
|
||||
"ll_dropship_sku_inventory", "ll_dropship_carton_ledger", "ll_exception_order",
|
||||
"ll_customer_visit");
|
||||
|
||||
+18
-9
@@ -12,7 +12,8 @@ import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderFeeMapper;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.service.OverseasFeeSourceService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderManualNodeMapper;
|
||||
import com.baiyue.lastleg.mapper.ReceivingWarehouseOperationMapper;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -53,7 +55,7 @@ class LastlegOrderServiceTest {
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderDetailMapper details;
|
||||
@Mock LastlegOrderStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderFeeMapper fees;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
@Mock LastlegOrderOperationLogMapper operations;
|
||||
@Mock LastlegOrderManualNodeMapper manualNodes;
|
||||
@Mock InboundPlanCartonMapper cartons;
|
||||
@@ -70,7 +72,7 @@ class LastlegOrderServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderMapper", orders);
|
||||
ReflectionTestUtils.setField(service, "detailMapper", details);
|
||||
ReflectionTestUtils.setField(service, "historyMapper", histories);
|
||||
ReflectionTestUtils.setField(service, "feeMapper", fees);
|
||||
ReflectionTestUtils.setField(service, "feeSources", feeSources);
|
||||
ReflectionTestUtils.setField(service, "operationLogMapper", operations);
|
||||
ReflectionTestUtils.setField(service, "manualNodeMapper", manualNodes);
|
||||
ReflectionTestUtils.setField(service, "cartonMapper", cartons);
|
||||
@@ -140,7 +142,7 @@ class LastlegOrderServiceTest {
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(details.selectList(any())).thenReturn(List.of());
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(fees.selectList(any())).thenReturn(List.of());
|
||||
when(feeSources.listForBusiness("100", "LASTLEG_ORDER", 11L)).thenReturn(List.of());
|
||||
when(operations.selectList(any())).thenReturn(List.of());
|
||||
when(manualNodes.selectList(any())).thenReturn(List.of());
|
||||
|
||||
@@ -186,14 +188,21 @@ class LastlegOrderServiceTest {
|
||||
void createFeePersistsFeeAndOperationForOwnedOrder() {
|
||||
when(orders.selectOne(any())).thenReturn(order(11L, "100", "PENDING_ASSIGNMENT", null));
|
||||
var request = new LastlegOrderRequests.CreateFee(
|
||||
"清关费", "USD", new BigDecimal("420.00"), "PENDING_REVIEW", "报关代理");
|
||||
"CUSTOMS_CLEARANCE", "清关费", "RECEIVABLE", "USD", new BigDecimal("420.00"),
|
||||
LocalDate.of(2026, 9, 9), null, null, null, null, null, null, null, List.of(),
|
||||
null, null, null, null, null);
|
||||
OverseasFeeSource draft = new OverseasFeeSource();
|
||||
draft.setSourceFeeId(701L); draft.setCustomerId("100"); draft.setFeeItemName("清关费");
|
||||
draft.setOriginalCurrency("USD"); draft.setOriginalAmount(new BigDecimal("420.00"));
|
||||
when(feeSources.create(eq("platform_admin"), eq("fee-key"), any())).thenReturn(draft);
|
||||
when(feeSources.submit(701L, "platform_admin", "fee-key:submit")).thenReturn(draft);
|
||||
|
||||
var fee = service.createFee("100", 11L, request, "platform_admin");
|
||||
var fee = service.createFee("100", 11L, request, "platform_admin", "fee-key");
|
||||
|
||||
assertEquals("100", fee.getCustomerId());
|
||||
assertEquals("清关费", fee.getFeeItem());
|
||||
assertEquals(new BigDecimal("420.00"), fee.getAmount());
|
||||
verify(fees).insert(any(com.baiyue.lastleg.entity.LastlegOrderFee.class));
|
||||
assertEquals("清关费", fee.getFeeItemName());
|
||||
verify(feeSources).create(eq("platform_admin"), eq("fee-key"), any());
|
||||
verify(feeSources).submit(701L, "platform_admin", "fee-key:submit");
|
||||
verify(operations).insert(any(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user