feat(firstleg): add private warehouse delivery fee

This commit is contained in:
2026-09-08 17:27:20 +08:00
parent ba81dece5e
commit 442518c382
6 changed files with 67 additions and 6 deletions
@@ -7681,6 +7681,15 @@ SELECT 193000000001000111, 0, 'FBA_HEAD_FREIGHT', '头程运费', 'INTERNATIONAL
0, 'system', '2026-08-14 00:00:00.000', 0, 'system', '2026-08-14 00:00:00.000'
WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'FBA_HEAD_FREIGHT');
INSERT INTO fl_fee_item (id, tenant_id, fee_code, category, trade_scope, fee_type_name,
pickup_payable, pickup_receivable, warehouse_payable, warehouse_receivable,
allocation_fee, description, status, created_by, created_by_name, created_at,
updated_by, updated_by_name, updated_at, deleted, version)
SELECT 193000000001000112, 0, 'PRIVATE_LAST_MILE_DELIVERY', '尾程派送费', 'INTERNATIONAL',
'私人仓尾程派送费', 0, 0, 1, 1, 0, '仅用于destinationType=PRIVATE的入库单',
'ACTIVE', 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'PRIVATE_LAST_MILE_DELIVERY');
-- ============================================================================
-- 2026-08-06:三期分柜、预装、拣货、装柜和派送当前基线
-- ============================================================================
@@ -15607,6 +15616,11 @@ BEGIN
AND category = '头程运费' AND trade_scope = 'INTERNATIONAL' AND fee_type_name = 'FBA头程运费'
AND pickup_payable = 0 AND pickup_receivable = 0 AND warehouse_payable = 0
AND warehouse_receivable = 1 AND allocation_fee = 0 AND status = 'ACTIVE' AND deleted = 0) <> 1
OR (SELECT COUNT(*) FROM baiyue_firstleg.fl_fee_item
WHERE tenant_id = 0 AND fee_code = 'PRIVATE_LAST_MILE_DELIVERY'
AND category = '尾程派送费' AND trade_scope = 'INTERNATIONAL'
AND fee_type_name = '私人仓尾程派送费' AND warehouse_payable = 1
AND warehouse_receivable = 1 AND status = 'ACTIVE' AND deleted = 0) <> 1
OR EXISTS (
SELECT 1
FROM baiyue_firstleg.fl_inbound_fee_detail detail
@@ -23,6 +23,7 @@ import java.util.List;
@Service
public class InboundFeeDetailService {
private static final String FREIGHT_FEE_CODE = "FBA_HEAD_FREIGHT";
private static final String PRIVATE_LAST_MILE_DELIVERY_FEE_CODE = "PRIVATE_LAST_MILE_DELIVERY";
@Resource
private FeeItemRepository repository;
@Resource
@@ -379,6 +380,7 @@ public class InboundFeeDetailService {
private void apply(InboundFeeDetailEntity value, InboundOrderEntity inbound,
FeeItemEntity item, SaveRequest request, long operatorId) {
ensureDestinationApplicable(item, inbound);
ensureWarehouseApplicable(item, request.direction());
value.setFeeItemId(item.getId());
value.setInboundCode(inbound.getInboundCode());
@@ -408,6 +410,13 @@ public class InboundFeeDetailService {
value.setUpdatedBy(operatorId);
}
private static void ensureDestinationApplicable(FeeItemEntity item, InboundOrderEntity inbound) {
if (PRIVATE_LAST_MILE_DELIVERY_FEE_CODE.equals(item.getFeeCode())
&& !"PRIVATE".equals(inbound.getDestinationType())) {
throw conflict("私人仓尾程派送费只能绑定目的地类型为PRIVATE的入库单");
}
}
private void applySupplierSnapshot(InboundFeeDetailEntity value, FeeItemEntity item,
String direction, Long supplierId) {
if (!"PAYABLE".equals(direction)) {
@@ -471,6 +471,15 @@ SELECT 193000000001000111, 0, 'FBA_HEAD_FREIGHT', '头程运费', 'INTERNATIONAL
0, 'system', '2026-08-14 00:00:00.000', 0, 'system', '2026-08-14 00:00:00.000'
WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'FBA_HEAD_FREIGHT');
INSERT INTO fl_fee_item (id, tenant_id, fee_code, category, trade_scope, fee_type_name,
pickup_payable, pickup_receivable, warehouse_payable, warehouse_receivable,
allocation_fee, description, status, created_by, created_by_name, created_at,
updated_by, updated_by_name, updated_at, deleted, version)
SELECT 193000000001000112, 0, 'PRIVATE_LAST_MILE_DELIVERY', '尾程派送费', 'INTERNATIONAL',
'私人仓尾程派送费', 0, 0, 1, 1, 0, '仅用于destinationType=PRIVATE的入库单',
'ACTIVE', 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 0
WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'PRIVATE_LAST_MILE_DELIVERY');
DROP PROCEDURE IF EXISTS phase2_publish_assert_fee_schema;
DELIMITER $$
CREATE PROCEDURE phase2_publish_assert_fee_schema()
@@ -491,11 +500,15 @@ BEGIN
OR (SELECT COUNT(*) FROM fl_fee_item WHERE tenant_id = 0 AND status = 'ACTIVE' AND deleted = 0
AND id BETWEEN 193000000001000001 AND 193000000001000110
AND fee_code = CONCAT('FEE_TYPE_', LPAD(id - 193000000001000000, 3, '0'))) <> 110
OR (SELECT COUNT(*) FROM fl_fee_item WHERE tenant_id = 0 AND fee_code = 'FBA_HEAD_FREIGHT'
AND category = '头程运费' AND trade_scope = 'INTERNATIONAL' AND fee_type_name = 'FBA头程运费'
AND pickup_payable = 0 AND pickup_receivable = 0 AND warehouse_payable = 0
AND warehouse_receivable = 1 AND allocation_fee = 0 AND status = 'ACTIVE' AND deleted = 0) <> 1
OR EXISTS (SELECT 1 FROM fl_inbound_fee_detail d JOIN fl_fee_item f ON f.id = d.fee_item_id
OR (SELECT COUNT(*) FROM fl_fee_item WHERE tenant_id = 0 AND fee_code = 'FBA_HEAD_FREIGHT'
AND category = '头程运费' AND trade_scope = 'INTERNATIONAL' AND fee_type_name = 'FBA头程运费'
AND pickup_payable = 0 AND pickup_receivable = 0 AND warehouse_payable = 0
AND warehouse_receivable = 1 AND allocation_fee = 0 AND status = 'ACTIVE' AND deleted = 0) <> 1
OR (SELECT COUNT(*) FROM fl_fee_item WHERE tenant_id = 0 AND fee_code = 'PRIVATE_LAST_MILE_DELIVERY'
AND category = '尾程派送费' AND trade_scope = 'INTERNATIONAL'
AND fee_type_name = '私人仓尾程派送费' AND warehouse_payable = 1
AND warehouse_receivable = 1 AND status = 'ACTIVE' AND deleted = 0) <> 1
OR EXISTS (SELECT 1 FROM fl_inbound_fee_detail d JOIN fl_fee_item f ON f.id = d.fee_item_id
WHERE d.tenant_id = 0 OR f.tenant_id <> 0) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期迁移失败:费用项目未完成平台公共作用域或明细跨客户引用校验';
END IF;
@@ -184,7 +184,7 @@ paths:
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/InboundFeeDetailListApiResponse'}}}}, '403': {description: 无权限}}
post:
summary: 新增 MANUAL 其他费用
description: 只允许创建 MANUAL 来源;FBA_PRICING 由系统从 fl_inbound_fee 派生。
description: 只允许创建 MANUAL 来源;FBA_PRICING 由系统从 fl_inbound_fee 派生。费用项目 PRIVATE_LAST_MILE_DELIVERY 是独立的私人仓尾程派送费,仅 destinationType=PRIVATE 的入库单可创建;服务端按稳定 feeCode 校验,不按中文名称判断。
parameters: [{$ref: '#/components/parameters/AccountId'}, {$ref: '#/components/parameters/InboundId'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/InboundFeeDetailSaveRequest'}}}}
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/InboundFeeDetailApiResponse'}}}}, '403': {description: 无权限}}
@@ -24,6 +24,8 @@ class FeeItemMigrationSqlContractTest {
.contains("matched_seed.trade_scope = f.trade_scope")
.contains("193000000001000111, 0, 'FBA_HEAD_FREIGHT'")
.contains("fee_code = 'FBA_HEAD_FREIGHT'")
.contains("193000000001000112, 0, 'PRIVATE_LAST_MILE_DELIVERY'")
.contains("fee_code = 'PRIVATE_LAST_MILE_DELIVERY'")
.contains("warehouse_receivable = 1")
.doesNotContain("LEFT JOIN tmp_fee_item_seed by_code")
.doesNotContain("LEFT JOIN tmp_fee_item_seed by_semantic")
@@ -337,6 +337,29 @@ class FeeManagementServiceTest {
.hasMessageContaining("仓储应收/应付");
}
@Test
@DisplayName("私人仓尾程派送费只允许PRIVATE目的地")
void restrictsPrivateLastMileDeliveryFeeByDestinationType() {
InboundOrderEntity fbaInbound = inbound();
fbaInbound.setDestinationType("FBA");
FeeItemEntity privateDelivery = item(true, true);
privateDelivery.setFeeCode("PRIVATE_LAST_MILE_DELIVERY");
when(repository.findInbound(10001L, 21L)).thenReturn(fbaInbound);
when(repository.findItem(11L)).thenReturn(privateDelivery);
assertThatThrownBy(() -> details.create(10001L, 21L, 9L,
detailRequest("RECEIVABLE")))
.isInstanceOf(BizException.class)
.hasMessageContaining("目的地类型为PRIVATE");
verify(repository, never()).insertDetail(any());
fbaInbound.setDestinationType("PRIVATE");
doAnswer(invocation -> { ((InboundFeeDetailEntity) invocation.getArgument(0)).setId(32L); return null; })
.when(repository).insertDetail(any());
assertThat(details.create(10001L, 21L, 9L, detailRequest("RECEIVABLE")).getId())
.isEqualTo(32L);
}
@Test
@DisplayName("审核只更新待审核明细")
void approvesPendingDetail() {