diff --git a/db.sql b/db.sql index 595997a..32e2179 100644 --- a/db.sql +++ b/db.sql @@ -1489,6 +1489,17 @@ CREATE TABLE `t_sales_store_order` ( `PlatformCode` VARCHAR(32) NOT NULL COMMENT '平台编码', `PlatformOrderNo` VARCHAR(64) NOT NULL COMMENT '平台订单号', `OrderCountry` VARCHAR(8) NULL COMMENT '订单国家', + `RecipientName` VARCHAR(64) NULL COMMENT '收件人姓名', + `RecipientCompany` VARCHAR(128) NULL COMMENT '收件人公司', + `RecipientPhone` VARCHAR(32) NULL COMMENT '收件人手机', + `RecipientTelePhone` VARCHAR(32) NULL COMMENT '收件人电话', + `RecipientEmail` VARCHAR(64) NULL COMMENT '收件人邮箱', + `RecipientCountry` VARCHAR(8) NULL COMMENT '收件国家码', + `RecipientProvince` VARCHAR(64) NULL COMMENT '收件省/州', + `RecipientCity` VARCHAR(64) NULL COMMENT '收件城市', + `RecipientZipCode` VARCHAR(32) NULL COMMENT '收件邮编', + `RecipientAddress1` VARCHAR(512) NULL COMMENT '收件地址1', + `RecipientAddress2` VARCHAR(512) NULL COMMENT '收件地址2', `ItemCount` INT NULL COMMENT '商品行数', `TotalAmount` DECIMAL(18,4) NULL COMMENT '订单总额', `Currency` VARCHAR(8) NULL COMMENT '币种', diff --git a/sql/fix_sales_store_concurrency_stamp.sql b/sql/fix_sales_store_concurrency_stamp.sql new file mode 100644 index 0000000..f8b503b --- /dev/null +++ b/sql/fix_sales_store_concurrency_stamp.sql @@ -0,0 +1,41 @@ +-- 修复店铺相关表 ConcurrencyStamp 无法绑定到 Guid 的问题 +-- 典型报错:ConcurrencyStamp绑定到SalesStoreOrder失败 / SalesStoreOrder ConcurrencyStamp bind error +-- +-- 原因:补列时若写入空串 '',SqlSugar 无法把 '' 转成 System.Guid +-- 用法:mysql -u root -p oms < sql/fix_sales_store_concurrency_stamp.sql + +SET NAMES utf8mb4; + +UPDATE `t_sales_store` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL + OR TRIM(`ConcurrencyStamp`) = '' + OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; + +UPDATE `t_sales_store_order` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL + OR TRIM(`ConcurrencyStamp`) = '' + OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; + +UPDATE `t_sales_store_order_line` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL + OR TRIM(`ConcurrencyStamp`) = '' + OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; + +-- 抽查 +SELECT 't_sales_store' AS tbl, + SUM(CASE WHEN TRIM(IFNULL(ConcurrencyStamp,'')) = '' OR LENGTH(TRIM(ConcurrencyStamp)) <> 36 THEN 1 ELSE 0 END) AS bad_cnt, + COUNT(*) AS total +FROM `t_sales_store` +UNION ALL +SELECT 't_sales_store_order', + SUM(CASE WHEN TRIM(IFNULL(ConcurrencyStamp,'')) = '' OR LENGTH(TRIM(ConcurrencyStamp)) <> 36 THEN 1 ELSE 0 END), + COUNT(*) +FROM `t_sales_store_order` +UNION ALL +SELECT 't_sales_store_order_line', + SUM(CASE WHEN TRIM(IFNULL(ConcurrencyStamp,'')) = '' OR LENGTH(TRIM(ConcurrencyStamp)) <> 36 THEN 1 ELSE 0 END), + COUNT(*) +FROM `t_sales_store_order_line`; diff --git a/sql/migrate_t_sales_store_order_recipient.sql b/sql/migrate_t_sales_store_order_recipient.sql new file mode 100644 index 0000000..588025f --- /dev/null +++ b/sql/migrate_t_sales_store_order_recipient.sql @@ -0,0 +1,32 @@ +-- 店铺订单补收件人字段(可重复执行) +-- 用法:mysql -u root -p oms < sql/migrate_t_sales_store_order_recipient.sql + +SET NAMES utf8mb4; + +DROP PROCEDURE IF EXISTS sp_add_store_order_recipient_cols; +DELIMITER $$ +CREATE PROCEDURE sp_add_store_order_recipient_cols() +BEGIN + DECLARE col_exists INT DEFAULT 0; + + SELECT COUNT(*) INTO col_exists FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_sales_store_order' AND COLUMN_NAME = 'RecipientName'; + IF col_exists = 0 THEN + ALTER TABLE `t_sales_store_order` + ADD COLUMN `RecipientName` VARCHAR(64) NULL COMMENT '收件人姓名' AFTER `OrderCountry`, + ADD COLUMN `RecipientCompany` VARCHAR(128) NULL COMMENT '收件人公司' AFTER `RecipientName`, + ADD COLUMN `RecipientPhone` VARCHAR(32) NULL COMMENT '收件人手机' AFTER `RecipientCompany`, + ADD COLUMN `RecipientTelePhone` VARCHAR(32) NULL COMMENT '收件人电话' AFTER `RecipientPhone`, + ADD COLUMN `RecipientEmail` VARCHAR(64) NULL COMMENT '收件人邮箱' AFTER `RecipientTelePhone`, + ADD COLUMN `RecipientCountry` VARCHAR(8) NULL COMMENT '收件国家码' AFTER `RecipientEmail`, + ADD COLUMN `RecipientProvince` VARCHAR(64) NULL COMMENT '收件省/州' AFTER `RecipientCountry`, + ADD COLUMN `RecipientCity` VARCHAR(64) NULL COMMENT '收件城市' AFTER `RecipientProvince`, + ADD COLUMN `RecipientZipCode` VARCHAR(32) NULL COMMENT '收件邮编' AFTER `RecipientCity`, + ADD COLUMN `RecipientAddress1` VARCHAR(512) NULL COMMENT '收件地址1' AFTER `RecipientZipCode`, + ADD COLUMN `RecipientAddress2` VARCHAR(512) NULL COMMENT '收件地址2' AFTER `RecipientAddress1`; + END IF; +END$$ +DELIMITER ; + +CALL sp_add_store_order_recipient_cols(); +DROP PROCEDURE IF EXISTS sp_add_store_order_recipient_cols; diff --git a/sql/seed_pickup_order_customer9.sql b/sql/seed_pickup_order_customer9.sql new file mode 100644 index 0000000..07388df --- /dev/null +++ b/sql/seed_pickup_order_customer9.sql @@ -0,0 +1,306 @@ +-- 揽收订单种子(CustomerId = 9,OrderSource = pickup) +-- 可重复执行:已存在则跳过 +-- 用法:mysql -u root -p oms < sql/seed_pickup_order_customer9.sql +-- +-- 依赖:t_customer.Id=9 存在;仓库/销售产品尽量取库内已有,否则用占位编码 + +SET NAMES utf8mb4; + +SET @customerId := 9; + +-- --------------------------------------------------------------------------- +-- 0. 修复 t_order 脏 ConcurrencyStamp(空串无法绑 Guid) +-- --------------------------------------------------------------------------- +UPDATE `t_order` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL + OR TRIM(`ConcurrencyStamp`) = '' + OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; + +-- --------------------------------------------------------------------------- +-- 1. 发件地址簿(揽收创建依赖) +-- --------------------------------------------------------------------------- +INSERT INTO `t_customer_sender_address` ( + `CustomerId`, `AddressType`, `AddressName`, `SenderName`, `Company`, + `Province`, `City`, `ZipCode`, `StreetAddress1`, `Phone`, + `IsDefault`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT @customerId, 'sender', '种子-默认发件', '张三', '种子发件公司', + '广东省', '深圳市', '518000', '南山区科技园种子路 1 号', '13800000009', + 1, 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_customer_sender_address` + WHERE `CustomerId` = @customerId + AND `AddressType` = 'sender' + AND `AddressName` = '种子-默认发件' + AND `IsDeleted` = 0 +); + +SET @senderAddressId := ( + SELECT `Id` FROM `t_customer_sender_address` + WHERE `CustomerId` = @customerId + AND `AddressType` = 'sender' + AND `AddressName` = '种子-默认发件' + AND `IsDeleted` = 0 + ORDER BY `Id` + LIMIT 1 +); + +-- 仓库:优先取已有;没有则插入一条种子仓 +INSERT INTO `t_warehouse_address` ( + `WarehouseName`, `WarehouseType`, `Recipient`, `ContactPhone`, `ShippingAddress`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT '种子-深圳仓', 'main', '仓管员', '0755-80000009', '广东省深圳市南山区仓库大道 9 号', + 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_warehouse_address` WHERE `IsDeleted` = 0 LIMIT 1 +) +AND NOT EXISTS ( + SELECT 1 FROM `t_warehouse_address` WHERE `WarehouseName` = '种子-深圳仓' AND `IsDeleted` = 0 +); + +SET @warehouseId := ( + SELECT `Id` FROM `t_warehouse_address` + WHERE `IsDeleted` = 0 + ORDER BY CASE WHEN `WarehouseName` = '种子-深圳仓' THEN 0 ELSE 1 END, `Id` + LIMIT 1 +); + +SET @productCode := ( + SELECT `ProductCode` FROM `t_sales_product` + WHERE `IsDeleted` = 0 + ORDER BY `Id` + LIMIT 1 +); +SET @productCode := IFNULL(NULLIF(TRIM(@productCode), ''), 'SEED-PICKUP-PRODUCT'); + +-- --------------------------------------------------------------------------- +-- 2. 揽收订单 t_order(若干状态) +-- --------------------------------------------------------------------------- + +-- draft +INSERT INTO `t_order` ( + `RequestId`, `ServiceNo`, `ClientNo`, `ProductCode`, `ChannelCode`, + `ConsigneeCountry`, `ConsigneeCountryName`, `CargoAttribute`, + `OrderStatus`, `HandleStatus`, `WarehouseId`, `CustomerId`, + `OrderSource`, `PickupTime`, `CustomerSenderAddressId`, + `DeclaredCurrency`, `DeclaredSum`, `Quantity`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT + 'SEED9-PK-REQ-001', 'SEED9-PK-001', 'PK-CLIENT-001', @productCode, @productCode, + 'CN', '中国', 'general', + 'draft', 'pending', @warehouseId, @customerId, + 'pickup', DATE_ADD(NOW(), INTERVAL 1 DAY), @senderAddressId, + 'CNY', 1.0000, 1, + 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 2 DAY) +WHERE @warehouseId IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `t_order` WHERE `ServiceNo` = 'SEED9-PK-001'); + +-- pendingPayment +INSERT INTO `t_order` ( + `RequestId`, `ServiceNo`, `ClientNo`, `ProductCode`, `ChannelCode`, + `ConsigneeCountry`, `ConsigneeCountryName`, `CargoAttribute`, + `OrderStatus`, `HandleStatus`, `WarehouseId`, `CustomerId`, + `OrderSource`, `PickupTime`, `CustomerSenderAddressId`, + `DeclaredCurrency`, `DeclaredSum`, `Quantity`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT + 'SEED9-PK-REQ-002', 'SEED9-PK-002', 'PK-CLIENT-002', @productCode, @productCode, + 'CN', '中国', 'general', + 'pendingPayment', 'pending', @warehouseId, @customerId, + 'pickup', DATE_ADD(NOW(), INTERVAL 2 DAY), @senderAddressId, + 'CNY', 1.0000, 1, + 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 1 DAY) +WHERE @warehouseId IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `t_order` WHERE `ServiceNo` = 'SEED9-PK-002'); + +-- pendingShip +INSERT INTO `t_order` ( + `RequestId`, `ServiceNo`, `ClientNo`, `ProductCode`, `ChannelCode`, + `ConsigneeCountry`, `ConsigneeCountryName`, `CargoAttribute`, + `OrderStatus`, `HandleStatus`, `WarehouseId`, `CustomerId`, + `OrderSource`, `PickupTime`, `CustomerSenderAddressId`, + `DeclaredCurrency`, `DeclaredSum`, `Quantity`, + `IsPaid`, `PayTime`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT + 'SEED9-PK-REQ-003', 'SEED9-PK-003', 'PK-CLIENT-003', @productCode, @productCode, + 'CN', '中国', 'electronics', + 'pendingShip', 'pending', @warehouseId, @customerId, + 'pickup', DATE_ADD(NOW(), INTERVAL 3 DAY), @senderAddressId, + 'CNY', 1.0000, 1, + 1, DATE_SUB(NOW(), INTERVAL 6 HOUR), + 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 12 HOUR) +WHERE @warehouseId IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `t_order` WHERE `ServiceNo` = 'SEED9-PK-003'); + +-- received +INSERT INTO `t_order` ( + `RequestId`, `ServiceNo`, `ClientNo`, `ProductCode`, `ChannelCode`, + `ConsigneeCountry`, `ConsigneeCountryName`, `CargoAttribute`, + `OrderStatus`, `HandleStatus`, `WarehouseId`, `CustomerId`, + `OrderSource`, `PickupTime`, `CustomerSenderAddressId`, + `DeclaredCurrency`, `DeclaredSum`, `Quantity`, + `ReceiveTime`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT + 'SEED9-PK-REQ-004', 'SEED9-PK-004', 'PK-CLIENT-004', @productCode, @productCode, + 'CN', '中国', 'general', + 'received', 'processed', @warehouseId, @customerId, + 'pickup', DATE_SUB(NOW(), INTERVAL 1 DAY), @senderAddressId, + 'CNY', 1.0000, 1, + DATE_SUB(NOW(), INTERVAL 8 HOUR), + 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 3 DAY) +WHERE @warehouseId IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `t_order` WHERE `ServiceNo` = 'SEED9-PK-004'); + +-- pendingOnline +INSERT INTO `t_order` ( + `RequestId`, `ServiceNo`, `ClientNo`, `ProductCode`, `ChannelCode`, + `ConsigneeCountry`, `ConsigneeCountryName`, `CargoAttribute`, + `OrderStatus`, `HandleStatus`, `WarehouseId`, `CustomerId`, + `OrderSource`, `PickupTime`, `CustomerSenderAddressId`, + `DeclaredCurrency`, `DeclaredSum`, `Quantity`, + `IsPaid`, `PayTime`, `ShipTime`, + `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT + 'SEED9-PK-REQ-005', 'SEED9-PK-005', 'PK-CLIENT-005', @productCode, @productCode, + 'CN', '中国', 'general', + 'pendingOnline', 'processed', @warehouseId, @customerId, + 'pickup', DATE_SUB(NOW(), INTERVAL 2 DAY), @senderAddressId, + 'CNY', 1.0000, 1, + 1, DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), + 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 4 DAY) +WHERE @warehouseId IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `t_order` WHERE `ServiceNo` = 'SEED9-PK-005'); + +-- --------------------------------------------------------------------------- +-- 3. 子表:发件人 / 收件人 / 申报 / 件重尺 +-- --------------------------------------------------------------------------- + +-- 发件人 +INSERT INTO `t_order_sender` ( + `OrderId`, `SenderName`, `Company`, `Province`, `City`, `ZipCode`, + `StreetAddress1`, `Phone`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, '张三', '种子发件公司', '广东省', '深圳市', '518000', + '南山区科技园种子路 1 号', '13800000009', 0, 0, NOW() +FROM `t_order` o +WHERE o.`ServiceNo` IN ('SEED9-PK-001','SEED9-PK-002','SEED9-PK-003','SEED9-PK-004','SEED9-PK-005') + AND o.`CustomerId` = @customerId + AND o.`OrderSource` = 'pickup' + AND NOT EXISTS (SELECT 1 FROM `t_order_sender` s WHERE s.`OrderId` = o.`Id` AND s.`IsDeleted` = 0); + +-- 收件人(仓库) +INSERT INTO `t_order_recipient` ( + `OrderId`, `RecipientsName`, `Phone`, `ZipCode`, `StreetAddress1`, + `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, + IFNULL(NULLIF(TRIM(w.`Recipient`), ''), IFNULL(w.`WarehouseName`, '种子仓')), + w.`ContactPhone`, + '000000', + IFNULL(NULLIF(TRIM(w.`ShippingAddress`), ''), IFNULL(w.`WarehouseName`, '种子仓地址')), + 0, 0, NOW() +FROM `t_order` o +LEFT JOIN `t_warehouse_address` w ON w.`Id` = o.`WarehouseId` +WHERE o.`ServiceNo` IN ('SEED9-PK-001','SEED9-PK-002','SEED9-PK-003','SEED9-PK-004','SEED9-PK-005') + AND o.`CustomerId` = @customerId + AND o.`OrderSource` = 'pickup' + AND NOT EXISTS (SELECT 1 FROM `t_order_recipient` r WHERE r.`OrderId` = o.`Id` AND r.`IsDeleted` = 0); + +-- 申报行(货物名称) +INSERT INTO `t_order_declaration` ( + `OrderId`, `CnName`, `EnName`, `UnitPrice`, `UnitWeight`, `Quantity`, `Amount`, `Weight`, + `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN '日用百货样品' + WHEN 'SEED9-PK-002' THEN '服装配件' + WHEN 'SEED9-PK-003' THEN '蓝牙耳机' + WHEN 'SEED9-PK-004' THEN '家居收纳盒' + ELSE '办公文具套装' + END, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN 'Daily Sample Goods' + WHEN 'SEED9-PK-002' THEN 'Apparel Accessories' + WHEN 'SEED9-PK-003' THEN 'Bluetooth Earbuds' + WHEN 'SEED9-PK-004' THEN 'Storage Box' + ELSE 'Stationery Set' + END, + 1.00, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN 0.500 + WHEN 'SEED9-PK-002' THEN 1.200 + WHEN 'SEED9-PK-003' THEN 0.350 + WHEN 'SEED9-PK-004' THEN 2.000 + ELSE 0.800 + END, + 1, + 1.00, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN 0.500 + WHEN 'SEED9-PK-002' THEN 1.200 + WHEN 'SEED9-PK-003' THEN 0.350 + WHEN 'SEED9-PK-004' THEN 2.000 + ELSE 0.800 + END, + 0, 0, NOW() +FROM `t_order` o +WHERE o.`ServiceNo` IN ('SEED9-PK-001','SEED9-PK-002','SEED9-PK-003','SEED9-PK-004','SEED9-PK-005') + AND o.`CustomerId` = @customerId + AND o.`OrderSource` = 'pickup' + AND NOT EXISTS (SELECT 1 FROM `t_order_declaration` d WHERE d.`OrderId` = o.`Id` AND d.`IsDeleted` = 0); + +-- 预报件重尺 +INSERT INTO `t_order_weight` ( + `OrderId`, `Type`, `Length`, `Width`, `Height`, `ActualWeight`, `VolumetricWeight`, `ChargeableWeight`, + `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'forecast', 1.00, 1.00, 1.00, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN 0.500 + WHEN 'SEED9-PK-002' THEN 1.200 + WHEN 'SEED9-PK-003' THEN 0.350 + WHEN 'SEED9-PK-004' THEN 2.000 + ELSE 0.800 + END, + 0.001, + CASE o.`ServiceNo` + WHEN 'SEED9-PK-001' THEN 0.500 + WHEN 'SEED9-PK-002' THEN 1.200 + WHEN 'SEED9-PK-003' THEN 0.350 + WHEN 'SEED9-PK-004' THEN 2.000 + ELSE 0.800 + END, + 0, 0, NOW() +FROM `t_order` o +WHERE o.`ServiceNo` IN ('SEED9-PK-001','SEED9-PK-002','SEED9-PK-003','SEED9-PK-004','SEED9-PK-005') + AND o.`CustomerId` = @customerId + AND o.`OrderSource` = 'pickup' + AND NOT EXISTS ( + SELECT 1 FROM `t_order_weight` w + WHERE w.`OrderId` = o.`Id` AND w.`Type` = 'forecast' AND w.`IsDeleted` = 0 + ); + +-- --------------------------------------------------------------------------- +-- 4. 摘要 +-- --------------------------------------------------------------------------- +SELECT 'pickup_orders' AS tbl, COUNT(*) AS cnt +FROM `t_order` +WHERE `CustomerId` = @customerId AND `OrderSource` = 'pickup' AND `IsDeleted` = 0 +UNION ALL +SELECT 'sender_address', COUNT(*) +FROM `t_customer_sender_address` +WHERE `CustomerId` = @customerId AND `AddressType` = 'sender' AND `IsDeleted` = 0 +UNION ALL +SELECT 'warehouse_used', IF(@warehouseId IS NULL, 0, 1) +UNION ALL +SELECT 'product_code', IF(@productCode IS NULL OR @productCode = '', 0, 1); diff --git a/sql/seed_sales_store_customer9.sql b/sql/seed_sales_store_customer9.sql new file mode 100644 index 0000000..58b9257 --- /dev/null +++ b/sql/seed_sales_store_customer9.sql @@ -0,0 +1,510 @@ +-- 店铺授权 + 店铺订单种子(CustomerId = 9) +-- 可重复执行:已存在则跳过 +-- 用法: +-- 1) mysql -u root -p oms < sql/migrate_t_sales_store_order_recipient.sql +-- 2) mysql -u root -p oms < sql/seed_sales_store_customer9.sql +-- +-- 注意:ConcurrencyStamp 必须是合法 Guid 字符串;空串会导致 +-- 「ConcurrencyStamp绑定到SalesStoreOrder失败」 + +SET NAMES utf8mb4; + +-- 先修好已有脏数据(空串 / 非 36 位) +UPDATE `t_sales_store` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL OR TRIM(`ConcurrencyStamp`) = '' OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; +UPDATE `t_sales_store_order` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL OR TRIM(`ConcurrencyStamp`) = '' OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; +UPDATE `t_sales_store_order_line` +SET `ConcurrencyStamp` = UUID() +WHERE `ConcurrencyStamp` IS NULL OR TRIM(`ConcurrencyStamp`) = '' OR LENGTH(TRIM(`ConcurrencyStamp`)) <> 36; + +-- --------------------------------------------------------------------------- +-- 1. 店铺授权 t_sales_store(4 个平台,覆盖 active/expired) +-- --------------------------------------------------------------------------- +INSERT INTO `t_sales_store` ( + `CustomerId`, `PlatformCode`, `StoreNickname`, `PlatformStoreId`, `PlatformAccount`, + `SiteCountry`, `SellerId`, `AuthStatus`, `AuthTime`, `TokenExpireTime`, `RefreshExpireTime`, + `AccessToken`, `RefreshToken`, `IsAutoFulfill`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT 9, 'shopee', '种子-Shopee旗舰店', 'SEED9-SHOPEE-001', 'seed9.shopee@demo.com', + 'SG', 'SELLER-SHOPEE-9001', 'active', + DATE_SUB(NOW(), INTERVAL 30 DAY), DATE_ADD(NOW(), INTERVAL 60 DAY), DATE_ADD(NOW(), INTERVAL 180 DAY), + 'seed_access_token_shopee_9', 'seed_refresh_token_shopee_9', 1, 'CustomerId=9 种子店铺', 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_sales_store` + WHERE `CustomerId` = 9 AND `PlatformStoreId` = 'SEED9-SHOPEE-001' AND `IsDeleted` = 0 +); + +INSERT INTO `t_sales_store` ( + `CustomerId`, `PlatformCode`, `StoreNickname`, `PlatformStoreId`, `PlatformAccount`, + `SiteCountry`, `SellerId`, `AuthStatus`, `AuthTime`, `TokenExpireTime`, `RefreshExpireTime`, + `AccessToken`, `RefreshToken`, `IsAutoFulfill`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT 9, 'lazada', '种子-Lazada马来店', 'SEED9-LAZADA-001', 'seed9.lazada@demo.com', + 'MY', 'SELLER-LAZADA-9002', 'active', + DATE_SUB(NOW(), INTERVAL 14 DAY), DATE_ADD(NOW(), INTERVAL 45 DAY), DATE_ADD(NOW(), INTERVAL 120 DAY), + 'seed_access_token_lazada_9', 'seed_refresh_token_lazada_9', 0, 'CustomerId=9 种子店铺', 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_sales_store` + WHERE `CustomerId` = 9 AND `PlatformStoreId` = 'SEED9-LAZADA-001' AND `IsDeleted` = 0 +); + +INSERT INTO `t_sales_store` ( + `CustomerId`, `PlatformCode`, `StoreNickname`, `PlatformStoreId`, `PlatformAccount`, + `SiteCountry`, `SellerId`, `AuthStatus`, `AuthTime`, `TokenExpireTime`, `RefreshExpireTime`, + `AccessToken`, `RefreshToken`, `IsAutoFulfill`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT 9, 'tiktok', '种子-TikTok跨境店', 'SEED9-TIKTOK-001', 'seed9.tiktok@demo.com', + 'US', 'SELLER-TIKTOK-9003', 'active', + DATE_SUB(NOW(), INTERVAL 7 DAY), DATE_ADD(NOW(), INTERVAL 30 DAY), DATE_ADD(NOW(), INTERVAL 90 DAY), + 'seed_access_token_tiktok_9', 'seed_refresh_token_tiktok_9', 1, 'CustomerId=9 种子店铺', 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_sales_store` + WHERE `CustomerId` = 9 AND `PlatformStoreId` = 'SEED9-TIKTOK-001' AND `IsDeleted` = 0 +); + +INSERT INTO `t_sales_store` ( + `CustomerId`, `PlatformCode`, `StoreNickname`, `PlatformStoreId`, `PlatformAccount`, + `SiteCountry`, `SellerId`, `AuthStatus`, `AuthTime`, `TokenExpireTime`, `RefreshExpireTime`, + `AccessToken`, `RefreshToken`, `IsAutoFulfill`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime` +) +SELECT 9, 'temu', '种子-Temu美区店(已过期)', 'SEED9-TEMU-001', 'seed9.temu@demo.com', + 'US', 'SELLER-TEMU-9004', 'expired', + DATE_SUB(NOW(), INTERVAL 90 DAY), DATE_SUB(NOW(), INTERVAL 5 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), + 'seed_access_token_temu_9_expired', 'seed_refresh_token_temu_9_expired', 0, 'CustomerId=9 种子店铺-授权过期', 0, UUID(), 0, NOW() +WHERE NOT EXISTS ( + SELECT 1 FROM `t_sales_store` + WHERE `CustomerId` = 9 AND `PlatformStoreId` = 'SEED9-TEMU-001' AND `IsDeleted` = 0 +); + +-- --------------------------------------------------------------------------- +-- 2. 店铺订单 t_sales_store_order(覆盖各履约状态) +-- --------------------------------------------------------------------------- + +-- pending +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `IsMultiLabel`, `IsSuspended`, + `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'shopee', 'SEED9-ORD-SHOPEE-P001', 'SG', + 2, 45.8000, 'SGD', DATE_SUB(NOW(), INTERVAL 2 HOUR), 'pending', + 'UNPAID', '待付款', 0, 0, + NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-SHOPEE-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-P001' AND o.`IsDeleted` = 0 + ); + +-- toShip +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `IsMultiLabel`, `CustomTags`, `IsSuspended`, + `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'shopee', 'SEED9-ORD-SHOPEE-T001', 'SG', + 1, 28.5000, 'SGD', DATE_SUB(NOW(), INTERVAL 1 DAY), 'toShip', + 'READY_TO_SHIP', '待发货', 0, '急单', 0, + NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-SHOPEE-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T001' AND o.`IsDeleted` = 0 + ); + +-- shipped +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `ShippingLabelNo`, `ShippingLabelUrl`, + `IsMultiLabel`, `IsSuspended`, `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'lazada', 'SEED9-ORD-LAZADA-S001', 'MY', + 3, 129.9000, 'MYR', DATE_SUB(NOW(), INTERVAL 3 DAY), 'shipped', + 'SHIPPED', '已发货', 'LZD-LABEL-90001', 'https://example.com/labels/lzd-90001.pdf', + 0, 0, NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-LAZADA-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-S001' AND o.`IsDeleted` = 0 + ); + +-- completed +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `ShippingLabelNo`, + `IsMultiLabel`, `IsSuspended`, `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'tiktok', 'SEED9-ORD-TIKTOK-C001', 'US', + 1, 19.9900, 'USD', DATE_SUB(NOW(), INTERVAL 10 DAY), 'completed', + 'COMPLETED', '已完成', 'TT-LABEL-90002', + 0, 0, NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-TIKTOK-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-TIKTOK-C001' AND o.`IsDeleted` = 0 + ); + +-- canceled +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `IsMultiLabel`, `IsSuspended`, + `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'tiktok', 'SEED9-ORD-TIKTOK-X001', 'US', + 2, 35.5000, 'USD', DATE_SUB(NOW(), INTERVAL 5 DAY), 'canceled', + 'CANCELLED', '已取消', 0, 0, + NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-TIKTOK-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-TIKTOK-X001' AND o.`IsDeleted` = 0 + ); + +-- suspended +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `IsMultiLabel`, `CustomTags`, `IsSuspended`, + `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'lazada', 'SEED9-ORD-LAZADA-H001', 'MY', + 1, 59.0000, 'MYR', DATE_SUB(NOW(), INTERVAL 4 DAY), 'suspended', + 'PENDING', '待处理', 0, '缺货搁置', 1, + NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-LAZADA-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-H001' AND o.`IsDeleted` = 0 + ); + +-- toShip(多件) +INSERT INTO `t_sales_store_order` ( + `CustomerId`, `StoreId`, `PlatformCode`, `PlatformOrderNo`, `OrderCountry`, + `ItemCount`, `TotalAmount`, `Currency`, `PlatformOrderTime`, `FulfillStatus`, + `PlatformStatus`, `PlatformStatusLabel`, `IsMultiLabel`, `IsSuspended`, + `LastSyncTime`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT 9, s.`Id`, 'shopee', 'SEED9-ORD-SHOPEE-T002', 'PH', + 2, 88.0000, 'PHP', DATE_SUB(NOW(), INTERVAL 6 HOUR), 'toShip', + 'READY_TO_SHIP', '待发货', 1, 0, + NOW(), 0, 0, NOW() +FROM `t_sales_store` s +WHERE s.`CustomerId` = 9 AND s.`PlatformStoreId` = 'SEED9-SHOPEE-001' AND s.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order` o + WHERE o.`StoreId` = s.`Id` AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T002' AND o.`IsDeleted` = 0 + ); + +-- --------------------------------------------------------------------------- +-- 3. 订单明细 t_sales_store_order_line +-- --------------------------------------------------------------------------- + +-- SEED9-ORD-SHOPEE-P001 两行 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-P001-1', 'SKU-PHONE-CASE-01', '硅胶手机壳', 'https://example.com/img/phone-case.jpg', + '颜色:黑色;型号:iPhone15', 18.9000, 1, 'SGD', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-P001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-P001-1' AND l.`IsDeleted` = 0 + ); + +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-P001-2', 'SKU-CABLE-USB-C', 'USB-C 数据线 1m', 'https://example.com/img/usb-c.jpg', + '长度:1m', 26.9000, 1, 'SGD', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-P001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-P001-2' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-SHOPEE-T001 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-T001-1', 'SKU-EARBUD-PRO', '无线蓝牙耳机 Pro', 'https://example.com/img/earbud.jpg', + '颜色:白色', 28.5000, 1, 'SGD', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-T001-1' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-LAZADA-S001 三行 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-S001-1', 'SKU-TSHIRT-M', '纯棉短袖 T 恤', 'https://example.com/img/tshirt.jpg', + '尺码:M;颜色:藏青', 29.9000, 2, 'MYR', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-S001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-S001-1' AND l.`IsDeleted` = 0 + ); + +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-S001-2', 'SKU-SOCK-PACK', '运动短袜 3 双装', 'https://example.com/img/socks.jpg', + '颜色:混色', 19.9000, 1, 'MYR', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-S001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-S001-2' AND l.`IsDeleted` = 0 + ); + +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-S001-3', 'SKU-CAP-BLK', '棒球帽', 'https://example.com/img/cap.jpg', + '颜色:黑色', 50.2000, 1, 'MYR', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-S001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-S001-3' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-TIKTOK-C001 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-C001-1', 'SKU-LED-STRIP', 'LED 灯带 5m', 'https://example.com/img/led.jpg', + '色温:暖白', 19.9900, 1, 'USD', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-TIKTOK-C001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-C001-1' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-TIKTOK-X001 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-X001-1', 'SKU-MOUSE-PAD', '加大鼠标垫', 'https://example.com/img/mousepad.jpg', + '尺寸:900x400', 15.5000, 1, 'USD', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-TIKTOK-X001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-X001-1' AND l.`IsDeleted` = 0 + ); + +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-X001-2', 'SKU-KEYCAP-SET', '键帽套装 104 键', 'https://example.com/img/keycap.jpg', + '风格:布丁', 20.0000, 1, 'USD', 1, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-TIKTOK-X001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-X001-2' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-LAZADA-H001 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-H001-1', 'SKU-POWERBANK-20K', '移动电源 20000mAh', 'https://example.com/img/powerbank.jpg', + '颜色:灰色', 59.0000, 1, 'MYR', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-LAZADA-H001' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-H001-1' AND l.`IsDeleted` = 0 + ); + +-- SEED9-ORD-SHOPEE-T002 两行 +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-T002-1', 'SKU-BAG-TOTE', '帆布托特包', 'https://example.com/img/tote.jpg', + '颜色:米白', 48.0000, 1, 'PHP', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T002' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-T002-1' AND l.`IsDeleted` = 0 + ); + +INSERT INTO `t_sales_store_order_line` ( + `StoreOrderId`, `PlatformLineId`, `PlatformSku`, `ProductName`, `ProductImageUrl`, + `VariantText`, `UnitPrice`, `Quantity`, `Currency`, `IsShipExcluded`, `IsDeleted`, `CreateUserId`, `CreateTime` +) +SELECT o.`Id`, 'LINE-T002-2', 'SKU-WALLET-MINI', '迷你卡包', 'https://example.com/img/wallet.jpg', + '颜色:棕色', 40.0000, 1, 'PHP', 0, 0, 0, NOW() +FROM `t_sales_store_order` o +WHERE o.`CustomerId` = 9 AND o.`PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T002' AND o.`IsDeleted` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sales_store_order_line` l + WHERE l.`StoreOrderId` = o.`Id` AND l.`PlatformLineId` = 'LINE-T002-2' AND l.`IsDeleted` = 0 + ); + +-- --------------------------------------------------------------------------- +-- 4. 补齐收件人(需先执行 migrate_t_sales_store_order_recipient.sql) +-- --------------------------------------------------------------------------- +UPDATE `t_sales_store_order` SET + `RecipientName` = 'Tan Wei Ming', + `RecipientCompany` = NULL, + `RecipientPhone` = '+65-91234567', + `RecipientEmail` = 'tan.wm@example.com', + `RecipientCountry` = 'SG', + `RecipientProvince` = 'Singapore', + `RecipientCity` = 'Singapore', + `RecipientZipCode` = '018956', + `RecipientAddress1` = '10 Bayfront Avenue, Marina Bay Sands', + `RecipientAddress2` = 'Tower 1 #12-01', + `PlatformExtraJson` = JSON_OBJECT( + 'recipient', JSON_OBJECT( + 'recipientsName', 'Tan Wei Ming', + 'phone', '+65-91234567', + 'email', 'tan.wm@example.com', + 'countryCode', 'SG', + 'province', 'Singapore', + 'city', 'Singapore', + 'zipCode', '018956', + 'streetAddress1', '10 Bayfront Avenue, Marina Bay Sands', + 'streetAddress2', 'Tower 1 #12-01' + ) + ) +WHERE `CustomerId` = 9 AND `PlatformOrderNo` = 'SEED9-ORD-SHOPEE-P001' AND `IsDeleted` = 0 + AND (`RecipientName` IS NULL OR `RecipientName` = ''); + +UPDATE `t_sales_store_order` SET + `RecipientName` = 'Lim Jia Hui', + `RecipientPhone` = '+65-98887766', + `RecipientCountry` = 'SG', + `RecipientProvince` = 'Singapore', + `RecipientCity` = 'Singapore', + `RecipientZipCode` = '238801', + `RecipientAddress1` = '313 Orchard Road', + `PlatformExtraJson` = JSON_OBJECT( + 'recipient', JSON_OBJECT( + 'recipientsName', 'Lim Jia Hui', + 'phone', '+65-98887766', + 'countryCode', 'SG', + 'city', 'Singapore', + 'zipCode', '238801', + 'streetAddress1', '313 Orchard Road' + ) + ) +WHERE `CustomerId` = 9 AND `PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T001' AND `IsDeleted` = 0 + AND (`RecipientName` IS NULL OR `RecipientName` = ''); + +UPDATE `t_sales_store_order` SET + `RecipientName` = 'Ahmad Faizal', + `RecipientPhone` = '+60-12-3456789', + `RecipientCountry` = 'MY', + `RecipientProvince` = 'Selangor', + `RecipientCity` = 'Petaling Jaya', + `RecipientZipCode` = '47800', + `RecipientAddress1` = 'No. 18, Jalan SS2/24', + `PlatformExtraJson` = JSON_OBJECT( + 'recipient', JSON_OBJECT( + 'recipientsName', 'Ahmad Faizal', + 'phone', '+60-12-3456789', + 'countryCode', 'MY', + 'province', 'Selangor', + 'city', 'Petaling Jaya', + 'zipCode', '47800', + 'streetAddress1', 'No. 18, Jalan SS2/24' + ) + ) +WHERE `CustomerId` = 9 AND `PlatformOrderNo` IN ('SEED9-ORD-LAZADA-S001', 'SEED9-ORD-LAZADA-H001') AND `IsDeleted` = 0 + AND (`RecipientName` IS NULL OR `RecipientName` = ''); + +UPDATE `t_sales_store_order` SET + `RecipientName` = 'John Smith', + `RecipientPhone` = '+1-415-555-0198', + `RecipientEmail` = 'john.smith@example.com', + `RecipientCountry` = 'US', + `RecipientProvince` = 'CA', + `RecipientCity` = 'San Francisco', + `RecipientZipCode` = '94105', + `RecipientAddress1` = '1 Market St, Suite 300', + `PlatformExtraJson` = JSON_OBJECT( + 'recipient', JSON_OBJECT( + 'recipientsName', 'John Smith', + 'phone', '+1-415-555-0198', + 'email', 'john.smith@example.com', + 'countryCode', 'US', + 'province', 'CA', + 'city', 'San Francisco', + 'zipCode', '94105', + 'streetAddress1', '1 Market St, Suite 300' + ) + ) +WHERE `CustomerId` = 9 AND `PlatformOrderNo` IN ('SEED9-ORD-TIKTOK-C001', 'SEED9-ORD-TIKTOK-X001') AND `IsDeleted` = 0 + AND (`RecipientName` IS NULL OR `RecipientName` = ''); + +UPDATE `t_sales_store_order` SET + `RecipientName` = 'Maria Santos', + `RecipientPhone` = '+63-917-123-4567', + `RecipientCountry` = 'PH', + `RecipientProvince` = 'Metro Manila', + `RecipientCity` = 'Makati', + `RecipientZipCode` = '1226', + `RecipientAddress1` = 'Ayala Avenue, Glorietta 4', + `PlatformExtraJson` = JSON_OBJECT( + 'recipient', JSON_OBJECT( + 'recipientsName', 'Maria Santos', + 'phone', '+63-917-123-4567', + 'countryCode', 'PH', + 'province', 'Metro Manila', + 'city', 'Makati', + 'zipCode', '1226', + 'streetAddress1', 'Ayala Avenue, Glorietta 4' + ) + ) +WHERE `CustomerId` = 9 AND `PlatformOrderNo` = 'SEED9-ORD-SHOPEE-T002' AND `IsDeleted` = 0 + AND (`RecipientName` IS NULL OR `RecipientName` = ''); + +-- 校验摘要(执行后可看) +SELECT 't_sales_store' AS tbl, COUNT(*) AS cnt FROM `t_sales_store` WHERE `CustomerId` = 9 AND `IsDeleted` = 0 +UNION ALL +SELECT 't_sales_store_order', COUNT(*) FROM `t_sales_store_order` WHERE `CustomerId` = 9 AND `IsDeleted` = 0 +UNION ALL +SELECT 't_sales_store_order_with_recipient', COUNT(*) +FROM `t_sales_store_order` +WHERE `CustomerId` = 9 AND `IsDeleted` = 0 AND `RecipientName` IS NOT NULL AND `RecipientName` <> '' +UNION ALL +SELECT 't_sales_store_order_line', COUNT(*) +FROM `t_sales_store_order_line` l +INNER JOIN `t_sales_store_order` o ON o.`Id` = l.`StoreOrderId` +WHERE o.`CustomerId` = 9 AND o.`IsDeleted` = 0 AND l.`IsDeleted` = 0; diff --git a/src/Oms.Application/Dtos/Orders/OrderStockBindDtos.cs b/src/Oms.Application/Dtos/Orders/OrderStockBindDtos.cs index 0d02cde..6c7d390 100644 --- a/src/Oms.Application/Dtos/Orders/OrderStockBindDtos.cs +++ b/src/Oms.Application/Dtos/Orders/OrderStockBindDtos.cs @@ -55,11 +55,16 @@ public class OrderStockBindItemOutput public class OrderStockBindCandidatesInput { - [Required] + /// + /// 物流单 Id。创建前草稿选仓时可传 0,此时须传 ,客户取会话。 + /// public int OrderId { get; set; } public int? WarehouseId { get; set; } + /// 草稿模式(无物流单)时可选,管理端指定客户。 + public int? CustomerId { get; set; } + public string? Keyword { get; set; } /// 包裹候选:空/all=全部生命周期。 diff --git a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs index 2cc2f38..36baf5b 100644 --- a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs +++ b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs @@ -204,6 +204,28 @@ public class PlatformStoreOrderOutput public string? OrderCountry { get; set; } + public string? RecipientName { get; set; } + + public string? RecipientCompany { get; set; } + + public string? RecipientPhone { get; set; } + + public string? RecipientTelePhone { get; set; } + + public string? RecipientEmail { get; set; } + + public string? RecipientCountry { get; set; } + + public string? RecipientProvince { get; set; } + + public string? RecipientCity { get; set; } + + public string? RecipientZipCode { get; set; } + + public string? RecipientAddress1 { get; set; } + + public string? RecipientAddress2 { get; set; } + public int? ItemCount { get; set; } public decimal? TotalAmount { get; set; } @@ -333,6 +355,54 @@ public class PlatformStoreOrderMatchInventoryOutput public string? ClientNo { get; set; } } +public class PlatformStoreOrderBatchCreateLogisticsInput +{ + [Required] + public string ProductCode { get; set; } = string.Empty; + + public int? WarehouseId { get; set; } + + public int? CustomerId { get; set; } + + [Required] + public List Items { get; set; } = []; +} + +public class PlatformStoreOrderBatchCreateFulfillInput +{ + [Required] + public string ProductCode { get; set; } = string.Empty; + + [Required] + public int WarehouseId { get; set; } + + public int? CustomerId { get; set; } + + [Required] + public List Items { get; set; } = []; +} + +public class PlatformStoreOrderBatchCreateItemInput +{ + [Required] + public int StoreOrderId { get; set; } + + public OrderParcelReceiverInput? Receiver { get; set; } + + public List BindItems { get; set; } = []; +} + +public class PlatformStoreOrderBatchCreateLogisticsOutput +{ + public string BatchNo { get; set; } = string.Empty; + + public int SuccessCount { get; set; } + + public int FailCount { get; set; } + + public List Items { get; set; } = []; +} + public class PlatformStoreOrderToggleSuspendInput { [Required] diff --git a/src/Oms.Application/Services/Orders/OrderService.BatchCreate.cs b/src/Oms.Application/Services/Orders/OrderService.BatchCreate.cs index 1059450..510e863 100644 --- a/src/Oms.Application/Services/Orders/OrderService.BatchCreate.cs +++ b/src/Oms.Application/Services/Orders/OrderService.BatchCreate.cs @@ -78,6 +78,24 @@ public partial class OrderService int operatorUserId, CancellationToken cancellationToken = default) { - return await CreateParcelCoreAsync(input, operatorUserId); + return await CreateParcelForBatchAsync(input, operatorUserId, orderSource: null, cancellationToken); + } + + /// + /// 批量 Worker 创建小包订单(可指定 )。 + /// + public async Task CreateParcelForBatchAsync( + OrderParcelCreateInput input, + int operatorUserId, + string? orderSource, + CancellationToken cancellationToken = default) + { + ParcelCreateContext? createContext = null; + if (!string.IsNullOrWhiteSpace(orderSource)) + { + createContext = new ParcelCreateContext { OrderSource = orderSource.Trim() }; + } + + return await CreateParcelCoreAsync(input, operatorUserId, createContext); } } diff --git a/src/Oms.Application/Services/Orders/OrderService.StockBind.cs b/src/Oms.Application/Services/Orders/OrderService.StockBind.cs index f67978f..50e475c 100644 --- a/src/Oms.Application/Services/Orders/OrderService.StockBind.cs +++ b/src/Oms.Application/Services/Orders/OrderService.StockBind.cs @@ -49,10 +49,7 @@ public partial class OrderService public async Task> GetStockBindInventoryCandidatesAsync( [FromBody] OrderStockBindCandidatesInput input) { - var order = await GetOrderOrThrowAsync(input.OrderId); - EnsureOrderCanBindStock(order); - var warehouseId = ResolveBindWarehouseId(order, input.WarehouseId); - var customerId = await ResolveStockBindCustomerIdAsync(order); + var (warehouseId, customerId) = await ResolveCandidatesContextAsync(input); var inventories = await StockBindInventoryRepository.GetListAsync(x => x.CustomerId == customerId @@ -108,10 +105,7 @@ public partial class OrderService public async Task> GetStockBindParcelCandidatesAsync( [FromBody] OrderStockBindCandidatesInput input) { - var order = await GetOrderOrThrowAsync(input.OrderId); - EnsureOrderCanBindStock(order); - var warehouseId = ResolveBindWarehouseId(order, input.WarehouseId); - var customerId = await ResolveStockBindCustomerIdAsync(order); + var (warehouseId, customerId) = await ResolveCandidatesContextAsync(input); var lifecycle = input.LifecycleStatus?.Trim(); var filterLifecycle = !string.IsNullOrWhiteSpace(lifecycle) @@ -631,6 +625,34 @@ public partial class OrderService } } + private async Task<(int WarehouseId, int CustomerId)> ResolveCandidatesContextAsync(OrderStockBindCandidatesInput input) + { + if (input.OrderId > 0) + { + var order = await GetOrderOrThrowAsync(input.OrderId); + EnsureOrderCanBindStock(order); + var warehouseId = ResolveBindWarehouseId(order, input.WarehouseId); + var customerId = await ResolveStockBindCustomerIdAsync(order); + return (warehouseId, customerId); + } + + if (input.WarehouseId is not > 0) + { + throw new BusinessException("请指定发货仓库"); + } + + var sessionCustomerId = await OmsSessionHelper.ResolveListCustomerIdAsync( + CustomerRepository, + CurrentUser, + input.CustomerId); + if (sessionCustomerId is not > 0) + { + throw new BusinessException("请指定客户"); + } + + return (input.WarehouseId.Value, sessionCustomerId.Value); + } + private static int RequireOrderCustomerId(Order order) { if (!order.CustomerId.HasValue || order.CustomerId.Value <= 0) diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs index 4d9931f..e8e06de 100644 --- a/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs +++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs @@ -318,6 +318,7 @@ internal sealed class PlatformStoreOrderFulfillmentService( entity.ShippingLabelUrl = pulled.ShippingLabelUrl ?? entity.ShippingLabelUrl; entity.PlatformExtraJson = pulled.ExtraJson ?? entity.PlatformExtraJson; entity.LastSyncTime = now; + PlatformStoreOrderParcelBuilder.TryFillRecipientFields(entity, pulled.ExtraJson); if (entity.Id > 0) { diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs index e912d37..c4828d8 100644 --- a/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs +++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs @@ -59,6 +59,99 @@ public static class PlatformStoreOrderParcelBuilder }; } + /// + /// 按平台商品行组装小包入参(不要求库内 SKU 映射;用于创建物流 / 物流&代发)。 + /// + public static OrderParcelCreateInput BuildFromPlatformLines( + SalesStoreOrder storeOrder, + IReadOnlyCollection lines, + WarehouseAddress? warehouse, + string productCode, + int? warehouseId, + int customerId, + OrderParcelReceiverInput? receiverOverride = null, + string? trackNoOverride = null) + { + var receiver = receiverOverride ?? ParseReceiver(storeOrder); + ValidateReceiver(receiver); + + var declarationLines = BuildDeclarationLinesFromPlatform(lines); + var totalWeightKg = Math.Max( + declarationLines.Sum(x => x.Weight ?? 0m), + MinActualWeightKg); + + return new OrderParcelCreateInput + { + RequestId = $"platform-store-order:{storeOrder.Id}", + ClientNo = storeOrder.PlatformOrderNo, + TrackNo = string.IsNullOrWhiteSpace(trackNoOverride) ? storeOrder.ShippingLabelNo : trackNoOverride?.Trim(), + ProductCode = productCode, + WarehouseId = warehouseId, + CustomerId = customerId, + Length = DefaultLengthCm, + Width = DefaultWidthCm, + Height = DefaultHeightCm, + ActualWeight = totalWeightKg, + ChargeableWeight = totalWeightKg, + CargoAttribute = CargoAttributeCodes.General, + DeclaredCurrency = string.IsNullOrWhiteSpace(storeOrder.Currency) ? "USD" : storeOrder.Currency.Trim(), + DeclaredSum = storeOrder.TotalAmount, + Quantity = 1, + Receiver = receiver, + Sender = warehouse != null ? BuildSender(warehouse) : BuildFallbackSender(storeOrder), + Items = declarationLines + }; + } + + private static List BuildDeclarationLinesFromPlatform( + IReadOnlyCollection lines) + { + var items = new List(); + foreach (var line in lines.Where(x => !x.IsShipExcluded)) + { + var cnName = string.IsNullOrWhiteSpace(line.ProductName) + ? (string.IsNullOrWhiteSpace(line.PlatformSku) ? "Platform Item" : line.PlatformSku.Trim()) + : line.ProductName.Trim(); + var sku = string.IsNullOrWhiteSpace(line.PlatformSku) + ? (cnName.Length > 64 ? cnName[..64] : cnName) + : line.PlatformSku.Trim(); + var unitPrice = line.UnitPrice is > 0 ? line.UnitPrice.Value : 1m; + var qty = Math.Max(1, line.Quantity); + + items.Add(new OrderParcelItemInput + { + CnName = cnName, + EnName = cnName, + Sku = sku, + Unit = "PCS", + UnitPrice = unitPrice, + UnitWeight = MinActualWeightKg, + Quantity = qty, + Amount = unitPrice * qty, + Weight = MinActualWeightKg * qty + }); + } + + if (items.Count == 0) + { + throw new BusinessException("没有可申报的商品行"); + } + + return items; + } + + private static OrderParcelSenderInput BuildFallbackSender(SalesStoreOrder storeOrder) + { + return new OrderParcelSenderInput + { + SenderName = string.IsNullOrWhiteSpace(storeOrder.PlatformOrderNo) + ? "Platform Seller" + : $"Store#{storeOrder.StoreId}", + StreetAddress1 = "TBD", + CountryCode = storeOrder.OrderCountry + }; + } + private static List BuildDeclarationLines( IReadOnlyCollection lines, IReadOnlyDictionary skuMap) @@ -155,6 +248,27 @@ public static class PlatformStoreOrderParcelBuilder private static OrderParcelReceiverInput? ParseReceiver(SalesStoreOrder storeOrder) { + if (!string.IsNullOrWhiteSpace(storeOrder.RecipientName) + && !string.IsNullOrWhiteSpace(storeOrder.RecipientCountry ?? storeOrder.OrderCountry) + && !string.IsNullOrWhiteSpace(storeOrder.RecipientZipCode) + && !string.IsNullOrWhiteSpace(storeOrder.RecipientAddress1)) + { + return new OrderParcelReceiverInput + { + RecipientsName = storeOrder.RecipientName.Trim(), + CompanyName = storeOrder.RecipientCompany, + Phone = storeOrder.RecipientPhone, + TelePhone = storeOrder.RecipientTelePhone, + Email = storeOrder.RecipientEmail, + CountryCode = (storeOrder.RecipientCountry ?? storeOrder.OrderCountry)!.Trim(), + Province = storeOrder.RecipientProvince, + City = storeOrder.RecipientCity, + StreetAddress1 = storeOrder.RecipientAddress1.Trim(), + StreetAddress2 = storeOrder.RecipientAddress2, + ZipCode = storeOrder.RecipientZipCode.Trim() + }; + } + if (!string.IsNullOrWhiteSpace(storeOrder.PlatformExtraJson)) { try @@ -260,4 +374,59 @@ public static class PlatformStoreOrderParcelBuilder throw new BusinessException("收件人信息不完整,请同步平台订单或在匹配时补充收件人"); } } + + /// + /// 从 PlatformExtraJson / ExtraJson 解析收件人并写入结构化字段(已有值不覆盖)。 + /// + public static void TryFillRecipientFields(SalesStoreOrder order, string? extraJson = null) + { + if (!string.IsNullOrWhiteSpace(order.RecipientName) + && !string.IsNullOrWhiteSpace(order.RecipientAddress1) + && !string.IsNullOrWhiteSpace(order.RecipientZipCode) + && !string.IsNullOrWhiteSpace(order.RecipientCountry ?? order.OrderCountry)) + { + return; + } + + var json = !string.IsNullOrWhiteSpace(extraJson) ? extraJson : order.PlatformExtraJson; + if (string.IsNullOrWhiteSpace(json)) + { + return; + } + + try + { + var node = JsonNode.Parse(json); + if (node == null) + { + return; + } + + var parsed = ParseReceiverNode(node); + if (parsed == null) + { + return; + } + + order.RecipientName ??= parsed.RecipientsName; + order.RecipientCompany ??= parsed.CompanyName; + order.RecipientPhone ??= parsed.Phone; + order.RecipientTelePhone ??= parsed.TelePhone; + order.RecipientEmail ??= parsed.Email; + order.RecipientCountry ??= parsed.CountryCode; + order.RecipientProvince ??= parsed.Province; + order.RecipientCity ??= parsed.City; + order.RecipientZipCode ??= parsed.ZipCode; + order.RecipientAddress1 ??= parsed.StreetAddress1; + order.RecipientAddress2 ??= parsed.StreetAddress2; + if (string.IsNullOrWhiteSpace(order.OrderCountry) && !string.IsNullOrWhiteSpace(parsed.CountryCode)) + { + order.OrderCountry = parsed.CountryCode; + } + } + catch (JsonException) + { + // ignore + } + } } diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs index 45f11ee..dc38a53 100644 --- a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs +++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs @@ -11,6 +11,7 @@ using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.Shared.Enums; +using Oms.Domain.SystemAggregate.Entities; using Oms.Domain.SystemAggregate.Repositories; using Oms.Domain.WarehouseAggregate.Consts; using Oms.Domain.WarehouseAggregate.Repositories; @@ -372,6 +373,253 @@ public class PlatformStoreOrderService : ApplicationService return output; } + /// + /// 批量创建物流订单(不扣库存、不要求 SKU 映射)。 + /// + [HttpPost("/order/platform-store-order/batch-create-logistics")] + public async Task BatchCreateLogisticsAsync( + PlatformStoreOrderBatchCreateLogisticsInput input) + { + return await BatchCreateLogisticsInternalAsync( + input.CustomerId, + input.ProductCode, + input.WarehouseId, + input.Items, + requireBind: false); + } + + /// + /// 批量创建物流订单并绑定包裹/库存(冻结,不即时扣实物)。 + /// + [HttpPost("/order/platform-store-order/batch-create-fulfill")] + public async Task BatchCreateFulfillAsync( + PlatformStoreOrderBatchCreateFulfillInput input) + { + if (input.WarehouseId <= 0) + { + throw new BusinessException("请指定发货仓库"); + } + + return await BatchCreateLogisticsInternalAsync( + input.CustomerId, + input.ProductCode, + input.WarehouseId, + input.Items, + requireBind: true); + } + + private async Task BatchCreateLogisticsInternalAsync( + int? customerIdInput, + string? productCode, + int? warehouseId, + List items, + bool requireBind) + { + if (items == null || items.Count == 0) + { + throw new BusinessException("请选择要创建的店铺订单"); + } + + if (items.Count > 50) + { + throw new BusinessException("单次最多创建 50 单"); + } + + if (string.IsNullOrWhiteSpace(productCode)) + { + throw new BusinessException("请选择运输方式"); + } + + var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync( + CustomerRepository, + CurrentUser, + customerIdInput); + var resolvedProductCode = await ResolveProductCodeRequiredAsync(productCode); + var batchNo = $"PSL{DateTime.Now:yyyyMMddHHmmss}"; + var output = new PlatformStoreOrderBatchCreateLogisticsOutput { BatchNo = batchNo }; + + foreach (var itemInput in items) + { + var storeOrderId = itemInput.StoreOrderId; + var storeOrder = await StoreOrderRepository.FirstOrDefaultAsync(x => + x.Id == storeOrderId && x.CustomerId == customerId); + var item = new PlatformStoreOrderBatchMatchItemOutput + { + Id = storeOrderId, + PlatformOrderNo = storeOrder?.PlatformOrderNo ?? storeOrderId.ToString() + }; + + try + { + if (requireBind && (itemInput.BindItems == null || itemInput.BindItems.Count == 0)) + { + throw new BusinessException("请先绑定包裹或库存"); + } + + var result = await CreateLogisticsCoreAsync( + customerId, + storeOrderId, + warehouseId, + resolvedProductCode, + itemInput.Receiver, + requireBind ? itemInput.BindItems : null); + item.Success = true; + item.Message = requireBind + ? $"已创建物流单 {result.ServiceNo} 并绑定" + : $"已创建物流单 {result.ServiceNo}"; + item.LinkedOrderId = result.OrderId; + item.ServiceNo = result.ServiceNo; + output.SuccessCount++; + } + catch (BusinessException ex) + { + item.Success = false; + item.Message = ex.Message; + output.FailCount++; + } + catch (Exception ex) + { + item.Success = false; + item.Message = ex.Message; + output.FailCount++; + } + + output.Items.Add(item); + } + + return output; + } + + private async Task CreateLogisticsCoreAsync( + int customerId, + int storeOrderId, + int? warehouseId, + string productCode, + OrderParcelReceiverInput? receiver, + List? bindItems) + { + var order = await GetOrderOrThrowAsync(customerId, storeOrderId); + await EnsureMatchableAsync(order); + + var lines = await StoreOrderLineRepository.GetListAsync(x => x.StoreOrderId == order.Id && !x.IsShipExcluded); + if (lines.Count == 0) + { + throw new BusinessException("没有可发货的商品行"); + } + + var resolvedWarehouseId = warehouseId ?? order.WarehouseId; + WarehouseAddress? warehouse = null; + if (resolvedWarehouseId is > 0) + { + warehouse = await WarehouseAddressRepository.FirstOrDefaultAsync(x => x.Id == resolvedWarehouseId.Value) + ?? throw new BusinessException("仓库不存在"); + } + + if (bindItems is { Count: > 0 } && resolvedWarehouseId is not > 0) + { + throw new BusinessException("请指定发货仓库"); + } + + var parcelInput = PlatformStoreOrderParcelBuilder.BuildFromPlatformLines( + order, + lines, + warehouse, + productCode, + resolvedWarehouseId, + customerId, + receiver); + + var parcelOutput = await OrderService.CreateParcelForBatchAsync( + parcelInput, + CurrentUser.GetId(), + OrderSourceCodes.Platform); + + order.WarehouseId = resolvedWarehouseId ?? order.WarehouseId; + order.LinkedOrderId = parcelOutput.OrderId; + order.FulfillStatus = SalesStoreFulfillStatusCodes.ToShip; + order.LastSyncTime = DateTime.Now; + if (!string.IsNullOrWhiteSpace(parcelOutput.TrackNo)) + { + order.ShippingLabelNo = parcelOutput.TrackNo; + } + + await StoreOrderRepository.UpdateAsync(order); + + if (bindItems is not { Count: > 0 }) + { + return parcelOutput; + } + + try + { + // 确保物流单带仓库,绑定校验依赖 WarehouseId + if (resolvedWarehouseId is > 0) + { + var logisticsOrder = await OrderRepository.FirstOrDefaultAsync(x => x.Id == parcelOutput.OrderId); + if (logisticsOrder != null && logisticsOrder.WarehouseId is not > 0) + { + logisticsOrder.WarehouseId = resolvedWarehouseId; + await OrderRepository.UpdateAsync(logisticsOrder); + } + } + + await OrderService.SaveStockBindAsync(new OrderStockBindSaveInput + { + OrderId = parcelOutput.OrderId, + Items = bindItems + }); + return parcelOutput; + } + catch (Exception) + { + await RollbackCreatedLogisticsAsync(order, parcelOutput.OrderId); + throw; + } + } + + private async Task RollbackCreatedLogisticsAsync(SalesStoreOrder storeOrder, int logisticsOrderId) + { + try + { + await OrderService.ClearStockBindAsync(new OrderStockBindOrderInput { OrderId = logisticsOrderId }); + } + catch + { + // 可能尚未写入绑定 + } + + storeOrder.LinkedOrderId = null; + storeOrder.FulfillStatus = SalesStoreFulfillStatusCodes.Pending; + storeOrder.LastSyncTime = DateTime.Now; + await StoreOrderRepository.UpdateAsync(storeOrder); + + var logisticsOrder = await OrderRepository.FirstOrDefaultAsync(x => x.Id == logisticsOrderId); + if (logisticsOrder == null) + { + return; + } + + logisticsOrder.OrderStatus = OrderStatusCodes.Deleted; + if (!string.IsNullOrWhiteSpace(logisticsOrder.RequestId)) + { + logisticsOrder.RequestId = $"{logisticsOrder.RequestId}:rolled-back:{DateTime.Now.Ticks}"; + } + + await OrderRepository.UpdateAsync(logisticsOrder); + } + + private async Task ResolveProductCodeRequiredAsync(string productCode) + { + var normalized = productCode.Trim(); + var product = await SalesProductRepository.GetByProductCodeAsync(normalized); + if (product == null || product.IsActive != EnableStatus.Enabled) + { + throw new BusinessException("销售产品不存在或未启用"); + } + + return normalized; + } + private async Task MatchInventoryCoreAsync( int customerId, int storeOrderId, @@ -727,6 +975,17 @@ public class PlatformStoreOrderService : ApplicationService PlatformCode = order.PlatformCode, PlatformOrderNo = order.PlatformOrderNo, OrderCountry = order.OrderCountry, + RecipientName = order.RecipientName, + RecipientCompany = order.RecipientCompany, + RecipientPhone = order.RecipientPhone, + RecipientTelePhone = order.RecipientTelePhone, + RecipientEmail = order.RecipientEmail, + RecipientCountry = order.RecipientCountry, + RecipientProvince = order.RecipientProvince, + RecipientCity = order.RecipientCity, + RecipientZipCode = order.RecipientZipCode, + RecipientAddress1 = order.RecipientAddress1, + RecipientAddress2 = order.RecipientAddress2, ItemCount = order.ItemCount, TotalAmount = order.TotalAmount, Currency = order.Currency, diff --git a/src/Oms.Domain/OrdersAggregate/Entities/SalesStoreOrder.cs b/src/Oms.Domain/OrdersAggregate/Entities/SalesStoreOrder.cs index cbc2f9d..a297d48 100644 --- a/src/Oms.Domain/OrdersAggregate/Entities/SalesStoreOrder.cs +++ b/src/Oms.Domain/OrdersAggregate/Entities/SalesStoreOrder.cs @@ -16,6 +16,28 @@ public class SalesStoreOrder : OmsAuditedAggregateRoot public string? OrderCountry { get; set; } + public string? RecipientName { get; set; } + + public string? RecipientCompany { get; set; } + + public string? RecipientPhone { get; set; } + + public string? RecipientTelePhone { get; set; } + + public string? RecipientEmail { get; set; } + + public string? RecipientCountry { get; set; } + + public string? RecipientProvince { get; set; } + + public string? RecipientCity { get; set; } + + public string? RecipientZipCode { get; set; } + + public string? RecipientAddress1 { get; set; } + + public string? RecipientAddress2 { get; set; } + public int? ItemCount { get; set; } public decimal? TotalAmount { get; set; }