一件代发拣货
This commit is contained in:
@@ -2743,6 +2743,8 @@ CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item (
|
|||||||
total_weight_kg DECIMAL(18,3) NULL COMMENT '本箱规总重量kg',
|
total_weight_kg DECIMAL(18,3) NULL COMMENT '本箱规总重量kg',
|
||||||
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
|
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
|
||||||
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
|
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
|
||||||
|
charge_rule_id BIGINT UNSIGNED NULL COMMENT '该测量明细绑定的客户计费规则ID',
|
||||||
|
coefficient_mode VARCHAR(16) NULL COMMENT '系数模式快照:ADD或MULTIPLY',
|
||||||
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
|
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
|
||||||
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
|
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
|
||||||
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
|
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
|
||||||
@@ -3313,6 +3315,10 @@ CALL phase2_measure_add_column('fl_inbound_measurement_item', 'total_volume_cbm'
|
|||||||
'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''本箱规总体积m3'' AFTER total_weight_kg');
|
'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''本箱规总体积m3'' AFTER total_weight_kg');
|
||||||
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'volume_weight_kg',
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'volume_weight_kg',
|
||||||
'DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''本箱规材积重kg'' AFTER total_volume_cbm');
|
'DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''本箱规材积重kg'' AFTER total_volume_cbm');
|
||||||
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'charge_rule_id',
|
||||||
|
'BIGINT UNSIGNED NULL COMMENT ''该测量明细绑定的客户计费规则ID'' AFTER volume_weight_kg');
|
||||||
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'coefficient_mode',
|
||||||
|
'VARCHAR(16) NULL COMMENT ''系数模式快照:ADD或MULTIPLY'' AFTER charge_rule_id');
|
||||||
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'over_limit_warning',
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'over_limit_warning',
|
||||||
'VARCHAR(500) NULL COMMENT ''超线提醒快照'' AFTER source_image_file_id');
|
'VARCHAR(500) NULL COMMENT ''超线提醒快照'' AFTER source_image_file_id');
|
||||||
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'machine_no',
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'machine_no',
|
||||||
@@ -3579,6 +3585,10 @@ CALL phase2_fba_add_column('fl_inbound_order', 'carrier_id', "BIGINT UNSIGNED NU
|
|||||||
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id");
|
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id");
|
||||||
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name");
|
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name");
|
||||||
CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'length_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '长度计算系数' AFTER conversion_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'width_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '宽度计算系数' AFTER length_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'height_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '高度计算系数' AFTER width_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'default_rule', "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '客户默认生效规则' AFTER status");
|
||||||
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
||||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version");
|
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version");
|
||||||
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code");
|
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code");
|
||||||
@@ -3834,7 +3844,9 @@ VALUES
|
|||||||
(5102045, 0, 'PLATFORM', 'BUTTON', 5102030,
|
(5102045, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
'platform:firstleg:inbound:weight:confirm', '确认OCR重量', NULL, NULL, 40, 'ACTIVE', 0),
|
'platform:firstleg:inbound:weight:confirm', '确认OCR重量', NULL, NULL, 40, 'ACTIVE', 0),
|
||||||
(5102194, 0, 'PLATFORM', 'BUTTON', 5102030,
|
(5102194, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
'platform:firstleg:inbound:measure:manual-entry', '手动录入箱规', NULL, NULL, 25, 'ACTIVE', 0)
|
'platform:firstleg:inbound:measure:manual-entry', '手动录入箱规', NULL, NULL, 25, 'ACTIVE', 0),
|
||||||
|
(5102195, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
|
'platform:firstleg:inbound:warehouse-suggestion:edit', '维护仓库建议测量数据', NULL, NULL, 27, 'ACTIVE', 0)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
|
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
|
||||||
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
|
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
|
||||||
@@ -3842,12 +3854,21 @@ ON DUPLICATE KEY UPDATE
|
|||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000001, id, 0 FROM iam_permission
|
SELECT 4000001, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
|
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194, 5102195)
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000003, id, 0 FROM iam_permission
|
SELECT 4000003, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
|
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194, 5102195)
|
||||||
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
|
SELECT role.id, permission.id, 0
|
||||||
|
FROM iam_role role
|
||||||
|
JOIN iam_permission permission
|
||||||
|
ON permission.tenant_id = 0
|
||||||
|
AND permission.permission_code = 'platform:firstleg:inbound:warehouse-suggestion:edit'
|
||||||
|
WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN'
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
@@ -3978,19 +3999,29 @@ VALUES
|
|||||||
(5102065, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:calculate', '计算入库费用', NULL, NULL, 50, 'ACTIVE', 1, 0),
|
(5102065, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:calculate', '计算入库费用', NULL, NULL, 50, 'ACTIVE', 1, 0),
|
||||||
(5102066, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:confirm', '内部确认入库费用', NULL, NULL, 60, 'ACTIVE', 1, 0),
|
(5102066, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:confirm', '内部确认入库费用', NULL, NULL, 60, 'ACTIVE', 1, 0),
|
||||||
(5102067, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:view', '查看入库费用', NULL, NULL, 70, 'ACTIVE', 1, 0),
|
(5102067, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:view', '查看入库费用', NULL, NULL, 70, 'ACTIVE', 1, 0),
|
||||||
(5102068, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:adjust', '调整已发布费用', NULL, NULL, 80, 'ACTIVE', 1, 0)
|
(5102068, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:adjust', '调整已发布费用', NULL, NULL, 80, 'ACTIVE', 1, 0),
|
||||||
|
(5102069, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:confirm-send-fee', '确认并推送客户费用', NULL, NULL, 90, 'ACTIVE', 1, 0)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
|
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
|
||||||
status = 'ACTIVE', built_in = 1, deleted = 0;
|
status = 'ACTIVE', built_in = 1, deleted = 0;
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000001, id, 0 FROM iam_permission
|
SELECT 4000001, id, 0 FROM iam_permission
|
||||||
WHERE id BETWEEN 5102061 AND 5102068
|
WHERE id BETWEEN 5102061 AND 5102069
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000003, id, 0 FROM iam_permission
|
SELECT 4000003, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102061, 5102063, 5102065, 5102066, 5102067, 5102068)
|
WHERE id IN (5102061, 5102063, 5102065, 5102066, 5102067, 5102068, 5102069)
|
||||||
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
|
SELECT role.id, permission.id, 0
|
||||||
|
FROM iam_role role
|
||||||
|
JOIN iam_permission permission
|
||||||
|
ON permission.tenant_id = 0
|
||||||
|
AND permission.permission_code = 'firstleg:inbound:confirm-send-fee'
|
||||||
|
WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN'
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
-- 客户尺寸计算配置权限;与 FBA 计费规则相互独立。
|
-- 客户尺寸计算配置权限;与 FBA 计费规则相互独立。
|
||||||
@@ -4294,6 +4325,7 @@ CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'w
|
|||||||
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'pricing_tax_mode');
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'pricing_tax_mode');
|
||||||
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'fulfillment_type');
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'fulfillment_type');
|
||||||
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no');
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no');
|
||||||
|
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'coefficient_mode');
|
||||||
CALL unified_assert_table('baiyue_firstleg', 'fl_price_channel_catalog');
|
CALL unified_assert_table('baiyue_firstleg', 'fl_price_channel_catalog');
|
||||||
CALL unified_assert_table('baiyue_firstleg', 'fl_price_origin_alias');
|
CALL unified_assert_table('baiyue_firstleg', 'fl_price_origin_alias');
|
||||||
CALL unified_assert_table('baiyue_firstleg', 'fl_price_warehouse_mapping');
|
CALL unified_assert_table('baiyue_firstleg', 'fl_price_warehouse_mapping');
|
||||||
@@ -4319,10 +4351,11 @@ SET @phase2_permission_count = (
|
|||||||
AND permission_code IN (
|
AND permission_code IN (
|
||||||
'platform:firstleg:inbound:list', 'platform:firstleg:inbound:view',
|
'platform:firstleg:inbound:list', 'platform:firstleg:inbound:view',
|
||||||
'platform:firstleg:inbound:measure:view', 'platform:firstleg:inbound:measure:edit',
|
'platform:firstleg:inbound:measure:view', 'platform:firstleg:inbound:measure:edit',
|
||||||
'platform:firstleg:inbound:measure:submit', 'platform:firstleg:inbound:weight:edit',
|
'platform:firstleg:inbound:measure:submit', 'platform:firstleg:inbound:warehouse-suggestion:edit',
|
||||||
|
'platform:firstleg:inbound:weight:edit',
|
||||||
'platform:firstleg:inbound:weight:confirm', 'firstleg:inbound:measurement-review',
|
'platform:firstleg:inbound:weight:confirm', 'firstleg:inbound:measurement-review',
|
||||||
'firstleg:inbound:measurement-review-history', 'firstleg:inbound:exception-release',
|
'firstleg:inbound:measurement-review-history', 'firstleg:inbound:exception-release',
|
||||||
'firstleg:inbound:send-fee', 'firstleg:fba-pricing:rule:view',
|
'firstleg:inbound:send-fee', 'firstleg:inbound:confirm-send-fee', 'firstleg:fba-pricing:rule:view',
|
||||||
'firstleg:fba-pricing:rule:edit', 'firstleg:fba-pricing:rate:view',
|
'firstleg:fba-pricing:rule:edit', 'firstleg:fba-pricing:rate:view',
|
||||||
'firstleg:fba-pricing:rate:edit', 'firstleg:fba-pricing:calculate',
|
'firstleg:fba-pricing:rate:edit', 'firstleg:fba-pricing:calculate',
|
||||||
'firstleg:fba-pricing:confirm', 'firstleg:fba-pricing:view',
|
'firstleg:fba-pricing:confirm', 'firstleg:fba-pricing:view',
|
||||||
@@ -4338,9 +4371,9 @@ DROP PROCEDURE IF EXISTS unified_assert_permission_count;
|
|||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE unified_assert_permission_count()
|
CREATE PROCEDURE unified_assert_permission_count()
|
||||||
BEGIN
|
BEGIN
|
||||||
IF @phase2_permission_count <> 30 THEN
|
IF @phase2_permission_count <> 32 THEN
|
||||||
SIGNAL SQLSTATE '45000'
|
SIGNAL SQLSTATE '45000'
|
||||||
SET MESSAGE_TEXT = '统一迁移失败:二期 IAM 权限资源未达到 30 项';
|
SET MESSAGE_TEXT = '统一迁移失败:二期 IAM 权限资源未达到 32 项';
|
||||||
END IF;
|
END IF;
|
||||||
END$$
|
END$$
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
@@ -7907,6 +7940,7 @@ CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config (
|
|||||||
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
||||||
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
||||||
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
||||||
|
default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD' COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY',
|
||||||
remark VARCHAR(500) NULL COMMENT '备注',
|
remark VARCHAR(500) NULL COMMENT '备注',
|
||||||
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
||||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||||
@@ -7926,6 +7960,25 @@ CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config (
|
|||||||
CHECK (edge_addition_cm >= 0.001 AND edge_addition_cm <= 100.000)
|
CHECK (edge_addition_cm >= 0.001 AND edge_addition_cm <= 100.000)
|
||||||
) ENGINE=InnoDB COMMENT='客户固定四箱规尺寸配置';
|
) ENGINE=InnoDB COMMENT='客户固定四箱规尺寸配置';
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS unified_dimension_config_add_default_mode;
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE unified_dimension_config_add_default_mode()
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'baiyue_firstleg'
|
||||||
|
AND table_name = 'fl_customer_dimension_config'
|
||||||
|
AND column_name = 'default_calculation_mode'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE baiyue_firstleg.fl_customer_dimension_config
|
||||||
|
ADD COLUMN default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD'
|
||||||
|
COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY' AFTER edge_addition_cm;
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
CALL unified_dimension_config_add_default_mode();
|
||||||
|
DROP PROCEDURE IF EXISTS unified_dimension_config_add_default_mode;
|
||||||
|
|
||||||
-- 新结构首次部署或旧结构受控重建后,为所有现有企业客户补齐缺失箱规。
|
-- 新结构首次部署或旧结构受控重建后,为所有现有企业客户补齐缺失箱规。
|
||||||
-- 复跑命中 (tenant_id, box_type) 唯一键时只做空更新,不覆盖尺寸、备注、版本或审计字段。
|
-- 复跑命中 (tenant_id, box_type) 唯一键时只做空更新,不覆盖尺寸、备注、版本或审计字段。
|
||||||
INSERT INTO baiyue_firstleg.fl_customer_dimension_config (
|
INSERT INTO baiyue_firstleg.fl_customer_dimension_config (
|
||||||
@@ -12937,6 +12990,7 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'cr
|
|||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'created_by', '创建人ID');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'created_by', '创建人ID');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'deleted', '逻辑删除');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'deleted', '逻辑删除');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'edge_addition_cm', '每个尺寸方向的单侧加宽厘米数');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'edge_addition_cm', '每个尺寸方向的单侧加宽厘米数');
|
||||||
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'default_calculation_mode', '订单外箱规默认计算方式:ADD或MULTIPLY');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'id', '配置ID');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'id', '配置ID');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'initialization_key', '创建流程幂等键及补偿归属');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'initialization_key', '创建流程幂等键及补偿归属');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'inner_height_cm', '箱规内高(厘米)');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'inner_height_cm', '箱规内高(厘米)');
|
||||||
@@ -13218,6 +13272,7 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'warehous
|
|||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_code', '柏悦唛头箱号;设备单箱测量时同一客户内唯一');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_code', '柏悦唛头箱号;设备单箱测量时同一客户内唯一');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_specification', '箱规标识');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_specification', '箱规标识');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'calculation_rule_version', '体积计算和舍入规则版本');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'calculation_rule_version', '体积计算和舍入规则版本');
|
||||||
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'coefficient_mode', '系数模式快照:ADD或MULTIPLY');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'created_at', '创建时间');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'created_at', '创建时间');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'device_measured_at', '本次设备测量时间');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'device_measured_at', '本次设备测量时间');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no', '第三方测量明细唯一号,平台手工明细可为空');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no', '第三方测量明细唯一号,平台手工明细可为空');
|
||||||
@@ -14874,8 +14929,9 @@ BEGIN
|
|||||||
IF (SELECT COUNT(*) FROM information_schema.columns
|
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||||
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_customer_dimension_config'
|
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_customer_dimension_config'
|
||||||
AND column_name IN ('id', 'tenant_id', 'box_type', 'inner_length_cm', 'inner_width_cm',
|
AND column_name IN ('id', 'tenant_id', 'box_type', 'inner_length_cm', 'inner_width_cm',
|
||||||
'inner_height_cm', 'edge_addition_cm', 'remark', 'initialization_key',
|
'inner_height_cm', 'edge_addition_cm', 'default_calculation_mode', 'remark',
|
||||||
'created_by', 'created_at', 'updated_by', 'updated_at', 'deleted', 'version')) <> 15 THEN
|
'initialization_key', 'created_by', 'created_at', 'updated_by', 'updated_at',
|
||||||
|
'deleted', 'version')) <> 16 THEN
|
||||||
SIGNAL SQLSTATE '45000'
|
SIGNAL SQLSTATE '45000'
|
||||||
SET MESSAGE_TEXT = '统一迁移失败:客户四箱规尺寸配置字段不完整';
|
SET MESSAGE_TEXT = '统一迁移失败:客户四箱规尺寸配置字段不完整';
|
||||||
END IF;
|
END IF;
|
||||||
|
|||||||
+16
@@ -4,6 +4,7 @@ import com.baiyue.common.audit.BusinessAudit;
|
|||||||
import com.baiyue.common.core.api.ApiResponse;
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
import com.baiyue.common.security.AuthenticatedUser;
|
import com.baiyue.common.security.AuthenticatedUser;
|
||||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.GroupResponse;
|
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.GroupResponse;
|
||||||
|
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.DefaultCalculationModeUpdateRequest;
|
||||||
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest;
|
import com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest;
|
||||||
import com.baiyue.firstleg.service.CustomerDimensionConfigService;
|
import com.baiyue.firstleg.service.CustomerDimensionConfigService;
|
||||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||||
@@ -47,4 +48,19 @@ public class PlatformCustomerDimensionConfigController {
|
|||||||
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
||||||
return ApiResponse.success(service.update(scope, user.userId(), request));
|
return ApiResponse.success(service.update(scope, user.userId(), request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/default-calculation-mode")
|
||||||
|
@PreAuthorize("hasAuthority('firstleg:dimension-config:edit')")
|
||||||
|
@BusinessAudit(event = "CUSTOMER_DIMENSION_DEFAULT_MODE_UPDATE",
|
||||||
|
resourceType = "DIMENSION_CONFIG", resourceId = "#tenantId",
|
||||||
|
description = "平台设置客户默认箱规计算方式")
|
||||||
|
@Operation(summary = "设置客户默认箱规计算方式")
|
||||||
|
public ApiResponse<GroupResponse> updateDefaultCalculationMode(
|
||||||
|
@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
|
@RequestParam long tenantId,
|
||||||
|
@Valid @RequestBody DefaultCalculationModeUpdateRequest request) {
|
||||||
|
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
||||||
|
return ApiResponse.success(service.updateDefaultCalculationMode(
|
||||||
|
scope, user.userId(), request.defaultCalculationMode()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-2
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -41,8 +42,9 @@ public class PlatformFbaMasterDataController {
|
|||||||
public ApiResponse<List<CustomerChargeRuleEntity>> rules(
|
public ApiResponse<List<CustomerChargeRuleEntity>> rules(
|
||||||
@AuthenticationPrincipal AuthenticatedUser user,
|
@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam(required = false) Long tenantId,
|
@RequestParam(required = false) Long tenantId,
|
||||||
|
@RequestParam(required = false) Long customerId,
|
||||||
@RequestParam(required = false) String ruleCode) {
|
@RequestParam(required = false) String ruleCode) {
|
||||||
return ApiResponse.success(service.listRules(optionalPlatformTenant(user, tenantId), ruleCode));
|
return ApiResponse.success(service.listRules(optionalPlatformTenant(user, tenantId), customerId, ruleCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/customer-rules/{id}")
|
@GetMapping("/customer-rules/{id}")
|
||||||
@@ -66,13 +68,24 @@ public class PlatformFbaMasterDataController {
|
|||||||
@DeleteMapping("/customer-rules/{id}")
|
@DeleteMapping("/customer-rules/{id}")
|
||||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rule:edit')")
|
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rule:edit')")
|
||||||
@BusinessAudit(event = "FBA_RULE_RETIRE", resourceType = "CUSTOMER_CHARGE_RULE", resourceId = "#id",
|
@BusinessAudit(event = "FBA_RULE_RETIRE", resourceType = "CUSTOMER_CHARGE_RULE", resourceId = "#id",
|
||||||
description = "停用客户FBA计费规则版本")
|
description = "删除客户系数配置")
|
||||||
public ApiResponse<Void> retireRule(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<Void> retireRule(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam long tenantId, @PathVariable long id) {
|
@RequestParam long tenantId, @PathVariable long id) {
|
||||||
service.retireRule(FirstlegScopeGuard.platform(user, tenantId), id, user.userId());
|
service.retireRule(FirstlegScopeGuard.platform(user, tenantId), id, user.userId());
|
||||||
return ApiResponse.success(null);
|
return ApiResponse.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/customer-rules/{id}/default")
|
||||||
|
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rule:edit')")
|
||||||
|
@BusinessAudit(event = "FBA_RULE_DEFAULT_SET", resourceType = "CUSTOMER_CHARGE_RULE", resourceId = "#id",
|
||||||
|
description = "设置客户默认FBA计费规则")
|
||||||
|
public ApiResponse<Void> setDefaultRule(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
|
@RequestParam long tenantId, @PathVariable long id) {
|
||||||
|
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
||||||
|
service.setDefaultRule(scope, id, user.userId());
|
||||||
|
return ApiResponse.success(null);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/carrier-rates")
|
@GetMapping("/carrier-rates")
|
||||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rate:view')")
|
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rate:view')")
|
||||||
@Operation(summary = "查询船司 FBA 价格版本", description = "查询平台公共价格;所有客户共享")
|
@Operation(summary = "查询船司 FBA 价格版本", description = "查询平台公共价格;所有客户共享")
|
||||||
|
|||||||
+7
-3
@@ -43,11 +43,14 @@ public class PlatformFbaPricingController {
|
|||||||
@Valid @RequestBody(required = false) CalculateRequest request) {
|
@Valid @RequestBody(required = false) CalculateRequest request) {
|
||||||
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
long scope = FirstlegScopeGuard.platform(user, tenantId);
|
||||||
return ApiResponse.success(service.calculate(scope, user.userId(), inboundId,
|
return ApiResponse.success(service.calculate(scope, user.userId(), inboundId,
|
||||||
request == null ? null : request.pricingAt(), idempotencyKey));
|
request == null ? null : request.pricingAt(),
|
||||||
|
request == null ? null : request.selectedWeightKg(),
|
||||||
|
request == null ? null : request.selectedVolumeCbm(), idempotencyKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/confirm")
|
@PostMapping("/confirm")
|
||||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:confirm')")
|
@PreAuthorize("hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
||||||
|
+ " 'firstleg:fba-pricing:confirm')")
|
||||||
@BusinessAudit(event = "FBA_FEE_CONFIRM", resourceType = "INBOUND", resourceId = "#inboundId",
|
@BusinessAudit(event = "FBA_FEE_CONFIRM", resourceType = "INBOUND", resourceId = "#inboundId",
|
||||||
description = "确认FBA费用快照")
|
description = "确认FBA费用快照")
|
||||||
public ApiResponse<FeeSnapshotResponse> confirm(
|
public ApiResponse<FeeSnapshotResponse> confirm(
|
||||||
@@ -58,7 +61,8 @@ public class PlatformFbaPricingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/send")
|
@PostMapping("/send")
|
||||||
@PreAuthorize("hasAuthority('firstleg:inbound:send-fee')")
|
@PreAuthorize("hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
||||||
|
+ " 'firstleg:inbound:send-fee')")
|
||||||
@BusinessAudit(event = "FBA_FEE_SEND", resourceType = "INBOUND", resourceId = "#inboundId",
|
@BusinessAudit(event = "FBA_FEE_SEND", resourceType = "INBOUND", resourceId = "#inboundId",
|
||||||
description = "发送FBA费用快照并进入待托盘状态")
|
description = "发送FBA费用快照并进入待托盘状态")
|
||||||
public ApiResponse<FeeSnapshotResponse> send(
|
public ApiResponse<FeeSnapshotResponse> send(
|
||||||
|
|||||||
+2
-1
@@ -80,7 +80,8 @@ public class PlatformInboundMeasurementController {
|
|||||||
resourceId = "#inboundId", description = "保存入库测量草稿")
|
resourceId = "#inboundId", description = "保存入库测量草稿")
|
||||||
@Operation(summary = "保存测量草稿", description = "保存人工测量批次和服务端计算明细,不推进入库状态。")
|
@Operation(summary = "保存测量草稿", description = "保存人工测量批次和服务端计算明细,不推进入库状态。")
|
||||||
@PutMapping("/measurement/draft")
|
@PutMapping("/measurement/draft")
|
||||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:measure:edit')")
|
@PreAuthorize("hasAnyAuthority('platform:firstleg:inbound:warehouse-suggestion:edit',"
|
||||||
|
+ " 'platform:firstleg:inbound:measure:edit')")
|
||||||
public ApiResponse<InboundMeasurementResponses.DraftSaved> saveDraft(
|
public ApiResponse<InboundMeasurementResponses.DraftSaved> saveDraft(
|
||||||
@AuthenticationPrincipal AuthenticatedUser user,
|
@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam long tenantId,
|
@RequestParam long tenantId,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import jakarta.validation.constraints.Digits;
|
|||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -21,6 +22,10 @@ public final class CustomerDimensionConfigDtos {
|
|||||||
@NotNull @Size(min = 4, max = 4) List<@Valid RuleUpdateRequest> rules
|
@NotNull @Size(min = 4, max = 4) List<@Valid RuleUpdateRequest> rules
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
public record DefaultCalculationModeUpdateRequest(
|
||||||
|
@NotBlank @Pattern(regexp = "ADD|MULTIPLY") String defaultCalculationMode
|
||||||
|
) { }
|
||||||
|
|
||||||
public record RuleUpdateRequest(
|
public record RuleUpdateRequest(
|
||||||
@NotNull CustomerDimensionBoxType boxType,
|
@NotNull CustomerDimensionBoxType boxType,
|
||||||
@NotNull @DecimalMin("0.001") @Digits(integer = 6, fraction = 3) BigDecimal innerLengthCm,
|
@NotNull @DecimalMin("0.001") @Digits(integer = 6, fraction = 3) BigDecimal innerLengthCm,
|
||||||
@@ -32,7 +37,7 @@ public final class CustomerDimensionConfigDtos {
|
|||||||
@NotNull @Min(0) Integer version
|
@NotNull @Min(0) Integer version
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public record GroupResponse(Long tenantId, List<RuleResponse> rules) { }
|
public record GroupResponse(Long tenantId, String defaultCalculationMode, List<RuleResponse> rules) { }
|
||||||
|
|
||||||
public record RuleResponse(
|
public record RuleResponse(
|
||||||
Long id,
|
Long id,
|
||||||
|
|||||||
@@ -29,8 +29,21 @@ public final class FbaPricingDtos {
|
|||||||
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currency,
|
||||||
@NotNull LocalDateTime effectiveFrom,
|
@NotNull LocalDateTime effectiveFrom,
|
||||||
LocalDateTime effectiveTo,
|
LocalDateTime effectiveTo,
|
||||||
@Size(max = 500) String remark
|
@Size(max = 500) String remark,
|
||||||
) {}
|
@DecimalMin(value = "0", inclusive = false) BigDecimal lengthFactor,
|
||||||
|
@DecimalMin(value = "0", inclusive = false) BigDecimal widthFactor,
|
||||||
|
@DecimalMin(value = "0", inclusive = false) BigDecimal heightFactor,
|
||||||
|
Boolean defaultRule
|
||||||
|
) {
|
||||||
|
public CustomerRuleVersionRequest(String ruleCode, Long customerId, String customerName,
|
||||||
|
String businessType, String chargeMode, String taxMode,
|
||||||
|
BigDecimal conversionFactor, Integer roundingScale,
|
||||||
|
String roundingMode, String currency, LocalDateTime effectiveFrom,
|
||||||
|
LocalDateTime effectiveTo, String remark) {
|
||||||
|
this(ruleCode, customerId, customerName, businessType, chargeMode, taxMode, conversionFactor,
|
||||||
|
roundingScale, roundingMode, currency, effectiveFrom, effectiveTo, remark, null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public record CarrierRateVersionRequest(
|
public record CarrierRateVersionRequest(
|
||||||
@NotBlank @Size(max = 50) String rateCode,
|
@NotBlank @Size(max = 50) String rateCode,
|
||||||
@@ -50,7 +63,17 @@ public final class FbaPricingDtos {
|
|||||||
@Size(max = 500) String remark
|
@Size(max = 500) String remark
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public record CalculateRequest(LocalDateTime pricingAt) {}
|
public record CalculateRequest(
|
||||||
|
LocalDateTime pricingAt,
|
||||||
|
@DecimalMin(value = "0", inclusive = false) BigDecimal selectedWeightKg,
|
||||||
|
@DecimalMin(value = "0", inclusive = false) BigDecimal selectedVolumeCbm,
|
||||||
|
@Size(max = 32) String weightSource,
|
||||||
|
@Size(max = 32) String volumeSource
|
||||||
|
) {
|
||||||
|
public CalculateRequest(LocalDateTime pricingAt) {
|
||||||
|
this(pricingAt, null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public record AdjustFeeRequest(
|
public record AdjustFeeRequest(
|
||||||
@NotNull @Positive Integer expectedFeeVersion,
|
@NotNull @Positive Integer expectedFeeVersion,
|
||||||
|
|||||||
@@ -55,8 +55,29 @@ public final class InboundMeasurementRequests {
|
|||||||
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerLengthCm,
|
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerLengthCm,
|
||||||
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerWidthCm,
|
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerWidthCm,
|
||||||
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerHeightCm,
|
@DecimalMin("0.000") @Digits(integer = 9, fraction = 3) BigDecimal outerHeightCm,
|
||||||
|
@Positive Long chargeRuleId,
|
||||||
|
@jakarta.validation.constraints.Pattern(regexp = "ADD|MULTIPLY") String coefficientMode,
|
||||||
@Size(max = 500) String remark
|
@Size(max = 500) String remark
|
||||||
) {
|
) {
|
||||||
|
public ItemRequest(Long inboundDetailId, String itemNo, String boxSpecification, Integer packageCount,
|
||||||
|
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||||
|
Long intelligentImageFileId, String overLimitWarning,
|
||||||
|
BigDecimal outerLengthCm, BigDecimal outerWidthCm, BigDecimal outerHeightCm,
|
||||||
|
Long chargeRuleId, String remark) {
|
||||||
|
this(inboundDetailId, itemNo, boxSpecification, packageCount, lengthCm, widthCm, heightCm,
|
||||||
|
intelligentImageFileId, overLimitWarning, outerLengthCm, outerWidthCm, outerHeightCm,
|
||||||
|
chargeRuleId, null, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemRequest(Long inboundDetailId, String itemNo, String boxSpecification, Integer packageCount,
|
||||||
|
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||||
|
Long intelligentImageFileId, String overLimitWarning,
|
||||||
|
BigDecimal outerLengthCm, BigDecimal outerWidthCm, BigDecimal outerHeightCm,
|
||||||
|
String remark) {
|
||||||
|
this(inboundDetailId, itemNo, boxSpecification, packageCount, lengthCm, widthCm, heightCm,
|
||||||
|
intelligentImageFileId, overLimitWarning, outerLengthCm, outerWidthCm, outerHeightCm,
|
||||||
|
null, null, remark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record SubmitRequest(
|
public record SubmitRequest(
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ public final class InboundMeasurementResponses {
|
|||||||
BigDecimal outerLengthCm,
|
BigDecimal outerLengthCm,
|
||||||
BigDecimal outerWidthCm,
|
BigDecimal outerWidthCm,
|
||||||
BigDecimal outerHeightCm,
|
BigDecimal outerHeightCm,
|
||||||
|
Long chargeRuleId,
|
||||||
|
String coefficientMode,
|
||||||
String remark,
|
String remark,
|
||||||
String boxCode,
|
String boxCode,
|
||||||
String scannedShipmentId,
|
String scannedShipmentId,
|
||||||
|
|||||||
@@ -40,7 +40,28 @@ public record MeasurementBatchCommand(
|
|||||||
BigDecimal outerLengthCm,
|
BigDecimal outerLengthCm,
|
||||||
BigDecimal outerWidthCm,
|
BigDecimal outerWidthCm,
|
||||||
BigDecimal outerHeightCm,
|
BigDecimal outerHeightCm,
|
||||||
|
Long chargeRuleId,
|
||||||
|
String coefficientMode,
|
||||||
String remark
|
String remark
|
||||||
) {
|
) {
|
||||||
|
public Item(Long inboundDetailId, String itemNo, String boxSpecification, int packageCount,
|
||||||
|
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||||
|
Long intelligentImageFileId, String overLimitWarning,
|
||||||
|
BigDecimal outerLengthCm, BigDecimal outerWidthCm, BigDecimal outerHeightCm,
|
||||||
|
Long chargeRuleId, String remark) {
|
||||||
|
this(inboundDetailId, itemNo, boxSpecification, packageCount, lengthCm, widthCm, heightCm,
|
||||||
|
intelligentImageFileId, overLimitWarning, outerLengthCm, outerWidthCm, outerHeightCm,
|
||||||
|
chargeRuleId, null, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item(Long inboundDetailId, String itemNo, String boxSpecification, int packageCount,
|
||||||
|
BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm,
|
||||||
|
Long intelligentImageFileId, String overLimitWarning,
|
||||||
|
BigDecimal outerLengthCm, BigDecimal outerWidthCm, BigDecimal outerHeightCm,
|
||||||
|
String remark) {
|
||||||
|
this(inboundDetailId, itemNo, boxSpecification, packageCount, lengthCm, widthCm, heightCm,
|
||||||
|
intelligentImageFileId, overLimitWarning, outerLengthCm, outerWidthCm, outerHeightCm,
|
||||||
|
null, null, remark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ public class CustomerChargeRuleEntity extends BaseEntity {
|
|||||||
private String chargeMode;
|
private String chargeMode;
|
||||||
private String taxMode;
|
private String taxMode;
|
||||||
private BigDecimal conversionFactor;
|
private BigDecimal conversionFactor;
|
||||||
|
/** 体积计算各方向系数;历史数据为空时按 1 处理。 */
|
||||||
|
private BigDecimal lengthFactor;
|
||||||
|
private BigDecimal widthFactor;
|
||||||
|
private BigDecimal heightFactor;
|
||||||
|
private Boolean defaultRule;
|
||||||
private Integer roundingScale;
|
private Integer roundingScale;
|
||||||
private String roundingMode;
|
private String roundingMode;
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class CustomerDimensionConfigEntity extends BaseEntity {
|
|||||||
private BigDecimal innerWidthCm;
|
private BigDecimal innerWidthCm;
|
||||||
private BigDecimal innerHeightCm;
|
private BigDecimal innerHeightCm;
|
||||||
private BigDecimal edgeAdditionCm;
|
private BigDecimal edgeAdditionCm;
|
||||||
|
private String defaultCalculationMode;
|
||||||
private String remark;
|
private String remark;
|
||||||
private String initializationKey;
|
private String initializationKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ public class InboundMeasurementItemEntity {
|
|||||||
private BigDecimal volumeCbm;
|
private BigDecimal volumeCbm;
|
||||||
private BigDecimal totalVolumeCbm;
|
private BigDecimal totalVolumeCbm;
|
||||||
private BigDecimal volumeWeightKg;
|
private BigDecimal volumeWeightKg;
|
||||||
|
private Long chargeRuleId;
|
||||||
|
private String coefficientMode;
|
||||||
private String calculationRuleVersion;
|
private String calculationRuleVersion;
|
||||||
private Long sourceImageFileId;
|
private Long sourceImageFileId;
|
||||||
private String machineNo;
|
private String machineNo;
|
||||||
|
|||||||
+14
-3
@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Delete;
|
|||||||
import org.apache.ibatis.annotations.Insert;
|
import org.apache.ibatis.annotations.Insert;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -17,12 +18,12 @@ public interface CustomerDimensionConfigMapper extends BaseMapper<CustomerDimens
|
|||||||
<script>
|
<script>
|
||||||
INSERT IGNORE INTO fl_customer_dimension_config
|
INSERT IGNORE INTO fl_customer_dimension_config
|
||||||
(id, tenant_id, box_type, inner_length_cm, inner_width_cm, inner_height_cm,
|
(id, tenant_id, box_type, inner_length_cm, inner_width_cm, inner_height_cm,
|
||||||
edge_addition_cm, remark, initialization_key, created_by, created_at,
|
edge_addition_cm, default_calculation_mode, remark, initialization_key, created_by, created_at,
|
||||||
updated_by, updated_at, deleted, version)
|
updated_by, updated_at, deleted, version)
|
||||||
VALUES
|
VALUES
|
||||||
<foreach collection="rows" item="row" separator=",">
|
<foreach collection="rows" item="row" separator=",">
|
||||||
(#{row.id}, #{row.tenantId}, #{row.boxType}, #{row.innerLengthCm}, #{row.innerWidthCm},
|
(#{row.id}, #{row.tenantId}, #{row.boxType}, #{row.innerLengthCm}, #{row.innerWidthCm},
|
||||||
#{row.innerHeightCm}, #{row.edgeAdditionCm}, #{row.remark}, #{row.initializationKey},
|
#{row.innerHeightCm}, #{row.edgeAdditionCm}, #{row.defaultCalculationMode}, #{row.remark}, #{row.initializationKey},
|
||||||
#{operatorId}, CURRENT_TIMESTAMP(3), #{operatorId}, CURRENT_TIMESTAMP(3), 0, 0)
|
#{operatorId}, CURRENT_TIMESTAMP(3), #{operatorId}, CURRENT_TIMESTAMP(3), 0, 0)
|
||||||
</foreach>
|
</foreach>
|
||||||
</script>
|
</script>
|
||||||
@@ -32,7 +33,7 @@ public interface CustomerDimensionConfigMapper extends BaseMapper<CustomerDimens
|
|||||||
|
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT id, tenant_id, box_type, inner_length_cm, inner_width_cm, inner_height_cm,
|
SELECT id, tenant_id, box_type, inner_length_cm, inner_width_cm, inner_height_cm,
|
||||||
edge_addition_cm, remark, initialization_key, created_by, created_at,
|
edge_addition_cm, default_calculation_mode, remark, initialization_key, created_by, created_at,
|
||||||
updated_by, updated_at, deleted, version
|
updated_by, updated_at, deleted, version
|
||||||
FROM fl_customer_dimension_config
|
FROM fl_customer_dimension_config
|
||||||
WHERE tenant_id = #{tenantId}
|
WHERE tenant_id = #{tenantId}
|
||||||
@@ -43,6 +44,16 @@ public interface CustomerDimensionConfigMapper extends BaseMapper<CustomerDimens
|
|||||||
List<CustomerDimensionConfigEntity> selectDefaultsForCompensation(
|
List<CustomerDimensionConfigEntity> selectDefaultsForCompensation(
|
||||||
@Param("tenantId") long tenantId, @Param("initializationKey") String initializationKey);
|
@Param("tenantId") long tenantId, @Param("initializationKey") String initializationKey);
|
||||||
|
|
||||||
|
@Update("""
|
||||||
|
UPDATE fl_customer_dimension_config
|
||||||
|
SET default_calculation_mode = #{mode}, updated_by = #{operatorId},
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3), version = version + 1
|
||||||
|
WHERE tenant_id = #{tenantId} AND deleted = 0
|
||||||
|
""")
|
||||||
|
int updateDefaultCalculationMode(@Param("tenantId") long tenantId,
|
||||||
|
@Param("mode") String mode,
|
||||||
|
@Param("operatorId") long operatorId);
|
||||||
|
|
||||||
@Delete("""
|
@Delete("""
|
||||||
DELETE FROM fl_customer_dimension_config
|
DELETE FROM fl_customer_dimension_config
|
||||||
WHERE tenant_id = #{tenantId}
|
WHERE tenant_id = #{tenantId}
|
||||||
|
|||||||
+4
@@ -26,6 +26,10 @@ public class CustomerDimensionConfigRepository {
|
|||||||
return mapper.updateById(value);
|
return mapper.updateById(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int updateDefaultCalculationMode(long tenantId, String mode, long operatorId) {
|
||||||
|
return mapper.updateDefaultCalculationMode(tenantId, mode, operatorId);
|
||||||
|
}
|
||||||
|
|
||||||
public List<CustomerDimensionConfigEntity> findDefaultsForCompensation(long tenantId,
|
public List<CustomerDimensionConfigEntity> findDefaultsForCompensation(long tenantId,
|
||||||
String initializationKey) {
|
String initializationKey) {
|
||||||
return mapper.selectDefaultsForCompensation(tenantId, initializationKey);
|
return mapper.selectDefaultsForCompensation(tenantId, initializationKey);
|
||||||
|
|||||||
@@ -42,6 +42,24 @@ public class FbaPricingRepository {
|
|||||||
private InboundOutboxMapper outbox;
|
private InboundOutboxMapper outbox;
|
||||||
|
|
||||||
public void insertRule(CustomerChargeRuleEntity rule) { rules.insert(rule); }
|
public void insertRule(CustomerChargeRuleEntity rule) { rules.insert(rule); }
|
||||||
|
|
||||||
|
public int clearDefaultRule(long tenantId, long customerId, long operatorId) {
|
||||||
|
return rules.update(null, new LambdaUpdateWrapper<CustomerChargeRuleEntity>()
|
||||||
|
.eq(CustomerChargeRuleEntity::getTenantId, tenantId)
|
||||||
|
.eq(CustomerChargeRuleEntity::getCustomerId, customerId)
|
||||||
|
.eq(CustomerChargeRuleEntity::getDefaultRule, true)
|
||||||
|
.set(CustomerChargeRuleEntity::getDefaultRule, false)
|
||||||
|
.set(CustomerChargeRuleEntity::getUpdatedBy, operatorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int setDefaultRule(long tenantId, long id, long operatorId) {
|
||||||
|
return rules.update(null, new LambdaUpdateWrapper<CustomerChargeRuleEntity>()
|
||||||
|
.eq(CustomerChargeRuleEntity::getTenantId, tenantId)
|
||||||
|
.eq(CustomerChargeRuleEntity::getId, id)
|
||||||
|
.eq(CustomerChargeRuleEntity::getStatus, "ACTIVE")
|
||||||
|
.set(CustomerChargeRuleEntity::getDefaultRule, true)
|
||||||
|
.set(CustomerChargeRuleEntity::getUpdatedBy, operatorId));
|
||||||
|
}
|
||||||
public void insertRate(CarrierFbaRateEntity rate) { rates.insert(rate); }
|
public void insertRate(CarrierFbaRateEntity rate) { rates.insert(rate); }
|
||||||
|
|
||||||
public CustomerChargeRuleEntity findRule(long tenantId, long id) {
|
public CustomerChargeRuleEntity findRule(long tenantId, long id) {
|
||||||
@@ -50,6 +68,15 @@ public class FbaPricingRepository {
|
|||||||
.eq(CustomerChargeRuleEntity::getId, id));
|
.eq(CustomerChargeRuleEntity::getId, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CustomerChargeRuleEntity findDefaultRule(long tenantId, long customerId) {
|
||||||
|
return rules.selectOne(new LambdaQueryWrapper<CustomerChargeRuleEntity>()
|
||||||
|
.eq(CustomerChargeRuleEntity::getTenantId, tenantId)
|
||||||
|
.eq(CustomerChargeRuleEntity::getCustomerId, customerId)
|
||||||
|
.eq(CustomerChargeRuleEntity::getStatus, "ACTIVE")
|
||||||
|
.eq(CustomerChargeRuleEntity::getDefaultRule, true)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
public CarrierFbaRateEntity findRate(long id) {
|
public CarrierFbaRateEntity findRate(long id) {
|
||||||
return rates.selectOne(new LambdaQueryWrapper<CarrierFbaRateEntity>()
|
return rates.selectOne(new LambdaQueryWrapper<CarrierFbaRateEntity>()
|
||||||
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
|
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
|
||||||
@@ -57,10 +84,16 @@ public class FbaPricingRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<CustomerChargeRuleEntity> listRules(Long tenantId, String ruleCode) {
|
public List<CustomerChargeRuleEntity> listRules(Long tenantId, String ruleCode) {
|
||||||
|
return listRules(tenantId, null, ruleCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CustomerChargeRuleEntity> listRules(Long tenantId, Long customerId, String ruleCode) {
|
||||||
return rules.selectList(new LambdaQueryWrapper<CustomerChargeRuleEntity>()
|
return rules.selectList(new LambdaQueryWrapper<CustomerChargeRuleEntity>()
|
||||||
.eq(tenantId != null, CustomerChargeRuleEntity::getTenantId, tenantId)
|
.eq(tenantId != null, CustomerChargeRuleEntity::getTenantId, tenantId)
|
||||||
|
.eq(customerId != null, CustomerChargeRuleEntity::getCustomerId, customerId)
|
||||||
.eq(ruleCode != null && !ruleCode.isBlank(), CustomerChargeRuleEntity::getRuleCode, ruleCode)
|
.eq(ruleCode != null && !ruleCode.isBlank(), CustomerChargeRuleEntity::getRuleCode, ruleCode)
|
||||||
.orderByAsc(CustomerChargeRuleEntity::getRuleCode)
|
.orderByAsc(CustomerChargeRuleEntity::getRuleCode)
|
||||||
|
.orderByDesc(CustomerChargeRuleEntity::getDefaultRule)
|
||||||
.orderByDesc(CustomerChargeRuleEntity::getRuleVersion));
|
.orderByDesc(CustomerChargeRuleEntity::getRuleVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +134,7 @@ public class FbaPricingRepository {
|
|||||||
.eq(CustomerChargeRuleEntity::getId, id)
|
.eq(CustomerChargeRuleEntity::getId, id)
|
||||||
.eq(CustomerChargeRuleEntity::getStatus, "ACTIVE")
|
.eq(CustomerChargeRuleEntity::getStatus, "ACTIVE")
|
||||||
.set(CustomerChargeRuleEntity::getStatus, "INACTIVE")
|
.set(CustomerChargeRuleEntity::getStatus, "INACTIVE")
|
||||||
|
.set(CustomerChargeRuleEntity::getDefaultRule, false)
|
||||||
.set(CustomerChargeRuleEntity::getUpdatedBy, operatorId));
|
.set(CustomerChargeRuleEntity::getUpdatedBy, operatorId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -26,6 +26,7 @@ import java.util.Map;
|
|||||||
@Service
|
@Service
|
||||||
public class CustomerDimensionConfigService {
|
public class CustomerDimensionConfigService {
|
||||||
public static final BigDecimal DEFAULT_EDGE_ADDITION_CM = new BigDecimal("2.000");
|
public static final BigDecimal DEFAULT_EDGE_ADDITION_CM = new BigDecimal("2.000");
|
||||||
|
public static final String DEFAULT_CALCULATION_MODE = "ADD";
|
||||||
private static final BigDecimal TWO = new BigDecimal("2");
|
private static final BigDecimal TWO = new BigDecimal("2");
|
||||||
private static final BigDecimal CUBIC_CENTIMETERS_PER_CBM = new BigDecimal("1000000");
|
private static final BigDecimal CUBIC_CENTIMETERS_PER_CBM = new BigDecimal("1000000");
|
||||||
|
|
||||||
@@ -65,6 +66,17 @@ public class CustomerDimensionConfigService {
|
|||||||
return response(tenantId, existing);
|
return response(tenantId, existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public GroupResponse updateDefaultCalculationMode(long tenantId, long operatorId, String mode) {
|
||||||
|
String normalizedMode = normalizeCalculationMode(mode);
|
||||||
|
requireCompleteRules(repository.findAll(tenantId));
|
||||||
|
if (repository.updateDefaultCalculationMode(tenantId, normalizedMode, operatorId)
|
||||||
|
!= CustomerDimensionBoxType.values().length) {
|
||||||
|
throw staleConfiguration();
|
||||||
|
}
|
||||||
|
return response(tenantId, requireCompleteRules(repository.findAll(tenantId)));
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public InitializeResponse initializeDefault(long tenantId, long operatorId, String initializationKey) {
|
public InitializeResponse initializeDefault(long tenantId, long operatorId, String initializationKey) {
|
||||||
requireTenant(tenantId);
|
requireTenant(tenantId);
|
||||||
@@ -159,6 +171,7 @@ public class CustomerDimensionConfigService {
|
|||||||
value.setInnerWidthCm(type.defaultWidthCm());
|
value.setInnerWidthCm(type.defaultWidthCm());
|
||||||
value.setInnerHeightCm(type.defaultHeightCm());
|
value.setInnerHeightCm(type.defaultHeightCm());
|
||||||
value.setEdgeAdditionCm(DEFAULT_EDGE_ADDITION_CM);
|
value.setEdgeAdditionCm(DEFAULT_EDGE_ADDITION_CM);
|
||||||
|
value.setDefaultCalculationMode(DEFAULT_CALCULATION_MODE);
|
||||||
value.setRemark(type.defaultRemark());
|
value.setRemark(type.defaultRemark());
|
||||||
value.setInitializationKey(initializationKey);
|
value.setInitializationKey(initializationKey);
|
||||||
rows.add(value);
|
rows.add(value);
|
||||||
@@ -181,6 +194,7 @@ public class CustomerDimensionConfigService {
|
|||||||
|| value.getVersion() == null || value.getVersion() != 0
|
|| value.getVersion() == null || value.getVersion() != 0
|
||||||
|| value.getInnerLengthCm() == null || value.getInnerWidthCm() == null
|
|| value.getInnerLengthCm() == null || value.getInnerWidthCm() == null
|
||||||
|| value.getInnerHeightCm() == null || value.getEdgeAdditionCm() == null
|
|| value.getInnerHeightCm() == null || value.getEdgeAdditionCm() == null
|
||||||
|
|| !DEFAULT_CALCULATION_MODE.equals(normalizeCalculationMode(value.getDefaultCalculationMode()))
|
||||||
|| value.getInnerLengthCm().compareTo(type.defaultLengthCm()) != 0
|
|| value.getInnerLengthCm().compareTo(type.defaultLengthCm()) != 0
|
||||||
|| value.getInnerWidthCm().compareTo(type.defaultWidthCm()) != 0
|
|| value.getInnerWidthCm().compareTo(type.defaultWidthCm()) != 0
|
||||||
|| value.getInnerHeightCm().compareTo(type.defaultHeightCm()) != 0
|
|| value.getInnerHeightCm().compareTo(type.defaultHeightCm()) != 0
|
||||||
@@ -200,7 +214,12 @@ public class CustomerDimensionConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static GroupResponse response(long tenantId, List<CustomerDimensionConfigEntity> values) {
|
private static GroupResponse response(long tenantId, List<CustomerDimensionConfigEntity> values) {
|
||||||
return new GroupResponse(tenantId, values.stream().map(CustomerDimensionConfigService::ruleResponse).toList());
|
String mode = normalizeCalculationMode(values.get(0).getDefaultCalculationMode());
|
||||||
|
if (values.stream().anyMatch(value -> !mode.equals(normalizeCalculationMode(value.getDefaultCalculationMode())))) {
|
||||||
|
throw new BizException(ResultCode.INTERNAL_ERROR, "客户默认箱规计算方式配置不一致");
|
||||||
|
}
|
||||||
|
return new GroupResponse(tenantId, mode,
|
||||||
|
values.stream().map(CustomerDimensionConfigService::ruleResponse).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RuleResponse ruleResponse(CustomerDimensionConfigEntity value) {
|
private static RuleResponse ruleResponse(CustomerDimensionConfigEntity value) {
|
||||||
@@ -232,6 +251,15 @@ public class CustomerDimensionConfigService {
|
|||||||
return value.trim();
|
return value.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String normalizeCalculationMode(String value) {
|
||||||
|
if (value == null || value.isBlank()) return DEFAULT_CALCULATION_MODE;
|
||||||
|
String normalized = value.trim().toUpperCase();
|
||||||
|
if (!"ADD".equals(normalized) && !"MULTIPLY".equals(normalized)) {
|
||||||
|
throw new BizException(ResultCode.BAD_REQUEST, "默认箱规计算方式无效");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
private static void requireTenant(long tenantId) {
|
private static void requireTenant(long tenantId) {
|
||||||
if (tenantId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "客户ID无效");
|
if (tenantId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "客户ID无效");
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -69,7 +69,7 @@ public class CustomerDimensionWeightingService {
|
|||||||
BigDecimal heightCm) {
|
BigDecimal heightCm) {
|
||||||
BigDecimal rawVolume = lengthCm.multiply(widthCm).multiply(heightCm)
|
BigDecimal rawVolume = lengthCm.multiply(widthCm).multiply(heightCm)
|
||||||
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
||||||
return new WeightedDimensions(null, null, lengthCm, widthCm, heightCm,
|
return new WeightedDimensions(null, null, null, lengthCm, widthCm, heightCm,
|
||||||
rawVolume, rawVolume.setScale(3, RoundingMode.HALF_UP),
|
rawVolume, rawVolume.setScale(3, RoundingMode.HALF_UP),
|
||||||
"ACTUAL_DIMENSION_V1");
|
"ACTUAL_DIMENSION_V1");
|
||||||
}
|
}
|
||||||
@@ -78,6 +78,7 @@ public class CustomerDimensionWeightingService {
|
|||||||
public record WeightedDimensions(
|
public record WeightedDimensions(
|
||||||
CustomerDimensionBoxType boxType,
|
CustomerDimensionBoxType boxType,
|
||||||
Integer ruleVersion,
|
Integer ruleVersion,
|
||||||
|
BigDecimal edgeAdditionCm,
|
||||||
BigDecimal outerLengthCm,
|
BigDecimal outerLengthCm,
|
||||||
BigDecimal outerWidthCm,
|
BigDecimal outerWidthCm,
|
||||||
BigDecimal outerHeightCm,
|
BigDecimal outerHeightCm,
|
||||||
@@ -119,7 +120,7 @@ public class CustomerDimensionWeightingService {
|
|||||||
BigDecimal rawVolume = outerLength.multiply(outerWidth).multiply(outerHeight)
|
BigDecimal rawVolume = outerLength.multiply(outerWidth).multiply(outerHeight)
|
||||||
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
||||||
BigDecimal unitVolume = rawVolume.setScale(3, RoundingMode.HALF_UP);
|
BigDecimal unitVolume = rawVolume.setScale(3, RoundingMode.HALF_UP);
|
||||||
return new WeightedDimensions(boxType, version, outerLength, outerWidth, outerHeight,
|
return new WeightedDimensions(boxType, version, edgeAdditionCm, outerLength, outerWidth, outerHeight,
|
||||||
rawVolume, unitVolume, "CUSTOMER_DIMENSION_" + boxType.name() + "_V" + version);
|
rawVolume, unitVolume, "CUSTOMER_DIMENSION_" + boxType.name() + "_V" + version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,15 +26,6 @@ public class FbaMasterDataService {
|
|||||||
public CustomerChargeRuleEntity createRuleVersion(long tenantId, long operatorId,
|
public CustomerChargeRuleEntity createRuleVersion(long tenantId, long operatorId,
|
||||||
CustomerRuleVersionRequest request) {
|
CustomerRuleVersionRequest request) {
|
||||||
validatePeriod(request.effectiveFrom(), request.effectiveTo());
|
validatePeriod(request.effectiveFrom(), request.effectiveTo());
|
||||||
boolean overlaps = repository.listRules(tenantId, null).stream()
|
|
||||||
.filter(v -> "ACTIVE".equals(v.getStatus()))
|
|
||||||
.filter(v -> Objects.equals(v.getCustomerId(), request.customerId()))
|
|
||||||
.filter(v -> Objects.equals(v.getBusinessType(), request.businessType()))
|
|
||||||
.filter(v -> Objects.equals(v.getTaxMode(), request.taxMode()))
|
|
||||||
.anyMatch(v -> overlaps(v.getEffectiveFrom(), v.getEffectiveTo(),
|
|
||||||
request.effectiveFrom(), request.effectiveTo()));
|
|
||||||
if (overlaps) throw conflict("客户计费规则有效期重叠,无法保证唯一匹配");
|
|
||||||
|
|
||||||
CustomerChargeRuleEntity value = new CustomerChargeRuleEntity();
|
CustomerChargeRuleEntity value = new CustomerChargeRuleEntity();
|
||||||
value.setTenantId(tenantId);
|
value.setTenantId(tenantId);
|
||||||
value.setRuleCode(request.ruleCode());
|
value.setRuleCode(request.ruleCode());
|
||||||
@@ -45,6 +36,10 @@ public class FbaMasterDataService {
|
|||||||
value.setChargeMode(request.chargeMode());
|
value.setChargeMode(request.chargeMode());
|
||||||
value.setTaxMode(request.taxMode());
|
value.setTaxMode(request.taxMode());
|
||||||
value.setConversionFactor(request.conversionFactor());
|
value.setConversionFactor(request.conversionFactor());
|
||||||
|
value.setLengthFactor(defaultFactor(request.lengthFactor()));
|
||||||
|
value.setWidthFactor(defaultFactor(request.widthFactor()));
|
||||||
|
value.setHeightFactor(defaultFactor(request.heightFactor()));
|
||||||
|
value.setDefaultRule(false);
|
||||||
value.setRoundingScale(request.roundingScale());
|
value.setRoundingScale(request.roundingScale());
|
||||||
value.setRoundingMode(request.roundingMode());
|
value.setRoundingMode(request.roundingMode());
|
||||||
value.setCurrency(request.currency());
|
value.setCurrency(request.currency());
|
||||||
@@ -58,6 +53,15 @@ public class FbaMasterDataService {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void setDefaultRule(long tenantId, long id, long operatorId) {
|
||||||
|
CustomerChargeRuleEntity value = rule(tenantId, id);
|
||||||
|
repository.clearDefaultRule(tenantId, value.getCustomerId(), operatorId);
|
||||||
|
if (repository.setDefaultRule(tenantId, id, operatorId) != 1) {
|
||||||
|
throw conflict("规则已停用或不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public CarrierFbaRateEntity createRateVersion(long operatorId, CarrierRateVersionRequest request) {
|
public CarrierFbaRateEntity createRateVersion(long operatorId, CarrierRateVersionRequest request) {
|
||||||
validatePeriod(request.effectiveFrom(), request.effectiveTo());
|
validatePeriod(request.effectiveFrom(), request.effectiveTo());
|
||||||
@@ -102,6 +106,10 @@ public class FbaMasterDataService {
|
|||||||
return repository.listRules(tenantId, code);
|
return repository.listRules(tenantId, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<CustomerChargeRuleEntity> listRules(Long tenantId, Long customerId, String code) {
|
||||||
|
return repository.listRules(tenantId, customerId, code);
|
||||||
|
}
|
||||||
|
|
||||||
public List<CarrierFbaRateEntity> listRates(String code) {
|
public List<CarrierFbaRateEntity> listRates(String code) {
|
||||||
return repository.listRates(code);
|
return repository.listRates(code);
|
||||||
}
|
}
|
||||||
@@ -142,4 +150,8 @@ public class FbaMasterDataService {
|
|||||||
private static BizException conflict(String message) {
|
private static BizException conflict(String message) {
|
||||||
return new BizException(ResultCode.CONFLICT, message);
|
return new BizException(ResultCode.CONFLICT, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static java.math.BigDecimal defaultFactor(java.math.BigDecimal value) {
|
||||||
|
return value == null ? java.math.BigDecimal.ONE : value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,17 @@ public class FbaPricingService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public FeeSnapshotResponse calculate(long tenantId, long operatorId, long inboundId,
|
public FeeSnapshotResponse calculate(long tenantId, long operatorId, long inboundId,
|
||||||
LocalDateTime requestedAt, String idempotencyKey) {
|
LocalDateTime requestedAt, String idempotencyKey) {
|
||||||
String hash = hash("CALCULATE", inboundId, requestedAt == null ? "CURRENT" : requestedAt.toString());
|
return calculate(tenantId, operatorId, inboundId, requestedAt, null, null, idempotencyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public FeeSnapshotResponse calculate(long tenantId, long operatorId, long inboundId,
|
||||||
|
LocalDateTime requestedAt, BigDecimal selectedWeightKg,
|
||||||
|
BigDecimal selectedVolumeCbm, String idempotencyKey) {
|
||||||
|
String discriminator = (requestedAt == null ? "CURRENT" : requestedAt.toString()) + ":"
|
||||||
|
+ (selectedWeightKg == null ? "ACTUAL_WEIGHT" : selectedWeightKg) + ":"
|
||||||
|
+ (selectedVolumeCbm == null ? "ACTUAL_VOLUME" : selectedVolumeCbm);
|
||||||
|
String hash = hash("CALCULATE", inboundId, discriminator);
|
||||||
InboundPricingIdempotencyEntity token = beginIdempotency(
|
InboundPricingIdempotencyEntity token = beginIdempotency(
|
||||||
tenantId, operatorId, inboundId, "FEE_CALCULATE", idempotencyKey, hash);
|
tenantId, operatorId, inboundId, "FEE_CALCULATE", idempotencyKey, hash);
|
||||||
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
|
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
|
||||||
@@ -58,10 +68,11 @@ public class FbaPricingService {
|
|||||||
ActivePriceBookEstimator.Estimate estimate = priceBookEstimator.estimate(new EstimateRequest(
|
ActivePriceBookEstimator.Estimate estimate = priceBookEstimator.estimate(new EstimateRequest(
|
||||||
inbound.getWarehouseId(), inbound.getChannelCode(), inbound.getDestinationCode(),
|
inbound.getWarehouseId(), inbound.getChannelCode(), inbound.getDestinationCode(),
|
||||||
destinationType(inbound), inbound.getDestinationWarehouseId(), inbound.getDestinationName(), null,
|
destinationType(inbound), inbound.getDestinationWarehouseId(), inbound.getDestinationName(), null,
|
||||||
pricingTaxMode(inbound), inbound.getActualNetWeightKg(), inbound.getActualVolumeCbm()));
|
pricingTaxMode(inbound), selectedWeightKg == null ? inbound.getActualNetWeightKg() : selectedWeightKg,
|
||||||
|
selectedVolumeCbm == null ? inbound.getActualVolumeCbm() : selectedVolumeCbm));
|
||||||
RateRow rate = estimate.rate();
|
RateRow rate = estimate.rate();
|
||||||
BigDecimal weight = inbound.getActualNetWeightKg().setScale(3, RoundingMode.HALF_UP);
|
BigDecimal weight = (selectedWeightKg == null ? inbound.getActualNetWeightKg() : selectedWeightKg).setScale(3, RoundingMode.HALF_UP);
|
||||||
BigDecimal volume = inbound.getActualVolumeCbm().setScale(4, RoundingMode.HALF_UP);
|
BigDecimal volume = (selectedVolumeCbm == null ? inbound.getActualVolumeCbm() : selectedVolumeCbm).setScale(4, RoundingMode.HALF_UP);
|
||||||
BigDecimal chargeable = estimate.chargeableQuantity();
|
BigDecimal chargeable = estimate.chargeableQuantity();
|
||||||
BigDecimal converted = chargeable;
|
BigDecimal converted = chargeable;
|
||||||
BigDecimal subtotal = estimate.amount().setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
BigDecimal subtotal = estimate.amount().setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public class InboundMeasurementService {
|
|||||||
@Resource
|
@Resource
|
||||||
private MeasurementBatchService batchService;
|
private MeasurementBatchService batchService;
|
||||||
@Resource
|
@Resource
|
||||||
|
private FbaMasterDataService fbaMasterDataService;
|
||||||
|
@Resource
|
||||||
private OrderCargoClient orderCargoClient;
|
private OrderCargoClient orderCargoClient;
|
||||||
@Value("${baiyue.security.internal-service-token:}")
|
@Value("${baiyue.security.internal-service-token:}")
|
||||||
private String internalToken;
|
private String internalToken;
|
||||||
@@ -91,6 +93,13 @@ public class InboundMeasurementService {
|
|||||||
InboundMeasurementRequests.SaveDraftRequest request) {
|
InboundMeasurementRequests.SaveDraftRequest request) {
|
||||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||||
validateInboundDetails(tenantId, inbound, request.items());
|
validateInboundDetails(tenantId, inbound, request.items());
|
||||||
|
request.items().stream().map(InboundMeasurementRequests.ItemRequest::chargeRuleId)
|
||||||
|
.filter(java.util.Objects::nonNull).distinct().forEach(ruleId -> {
|
||||||
|
var rule = fbaMasterDataService.rule(tenantId, ruleId);
|
||||||
|
if (!"ACTIVE".equals(rule.getStatus()) || !java.util.Objects.equals(rule.getCustomerId(), inbound.getCustomerId())) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "测量明细绑定的计费规则不属于当前客户或已停用");
|
||||||
|
}
|
||||||
|
});
|
||||||
InboundMeasurementEntity existing = request.measurementId() == null ? null
|
InboundMeasurementEntity existing = request.measurementId() == null ? null
|
||||||
: repository.findMeasurement(tenantId, inboundId, request.measurementId());
|
: repository.findMeasurement(tenantId, inboundId, request.measurementId());
|
||||||
OrderCargoClient.MeasurementDeclarationSnapshot declaration = loadDeclaration(inbound);
|
OrderCargoClient.MeasurementDeclarationSnapshot declaration = loadDeclaration(inbound);
|
||||||
@@ -112,7 +121,7 @@ public class InboundMeasurementService {
|
|||||||
item.inboundDetailId(), item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
item.inboundDetailId(), item.itemNo(), item.boxSpecification(), item.packageCount(), item.lengthCm(),
|
||||||
item.widthCm(), item.heightCm(), item.intelligentImageFileId(),
|
item.widthCm(), item.heightCm(), item.intelligentImageFileId(),
|
||||||
item.overLimitWarning(), item.outerLengthCm(), item.outerWidthCm(), item.outerHeightCm(),
|
item.overLimitWarning(), item.outerLengthCm(), item.outerWidthCm(), item.outerHeightCm(),
|
||||||
item.remark())).toList());
|
item.chargeRuleId(), item.coefficientMode(), item.remark())).toList());
|
||||||
InboundMeasurementEntity saved = batchService.saveBatch(command);
|
InboundMeasurementEntity saved = batchService.saveBatch(command);
|
||||||
return new InboundMeasurementResponses.DraftSaved(saved.getId(), saved.getVersion(), saved.getStatus());
|
return new InboundMeasurementResponses.DraftSaved(saved.getId(), saved.getVersion(), saved.getStatus());
|
||||||
}
|
}
|
||||||
@@ -531,7 +540,8 @@ public class InboundMeasurementService {
|
|||||||
item.getUnitVolumeCbm(),
|
item.getUnitVolumeCbm(),
|
||||||
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
||||||
item.getOverLimitWarning(), item.getOuterLengthCm(), item.getOuterWidthCm(),
|
item.getOverLimitWarning(), item.getOuterLengthCm(), item.getOuterWidthCm(),
|
||||||
item.getOuterHeightCm(), item.getRemark(), item.getBoxCode(), item.getScannedShipmentId(),
|
item.getOuterHeightCm(), item.getChargeRuleId(), item.getCoefficientMode(),
|
||||||
|
item.getRemark(), item.getBoxCode(), item.getScannedShipmentId(),
|
||||||
item.getPackageCount(),
|
item.getPackageCount(),
|
||||||
item.getMachineNo(), item.getDeviceMeasuredAt(), item.getUpdatedAt());
|
item.getMachineNo(), item.getDeviceMeasuredAt(), item.getUpdatedAt());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import com.baiyue.common.core.exception.BizException;
|
|||||||
import com.baiyue.firstleg.dto.MeasurementBatchCommand;
|
import com.baiyue.firstleg.dto.MeasurementBatchCommand;
|
||||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||||
|
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||||
|
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||||
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -14,9 +16,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后台录入和第三方回传共用的测量批次持久化服务。
|
* 后台录入和第三方回传共用的测量批次持久化服务。
|
||||||
@@ -37,10 +43,14 @@ public class MeasurementBatchService {
|
|||||||
static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||||
private static final BigDecimal MAX_RAW_VOLUME_CBM = new BigDecimal("999999999999.999999");
|
private static final BigDecimal MAX_RAW_VOLUME_CBM = new BigDecimal("999999999999.999999");
|
||||||
private static final BigDecimal MAX_TOTAL_VOLUME_CBM = new BigDecimal("99999999999999.9999");
|
private static final BigDecimal MAX_TOTAL_VOLUME_CBM = new BigDecimal("99999999999999.9999");
|
||||||
|
private static final Pattern SPECIFICATION_COEFFICIENT_PATTERN = Pattern.compile(
|
||||||
|
"(SMALL|MEDIUM|LARGE|EXTRA_LARGE):(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?);系数:(\\d+(?:\\.\\d+)?)");
|
||||||
@Resource
|
@Resource
|
||||||
private InboundMeasurementRepository repository;
|
private InboundMeasurementRepository repository;
|
||||||
@Resource
|
@Resource
|
||||||
private CustomerDimensionWeightingService dimensionWeighting;
|
private CustomerDimensionWeightingService dimensionWeighting;
|
||||||
|
@Resource
|
||||||
|
private FbaPricingRepository pricingRepository;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public InboundMeasurementEntity saveBatch(MeasurementBatchCommand command) {
|
public InboundMeasurementEntity saveBatch(MeasurementBatchCommand command) {
|
||||||
@@ -50,7 +60,7 @@ public class MeasurementBatchService {
|
|||||||
}
|
}
|
||||||
validateSource(command);
|
validateSource(command);
|
||||||
List<InboundMeasurementItemEntity> calculatedItems = calculateItems(
|
List<InboundMeasurementItemEntity> calculatedItems = calculateItems(
|
||||||
command, dimensionWeighting.load(command.tenantId()));
|
command, dimensionWeighting.load(command.tenantId()), inbound.getCustomerId());
|
||||||
|
|
||||||
InboundMeasurementEntity measurement;
|
InboundMeasurementEntity measurement;
|
||||||
if (command.measurementId() == null) {
|
if (command.measurementId() == null) {
|
||||||
@@ -130,7 +140,8 @@ public class MeasurementBatchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<InboundMeasurementItemEntity> calculateItems(
|
private List<InboundMeasurementItemEntity> calculateItems(
|
||||||
MeasurementBatchCommand command, CustomerDimensionWeightingService.RuleSet dimensionRules) {
|
MeasurementBatchCommand command, CustomerDimensionWeightingService.RuleSet dimensionRules,
|
||||||
|
Long customerId) {
|
||||||
Set<String> itemNumbers = new HashSet<>();
|
Set<String> itemNumbers = new HashSet<>();
|
||||||
return command.items().stream().map(source -> {
|
return command.items().stream().map(source -> {
|
||||||
if (!itemNumbers.add(source.itemNo())) {
|
if (!itemNumbers.add(source.itemNo())) {
|
||||||
@@ -138,8 +149,49 @@ public class MeasurementBatchService {
|
|||||||
}
|
}
|
||||||
CustomerDimensionWeightingService.WeightedDimensions weighted = dimensionRules.resolve(
|
CustomerDimensionWeightingService.WeightedDimensions weighted = dimensionRules.resolve(
|
||||||
source.lengthCm(), source.widthCm(), source.heightCm());
|
source.lengthCm(), source.widthCm(), source.heightCm());
|
||||||
BigDecimal unitVolumeRaw = weighted.rawVolumeCbm();
|
String requestedMode = normalizeMode(source.coefficientMode());
|
||||||
BigDecimal unitVolume = weighted.unitVolumeCbm();
|
CustomerChargeRuleEntity coefficientRule = "MULTIPLY".equals(requestedMode)
|
||||||
|
? requireCoefficientRule(command.tenantId(), customerId, source.chargeRuleId())
|
||||||
|
: requestedMode == null ? findDefaultCoefficientRule(command.tenantId(), customerId) : null;
|
||||||
|
String coefficientMode = coefficientRule != null ? "MULTIPLY" : requestedMode;
|
||||||
|
boolean useDimensionRule = "ADD".equals(coefficientMode);
|
||||||
|
// The box-dimension strategy bills against the matched carton tier.
|
||||||
|
// This is important when a measured carton is smaller than a standard
|
||||||
|
// carton: use the configured outer dimensions, rather than adding the
|
||||||
|
// edge allowance to each measured side.
|
||||||
|
BigDecimal addition = weighted.edgeAdditionCm();
|
||||||
|
boolean hasMatchedAddition = useDimensionRule && weighted.boxType() != null;
|
||||||
|
BigDecimal outerLength = hasMatchedAddition ? weighted.outerLengthCm() : source.lengthCm();
|
||||||
|
BigDecimal outerWidth = hasMatchedAddition ? weighted.outerWidthCm() : source.widthCm();
|
||||||
|
BigDecimal outerHeight = hasMatchedAddition ? weighted.outerHeightCm() : source.heightCm();
|
||||||
|
boolean hasOuterOverride = source.outerLengthCm() != null
|
||||||
|
|| source.outerWidthCm() != null
|
||||||
|
|| source.outerHeightCm() != null;
|
||||||
|
if (hasOuterOverride && (source.outerLengthCm() == null
|
||||||
|
|| source.outerWidthCm() == null || source.outerHeightCm() == null
|
||||||
|
|| source.outerLengthCm().signum() <= 0 || source.outerWidthCm().signum() <= 0
|
||||||
|
|| source.outerHeightCm().signum() <= 0)) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||||
|
"仓库修改外箱长、宽、高必须同时填写且大于0,明细 " + source.itemNo());
|
||||||
|
}
|
||||||
|
if (hasOuterOverride) {
|
||||||
|
// Warehouse-confirmed outer dimensions are authoritative. Derived
|
||||||
|
// values below (girth, volume and volumetric weight) use them directly.
|
||||||
|
outerLength = source.outerLengthCm();
|
||||||
|
outerWidth = source.outerWidthCm();
|
||||||
|
outerHeight = source.outerHeightCm();
|
||||||
|
} else if (coefficientRule != null) {
|
||||||
|
BigDecimal specificationCoefficient = resolveSpecificationCoefficient(
|
||||||
|
coefficientRule, source.lengthCm(), source.widthCm(), source.heightCm());
|
||||||
|
outerLength = applyCoefficient(outerLength, specificationCoefficient);
|
||||||
|
outerWidth = applyCoefficient(outerWidth, specificationCoefficient);
|
||||||
|
outerHeight = applyCoefficient(outerHeight, specificationCoefficient);
|
||||||
|
}
|
||||||
|
BigDecimal unitVolumeRaw = outerLength.multiply(outerWidth).multiply(outerHeight)
|
||||||
|
.divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP);
|
||||||
|
BigDecimal unitVolume = hasMatchedAddition && !hasOuterOverride
|
||||||
|
? unitVolumeRaw.setScale(3, RoundingMode.HALF_UP)
|
||||||
|
: unitVolumeRaw.setScale(4, RoundingMode.CEILING);
|
||||||
BigDecimal packageCount = BigDecimal.valueOf(source.packageCount());
|
BigDecimal packageCount = BigDecimal.valueOf(source.packageCount());
|
||||||
BigDecimal totalVolumeRaw = unitVolumeRaw.multiply(packageCount).setScale(6, RoundingMode.HALF_UP);
|
BigDecimal totalVolumeRaw = unitVolumeRaw.multiply(packageCount).setScale(6, RoundingMode.HALF_UP);
|
||||||
BigDecimal totalVolume;
|
BigDecimal totalVolume;
|
||||||
@@ -161,25 +213,115 @@ public class MeasurementBatchService {
|
|||||||
item.setLengthCm(source.lengthCm());
|
item.setLengthCm(source.lengthCm());
|
||||||
item.setWidthCm(source.widthCm());
|
item.setWidthCm(source.widthCm());
|
||||||
item.setHeightCm(source.heightCm());
|
item.setHeightCm(source.heightCm());
|
||||||
item.setGirthCm(source.lengthCm().add(source.widthCm().add(source.heightCm())
|
BigDecimal girth = outerLength.add(outerWidth.add(outerHeight).multiply(BigDecimal.valueOf(2)));
|
||||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
item.setGirthCm(girth.setScale(3, RoundingMode.CEILING));
|
||||||
item.setUnitVolumeCbm(unitVolume);
|
item.setUnitVolumeCbm(unitVolume);
|
||||||
item.setVolumeRawCbm(totalVolumeRaw);
|
item.setVolumeRawCbm(totalVolumeRaw);
|
||||||
item.setVolumeCbm(totalVolume);
|
item.setVolumeCbm(totalVolume);
|
||||||
item.setTotalVolumeCbm(totalVolume);
|
item.setTotalVolumeCbm(totalVolume);
|
||||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||||
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
|
item.setChargeRuleId(coefficientRule == null ? null : coefficientRule.getId());
|
||||||
|
item.setCoefficientMode(coefficientMode);
|
||||||
|
item.setCalculationRuleVersion(hasOuterOverride ? "WAREHOUSE_OUTER_OVERRIDE_V1"
|
||||||
|
: coefficientRule != null
|
||||||
|
? "CUSTOMER_COEFFICIENT_V" + coefficientRule.getRuleVersion() + "_MULTIPLY"
|
||||||
|
: useDimensionRule ? weighted.calculationRuleVersion() : "ACTUAL_DIMENSION_V1");
|
||||||
item.setSourceImageFileId(source.intelligentImageFileId());
|
item.setSourceImageFileId(source.intelligentImageFileId());
|
||||||
item.setOverLimitWarning(source.overLimitWarning());
|
item.setOverLimitWarning(source.overLimitWarning());
|
||||||
item.setOuterLengthCm(weighted.outerLengthCm());
|
item.setOuterLengthCm(outerLength.setScale(3, RoundingMode.HALF_UP));
|
||||||
item.setOuterWidthCm(weighted.outerWidthCm());
|
item.setOuterWidthCm(outerWidth.setScale(3, RoundingMode.HALF_UP));
|
||||||
item.setOuterHeightCm(weighted.outerHeightCm());
|
item.setOuterHeightCm(outerHeight.setScale(3, RoundingMode.HALF_UP));
|
||||||
item.setRemark(source.remark());
|
item.setRemark(source.remark());
|
||||||
item.setCreatedAt(LocalDateTime.now());
|
item.setCreatedAt(LocalDateTime.now());
|
||||||
return item;
|
return item;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeMode(String mode) {
|
||||||
|
if (mode == null || mode.isBlank()) return null;
|
||||||
|
String normalized = mode.trim().toUpperCase(java.util.Locale.ROOT);
|
||||||
|
if (!Set.of("ADD", "MULTIPLY").contains(normalized)) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "系数模式仅支持 ADD 或 MULTIPLY");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomerChargeRuleEntity findDefaultCoefficientRule(long tenantId, Long customerId) {
|
||||||
|
if (customerId == null || customerId <= 0) return null;
|
||||||
|
return pricingRepository.findDefaultRule(tenantId, customerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomerChargeRuleEntity requireCoefficientRule(long tenantId, Long customerId, Long ruleId) {
|
||||||
|
if (customerId == null || customerId <= 0) {
|
||||||
|
if (ruleId == null) return null;
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "入库单客户无效,不能应用计费系数");
|
||||||
|
}
|
||||||
|
CustomerChargeRuleEntity rule = ruleId == null
|
||||||
|
? pricingRepository.findDefaultRule(tenantId, customerId)
|
||||||
|
: pricingRepository.findRule(tenantId, ruleId);
|
||||||
|
if (rule == null || !"ACTIVE".equals(rule.getStatus())
|
||||||
|
|| !Boolean.TRUE.equals(rule.getDefaultRule()) || !customerId.equals(rule.getCustomerId())) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "计费规则不属于当前客户、未启用或未设置默认规则");
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal applyCoefficient(BigDecimal dimension, BigDecimal coefficient) {
|
||||||
|
if (coefficient == null) coefficient = BigDecimal.ONE;
|
||||||
|
BigDecimal result = dimension.multiply(coefficient);
|
||||||
|
if (result.compareTo(BigDecimal.ZERO) <= 0 || result.compareTo(new BigDecimal("999999999.999")) > 0) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "应用计费系数后的箱规尺寸超出允许范围");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal resolveSpecificationCoefficient(CustomerChargeRuleEntity rule,
|
||||||
|
BigDecimal length, BigDecimal width,
|
||||||
|
BigDecimal height) {
|
||||||
|
List<SpecificationCoefficient> tiers = parseSpecificationCoefficients(rule.getRemark());
|
||||||
|
if (tiers.isEmpty()) {
|
||||||
|
return rule.getLengthFactor() == null ? BigDecimal.ONE : rule.getLengthFactor();
|
||||||
|
}
|
||||||
|
List<BigDecimal> measured = sortedDimensions(length, width, height);
|
||||||
|
return tiers.stream()
|
||||||
|
.filter(tier -> tier.contains(measured))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(tiers.get(tiers.size() - 1))
|
||||||
|
.coefficient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SpecificationCoefficient> parseSpecificationCoefficients(String remark) {
|
||||||
|
if (remark == null || remark.isBlank()) return List.of();
|
||||||
|
Matcher matcher = SPECIFICATION_COEFFICIENT_PATTERN.matcher(remark);
|
||||||
|
List<SpecificationCoefficient> tiers = new ArrayList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
List<BigDecimal> dimensions = sortedDimensions(new BigDecimal(matcher.group(2)),
|
||||||
|
new BigDecimal(matcher.group(3)), new BigDecimal(matcher.group(4)));
|
||||||
|
BigDecimal coefficient = new BigDecimal(matcher.group(5));
|
||||||
|
if (coefficient.signum() > 0) tiers.add(new SpecificationCoefficient(dimensions, coefficient));
|
||||||
|
}
|
||||||
|
tiers.sort(Comparator.comparing(SpecificationCoefficient::volume));
|
||||||
|
return tiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BigDecimal> sortedDimensions(BigDecimal length, BigDecimal width, BigDecimal height) {
|
||||||
|
List<BigDecimal> dimensions = new ArrayList<>(List.of(length, width, height));
|
||||||
|
dimensions.sort(Comparator.reverseOrder());
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SpecificationCoefficient(List<BigDecimal> dimensions, BigDecimal coefficient) {
|
||||||
|
private BigDecimal volume() {
|
||||||
|
return dimensions.get(0).multiply(dimensions.get(1)).multiply(dimensions.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(List<BigDecimal> measured) {
|
||||||
|
return measured.get(0).compareTo(dimensions.get(0)) <= 0
|
||||||
|
&& measured.get(1).compareTo(dimensions.get(1)) <= 0
|
||||||
|
&& measured.get(2).compareTo(dimensions.get(2)) <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static BizException measurementRangeExceeded(MeasurementBatchCommand.Item source) {
|
private static BizException measurementRangeExceeded(MeasurementBatchCommand.Item source) {
|
||||||
return new BizException(ResultCode.VALIDATION_FAILED,
|
return new BizException(ResultCode.VALIDATION_FAILED,
|
||||||
"箱规尺寸或箱数超出系统允许范围,明细 " + source.itemNo() + ":"
|
"箱规尺寸或箱数超出系统允许范围,明细 " + source.itemNo() + ":"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS fl_customer_dimension_config (
|
|||||||
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
||||||
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
||||||
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
||||||
|
default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD' COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY',
|
||||||
remark VARCHAR(500) NULL COMMENT '备注',
|
remark VARCHAR(500) NULL COMMENT '备注',
|
||||||
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
||||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS fl_customer_dimension_config (
|
|||||||
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
||||||
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
||||||
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
||||||
|
default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD' COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY',
|
||||||
remark VARCHAR(500) NULL COMMENT '备注',
|
remark VARCHAR(500) NULL COMMENT '备注',
|
||||||
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
||||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ CALL phase2_fba_add_column('fl_inbound_order', 'carrier_id', "BIGINT UNSIGNED NU
|
|||||||
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id");
|
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id");
|
||||||
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name");
|
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name");
|
||||||
CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'length_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '长度计算系数' AFTER conversion_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'width_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '宽度计算系数' AFTER length_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'height_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '高度计算系数' AFTER width_factor");
|
||||||
|
CALL phase2_fba_add_column('fl_customer_charge_rule', 'default_rule', "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '客户默认生效规则' AFTER status");
|
||||||
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
|
||||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version");
|
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version");
|
||||||
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code");
|
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code");
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_width_cm',
|
|||||||
'DECIMAL(12,3) NULL COMMENT ''外箱规宽度cm'' AFTER outer_length_cm');
|
'DECIMAL(12,3) NULL COMMENT ''外箱规宽度cm'' AFTER outer_length_cm');
|
||||||
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_height_cm',
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_height_cm',
|
||||||
'DECIMAL(12,3) NULL COMMENT ''外箱规高度cm'' AFTER outer_width_cm');
|
'DECIMAL(12,3) NULL COMMENT ''外箱规高度cm'' AFTER outer_width_cm');
|
||||||
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'charge_rule_id',
|
||||||
|
'BIGINT UNSIGNED NULL COMMENT ''该测量明细绑定的客户计费规则ID'' AFTER volume_weight_kg');
|
||||||
|
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'coefficient_mode',
|
||||||
|
'VARCHAR(16) NULL COMMENT ''系数模式快照:ADD或MULTIPLY'' AFTER charge_rule_id');
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS phase2_measure_add_column;
|
DROP PROCEDURE IF EXISTS phase2_measure_add_column;
|
||||||
|
|
||||||
|
|||||||
@@ -268,6 +268,8 @@ CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item (
|
|||||||
volume_cbm DECIMAL(18,4) NOT NULL COMMENT '业务体积m3',
|
volume_cbm DECIMAL(18,4) NOT NULL COMMENT '业务体积m3',
|
||||||
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
|
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
|
||||||
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
|
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
|
||||||
|
charge_rule_id BIGINT UNSIGNED NULL COMMENT '该测量明细绑定的客户计费规则ID',
|
||||||
|
coefficient_mode VARCHAR(16) NULL COMMENT '系数模式快照:ADD或MULTIPLY',
|
||||||
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
|
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
|
||||||
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
|
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
|
||||||
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
|
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
|
||||||
@@ -858,6 +860,7 @@ CREATE TABLE IF NOT EXISTS fl_customer_dimension_config (
|
|||||||
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
|
||||||
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
|
||||||
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
|
||||||
|
default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD' COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY',
|
||||||
remark VARCHAR(500) NULL COMMENT '备注',
|
remark VARCHAR(500) NULL COMMENT '备注',
|
||||||
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
|
||||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ paths:
|
|||||||
description: 需要 firstleg:fba-pricing:rule:view 权限
|
description: 需要 firstleg:fba-pricing:rule:view 权限
|
||||||
parameters:
|
parameters:
|
||||||
- {$ref: '#/components/parameters/OptionalTenantId'}
|
- {$ref: '#/components/parameters/OptionalTenantId'}
|
||||||
|
- name: customerId
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema: {type: integer, format: int64, minimum: 1}
|
||||||
- {name: ruleCode, in: query, schema: {type: string, maxLength: 50}}
|
- {name: ruleCode, in: query, schema: {type: string, maxLength: 50}}
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -23,10 +27,16 @@ paths:
|
|||||||
/api/platform/v1/fba-pricing/customer-rules/versions:
|
/api/platform/v1/fba-pricing/customer-rules/versions:
|
||||||
post:
|
post:
|
||||||
summary: 新建客户计费规则版本
|
summary: 新建客户计费规则版本
|
||||||
description: 版本内容不可原地修改;有效期使用 [effectiveFrom,effectiveTo) 半开区间。
|
description: 新增配置固定为未启用,可保存有效期重叠的多条配置;保存后通过设置默认规则接口启用其中一条。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CustomerRuleVersionRequest'}}}}
|
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CustomerRuleVersionRequest'}}}}
|
||||||
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
||||||
|
/api/platform/v1/fba-pricing/customer-rules/{id}/default:
|
||||||
|
put:
|
||||||
|
summary: 启用客户计费规则
|
||||||
|
description: 将指定配置设为当前客户唯一默认生效规则,并取消原默认规则。
|
||||||
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/Id'}]
|
||||||
|
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/fba-pricing/customer-rules/{id}:
|
/api/platform/v1/fba-pricing/customer-rules/{id}:
|
||||||
get:
|
get:
|
||||||
summary: 查询客户计费规则版本详情
|
summary: 查询客户计费规则版本详情
|
||||||
@@ -137,13 +147,13 @@ paths:
|
|||||||
/api/platform/v1/inbounds/{inboundId}/fees/confirm:
|
/api/platform/v1/inbounds/{inboundId}/fees/confirm:
|
||||||
post:
|
post:
|
||||||
summary: 确认最新费用快照
|
summary: 确认最新费用快照
|
||||||
description: 确认费用只更新费用状态;主流程已到 WAIT_SPLIT 时保持不变。
|
description: 需要 firstleg:inbound:confirm-send-fee 或兼容权限 firstleg:fba-pricing:confirm。确认费用只更新费用状态;主流程已到 WAIT_SPLIT 时保持不变。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/FeeSnapshotApiResponse'}}}}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/FeeSnapshotApiResponse'}}}}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/inbounds/{inboundId}/fees/send:
|
/api/platform/v1/inbounds/{inboundId}/fees/send:
|
||||||
post:
|
post:
|
||||||
summary: 发送已确认费用快照
|
summary: 发送已确认费用快照
|
||||||
description: 人工发布当前已确认版本;仅此动作将费用置为 SENT、生成 PUBLISHED 的 FBA_PRICING 只读派生明细并写入 Finance Outbox。入库单已到 WAIT_SPLIT 时不得回退或阻塞主流程。
|
description: 需要 firstleg:inbound:confirm-send-fee 或兼容权限 firstleg:inbound:send-fee。人工发布当前已确认版本;仅此动作将费用置为 SENT、生成 PUBLISHED 的 FBA_PRICING 只读派生明细并写入 Finance Outbox。入库单已到 WAIT_SPLIT 时不得回退或阻塞主流程。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/FeeSnapshotApiResponse'}}}}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/FeeSnapshotApiResponse'}}}}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/inbounds/{inboundId}/fees/latest:
|
/api/platform/v1/inbounds/{inboundId}/fees/latest:
|
||||||
|
|||||||
@@ -168,6 +168,20 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200': {description: 成功时 data 为更新后的 CustomerDimensionConfigGroupResponse}
|
'200': {description: 成功时 data 为更新后的 CustomerDimensionConfigGroupResponse}
|
||||||
'403': {description: 无编辑权限}
|
'403': {description: 无编辑权限}
|
||||||
|
/api/platform/v1/first-leg/customer-dimension-config/default-calculation-mode:
|
||||||
|
put:
|
||||||
|
summary: 设置客户订单外箱规默认计算方式
|
||||||
|
description: 需要 firstleg:dimension-config:edit 权限;ADD 表示按箱规尺寸加宽,MULTIPLY 表示按默认权重系数计算。
|
||||||
|
parameters:
|
||||||
|
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/CustomerDimensionDefaultCalculationModeUpdateRequest'}
|
||||||
|
responses:
|
||||||
|
'200': {description: 成功时 data 为更新后的 CustomerDimensionConfigGroupResponse}
|
||||||
|
'403': {description: 无编辑权限}
|
||||||
/internal/firstleg/v1/inbounds/order-approved:
|
/internal/firstleg/v1/inbounds/order-approved:
|
||||||
post:
|
post:
|
||||||
summary: 订单审核通过后幂等创建入库单
|
summary: 订单审核通过后幂等创建入库单
|
||||||
@@ -762,7 +776,8 @@ paths:
|
|||||||
post:
|
post:
|
||||||
summary: 平台模拟单箱设备扫描
|
summary: 平台模拟单箱设备扫描
|
||||||
description: >-
|
description: >-
|
||||||
需要 platform:firstleg:inbound:measure:edit 权限;仅用于平台联调和代设备录入,
|
需要 platform:firstleg:inbound:warehouse-suggestion:edit 或兼容权限
|
||||||
|
platform:firstleg:inbound:measure:edit;仅用于平台联调和代设备录入,
|
||||||
复用正式单箱覆盖规则,不代表最终第三方设备协议或鉴权方式。
|
复用正式单箱覆盖规则,不代表最终第三方设备协议或鉴权方式。
|
||||||
parameters:
|
parameters:
|
||||||
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
@@ -1354,6 +1369,11 @@ components:
|
|||||||
maxItems: 4
|
maxItems: 4
|
||||||
description: 必须恰好包含 SMALL、MEDIUM、LARGE、EXTRA_LARGE 各一行
|
description: 必须恰好包含 SMALL、MEDIUM、LARGE、EXTRA_LARGE 各一行
|
||||||
items: {$ref: '#/components/schemas/CustomerDimensionConfigRuleUpdateRequest'}
|
items: {$ref: '#/components/schemas/CustomerDimensionConfigRuleUpdateRequest'}
|
||||||
|
CustomerDimensionDefaultCalculationModeUpdateRequest:
|
||||||
|
type: object
|
||||||
|
required: [defaultCalculationMode]
|
||||||
|
properties:
|
||||||
|
defaultCalculationMode: {type: string, enum: [ADD, MULTIPLY]}
|
||||||
CustomerDimensionConfigRuleUpdateRequest:
|
CustomerDimensionConfigRuleUpdateRequest:
|
||||||
type: object
|
type: object
|
||||||
required: [boxType, innerLengthCm, innerWidthCm, innerHeightCm, edgeAdditionCm, version]
|
required: [boxType, innerLengthCm, innerWidthCm, innerHeightCm, edgeAdditionCm, version]
|
||||||
@@ -1373,9 +1393,10 @@ components:
|
|||||||
version: {type: integer, minimum: 0, description: 乐观锁版本号}
|
version: {type: integer, minimum: 0, description: 乐观锁版本号}
|
||||||
CustomerDimensionConfigGroupResponse:
|
CustomerDimensionConfigGroupResponse:
|
||||||
type: object
|
type: object
|
||||||
required: [tenantId, rules]
|
required: [tenantId, defaultCalculationMode, rules]
|
||||||
properties:
|
properties:
|
||||||
tenantId: {type: string, pattern: '^[1-9][0-9]*$'}
|
tenantId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
defaultCalculationMode: {type: string, enum: [ADD, MULTIPLY]}
|
||||||
rules:
|
rules:
|
||||||
type: array
|
type: array
|
||||||
minItems: 4
|
minItems: 4
|
||||||
@@ -1508,6 +1529,8 @@ components:
|
|||||||
outerLengthCm: {type: number, format: double, nullable: true}
|
outerLengthCm: {type: number, format: double, nullable: true}
|
||||||
outerWidthCm: {type: number, format: double, nullable: true}
|
outerWidthCm: {type: number, format: double, nullable: true}
|
||||||
outerHeightCm: {type: number, format: double, nullable: true}
|
outerHeightCm: {type: number, format: double, nullable: true}
|
||||||
|
chargeRuleId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||||
|
coefficientMode: {type: string, enum: [ADD, MULTIPLY], nullable: true}
|
||||||
remark: {type: string, nullable: true}
|
remark: {type: string, nullable: true}
|
||||||
boxCode: {type: string, nullable: true}
|
boxCode: {type: string, nullable: true}
|
||||||
scannedShipmentId: {type: string, nullable: true}
|
scannedShipmentId: {type: string, nullable: true}
|
||||||
@@ -1622,6 +1645,8 @@ components:
|
|||||||
outerLengthCm: {type: number, minimum: 0, nullable: true}
|
outerLengthCm: {type: number, minimum: 0, nullable: true}
|
||||||
outerWidthCm: {type: number, minimum: 0, nullable: true}
|
outerWidthCm: {type: number, minimum: 0, nullable: true}
|
||||||
outerHeightCm: {type: number, minimum: 0, nullable: true}
|
outerHeightCm: {type: number, minimum: 0, nullable: true}
|
||||||
|
chargeRuleId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||||
|
coefficientMode: {type: string, enum: [ADD, MULTIPLY], nullable: true}
|
||||||
remark: {type: string, maxLength: 500, nullable: true}
|
remark: {type: string, maxLength: 500, nullable: true}
|
||||||
InboundMeasurementDetailEnvelope:
|
InboundMeasurementDetailEnvelope:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
+6
@@ -58,5 +58,11 @@ class CustomerDimensionConfigControllerContractTest {
|
|||||||
long.class, com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest.class)
|
long.class, com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest.class)
|
||||||
.getAnnotation(PreAuthorize.class).value())
|
.getAnnotation(PreAuthorize.class).value())
|
||||||
.isEqualTo("hasAuthority('firstleg:dimension-config:edit')");
|
.isEqualTo("hasAuthority('firstleg:dimension-config:edit')");
|
||||||
|
assertThat(PlatformCustomerDimensionConfigController.class
|
||||||
|
.getDeclaredMethod("updateDefaultCalculationMode",
|
||||||
|
com.baiyue.common.security.AuthenticatedUser.class, long.class,
|
||||||
|
com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.DefaultCalculationModeUpdateRequest.class)
|
||||||
|
.getAnnotation(PreAuthorize.class).value())
|
||||||
|
.isEqualTo("hasAuthority('firstleg:dimension-config:edit')");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.controller;
|
|||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
@@ -15,6 +16,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
@DisplayName("FBA计费OpenAPI契约")
|
@DisplayName("FBA计费OpenAPI契约")
|
||||||
class FbaPricingContractTest {
|
class FbaPricingContractTest {
|
||||||
|
@Test
|
||||||
|
@DisplayName("确认和发送费用兼容新的组合权限与原有权限")
|
||||||
|
void allowsCombinedConfirmSendPermissionAndLegacyPermissions() {
|
||||||
|
assertPricingAuthority("confirm", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
||||||
|
+ " 'firstleg:fba-pricing:confirm')");
|
||||||
|
assertPricingAuthority("send", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
||||||
|
+ " 'firstleg:inbound:send-fee')");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("客户规则保留客户参数,船司价格使用平台公共作用域")
|
@DisplayName("客户规则保留客户参数,船司价格使用平台公共作用域")
|
||||||
void keepsCustomerRuleScopeAndRemovesCarrierRateTenantScope() {
|
void keepsCustomerRuleScopeAndRemovesCarrierRateTenantScope() {
|
||||||
@@ -25,7 +35,7 @@ class FbaPricingContractTest {
|
|||||||
Method rates = method("rates");
|
Method rates = method("rates");
|
||||||
assertThat(rates.getParameterTypes()).doesNotContain(Long.class);
|
assertThat(rates.getParameterTypes()).doesNotContain(Long.class);
|
||||||
|
|
||||||
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule"}) {
|
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule", "setDefaultRule"}) {
|
||||||
Method method = method(scopedMethod);
|
Method method = method(scopedMethod);
|
||||||
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
|
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
|
||||||
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
|
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
|
||||||
@@ -45,6 +55,7 @@ class FbaPricingContractTest {
|
|||||||
.getContentAsString(StandardCharsets.UTF_8);
|
.getContentAsString(StandardCharsets.UTF_8);
|
||||||
assertThat(openapi)
|
assertThat(openapi)
|
||||||
.contains("/api/platform/v1/fba-pricing/customer-rules/versions:")
|
.contains("/api/platform/v1/fba-pricing/customer-rules/versions:")
|
||||||
|
.contains("/api/platform/v1/fba-pricing/customer-rules/{id}/default:")
|
||||||
.contains("/api/platform/v1/fba-pricing/carrier-rates/versions:")
|
.contains("/api/platform/v1/fba-pricing/carrier-rates/versions:")
|
||||||
.contains("/api/platform/v1/inbounds/{inboundId}/fees/calculate:")
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/calculate:")
|
||||||
.contains("/api/platform/v1/inbounds/{inboundId}/fees/confirm:")
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/confirm:")
|
||||||
@@ -146,6 +157,13 @@ class FbaPricingContractTest {
|
|||||||
.findFirst().orElseThrow();
|
.findFirst().orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertPricingAuthority(String name, String expected) {
|
||||||
|
Method method = java.util.Arrays.stream(PlatformFbaPricingController.class.getDeclaredMethods())
|
||||||
|
.filter(candidate -> candidate.getName().equals(name))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static Map<String, Object> map(Object value) {
|
private static Map<String, Object> map(Object value) {
|
||||||
return (Map<String, Object>) value;
|
return (Map<String, Object>) value;
|
||||||
|
|||||||
+2
-1
@@ -58,7 +58,8 @@ class PlatformInboundMeasurementControllerContractTest {
|
|||||||
"hasAnyAuthority('platform:firstleg:inbound:measure:view',"
|
"hasAnyAuthority('platform:firstleg:inbound:measure:view',"
|
||||||
+ " 'platform:firstleg:inbound:weight:edit')",
|
+ " 'platform:firstleg:inbound:weight:edit')",
|
||||||
AuthenticatedUser.class, long.class, long.class, long.class, long.class, String.class);
|
AuthenticatedUser.class, long.class, long.class, long.class, long.class, String.class);
|
||||||
assertAuthority("saveDraft", "hasAuthority('platform:firstleg:inbound:measure:edit')",
|
assertAuthority("saveDraft", "hasAnyAuthority('platform:firstleg:inbound:warehouse-suggestion:edit',"
|
||||||
|
+ " 'platform:firstleg:inbound:measure:edit')",
|
||||||
AuthenticatedUser.class, long.class, long.class,
|
AuthenticatedUser.class, long.class, long.class,
|
||||||
InboundMeasurementRequests.SaveDraftRequest.class);
|
InboundMeasurementRequests.SaveDraftRequest.class);
|
||||||
assertAuthority("submit", "hasAuthority('platform:firstleg:inbound:measure:submit')",
|
assertAuthority("submit", "hasAuthority('platform:firstleg:inbound:measure:submit')",
|
||||||
|
|||||||
+15
@@ -5,6 +5,7 @@ import com.baiyue.firstleg.mapper.CustomerDimensionConfigMapper;
|
|||||||
import org.apache.ibatis.annotations.Delete;
|
import org.apache.ibatis.annotations.Delete;
|
||||||
import org.apache.ibatis.annotations.Insert;
|
import org.apache.ibatis.annotations.Insert;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ class CustomerDimensionConfigSqlContractTest {
|
|||||||
.contains("inner_width_cm DECIMAL(9,3) NOT NULL")
|
.contains("inner_width_cm DECIMAL(9,3) NOT NULL")
|
||||||
.contains("inner_height_cm DECIMAL(9,3) NOT NULL")
|
.contains("inner_height_cm DECIMAL(9,3) NOT NULL")
|
||||||
.contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000")
|
.contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000")
|
||||||
|
.contains("default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD'")
|
||||||
.contains("initialization_key VARCHAR(128) NOT NULL")
|
.contains("initialization_key VARCHAR(128) NOT NULL")
|
||||||
.contains("created_by BIGINT UNSIGNED NOT NULL")
|
.contains("created_by BIGINT UNSIGNED NOT NULL")
|
||||||
.contains("updated_by BIGINT UNSIGNED NOT NULL")
|
.contains("updated_by BIGINT UNSIGNED NOT NULL")
|
||||||
@@ -88,6 +90,19 @@ class CustomerDimensionConfigSqlContractTest {
|
|||||||
.contains("remark = CASE box_type");
|
.contains("remark = CASE box_type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defaultCalculationModeUpdateCoversTheWholeCustomerRuleSet() throws Exception {
|
||||||
|
Update annotation = CustomerDimensionConfigMapper.class
|
||||||
|
.getDeclaredMethod("updateDefaultCalculationMode", long.class, String.class, long.class)
|
||||||
|
.getAnnotation(Update.class);
|
||||||
|
String sql = String.join(" ", annotation.value());
|
||||||
|
|
||||||
|
assertThat(sql)
|
||||||
|
.contains("default_calculation_mode = #{mode}")
|
||||||
|
.contains("tenant_id = #{tenantId}")
|
||||||
|
.contains("version = version + 1");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void unifiedMigrationKeepsPermissionGuardsAndAssertsFinalFourRowSchema() throws Exception {
|
void unifiedMigrationKeepsPermissionGuardsAndAssertsFinalFourRowSchema() throws Exception {
|
||||||
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||||
|
|||||||
+15
@@ -140,6 +140,20 @@ class CustomerDimensionConfigServiceTest {
|
|||||||
verify(repository, times(2)).update(any());
|
verify(repository, times(2)).update(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void savesAndReturnsTheCustomerDefaultCalculationMode() {
|
||||||
|
List<CustomerDimensionConfigEntity> existing = defaultConfigs("registration-1");
|
||||||
|
List<CustomerDimensionConfigEntity> saved = defaultConfigs("registration-1");
|
||||||
|
saved.forEach(value -> value.setDefaultCalculationMode("MULTIPLY"));
|
||||||
|
when(repository.findAll(8L)).thenReturn(existing, saved);
|
||||||
|
when(repository.updateDefaultCalculationMode(8L, "MULTIPLY", 100L)).thenReturn(4);
|
||||||
|
|
||||||
|
GroupResponse result = service.updateDefaultCalculationMode(8L, 100L, "MULTIPLY");
|
||||||
|
|
||||||
|
assertThat(result.defaultCalculationMode()).isEqualTo("MULTIPLY");
|
||||||
|
verify(repository).updateDefaultCalculationMode(8L, "MULTIPLY", 100L);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void compensationLocksAndDeletesExactlyFourUnchangedDefaults() {
|
void compensationLocksAndDeletesExactlyFourUnchangedDefaults() {
|
||||||
when(repository.findDefaultsForCompensation(8L, "registration-1"))
|
when(repository.findDefaultsForCompensation(8L, "registration-1"))
|
||||||
@@ -192,6 +206,7 @@ class CustomerDimensionConfigServiceTest {
|
|||||||
value.setInnerWidthCm(type.defaultWidthCm());
|
value.setInnerWidthCm(type.defaultWidthCm());
|
||||||
value.setInnerHeightCm(type.defaultHeightCm());
|
value.setInnerHeightCm(type.defaultHeightCm());
|
||||||
value.setEdgeAdditionCm(new BigDecimal("2.000"));
|
value.setEdgeAdditionCm(new BigDecimal("2.000"));
|
||||||
|
value.setDefaultCalculationMode("ADD");
|
||||||
value.setRemark(type.defaultRemark());
|
value.setRemark(type.defaultRemark());
|
||||||
value.setVersion(0);
|
value.setVersion(0);
|
||||||
value.setInitializationKey(initializationKey);
|
value.setInitializationKey(initializationKey);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.baiyue.firstleg.service;
|
package com.baiyue.firstleg.service;
|
||||||
|
|
||||||
import com.baiyue.common.core.exception.BizException;
|
|
||||||
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
|
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
|
||||||
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
|
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
|
||||||
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
||||||
@@ -17,10 +16,10 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -47,10 +46,9 @@ class FbaMasterDataServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("半开有效期首尾相接不视为重叠")
|
@DisplayName("有效期合法时创建客户规则")
|
||||||
void acceptsAdjacentHalfOpenPeriods() {
|
void createsRuleWithValidPeriod() {
|
||||||
CustomerChargeRuleEntity old = activeRule(AUGUST.minusMonths(1), AUGUST);
|
CustomerChargeRuleEntity old = activeRule(AUGUST.minusMonths(1), AUGUST);
|
||||||
when(repository.listRules(1L, null)).thenReturn(List.of(old));
|
|
||||||
when(repository.listRules(1L, "CR-1")).thenReturn(List.of(old));
|
when(repository.listRules(1L, "CR-1")).thenReturn(List.of(old));
|
||||||
when(repository.nextRuleVersion(1L, "CR-1")).thenReturn(2);
|
when(repository.nextRuleVersion(1L, "CR-1")).thenReturn(2);
|
||||||
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(10L); return null; })
|
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(10L); return null; })
|
||||||
@@ -61,18 +59,25 @@ class FbaMasterDataServiceTest {
|
|||||||
|
|
||||||
assertThat(created.getId()).isEqualTo(10L);
|
assertThat(created.getId()).isEqualTo(10L);
|
||||||
assertThat(created.getRuleVersion()).isEqualTo(2);
|
assertThat(created.getRuleVersion()).isEqualTo(2);
|
||||||
|
assertThat(created.getEffectiveFrom()).isEqualTo(AUGUST);
|
||||||
|
assertThat(created.getEffectiveTo()).isEqualTo(AUGUST.plusMonths(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("任意重叠有效期在保存阶段拒绝避免多规则匹配")
|
@DisplayName("重叠配置允许新增且新增时固定不启用")
|
||||||
void rejectsOverlappingPeriods() {
|
void createsOverlappingRuleAsNonDefault() {
|
||||||
when(repository.listRules(1L, null)).thenReturn(List.of(
|
when(repository.nextRuleVersion(1L, "CR-1")).thenReturn(2);
|
||||||
activeRule(AUGUST, AUGUST.plusMonths(1))));
|
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(11L); return null; })
|
||||||
|
.when(repository).insertRule(any());
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.createRuleVersion(
|
CustomerChargeRuleEntity created = service.createRuleVersion(
|
||||||
1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2))))
|
1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2), true));
|
||||||
.isInstanceOf(BizException.class)
|
|
||||||
.hasMessageContaining("有效期重叠");
|
assertThat(created.getId()).isEqualTo(11L);
|
||||||
|
assertThat(created.getDefaultRule()).isFalse();
|
||||||
|
verify(repository, never()).clearDefaultRule(anyLong(), anyLong(), anyLong());
|
||||||
|
verify(repository, never()).setDefaultRule(anyLong(), anyLong(), anyLong());
|
||||||
|
verify(repository, never()).listRules(1L, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -92,8 +97,13 @@ class FbaMasterDataServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to) {
|
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to) {
|
||||||
|
return request(from, to, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to, Boolean defaultRule) {
|
||||||
return new CustomerRuleVersionRequest("CR-1", 100L, "客户A", "FBA_SEA", "KG",
|
return new CustomerRuleVersionRequest("CR-1", 100L, "客户A", "FBA_SEA", "KG",
|
||||||
"TAX_INCLUDED", new BigDecimal("167"), 3, "CEILING", "USD", from, to, null);
|
"TAX_INCLUDED", new BigDecimal("167"), 3, "CEILING", "USD", from, to, null,
|
||||||
|
BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, defaultRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CarrierRateVersionRequest carrierRateRequest() {
|
private CarrierRateVersionRequest carrierRateRequest() {
|
||||||
|
|||||||
+165
-9
@@ -7,6 +7,8 @@ import com.baiyue.firstleg.entity.CustomerDimensionConfigEntity;
|
|||||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
|
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||||
|
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||||
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||||
import com.baiyue.firstleg.repository.CustomerDimensionConfigRepository;
|
import com.baiyue.firstleg.repository.CustomerDimensionConfigRepository;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -34,6 +36,7 @@ import static org.mockito.Mockito.when;
|
|||||||
class MeasurementBatchServiceTest {
|
class MeasurementBatchServiceTest {
|
||||||
@MockBean private InboundMeasurementRepository repository;
|
@MockBean private InboundMeasurementRepository repository;
|
||||||
@MockBean private CustomerDimensionConfigRepository dimensionConfigs;
|
@MockBean private CustomerDimensionConfigRepository dimensionConfigs;
|
||||||
|
@MockBean private FbaPricingRepository pricingRepository;
|
||||||
@Resource private MeasurementBatchService service;
|
@Resource private MeasurementBatchService service;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -62,14 +65,17 @@ class MeasurementBatchServiceTest {
|
|||||||
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
assertThat(items.getValue().get(0).getUnitVolumeCbm()).isEqualByComparingTo("0.0510");
|
assertThat(items.getValue().get(0).getUnitVolumeCbm()).isEqualByComparingTo("0.0267");
|
||||||
assertThat(items.getValue().get(0).getTotalVolumeCbm()).isEqualByComparingTo("0.1020");
|
assertThat(items.getValue().get(0).getTotalVolumeCbm()).isEqualByComparingTo("0.0534");
|
||||||
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("44.000");
|
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("41.000");
|
||||||
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("34.000");
|
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("31.000");
|
||||||
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("34.000");
|
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("21.000");
|
||||||
assertThat(items.getValue().get(0).getCalculationRuleVersion())
|
assertThat(items.getValue().get(0).getCalculationRuleVersion())
|
||||||
.isEqualTo("CUSTOMER_DIMENSION_MEDIUM_V0");
|
.isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
|
||||||
assertThat(items.getValue().get(0).getGirthCm()).isEqualByComparingTo("140.000");
|
assertThat(items.getValue().get(0).getCoefficientMode()).isNull();
|
||||||
|
assertThat(items.getValue().get(0).getChargeRuleId()).isNull();
|
||||||
|
assertThat(items.getValue().get(0).getGirthCm()).isEqualByComparingTo("145.000");
|
||||||
|
verify(pricingRepository).findDefaultRule(1L, 1L);
|
||||||
verify(repository, never()).updateInbound(any());
|
verify(repository, never()).updateInbound(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,14 +89,163 @@ class MeasurementBatchServiceTest {
|
|||||||
InboundMeasurementEntity saved = service.saveBatch(command);
|
InboundMeasurementEntity saved = service.saveBatch(command);
|
||||||
|
|
||||||
assertThat(saved.getStatus()).isEqualTo("DRAFT");
|
assertThat(saved.getStatus()).isEqualTo("DRAFT");
|
||||||
assertThat(saved.getTotalVolumeCbm()).isEqualByComparingTo("0.1020");
|
assertThat(saved.getTotalVolumeCbm()).isEqualByComparingTo("0.0534");
|
||||||
assertThat(saved.getVolumeWeightKg()).isEqualByComparingTo("17.034");
|
assertThat(saved.getVolumeWeightKg()).isEqualByComparingTo("8.918");
|
||||||
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
assertThat(items.getValue().get(0).getInboundDetailId()).isEqualTo(80001L);
|
assertThat(items.getValue().get(0).getInboundDetailId()).isEqualTo(80001L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void automaticallyAppliesCustomerDefaultRuleToActualMeasurement() {
|
||||||
|
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
|
||||||
|
rule.setId(77L);
|
||||||
|
rule.setTenantId(1L);
|
||||||
|
rule.setCustomerId(1L);
|
||||||
|
rule.setRuleCode("DEFAULT");
|
||||||
|
rule.setRuleVersion(2);
|
||||||
|
rule.setStatus("ACTIVE");
|
||||||
|
rule.setDefaultRule(true);
|
||||||
|
rule.setLengthFactor(new BigDecimal("1.1"));
|
||||||
|
rule.setWidthFactor(new BigDecimal("1.1"));
|
||||||
|
rule.setHeightFactor(new BigDecimal("1.1"));
|
||||||
|
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
|
||||||
|
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
|
||||||
|
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||||
|
new BigDecimal("20"), 500L, null, null, null, null, null,
|
||||||
|
null, "备注");
|
||||||
|
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||||
|
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||||
|
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
|
||||||
|
|
||||||
|
service.saveBatch(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
|
InboundMeasurementItemEntity calculated = items.getValue().get(0);
|
||||||
|
assertThat(calculated.getChargeRuleId()).isEqualTo(77L);
|
||||||
|
assertThat(calculated.getCoefficientMode()).isEqualTo("MULTIPLY");
|
||||||
|
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("44.000");
|
||||||
|
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("33.000");
|
||||||
|
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("22.000");
|
||||||
|
assertThat(calculated.getGirthCm()).isEqualByComparingTo("154.000");
|
||||||
|
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0320");
|
||||||
|
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.0640");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usesOneMatchedTierCoefficientForAllDimensionsOfEachSpecification() {
|
||||||
|
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
|
||||||
|
rule.setId(78L);
|
||||||
|
rule.setTenantId(1L);
|
||||||
|
rule.setCustomerId(1L);
|
||||||
|
rule.setRuleCode("TIERED");
|
||||||
|
rule.setRuleVersion(3);
|
||||||
|
rule.setStatus("ACTIVE");
|
||||||
|
rule.setDefaultRule(true);
|
||||||
|
rule.setLengthFactor(new BigDecimal("1.05"));
|
||||||
|
rule.setWidthFactor(new BigDecimal("1.25"));
|
||||||
|
rule.setHeightFactor(new BigDecimal("1.35"));
|
||||||
|
rule.setRemark("SMALL:30,20,20;系数:1.1;小箱:—;MEDIUM:40,30,30;系数:1.2;中箱:—;"
|
||||||
|
+ "LARGE:50,40,40;系数:1.3;大箱:—;EXTRA_LARGE:60,50,50;系数:1.4;特大箱:—");
|
||||||
|
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
|
||||||
|
MeasurementBatchCommand.Item small = new MeasurementBatchCommand.Item(
|
||||||
|
80001L, "001", "A", 1, new BigDecimal("30"), new BigDecimal("20"),
|
||||||
|
new BigDecimal("20"), 500L, null, null, null, null, null, null, "小箱");
|
||||||
|
MeasurementBatchCommand.Item medium = new MeasurementBatchCommand.Item(
|
||||||
|
80002L, "002", "B", 1, new BigDecimal("31"), new BigDecimal("20"),
|
||||||
|
new BigDecimal("20"), 501L, null, null, null, null, null, null, "中箱");
|
||||||
|
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||||
|
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||||
|
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(small, medium));
|
||||||
|
|
||||||
|
service.saveBatch(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
|
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("33.000");
|
||||||
|
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("22.000");
|
||||||
|
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("22.000");
|
||||||
|
assertThat(items.getValue().get(1).getOuterLengthCm()).isEqualByComparingTo("37.200");
|
||||||
|
assertThat(items.getValue().get(1).getOuterWidthCm()).isEqualByComparingTo("24.000");
|
||||||
|
assertThat(items.getValue().get(1).getOuterHeightCm()).isEqualByComparingTo("24.000");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsActualMeasurementUnchangedWhenCustomerHasNoDefaultRule() {
|
||||||
|
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
|
||||||
|
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||||
|
new BigDecimal("20"), 500L, null, null, null, null, null, null, "备注");
|
||||||
|
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||||
|
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||||
|
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
|
||||||
|
|
||||||
|
service.saveBatch(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
|
InboundMeasurementItemEntity calculated = items.getValue().get(0);
|
||||||
|
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("40.000");
|
||||||
|
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("30.000");
|
||||||
|
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("20.000");
|
||||||
|
assertThat(calculated.getChargeRuleId()).isNull();
|
||||||
|
assertThat(calculated.getCoefficientMode()).isNull();
|
||||||
|
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("ACTUAL_DIMENSION_V1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addsMatchedEdgeValueToMeasuredDimensionsInsteadOfRuleBoundary() {
|
||||||
|
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
|
||||||
|
80001L, "001", "A", 2, new BigDecimal("59.5"), new BigDecimal("41.2"),
|
||||||
|
new BigDecimal("31.3"), 500L, null, null, null, null, null, "ADD", "备注");
|
||||||
|
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||||
|
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||||
|
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
|
||||||
|
|
||||||
|
service.saveBatch(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
|
InboundMeasurementItemEntity calculated = items.getValue().get(0);
|
||||||
|
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("61.500");
|
||||||
|
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("43.200");
|
||||||
|
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("33.300");
|
||||||
|
assertThat(calculated.getGirthCm()).isEqualByComparingTo("214.500");
|
||||||
|
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.088");
|
||||||
|
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1760");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void warehouseOuterDimensionsOverrideCalculatedCartonAndAllDerivedValues() {
|
||||||
|
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
|
||||||
|
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||||
|
new BigDecimal("20"), 500L, null, new BigDecimal("50"),
|
||||||
|
new BigDecimal("40"), new BigDecimal("30"), null, "ADD", "仓库修正");
|
||||||
|
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||||
|
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||||
|
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
|
||||||
|
|
||||||
|
service.saveBatch(command);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||||
|
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||||
|
InboundMeasurementItemEntity calculated = items.getValue().get(0);
|
||||||
|
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("50.000");
|
||||||
|
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("40.000");
|
||||||
|
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("30.000");
|
||||||
|
assertThat(calculated.getGirthCm()).isEqualByComparingTo("190.000");
|
||||||
|
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0600");
|
||||||
|
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1200");
|
||||||
|
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("20.040");
|
||||||
|
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void staleDraftVersionIsRejectedBeforeReplacingItems() {
|
void staleDraftVersionIsRejectedBeforeReplacingItems() {
|
||||||
InboundMeasurementEntity existing = new InboundMeasurementEntity();
|
InboundMeasurementEntity existing = new InboundMeasurementEntity();
|
||||||
@@ -149,6 +304,7 @@ class MeasurementBatchServiceTest {
|
|||||||
InboundOrderEntity inbound = new InboundOrderEntity();
|
InboundOrderEntity inbound = new InboundOrderEntity();
|
||||||
inbound.setId(10L);
|
inbound.setId(10L);
|
||||||
inbound.setTenantId(1L);
|
inbound.setTenantId(1L);
|
||||||
|
inbound.setCustomerId(1L);
|
||||||
inbound.setProcessStatus("WAIT_MEASURE");
|
inbound.setProcessStatus("WAIT_MEASURE");
|
||||||
inbound.setVersion(0);
|
inbound.setVersion(0);
|
||||||
return inbound;
|
return inbound;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
|
|||||||
|
|
||||||
UPDATE iam_permission p
|
UPDATE iam_permission p
|
||||||
JOIN (SELECT 'platform:firstleg:inbound:measure:edit' code, 5102041 parent_id UNION ALL
|
JOIN (SELECT 'platform:firstleg:inbound:measure:edit' code, 5102041 parent_id UNION ALL
|
||||||
|
SELECT 'platform:firstleg:inbound:warehouse-suggestion:edit', 5102041 UNION ALL
|
||||||
SELECT 'platform:firstleg:inbound:measure:submit', 5102041 UNION ALL
|
SELECT 'platform:firstleg:inbound:measure:submit', 5102041 UNION ALL
|
||||||
SELECT 'platform:firstleg:inbound:measure:manual-entry', 5102041 UNION ALL
|
SELECT 'platform:firstleg:inbound:measure:manual-entry', 5102041 UNION ALL
|
||||||
SELECT 'platform:firstleg:inbound:weight:edit', 5102041 UNION ALL
|
SELECT 'platform:firstleg:inbound:weight:edit', 5102041 UNION ALL
|
||||||
|
|||||||
@@ -13,17 +13,27 @@ VALUES
|
|||||||
(5102065, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:calculate', '计算入库费用', NULL, NULL, 50, 'ACTIVE', 1, 0),
|
(5102065, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:calculate', '计算入库费用', NULL, NULL, 50, 'ACTIVE', 1, 0),
|
||||||
(5102066, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:confirm', '内部确认入库费用', NULL, NULL, 60, 'ACTIVE', 1, 0),
|
(5102066, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:confirm', '内部确认入库费用', NULL, NULL, 60, 'ACTIVE', 1, 0),
|
||||||
(5102067, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:view', '查看入库费用', NULL, NULL, 70, 'ACTIVE', 1, 0),
|
(5102067, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:view', '查看入库费用', NULL, NULL, 70, 'ACTIVE', 1, 0),
|
||||||
(5102068, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:adjust', '调整已发布费用', NULL, NULL, 80, 'ACTIVE', 1, 0)
|
(5102068, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:adjust', '调整已发布费用', NULL, NULL, 80, 'ACTIVE', 1, 0),
|
||||||
|
(5102069, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:confirm-send-fee', '确认并推送客户费用', NULL, NULL, 90, 'ACTIVE', 1, 0)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
|
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
|
||||||
status = 'ACTIVE', built_in = 1, deleted = 0;
|
status = 'ACTIVE', built_in = 1, deleted = 0;
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000001, id, 0 FROM iam_permission
|
SELECT 4000001, id, 0 FROM iam_permission
|
||||||
WHERE id BETWEEN 5102061 AND 5102068
|
WHERE id BETWEEN 5102061 AND 5102069
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000003, id, 0 FROM iam_permission
|
SELECT 4000003, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102061, 5102063, 5102065, 5102066, 5102067, 5102068)
|
WHERE id IN (5102061, 5102063, 5102065, 5102066, 5102067, 5102068, 5102069)
|
||||||
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
|
SELECT role.id, permission.id, 0
|
||||||
|
FROM iam_role role
|
||||||
|
JOIN iam_permission permission
|
||||||
|
ON permission.tenant_id = 0
|
||||||
|
AND permission.permission_code = 'firstleg:inbound:confirm-send-fee'
|
||||||
|
WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN'
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ VALUES
|
|||||||
(5102045, 0, 'PLATFORM', 'BUTTON', 5102030,
|
(5102045, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
'platform:firstleg:inbound:weight:confirm', '确认OCR重量', NULL, NULL, 40, 'ACTIVE', 0),
|
'platform:firstleg:inbound:weight:confirm', '确认OCR重量', NULL, NULL, 40, 'ACTIVE', 0),
|
||||||
(5102194, 0, 'PLATFORM', 'BUTTON', 5102030,
|
(5102194, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
'platform:firstleg:inbound:measure:manual-entry', '手动录入箱规', NULL, NULL, 25, 'ACTIVE', 0)
|
'platform:firstleg:inbound:measure:manual-entry', '手动录入箱规', NULL, NULL, 25, 'ACTIVE', 0),
|
||||||
|
(5102195, 0, 'PLATFORM', 'BUTTON', 5102030,
|
||||||
|
'platform:firstleg:inbound:warehouse-suggestion:edit', '维护仓库建议测量数据', NULL, NULL, 27, 'ACTIVE', 0)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
|
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
|
||||||
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
|
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
|
||||||
@@ -25,12 +27,21 @@ ON DUPLICATE KEY UPDATE
|
|||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000001, id, 0 FROM iam_permission
|
SELECT 4000001, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
|
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194, 5102195)
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
SELECT 4000003, id, 0 FROM iam_permission
|
SELECT 4000003, id, 0 FROM iam_permission
|
||||||
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
|
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194, 5102195)
|
||||||
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
|
SELECT role.id, permission.id, 0
|
||||||
|
FROM iam_role role
|
||||||
|
JOIN iam_permission permission
|
||||||
|
ON permission.tenant_id = 0
|
||||||
|
AND permission.permission_code = 'platform:firstleg:inbound:warehouse-suggestion:edit'
|
||||||
|
WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN'
|
||||||
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
|
||||||
|
|||||||
@@ -726,19 +726,6 @@ UPDATE ll_inbound_plan_pallet_allocation a JOIN ll_inbound_plan p ON p.id=a.plan
|
|||||||
ALTER TABLE ll_inbound_plan_pallet_allocation MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识';
|
ALTER TABLE ll_inbound_plan_pallet_allocation MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识';
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND index_name='idx_ll_inbound_allocation_customer_plan'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD KEY idx_ll_inbound_allocation_customer_plan(customer_id,plan_id)'));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND index_name='idx_ll_inbound_allocation_customer_plan'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD KEY idx_ll_inbound_allocation_customer_plan(customer_id,plan_id)'));
|
||||||
PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt;
|
PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt;
|
||||||
DROP PROCEDURE IF EXISTS assert_inbound_plan_customer_scope_ready;
|
|
||||||
DELIMITER $$
|
|
||||||
CREATE PROCEDURE assert_inbound_plan_customer_scope_ready()
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM ll_inbound_plan_line l LEFT JOIN ll_inbound_plan p ON p.id=l.plan_id WHERE l.customer_id IS NULL OR l.customer_id='' OR p.id IS NULL OR l.customer_id<>p.customer_id)
|
|
||||||
OR EXISTS (SELECT 1 FROM ll_inbound_plan_pallet v LEFT JOIN ll_inbound_plan p ON p.id=v.plan_id WHERE v.customer_id IS NULL OR v.customer_id='' OR p.id IS NULL OR v.customer_id<>p.customer_id)
|
|
||||||
OR EXISTS (SELECT 1 FROM ll_inbound_plan_pallet_allocation a LEFT JOIN ll_inbound_plan p ON p.id=a.plan_id WHERE a.customer_id IS NULL OR a.customer_id='' OR p.id IS NULL OR a.customer_id<>p.customer_id) THEN
|
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Inbound plan child customer scope migration is incomplete';
|
|
||||||
END IF;
|
|
||||||
END$$
|
|
||||||
DELIMITER ;
|
|
||||||
CALL assert_inbound_plan_customer_scope_ready();
|
|
||||||
DROP PROCEDURE assert_inbound_plan_customer_scope_ready;
|
|
||||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='destination_type'), 'ALTER TABLE ll_inbound_plan_line MODIFY COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码''', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER inbound_code'));
|
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='destination_type'), 'ALTER TABLE ll_inbound_plan_line MODIFY COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码''', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER inbound_code'));
|
||||||
PREPARE receiving_container_line_stmt FROM @ddl; EXECUTE receiving_container_line_stmt; DEALLOCATE PREPARE receiving_container_line_stmt;
|
PREPARE receiving_container_line_stmt FROM @ddl; EXECUTE receiving_container_line_stmt; DEALLOCATE PREPARE receiving_container_line_stmt;
|
||||||
UPDATE ll_inbound_plan_line l JOIN ll_order o ON o.id=l.order_id
|
UPDATE ll_inbound_plan_line l JOIN ll_order o ON o.id=l.order_id
|
||||||
|
|||||||
Reference in New Issue
Block a user