fix: 字典创建支持恢复软删项并完善类型迁移脚本

批量导入时若同类型编码曾被软删,改为恢复并更新而非插入,避免唯一键冲突 500;迁移脚本补全预置类型中文名并支持覆盖 Remark/TypeSort。
This commit is contained in:
zzh 2026-07-21 18:49:59 +08:00
parent 93cf065165
commit 5a2d56986a
4 changed files with 104 additions and 10 deletions

View File

@ -1,11 +1,12 @@
-- 已有库增量:字典类型元数据 t_dict_type(类型编码 + 备注)
-- 可重复执行(IF NOT EXISTS)
-- 已有库增量:字典类型元数据 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 '字典类型编码',
`DictType` VARCHAR(64) NOT NULL COMMENT '字典类型编码,与 t_dict.DictType 对应',
`Remark` VARCHAR(512) NULL COMMENT '字典类型备注',
`TypeSort` INT NOT NULL DEFAULT 0 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 '创建人',
@ -16,8 +17,74 @@ CREATE TABLE IF NOT EXISTS `t_dict_type` (
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

@ -184,14 +184,28 @@ public class DictService : ApplicationService
[HttpPost("/system/dict/create")]
public async Task CreateAsync(DictCreateInput input)
{
await ValidateDictInputAsync(input.DictType, input.DictCode, input.DictName, null);
await EnsureDictTypeMetaAsync(input.DictType.Trim());
var dictType = input.DictType.Trim();
var dictCode = input.DictCode.Trim();
var dictName = input.DictName.Trim();
await ValidateDictInputAsync(dictType, dictCode, dictName, null);
await EnsureDictTypeMetaAsync(dictType);
var deleted = await DictRepository.GetByTypeAndCodeIncludingDeletedAsync(dictType, dictCode);
if (deleted != null && deleted.IsDeleted)
{
deleted.IsDeleted = false;
deleted.DictSort = input.DictSort;
deleted.DictName = dictName;
await DictRepository.UpdateAsync(deleted);
return;
}
await DictRepository.InsertAsync(new Dict
{
DictType = input.DictType.Trim(),
DictType = dictType,
DictSort = input.DictSort,
DictCode = input.DictCode.Trim(),
DictName = input.DictName.Trim()
DictCode = dictCode,
DictName = dictName
});
}

View File

@ -7,5 +7,7 @@ public interface IDictRepository : IRepository<Dict>
{
Task<Dict?> GetByTypeAndCodeAsync(string dictType, string dictCode, CancellationToken cancellationToken = default);
Task<Dict?> GetByTypeAndCodeIncludingDeletedAsync(string dictType, string dictCode, CancellationToken cancellationToken = default);
Task<List<(string DictType, int ItemCount)>> GetItemCountByTypeAsync(CancellationToken cancellationToken = default);
}

View File

@ -13,6 +13,17 @@ public class DictRepository : OmsSqlSugarRepository<Dict>, IDictRepository, ISco
return FirstOrDefaultAsync(x => x.DictType == dictType && x.DictCode == dictCode, cancellationToken);
}
public async Task<Dict?> GetByTypeAndCodeIncludingDeletedAsync(
string dictType,
string dictCode,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<Dict>()
.ClearFilter()
.FirstAsync(x => x.DictType == dictType && x.DictCode == dictCode, cancellationToken);
}
public async Task<List<(string DictType, int ItemCount)>> GetItemCountByTypeAsync(CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);