Merge pull request 'fix: 裂变邀请人发券,并合并营销 SQL 到 admin1008' (#2) from test into master
All checks were successful
Build and Deploy / deploy (push) Successful in 1m45s

Reviewed-on: #2
This commit is contained in:
zzh 2026-10-08 17:44:33 +08:00
commit 811795f74c
10 changed files with 797 additions and 923 deletions

View File

@ -1,361 +0,0 @@
-- =============================================================================
-- 存量库增量迁移(合并自 sql/migrate_*.sql)
-- 可按整文件执行;多数段可重复执行(information_schema 判断)。
-- 注意:揽收 Direction / FeePush 两段为裸 ALTER,已执行过的库请跳过或手工注释。
-- =============================================================================
SET @db := DATABASE();
-- -----------------------------------------------------------------------------
-- 1) 排货规则服务商表:MatchCondition
-- 原:migrate_allocation_match_condition.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_sales_product_allocation_provider'
AND COLUMN_NAME = 'MatchCondition'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_sales_product_allocation_provider` ADD COLUMN `MatchCondition` TEXT NULL COMMENT ''排货判断条件(中文公式),空表示全部订单可用'' AFTER `CustomerIds`',
'SELECT ''MatchCondition already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- 2) 客户出口资质备案表(含 IsDefault / Remark)
-- 原:migrate_customer_export_qualification.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_customer_export_qualification'
);
SET @sql := IF(
@exists = 0,
'CREATE TABLE `t_customer_export_qualification` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
`CustomerId` INT NOT NULL COMMENT ''归属客户 t_customer.Id'',
`CompanyName` VARCHAR(200) NOT NULL COMMENT ''企业名称'',
`SocialCreditCode` VARCHAR(64) NOT NULL COMMENT ''企业社会信用代码'',
`DomesticConsignor` VARCHAR(200) NOT NULL COMMENT ''境内发货人'',
`DomesticConsignorCustomsCode` VARCHAR(64) NULL COMMENT ''境内发货人海关代码'',
`ProducerName` VARCHAR(200) NOT NULL COMMENT ''生产销售单位'',
`ProducerCustomsCode` VARCHAR(64) NULL COMMENT ''生产销售单位海关代码'',
`CustomsPort` VARCHAR(100) NULL COMMENT ''备案海关港口地'',
`IsDefault` TINYINT NOT NULL DEFAULT 0 COMMENT ''是否默认:0-否,1-是(同客户仅一条)'',
`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`),
KEY `idx_ceq_customer` (`CustomerId`, `IsDeleted`, `Id`),
KEY `idx_ceq_credit` (`SocialCreditCode`, `IsDeleted`),
KEY `idx_ceq_customer_default` (`CustomerId`, `IsDefault`)
) COMMENT=''客户出口资质备案''',
'SELECT ''t_customer_export_qualification already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_customer_export_qualification'
AND COLUMN_NAME = 'IsDefault'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_customer_export_qualification`
ADD COLUMN `IsDefault` TINYINT NOT NULL DEFAULT 0 COMMENT ''是否默认:0-否,1-是(同客户仅一条)'' AFTER `CustomsPort`',
'SELECT ''IsDefault already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_customer_export_qualification'
AND COLUMN_NAME = 'Remark'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_customer_export_qualification`
ADD COLUMN `Remark` VARCHAR(512) NULL COMMENT ''备注'' AFTER `IsDefault`',
'SELECT ''Remark already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_customer_export_qualification'
AND INDEX_NAME = 'idx_ceq_customer_default'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_customer_export_qualification` ADD KEY `idx_ceq_customer_default` (`CustomerId`, `IsDefault`)',
'SELECT ''idx_ceq_customer_default already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- 3) t_order:出口资质备案 Id
-- 原:migrate_order_export_qualification_id.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'ExportQualificationId'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_order`
ADD COLUMN `ExportQualificationId` INT NULL COMMENT ''出口资质备案 Id(t_customer_export_qualification.Id)'' AFTER `ExportCustomsMode`,
ADD KEY `idx_order_export_qualification` (`ExportQualificationId`)',
'SELECT ''t_order.ExportQualificationId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- 4) SKU / 订单申报:DeclareElement
-- 原:migrate_declare_element.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 'c_sku'
AND COLUMN_NAME = 'DeclareElement'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `c_sku` ADD COLUMN `DeclareElement` VARCHAR(255) NULL COMMENT ''出口申报要素'' AFTER `ImHsCode`',
'SELECT ''c_sku.DeclareElement already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order_declaration'
AND COLUMN_NAME = 'DeclareElement'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_order_declaration` ADD COLUMN `DeclareElement` VARCHAR(255) NULL COMMENT ''出口申报要素'' AFTER `ExHsCode`',
'SELECT ''t_order_declaration.DeclareElement already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- 5) 问题模板:处理人字段
-- 原:migrate_issue_template_handler.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_issue_template'
AND COLUMN_NAME = 'HandlerType'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_issue_template` ADD COLUMN `HandlerType` VARCHAR(16) NULL COMMENT ''处理人类型:customer=订单所属客户,user=指定用户'' AFTER `NotifyMethod`',
'SELECT ''HandlerType already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @exists := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_issue_template'
AND COLUMN_NAME = 'HandlerUserId'
);
SET @sql := IF(
@exists = 0,
'ALTER TABLE `t_issue_template` ADD COLUMN `HandlerUserId` INT NULL COMMENT ''指定处理人用户Id(HandlerType=user 时必填)'' AFTER `HandlerType`',
'SELECT ''HandlerUserId already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_issue_template`
SET `HandlerType` = 'user'
WHERE (`HandlerType` IS NULL OR `HandlerType` = '')
AND `IsDeleted` = 0;
-- -----------------------------------------------------------------------------
-- 6) 揽收方向 + 客户地址类型
-- 原:migrate_pickup_direction_and_address_type.sql
-- 注意:裸 ALTER,已执行过勿再跑本段
-- -----------------------------------------------------------------------------
ALTER TABLE `t_pickup_order`
ADD COLUMN `Direction` VARCHAR(16) NOT NULL DEFAULT 'forward'
COMMENT 'forward正向(客户→仓)/reverse逆向(仓→客户)'
AFTER `SenderAddressId`;
ALTER TABLE `t_pickup_order`
ADD KEY `idx_pickup_direction_status` (`Direction`, `Status`, `CreateTime`);
ALTER TABLE `t_customer_sender_address`
MODIFY COLUMN `AddressType` VARCHAR(16) NOT NULL DEFAULT 'sender'
COMMENT 'sender国内发件/return国内退件/pickup国内揽收/receiver海外收件';
-- -----------------------------------------------------------------------------
-- 7) 揽收单:承运推送件重尺 + 费用快照
-- 原:migrate_pickup_fee_push.sql
-- 注意:裸 ALTER,已执行过勿再跑本段
-- -----------------------------------------------------------------------------
ALTER TABLE `t_pickup_order`
ADD COLUMN `VolumetricWeight` DECIMAL(18,3) NULL COMMENT '体积重' AFTER `ChargeableWeight`,
ADD COLUMN `PackageSizesJson` TEXT NULL COMMENT '承运回传尺寸 JSON' AFTER `VolumetricWeight`,
ADD COLUMN `CarrierFreightWeight` DECIMAL(18,3) NULL COMMENT '承运计费重(对照)' AFTER `PackageSizesJson`,
ADD COLUMN `CarrierQuotedFreightAmount` DECIMAL(18,2) NULL COMMENT '承运报价运费(对照)' AFTER `CarrierFreightWeight`,
ADD COLUMN `CarrierQuotedReceivableAmount` DECIMAL(18,2) NULL COMMENT '承运报文应收(对照)' AFTER `CarrierQuotedFreightAmount`,
ADD COLUMN `CarrierQuoteJson` TEXT NULL COMMENT '承运报价原文' AFTER `CarrierQuotedReceivableAmount`,
ADD COLUMN `ReceivableFeeJson` TEXT NULL COMMENT '系统卖价费用明细 JSON' AFTER `CarrierQuoteJson`,
ADD COLUMN `PayableFeeJson` TEXT NULL COMMENT '系统买价费用明细 JSON' AFTER `ReceivableFeeJson`,
ADD COLUMN `FeePushedAt` DATETIME NULL COMMENT '承运推送件重尺时间' AFTER `PayableFeeJson`;
-- -----------------------------------------------------------------------------
-- 8) 客户 OpenAPI 机机凭证
-- 原:migrate_customer_openapi_credential.sql
-- -----------------------------------------------------------------------------
SET @exists := (
SELECT COUNT(1)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_customer_openapi_credential'
);
SET @sql := IF(
@exists = 0,
'CREATE TABLE `t_customer_openapi_credential` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
`CustomerId` INT NOT NULL COMMENT ''归属主客户 t_customer.Id'',
`AppKey` VARCHAR(64) NOT NULL COMMENT ''开放接口 AppKey'',
`AppSecret` VARCHAR(128) NOT NULL COMMENT ''开放接口 AppSecret(明文,创建/重置时返回)'',
`Enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否启用:0-否,1-是'',
`LastUsedAt` DATETIME NULL COMMENT ''最近鉴权成功时间'',
`LastUsedIp` VARCHAR(64) NULL COMMENT ''最近鉴权成功 IP'',
`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_coc_customer` (`CustomerId`),
UNIQUE KEY `uk_coc_app_key` (`AppKey`),
KEY `idx_coc_enabled` (`Enabled`, `IsDeleted`)
) COMMENT=''客户 OpenAPI 机机凭证''',
'SELECT ''t_customer_openapi_credential already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- 9) 分区明细:匹配维度 / 方式 / 前缀长度
-- -----------------------------------------------------------------------------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND COLUMN_NAME = 'MatchDim'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_kpi_area_info`
ADD COLUMN `MatchDim` VARCHAR(16) NOT NULL DEFAULT ''country'' COMMENT ''匹配维度:zip/state/city/country'' AFTER `RightPostcode`,
ADD COLUMN `MatchMode` VARCHAR(16) NOT NULL DEFAULT ''all'' COMMENT ''匹配方式:prefix/exact/range/all'' AFTER `MatchDim`,
ADD COLUMN `PrefixLen` INT NOT NULL DEFAULT 0 COMMENT ''邮编前缀长度(zip+prefix)'' AFTER `MatchMode`',
'SELECT ''kpi area match columns already exist'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND INDEX_NAME = 'idx_kpi_info_area_dim'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_kpi_area_info` ADD KEY `idx_kpi_info_area_dim` (`AreaId`, `CountryCode`, `MatchDim`)',
'SELECT ''idx_kpi_info_area_dim already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_kpi_area_info`
SET
`MatchDim` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' OR IFNULL(`RightPostcode`, '') <> '' THEN 'zip'
WHEN IFNULL(`City`, '') <> '' THEN 'city'
WHEN IFNULL(`Province`, '') <> '' THEN 'state'
ELSE 'country'
END,
`MatchMode` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN 'prefix'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN 'exact'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN 'range'
WHEN IFNULL(`City`, '') <> '' OR IFNULL(`Province`, '') <> '' THEN 'exact'
ELSE 'all'
END,
`PrefixLen` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN LEAST(CHAR_LENGTH(`LeftPostcode`), CHAR_LENGTH(`RightPostcode`))
ELSE 0
END
WHERE IFNULL(`MatchDim`, '') IN ('', 'country')
AND (
IFNULL(`LeftPostcode`, '') <> ''
OR IFNULL(`RightPostcode`, '') <> ''
OR IFNULL(`Province`, '') <> ''
OR IFNULL(`City`, '') <> ''
);

626
sql/admin1008.sql Normal file
View File

@ -0,0 +1,626 @@
-- =============================================================================
-- admin.sql
-- 存量库增量迁移合集(由 sql/ 下各 migrate_*.sql 合并,可重复执行)
-- 新环境请直接执行仓库根目录 db.sql,勿在有生产数据的库上 DROP 重建。
-- =============================================================================
-- =============================================================================
-- migrate_member_level_rolling_points.sql
-- 旧会员体系 → V0~V5(可重复执行)
-- old: normal/silver/gold/diamond
-- new: V0 普通 / V1 铜卡(新增) / V2 银卡 / V3 金卡 / V4 钻石 / V5 皇冠(新增)
-- =============================================================================
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_member_level'
AND COLUMN_NAME = 'CanDowngrade'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_member_level`
ADD COLUMN `CanDowngrade` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''V4/V5 允许逐级降级'' AFTER `ExtraDiscountRate`',
'SELECT ''CanDowngrade already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ---------- 把旧行升级成新编码(仅当目标 V* 还不存在)----------
UPDATE `t_member_level`
SET
`LevelCode` = 'V0',
`LevelName` = '普通会员',
`MinPoints` = 0,
`ExtraDiscountRate` = 0.00,
`CanDowngrade` = 0,
`Sort` = 0,
`BenefitDesc` = '新注册 / 小单试单卖家',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'normal'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V0') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V2',
`LevelName` = '银卡会员',
`MinPoints` = 60000,
`ExtraDiscountRate` = 0.02,
`CanDowngrade` = 0,
`Sort` = 2,
`BenefitDesc` = '主力中小卖家,基础运费 98 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'silver'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V2') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V3',
`LevelName` = '金卡会员',
`MinPoints` = 120000,
`ExtraDiscountRate` = 0.03,
`CanDowngrade` = 0,
`Sort` = 3,
`BenefitDesc` = '优质中型卖家,基础运费 97 折;此级及以下只升不降',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'gold'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V3') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V4',
`LevelName` = '钻石会员',
`MinPoints` = 240000,
`ExtraDiscountRate` = 0.04,
`CanDowngrade` = 1,
`Sort` = 4,
`BenefitDesc` = '中大卖家,基础运费 96 折;滚动积分不足可降到金卡',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'diamond'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V4') t);
-- ---------- 缺档则插入(铜卡、皇冠,以及未被旧行带过来的档)----------
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V0', '普通会员', 0, 0.00, 0, 0, '新注册 / 小单试单卖家', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V0' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V1', '铜卡会员', 24000, 0.01, 0, 1, '起步稳定发货卖家,基础运费 99 折', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V1' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V2', '银卡会员', 60000, 0.02, 0, 2, '主力中小卖家,基础运费 98 折', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V2' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V3', '金卡会员', 120000, 0.03, 0, 3, '优质中型卖家,基础运费 97 折;此级及以下只升不降', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V3' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V4', '钻石会员', 240000, 0.04, 1, 4, '中大卖家,基础运费 96 折;滚动积分不足可降到金卡', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V4' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V5', '皇冠会员', 600000, 0.05, 1, 5, '顶级头部卖家,基础运费 95 折封顶;滚动积分不足可降到钻石', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V5' AND `IsDeleted` = 0);
-- ---------- 旧编码与新档并存时:客户挂到新档,旧行停用 ----------
UPDATE `t_customer` c
INNER JOIN `t_member_level` old ON old.`Id` = c.`MemberLevelId` AND old.`IsDeleted` = 0
INNER JOIN `t_member_level` canon ON canon.`IsDeleted` = 0 AND canon.`LevelCode` = CASE LOWER(old.`LevelCode`)
WHEN 'normal' THEN 'V0'
WHEN 'silver' THEN 'V2'
WHEN 'gold' THEN 'V3'
WHEN 'diamond' THEN 'V4'
ELSE NULL
END
SET c.`MemberLevelId` = canon.`Id`, c.`UpdateTime` = NOW()
WHERE LOWER(old.`LevelCode`) IN ('normal', 'silver', 'gold', 'diamond')
AND old.`Id` <> canon.`Id`;
UPDATE `t_member_level` old
INNER JOIN `t_member_level` canon ON canon.`IsDeleted` = 0 AND canon.`LevelCode` = CASE LOWER(old.`LevelCode`)
WHEN 'normal' THEN 'V0'
WHEN 'silver' THEN 'V2'
WHEN 'gold' THEN 'V3'
WHEN 'diamond' THEN 'V4'
ELSE NULL
END
SET
old.`IsDeleted` = 1,
old.`Status` = 1,
old.`UpdateTime` = NOW()
WHERE old.`IsDeleted` = 0
AND LOWER(old.`LevelCode`) IN ('normal', 'silver', 'gold', 'diamond')
AND old.`Id` <> canon.`Id`;
-- ---------- 校正 V0~V5 门槛/折扣(已占用客户的行 Id 不变)----------
UPDATE `t_member_level`
SET
`LevelName` = '普通会员',
`MinPoints` = 0,
`ExtraDiscountRate` = 0.00,
`CanDowngrade` = 0,
`Sort` = 0,
`BenefitDesc` = '新注册 / 小单试单卖家',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V0';
UPDATE `t_member_level`
SET
`LevelName` = '铜卡会员',
`MinPoints` = 24000,
`ExtraDiscountRate` = 0.01,
`CanDowngrade` = 0,
`Sort` = 1,
`BenefitDesc` = '起步稳定发货卖家,基础运费 99 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V1';
UPDATE `t_member_level`
SET
`LevelName` = '银卡会员',
`MinPoints` = 60000,
`ExtraDiscountRate` = 0.02,
`CanDowngrade` = 0,
`Sort` = 2,
`BenefitDesc` = '主力中小卖家,基础运费 98 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V2';
UPDATE `t_member_level`
SET
`LevelName` = '金卡会员',
`MinPoints` = 120000,
`ExtraDiscountRate` = 0.03,
`CanDowngrade` = 0,
`Sort` = 3,
`BenefitDesc` = '优质中型卖家,基础运费 97 折;此级及以下只升不降',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V3';
UPDATE `t_member_level`
SET
`LevelName` = '钻石会员',
`MinPoints` = 240000,
`ExtraDiscountRate` = 0.04,
`CanDowngrade` = 1,
`Sort` = 4,
`BenefitDesc` = '中大卖家,基础运费 96 折;滚动积分不足可降到金卡',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V4';
UPDATE `t_member_level`
SET
`LevelName` = '皇冠会员',
`MinPoints` = 600000,
`ExtraDiscountRate` = 0.05,
`CanDowngrade` = 1,
`Sort` = 5,
`BenefitDesc` = '顶级头部卖家,基础运费 95 折封顶;滚动积分不足可降到钻石',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V5';
-- =============================================================================
-- migrate_coupon_grant_system.sql
-- 优惠券发放体系:未解锁状态、到期提醒标记、新人/裂变券模板(可重复执行)
-- =============================================================================
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_coupon_instance'
AND COLUMN_NAME = 'ExpiryNoticeSent'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_coupon_instance`
ADD COLUMN `ExpiryNoticeSent` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''到期前3天已提醒'' AFTER `LockExpireTime`',
'SELECT ''ExpiryNoticeSent already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 新人首单券 15 元,领取后 15 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_FIRST', '新人首单券', 'fixed_off', 15.00, NULL, NULL,
0, 0, 0, 1,
2, NULL, NULL, 15,
NULL, 'newcomer', 1, '注册自动下发,仅抵基础运费,退款不退回',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_FIRST'
);
-- 新人第二单券 15 元,满 150 基础运费,解锁后 30 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_SECOND', '新人第二单券', 'fixed_off', 15.00, NULL, NULL,
150, 0, 0, 1,
3, NULL, NULL, 30,
NULL, 'newcomer', 1, '首单派送成功后解锁,实付基础运费满 150',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_SECOND'
);
-- 新人备货券 20 元,满 200,注册后 60 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_STOCK', '新人备货券', 'fixed_off', 20.00, NULL, NULL,
200, 0, 0, 1,
2, NULL, NULL, 60,
NULL, 'newcomer', 1, '注册自动下发,实付基础运费满 200',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_STOCK'
);
-- 裂变:老客 20 元无门槛
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'REFERRAL_INVITER', '邀请有礼券', 'fixed_off', 20.00, NULL, NULL,
0, 0, 0, 9999,
2, NULL, NULL, 30,
NULL, 'referral', 1, '被邀请人首单派送成功后发给邀请人(每位被邀请人最多触发一次)',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'REFERRAL_INVITER'
);
-- 已存在的邀请有礼券:放宽每人限领,避免仅首个被邀请人能给邀请人发券
UPDATE `t_coupon_template`
SET `PerCustomerLimit` = 9999,
`Remark` = '被邀请人首单派送成功后发给邀请人(每位被邀请人最多触发一次)',
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND `TemplateCode` = 'REFERRAL_INVITER'
AND `PerCustomerLimit` < 9999;
-- 裂变:新客额外 5 元
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'REFERRAL_INVITEE', '邀请注册加赠券', 'fixed_off', 5.00, NULL, NULL,
0, 0, 0, 1,
2, NULL, NULL, 30,
NULL, 'referral', 1, '被邀请人注册即发 5 元券',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'REFERRAL_INVITEE'
);
-- =============================================================================
-- migrate_referral_invite_reward.sql
-- 邀请奖励:按实付基础运费百分比、T+N 工作日入运费余额、退款标记(可重复执行)
-- =============================================================================
SET @db := DATABASE();
-- ---------- t_order.IsRefunded ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'IsRefunded'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_order`
ADD COLUMN `IsRefunded` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否已退款(分成计次排除)'' AFTER `IsPaid`',
'SELECT ''t_order.IsRefunded already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ---------- t_referral_commission_rule 新字段 ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_rule'
AND COLUMN_NAME = 'MinInviteLevelCode'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_rule`
ADD COLUMN `MinInviteLevelCode` VARCHAR(16) NOT NULL DEFAULT ''V1'' COMMENT ''邀请资格最低等级'' AFTER `MonthlyCap`,
ADD COLUMN `MinPaidBaseFreight` DECIMAL(18,2) NOT NULL DEFAULT 50.00 COMMENT ''计奖最低实付基础运费'' AFTER `MinInviteLevelCode`,
ADD COLUMN `FirstOrderRate` DECIMAL(8,4) NOT NULL DEFAULT 0.0500 COMMENT ''首单奖励比例'' AFTER `MinPaidBaseFreight`,
ADD COLUMN `FirstOrderCap` DECIMAL(18,2) NOT NULL DEFAULT 50.00 COMMENT ''首单奖励封顶'' AFTER `FirstOrderRate`,
ADD COLUMN `FollowOrderRate` DECIMAL(8,4) NOT NULL DEFAULT 0.0300 COMMENT ''后续单奖励比例'' AFTER `FirstOrderCap`,
ADD COLUMN `FollowOrderCap` DECIMAL(18,2) NOT NULL DEFAULT 30.00 COMMENT ''后续单奖励封顶'' AFTER `FollowOrderRate`,
ADD COLUMN `FollowWindowDays` INT NOT NULL DEFAULT 90 COMMENT ''后续单注册后天数窗口'' AFTER `FollowOrderCap`,
ADD COLUMN `MaxRewardOrders` INT NOT NULL DEFAULT 6 COMMENT ''最多计奖订单数'' AFTER `FollowWindowDays`,
ADD COLUMN `SettleBusinessDays` INT NOT NULL DEFAULT 3 COMMENT ''派送成功后结算工作日'' AFTER `MaxRewardOrders`',
'SELECT ''t_referral_commission_rule reward columns already exist'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_referral_commission_rule`
SET
`CommissionMode` = 'percent_of_paid_base_freight',
`SettleTriggerOrderStatus` = 'deliverySuccess',
`SettleBusinessDays` = CASE WHEN `SettleBusinessDays` > 0 THEN `SettleBusinessDays` ELSE 3 END,
`FreezeDays` = CASE WHEN `SettleBusinessDays` > 0 THEN `SettleBusinessDays` ELSE 3 END,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0;
-- ---------- t_referral_commission_detail ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_detail'
AND COLUMN_NAME = 'SettleDueTime'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_detail`
ADD COLUMN `SettleDueTime` DATETIME NULL COMMENT ''计划结算时间'' AFTER `EligibleTime`',
'SELECT ''SettleDueTime already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_detail'
AND COLUMN_NAME = 'RewardOrderIndex'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_detail`
ADD COLUMN `RewardOrderIndex` INT NOT NULL DEFAULT 0 COMMENT ''被邀请人第几单'' AFTER `SettleDueTime`',
'SELECT ''RewardOrderIndex already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_coupon_template`
SET
`Remark` = '被邀请人注册即发 5 元券',
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND `TemplateCode` = 'REFERRAL_INVITEE';
-- =============================================================================
-- migrate_kpi_area_info_match.sql
-- 存量库:分区明细增加匹配维度/方式/前缀长度(可重复执行)
-- =============================================================================
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND COLUMN_NAME = 'MatchDim'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_kpi_area_info`
ADD COLUMN `MatchDim` VARCHAR(16) NOT NULL DEFAULT ''country'' COMMENT ''匹配维度:zip/state/city/country'' AFTER `RightPostcode`,
ADD COLUMN `MatchMode` VARCHAR(16) NOT NULL DEFAULT ''all'' COMMENT ''匹配方式:prefix/exact/range/all'' AFTER `MatchDim`,
ADD COLUMN `PrefixLen` INT NOT NULL DEFAULT 0 COMMENT ''邮编前缀长度(zip+prefix)'' AFTER `MatchMode`',
'SELECT ''kpi area match columns already exist'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND INDEX_NAME = 'idx_kpi_info_area_dim'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_kpi_area_info` ADD KEY `idx_kpi_info_area_dim` (`AreaId`, `CountryCode`, `MatchDim`)',
'SELECT ''idx_kpi_info_area_dim already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_kpi_area_info`
SET
`MatchDim` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' OR IFNULL(`RightPostcode`, '') <> '' THEN 'zip'
WHEN IFNULL(`City`, '') <> '' THEN 'city'
WHEN IFNULL(`Province`, '') <> '' THEN 'state'
ELSE 'country'
END,
`MatchMode` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN 'prefix'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN 'exact'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN 'range'
WHEN IFNULL(`City`, '') <> '' OR IFNULL(`Province`, '') <> '' THEN 'exact'
ELSE 'all'
END,
`PrefixLen` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN LEAST(CHAR_LENGTH(`LeftPostcode`), CHAR_LENGTH(`RightPostcode`))
ELSE 0
END
WHERE IFNULL(`MatchDim`, '') IN ('', 'country')
AND (
IFNULL(`LeftPostcode`, '') <> ''
OR IFNULL(`RightPostcode`, '') <> ''
OR IFNULL(`Province`, '') <> ''
OR IFNULL(`City`, '') <> ''
);
-- =============================================================================
-- migrate_kpi_area_info_area_id_index.sql
-- 分区明细按 AreaId 全量/分页查询时避免 filesort 打满 sort_buffer(查价 LIVE 路径易踩)
-- =============================================================================
SET @db := DATABASE();
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND INDEX_NAME = 'idx_kpi_info_area_id'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_kpi_area_info` ADD KEY `idx_kpi_info_area_id` (`AreaId`, `Id`)',
'SELECT ''idx_kpi_info_area_id already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================================================
-- migrate_kpi_area_publish_record.sql
-- 分区包发布表(价区 / 黑名单 / 偏远):卖价/规则发布钉包,计费按 version 加载
-- =============================================================================
SET @db := DATABASE();
SET @tbl := (
SELECT COUNT(1)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_publish_record'
);
SET @sql := IF(
@tbl = 0,
'CREATE TABLE `t_kpi_area_publish_record` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
`AreaId` INT NOT NULL COMMENT ''所属分区表 t_kpi_area.Id'',
`AreaType` VARCHAR(32) NOT NULL COMMENT ''分区类型:price=价格分区,blacklist=黑名单,remote=偏远'',
`Version` VARCHAR(32) NOT NULL COMMENT ''包版本号(yyyyMMddHHmmssfff)'',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT ''状态:1=生效 Active,2=失效 Inactive'',
`ContentHash` VARCHAR(64) NOT NULL COMMENT ''规范化分区明细 Content 的 SHA256(hex),同 AreaId+Hash 去重复用'',
`Content` JSON NULL COMMENT ''分区明细快照 JSON(KpiAreaSnapshotRuleDto 数组)'',
`PublishUser` VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''发布人'',
`PublishTime` DATETIME NOT NULL COMMENT ''发布时间'',
`Description` VARCHAR(512) NULL COMMENT ''发布说明'',
`IsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''软删除:0=否,1=是'',
`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_kpi_pack_area_hash` (`AreaId`, `ContentHash`),
KEY `idx_kpi_pack_area_ver` (`AreaId`, `Version`),
KEY `idx_kpi_pack_area_type_time` (`AreaId`, `AreaType`, `PublishTime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''KPI分区发布包(价区/黑名单/偏远明细不可变快照)''',
'SELECT ''t_kpi_area_publish_record already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 表已存在时回填表/字段注释(可重复执行;不包进 PREPARE)
ALTER TABLE `t_kpi_area_publish_record`
COMMENT = 'KPI分区发布包(价区/黑名单/偏远明细不可变快照)',
MODIFY COLUMN `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
MODIFY COLUMN `AreaId` INT NOT NULL COMMENT '所属分区表 t_kpi_area.Id',
MODIFY COLUMN `AreaType` VARCHAR(32) NOT NULL COMMENT '分区类型:price=价格分区,blacklist=黑名单,remote=偏远',
MODIFY COLUMN `Version` VARCHAR(32) NOT NULL COMMENT '包版本号(yyyyMMddHHmmssfff)',
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=生效 Active,2=失效 Inactive',
MODIFY COLUMN `ContentHash` VARCHAR(64) NOT NULL COMMENT '规范化分区明细 Content 的 SHA256(hex),同 AreaId+Hash 去重复用',
MODIFY COLUMN `Content` JSON NULL COMMENT '分区明细快照 JSON(KpiAreaSnapshotRuleDto 数组)',
MODIFY COLUMN `PublishUser` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发布人',
MODIFY COLUMN `PublishTime` DATETIME NOT NULL COMMENT '发布时间',
MODIFY COLUMN `Description` VARCHAR(512) NULL COMMENT '发布说明',
MODIFY COLUMN `IsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除:0=否,1=是',
MODIFY COLUMN `CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
MODIFY COLUMN `CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
MODIFY COLUMN `UpdateUserId` INT NULL COMMENT '更新人',
MODIFY COLUMN `UpdateTime` DATETIME NULL COMMENT '更新时间';
-- =============================================================================
-- migrate_customer_export_qualification_comment.sql
-- 存量库:补 t_customer_export_qualification 表注释与字段注释(可重复执行)
-- 列类型、空值、默认值与 db.sql 一致,只回填 COMMENT。
-- 请直接执行本段,不要包进 PREPARE。
-- =============================================================================
ALTER TABLE `t_customer_export_qualification`
COMMENT = '客户出口资质备案',
MODIFY COLUMN `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
MODIFY COLUMN `CustomerId` INT NOT NULL COMMENT '归属客户 t_customer.Id',
MODIFY COLUMN `CompanyName` VARCHAR(200) NOT NULL COMMENT '企业名称',
MODIFY COLUMN `SocialCreditCode` VARCHAR(64) NOT NULL COMMENT '企业社会信用代码',
MODIFY COLUMN `DomesticConsignor` VARCHAR(200) NOT NULL COMMENT '境内发货人',
MODIFY COLUMN `DomesticConsignorCustomsCode` VARCHAR(64) NULL COMMENT '境内发货人海关代码',
MODIFY COLUMN `ProducerName` VARCHAR(200) NOT NULL COMMENT '生产销售单位',
MODIFY COLUMN `ProducerCustomsCode` VARCHAR(64) NULL COMMENT '生产销售单位海关代码',
MODIFY COLUMN `CustomsPort` VARCHAR(100) NULL COMMENT '备案海关港口地',
MODIFY COLUMN `IsDefault` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认:0-否,1-是(同客户仅一条)',
MODIFY COLUMN `Remark` VARCHAR(512) NULL COMMENT '备注',
MODIFY COLUMN `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
MODIFY COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
MODIFY COLUMN `CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
MODIFY COLUMN `CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
MODIFY COLUMN `UpdateUserId` INT NULL COMMENT '更新人',
MODIFY COLUMN `UpdateTime` DATETIME NULL COMMENT '更新时间';

100
sql/clear_biz_data.sql Normal file
View File

@ -0,0 +1,100 @@
-- =============================================================================
-- 只清空表里的数据,不删表、不改表结构(DELETE FROM,不是 DROP / TRUNCATE)
-- 1) fake_simple_admin:表名以前缀开头
-- t_order / c_ / t_pickup / t_recharge / t_customer
-- t_coupon / t_member / t_referral / t_sales / t_service
-- 2) track_center:表名以 t_order 开头
-- 请在 Navicat / DBeaver / MySQL Workbench 里整段执行(需要 DELIMITER)。
-- =============================================================================
-- 执行前先看将要清空的表
SELECT TABLE_SCHEMA, TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND (
(
TABLE_SCHEMA = 'fake_simple_admin'
AND (
TABLE_NAME LIKE 't\_order%'
OR TABLE_NAME LIKE 'c\_%'
OR TABLE_NAME LIKE 't\_pickup%'
OR TABLE_NAME LIKE 't\_recharge%'
OR TABLE_NAME LIKE 't\_customer%'
OR TABLE_NAME LIKE 't\_coupon%'
OR TABLE_NAME LIKE 't\_member%'
OR TABLE_NAME LIKE 't\_referral%'
OR TABLE_NAME LIKE 't\_sales%'
OR TABLE_NAME LIKE 't\_service%'
)
)
OR (
TABLE_SCHEMA = 'track_center'
AND TABLE_NAME LIKE 't\_order%'
)
)
ORDER BY TABLE_SCHEMA, TABLE_NAME;
DELIMITER $$
DROP PROCEDURE IF EXISTS `fake_simple_admin`.`sp_clear_biz_tables`$$
CREATE PROCEDURE `fake_simple_admin`.`sp_clear_biz_tables`()
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_schema VARCHAR(64);
DECLARE v_table VARCHAR(128);
DECLARE v_sql TEXT;
DECLARE cur CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND (
(
TABLE_SCHEMA = 'fake_simple_admin'
AND (
TABLE_NAME LIKE 't\_order%'
OR TABLE_NAME LIKE 'c\_%'
OR TABLE_NAME LIKE 't\_pickup%'
OR TABLE_NAME LIKE 't\_recharge%'
OR TABLE_NAME LIKE 't\_customer%'
OR TABLE_NAME LIKE 't\_coupon%'
OR TABLE_NAME LIKE 't\_member%'
OR TABLE_NAME LIKE 't\_referral%'
OR TABLE_NAME LIKE 't\_sales%'
OR TABLE_NAME LIKE 't\_service%'
)
)
OR (
TABLE_SCHEMA = 'track_center'
AND TABLE_NAME LIKE 't\_order%'
)
)
ORDER BY TABLE_SCHEMA, TABLE_NAME;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
SET FOREIGN_KEY_CHECKS = 0;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_schema, v_table;
IF v_done = 1 THEN
LEAVE read_loop;
END IF;
SET v_sql = CONCAT('DELETE FROM `', v_schema, '`.`', v_table, '`');
SET @stmt = v_sql;
PREPARE s FROM @stmt;
EXECUTE s;
DEALLOCATE PREPARE s;
END LOOP;
CLOSE cur;
SET FOREIGN_KEY_CHECKS = 1;
END$$
DELIMITER ;
CALL `fake_simple_admin`.`sp_clear_biz_tables`();
DROP PROCEDURE IF EXISTS `fake_simple_admin`.`sp_clear_biz_tables`;

View File

@ -1,109 +0,0 @@
-- 优惠券发放体系:未解锁状态、到期提醒标记、新人/裂变券模板(可重复执行)
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_coupon_instance'
AND COLUMN_NAME = 'ExpiryNoticeSent'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_coupon_instance`
ADD COLUMN `ExpiryNoticeSent` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''到期前3天已提醒'' AFTER `LockExpireTime`',
'SELECT ''ExpiryNoticeSent already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 新人首单券 15 元,领取后 15 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_FIRST', '新人首单券', 'fixed_off', 15.00, NULL, NULL,
0, 0, 0, 1,
2, NULL, NULL, 15,
NULL, 'newcomer', 1, '注册自动下发,仅抵基础运费,退款不退回',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_FIRST'
);
-- 新人第二单券 15 元,满 150 基础运费,解锁后 30 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_SECOND', '新人第二单券', 'fixed_off', 15.00, NULL, NULL,
150, 0, 0, 1,
3, NULL, NULL, 30,
NULL, 'newcomer', 1, '首单派送成功后解锁,实付基础运费满 150',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_SECOND'
);
-- 新人备货券 20 元,满 200,注册后 60 天
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'NEWCOMER_STOCK', '新人备货券', 'fixed_off', 20.00, NULL, NULL,
200, 0, 0, 1,
2, NULL, NULL, 60,
NULL, 'newcomer', 1, '注册自动下发,实付基础运费满 200',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'NEWCOMER_STOCK'
);
-- 裂变:老客 20 元无门槛
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'REFERRAL_INVITER', '邀请有礼券', 'fixed_off', 20.00, NULL, NULL,
0, 0, 0, 1,
2, NULL, NULL, 30,
NULL, 'referral', 1, '被邀请人首单派送成功后发给邀请人',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'REFERRAL_INVITER'
);
-- 裂变:新客额外 5 元
INSERT INTO `t_coupon_template` (
`TemplateCode`, `TemplateName`, `CouponType`, `FaceValue`, `DiscountRate`, `MaxDiscountAmount`,
`MinOrderAmount`, `TotalQuantity`, `IssuedQuantity`, `PerCustomerLimit`,
`ValidType`, `ValidStartTime`, `ValidEndTime`, `ValidDaysAfterReceive`,
`ApplyScopeJson`, `GrantChannel`, `Status`, `Remark`,
`IsDeleted`, `CreateUserId`, `CreateTime`, `UpdateUserId`, `UpdateTime`
)
SELECT
'REFERRAL_INVITEE', '邀请注册加赠券', 'fixed_off', 5.00, NULL, NULL,
0, 0, 0, 1,
2, NULL, NULL, 30,
NULL, 'referral', 1, '被邀请人注册即发 5 元券',
0, 0, NOW(), 0, NOW()
WHERE NOT EXISTS (
SELECT 1 FROM `t_coupon_template` x WHERE x.`IsDeleted` = 0 AND x.`TemplateCode` = 'REFERRAL_INVITEE'
);

View File

@ -1,18 +0,0 @@
-- 分区明细按 AreaId 全量/分页查询时避免 filesort 打满 sort_buffer(查价 LIVE 路径易踩)
SET @db := DATABASE();
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND INDEX_NAME = 'idx_kpi_info_area_id'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_kpi_area_info` ADD KEY `idx_kpi_info_area_id` (`AreaId`, `Id`)',
'SELECT ''idx_kpi_info_area_id already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -1,66 +0,0 @@
-- 存量库:分区明细增加匹配维度/方式/前缀长度(可重复执行)
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND COLUMN_NAME = 'MatchDim'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_kpi_area_info`
ADD COLUMN `MatchDim` VARCHAR(16) NOT NULL DEFAULT ''country'' COMMENT ''匹配维度:zip/state/city/country'' AFTER `RightPostcode`,
ADD COLUMN `MatchMode` VARCHAR(16) NOT NULL DEFAULT ''all'' COMMENT ''匹配方式:prefix/exact/range/all'' AFTER `MatchDim`,
ADD COLUMN `PrefixLen` INT NOT NULL DEFAULT 0 COMMENT ''邮编前缀长度(zip+prefix)'' AFTER `MatchMode`',
'SELECT ''kpi area match columns already exist'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx := (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_info'
AND INDEX_NAME = 'idx_kpi_info_area_dim'
);
SET @sql := IF(
@idx = 0,
'ALTER TABLE `t_kpi_area_info` ADD KEY `idx_kpi_info_area_dim` (`AreaId`, `CountryCode`, `MatchDim`)',
'SELECT ''idx_kpi_info_area_dim already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_kpi_area_info`
SET
`MatchDim` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' OR IFNULL(`RightPostcode`, '') <> '' THEN 'zip'
WHEN IFNULL(`City`, '') <> '' THEN 'city'
WHEN IFNULL(`Province`, '') <> '' THEN 'state'
ELSE 'country'
END,
`MatchMode` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN 'prefix'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN 'exact'
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN 'range'
WHEN IFNULL(`City`, '') <> '' OR IFNULL(`Province`, '') <> '' THEN 'exact'
ELSE 'all'
END,
`PrefixLen` = CASE
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') = '' THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND `LeftPostcode` = `RightPostcode` THEN CHAR_LENGTH(`LeftPostcode`)
WHEN IFNULL(`LeftPostcode`, '') <> '' AND IFNULL(`RightPostcode`, '') <> '' THEN LEAST(CHAR_LENGTH(`LeftPostcode`), CHAR_LENGTH(`RightPostcode`))
ELSE 0
END
WHERE IFNULL(`MatchDim`, '') IN ('', 'country')
AND (
IFNULL(`LeftPostcode`, '') <> ''
OR IFNULL(`RightPostcode`, '') <> ''
OR IFNULL(`Province`, '') <> ''
OR IFNULL(`City`, '') <> ''
);

View File

@ -1,56 +0,0 @@
-- 分区包发布表(价区 / 黑名单 / 偏远):卖价/规则发布钉包,计费按 version 加载
SET @db := DATABASE();
SET @tbl := (
SELECT COUNT(1)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_kpi_area_publish_record'
);
SET @sql := IF(
@tbl = 0,
'CREATE TABLE `t_kpi_area_publish_record` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
`AreaId` INT NOT NULL COMMENT ''所属分区表 t_kpi_area.Id'',
`AreaType` VARCHAR(32) NOT NULL COMMENT ''分区类型:price=价格分区,blacklist=黑名单,remote=偏远'',
`Version` VARCHAR(32) NOT NULL COMMENT ''包版本号(yyyyMMddHHmmssfff)'',
`Status` TINYINT NOT NULL DEFAULT 1 COMMENT ''状态:1=生效 Active,2=失效 Inactive'',
`ContentHash` VARCHAR(64) NOT NULL COMMENT ''规范化分区明细 Content 的 SHA256(hex),同 AreaId+Hash 去重复用'',
`Content` JSON NULL COMMENT ''分区明细快照 JSON(KpiAreaSnapshotRuleDto 数组)'',
`PublishUser` VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''发布人'',
`PublishTime` DATETIME NOT NULL COMMENT ''发布时间'',
`Description` VARCHAR(512) NULL COMMENT ''发布说明'',
`IsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''软删除:0=否,1=是'',
`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_kpi_pack_area_hash` (`AreaId`, `ContentHash`),
KEY `idx_kpi_pack_area_ver` (`AreaId`, `Version`),
KEY `idx_kpi_pack_area_type_time` (`AreaId`, `AreaType`, `PublishTime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''KPI分区发布包(价区/黑名单/偏远明细不可变快照)''',
'SELECT ''t_kpi_area_publish_record already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 表已存在时回填表/字段注释(可重复执行;不包进 PREPARE)
ALTER TABLE `t_kpi_area_publish_record`
COMMENT = 'KPI分区发布包(价区/黑名单/偏远明细不可变快照)',
MODIFY COLUMN `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
MODIFY COLUMN `AreaId` INT NOT NULL COMMENT '所属分区表 t_kpi_area.Id',
MODIFY COLUMN `AreaType` VARCHAR(32) NOT NULL COMMENT '分区类型:price=价格分区,blacklist=黑名单,remote=偏远',
MODIFY COLUMN `Version` VARCHAR(32) NOT NULL COMMENT '包版本号(yyyyMMddHHmmssfff)',
MODIFY COLUMN `Status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=生效 Active,2=失效 Inactive',
MODIFY COLUMN `ContentHash` VARCHAR(64) NOT NULL COMMENT '规范化分区明细 Content 的 SHA256(hex),同 AreaId+Hash 去重复用',
MODIFY COLUMN `Content` JSON NULL COMMENT '分区明细快照 JSON(KpiAreaSnapshotRuleDto 数组)',
MODIFY COLUMN `PublishUser` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发布人',
MODIFY COLUMN `PublishTime` DATETIME NOT NULL COMMENT '发布时间',
MODIFY COLUMN `Description` VARCHAR(512) NULL COMMENT '发布说明',
MODIFY COLUMN `IsDeleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除:0=否,1=是',
MODIFY COLUMN `CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
MODIFY COLUMN `CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
MODIFY COLUMN `UpdateUserId` INT NULL COMMENT '更新人',
MODIFY COLUMN `UpdateTime` DATETIME NULL COMMENT '更新时间';

View File

@ -1,216 +0,0 @@
-- 旧会员体系 → V0~V5(可重复执行)
-- old: normal/silver/gold/diamond
-- new: V0 普通 / V1 铜卡(新增) / V2 银卡 / V3 金卡 / V4 钻石 / V5 皇冠(新增)
SET @db := DATABASE();
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_member_level'
AND COLUMN_NAME = 'CanDowngrade'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_member_level`
ADD COLUMN `CanDowngrade` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''V4/V5 允许逐级降级'' AFTER `ExtraDiscountRate`',
'SELECT ''CanDowngrade already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ---------- 把旧行升级成新编码(仅当目标 V* 还不存在)----------
UPDATE `t_member_level`
SET
`LevelCode` = 'V0',
`LevelName` = '普通会员',
`MinPoints` = 0,
`ExtraDiscountRate` = 0.00,
`CanDowngrade` = 0,
`Sort` = 0,
`BenefitDesc` = '新注册 / 小单试单卖家',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'normal'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V0') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V2',
`LevelName` = '银卡会员',
`MinPoints` = 60000,
`ExtraDiscountRate` = 0.02,
`CanDowngrade` = 0,
`Sort` = 2,
`BenefitDesc` = '主力中小卖家,基础运费 98 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'silver'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V2') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V3',
`LevelName` = '金卡会员',
`MinPoints` = 120000,
`ExtraDiscountRate` = 0.03,
`CanDowngrade` = 0,
`Sort` = 3,
`BenefitDesc` = '优质中型卖家,基础运费 97 折;此级及以下只升不降',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'gold'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V3') t);
UPDATE `t_member_level`
SET
`LevelCode` = 'V4',
`LevelName` = '钻石会员',
`MinPoints` = 240000,
`ExtraDiscountRate` = 0.04,
`CanDowngrade` = 1,
`Sort` = 4,
`BenefitDesc` = '中大卖家,基础运费 96 折;滚动积分不足可降到金卡',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND LOWER(`LevelCode`) = 'diamond'
AND NOT EXISTS (SELECT 1 FROM (SELECT 1 FROM `t_member_level` x WHERE x.`IsDeleted` = 0 AND x.`LevelCode` = 'V4') t);
-- ---------- 缺档则插入(铜卡、皇冠,以及未被旧行带过来的档)----------
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V0', '普通会员', 0, 0.00, 0, 0, '新注册 / 小单试单卖家', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V0' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V1', '铜卡会员', 24000, 0.01, 0, 1, '起步稳定发货卖家,基础运费 99 折', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V1' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V2', '银卡会员', 60000, 0.02, 0, 2, '主力中小卖家,基础运费 98 折', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V2' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V3', '金卡会员', 120000, 0.03, 0, 3, '优质中型卖家,基础运费 97 折;此级及以下只升不降', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V3' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V4', '钻石会员', 240000, 0.04, 1, 4, '中大卖家,基础运费 96 折;滚动积分不足可降到金卡', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V4' AND `IsDeleted` = 0);
INSERT INTO `t_member_level`
(`LevelCode`, `LevelName`, `MinPoints`, `ExtraDiscountRate`, `CanDowngrade`, `Sort`, `BenefitDesc`, `Status`, `IsDeleted`, `CreateTime`)
SELECT 'V5', '皇冠会员', 600000, 0.05, 1, 5, '顶级头部卖家,基础运费 95 折封顶;滚动积分不足可降到钻石', 2, 0, NOW()
WHERE NOT EXISTS (SELECT 1 FROM `t_member_level` WHERE `LevelCode` = 'V5' AND `IsDeleted` = 0);
-- ---------- 旧编码与新档并存时:客户挂到新档,旧行停用 ----------
UPDATE `t_customer` c
INNER JOIN `t_member_level` old ON old.`Id` = c.`MemberLevelId` AND old.`IsDeleted` = 0
INNER JOIN `t_member_level` canon ON canon.`IsDeleted` = 0 AND canon.`LevelCode` = CASE LOWER(old.`LevelCode`)
WHEN 'normal' THEN 'V0'
WHEN 'silver' THEN 'V2'
WHEN 'gold' THEN 'V3'
WHEN 'diamond' THEN 'V4'
ELSE NULL
END
SET c.`MemberLevelId` = canon.`Id`, c.`UpdateTime` = NOW()
WHERE LOWER(old.`LevelCode`) IN ('normal', 'silver', 'gold', 'diamond')
AND old.`Id` <> canon.`Id`;
UPDATE `t_member_level` old
INNER JOIN `t_member_level` canon ON canon.`IsDeleted` = 0 AND canon.`LevelCode` = CASE LOWER(old.`LevelCode`)
WHEN 'normal' THEN 'V0'
WHEN 'silver' THEN 'V2'
WHEN 'gold' THEN 'V3'
WHEN 'diamond' THEN 'V4'
ELSE NULL
END
SET
old.`IsDeleted` = 1,
old.`Status` = 1,
old.`UpdateTime` = NOW()
WHERE old.`IsDeleted` = 0
AND LOWER(old.`LevelCode`) IN ('normal', 'silver', 'gold', 'diamond')
AND old.`Id` <> canon.`Id`;
-- ---------- 校正 V0~V5 门槛/折扣(已占用客户的行 Id 不变)----------
UPDATE `t_member_level`
SET
`LevelName` = '普通会员',
`MinPoints` = 0,
`ExtraDiscountRate` = 0.00,
`CanDowngrade` = 0,
`Sort` = 0,
`BenefitDesc` = '新注册 / 小单试单卖家',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V0';
UPDATE `t_member_level`
SET
`LevelName` = '铜卡会员',
`MinPoints` = 24000,
`ExtraDiscountRate` = 0.01,
`CanDowngrade` = 0,
`Sort` = 1,
`BenefitDesc` = '起步稳定发货卖家,基础运费 99 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V1';
UPDATE `t_member_level`
SET
`LevelName` = '银卡会员',
`MinPoints` = 60000,
`ExtraDiscountRate` = 0.02,
`CanDowngrade` = 0,
`Sort` = 2,
`BenefitDesc` = '主力中小卖家,基础运费 98 折',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V2';
UPDATE `t_member_level`
SET
`LevelName` = '金卡会员',
`MinPoints` = 120000,
`ExtraDiscountRate` = 0.03,
`CanDowngrade` = 0,
`Sort` = 3,
`BenefitDesc` = '优质中型卖家,基础运费 97 折;此级及以下只升不降',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V3';
UPDATE `t_member_level`
SET
`LevelName` = '钻石会员',
`MinPoints` = 240000,
`ExtraDiscountRate` = 0.04,
`CanDowngrade` = 1,
`Sort` = 4,
`BenefitDesc` = '中大卖家,基础运费 96 折;滚动积分不足可降到金卡',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V4';
UPDATE `t_member_level`
SET
`LevelName` = '皇冠会员',
`MinPoints` = 600000,
`ExtraDiscountRate` = 0.05,
`CanDowngrade` = 1,
`Sort` = 5,
`BenefitDesc` = '顶级头部卖家,基础运费 95 折封顶;滚动积分不足可降到钻石',
`Status` = 2,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0 AND `LevelCode` = 'V5';

View File

@ -1,97 +0,0 @@
-- 邀请奖励:按实付基础运费百分比、T+N 工作日入运费余额、退款标记(可重复执行)
SET @db := DATABASE();
-- ---------- t_order.IsRefunded ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_order'
AND COLUMN_NAME = 'IsRefunded'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_order`
ADD COLUMN `IsRefunded` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否已退款(分成计次排除)'' AFTER `IsPaid`',
'SELECT ''t_order.IsRefunded already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ---------- t_referral_commission_rule 新字段 ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_rule'
AND COLUMN_NAME = 'MinInviteLevelCode'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_rule`
ADD COLUMN `MinInviteLevelCode` VARCHAR(16) NOT NULL DEFAULT ''V1'' COMMENT ''邀请资格最低等级'' AFTER `MonthlyCap`,
ADD COLUMN `MinPaidBaseFreight` DECIMAL(18,2) NOT NULL DEFAULT 50.00 COMMENT ''计奖最低实付基础运费'' AFTER `MinInviteLevelCode`,
ADD COLUMN `FirstOrderRate` DECIMAL(8,4) NOT NULL DEFAULT 0.0500 COMMENT ''首单奖励比例'' AFTER `MinPaidBaseFreight`,
ADD COLUMN `FirstOrderCap` DECIMAL(18,2) NOT NULL DEFAULT 50.00 COMMENT ''首单奖励封顶'' AFTER `FirstOrderRate`,
ADD COLUMN `FollowOrderRate` DECIMAL(8,4) NOT NULL DEFAULT 0.0300 COMMENT ''后续单奖励比例'' AFTER `FirstOrderCap`,
ADD COLUMN `FollowOrderCap` DECIMAL(18,2) NOT NULL DEFAULT 30.00 COMMENT ''后续单奖励封顶'' AFTER `FollowOrderRate`,
ADD COLUMN `FollowWindowDays` INT NOT NULL DEFAULT 90 COMMENT ''后续单注册后天数窗口'' AFTER `FollowOrderCap`,
ADD COLUMN `MaxRewardOrders` INT NOT NULL DEFAULT 6 COMMENT ''最多计奖订单数'' AFTER `FollowWindowDays`,
ADD COLUMN `SettleBusinessDays` INT NOT NULL DEFAULT 3 COMMENT ''派送成功后结算工作日'' AFTER `MaxRewardOrders`',
'SELECT ''t_referral_commission_rule reward columns already exist'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_referral_commission_rule`
SET
`CommissionMode` = 'percent_of_paid_base_freight',
`SettleTriggerOrderStatus` = 'deliverySuccess',
`SettleBusinessDays` = CASE WHEN `SettleBusinessDays` > 0 THEN `SettleBusinessDays` ELSE 3 END,
`FreezeDays` = CASE WHEN `SettleBusinessDays` > 0 THEN `SettleBusinessDays` ELSE 3 END,
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0;
-- ---------- t_referral_commission_detail ----------
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_detail'
AND COLUMN_NAME = 'SettleDueTime'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_detail`
ADD COLUMN `SettleDueTime` DATETIME NULL COMMENT ''计划结算时间'' AFTER `EligibleTime`',
'SELECT ''SettleDueTime already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @col := (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 't_referral_commission_detail'
AND COLUMN_NAME = 'RewardOrderIndex'
);
SET @sql := IF(
@col = 0,
'ALTER TABLE `t_referral_commission_detail`
ADD COLUMN `RewardOrderIndex` INT NOT NULL DEFAULT 0 COMMENT ''被邀请人第几单'' AFTER `SettleDueTime`',
'SELECT ''RewardOrderIndex already exists'' AS info'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `t_coupon_template`
SET
`Remark` = '被邀请人注册即发 5 元券',
`UpdateTime` = NOW()
WHERE `IsDeleted` = 0
AND `TemplateCode` = 'REFERRAL_INVITEE';

View File

@ -154,6 +154,54 @@ public class MarketingCouponManager : IScopedDependency
} }
} }
/// <summary>
/// 被邀请人首单派送成功后,向邀请人发放裂变券(REFERRAL_INVITER)。
/// </summary>
public async Task GrantInviterCouponAsync(
int referrerMainCustomerId,
int inviteeMainCustomerId,
CancellationToken cancellationToken = default)
{
if (referrerMainCustomerId <= 0
|| inviteeMainCustomerId <= 0
|| referrerMainCustomerId == inviteeMainCustomerId)
{
return;
}
var templates = await _templateRepository.GetListAsync(
x => x.Status == CouponTemplateStatus.Active
&& x.GrantChannel == MarketingGrantChannels.Referral
&& x.TemplateCode == MarketingCouponTemplateCodes.ReferralInviter);
if (templates.Count == 0)
{
return;
}
var remark = $"referral_inviter_first_delivery:{inviteeMainCustomerId}";
var alreadyGranted = await _auditLogRepository.AnyAsync(
x => x.Action == MarketingAuditActions.GrantCoupon
&& x.TargetType == "customer"
&& x.TargetId == referrerMainCustomerId
&& x.Remark == remark,
cancellationToken);
if (alreadyGranted)
{
return;
}
foreach (var template in templates)
{
try
{
await GrantAsync(template.Id, referrerMainCustomerId, remark, cancellationToken);
}
catch (BusinessException)
{
}
}
}
public async Task OnFirstDeliverySuccessAsync(Order order, CancellationToken cancellationToken = default) public async Task OnFirstDeliverySuccessAsync(Order order, CancellationToken cancellationToken = default)
{ {
if (order.CustomerId is not > 0) if (order.CustomerId is not > 0)
@ -182,6 +230,29 @@ public class MarketingCouponManager : IScopedDependency
} }
await UnlockPendingCouponsAsync(mainCustomer.Id, cancellationToken); await UnlockPendingCouponsAsync(mainCustomer.Id, cancellationToken);
await TryGrantInviterCouponOnFirstDeliveryAsync(mainCustomer, cancellationToken);
}
private async Task TryGrantInviterCouponOnFirstDeliveryAsync(
Customer inviteeMainCustomer,
CancellationToken cancellationToken)
{
if (inviteeMainCustomer.ReferrerCustomerId is not > 0
|| inviteeMainCustomer.ReferrerCustomerId.Value == inviteeMainCustomer.Id)
{
return;
}
var referrer = await _customerRepository.FirstOrDefaultAsync(
x => x.Id == inviteeMainCustomer.ReferrerCustomerId.Value, cancellationToken);
if (referrer == null)
{
return;
}
var referrerMain = await MarketingCustomerHelper.ResolveMarketingMainCustomerAsync(
_customerRepository, referrer, cancellationToken);
await GrantInviterCouponAsync(referrerMain.Id, inviteeMainCustomer.Id, cancellationToken);
} }
public async Task LockForOrderAsync(int couponInstanceId, int mainCustomerId, int orderId, CancellationToken cancellationToken = default) public async Task LockForOrderAsync(int couponInstanceId, int mainCustomerId, int orderId, CancellationToken cancellationToken = default)