diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java index 50584e52..8d6c417a 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java @@ -185,4 +185,115 @@ class KingdeeEntrySplitTest { assertFalse(KingdeeApBillSyncSupport.isFreightCategory("A10", "人工装卸费用", Map.of())); assertTrue(KingdeeApBillSyncSupport.isFreightCategory("", "运输费", Map.of())); } + + /** 仓储同税率合并:同货物行档案 A05 的仓储费 + 堆存费同税率合并一行 */ + @Test + void mergeWarehouseSameTaxRate() { + Map feeItems = new LinkedHashMap<>(); + feeItems.put("仓储费", new BigDecimal("300")); + feeItems.put("堆存费", new BigDecimal("200")); + Map feeCategoryByName = Map.of( + "仓储费", "A05", + "堆存费", "A05"); + Map taxRateByRule = Map.of( + "仓储费", new BigDecimal("6"), + "堆存费", new BigDecimal("6")); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, feeCategoryByName, taxRateByRule, + new BigDecimal("500")); + assertEquals(1, splits.size()); + assertTrue(splits.get(0).isWarehouse()); + assertEquals(new BigDecimal("6"), splits.get(0).getTaxRate()); + assertEquals(new BigDecimal("500"), splits.get(0).getAmount()); + assertEquals("-6", splits.get(0).taxRateSuffix()); + } + + /** 仓储不同税率拆行:同大类不同税率各生成一行 */ + @Test + void splitWarehouseByTaxRate() { + Map feeItems = new LinkedHashMap<>(); + feeItems.put("仓储费", new BigDecimal("300")); + feeItems.put("保管费", new BigDecimal("200")); + Map feeCategoryByName = Map.of( + "仓储费", "A05", + "保管费", "A05"); + Map taxRateByRule = Map.of( + "仓储费", new BigDecimal("6"), + "保管费", new BigDecimal("9")); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, feeCategoryByName, taxRateByRule, + new BigDecimal("500")); + assertEquals(2, splits.size()); + assertTrue(splits.get(0).isWarehouse()); + assertEquals(new BigDecimal("6"), splits.get(0).getTaxRate()); + assertEquals(new BigDecimal("300"), splits.get(0).getAmount()); + assertEquals("-6", splits.get(0).taxRateSuffix()); + assertTrue(splits.get(1).isWarehouse()); + assertEquals(new BigDecimal("9"), splits.get(1).getTaxRate()); + assertEquals(new BigDecimal("200"), splits.get(1).getAmount()); + assertEquals("-9", splits.get(1).taxRateSuffix()); + } + + /** 仓储不混入运输组:运输费与档案 A05 仓储费按大类拆开 */ + @Test + void warehouseNotMergedIntoFreight() { + Map feeItems = new LinkedHashMap<>(); + feeItems.put("运输费", new BigDecimal("1000")); + feeItems.put("仓储费", new BigDecimal("500")); + Map feeCategoryByName = Map.of("仓储费", "A05"); + Map taxRateByRule = Map.of( + "运输费", new BigDecimal("9"), + "仓储费", new BigDecimal("6")); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, feeCategoryByName, taxRateByRule, + new BigDecimal("1500")); + assertEquals(2, splits.size()); + assertFalse(splits.get(0).isWarehouse()); + assertFalse(splits.get(0).isLoading()); + assertEquals(new BigDecimal("1000"), splits.get(0).getAmount()); + assertTrue(splits.get(1).isWarehouse()); + assertEquals(new BigDecimal("500"), splits.get(1).getAmount()); + } + + /** 档案未配 A05 不做名称关键字兜底:「仓储服务费」档案 A02 仍归运输组 */ + @Test + void warehouseWithoutArchiveCodeStaysFreight() { + Map feeItems = Map.of("仓储服务费", new BigDecimal("500")); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, + Map.of("仓储服务费", "A02"), Map.of(), new BigDecimal("500")); + assertEquals(1, splits.size()); + assertFalse(splits.get(0).isWarehouse()); + assertFalse(splits.get(0).isLoading()); + assertEquals(new BigDecimal("500"), splits.get(0).getAmount()); + } + + /** 残差勾稽不变:含仓储费时残差仍并入运输无税率组 */ + @Test + void residualStillGoesToFreightWhenWarehousePresent() { + Map feeItems = new LinkedHashMap<>(); + feeItems.put("运输费", new BigDecimal("2000")); + feeItems.put("仓储费", new BigDecimal("500")); + Map feeCategoryByName = Map.of("仓储费", "A05"); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, feeCategoryByName, + Map.of("运输费", new BigDecimal("9")), new BigDecimal("2520")); + assertEquals(3, splits.size()); + assertEquals(new BigDecimal("2000"), splits.get(0).getAmount()); + assertTrue(splits.get(1).isWarehouse()); + assertEquals(new BigDecimal("500"), splits.get(1).getAmount()); + assertFalse(splits.get(2).isWarehouse()); + assertFalse(splits.get(2).isLoading()); + assertNull(splits.get(2).getTaxRate()); + assertEquals(new BigDecimal("20"), splits.get(2).getAmount()); + } + + /** 手工合计行仓储判定:档案 A05 或费用类型编码 A05 归仓储物料 */ + @Test + void warehouseCategoryMatchesArchiveOrFeeType() { + Map feeCategoryByName = Map.of("仓储堆存保管费", "A05"); + assertTrue(KingdeeApBillSyncSupport.isWarehouseCategory("A05", "仓储堆存保管费", feeCategoryByName)); + assertFalse(KingdeeApBillSyncSupport.isWarehouseCategory("A05", "人工装卸费用", + Map.of("人工装卸费用", "A10"))); + // 档案未维护:回退行费用类型编码 A05 + assertTrue(KingdeeApBillSyncSupport.isWarehouseCategory("A05", "临时仓储杂费", Map.of())); + assertFalse(KingdeeApBillSyncSupport.isWarehouseCategory("A10", "临时仓储杂费", Map.of())); + // 无名称关键字兜底:名称含仓储但档案/费用类型均非 A05 不归仓储 + assertFalse(KingdeeApBillSyncSupport.isWarehouseCategory("A02", "仓储服务费", Map.of())); + } } diff --git a/doc/sql/transport/lqj/20261102/19_blade_dict_fee_category_warehouse_20261103.sql b/doc/sql/transport/lqj/20261102/19_blade_dict_fee_category_warehouse_20261103.sql new file mode 100644 index 00000000..808e3a23 --- /dev/null +++ b/doc/sql/transport/lqj/20261102/19_blade_dict_fee_category_warehouse_20261103.sql @@ -0,0 +1,25 @@ +-- 金蝶分录仓储大类(A102-2/A302-2/A103-1 分录拆分按费用项档案分类编码判定仓储大类,无名称关键字兜底)。 +-- ① 业务字典 fee_category 新增子项 A05 仓储;② 费用项档案仓储类费用项归入 A05(分类生效前提)。 +-- id 由 UUID_SHORT() 动态生成,幂等可重复执行。 +-- 排序规则:utf8mb4_general_ci(与业务表一致) + +-- ① 业务字典 fee_category 新增子项:A05 仓储 +INSERT INTO blade_dict_biz (id, tenant_id, parent_id, code, dict_key, dict_value, sort, remark, is_sealed, status, is_deleted) +SELECT UUID_SHORT(), '000000', + (SELECT id FROM blade_dict_biz WHERE code = 'fee_category' AND dict_key = '-1' AND tenant_id = '000000' AND is_deleted = 0 LIMIT 1), + 'fee_category', 'A05', '仓储', 1, '金蝶分录仓储大类', 0, 1, 0 +WHERE NOT EXISTS ( + SELECT 1 FROM blade_dict_biz + WHERE code = 'fee_category' AND dict_key = 'A05' AND tenant_id = '000000' AND is_deleted = 0 +); + +-- ② 费用项档案:仓储类费用项归入 A05(已执行则更新 0 行,幂等) +-- 执行前请先核对命中清单,避免把名称含「保管」但非仓储的费用项误归仓: +-- SELECT id, name, fee_category FROM blade_fee_item +-- WHERE is_deleted = 0 AND fee_category <> 'A05' +-- AND (name LIKE '%仓储%' OR name LIKE '%堆存%' OR name LIKE '%保管%'); +UPDATE blade_fee_item +SET fee_category = 'A05' +WHERE is_deleted = 0 + AND fee_category <> 'A05' + AND (name LIKE '%仓储%' OR name LIKE '%堆存%' OR name LIKE '%保管%');