feat: add employee profile, IDE employee nos, and marketing invite/coupon fixes

Introduce t_employee linked to RBAC users, switch employee numbers to IDE001+, and keep invite bind/newcomer coupon backend changes. Drop retired sql/migrate and seed scripts in favor of db.sql.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-07-26 23:18:57 +08:00
parent 71fbbf54c0
commit c5fb4c7e58
47 changed files with 823 additions and 2765 deletions

39
db.sql
View File

@ -57,6 +57,7 @@ DROP TABLE IF EXISTS `t_system_other_config`;
DROP TABLE IF EXISTS `t_warehouse_address`;
DROP TABLE IF EXISTS `t_notice_read`;
DROP TABLE IF EXISTS `t_notice`;
DROP TABLE IF EXISTS `t_employee`;
DROP TABLE IF EXISTS `t_dict_type`;
DROP TABLE IF EXISTS `t_dict`;
DROP TABLE IF EXISTS `t_country`;
@ -1106,6 +1107,44 @@ CREATE TABLE `t_notice` (
KEY `idx_notice_title` (`Title`)
) COMMENT='公告通知表';
CREATE TABLE `t_employee` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,步长 1',
`UserId` CHAR(36) NOT NULL COMMENT '关联 RBAC user.Id(工号/手机/邮箱见 user 表)',
`RealName` VARCHAR(64) NOT NULL COMMENT '姓名(人事档案用)',
`Gender` TINYINT NOT NULL DEFAULT 0 COMMENT 'Gender:0=Unknown未知,1=Male男,2=Female女',
`Birthday` DATE NULL COMMENT '出生日期',
`IdCardNo` VARCHAR(32) NULL COMMENT '身份证号',
`HireDate` DATE NULL COMMENT '入职日期',
`ProbationEndDate` DATE NULL COMMENT '试用期结束日期',
`RegularizationDate` DATE NULL COMMENT '转正日期',
`EmploymentStatus` TINYINT NOT NULL DEFAULT 1 COMMENT 'EmploymentStatus:0=None,1=Probation试用,2=Regular正式,3=Suspended停职,4=Resigned离职',
`ResignationDate` DATE NULL COMMENT '离职日期',
`Department` VARCHAR(128) NULL COMMENT '部门',
`Position` VARCHAR(128) NULL COMMENT '岗位/职位',
`JobLevel` VARCHAR(64) NULL COMMENT '职级',
`EmploymentType` TINYINT NOT NULL DEFAULT 1 COMMENT 'EmploymentType:0=None,1=FullTime全职,2=PartTime兼职,3=Intern实习,4=Outsourced外包',
`WorkLocation` VARCHAR(128) NULL COMMENT '工作地点',
`ManagerUserId` CHAR(36) NULL COMMENT '直属上级 user.Id',
`ContractStartDate` DATE NULL COMMENT '合同开始日期',
`ContractEndDate` DATE NULL COMMENT '合同结束日期',
`ContractType` VARCHAR(64) NULL COMMENT '合同类型',
`EmergencyContact` VARCHAR(64) NULL COMMENT '紧急联系人',
`EmergencyPhone` VARCHAR(32) NULL COMMENT '紧急联系人电话',
`Address` VARCHAR(256) NULL COMMENT '住址',
`Education` VARCHAR(64) NULL COMMENT '学历',
`Remark` VARCHAR(512) NULL COMMENT '备注',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_employee_user` (`UserId`),
KEY `idx_employee_status_dept` (`EmploymentStatus`, `Department`),
KEY `idx_employee_manager` (`ManagerUserId`)
) COMMENT='员工档案表(关联内部用户)';
CREATE TABLE `t_notice_read` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,步长 1',
`NoticeId` INT NOT NULL COMMENT '通知ID',

46
scripts/t_employee.sql Normal file
View File

@ -0,0 +1,46 @@
-- 员工档案表(已有库增量执行)
-- 关联 RBAC user.Id;工号/手机/邮箱以 user 表为准,本表不重复
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `t_employee`;
CREATE TABLE `t_employee` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,步长 1',
`UserId` CHAR(36) NOT NULL COMMENT '关联 RBAC user.Id(工号/手机/邮箱见 user 表)',
`RealName` VARCHAR(64) NOT NULL COMMENT '姓名(人事档案用)',
`Gender` TINYINT NOT NULL DEFAULT 0 COMMENT 'Gender:0=Unknown未知,1=Male男,2=Female女',
`Birthday` DATE NULL COMMENT '出生日期',
`IdCardNo` VARCHAR(32) NULL COMMENT '身份证号',
`HireDate` DATE NULL COMMENT '入职日期',
`ProbationEndDate` DATE NULL COMMENT '试用期结束日期',
`RegularizationDate` DATE NULL COMMENT '转正日期',
`EmploymentStatus` TINYINT NOT NULL DEFAULT 1 COMMENT 'EmploymentStatus:0=None,1=Probation试用,2=Regular正式,3=Suspended停职,4=Resigned离职',
`ResignationDate` DATE NULL COMMENT '离职日期',
`Department` VARCHAR(128) NULL COMMENT '部门',
`Position` VARCHAR(128) NULL COMMENT '岗位/职位',
`JobLevel` VARCHAR(64) NULL COMMENT '职级',
`EmploymentType` TINYINT NOT NULL DEFAULT 1 COMMENT 'EmploymentType:0=None,1=FullTime全职,2=PartTime兼职,3=Intern实习,4=Outsourced外包',
`WorkLocation` VARCHAR(128) NULL COMMENT '工作地点',
`ManagerUserId` CHAR(36) NULL COMMENT '直属上级 user.Id',
`ContractStartDate` DATE NULL COMMENT '合同开始日期',
`ContractEndDate` DATE NULL COMMENT '合同结束日期',
`ContractType` VARCHAR(64) NULL COMMENT '合同类型',
`EmergencyContact` VARCHAR(64) NULL COMMENT '紧急联系人',
`EmergencyPhone` VARCHAR(32) NULL COMMENT '紧急联系人电话',
`Address` VARCHAR(256) NULL COMMENT '住址',
`Education` VARCHAR(64) NULL COMMENT '学历',
`Remark` VARCHAR(512) NULL COMMENT '备注',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_employee_user` (`UserId`),
KEY `idx_employee_status_dept` (`EmploymentStatus`, `Department`),
KEY `idx_employee_manager` (`ManagerUserId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工档案表(关联内部用户)';
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,20 +0,0 @@
-- 已有库增量:快递到仓表 c_parcel_arrival
-- 对应功能:操作 → 快递到仓;全量结构见根目录 db.sql
-- 可重复执行(IF NOT EXISTS)
CREATE TABLE IF NOT EXISTS `c_parcel_arrival` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID',
`ParcelId` INT NULL COMMENT '关联包裹 c_parcel.Id',
`TrackingNo` VARCHAR(64) NOT NULL COMMENT '国内快递单号',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
KEY `idx_c_parcel_arrival_tracking` (`CustomerId`, `TrackingNo`, `WarehouseId`),
KEY `idx_c_parcel_arrival_ctime` (`CreateTime`)
) COMMENT='仓储-快递到仓记录';

View File

@ -1,16 +0,0 @@
-- 已有库增量:c_parcel_item.AttributeText
-- 可重复执行
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'c_parcel_item'
AND COLUMN_NAME = 'AttributeText'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `c_parcel_item` ADD COLUMN `AttributeText` VARCHAR(256) NULL COMMENT ''规格属性'' AFTER `ProductName`',
'SELECT ''c_parcel_item.AttributeText already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -1,16 +0,0 @@
-- 已有库增量:c_parcel_item.ImageUrl
-- 可重复执行
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'c_parcel_item'
AND COLUMN_NAME = 'ImageUrl'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `c_parcel_item` ADD COLUMN `ImageUrl` VARCHAR(1024) NULL COMMENT ''商品图片URL'' AFTER `AttributeText`',
'SELECT ''c_parcel_item.ImageUrl already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -1,63 +0,0 @@
-- 给缺少 ConcurrencyStamp 的 OMS 表补列(可重复执行)
-- Fake AggregateRoot 自带该字段,SqlSugar 查询会带上;库表没有就会报:
-- Unknown column 'ConcurrencyStamp' in 'field list'
--
-- 注意:不要用 DEFAULT (UUID())。在 STATEMENT/MIXED 复制下会触发:
-- 1674 Statement is unsafe because it uses a system function that may return a different value on the replica.
-- 默认用固定 GUID;插入/更新时由 ORM 写入真实 UUID。
--
-- 用法:在业务库执行本脚本;若仍报错,把报错 SQL 里的表名追加到下方列表。
SET @tables := 't_order_stock_bind,c_inventory,c_parcel,c_parcel_item,c_parcel_arrival,c_sku,c_bin_location,c_inbound_notice,c_inbound_box,c_inbound_box_line,c_sku_platform_map,t_sales_store,t_sales_store_order,t_sales_store_order_line,t_customer_sender_address';
DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing;
DELIMITER $$
CREATE PROCEDURE sp_add_concurrency_stamp_if_missing()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE tname VARCHAR(128);
DECLARE cur CURSOR FOR
SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(@tables, ',', n.n), ',', -1)) AS t
FROM (
SELECT a.N + b.N * 10 + 1 AS n
FROM
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b
) n
WHERE n.n <= 1 + (LENGTH(@tables) - LENGTH(REPLACE(@tables, ',', '')));
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
FETCH cur INTO tname;
IF done THEN
LEAVE read_loop;
END IF;
IF tname IS NULL OR tname = '' THEN
ITERATE read_loop;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tname
) THEN
ITERATE read_loop;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tname AND COLUMN_NAME = 'ConcurrencyStamp'
) THEN
SET @ddl := CONCAT(
'ALTER TABLE `', tname,
'` ADD COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT ''00000000-0000-0000-0000-000000000000'' COMMENT ''并发戳'''
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
CALL sp_add_concurrency_stamp_if_missing();
DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing;

View File

@ -1,41 +0,0 @@
-- 修复店铺相关表 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`;

View File

@ -1,6 +0,0 @@
-- 合并下单任务表到批量任务:删除 t_order_task / t_order_task_fail
-- 历史导入任务数据不再保留;Excel 导入统一走 t_order_batch_job(action=import-row)
-- 2026-07-22:触发重新发布,确保 API 不再访问已删表
DROP TABLE IF EXISTS `t_order_task_fail`;
DROP TABLE IF EXISTS `t_order_task`;

View File

@ -1,74 +0,0 @@
-- 已有库增量:字节枚举字段注释统一 + 发布记录 Status 改 TINYINT + 旧 0 起编码迁移
-- 对应 C# 枚举:EnableStatus / CustomerStatus / CustomerGroup / RechargeStatus / PayType / EsSnapshotStatus
-- 约定:0=None 未设置;业务值从 1 起。执行前请备份。
-- ---------- 1. 发布记录 Status:VARCHAR(中文) -> TINYINT(EsSnapshotStatus) ----------
ALTER TABLE `t_sales_product_publish_record`
ADD COLUMN `StatusInt` TINYINT NOT NULL DEFAULT 1 COMMENT 'EsSnapshotStatus:0=None,1=Active生效,2=Inactive失效' AFTER `OperatorType`;
UPDATE `t_sales_product_publish_record`
SET `StatusInt` = CASE
WHEN TRIM(`Status`) IN ('生效', 'active', 'Active', '1') THEN 1
WHEN TRIM(`Status`) IN ('失效', '无效', 'inactive', 'Inactive', '2') THEN 2
ELSE 1
END;
ALTER TABLE `t_sales_product_publish_record`
DROP COLUMN `Status`,
CHANGE COLUMN `StatusInt` `Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'EsSnapshotStatus:0=None未设置,1=Active生效,2=Inactive失效';
ALTER TABLE `t_service_channel_publish_record`
ADD COLUMN `StatusInt` TINYINT NOT NULL DEFAULT 1 COMMENT 'EsSnapshotStatus:0=None,1=Active生效,2=Inactive失效' AFTER `OperatorType`;
UPDATE `t_service_channel_publish_record`
SET `StatusInt` = CASE
WHEN TRIM(`Status`) IN ('生效', 'active', 'Active', '1') THEN 1
WHEN TRIM(`Status`) IN ('失效', '无效', 'inactive', 'Inactive', '2') THEN 2
ELSE 1
END;
ALTER TABLE `t_service_channel_publish_record`
DROP COLUMN `Status`,
CHANGE COLUMN `StatusInt` `Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'EsSnapshotStatus:0=None未设置,1=Active生效,2=Inactive失效';
-- ---------- 2. 旧 0 起编码 -> 1 起(若已迁移可跳过对应 UPDATE) ----------
UPDATE `t_customer` SET `Status` = `Status` + 1 WHERE `Status` BETWEEN 0 AND 2;
UPDATE `t_customer` SET `CustomerGroup` = `CustomerGroup` + 1 WHERE `CustomerGroup` BETWEEN 0 AND 5;
UPDATE `t_recharge_record` SET `RechargeStatus` = `RechargeStatus` + 1 WHERE `RechargeStatus` BETWEEN 0 AND 2;
UPDATE `t_sales_product` SET `IsActive` = CASE `IsActive` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `IsActive` END WHERE `IsActive` IN (0, 1);
UPDATE `t_service_channel` SET `IsActive` = CASE `IsActive` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `IsActive` END WHERE `IsActive` IN (0, 1);
UPDATE `t_sales_product_price` SET `Status` = CASE `Status` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `Status` END WHERE `Status` IN (0, 1);
UPDATE `t_service_channel_price` SET `Status` = CASE `Status` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `Status` END WHERE `Status` IN (0, 1);
UPDATE `t_auto_trigger` SET `Status` = CASE `Status` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `Status` END WHERE `Status` IN (0, 1);
UPDATE `t_issue_template` SET `Status` = CASE `Status` WHEN 0 THEN 1 WHEN 1 THEN 2 ELSE `Status` END WHERE `Status` IN (0, 1);
-- ---------- 3. 字段注释(可重复执行 MODIFY) ----------
ALTER TABLE `t_customer`
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'CustomerStatus:0=None未设置,1=Disabled禁用,2=Normal正常,3=Frozen冻结',
MODIFY COLUMN `CustomerGroup` TINYINT NULL COMMENT 'CustomerGroup:0=None未设置,1=Normal普通,2=Vip1…6=Vip5';
ALTER TABLE `t_recharge_record`
MODIFY COLUMN `PayType` TINYINT NOT NULL COMMENT 'PayType:0=None未设置,1=Wechat微信,2=Alipay支付宝',
MODIFY COLUMN `RechargeStatus` TINYINT NOT NULL DEFAULT 1 COMMENT 'RechargeStatus:0=None未设置,1=Pending待审核,2=Approved通过,3=Rejected驳回';
ALTER TABLE `t_sales_product`
MODIFY COLUMN `IsActive` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_service_channel`
MODIFY COLUMN `IsActive` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_sales_product_price`
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_service_channel_price`
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_auto_trigger`
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_issue_template`
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None未设置,1=Disabled禁用,2=Enabled启用';
ALTER TABLE `t_dict`
MODIFY COLUMN `DictCode` VARCHAR(64) NOT NULL COMMENT '字典编码:字符串类存语义码;数值类存整数字符串如1/2,与枚举值一致(0=None不入库)';

View File

@ -1,87 +0,0 @@
-- 已有库增量:订单面单 PDF 字段 + 销售产品 LabelTemplateId
-- 对应:docs/面单标签模板与PDF生成方案.md §4.2 / §4.3
-- 可重复执行
-- ========== t_order ==========
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'LabelUrl'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `LabelUrl` VARCHAR(500) NULL COMMENT ''面单PDF相对路径'' AFTER `IsRemote`',
'SELECT ''t_order.LabelUrl already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'LabelTemplateId'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `LabelTemplateId` INT NULL COMMENT ''面单模板Id'' AFTER `LabelUrl`',
'SELECT ''t_order.LabelTemplateId already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'LabelGenerateStatus'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `LabelGenerateStatus` VARCHAR(32) NULL COMMENT ''Pending/Ready/Failed'' AFTER `LabelTemplateId`',
'SELECT ''t_order.LabelGenerateStatus already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'LabelGenerateError'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `LabelGenerateError` VARCHAR(500) NULL COMMENT ''面单生成失败原因'' AFTER `LabelGenerateStatus`',
'SELECT ''t_order.LabelGenerateError already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(1) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND INDEX_NAME = 'idx_ord_label_status'
);
SET @sql := IF(
@idx_exists = 0,
'ALTER TABLE `t_order` ADD KEY `idx_ord_label_status` (`LabelGenerateStatus`)',
'SELECT ''idx_ord_label_status already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ========== t_sales_product ==========
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_sales_product' AND COLUMN_NAME = 'LabelTemplateId'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_sales_product` ADD COLUMN `LabelTemplateId` INT NULL COMMENT ''客户面单模板Id'' AFTER `CustomerWaybill`',
'SELECT ''t_sales_product.LabelTemplateId already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ========== 全局默认模板配置种子(AppKey 填模板 Id,Enabled=0 默认关闭)==========
INSERT INTO `t_system_other_config`
(`ConfigCode`, `ConfigName`, `Enabled`, `AppKey`, `TimeoutSeconds`, `Remark`, `CreateTime`)
SELECT
'label_default_template',
'全局默认面单模板',
0,
NULL,
30,
'AppKey=标签模板Id;启用后产品未绑模板时回落此 Id',
NOW()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_system_other_config` WHERE `ConfigCode` = 'label_default_template'
);

View File

@ -1,336 +0,0 @@
-- 客户营销模块:裂变 / 会员等级 / 优惠券 / 推荐分成
-- 前置约定(见 docs/客户裂变与营销体系设计方案.md §2、§24):
-- 1. 子账号不参与营销,一律归主客户
-- 2. MemberLevel 替换 CustomerGroup
-- 3. CommissionBalance 独立字段,可提现或转余额
-- 4. 默认分成:实付每 1000 元 → 2 元(0.2%)
-- 表结构遵循 docs/oms系统设计-050801.md §3.1.2(ConcurrencyStamp 固定默认值,禁止 UUID())
-- ---------------------------------------------------------------------------
-- 1. t_customer 扩展
-- ---------------------------------------------------------------------------
SET @col_exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_customer' AND COLUMN_NAME = 'ReferrerCustomerId'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE `t_customer`
ADD COLUMN `ReferrerCustomerId` INT NULL COMMENT ''引荐人主客户Id → t_customer.Id'' AFTER `ParentCustomerId`,
ADD COLUMN `ReferralBindTime` DATETIME NULL COMMENT ''引荐绑定时间'' AFTER `ReferrerCustomerId`,
ADD COLUMN `ReferralChannel` VARCHAR(32) NULL COMMENT ''引荐渠道 link/qrcode/manual'' AFTER `ReferralBindTime`,
ADD COLUMN `MemberLevelId` INT NULL COMMENT ''会员等级Id → t_member_level.Id'' AFTER `CustomerGroup`,
ADD COLUMN `CommissionBalance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT ''可提现佣金余额'' AFTER `Balance`,
ADD KEY `idx_cus_referrer` (`ReferrerCustomerId`),
ADD KEY `idx_cus_member_level` (`MemberLevelId`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ---------------------------------------------------------------------------
-- 2. 引荐码
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `t_customer_referral_code` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '主客户Id → t_customer.Id',
`InviteCode` VARCHAR(16) NOT NULL COMMENT '邀请短码,全局唯一',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'ReferralCodeStatus:0=Disabled,1=Enabled',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_referral_invite_code` (`InviteCode`),
UNIQUE KEY `uk_referral_customer` (`CustomerId`)
) COMMENT='客户引荐码(主客户)';
-- ---------------------------------------------------------------------------
-- 3. 会员等级
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `t_member_level` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`LevelCode` VARCHAR(32) NOT NULL COMMENT '等级编码,唯一',
`LevelName` VARCHAR(64) NOT NULL COMMENT '等级名称',
`MinPoints` INT NOT NULL DEFAULT 0 COMMENT '升级所需累计积分',
`PriceTierKey` VARCHAR(32) NOT NULL COMMENT '价区键,对应卖价 CustomerGroupValues JSON 键',
`ExtraDiscountRate` DECIMAL(8,4) NOT NULL DEFAULT 0.0000 COMMENT '额外折扣率,如 0.03=97折',
`Sort` INT NOT NULL DEFAULT 0 COMMENT '排序,越小越低',
`IconUrl` VARCHAR(255) NULL COMMENT '等级图标',
`BenefitDesc` VARCHAR(512) NULL COMMENT '权益说明',
`Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None,1=Disabled,2=Enabled',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_member_level_code` (`LevelCode`),
KEY `idx_member_level_sort` (`Sort`, `Status`)
) COMMENT='会员等级配置';
CREATE TABLE IF NOT EXISTS `t_customer_points_account` (
`Id` INT NOT NULL AUTO_INCREMENT,
`CustomerId` INT NOT NULL COMMENT '主客户Id',
`TotalPoints` INT NOT NULL DEFAULT 0 COMMENT '累计积分(含已消耗,用于定级)',
`AvailablePoints` INT NOT NULL DEFAULT 0 COMMENT '可用积分',
`Version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_points_customer` (`CustomerId`)
) COMMENT='客户积分账户(主客户)';
CREATE TABLE IF NOT EXISTS `t_customer_points_flow` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '主客户Id',
`ChangeType` TINYINT NOT NULL COMMENT 'PointsChangeType:1=Earn,2=Consume,3=Adjust,4=RefundClawback',
`Points` INT NOT NULL COMMENT '变动积分,正增负减',
`BizType` VARCHAR(32) NOT NULL COMMENT 'orderPay/refund/adminAdjust 等',
`BizId` INT NULL,
`BizNo` VARCHAR(64) NULL,
`Remark` VARCHAR(512) NULL,
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
KEY `idx_points_flow_customer_time` (`CustomerId`, `CreateTime`),
KEY `idx_points_flow_biz` (`BizType`, `BizId`)
) COMMENT='积分流水';
CREATE TABLE IF NOT EXISTS `t_member_points_rule` (
`Id` INT NOT NULL AUTO_INCREMENT,
`RuleName` VARCHAR(64) NOT NULL COMMENT '规则名称',
`PointsPerYuan` DECIMAL(10,4) NOT NULL DEFAULT 1.0000 COMMENT '实付每1元得积分',
`FirstOrderMultiplier` DECIMAL(6,2) NOT NULL DEFAULT 1.00 COMMENT '首单积分倍数',
`MaxPointsPerOrder` INT NOT NULL DEFAULT 10000 COMMENT '单笔积分上限',
`Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None,1=Disabled,2=Enabled',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`)
) COMMENT='积分获取规则(全局单条有效)';
-- ---------------------------------------------------------------------------
-- 4. 优惠券
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `t_coupon_template` (
`Id` INT NOT NULL AUTO_INCREMENT,
`TemplateCode` VARCHAR(32) NOT NULL COMMENT '券模板编码',
`TemplateName` VARCHAR(128) NOT NULL COMMENT '券名称',
`CouponType` VARCHAR(32) NOT NULL COMMENT 'fixed_off/percent_off/direct_off/shipping_off',
`FaceValue` DECIMAL(18,2) NULL COMMENT '面额(满减/立减)',
`DiscountRate` DECIMAL(8,4) NULL COMMENT '折扣率,如0.95',
`MaxDiscountAmount` DECIMAL(18,2) NULL COMMENT '折扣券封顶',
`MinOrderAmount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '使用门槛',
`TotalQuantity` INT NOT NULL DEFAULT 0 COMMENT '发放总量,0=不限',
`IssuedQuantity` INT NOT NULL DEFAULT 0 COMMENT '已发放数量',
`PerCustomerLimit` INT NOT NULL DEFAULT 1 COMMENT '每客户领取上限',
`ValidType` TINYINT NOT NULL DEFAULT 1 COMMENT '1=固定日期,2=领取后N天',
`ValidStartTime` DATETIME NULL,
`ValidEndTime` DATETIME NULL,
`ValidDaysAfterReceive` INT NULL COMMENT '领取后有效天数',
`ApplyScopeJson` JSON NULL COMMENT '适用产品/国家等 JSON',
`GrantChannel` VARCHAR(32) NOT NULL DEFAULT 'manual' COMMENT 'manual/register/referral/level/activity',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'CouponTemplateStatus:0=Draft,1=Active,2=Ended',
`Remark` VARCHAR(512) NULL,
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_coupon_template_code` (`TemplateCode`),
KEY `idx_coupon_template_status` (`Status`, `ValidEndTime`)
) COMMENT='优惠券模板';
CREATE TABLE IF NOT EXISTS `t_coupon_instance` (
`Id` INT NOT NULL AUTO_INCREMENT,
`TemplateId` INT NOT NULL COMMENT '→ t_coupon_template.Id',
`CustomerId` INT NOT NULL COMMENT '主客户Id',
`CouponCode` VARCHAR(32) NOT NULL COMMENT '券实例码',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'CouponInstanceStatus:1=Available,2=Locked,3=Used,4=Expired',
`ReceiveTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ValidStartTime` DATETIME NOT NULL,
`ValidEndTime` DATETIME NOT NULL,
`UseTime` DATETIME NULL,
`OrderId` INT NULL COMMENT '核销订单Id',
`LockExpireTime` DATETIME NULL COMMENT '下单预占过期',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_coupon_instance_code` (`CouponCode`),
KEY `idx_coupon_instance_customer_status` (`CustomerId`, `Status`),
KEY `idx_coupon_instance_order` (`OrderId`)
) COMMENT='客户优惠券实例';
-- ---------------------------------------------------------------------------
-- 5. 推荐分成
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `t_referral_commission_rule` (
`Id` INT NOT NULL AUTO_INCREMENT,
`RuleName` VARCHAR(64) NOT NULL DEFAULT '默认分成规则',
`CommissionMode` VARCHAR(32) NOT NULL DEFAULT 'per_amount' COMMENT 'per_amount=每满X元得Y元',
`BaseAmount` DECIMAL(18,2) NOT NULL DEFAULT 1000.00 COMMENT '基数金额',
`CommissionAmount` DECIMAL(18,2) NOT NULL DEFAULT 2.00 COMMENT '每基数分成金额',
`FreezeDays` INT NOT NULL DEFAULT 7 COMMENT '派送成功后的结算冻结天数',
`SettleTriggerOrderStatus` VARCHAR(32) NOT NULL DEFAULT 'deliverySuccess' COMMENT '允许结算的订单状态,固定 deliverySuccess',
`MonthlyCap` DECIMAL(18,2) NULL COMMENT '引荐人单月分成封顶,NULL=不限',
`Status` TINYINT NOT NULL DEFAULT 2 COMMENT 'EnableStatus:0=None,1=Disabled,2=Enabled',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`)
) COMMENT='引荐分成规则';
CREATE TABLE IF NOT EXISTS `t_referral_commission_detail` (
`Id` INT NOT NULL AUTO_INCREMENT,
`ReferrerCustomerId` INT NOT NULL COMMENT '引荐人主客户Id',
`RefereeCustomerId` INT NOT NULL COMMENT '被引荐主客户Id',
`OrderId` INT NOT NULL COMMENT '订单Id',
`OrderNo` VARCHAR(64) NOT NULL COMMENT '运单号快照',
`BasePayableAmount` DECIMAL(18,2) NOT NULL COMMENT '分成基数(实付)',
`CommissionAmount` DECIMAL(18,2) NOT NULL COMMENT '分成金额',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'CommissionStatus:1=Pending,2=Settled,3=Cancelled',
`EligibleTime` DATETIME NULL COMMENT '订单达到派送成功时间,分成计算触发点',
`SettleTime` DATETIME NULL COMMENT '入账 CommissionBalance 时间',
`RuleSnapshotJson` JSON NULL COMMENT '分成规则快照:BaseAmount/CommissionAmount/FreezeDays/MonthlyCap 等',
`CancelReason` VARCHAR(256) NULL,
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_commission_order_referrer` (`OrderId`, `ReferrerCustomerId`),
KEY `idx_commission_referrer_status` (`ReferrerCustomerId`, `Status`, `CreateTime`),
KEY `idx_commission_referee` (`RefereeCustomerId`)
) COMMENT='引荐分成明细';
CREATE TABLE IF NOT EXISTS `t_commission_withdraw_request` (
`Id` INT NOT NULL AUTO_INCREMENT,
`CustomerId` INT NOT NULL COMMENT '主客户Id',
`SerialNumber` VARCHAR(64) NOT NULL COMMENT '提现单号',
`Amount` DECIMAL(18,2) NOT NULL COMMENT '申请金额',
`WithdrawType` TINYINT NOT NULL COMMENT '1=提现到外部,2=转入余额',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT 'WithdrawStatus:1=Pending,2=Approved,3=Rejected,4=Paid',
`Remark` VARCHAR(512) NULL COMMENT '客户备注',
`AuditRemark` VARCHAR(512) NULL COMMENT '审核备注',
`AuditBy` VARCHAR(32) NULL,
`AuditTime` DATETIME NULL,
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0,
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_commission_withdraw_serial` (`SerialNumber`),
KEY `idx_commission_withdraw_customer` (`CustomerId`, `Status`, `CreateTime`)
) COMMENT='佣金提现/转余额申请';
-- ---------------------------------------------------------------------------
-- 5.1 营销审计日志
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `t_marketing_audit_log` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`Action` VARCHAR(64) NOT NULL COMMENT 'grantCoupon/adjustPoints/rebindReferral 等',
`OperatorId` INT NOT NULL DEFAULT 0 COMMENT '操作人 mapId',
`TargetType` VARCHAR(32) NOT NULL COMMENT 'customer/template/order 等',
`TargetId` INT NULL COMMENT '目标主键',
`BeforeJson` JSON NULL COMMENT '变更前快照',
`AfterJson` JSON NULL COMMENT '变更后快照',
`Remark` VARCHAR(512) NULL COMMENT '原因/备注',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
KEY `idx_marketing_audit_action_time` (`Action`, `CreateTime`),
KEY `idx_marketing_audit_target` (`TargetType`, `TargetId`)
) COMMENT='营销高危操作审计';
-- ---------------------------------------------------------------------------
-- 6. 订单营销快照
-- ---------------------------------------------------------------------------
SET @col_exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order' AND COLUMN_NAME = 'MemberLevelId'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE `t_order`
ADD COLUMN `MemberLevelId` INT NULL COMMENT ''下单时会员等级快照'' AFTER `CustomerId`,
ADD COLUMN `PriceTierKey` VARCHAR(32) NULL COMMENT ''下单时价区键快照'' AFTER `MemberLevelId`,
ADD COLUMN `ReceivableSubtotal` DECIMAL(18,2) NULL COMMENT ''等级价后小计'' AFTER `PriceTierKey`,
ADD COLUMN `LevelDiscountAmount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT ''等级额外折扣额'' AFTER `ReceivableSubtotal`,
ADD COLUMN `CouponInstanceId` INT NULL COMMENT ''核销券实例Id'' AFTER `LevelDiscountAmount`,
ADD COLUMN `CouponDiscountAmount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT ''优惠券抵扣额'' AFTER `CouponInstanceId`,
ADD COLUMN `PayableAmount` DECIMAL(18,2) NULL COMMENT ''应付实付'' AFTER `CouponDiscountAmount`,
ADD KEY `idx_ord_coupon` (`CouponInstanceId`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ---------------------------------------------------------------------------
-- 7. 种子:默认会员等级(映射原 CustomerGroup 价区)
-- ---------------------------------------------------------------------------
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `PriceTierKey`, `ExtraDiscountRate`, `Sort`, `BenefitDesc`, `Status`, `CreateUserId`)
VALUES
('normal', '普通会员', 0, '普通客户', 0.0000, 10, '标准运价', 2, 0),
('silver', '银卡会员', 5000, 'VIP1', 0.0000, 20, 'VIP1 价区', 2, 0),
('gold', '金卡会员', 20000, 'VIP2', 0.0100, 30, 'VIP2 价区 + 额外 1% 折', 2, 0),
('diamond', '钻石会员', 100000, 'VIP3', 0.0200, 40, 'VIP3 价区 + 额外 2% 折', 2, 0)
ON DUPLICATE KEY UPDATE
`LevelName` = VALUES(`LevelName`),
`MinPoints` = VALUES(`MinPoints`),
`PriceTierKey` = VALUES(`PriceTierKey`),
`ExtraDiscountRate` = VALUES(`ExtraDiscountRate`),
`Sort` = VALUES(`Sort`),
`BenefitDesc` = VALUES(`BenefitDesc`);
-- 主客户 MemberLevelId 迁移(按原 CustomerGroup)
UPDATE `t_customer` c
LEFT JOIN `t_member_level` ml ON (
(c.CustomerGroup IS NULL OR c.CustomerGroup = 0 OR c.CustomerGroup = 1) AND ml.LevelCode = 'normal'
OR (c.CustomerGroup = 2 AND ml.LevelCode = 'silver')
OR (c.CustomerGroup = 3 AND ml.LevelCode = 'gold')
OR (c.CustomerGroup = 4 AND ml.LevelCode = 'diamond')
OR (c.CustomerGroup >= 5 AND ml.LevelCode = 'diamond')
)
SET c.MemberLevelId = ml.Id
WHERE c.ParentCustomerId IS NULL AND c.MemberLevelId IS NULL AND ml.Id IS NOT NULL;
-- 默认积分规则
INSERT INTO `t_member_points_rule`
(`RuleName`, `PointsPerYuan`, `FirstOrderMultiplier`, `MaxPointsPerOrder`, `Status`, `CreateUserId`)
SELECT '默认积分规则', 1.0000, 1.00, 10000, 2, 0
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `t_member_points_rule` WHERE `IsDeleted` = 0 LIMIT 1);
-- 默认分成规则:每 1000 元 → 2 元
INSERT INTO `t_referral_commission_rule`
(`RuleName`, `CommissionMode`, `BaseAmount`, `CommissionAmount`, `FreezeDays`, `Status`, `CreateUserId`)
SELECT '默认分成', 'per_amount', 1000.00, 2.00, 7, 2, 0
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `t_referral_commission_rule` WHERE `IsDeleted` = 0 LIMIT 1);

View File

@ -1,3 +0,0 @@
-- 批量任务明细:增加客户单号字段(Excel 导入/小包创建反馈用)
ALTER TABLE `t_order_batch_job_item`
ADD COLUMN `ClientNo` VARCHAR(64) NULL COMMENT '客户单号' AFTER `ServiceNo`;

View File

@ -1,12 +0,0 @@
-- 订单状态:待排货(入库排货/渠道买价未成功)
-- 可重复执行(INSERT IGNORE)
INSERT IGNORE INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `CreateUserId`)
VALUES ('order_status', 35, 'pendingAllocation', '待排货', 0);
-- 历史数据:有排货失败原因但状态仍为待支付的订单,修正为待排货
UPDATE `t_order`
SET `OrderStatus` = 'pendingAllocation',
`UpdateTime` = NOW()
WHERE TRIM(IFNULL(`AllocationPendingReason`, '')) <> ''
AND `OrderStatus` = 'pendingPayment';

View File

@ -1,123 +0,0 @@
-- 仓储表 C_* → c_*(小写 c 前缀)
-- 不依赖 DELIMITER / 存储过程,Navicat、DMS、mysql 命令行均可整段执行
-- 可重复执行
--
-- 说明:
-- 1) 若库已开启 lower_case_table_names(Windows/部分云库),建表时 C_sku 磁盘上已是 c_sku,
-- 无需 RENAME,部署新代码即可;下方「诊断」应只看到 c_*。
-- 2) Linux 且区分大小写时,须经临时表名两步改名,否则可能报 Table 'c_sku' already exists。
-- 3) 执行前请确认已 USE 到业务库(DATABASE() 不能为 NULL)。
-- ========== 诊断(先看当前表名)==========
SELECT TABLE_NAME AS warehouse_table
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND (
TABLE_NAME LIKE 'C\_%' ESCAPE '\\'
OR TABLE_NAME LIKE 'c\_%' ESCAPE '\\'
OR TABLE_NAME LIKE '\_\_oms\_ren\_%' ESCAPE '\\'
)
ORDER BY TABLE_NAME;
-- ========== 单表改名:大写 C_ 存在 → 临时名 → c_* ==========
-- c_sku
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_sku');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_sku` TO `__oms_ren_sku`', 'SELECT ''skip: C_sku'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_sku');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_sku` TO `c_sku`', 'SELECT ''skip: __oms_ren_sku'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_bin_location
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_bin_location');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_bin_location` TO `__oms_ren_bin_location`', 'SELECT ''skip: C_bin_location'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_bin_location');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_bin_location` TO `c_bin_location`', 'SELECT ''skip: __oms_ren_bin_location'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_inventory
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_inventory');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_inventory` TO `__oms_ren_inventory`', 'SELECT ''skip: C_inventory'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_inventory');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_inventory` TO `c_inventory`', 'SELECT ''skip: __oms_ren_inventory'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_inbound_notice
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_inbound_notice');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_inbound_notice` TO `__oms_ren_inbound_notice`', 'SELECT ''skip: C_inbound_notice'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_inbound_notice');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_inbound_notice` TO `c_inbound_notice`', 'SELECT ''skip: __oms_ren_inbound_notice'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_inbound_box
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_inbound_box');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_inbound_box` TO `__oms_ren_inbound_box`', 'SELECT ''skip: C_inbound_box'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_inbound_box');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_inbound_box` TO `c_inbound_box`', 'SELECT ''skip: __oms_ren_inbound_box'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_inbound_box_line
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_inbound_box_line');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_inbound_box_line` TO `__oms_ren_inbound_box_line`', 'SELECT ''skip: C_inbound_box_line'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_inbound_box_line');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_inbound_box_line` TO `c_inbound_box_line`', 'SELECT ''skip: __oms_ren_inbound_box_line'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_parcel
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_parcel');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_parcel` TO `__oms_ren_parcel`', 'SELECT ''skip: C_parcel'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_parcel');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_parcel` TO `c_parcel`', 'SELECT ''skip: __oms_ren_parcel'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_parcel_arrival
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_parcel_arrival');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_parcel_arrival` TO `__oms_ren_parcel_arrival`', 'SELECT ''skip: C_parcel_arrival'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_parcel_arrival');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_parcel_arrival` TO `c_parcel_arrival`', 'SELECT ''skip: __oms_ren_parcel_arrival'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_parcel_item
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_parcel_item');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_parcel_item` TO `__oms_ren_parcel_item`', 'SELECT ''skip: C_parcel_item'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_parcel_item');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_parcel_item` TO `c_parcel_item`', 'SELECT ''skip: __oms_ren_parcel_item'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- c_sku_platform_map
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND BINARY TABLE_NAME = 'C_sku_platform_map');
SET @sql := IF(@need > 0, 'RENAME TABLE `C_sku_platform_map` TO `__oms_ren_sku_platform_map`', 'SELECT ''skip: C_sku_platform_map'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
SET @need := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '__oms_ren_sku_platform_map');
SET @sql := IF(@need > 0, 'RENAME TABLE `__oms_ren_sku_platform_map` TO `c_sku_platform_map`', 'SELECT ''skip: __oms_ren_sku_platform_map'' AS info');
PREPARE _s FROM @sql; EXECUTE _s; DEALLOCATE PREPARE _s;
-- ========== 执行后复查 ==========
SELECT TABLE_NAME AS warehouse_table_after
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME LIKE 'c\_%' ESCAPE '\\'
ORDER BY TABLE_NAME;
-- ========== 若上面全是 skip 且已是 c_*:无需再改,直接部署后端即可 ==========
-- 若仍残留 C_*(区分大小写 Linux),可手工一次性执行(会失败则勿重复跑):
-- RENAME TABLE
-- `C_sku` TO `__oms_ren_sku`, `C_bin_location` TO `__oms_ren_bin_location`,
-- `C_inventory` TO `__oms_ren_inventory`, `C_inbound_notice` TO `__oms_ren_inbound_notice`,
-- `C_inbound_box` TO `__oms_ren_inbound_box`, `C_inbound_box_line` TO `__oms_ren_inbound_box_line`,
-- `C_parcel` TO `__oms_ren_parcel`, `C_parcel_arrival` TO `__oms_ren_parcel_arrival`,
-- `C_parcel_item` TO `__oms_ren_parcel_item`, `C_sku_platform_map` TO `__oms_ren_sku_platform_map`;
-- RENAME TABLE
-- `__oms_ren_sku` TO `c_sku`, `__oms_ren_bin_location` TO `c_bin_location`,
-- `__oms_ren_inventory` TO `c_inventory`, `__oms_ren_inbound_notice` TO `c_inbound_notice`,
-- `__oms_ren_inbound_box` TO `c_inbound_box`, `__oms_ren_inbound_box_line` TO `c_inbound_box_line`,
-- `__oms_ren_parcel` TO `c_parcel`, `__oms_ren_parcel_arrival` TO `c_parcel_arrival`,
-- `__oms_ren_parcel_item` TO `c_parcel_item`, `__oms_ren_sku_platform_map` TO `c_sku_platform_map`;

View File

@ -1,34 +0,0 @@
-- 已有库增量:客户地址簿 t_customer_sender_address(发件/退件/收件)
-- 对应功能:客户 → 地址管理;揽收订单选发件地址
-- 可重复执行(IF NOT EXISTS)
CREATE TABLE IF NOT EXISTS `t_customer_sender_address` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '归属客户 t_customer.Id',
`AddressType` VARCHAR(16) NOT NULL DEFAULT 'sender' COMMENT '地址类型:sender发件/return退件/receiver收件',
`AddressName` VARCHAR(64) NULL COMMENT '地址别名',
`SenderName` VARCHAR(50) NOT NULL COMMENT '联系人姓名',
`Company` VARCHAR(255) NULL COMMENT '公司',
`Country` INT NULL COMMENT '国家 t_country.Id',
`Province` VARCHAR(50) NULL COMMENT '州省',
`City` VARCHAR(50) NULL COMMENT '城市',
`ZipCode` VARCHAR(50) NULL COMMENT '邮编',
`StreetAddress1` VARCHAR(500) NOT NULL COMMENT '地址1',
`StreetAddress2` VARCHAR(500) NULL COMMENT '地址2',
`StreetAddress3` VARCHAR(500) NULL COMMENT '地址3',
`HouseNumber` VARCHAR(50) NULL COMMENT '门牌号',
`Phone` VARCHAR(50) NULL COMMENT '手机',
`TelePhone` VARCHAR(50) NULL COMMENT '电话',
`Email` VARCHAR(60) NULL COMMENT '邮箱',
`IsDefault` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认:0-否,1-是(同客户同类型仅一条)',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
KEY `idx_csa_customer` (`CustomerId`, `IsDeleted`, `Id`),
KEY `idx_csa_customer_type` (`CustomerId`, `AddressType`, `IsDeleted`, `Id`),
KEY `idx_csa_customer_default` (`CustomerId`, `AddressType`, `IsDefault`)
) COMMENT='客户地址簿(发件/退件/收件)';

View File

@ -1,41 +0,0 @@
-- 已有库增量:客户地址簿增加 AddressType(发件/退件/收件)
-- sender=发件,return=退件,receiver=收件;存量默认 sender
-- 可重复执行
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_customer_sender_address'
AND COLUMN_NAME = 'AddressType'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_customer_sender_address` ADD COLUMN `AddressType` VARCHAR(16) NOT NULL DEFAULT ''sender'' COMMENT ''地址类型:sender发件/return退件/receiver收件'' AFTER `CustomerId`',
'SELECT ''t_customer_sender_address.AddressType already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_customer_sender_address`
SET `AddressType` = 'sender'
WHERE `AddressType` IS NULL OR `AddressType` = '';
SET @idx_exists := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_customer_sender_address'
AND INDEX_NAME = 'idx_csa_customer_type'
);
SET @sql := IF(
@idx_exists = 0,
'ALTER TABLE `t_customer_sender_address` ADD KEY `idx_csa_customer_type` (`CustomerId`, `AddressType`, `IsDeleted`, `Id`)',
'SELECT ''idx_csa_customer_type already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -1,90 +0,0 @@
-- 已有库增量:字典类型元数据 t_dict_type(类型编码 + 备注 + 排序)
-- 对应功能:系统 → 数据字典,左侧类型列表与类型备注
-- 可重复执行(IF NOT EXISTS / INSERT IGNORE;预置名称直接覆盖 Remark、TypeSort)
CREATE TABLE IF NOT EXISTS `t_dict_type` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,步长 1',
`DictType` VARCHAR(64) NOT NULL COMMENT '字典类型编码,与 t_dict.DictType 对应',
`Remark` VARCHAR(512) NULL COMMENT '字典类型备注',
`TypeSort` INT NOT NULL DEFAULT 0 COMMENT '左侧菜单排序,值越小越靠前',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`UpdateUserId` INT NULL COMMENT '更新人',
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
UNIQUE KEY `uk_dict_type_code` (`DictType`)
) COMMENT='字典类型元数据表';
-- 从已有字典项回填类型(无备注)
INSERT IGNORE INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `CreateUserId`)
SELECT DISTINCT `DictType`, NULL, 0, 0
FROM `t_dict`
WHERE `IsDeleted` = 0 AND TRIM(`DictType`) <> '';
-- 预置 OMS 字典类型中文名称(与前端 dict-batch-import.ts OMS_DICT_TYPE_REMARKS 对齐)
-- 已存在记录时直接覆盖 Remark、TypeSort
INSERT INTO `t_dict_type` (`DictType`, `TypeSort`, `Remark`, `CreateUserId`)
VALUES
('area_type', 1, '分区类型', 0),
('auto_trigger_operation', 2, '自动触发操作', 0),
('auto_trigger_timing', 3, '自动触发时机', 0),
('billable_weight', 4, '计重取值', 0),
('calculation_formula', 5, '计算公式', 0),
('cargo_attribute', 6, '货物属性', 0),
('claim_type', 7, '理赔类型', 0),
('cost_unit', 8, '计费单位', 0),
('currency', 9, '币种', 0),
('customer_account_flow_biz_type', 10, '账户流水业务类型', 0),
('customer_address_type', 11, '客户地址类型', 0),
('customer_group', 12, '客户组', 0),
('customer_status', 13, '客户状态', 0),
('customer_waybill', 14, '客户面单', 0),
('data_scope', 15, '数据范围', 0),
('enable_status', 16, '启用状态', 0),
('es_snapshot_status', 17, '快照生效状态', 0),
('export_customs_mode', 18, '出口报关方式', 0),
('fee_item', 19, '费用项', 0),
('forecast_type', 20, '预报类型', 0),
('issue_notify_method', 21, '问题通知方式', 0),
('issue_type', 22, '问题件类型', 0),
('label_generate_status', 23, '面单生成状态', 0),
('label_template_type', 24, '面单模板类型', 0),
('notice_type', 25, '通知类型', 0),
('number_type', 26, '打印号码类型', 0),
('operator_type', 27, '发布操作类型', 0),
('order_batch_job_item_status', 28, '批量任务明细状态', 0),
('order_batch_job_status', 29, '批量任务状态', 0),
('order_fee_charging_stage', 30, '费用计费阶段', 0),
('order_fee_type', 31, '费用类型', 0),
('order_handle_status', 32, '订单处理子状态', 0),
('order_source', 33, '订单来源', 0),
('order_status', 34, '订单状态', 0),
('order_stock_bind_status', 35, '库存绑定状态', 0),
('order_stock_bind_type', 36, '库存绑定类型', 0),
('order_task_status', 37, '订单导入任务状态', 0),
('order_template_mapping_status', 38, '模板映射状态', 0),
('order_template_type', 39, '订单模板类型', 0),
('order_weight_type', 40, '件重尺类型', 0),
('org_scope', 41, '组织范围', 0),
('pay_type', 42, '支付方式', 0),
('pricing_factor', 43, '定价因素', 0),
('principal_type', 44, '主体类型', 0),
('publish_record_type', 45, '发布记录类型', 0),
('receiver_type', 46, '接收方类型', 0),
('recharge_status', 47, '充值审核状态', 0),
('sales_product_group', 48, '卖价分组', 0),
('sales_store_auth_status', 49, '店铺授权状态', 0),
('sales_store_fulfill_status', 50, '平台店铺履约状态', 0),
('scheme', 51, '排货方案', 0),
('secret_level', 52, '密级', 0),
('warehouse_inbound_type', 53, '入库类型', 0),
('warehouse_parcel_lifecycle', 54, '包裹生命周期', 0),
('warehouse_receipt_status', 55, '收货状态', 0),
('warehouse_type', 56, '仓库类型', 0),
('weight_rule_condition', 57, '计重条件', 0),
('yes_no', 58, '通用是否', 0)
ON DUPLICATE KEY UPDATE
`TypeSort` = VALUES(`TypeSort`),
`Remark` = VALUES(`Remark`);

View File

@ -1,3 +0,0 @@
-- 待排货原因:入库排货/买价未成功时记录,便于运营补配置后重算应付
ALTER TABLE `t_order`
ADD COLUMN `AllocationPendingReason` VARCHAR(512) NULL COMMENT '待排货原因(排货或渠道买价未匹配)' AFTER `PredeclareFailReason`;

View File

@ -1,75 +0,0 @@
-- 已有库增量:t_order 揽收订单字段 OrderSource / PickupTime / CustomerSenderAddressId
-- 对应功能:订单 → 揽收订单;见 oms系统设计-050801.md §4.2.1 / §4.4.4
-- 可重复执行
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'OrderSource'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `OrderSource` VARCHAR(32) NULL COMMENT ''订单来源:manual/pickup/upload/batch/platform'' AFTER `CustomerId`',
'SELECT ''t_order.OrderSource already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'PickupTime'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `PickupTime` DATETIME NULL COMMENT ''预约揽收时间'' AFTER `OrderSource`',
'SELECT ''t_order.PickupTime already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col_exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'CustomerSenderAddressId'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order` ADD COLUMN `CustomerSenderAddressId` INT NULL COMMENT ''发件地址簿 Id'' AFTER `PickupTime`',
'SELECT ''t_order.CustomerSenderAddressId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_order`
SET `OrderSource` = 'manual'
WHERE `OrderSource` IS NULL OR `OrderSource` = '';
SET @idx_exists := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 't_order'
AND INDEX_NAME = 'idx_ord_source_ctime'
);
SET @sql := IF(
@idx_exists = 0,
'ALTER TABLE `t_order` ADD KEY `idx_ord_source_ctime` (`OrderSource`, `CreateTime`)',
'SELECT ''idx_ord_source_ctime already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -1,120 +0,0 @@
-- 物流单绑定包裹/库存:t_order_stock_bind + c_inventory/c_parcel_item 冻结与可用字段
-- 见 oms系统设计-050801.md §4.2.11 / §4.4.6 / §3.1.2
-- ConcurrencyStamp 默认用固定 GUID,避免 DEFAULT (UUID()) 触发复制 1674
-- 可重复执行
CREATE TABLE IF NOT EXISTS `t_order_stock_bind` (
`Id` INT NOT NULL AUTO_INCREMENT,
`OrderId` INT NOT NULL COMMENT '物流单 t_order.Id',
`CustomerId` INT NOT NULL COMMENT '客户隔离键',
`WarehouseId` INT NOT NULL COMMENT '仓库',
`BindType` VARCHAR(20) NOT NULL COMMENT 'inventory / parcelItem',
`SkuId` INT NULL COMMENT 'SKU',
`InventoryId` INT NULL COMMENT 'c_inventory.Id,可空=按SKU分摊',
`BinLocationId` INT NULL COMMENT '库位快照',
`ParcelId` INT NULL COMMENT 'c_parcel.Id',
`ParcelItemId` INT NULL COMMENT 'c_parcel_item.Id',
`BindQty` INT NOT NULL COMMENT '绑定数量',
`Status` VARCHAR(20) NOT NULL DEFAULT 'bound' COMMENT 'bound/deducted/released',
`SkuCode` VARCHAR(64) NULL,
`SkuName` VARCHAR(128) NULL,
`TrackingNo` VARCHAR(64) NULL,
`ProductName` VARCHAR(256) NULL,
`ImageUrl` VARCHAR(512) NULL,
`AttributeText` VARCHAR(256) NULL,
`Remark` VARCHAR(512) NULL,
`IsDeleted` TINYINT NOT NULL DEFAULT 0,
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
`CreateUserId` INT NOT NULL,
`CreateTime` DATETIME NOT NULL,
`UpdateUserId` INT NULL,
`UpdateTime` DATETIME NULL,
PRIMARY KEY (`Id`),
KEY `idx_osb_order` (`OrderId`, `IsDeleted`, `Status`),
KEY `idx_osb_parcel_item` (`ParcelItemId`, `Status`, `IsDeleted`),
KEY `idx_osb_sku` (`CustomerId`, `WarehouseId`, `SkuId`, `Status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流单仓储绑定明细';
-- 已建表但缺 ConcurrencyStamp 时补列(可重复执行)
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order_stock_bind' AND COLUMN_NAME = 'ConcurrencyStamp'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `t_order_stock_bind` ADD COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT ''00000000-0000-0000-0000-000000000000'' COMMENT ''并发戳'' AFTER `IsDeleted`',
'SELECT ''t_order_stock_bind.ConcurrencyStamp already exists'' AS info'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- c_inventory.FrozenQty(兼容尚未 rename 的旧表名 C_inventory)
SET @inv_table := IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'c_inventory'),
'c_inventory',
IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_inventory'),
'C_inventory',
NULL
)
);
SET @col_exists := IF(@inv_table IS NULL, 1,
(SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @inv_table AND COLUMN_NAME = 'FrozenQty')
);
SET @sql := IF(@inv_table IS NULL OR @col_exists > 0,
'SELECT ''c_inventory.FrozenQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @inv_table, '` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Qty`')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := IF(@inv_table IS NULL, 1,
(SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @inv_table AND COLUMN_NAME = 'AvailableQty')
);
SET @sql := IF(@inv_table IS NULL OR @col_exists > 0,
'SELECT ''c_inventory.AvailableQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @inv_table, '` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Qty-FrozenQty'' AFTER `FrozenQty`')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @sql := IF(@inv_table IS NULL,
'SELECT ''c_inventory table missing'' AS info',
CONCAT('UPDATE `', @inv_table, '` SET `AvailableQty` = GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) WHERE `AvailableQty` <> GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) OR (`AvailableQty` = 0 AND `Qty` > 0 AND IFNULL(`FrozenQty`, 0) = 0)')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- c_parcel_item.FrozenQty
SET @pi_table := IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'c_parcel_item'),
'c_parcel_item',
IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_parcel_item'),
'C_parcel_item',
NULL
)
);
SET @col_exists := IF(@pi_table IS NULL, 1,
(SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @pi_table AND COLUMN_NAME = 'FrozenQty')
);
SET @sql := IF(@pi_table IS NULL OR @col_exists > 0,
'SELECT ''c_parcel_item.FrozenQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @pi_table, '` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Quantity`')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := IF(@pi_table IS NULL, 1,
(SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @pi_table AND COLUMN_NAME = 'AvailableQty')
);
SET @sql := IF(@pi_table IS NULL OR @col_exists > 0,
'SELECT ''c_parcel_item.AvailableQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @pi_table, '` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Quantity-FrozenQty'' AFTER `FrozenQty`')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @sql := IF(@pi_table IS NULL,
'SELECT ''c_parcel_item table missing'' AS info',
CONCAT('UPDATE `', @pi_table, '` SET `AvailableQty` = GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) WHERE `AvailableQty` <> GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) OR (`AvailableQty` = 0 AND `Quantity` > 0 AND IFNULL(`FrozenQty`, 0) = 0)')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -1,32 +0,0 @@
-- 店铺订单补收件人字段(可重复执行)
-- 用法: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;

View File

@ -1,621 +0,0 @@
-- 营销模块种子(CustomerId=9 引荐人主客户,CustomerId=8 被引荐下级)
-- 可重复执行:已存在则跳过 / 幂等更新
--
-- 用法:
-- 1) mysql -u root -p oms < sql/migrate_marketing_module.sql
-- 2) mysql -u root -p oms < sql/seed_marketing_customer9.sql
--
-- 覆盖页面:
-- 管理端:会员等级 / 积分规则 / 积分流水 / 优惠券模板 / 发券实例 /
-- 分成规则 / 分成明细 / 提现审核 / 引荐关系
-- 门户端:邀请好友 / 我的等级 / 我的优惠券 / 推荐收益
--
-- 约定:此处「子」= 引荐下级(ReferrerCustomerId),非 ParentCustomerId 子账号。
-- 8、9 均须为主客户(ParentCustomerId IS NULL),营销权益才挂得上。
SET NAMES utf8mb4;
SET @referrerId := 9;
SET @refereeId := 8;
-- ---------------------------------------------------------------------------
-- 0. 前置校验
-- ---------------------------------------------------------------------------
SET @seed_err := (
SELECT
CASE
WHEN NOT EXISTS (SELECT 1 FROM `t_customer` WHERE `Id` = @referrerId AND `IsDeleted` = 0)
THEN CONCAT('缺少客户 Id=', @referrerId)
WHEN NOT EXISTS (SELECT 1 FROM `t_customer` WHERE `Id` = @refereeId AND `IsDeleted` = 0)
THEN CONCAT('缺少客户 Id=', @refereeId)
WHEN EXISTS (
SELECT 1 FROM `t_customer`
WHERE `Id` = @referrerId AND `ParentCustomerId` IS NOT NULL AND `IsDeleted` = 0
)
THEN CONCAT('客户 Id=', @referrerId, ' 不是主客户(存在 ParentCustomerId)')
WHEN EXISTS (
SELECT 1 FROM `t_customer`
WHERE `Id` = @refereeId AND `ParentCustomerId` IS NOT NULL AND `IsDeleted` = 0
)
THEN CONCAT('客户 Id=', @refereeId, ' 不是主客户(存在 ParentCustomerId);请先改为主客户再跑种子')
ELSE NULL
END
);
SELECT IFNULL(@seed_err, 'OK') AS seed_precheck;
-- 校验失败则中止,避免写脏数据
SET @__abort := IF(@seed_err IS NULL, 'SELECT 1', CONCAT('SIGNAL SQLSTATE ''45000'' SET MESSAGE_TEXT = ''', REPLACE(@seed_err, '''', ''), ''''));
PREPARE abort_stmt FROM @__abort;
EXECUTE abort_stmt;
DEALLOCATE PREPARE abort_stmt;
-- ---------------------------------------------------------------------------
-- 1. 会员等级(migrate 已种子;此处确保存在并取 Id)
-- ---------------------------------------------------------------------------
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `PriceTierKey`, `ExtraDiscountRate`, `Sort`, `BenefitDesc`, `Status`, `CreateUserId`)
VALUES
('normal', '普通会员', 0, '普通客户', 0.0000, 10, '标准运价', 2, 0),
('silver', '银卡会员', 5000, 'VIP1', 0.0000, 20, 'VIP1 价区', 2, 0),
('gold', '金卡会员', 20000, 'VIP2', 0.0100, 30, 'VIP2 价区 + 额外 1% 折', 2, 0),
('diamond', '钻石会员', 100000, 'VIP3', 0.0200, 40, 'VIP3 价区 + 额外 2% 折', 2, 0)
ON DUPLICATE KEY UPDATE
`LevelName` = VALUES(`LevelName`),
`MinPoints` = VALUES(`MinPoints`),
`PriceTierKey` = VALUES(`PriceTierKey`),
`ExtraDiscountRate` = VALUES(`ExtraDiscountRate`),
`Sort` = VALUES(`Sort`),
`BenefitDesc` = VALUES(`BenefitDesc`),
`Status` = 2,
`IsDeleted` = 0;
SET @levelNormal := (SELECT `Id` FROM `t_member_level` WHERE `LevelCode` = 'normal' AND `IsDeleted` = 0 LIMIT 1);
SET @levelSilver := (SELECT `Id` FROM `t_member_level` WHERE `LevelCode` = 'silver' AND `IsDeleted` = 0 LIMIT 1);
SET @levelGold := (SELECT `Id` FROM `t_member_level` WHERE `LevelCode` = 'gold' AND `IsDeleted` = 0 LIMIT 1);
-- ---------------------------------------------------------------------------
-- 2. 积分规则 / 分成规则(全局,缺则补)
-- ---------------------------------------------------------------------------
INSERT INTO `t_member_points_rule`
(`RuleName`, `PointsPerYuan`, `FirstOrderMultiplier`, `MaxPointsPerOrder`, `Status`, `CreateUserId`)
SELECT '默认积分规则', 1.0000, 1.50, 10000, 2, 0
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `t_member_points_rule` WHERE `IsDeleted` = 0 LIMIT 1);
UPDATE `t_member_points_rule`
SET `RuleName` = '默认积分规则',
`PointsPerYuan` = 1.0000,
`FirstOrderMultiplier` = 1.50,
`MaxPointsPerOrder` = 10000,
`Status` = 2,
`UpdateTime` = NOW()
WHERE `Id` = (
SELECT `Id` FROM (
SELECT `Id` FROM `t_member_points_rule` WHERE `IsDeleted` = 0 ORDER BY `Id` LIMIT 1
) AS t
);
INSERT INTO `t_referral_commission_rule`
(`RuleName`, `CommissionMode`, `BaseAmount`, `CommissionAmount`, `FreezeDays`, `SettleTriggerOrderStatus`, `MonthlyCap`, `Status`, `CreateUserId`)
SELECT '默认分成', 'per_amount', 1000.00, 2.00, 7, 'deliverySuccess', NULL, 2, 0
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `t_referral_commission_rule` WHERE `IsDeleted` = 0 LIMIT 1);
UPDATE `t_referral_commission_rule`
SET `RuleName` = '默认分成',
`CommissionMode` = 'per_amount',
`BaseAmount` = 1000.00,
`CommissionAmount` = 2.00,
`FreezeDays` = 7,
`SettleTriggerOrderStatus` = 'deliverySuccess',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `Id` = (
SELECT `Id` FROM (
SELECT `Id` FROM `t_referral_commission_rule` WHERE `IsDeleted` = 0 ORDER BY `Id` LIMIT 1
) AS t
);
-- ---------------------------------------------------------------------------
-- 3. 客户:引荐关系 + 等级 + 佣金余额(9 引荐 8)
-- ---------------------------------------------------------------------------
UPDATE `t_customer`
SET
`ReferrerCustomerId` = NULL,
`ReferralBindTime` = NULL,
`ReferralChannel` = NULL,
`MemberLevelId` = @levelGold,
`CommissionBalance` = 128.60,
`UpdateTime` = NOW()
WHERE `Id` = @referrerId AND `IsDeleted` = 0 AND `ParentCustomerId` IS NULL;
UPDATE `t_customer`
SET
`ReferrerCustomerId` = @referrerId,
`ReferralBindTime` = DATE_SUB(NOW(), INTERVAL 45 DAY),
`ReferralChannel` = 'link',
`MemberLevelId` = @levelSilver,
`CommissionBalance` = 0.00,
`UpdateTime` = NOW()
WHERE `Id` = @refereeId AND `IsDeleted` = 0 AND `ParentCustomerId` IS NULL;
-- ---------------------------------------------------------------------------
-- 4. 引荐码(邀请好友页)
-- ---------------------------------------------------------------------------
INSERT INTO `t_customer_referral_code`
(`CustomerId`, `InviteCode`, `Status`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 'SEED9INV', 1, 0, UUID(), 0, NOW()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_referral_code`
WHERE `CustomerId` = @referrerId AND `IsDeleted` = 0
);
INSERT INTO `t_customer_referral_code`
(`CustomerId`, `InviteCode`, `Status`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @refereeId, 'SEED8INV', 1, 0, UUID(), 0, NOW()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_referral_code`
WHERE `CustomerId` = @refereeId AND `IsDeleted` = 0
);
UPDATE `t_customer_referral_code`
SET `InviteCode` = 'SEED9INV', `Status` = 1, `IsDeleted` = 0, `UpdateTime` = NOW()
WHERE `CustomerId` = @referrerId AND `IsDeleted` = 0;
UPDATE `t_customer_referral_code`
SET `InviteCode` = 'SEED8INV', `Status` = 1, `IsDeleted` = 0, `UpdateTime` = NOW()
WHERE `CustomerId` = @refereeId AND `IsDeleted` = 0;
-- ---------------------------------------------------------------------------
-- 5. 积分账户 + 流水(积分流水 / 我的等级)
-- ---------------------------------------------------------------------------
INSERT INTO `t_customer_points_account`
(`CustomerId`, `TotalPoints`, `AvailablePoints`, `Version`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 26800, 24500, 0, 0, UUID(), 0, NOW()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_account` WHERE `CustomerId` = @referrerId AND `IsDeleted` = 0
);
UPDATE `t_customer_points_account`
SET `TotalPoints` = 26800, `AvailablePoints` = 24500, `UpdateTime` = NOW()
WHERE `CustomerId` = @referrerId AND `IsDeleted` = 0;
INSERT INTO `t_customer_points_account`
(`CustomerId`, `TotalPoints`, `AvailablePoints`, `Version`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @refereeId, 6200, 5800, 0, 0, UUID(), 0, NOW()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_account` WHERE `CustomerId` = @refereeId AND `IsDeleted` = 0
);
UPDATE `t_customer_points_account`
SET `TotalPoints` = 6200, `AvailablePoints` = 5800, `UpdateTime` = NOW()
WHERE `CustomerId` = @refereeId AND `IsDeleted` = 0;
-- 9:赚取 / 消耗 / 调账
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 1, 1200, 'orderPay', 9009001, 'SEED-MKT-ORD-9001', '种子:支付赚取积分', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 20 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @referrerId AND `BizNo` = 'SEED-MKT-ORD-9001' AND `IsDeleted` = 0
);
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 1, 800, 'orderPay', 9009002, 'SEED-MKT-ORD-9002', '种子:支付赚取积分', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 12 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @referrerId AND `BizNo` = 'SEED-MKT-ORD-9002' AND `IsDeleted` = 0
);
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 2, -300, 'orderPay', 9009003, 'SEED-MKT-ORD-9003', '种子:下单抵扣积分', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 8 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @referrerId AND `BizNo` = 'SEED-MKT-ORD-9003' AND `IsDeleted` = 0
);
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @referrerId, 3, 500, 'adminAdjust', NULL, 'SEED-MKT-ADJ-9001', '种子:人工调账加分(演示用不少于十字原因)', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 3 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @referrerId AND `BizNo` = 'SEED-MKT-ADJ-9001' AND `IsDeleted` = 0
);
-- 8:赚取 / 退款追回
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @refereeId, 1, 1500, 'orderPay', 9008001, 'SEED-MKT-ORD-8001', '种子:被引荐人支付赚取', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 30 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @refereeId AND `BizNo` = 'SEED-MKT-ORD-8001' AND `IsDeleted` = 0
);
INSERT INTO `t_customer_points_flow`
(`CustomerId`, `ChangeType`, `Points`, `BizType`, `BizId`, `BizNo`, `Remark`, `IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`)
SELECT @refereeId, 4, -200, 'refund', 9008001, 'SEED-MKT-RF-8001', '种子:退款积分追回', 0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 25 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_customer_points_flow`
WHERE `CustomerId` = @refereeId AND `BizNo` = 'SEED-MKT-RF-8001' AND `IsDeleted` = 0
);
-- ---------------------------------------------------------------------------
-- 6. 优惠券模板(模板页)
-- ---------------------------------------------------------------------------
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'SEED-FIXED-20', '种子满减券20元', 'fixed_off', 20.00, NULL, NULL,
100.00, 1000, 0, 2,
1, DATE_SUB(NOW(), INTERVAL 7 DAY), DATE_ADD(NOW(), INTERVAL 180 DAY), NULL,
NULL, 'manual', 1, '种子:满100减20',
0, UUID(), 0, NOW()
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-FIXED-20' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'SEED-PCT-95', '种子95折券', 'percent_off', NULL, 0.9500, 50.00,
50.00, 500, 0, 1,
2, NULL, NULL, 30,
NULL, 'referral', 1, '种子:领取后30天有效,封顶50',
0, UUID(), 0, NOW()
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-PCT-95' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'SEED-SHIP-10', '种子运费抵扣10元', 'shipping_off', 10.00, NULL, NULL,
0.00, 200, 0, 3,
1, DATE_SUB(NOW(), INTERVAL 3 DAY), DATE_ADD(NOW(), INTERVAL 90 DAY), NULL,
NULL, 'activity', 1, '种子:运费抵扣',
0, UUID(), 0, NOW()
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-SHIP-10' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'SEED-DRAFT-OFF', '种子草稿立减券', 'direct_off', 5.00, NULL, NULL,
0.00, 0, 0, 1,
1, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), NULL,
NULL, 'manual', 0, '种子:草稿态模板',
0, UUID(), 0, NOW()
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-DRAFT-OFF' AND `IsDeleted` = 0
);
SET @tplFixed := (SELECT `Id` FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-FIXED-20' AND `IsDeleted` = 0 LIMIT 1);
SET @tplPct := (SELECT `Id` FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-PCT-95' AND `IsDeleted` = 0 LIMIT 1);
SET @tplShip := (SELECT `Id` FROM `t_coupon_template` WHERE `TemplateCode` = 'SEED-SHIP-10' AND `IsDeleted` = 0 LIMIT 1);
-- ---------------------------------------------------------------------------
-- 7. 优惠券实例(发券列表 / 我的优惠券)
-- ---------------------------------------------------------------------------
-- 9:可用 / 已用 / 过期
INSERT INTO `t_coupon_instance` (
`TemplateId`, `CustomerId`, `CouponCode`, `Status`,
`ReceiveTime`, `ValidStartTime`, `ValidEndTime`, `UseTime`, `OrderId`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @tplFixed, @referrerId, 'SEED9-FIXED-AVAIL-01', 1,
DATE_SUB(NOW(), INTERVAL 5 DAY), DATE_SUB(NOW(), INTERVAL 7 DAY), DATE_ADD(NOW(), INTERVAL 180 DAY), NULL, NULL,
0, UUID(), 0, NOW()
FROM DUAL WHERE @tplFixed IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM `t_coupon_instance` WHERE `CouponCode` = 'SEED9-FIXED-AVAIL-01' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_instance` (
`TemplateId`, `CustomerId`, `CouponCode`, `Status`,
`ReceiveTime`, `ValidStartTime`, `ValidEndTime`, `UseTime`, `OrderId`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @tplPct, @referrerId, 'SEED9-PCT-USED-01', 3,
DATE_SUB(NOW(), INTERVAL 40 DAY), DATE_SUB(NOW(), INTERVAL 40 DAY), DATE_SUB(NOW(), INTERVAL 10 DAY),
DATE_SUB(NOW(), INTERVAL 15 DAY), 9009002,
0, UUID(), 0, NOW()
FROM DUAL WHERE @tplPct IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM `t_coupon_instance` WHERE `CouponCode` = 'SEED9-PCT-USED-01' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_instance` (
`TemplateId`, `CustomerId`, `CouponCode`, `Status`,
`ReceiveTime`, `ValidStartTime`, `ValidEndTime`, `UseTime`, `OrderId`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @tplShip, @referrerId, 'SEED9-SHIP-EXP-01', 4,
DATE_SUB(NOW(), INTERVAL 60 DAY), DATE_SUB(NOW(), INTERVAL 60 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), NULL, NULL,
0, UUID(), 0, NOW()
FROM DUAL WHERE @tplShip IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM `t_coupon_instance` WHERE `CouponCode` = 'SEED9-SHIP-EXP-01' AND `IsDeleted` = 0
);
-- 8:可用 / 锁定
INSERT INTO `t_coupon_instance` (
`TemplateId`, `CustomerId`, `CouponCode`, `Status`,
`ReceiveTime`, `ValidStartTime`, `ValidEndTime`, `UseTime`, `OrderId`, `LockExpireTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @tplFixed, @refereeId, 'SEED8-FIXED-AVAIL-01', 1,
DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 7 DAY), DATE_ADD(NOW(), INTERVAL 180 DAY), NULL, NULL, NULL,
0, UUID(), 0, NOW()
FROM DUAL WHERE @tplFixed IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM `t_coupon_instance` WHERE `CouponCode` = 'SEED8-FIXED-AVAIL-01' AND `IsDeleted` = 0
);
INSERT INTO `t_coupon_instance` (
`TemplateId`, `CustomerId`, `CouponCode`, `Status`,
`ReceiveTime`, `ValidStartTime`, `ValidEndTime`, `UseTime`, `OrderId`, `LockExpireTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @tplShip, @refereeId, 'SEED8-SHIP-LOCK-01', 2,
DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_SUB(NOW(), INTERVAL 3 DAY), DATE_ADD(NOW(), INTERVAL 90 DAY),
NULL, 9008002, DATE_ADD(NOW(), INTERVAL 30 MINUTE),
0, UUID(), 0, NOW()
FROM DUAL WHERE @tplShip IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM `t_coupon_instance` WHERE `CouponCode` = 'SEED8-SHIP-LOCK-01' AND `IsDeleted` = 0
);
-- 回写模板已发数量
UPDATE `t_coupon_template` t
SET t.`IssuedQuantity` = (
SELECT COUNT(*) FROM `t_coupon_instance` i
WHERE i.`TemplateId` = t.`Id` AND i.`IsDeleted` = 0
)
WHERE t.`TemplateCode` IN ('SEED-FIXED-20', 'SEED-PCT-95', 'SEED-SHIP-10', 'SEED-DRAFT-OFF')
AND t.`IsDeleted` = 0;
-- ---------------------------------------------------------------------------
-- 8. 分成明细(分成明细 / 推荐收益):引荐人=9,被引荐人=8
-- ---------------------------------------------------------------------------
SET @ruleSnap := JSON_OBJECT(
'RuleName', '默认分成',
'CommissionMode', 'per_amount',
'BaseAmount', 1000,
'CommissionAmount', 2,
'FreezeDays', 7
);
-- 待结算
INSERT INTO `t_referral_commission_detail` (
`ReferrerCustomerId`, `RefereeCustomerId`, `OrderId`, `OrderNo`,
`BasePayableAmount`, `CommissionAmount`, `Status`,
`EligibleTime`, `SettleTime`, `RuleSnapshotJson`, `CancelReason`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, @refereeId, 9008001, 'SEED-MKT-ORD-8001',
3500.00, 6.00, 1,
DATE_SUB(NOW(), INTERVAL 2 DAY), NULL, @ruleSnap, NULL,
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 2 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_referral_commission_detail`
WHERE `OrderId` = 9008001 AND `ReferrerCustomerId` = @referrerId AND `IsDeleted` = 0
);
-- 已结算
INSERT INTO `t_referral_commission_detail` (
`ReferrerCustomerId`, `RefereeCustomerId`, `OrderId`, `OrderNo`,
`BasePayableAmount`, `CommissionAmount`, `Status`,
`EligibleTime`, `SettleTime`, `RuleSnapshotJson`, `CancelReason`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, @refereeId, 9008002, 'SEED-MKT-ORD-8002',
5200.00, 10.00, 2,
DATE_SUB(NOW(), INTERVAL 20 DAY), DATE_SUB(NOW(), INTERVAL 13 DAY), @ruleSnap, NULL,
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 20 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_referral_commission_detail`
WHERE `OrderId` = 9008002 AND `ReferrerCustomerId` = @referrerId AND `IsDeleted` = 0
);
INSERT INTO `t_referral_commission_detail` (
`ReferrerCustomerId`, `RefereeCustomerId`, `OrderId`, `OrderNo`,
`BasePayableAmount`, `CommissionAmount`, `Status`,
`EligibleTime`, `SettleTime`, `RuleSnapshotJson`, `CancelReason`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, @refereeId, 9008003, 'SEED-MKT-ORD-8003',
1800.00, 2.00, 2,
DATE_SUB(NOW(), INTERVAL 35 DAY), DATE_SUB(NOW(), INTERVAL 28 DAY), @ruleSnap, NULL,
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 35 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_referral_commission_detail`
WHERE `OrderId` = 9008003 AND `ReferrerCustomerId` = @referrerId AND `IsDeleted` = 0
);
-- 已取消(人工调整演示)
INSERT INTO `t_referral_commission_detail` (
`ReferrerCustomerId`, `RefereeCustomerId`, `OrderId`, `OrderNo`,
`BasePayableAmount`, `CommissionAmount`, `Status`,
`EligibleTime`, `SettleTime`, `RuleSnapshotJson`, `CancelReason`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, @refereeId, 9008004, 'SEED-MKT-ORD-8004',
1000.00, 2.00, 3,
DATE_SUB(NOW(), INTERVAL 10 DAY), NULL, @ruleSnap, '种子:订单退款取消分成',
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 10 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_referral_commission_detail`
WHERE `OrderId` = 9008004 AND `ReferrerCustomerId` = @referrerId AND `IsDeleted` = 0
);
-- ---------------------------------------------------------------------------
-- 9. 提现申请(提现审核 / 推荐收益)
-- ---------------------------------------------------------------------------
INSERT INTO `t_commission_withdraw_request` (
`CustomerId`, `SerialNumber`, `Amount`, `WithdrawType`, `Status`,
`Remark`, `AuditRemark`, `AuditBy`, `AuditTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, 'SEED-WD-9-PENDING-01', 50.00, 1, 1,
'种子:申请提现到外部账户', NULL, NULL, NULL,
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 1 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_commission_withdraw_request`
WHERE `SerialNumber` = 'SEED-WD-9-PENDING-01' AND `IsDeleted` = 0
);
INSERT INTO `t_commission_withdraw_request` (
`CustomerId`, `SerialNumber`, `Amount`, `WithdrawType`, `Status`,
`Remark`, `AuditRemark`, `AuditBy`, `AuditTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, 'SEED-WD-9-APPROVED-01', 30.00, 2, 2,
'种子:转入余额', '审核通过-种子数据', 'admin', DATE_SUB(NOW(), INTERVAL 5 DAY),
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 6 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_commission_withdraw_request`
WHERE `SerialNumber` = 'SEED-WD-9-APPROVED-01' AND `IsDeleted` = 0
);
INSERT INTO `t_commission_withdraw_request` (
`CustomerId`, `SerialNumber`, `Amount`, `WithdrawType`, `Status`,
`Remark`, `AuditRemark`, `AuditBy`, `AuditTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, 'SEED-WD-9-REJECTED-01', 20.00, 1, 3,
'种子:提现申请', '驳回原因:收款信息不完整(种子)', 'admin', DATE_SUB(NOW(), INTERVAL 8 DAY),
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 9 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_commission_withdraw_request`
WHERE `SerialNumber` = 'SEED-WD-9-REJECTED-01' AND `IsDeleted` = 0
);
INSERT INTO `t_commission_withdraw_request` (
`CustomerId`, `SerialNumber`, `Amount`, `WithdrawType`, `Status`,
`Remark`, `AuditRemark`, `AuditBy`, `AuditTime`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT @referrerId, 'SEED-WD-9-PAID-01', 40.00, 1, 4,
'种子:已打款', '已打款-种子数据', 'admin', DATE_SUB(NOW(), INTERVAL 15 DAY),
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 16 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_commission_withdraw_request`
WHERE `SerialNumber` = 'SEED-WD-9-PAID-01' AND `IsDeleted` = 0
);
-- ---------------------------------------------------------------------------
-- 10. 审计日志(高危操作痕迹,便于联调)
-- ---------------------------------------------------------------------------
INSERT INTO `t_marketing_audit_log` (
`Action`, `OperatorId`, `TargetType`, `TargetId`,
`BeforeJson`, `AfterJson`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'grantCoupon', 0, 'customer', @referrerId,
NULL,
JSON_OBJECT('templateCode', 'SEED-FIXED-20', 'couponCode', 'SEED9-FIXED-AVAIL-01'),
'种子:批量发券演示',
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 5 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_marketing_audit_log`
WHERE `Action` = 'grantCoupon' AND `Remark` = '种子:批量发券演示' AND `IsDeleted` = 0
);
INSERT INTO `t_marketing_audit_log` (
`Action`, `OperatorId`, `TargetType`, `TargetId`,
`BeforeJson`, `AfterJson`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'adjustPoints', 0, 'customer', @referrerId,
JSON_OBJECT('availablePoints', 24000),
JSON_OBJECT('availablePoints', 24500, 'delta', 500),
'种子:人工调账加分(演示用不少于十字原因)',
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 3 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_marketing_audit_log`
WHERE `Action` = 'adjustPoints' AND `TargetId` = @referrerId
AND `Remark` LIKE '种子:人工调账%' AND `IsDeleted` = 0
);
INSERT INTO `t_marketing_audit_log` (
`Action`, `OperatorId`, `TargetType`, `TargetId`,
`BeforeJson`, `AfterJson`, `Remark`,
`IsDeleted`, `ConcurrencyStamp`, `CreateUserId`, `CreateTime`
)
SELECT
'bindReferral', 0, 'customer', @refereeId,
JSON_OBJECT('referrerCustomerId', NULL),
JSON_OBJECT('referrerCustomerId', @referrerId, 'channel', 'link'),
'种子:绑定引荐关系 8←9',
0, UUID(), 0, DATE_SUB(NOW(), INTERVAL 45 DAY)
FROM DUAL WHERE NOT EXISTS (
SELECT 1 FROM `t_marketing_audit_log`
WHERE `Action` = 'bindReferral' AND `TargetId` = @refereeId AND `IsDeleted` = 0
);
-- ---------------------------------------------------------------------------
-- 11. 结果核对
-- ---------------------------------------------------------------------------
SELECT 'precheck_customers' AS item,
(SELECT COUNT(*) FROM `t_customer` WHERE `Id` IN (@referrerId, @refereeId) AND `IsDeleted` = 0) AS cnt
UNION ALL
SELECT 'referral_bind_8_to_9',
(SELECT COUNT(*) FROM `t_customer`
WHERE `Id` = @refereeId AND `ReferrerCustomerId` = @referrerId AND `ParentCustomerId` IS NULL AND `IsDeleted` = 0)
UNION ALL
SELECT 'referral_codes',
(SELECT COUNT(*) FROM `t_customer_referral_code`
WHERE `CustomerId` IN (@referrerId, @refereeId) AND `IsDeleted` = 0)
UNION ALL
SELECT 'points_accounts',
(SELECT COUNT(*) FROM `t_customer_points_account`
WHERE `CustomerId` IN (@referrerId, @refereeId) AND `IsDeleted` = 0)
UNION ALL
SELECT 'points_flows',
(SELECT COUNT(*) FROM `t_customer_points_flow`
WHERE `CustomerId` IN (@referrerId, @refereeId) AND `BizNo` LIKE 'SEED-MKT-%' AND `IsDeleted` = 0)
UNION ALL
SELECT 'coupon_templates',
(SELECT COUNT(*) FROM `t_coupon_template`
WHERE `TemplateCode` LIKE 'SEED-%' AND `IsDeleted` = 0)
UNION ALL
SELECT 'coupon_instances',
(SELECT COUNT(*) FROM `t_coupon_instance`
WHERE `CouponCode` LIKE 'SEED%' AND `IsDeleted` = 0)
UNION ALL
SELECT 'commission_details',
(SELECT COUNT(*) FROM `t_referral_commission_detail`
WHERE `ReferrerCustomerId` = @referrerId AND `RefereeCustomerId` = @refereeId AND `IsDeleted` = 0)
UNION ALL
SELECT 'withdraw_requests',
(SELECT COUNT(*) FROM `t_commission_withdraw_request`
WHERE `CustomerId` = @referrerId AND `SerialNumber` LIKE 'SEED-WD-9-%' AND `IsDeleted` = 0)
UNION ALL
SELECT 'audit_logs',
(SELECT COUNT(*) FROM `t_marketing_audit_log`
WHERE `Remark` LIKE '种子:%' AND `IsDeleted` = 0);

View File

@ -1,306 +0,0 @@
-- 揽收订单种子(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);

View File

@ -1,47 +0,0 @@
-- 店铺平台 OAuth 配置种子(其他配置页可改;已存在则跳过)
INSERT INTO t_system_other_config
(ConfigCode, ConfigName, Enabled, BaseUrl, AppKey, AppSecret, TimeoutSeconds, Remark, IsDeleted, CreateTime)
SELECT 'platform_shop', '店铺授权-公共', 1, 'http://localhost:5173', NULL, NULL, 30, NULL, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM t_system_other_config WHERE ConfigCode = 'platform_shop' AND IsDeleted = 0);
INSERT INTO t_system_other_config
(ConfigCode, ConfigName, Enabled, BaseUrl, AppKey, AppSecret, TimeoutSeconds, Remark, IsDeleted, CreateTime)
SELECT 'platform_shop_tiktok', '店铺授权-TikTok', 1,
'https://auth.tiktok-shops.com',
'6hc64lc6vl312',
'5a691a96ee6d844534769bab5c21de1f848fb678',
30,
'https://open-api.tiktokglobalshop.com',
0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM t_system_other_config WHERE ConfigCode = 'platform_shop_tiktok' AND IsDeleted = 0);
INSERT INTO t_system_other_config
(ConfigCode, ConfigName, Enabled, BaseUrl, AppKey, AppSecret, TimeoutSeconds, Remark, IsDeleted, CreateTime)
SELECT 'platform_shop_temu', '店铺授权-Temu', 1,
'https://openapi-b-us.temu.com/openapi/router',
'4ebbc9190ae410443d65b4c2faca981f',
'4782d2d827276688bf4758bed55dbdd4bbe79a79',
30, NULL, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM t_system_other_config WHERE ConfigCode = 'platform_shop_temu' AND IsDeleted = 0);
INSERT INTO t_system_other_config
(ConfigCode, ConfigName, Enabled, BaseUrl, AppKey, AppSecret, TimeoutSeconds, Remark, IsDeleted, CreateTime)
SELECT 'platform_shop_shopee', '店铺授权-Shopee', 1,
'https://partner.shopeemobile.com',
'2004112',
'2f385d7ea38b087d5d71f7292e06a09c9f19022ca6c3f60ec0110a8214ef7d65',
30,
'https://openplatform.shopee.cn',
0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM t_system_other_config WHERE ConfigCode = 'platform_shop_shopee' AND IsDeleted = 0);
INSERT INTO t_system_other_config
(ConfigCode, ConfigName, Enabled, BaseUrl, AppKey, AppSecret, TimeoutSeconds, Remark, IsDeleted, CreateTime)
SELECT 'platform_shop_lazada', '店铺授权-Lazada', 1,
'https://auth.lazada.com/oauth/authorize',
'123631',
'7vBz2EaD6rIupeGEfwrgkabsTNsXt2ix',
30,
'https://auth.lazada.com/rest',
0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM t_system_other_config WHERE ConfigCode = 'platform_shop_lazada' AND IsDeleted = 0);

View File

@ -1,510 +0,0 @@
-- 店铺授权 + 店铺订单种子(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;

View File

@ -186,6 +186,10 @@ public class CouponTemplateOutput
public string GrantChannelName { get; set; } = string.Empty;
public CouponTemplateStatus Status { get; set; }
@ -313,6 +317,34 @@ public class CouponInstanceOutput
public string? CouponTypeName { get; set; }
public string? GrantChannel { get; set; }
public string? GrantChannelName { get; set; }
public decimal? FaceValue { get; set; }
public decimal? DiscountRate { get; set; }
public decimal? MaxDiscountAmount { get; set; }
public decimal MinOrderAmount { get; set; }
public int CustomerId { get; set; }

View File

@ -0,0 +1,63 @@
using Oms.Domain.SystemAggregate.Enums;
namespace Oms.Application.Dtos.System;
/// <summary>
/// 员工档案出参(工号/手机/邮箱见用户表)。
/// </summary>
public class EmployeeOutput
{
public int Id { get; set; }
public string UserId { get; set; } = string.Empty;
public string RealName { get; set; } = string.Empty;
public Gender Gender { get; set; }
public DateTime? Birthday { get; set; }
public string? IdCardNo { get; set; }
public DateTime? HireDate { get; set; }
public DateTime? ProbationEndDate { get; set; }
public DateTime? RegularizationDate { get; set; }
public EmploymentStatus EmploymentStatus { get; set; }
public DateTime? ResignationDate { get; set; }
public string? Department { get; set; }
public string? Position { get; set; }
public string? JobLevel { get; set; }
public EmploymentType EmploymentType { get; set; }
public string? WorkLocation { get; set; }
public string? ManagerUserId { get; set; }
public DateTime? ContractStartDate { get; set; }
public DateTime? ContractEndDate { get; set; }
public string? ContractType { get; set; }
public string? EmergencyContact { get; set; }
public string? EmergencyPhone { get; set; }
public string? Address { get; set; }
public string? Education { get; set; }
public string? Remark { get; set; }
public DateTime CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
}

View File

@ -0,0 +1,60 @@
using Oms.Domain.SystemAggregate.Enums;
namespace Oms.Application.Dtos.System;
/// <summary>
/// 员工档案保存入参(按 UserId 新增或更新;工号/手机/邮箱不在此维护)。
/// </summary>
public class EmployeeSaveInput
{
/// <summary>
/// 关联 RBAC 用户 Id,必填。
/// </summary>
public string UserId { get; set; } = string.Empty;
public string RealName { get; set; } = string.Empty;
public Gender Gender { get; set; } = Gender.Unknown;
public DateTime? Birthday { get; set; }
public string? IdCardNo { get; set; }
public DateTime? HireDate { get; set; }
public DateTime? ProbationEndDate { get; set; }
public DateTime? RegularizationDate { get; set; }
public EmploymentStatus EmploymentStatus { get; set; } = EmploymentStatus.Probation;
public DateTime? ResignationDate { get; set; }
public string? Department { get; set; }
public string? Position { get; set; }
public string? JobLevel { get; set; }
public EmploymentType EmploymentType { get; set; } = EmploymentType.FullTime;
public string? WorkLocation { get; set; }
public string? ManagerUserId { get; set; }
public DateTime? ContractStartDate { get; set; }
public DateTime? ContractEndDate { get; set; }
public string? ContractType { get; set; }
public string? EmergencyContact { get; set; }
public string? EmergencyPhone { get; set; }
public string? Address { get; set; }
public string? Education { get; set; }
public string? Remark { get; set; }
}

View File

@ -8,7 +8,10 @@ namespace Oms.Application.Rbac;
public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDependency
{
private static readonly Regex EmployeeNoPattern = new(@"^NE\d{4}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// 工号规则:IDE + 至少 3 位数字,如 IDE001。
/// </summary>
private static readonly Regex EmployeeNoPattern = new(@"^IDE(\d{3,})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public async Task<UserProfileData?> GetAsync(Guid userId, CancellationToken cancellationToken = default)
{
@ -173,14 +176,14 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
continue;
}
if (int.TryParse(value[2..], out var number))
if (int.TryParse(match.Groups[1].Value, out var number))
{
max = Math.Max(max, number);
}
}
var next = max + 1;
return next > 9999 ? $"NE{next}" : $"NE{next:D4}";
return $"IDE{next:D3}";
}
public static bool IsValidEmployeeNo(string employeeNo) => EmployeeNoPattern.IsMatch(employeeNo.Trim());

View File

@ -35,6 +35,9 @@ public class CustomerService : ApplicationService
private ICustomerReferralCodeRepository ReferralCodeRepository =>
LazyServiceProvider.GetRequiredService<ICustomerReferralCodeRepository>();
private MarketingCouponManager CouponManager =>
LazyServiceProvider.GetRequiredService<MarketingCouponManager>();
private UserService UserService => LazyServiceProvider.GetRequiredService<UserService>();
private RoleService RoleService => LazyServiceProvider.GetRequiredService<RoleService>();
@ -165,6 +168,14 @@ public class CustomerService : ApplicationService
ReferralCodeRepository,
CustomerRepository);
await CustomerRepository.InsertAsync(entity);
try
{
await CouponManager.GrantNewcomerCouponsAsync(entity.Id);
}
catch
{
// 新人券发放失败不回滚注册
}
}
catch
{

View File

@ -131,7 +131,7 @@ public class MarketingCommissionAdminService : ApplicationService
}
var customer = await CustomerRepository.GetByCustomerCodeAsync(code)
?? throw new BusinessException(`"客户编号 {code} 不存在`");
?? throw new BusinessException($"客户编号 {code} 不存在");
input.CustomerId = customer.ParentCustomerId ?? customer.Id;
}

View File

@ -207,6 +207,13 @@ public class MarketingCouponGrantService : ApplicationService
TemplateId = entity.TemplateId,
TemplateName = template?.TemplateName,
CouponType = template?.CouponType,
CouponTypeName = template == null ? null : MarketingEnumNames.OfCouponType(template.CouponType),
GrantChannel = template?.GrantChannel,
GrantChannelName = MarketingEnumNames.OfGrantChannel(template?.GrantChannel),
FaceValue = template?.FaceValue,
DiscountRate = template?.DiscountRate,
MaxDiscountAmount = template?.MaxDiscountAmount,
MinOrderAmount = template?.MinOrderAmount ?? 0,
CustomerId = entity.CustomerId,
CustomerCode = customerCode,
CouponCode = entity.CouponCode,

View File

@ -234,6 +234,7 @@ public class MarketingCouponTemplateService : ApplicationService
ValidDaysAfterReceive = entity.ValidDaysAfterReceive,
ApplyScopeJson = entity.ApplyScopeJson,
GrantChannel = entity.GrantChannel,
GrantChannelName = MarketingEnumNames.OfGrantChannel(entity.GrantChannel),
Status = entity.Status,
StatusName = MarketingEnumNames.Of(entity.Status),
Remark = entity.Remark,

View File

@ -1,4 +1,5 @@
using Fake.Application;
using Fake.Domain.Exceptions;
using Fake.Rbac.Application.Dtos.Common;
using Fake.Rbac.Application.Dtos.Role;
using Fake.Rbac.Application.Dtos.User;
@ -7,6 +8,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Rbac;
using Oms.Application.Rbac;
using Oms.Domain.SystemAggregate.Entities;
using Oms.Domain.SystemAggregate.Enums;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Rbac;
@ -15,7 +19,10 @@ namespace Oms.Application.Services.Rbac;
/// </summary>
[Authorize]
[ApiExplorerSettings(GroupName = "RBAC-User")]
public class OmsUserService(UserService userService, UserProfileRepository profileRepository) : ApplicationService
public class OmsUserService(
UserService userService,
UserProfileRepository profileRepository,
IEmployeeRepository employeeRepository) : ApplicationService
{
/// <summary>
/// 获取用户详情(含扩展资料)。
@ -105,6 +112,8 @@ public class OmsUserService(UserService userService, UserProfileRepository profi
},
cancellationToken);
await EnsureEmployeeProfileAsync(user.Id, input, cancellationToken);
var profile = await profileRepository.GetAsync(user.Id, cancellationToken);
return OmsUserProfileMapper.ToDetailDto(user, profile);
}
@ -215,7 +224,7 @@ public class OmsUserService(UserService userService, UserProfileRepository profi
if (!UserProfileRepository.IsValidEmployeeNo(employeeNo))
{
throw new BusinessException("工号格式无效,应为 NE + 4 位数字");
throw new BusinessException("工号格式无效,应为 IDE + 至少 3 位数字,如 IDE001");
}
if (await profileRepository.IsEmployeeNoTakenAsync(employeeNo, null, cancellationToken))
@ -225,4 +234,27 @@ public class OmsUserService(UserService userService, UserProfileRepository profi
return employeeNo;
}
private async Task EnsureEmployeeProfileAsync(
Guid userId,
OmsUserCreateInput input,
CancellationToken cancellationToken)
{
var userIdText = userId.ToString();
if (await employeeRepository.ExistsByUserIdAsync(userIdText, null, cancellationToken))
{
return;
}
await employeeRepository.InsertAsync(
new Employee
{
UserId = userIdText,
RealName = string.IsNullOrWhiteSpace(input.Name) ? string.Empty : input.Name.Trim(),
HireDate = DateTime.Today,
EmploymentStatus = EmploymentStatus.Probation,
EmploymentType = EmploymentType.FullTime,
},
cancellationToken);
}
}

View File

@ -0,0 +1,147 @@
using Fake.Application;
using Fake.Domain.Exceptions;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.System;
using Oms.Domain.SystemAggregate.Entities;
using Oms.Domain.SystemAggregate.Enums;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.System;
/// <summary>
/// 员工档案 API(关联 RBAC 用户;工号/手机/邮箱以用户表为准)。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-System")]
public class EmployeeService : ApplicationService
{
private IEmployeeRepository EmployeeRepository =>
LazyServiceProvider.GetRequiredService<IEmployeeRepository>();
/// <summary>
/// 按用户 Id 查询员工档案。
/// </summary>
[HttpGet("/system/employee/get-by-user")]
public async Task<EmployeeOutput?> GetByUserAsync(
[FromQuery] Guid userId,
CancellationToken cancellationToken = default)
{
if (userId == Guid.Empty)
{
throw new BusinessException("用户 Id 无效");
}
var entity = await EmployeeRepository.GetByUserIdAsync(userId.ToString(), cancellationToken);
return entity == null ? null : MapOutput(entity);
}
/// <summary>
/// 保存员工档案(不存在则新增,存在则更新)。
/// </summary>
[HttpPost("/system/employee/save")]
public async Task<EmployeeOutput> SaveAsync(
[FromBody] EmployeeSaveInput input,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(input.UserId) || !Guid.TryParse(input.UserId.Trim(), out var userGuid))
{
throw new BusinessException("用户 Id 无效");
}
var realName = input.RealName?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(realName))
{
throw new BusinessException("姓名不能为空");
}
var userId = userGuid.ToString();
var entity = await EmployeeRepository.GetByUserIdAsync(userId, cancellationToken);
if (entity == null)
{
entity = new Employee { UserId = userId };
MapToEntity(input, entity, realName);
await EmployeeRepository.InsertAsync(entity, cancellationToken);
}
else
{
MapToEntity(input, entity, realName);
await EmployeeRepository.UpdateAsync(entity, cancellationToken);
}
return MapOutput(entity);
}
private static void MapToEntity(EmployeeSaveInput input, Employee entity, string realName)
{
entity.RealName = realName;
entity.Gender = Enum.IsDefined(typeof(Gender), input.Gender) ? input.Gender : Gender.Unknown;
entity.Birthday = input.Birthday;
entity.IdCardNo = NormalizeOptional(input.IdCardNo);
entity.HireDate = input.HireDate;
entity.ProbationEndDate = input.ProbationEndDate;
entity.RegularizationDate = input.RegularizationDate;
entity.EmploymentStatus = Enum.IsDefined(typeof(EmploymentStatus), input.EmploymentStatus)
? input.EmploymentStatus
: EmploymentStatus.Probation;
entity.ResignationDate = input.ResignationDate;
entity.Department = NormalizeOptional(input.Department);
entity.Position = NormalizeOptional(input.Position);
entity.JobLevel = NormalizeOptional(input.JobLevel);
entity.EmploymentType = Enum.IsDefined(typeof(EmploymentType), input.EmploymentType)
? input.EmploymentType
: EmploymentType.FullTime;
entity.WorkLocation = NormalizeOptional(input.WorkLocation);
entity.ManagerUserId = NormalizeGuidOptional(input.ManagerUserId);
entity.ContractStartDate = input.ContractStartDate;
entity.ContractEndDate = input.ContractEndDate;
entity.ContractType = NormalizeOptional(input.ContractType);
entity.EmergencyContact = NormalizeOptional(input.EmergencyContact);
entity.EmergencyPhone = NormalizeOptional(input.EmergencyPhone);
entity.Address = NormalizeOptional(input.Address);
entity.Education = NormalizeOptional(input.Education);
entity.Remark = NormalizeOptional(input.Remark);
}
private static EmployeeOutput MapOutput(Employee entity) => new()
{
Id = entity.Id,
UserId = entity.UserId,
RealName = entity.RealName,
Gender = entity.Gender,
Birthday = entity.Birthday,
IdCardNo = entity.IdCardNo,
HireDate = entity.HireDate,
ProbationEndDate = entity.ProbationEndDate,
RegularizationDate = entity.RegularizationDate,
EmploymentStatus = entity.EmploymentStatus,
ResignationDate = entity.ResignationDate,
Department = entity.Department,
Position = entity.Position,
JobLevel = entity.JobLevel,
EmploymentType = entity.EmploymentType,
WorkLocation = entity.WorkLocation,
ManagerUserId = entity.ManagerUserId,
ContractStartDate = entity.ContractStartDate,
ContractEndDate = entity.ContractEndDate,
ContractType = entity.ContractType,
EmergencyContact = entity.EmergencyContact,
EmergencyPhone = entity.EmergencyPhone,
Address = entity.Address,
Education = entity.Education,
Remark = entity.Remark,
CreateTime = entity.CreateTime,
UpdateTime = entity.UpdateTime,
};
private static string? NormalizeOptional(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static string? NormalizeGuidOptional(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return Guid.TryParse(value.Trim(), out var id) ? id.ToString() : null;
}
}

View File

@ -81,6 +81,42 @@ public class MarketingCouponManager : IScopedDependency
return instance;
}
/// <summary>
/// 注册成功后发放所有「新人券」模板(GrantChannel=newcomer 且已发布),有多少发多少;单张失败不阻断。
/// </summary>
public async Task<int> GrantNewcomerCouponsAsync(int mainCustomerId, CancellationToken cancellationToken = default)
{
if (mainCustomerId <= 0)
{
return 0;
}
var templates = await _templateRepository.GetListAsync(
x => x.Status == CouponTemplateStatus.Active
&& x.GrantChannel == MarketingGrantChannels.Newcomer,
cancellationToken);
if (templates.Count == 0)
{
return 0;
}
var granted = 0;
foreach (var template in templates.OrderBy(x => x.Id))
{
try
{
await GrantAsync(template.Id, mainCustomerId, "newcomer_register", cancellationToken);
granted++;
}
catch (BusinessException)
{
// 库存不足、已达上限等跳过
}
}
return granted;
}
public async Task LockForOrderAsync(int couponInstanceId, int mainCustomerId, int orderId, CancellationToken cancellationToken = default)
{
var instance = await GetOwnedInstanceAsync(couponInstanceId, mainCustomerId, cancellationToken);

View File

@ -1,81 +1,94 @@
using Oms.Domain.MarketingAggregate.Enums;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Shared;
public static class MarketingEnumNames
{
public static string Of(EnableStatus status) => status switch
{
EnableStatus.Disabled => "禁用",
EnableStatus.Enabled => "启用",
_ => string.Empty
};
public static string Of(CouponTemplateStatus status) => status switch
{
CouponTemplateStatus.Draft => "草稿",
CouponTemplateStatus.Active => "已发布",
CouponTemplateStatus.Ended => "已停发",
_ => string.Empty
};
public static string Of(CouponInstanceStatus status) => status switch
{
CouponInstanceStatus.Available => "可用",
CouponInstanceStatus.Locked => "预占中",
CouponInstanceStatus.Used => "已使用",
CouponInstanceStatus.Expired => "已过期",
_ => string.Empty
};
public static string Of(CommissionStatus status) => status switch
{
CommissionStatus.Pending => "待结算",
CommissionStatus.Settled => "已结算",
CommissionStatus.Cancelled => "已取消",
_ => string.Empty
};
public static string Of(CommissionWithdrawType type) => type switch
{
CommissionWithdrawType.External => "外部提现",
CommissionWithdrawType.ToBalance => "转余额",
_ => string.Empty
};
public static string Of(WithdrawStatus status) => status switch
{
WithdrawStatus.Pending => "待审核",
WithdrawStatus.Approved => "已通过",
WithdrawStatus.Rejected => "已驳回",
WithdrawStatus.Paid => "已打款",
_ => string.Empty
};
public static string Of(PointsChangeType type) => type switch
{
PointsChangeType.Earn => "获得",
PointsChangeType.Consume => "消耗",
PointsChangeType.Adjust => "调账",
PointsChangeType.RefundClawback => "退款扣回",
_ => string.Empty
};
public static string Of(ReferralCodeStatus status) => status switch
{
ReferralCodeStatus.Disabled => "已停用",
ReferralCodeStatus.Enabled => "启用",
_ => string.Empty
};
public static string OfCouponType(string couponType) => couponType switch
{
MarketingCouponTypeCodes.FixedOff => "满减券",
MarketingCouponTypeCodes.PercentOff => "折扣券",
MarketingCouponTypeCodes.DirectOff => "直减券",
MarketingCouponTypeCodes.ShippingOff => "运费券",
_ => couponType
};
}
using Oms.Domain.MarketingAggregate.Consts;
using Oms.Domain.MarketingAggregate.Enums;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Shared;
public static class MarketingEnumNames
{
public static string Of(EnableStatus status) => status switch
{
EnableStatus.Disabled => "禁用",
EnableStatus.Enabled => "启用",
_ => string.Empty
};
public static string Of(CouponTemplateStatus status) => status switch
{
CouponTemplateStatus.Draft => "草稿",
CouponTemplateStatus.Active => "已发布",
CouponTemplateStatus.Ended => "已停发",
_ => string.Empty
};
public static string Of(CouponInstanceStatus status) => status switch
{
CouponInstanceStatus.Available => "可用",
CouponInstanceStatus.Locked => "预占中",
CouponInstanceStatus.Used => "已使用",
CouponInstanceStatus.Expired => "已过期",
_ => string.Empty
};
public static string Of(CommissionStatus status) => status switch
{
CommissionStatus.Pending => "待结算",
CommissionStatus.Settled => "已结算",
CommissionStatus.Cancelled => "已取消",
_ => string.Empty
};
public static string Of(CommissionWithdrawType type) => type switch
{
CommissionWithdrawType.External => "外部提现",
CommissionWithdrawType.ToBalance => "转余额",
_ => string.Empty
};
public static string Of(WithdrawStatus status) => status switch
{
WithdrawStatus.Pending => "待审核",
WithdrawStatus.Approved => "已通过",
WithdrawStatus.Rejected => "已驳回",
WithdrawStatus.Paid => "已打款",
_ => string.Empty
};
public static string Of(PointsChangeType type) => type switch
{
PointsChangeType.Earn => "获得",
PointsChangeType.Consume => "消耗",
PointsChangeType.Adjust => "调账",
PointsChangeType.RefundClawback => "退款扣回",
_ => string.Empty
};
public static string Of(ReferralCodeStatus status) => status switch
{
ReferralCodeStatus.Disabled => "已停用",
ReferralCodeStatus.Enabled => "启用",
_ => string.Empty
};
public static string OfCouponType(string couponType) => couponType switch
{
MarketingCouponTypeCodes.FixedOff => "满减券",
MarketingCouponTypeCodes.PercentOff => "折扣券",
MarketingCouponTypeCodes.DirectOff => "直减券",
MarketingCouponTypeCodes.ShippingOff => "运费券",
_ => couponType
};
public static string OfGrantChannel(string? grantChannel) =>
string.IsNullOrWhiteSpace(grantChannel)
? "手动发放"
: grantChannel.Trim() switch
{
MarketingGrantChannels.Newcomer => "新人券",
MarketingGrantChannels.SelfReceive => "客户自领",
MarketingGrantChannels.Portal => "门户领取",
MarketingGrantChannels.Manual => "手动发放",
_ => grantChannel
};
}

View File

@ -1,4 +1,3 @@
using Fake.Application;
using Fake.Domain.Exceptions;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Repositories;
@ -33,7 +32,8 @@ public static class MarketingReferralBinder
throw new BusinessException("邀请码无效或已停用");
}
if (referral.CustomerId == entity.Id)
// 新建时 entity.Id 多为 0,不能只靠 Id 判自引荐
if (entity.Id > 0 && referral.CustomerId == entity.Id)
{
throw new BusinessException("不能使用自己的邀请码");
}
@ -44,8 +44,46 @@ public static class MarketingReferralBinder
throw new BusinessException("邀请码对应客户无效");
}
if (IsSameIdentity(entity, referrer))
{
throw new BusinessException("不能使用自己的邀请码");
}
entity.ReferrerCustomerId = referral.CustomerId;
entity.ReferralBindTime = DateTime.Now;
entity.ReferralChannel = "link";
}
private static bool IsSameIdentity(Customer entity, Customer referrer)
{
if (!string.IsNullOrWhiteSpace(entity.Phone) &&
!string.IsNullOrWhiteSpace(referrer.Phone) &&
string.Equals(entity.Phone.Trim(), referrer.Phone.Trim(), StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrWhiteSpace(entity.Email) &&
!string.IsNullOrWhiteSpace(referrer.Email) &&
string.Equals(entity.Email.Trim(), referrer.Email.Trim(), StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrWhiteSpace(entity.WechatOpenid) &&
!string.IsNullOrWhiteSpace(referrer.WechatOpenid) &&
string.Equals(entity.WechatOpenid.Trim(), referrer.WechatOpenid.Trim(), StringComparison.Ordinal))
{
return true;
}
if (!string.IsNullOrWhiteSpace(entity.CustomerCode) &&
!string.IsNullOrWhiteSpace(referrer.CustomerCode) &&
string.Equals(entity.CustomerCode.Trim(), referrer.CustomerCode.Trim(), StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
}

View File

@ -11,6 +11,20 @@ public static class MarketingCouponTypes
public const string ShippingOff = "shipping_off";
}
/// <summary>
/// 券模板发放渠道。新人券用 Newcomer,注册成功后自动发放所有已发布模板。
/// </summary>
public static class MarketingGrantChannels
{
public const string Manual = "manual";
public const string Newcomer = "newcomer";
public const string SelfReceive = "selfReceive";
public const string Portal = "portal";
}
public static class MarketingPointsBizTypes
{
public const string OrderPay = "orderPay";

View File

@ -0,0 +1,88 @@
using Oms.Domain.Shared;
using Oms.Domain.SystemAggregate.Enums;
using SqlSugar;
namespace Oms.Domain.SystemAggregate.Entities;
/// <summary>
/// 员工档案,关联 RBAC 用户(user.Id)。
/// 工号/手机/邮箱等以用户表为准,本表不重复存储。
/// </summary>
[SugarTable("t_employee")]
public class Employee : OmsAuditedAggregateRoot
{
/// <summary>
/// RBAC 用户 Id(CHAR(36))。
/// </summary>
[SugarColumn(Length = 36, IsNullable = false)]
public string UserId { get; set; } = string.Empty;
[SugarColumn(Length = 64, IsNullable = false)]
public string RealName { get; set; } = string.Empty;
public Gender Gender { get; set; } = Gender.Unknown;
[SugarColumn(IsNullable = true)]
public DateTime? Birthday { get; set; }
[SugarColumn(Length = 32, IsNullable = true)]
public string? IdCardNo { get; set; }
[SugarColumn(IsNullable = true)]
public DateTime? HireDate { get; set; }
[SugarColumn(IsNullable = true)]
public DateTime? ProbationEndDate { get; set; }
[SugarColumn(IsNullable = true)]
public DateTime? RegularizationDate { get; set; }
public EmploymentStatus EmploymentStatus { get; set; } = EmploymentStatus.Probation;
[SugarColumn(IsNullable = true)]
public DateTime? ResignationDate { get; set; }
[SugarColumn(Length = 128, IsNullable = true)]
public string? Department { get; set; }
[SugarColumn(Length = 128, IsNullable = true)]
public string? Position { get; set; }
[SugarColumn(Length = 64, IsNullable = true)]
public string? JobLevel { get; set; }
public EmploymentType EmploymentType { get; set; } = EmploymentType.FullTime;
[SugarColumn(Length = 128, IsNullable = true)]
public string? WorkLocation { get; set; }
/// <summary>
/// 直属上级 RBAC 用户 Id。
/// </summary>
[SugarColumn(Length = 36, IsNullable = true)]
public string? ManagerUserId { get; set; }
[SugarColumn(IsNullable = true)]
public DateTime? ContractStartDate { get; set; }
[SugarColumn(IsNullable = true)]
public DateTime? ContractEndDate { get; set; }
[SugarColumn(Length = 64, IsNullable = true)]
public string? ContractType { get; set; }
[SugarColumn(Length = 64, IsNullable = true)]
public string? EmergencyContact { get; set; }
[SugarColumn(Length = 32, IsNullable = true)]
public string? EmergencyPhone { get; set; }
[SugarColumn(Length = 256, IsNullable = true)]
public string? Address { get; set; }
[SugarColumn(Length = 64, IsNullable = true)]
public string? Education { get; set; }
[SugarColumn(Length = 512, IsNullable = true)]
public string? Remark { get; set; }
}

View File

@ -0,0 +1,13 @@
namespace Oms.Domain.SystemAggregate.Enums;
/// <summary>
/// 在职状态。0=未设置;1=试用;2=正式;3=停职;4=离职。
/// </summary>
public enum EmploymentStatus : byte
{
None = 0,
Probation = 1,
Regular = 2,
Suspended = 3,
Resigned = 4
}

View File

@ -0,0 +1,13 @@
namespace Oms.Domain.SystemAggregate.Enums;
/// <summary>
/// 用工类型。0=未设置;1=全职;2=兼职;3=实习;4=外包。
/// </summary>
public enum EmploymentType : byte
{
None = 0,
FullTime = 1,
PartTime = 2,
Intern = 3,
Outsourced = 4
}

View File

@ -0,0 +1,11 @@
namespace Oms.Domain.SystemAggregate.Enums;
/// <summary>
/// 性别。0=未知;1=男;2=女。
/// </summary>
public enum Gender : byte
{
Unknown = 0,
Male = 1,
Female = 2
}

View File

@ -0,0 +1,11 @@
using Fake.Domain.Repositories;
using Oms.Domain.SystemAggregate.Entities;
namespace Oms.Domain.SystemAggregate.Repositories;
public interface IEmployeeRepository : IRepository<Employee>
{
Task<Employee?> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default);
Task<bool> ExistsByUserIdAsync(string userId, int? excludeId = null, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,29 @@
using Fake.DependencyInjection;
using Oms.Domain.SystemAggregate.Entities;
using Oms.Domain.SystemAggregate.Repositories;
using Oms.SqlSugarCore.Repositories;
namespace Oms.SqlSugarCore.System;
public class EmployeeRepository : OmsSqlSugarRepository<Employee>, IEmployeeRepository, IScopedDependency
{
public Task<Employee?> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default)
{
var normalized = userId.Trim();
return FirstOrDefaultAsync(x => x.UserId == normalized, cancellationToken);
}
public async Task<bool> ExistsByUserIdAsync(
string userId,
int? excludeId = null,
CancellationToken cancellationToken = default)
{
var normalized = userId.Trim();
if (excludeId.HasValue)
{
return await AnyAsync(x => x.UserId == normalized && x.Id != excludeId.Value, cancellationToken);
}
return await AnyAsync(x => x.UserId == normalized, cancellationToken);
}
}

View File

@ -76,6 +76,11 @@ public class WeChatCustomerRegisterInput
[MinLength(6)]
public string Password { get; set; } = string.Empty;
/// <summary>
/// 邀请码(可选)。来自邀请链接 / 二维码 ?ref=,注册时一次性绑定引荐人。
/// </summary>
public string? InviteCode { get; set; }
[Required]
public WeChatProfileInput Wechat { get; set; } = new();
}

View File

@ -12,6 +12,7 @@ using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Enums;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.MarketingAggregate.Repositories;
using SimpleAdmin.Api.WeChat.Dtos;
using SimpleAdmin.Api.WeChat.Services;
@ -25,6 +26,8 @@ namespace SimpleAdmin.Api.WeChat;
public class WeChatCustomerService(
IWeChatMpApiClient weChatMpApiClient,
ICustomerRepository customerRepository,
ICustomerReferralCodeRepository referralCodeRepository,
MarketingCouponManager couponManager,
UserService userService,
RoleService roleService,
FakeRbacDbContext rbacDbContext,
@ -141,7 +144,26 @@ public class WeChatCustomerService(
WechatAvatar = wechatAvatar,
Status = CustomerStatus.Normal
};
await MarketingReferralBinder.BindReferrerOnCreateAsync(
entity,
input.InviteCode,
referralCodeRepository,
customerRepository,
cancellationToken);
if (!string.IsNullOrWhiteSpace(input.InviteCode) && entity.ReferrerCustomerId.HasValue)
{
entity.ReferralChannel = "wechat";
}
await customerRepository.InsertAsync(entity);
try
{
await couponManager.GrantNewcomerCouponsAsync(entity.Id, cancellationToken);
}
catch (Exception grantEx)
{
logger.LogWarning(grantEx, "新人券发放失败: customerId={CustomerId}", entity.Id);
}
}
catch (Exception ex)
{