using System.Data;
using System.Diagnostics;
using System.Text;
using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.Infrastructure;
///
/// S0 Excel 字段口径迁移:将现有旧列名重命名为 Excel 字段名。
/// 仅处理文档中已确认映射的字段,不自动补 Excel 独有字段。
///
public static class AidopS0ExcelColumnMigration
{
private sealed record ColumnRename(string TableName, string OldColumnName, string NewColumnName);
private sealed record PrimaryKeyRepair(string TableName, string PkColumnName, bool IsIdentity, string PkComment, params string[] ShadowColumns);
private sealed record MySqlColumnMeta(
string ColumnName,
string ColumnType,
string IsNullable,
string? ColumnDefault,
string? Extra,
string? ColumnComment,
string? CollationName,
string? CharacterSetName);
private static readonly ColumnRename[] Renames =
[
new("CostCtrMaster", "cost_ctr", "CostCtr"),
new("CostCtrMaster", "create_time", "CreateTime"),
new("CostCtrMaster", "create_user", "CreateUser"),
new("CostCtrMaster", "descr", "Descr"),
new("CostCtrMaster", "domain_code", "Domain"),
new("CostCtrMaster", "eff_time", "EffTime"),
new("CostCtrMaster", "rec_id", "RecID"),
new("CostCtrMaster", "ufld1", "Ufld1"),
new("CostCtrMaster", "update_time", "UpdateTime"),
new("CostCtrMaster", "update_user", "UpdateUser"),
new("CustMaster", "create_time", "CreateTime"),
new("CustMaster", "create_user", "CreateUser"),
new("CustMaster", "credit_rating", "CreditRating"),
new("CustMaster", "curr", "Curr"),
new("CustMaster", "cust", "Cust"),
new("CustMaster", "domain_code", "Domain"),
new("CustMaster", "is_active", "IsActive"),
new("CustMaster", "is_confirm", "IsConfirm"),
new("CustMaster", "rec_id", "RecID"),
new("CustMaster", "remarks", "Remarks"),
new("CustMaster", "ship_to", "ShipTo"),
new("CustMaster", "slspsn1", "Slspsn1"),
new("CustMaster", "slspsn2", "Slspsn2"),
new("CustMaster", "sort_name", "SortName"),
new("CustMaster", "tax_class", "TaxClass"),
new("CustMaster", "tax_in", "TaxIn"),
new("CustMaster", "update_time", "UpdateTime"),
new("CustMaster", "update_user", "UpdateUser"),
new("DepartmentMaster", "create_time", "CreateTime"),
new("DepartmentMaster", "create_user", "CreateUser"),
new("DepartmentMaster", "department", "Department"),
new("DepartmentMaster", "descr", "Descr"),
new("DepartmentMaster", "domain_code", "Domain"),
new("DepartmentMaster", "is_active", "IsActive"),
new("DepartmentMaster", "rec_id", "RecID"),
new("DepartmentMaster", "update_time", "UpdateTime"),
new("DepartmentMaster", "update_user", "UpdateUser"),
new("DevMonitor", "code_descr", "CodeDescr"),
new("DevMonitor", "code_type", "CodeType"),
new("DevMonitor", "create_time", "CreateTime"),
new("DevMonitor", "create_user", "CreateUser"),
new("DevMonitor", "device_descr", "DeviceDescr"),
new("DevMonitor", "device_number", "DeviceNumber"),
new("DevMonitor", "domain", "Domain"),
new("DevMonitor", "id", "RecID"),
new("DevMonitor", "low_limit", "LowLimit"),
new("DevMonitor", "message_code", "MessageCode"),
new("DevMonitor", "std_value", "StdValue"),
new("DevMonitor", "top_limit", "TopLimit"),
new("DevMonitor", "ufld1", "Ufld1"),
new("DevMonitor", "um", "UM"),
new("DevMonitor", "update_time", "UpdateTime"),
new("DevMonitor", "update_user", "UpdateUser"),
new("DevMonitor", "value_type", "ValueType"),
new("EmpSkills", "create_time", "CreateTime"),
new("EmpSkills", "create_user", "CreateUser"),
new("EmpSkills", "domain", "Domain"),
new("EmpSkills", "efficiency_coefficient", "EfficiencyCoefficient"),
new("EmpSkills", "employee", "Employee"),
new("EmpSkills", "employee_id", "EmployeeID"),
new("EmpSkills", "employee_name", "EmployeeName"),
new("EmpSkills", "id", "RecID"),
new("EmpSkills", "site", "Site"),
new("EmpSkills", "skill_description", "SkillDescription"),
new("EmpSkills", "skill_level", "SkillLevel"),
new("EmpSkills", "skill_no", "SkillNo"),
new("EmpSkills", "update_time", "UpdateTime"),
new("EmpSkills", "update_user", "UpdateUser"),
new("EmpWorkDutyMaster", "create_time", "CreateTime"),
new("EmpWorkDutyMaster", "create_user", "CreateUser"),
new("EmpWorkDutyMaster", "domain_code", "Domain"),
new("EmpWorkDutyMaster", "duty", "Duty"),
new("EmpWorkDutyMaster", "emp_type", "EmpType"),
new("EmpWorkDutyMaster", "employee", "Employee"),
new("EmpWorkDutyMaster", "item_num1", "ItemNum1"),
new("EmpWorkDutyMaster", "item_num2", "ItemNum2"),
new("EmpWorkDutyMaster", "location", "Location"),
new("EmpWorkDutyMaster", "prod_line", "ProdLine"),
new("EmpWorkDutyMaster", "rec_id", "RecID"),
new("EmpWorkDutyMaster", "ufld2", "Ufld2"),
new("EmpWorkDutyMaster", "update_time", "UpdateTime"),
new("EmpWorkDutyMaster", "update_user", "UpdateUser"),
new("EmployeeMaster", "birth_date", "BirthDate"),
new("EmployeeMaster", "car_id", "CarId"),
new("EmployeeMaster", "create_time", "CreateTime"),
new("EmployeeMaster", "create_user", "CreateUser"),
new("EmployeeMaster", "date_employed", "DateEmployed"),
new("EmployeeMaster", "date_terminated", "DateTerminated"),
new("EmployeeMaster", "default_work_location", "DefaultWorkLocation"),
new("EmployeeMaster", "department", "Department"),
new("EmployeeMaster", "domain_code", "Domain"),
new("EmployeeMaster", "email", "Email"),
new("EmployeeMaster", "employee", "Employee"),
new("EmployeeMaster", "employment_status", "EmploymentStatus"),
new("EmployeeMaster", "is_active", "IsActive"),
new("EmployeeMaster", "job_title", "JobTitle"),
new("EmployeeMaster", "marital_status", "MaritalStatus"),
new("EmployeeMaster", "name", "Name"),
new("EmployeeMaster", "phone", "Phone"),
new("EmployeeMaster", "rec_id", "RecID"),
new("EmployeeMaster", "sex", "Sex"),
new("EmployeeMaster", "update_time", "UpdateTime"),
new("EmployeeMaster", "update_user", "UpdateUser"),
new("EmployeeMaster", "work_ctr", "WorkCtr"),
new("ItemMaster", "allocate_single_lot", "AllocateSingleLot"),
new("ItemMaster", "auto_lot_nums", "AutoLotNums"),
new("ItemMaster", "commodity_code", "CommodityCode"),
new("ItemMaster", "create_time", "CreateTime"),
new("ItemMaster", "create_user", "CreateUser"),
new("ItemMaster", "days_between_pm", "DaysBetweenPM"),
new("ItemMaster", "default_shelf", "DefaultShelf"),
new("ItemMaster", "descr", "Descr"),
new("ItemMaster", "descr1", "Descr1"),
new("ItemMaster", "domain_code", "Domain"),
new("ItemMaster", "drawing", "Drawing"),
new("ItemMaster", "emt_type", "EMTType"),
new("ItemMaster", "ins_lt", "InsLT"),
new("ItemMaster", "inspect", "Inspect"),
new("ItemMaster", "install", "Install"),
new("ItemMaster", "is_active", "IsActive"),
new("ItemMaster", "is_confirm", "IsConfirm"),
new("ItemMaster", "is_main_mas", "IsMainMas"),
new("ItemMaster", "issue_policy", "IssuePolicy"),
new("ItemMaster", "item_num", "ItemNum"),
new("ItemMaster", "item_type", "ItemType"),
new("ItemMaster", "key_item", "KeyItem"),
new("ItemMaster", "length", "Length"),
new("ItemMaster", "location", "Location"),
new("ItemMaster", "location_type", "LocationType"),
new("ItemMaster", "lot_serial_control", "LotSerialControl"),
new("ItemMaster", "max_ord", "MaxOrd"),
new("ItemMaster", "mfg_lt", "MfgLT"),
new("ItemMaster", "mfgmttr", "MFGMTTR"),
new("ItemMaster", "min_ord", "MinOrd"),
new("ItemMaster", "min_ord_sales", "MinOrdSales"),
new("ItemMaster", "net_weight", "NetWeight"),
new("ItemMaster", "net_weight_um", "NetWeightUM"),
new("ItemMaster", "ord_mult", "OrdMult"),
new("ItemMaster", "owner_application", "OwnerApplication"),
new("ItemMaster", "pkg_code", "PkgCode"),
new("ItemMaster", "planner", "Planner"),
new("ItemMaster", "pur_lt", "PurLT"),
new("ItemMaster", "pur_mfg", "PurMfg"),
new("ItemMaster", "rec_id", "RecID"),
new("ItemMaster", "remark", "Remark"),
new("ItemMaster", "rev", "Rev"),
new("ItemMaster", "safety_stk", "SafetyStk"),
new("ItemMaster", "size", "Size"),
new("ItemMaster", "size_um", "SizeUM"),
new("ItemMaster", "status", "Status"),
new("ItemMaster", "stock_turn_over", "StockTurnOver"),
new("ItemMaster", "trace_detail", "TraceDetail"),
new("ItemMaster", "um", "UM"),
new("ItemMaster", "update_time", "UpdateTime"),
new("ItemMaster", "update_user", "UpdateUser"),
new("ItemOpCondition", "code_type", "CodeType"),
new("ItemOpCondition", "create_time", "CreateTime"),
new("ItemOpCondition", "create_user", "CreateUser"),
new("ItemOpCondition", "descr", "Descr"),
new("ItemOpCondition", "domain", "Domain"),
new("ItemOpCondition", "id", "RecID"),
new("ItemOpCondition", "is_main", "IsMain"),
new("ItemOpCondition", "item_code", "ItemCode"),
new("ItemOpCondition", "item_num", "ItemNum"),
new("ItemOpCondition", "line", "Line"),
new("ItemOpCondition", "op", "Op"),
new("ItemOpCondition", "typed", "Typed"),
new("ItemOpCondition", "update_time", "UpdateTime"),
new("ItemOpCondition", "update_user", "UpdateUser"),
new("ItemPackMaster", "create_time", "CreateTime"),
new("ItemPackMaster", "create_user", "CreateUser"),
new("ItemPackMaster", "cust_item", "CustItem"),
new("ItemPackMaster", "domain_code", "Domain"),
new("ItemPackMaster", "high", "High"),
new("ItemPackMaster", "is_active", "IsActive"),
new("ItemPackMaster", "iss_specific", "IssSpecific"),
new("ItemPackMaster", "item_num", "ItemNum"),
new("ItemPackMaster", "length", "Length"),
new("ItemPackMaster", "net_weight", "NetWeight"),
new("ItemPackMaster", "packing_qty", "PackingQty"),
new("ItemPackMaster", "packing_type", "PackingType"),
new("ItemPackMaster", "rec_id", "RecID"),
new("ItemPackMaster", "remark", "Remark"),
new("ItemPackMaster", "small_packing_qty", "SmallPackingQty"),
new("ItemPackMaster", "update_time", "UpdateTime"),
new("ItemPackMaster", "update_user", "UpdateUser"),
new("ItemPackMaster", "weight_um", "WeightUM"),
new("ItemPackMaster", "width", "Width"),
new("ItemSubstituteDetail", "create_time", "CreateTime"),
new("ItemSubstituteDetail", "create_user", "CreateUser"),
new("ItemSubstituteDetail", "domain", "Domain"),
new("ItemSubstituteDetail", "id", "RecID"),
new("ItemSubstituteDetail", "item_num", "ItemNum"),
new("ItemSubstituteDetail", "parent_item", "ParentItem"),
new("ItemSubstituteDetail", "remark", "Remark"),
new("ItemSubstituteDetail", "substitute_item", "SubstituteItem"),
new("ItemSubstituteDetail", "substitute_qty", "SubstituteQty"),
new("ItemSubstituteDetail", "update_time", "UpdateTime"),
new("ItemSubstituteDetail", "update_user", "UpdateUser"),
new("LineMaster", "create_time", "CreateTime"),
new("LineMaster", "create_user", "CreateUser"),
new("LineMaster", "domain", "Domain"),
new("LineMaster", "id", "RecID"),
new("LineMaster", "is_active", "IsActive"),
new("LineMaster", "line", "Line"),
new("LineMaster", "line_category", "LineCategory"),
new("LineMaster", "line_type", "LineType"),
new("LineMaster", "location", "Location"),
new("LineMaster", "mid_location", "MidLocation"),
new("LineMaster", "picking_location", "PickingLocation"),
new("LineMaster", "update_time", "UpdateTime"),
new("LineMaster", "update_user", "UpdateUser"),
new("LineMaster", "v_location", "VLocation"),
new("LineMaster", "workshop", "Workshop"),
new("LineSkillDetail", "create_time", "CreateTime"),
new("LineSkillMaster", "create_time", "CreateTime"),
new("LineSkillMaster", "create_user", "CreateUser"),
new("LineSkillMaster", "domain", "Domain"),
new("LineSkillMaster", "id", "RecID"),
new("LineSkillMaster", "job_descr", "JOBDescr"),
new("LineSkillMaster", "job_no", "JOBNo"),
new("LineSkillMaster", "prod_line", "ProdLine"),
new("LineSkillMaster", "update_time", "UpdateTime"),
new("LineSkillMaster", "update_user", "UpdateUser"),
new("PriorityCode", "create_time", "CreateTime"),
new("PriorityCode", "create_user", "CreateUser"),
new("PriorityCode", "descr", "Descr"),
new("PriorityCode", "domain_code", "Domain"),
new("PriorityCode", "is_active", "IsActive"),
new("PriorityCode", "priority", "Priority"),
new("PriorityCode", "rec_id", "RecID"),
new("PriorityCode", "source_column", "SourceColumn"),
new("PriorityCode", "source_id", "SourceID"),
new("PriorityCode", "source_table", "SourceTable"),
new("PriorityCode", "source_type", "SourceType"),
new("PriorityCode", "update_time", "UpdateTime"),
new("PriorityCode", "update_user", "UpdateUser"),
new("PriorityCode", "value", "Value"),
new("PriorityCode", "value_id", "ValueID"),
new("PriorityCode", "value_type", "ValueType"),
new("ProdLineDetail", "create_time", "CreateTime"),
new("ProdLineDetail", "create_user", "CreateUser"),
new("ProdLineDetail", "domain", "Domain"),
new("ProdLineDetail", "id", "RecID"),
new("ProdLineDetail", "is_active", "IsActive"),
new("ProdLineDetail", "is_confirm", "IsConfirm"),
new("ProdLineDetail", "line", "Line"),
new("ProdLineDetail", "mold_type_code", "MoldTypeCode"),
new("ProdLineDetail", "op", "Op"),
new("ProdLineDetail", "part", "Part"),
new("ProdLineDetail", "rate", "Rate"),
new("ProdLineDetail", "setup_time", "SetupTime"),
new("ProdLineDetail", "site", "Site"),
new("ProdLineDetail", "skill_no", "SkillNo"),
new("ProdLineDetail", "standard_staff_count", "StandardStaffCount"),
new("ProdLineDetail", "start", "Start"),
new("ProdLineDetail", "update_time", "UpdateTime"),
new("ProdLineDetail", "update_user", "UpdateUser"),
new("ProductStructureMaster", "end_eff", "EndEff"),
new("ProductStructureMaster", "id", "RecID"),
new("ProductStructureMaster", "qty", "Qty"),
new("ProductStructureMaster", "qty_consumed", "QtyConsumed"),
new("ProductStructureMaster", "refs", "Refs"),
new("ProductStructureMaster", "remark", "Remark"),
new("ProductStructureMaster", "scrap", "Scrap"),
new("ProductStructureMaster", "start_eff", "StartEff"),
new("ProductStructureMaster", "structure_type", "StructureType"),
new("ProductStructureMaster", "um", "UM"),
new("ProductStructureOp", "id", "RecID"),
new("ProductStructureOp", "op", "Op"),
new("RoutingOpDetail", "comment_index", "CommentIndex"),
new("RoutingOpDetail", "create_user", "CreateUser"),
new("RoutingOpDetail", "id", "RecID"),
new("RoutingOpDetail", "is_confirm", "IsConfirm"),
new("RoutingOpDetail", "u_deci1", "UDeci1"),
new("RoutingOpDetail", "u_deci2", "UDeci2"),
new("RoutingOpDetail", "u_deci3", "UDeci3"),
new("RoutingOpDetail", "u_deci4", "UDeci4"),
new("RoutingOpDetail", "u_deci5", "UDeci5"),
new("RoutingOpDetail", "ufld1", "Ufld1"),
new("RoutingOpDetail", "ufld2", "Ufld2"),
new("RoutingOpDetail", "ufld3", "Ufld3"),
new("RoutingOpDetail", "ufld4", "Ufld4"),
new("RoutingOpDetail", "ufld5", "Ufld5"),
new("RoutingOpDetail", "ufld6", "Ufld6"),
new("RoutingOpDetail", "ufld7", "Ufld7"),
new("RoutingOpDetail", "ufld8", "Ufld8"),
new("RoutingOpDetail", "ufld9", "Ufld9"),
new("RoutingOpDetail", "update_user", "UpdateUser"),
new("SuppMaster", "ap_end_day1", "APEndDay1"),
new("SuppMaster", "ap_end_day2", "APEndDay2"),
new("SuppMaster", "ap_start_day1", "APStartDay1"),
new("SuppMaster", "ap_start_day2", "APStartDay2"),
new("SuppMaster", "cr_terms", "CrTerms"),
new("SuppMaster", "create_time", "CreateTime"),
new("SuppMaster", "create_user", "CreateUser"),
new("SuppMaster", "curr", "Curr"),
new("SuppMaster", "domain_code", "Domain"),
new("SuppMaster", "is_active", "IsActive"),
new("SuppMaster", "pur_contact", "PurContact"),
new("SuppMaster", "rec_id", "RecID"),
new("SuppMaster", "remark", "Remark"),
new("SuppMaster", "sort_name", "SortName"),
new("SuppMaster", "supp", "Supp"),
new("SuppMaster", "tax_class", "TaxClass"),
new("SuppMaster", "tax_in", "TaxIn"),
new("SuppMaster", "type", "Type"),
new("SuppMaster", "update_time", "UpdateTime"),
new("SuppMaster", "update_user", "UpdateUser"),
new("WorkCtrMaster", "create_time", "CreateTime"),
new("WorkCtrMaster", "create_user", "CreateUser"),
new("WorkCtrMaster", "department", "Department"),
new("WorkCtrMaster", "descr", "Descr"),
new("WorkCtrMaster", "domain", "Domain"),
new("WorkCtrMaster", "id", "RecID"),
new("WorkCtrMaster", "is_active", "IsActive"),
new("WorkCtrMaster", "update_time", "UpdateTime"),
new("WorkCtrMaster", "update_user", "UpdateUser"),
new("WorkCtrMaster", "work_ctr", "WorkCtr"),
new("ic_substitute", "id", "Id"),
new("ic_substitute", "is_deleted", "IsDeleted"),
new("srm_purchase", "id", "Id"),
new("srm_purchase", "is_require_goods", "IsRequireGoods"),
];
private static readonly PrimaryKeyRepair[] PrimaryKeyRepairs =
[
new("ItemSubstituteDetail", "RecID", true, "物料替代关系主键", "Id"),
new("CostCtrMaster", "RecID", true, "主键"),
new("CustMaster", "RecID", true, "主键"),
new("DepartmentMaster", "RecID", true, "主键"),
new("DevMonitor", "RecID", true, "主键", "Id"),
new("EmpSkills", "RecID", true, "主键", "Id"),
new("EmpWorkDutyMaster", "RecID", true, "主键"),
new("EmployeeMaster", "RecID", true, "主键"),
new("ItemMaster", "RecID", true, "主键"),
new("ItemOpCondition", "RecID", true, "物料工序生产要素主键", "Id"),
new("ItemPackMaster", "RecID", true, "主键"),
new("LineMaster", "RecID", true, "生产线主键", "Id"),
new("LineSkillMaster", "RecID", true, "产线岗位主键", "Id"),
new("PriorityCode", "RecID", true, "主键"),
new("ProdLineDetail", "RecID", true, "生产线物料主键"),
new("ProductStructureMaster", "RecID", true, "主键", "Id"),
new("ProductStructureOp", "RecID", true, "主键", "Id"),
new("RoutingOpDetail", "RecID", true, "主键", "Id"),
new("SuppMaster", "RecID", true, "主键"),
new("WorkCtrMaster", "RecID", true, "工作中心主键"),
new("ic_substitute", "Id", false, "主键"),
new("srm_purchase", "Id", true, "货源清单主键")
];
public static void Migrate(ISqlSugarClient db)
{
var databaseName = db.Ado.SqlQuerySingle("SELECT DATABASE();");
foreach (var group in Renames.GroupBy(x => x.TableName))
{
var tableName = group.Key;
var tables = db.DbMaintenance.GetTableInfoList(false);
if (!tables.Any(t => t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase)))
continue;
try
{
var columns = db.DbMaintenance.GetColumnInfosByTableName(tableName, false) ?? [];
foreach (var rename in group)
{
var oldColumn = columns.FirstOrDefault(c => c.DbColumnName.Equals(rename.OldColumnName, StringComparison.Ordinal));
var newColumn = columns.FirstOrDefault(c => c.DbColumnName.Equals(rename.NewColumnName, StringComparison.Ordinal));
if (oldColumn == null)
continue;
if (newColumn != null)
{
if (oldColumn.IsPrimarykey)
{
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` DROP COLUMN `{rename.NewColumnName}`");
Trace.TraceInformation($"Ai-DOP S0 drop duplicate new PK shadow column: {tableName}.{rename.NewColumnName}");
RenameColumnMySql(db, databaseName, tableName, rename.OldColumnName, rename.NewColumnName);
Trace.TraceInformation($"Ai-DOP S0 rename PK column: {tableName}.{rename.OldColumnName} -> {rename.NewColumnName}");
}
else
{
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` DROP COLUMN `{rename.OldColumnName}`");
Trace.TraceInformation($"Ai-DOP S0 drop duplicate old column: {tableName}.{rename.OldColumnName}");
}
columns = db.DbMaintenance.GetColumnInfosByTableName(tableName, false) ?? [];
continue;
}
RenameColumnMySql(db, databaseName, tableName, rename.OldColumnName, rename.NewColumnName);
Trace.TraceInformation($"Ai-DOP S0 rename column: {tableName}.{rename.OldColumnName} -> {rename.NewColumnName}");
columns = db.DbMaintenance.GetColumnInfosByTableName(tableName, false) ?? [];
}
}
catch (Exception ex)
{
Trace.TraceError($"Ai-DOP S0 rename columns failed for {tableName}: {ex}");
throw;
}
}
RepairPrimaryKeys(db, databaseName);
EnsureS0SalesAddonColumnsAndSeeds(db, databaseName);
}
///
/// 产销扩展列(客户全称、优先级业务维度、订单评审订单类型)及示范合同评审五阶段种子。
///
private static void EnsureS0SalesAddonColumnsAndSeeds(ISqlSugarClient db, string databaseName)
{
EnsureColumn(db, databaseName, "CustMaster", "CustFullName",
"varchar(500) NULL COMMENT '客户全称/正式名称'");
EnsureColumn(db, databaseName, "StdOpMaster", "std_op_code",
"varchar(100) NULL COMMENT '标准工序编码'");
EnsureColumn(db, databaseName, "ItemSubstituteDetail", "SubstituteType",
"varchar(20) NULL COMMENT '替代关系类型(替代/取代)'");
EnsureColumn(db, databaseName, "SuppMaster", "address",
"varchar(500) NULL COMMENT '地址'");
EnsureColumn(db, databaseName, "SuppMaster", "contact",
"varchar(200) NULL COMMENT '联系人'");
EnsureColumn(db, databaseName, "SuppMaster", "position",
"varchar(200) NULL COMMENT '职务'");
EnsureColumn(db, databaseName, "SuppMaster", "contact_info",
"varchar(200) NULL COMMENT '联系方式'");
EnsureColumn(db, databaseName, "SuppMaster", "certification_level",
"varchar(100) NULL COMMENT '认证等级'");
EnsureColumn(db, databaseName, "EmpWorkDutyMaster", "ItemNum",
"varchar(100) NULL COMMENT '单物料编码'");
EnsureColumn(db, databaseName, "qms_sampscheme", "FFIXEDRATE",
"decimal(10,4) NULL COMMENT '固定抽样比例(%)'");
EnsureColumn(db, databaseName, "qms_lymjbmd", "wlbm",
"varchar(255) NULL COMMENT '物料编码'");
EnsureColumn(db, databaseName, "qms_lymjbmd", "wlmc",
"varchar(255) NULL COMMENT '物料名称'");
EnsureColumn(db, databaseName, "qms_lymjbmd", "dim_type",
"varchar(50) NULL COMMENT '维度类型(supplier/material/material_supplier)'");
EnsureColumn(db, databaseName, "qms_inspectioninstru", "gauge_category",
"varchar(255) NULL COMMENT '计量器具分类'");
EnsureColumn(db, databaseName, "qms_inspectioninstru", "calibration_cycle_days",
"int NULL COMMENT '校准周期(天)'");
EnsureColumn(db, databaseName, "qms_inspectioninstru", "next_calibration_date",
"varchar(50) NULL COMMENT '下次校准日期'");
EnsureColumn(db, databaseName, "PriorityCode", "ScopeCustClass",
"varchar(100) NULL COMMENT '业务维度:客户类型/级别(可空=不限)'");
EnsureColumn(db, databaseName, "PriorityCode", "ScopeOrderType",
"varchar(100) NULL COMMENT '业务维度:订单类型(可空=不限)'");
EnsureColumn(db, databaseName, "PriorityCode", "ScopeDueDaysMax",
"int NULL COMMENT '业务维度:承诺交期上限天数(可空=不限)'");
var hasOrderReviewTable = db.DbMaintenance.GetTableInfoList(false)
.Any(t => t.Name.Equals("S0OrderReviewCycle", StringComparison.OrdinalIgnoreCase));
if (hasOrderReviewTable)
{
EnsureColumn(db, databaseName, "S0OrderReviewCycle", "order_type",
"varchar(100) NOT NULL DEFAULT '' COMMENT '订单类型;空字符串=默认/不限'");
db.Ado.ExecuteCommand("UPDATE `S0OrderReviewCycle` SET `order_type` = '' WHERE `order_type` IS NULL");
TryDropIndex(db, databaseName, "S0OrderReviewCycle", "uk_S0OrderReviewCycle_factory");
EnsureUniqueIndex(db, databaseName, "S0OrderReviewCycle", "uk_S0OrderReviewCycle_factory_ordertype",
"`factory_ref_id`, `order_type`");
}
EnsureContractReviewCycleDemoStages(db);
}
private static void EnsureColumn(ISqlSugarClient db, string databaseName, string tableName, string columnName, string columnDefinitionTail)
{
var tables = db.DbMaintenance.GetTableInfoList(false);
if (!tables.Any(t => t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase)))
return;
var dbLit = databaseName.Replace("'", "''", StringComparison.Ordinal);
var tblLit = tableName.Replace("'", "''", StringComparison.Ordinal);
var colLit = columnName.Replace("'", "''", StringComparison.Ordinal);
var count = db.Ado.SqlQuerySingle(
$"""
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '{dbLit}' AND TABLE_NAME = '{tblLit}' AND COLUMN_NAME = '{colLit}'
""");
if (count > 0)
return;
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` ADD COLUMN `{columnName}` {columnDefinitionTail}");
Trace.TraceInformation($"Ai-DOP S0 add column: {tableName}.{columnName}");
}
private static void TryDropIndex(ISqlSugarClient db, string databaseName, string tableName, string indexName)
{
var tables = db.DbMaintenance.GetTableInfoList(false);
if (!tables.Any(t => t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase)))
return;
var dbLit = databaseName.Replace("'", "''", StringComparison.Ordinal);
var tblLit = tableName.Replace("'", "''", StringComparison.Ordinal);
var idxLit = indexName.Replace("'", "''", StringComparison.Ordinal);
var count = db.Ado.SqlQuerySingle(
$"""
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = '{dbLit}' AND TABLE_NAME = '{tblLit}' AND INDEX_NAME = '{idxLit}'
""");
if (count == 0)
return;
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` DROP INDEX `{indexName}`");
Trace.TraceInformation($"Ai-DOP S0 drop index: {tableName}.{indexName}");
}
private static void EnsureUniqueIndex(ISqlSugarClient db, string databaseName, string tableName, string indexName, string columnListSql)
{
var tables = db.DbMaintenance.GetTableInfoList(false);
if (!tables.Any(t => t.Name.Equals(tableName, StringComparison.OrdinalIgnoreCase)))
return;
var dbLit = databaseName.Replace("'", "''", StringComparison.Ordinal);
var tblLit = tableName.Replace("'", "''", StringComparison.Ordinal);
var idxLit = indexName.Replace("'", "''", StringComparison.Ordinal);
var count = db.Ado.SqlQuerySingle(
$"""
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = '{dbLit}' AND TABLE_NAME = '{tblLit}' AND INDEX_NAME = '{idxLit}'
""");
if (count > 0)
return;
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` ADD UNIQUE INDEX `{indexName}` ({columnListSql})");
Trace.TraceInformation($"Ai-DOP S0 add unique index: {tableName}.{indexName}");
}
private static void EnsureContractReviewCycleDemoStages(ISqlSugarClient db)
{
const long s0CompanyId = 1329900200001L;
const long s0Factory1Id = 1329900200002L;
const long s0Factory2Id = 1329900200003L;
var tables = db.DbMaintenance.GetTableInfoList(false);
if (!tables.Any(t => t.Name.Equals("S0ContractReviewCycle", StringComparison.OrdinalIgnoreCase)))
return;
var stages = new (string Code, string Name, int Order)[]
{
("CR1", "意见评审", 1),
("CR2", "意见反馈", 2),
("CR3", "二次评审", 3),
("CR4", "领导意见", 4),
("CR5", "合同盖章", 5),
};
foreach (var factoryId in new[] { s0Factory1Id, s0Factory2Id })
{
var hasAny = db.Queryable()
.Where(x => x.FactoryRefId == factoryId)
.Any();
if (hasAny)
continue;
var now = DateTime.Now;
foreach (var s in stages)
{
db.Insertable(new AdoS0ContractReviewCycle
{
CompanyRefId = s0CompanyId,
FactoryRefId = factoryId,
DomainCode = null,
StageCode = s.Code,
StageName = s.Name,
StdHours = 0,
OrderNo = s.Order,
IsActive = true,
CreateTime = now,
}).ExecuteCommand();
}
Trace.TraceInformation($"Ai-DOP S0 seeded contract review stages for factory {factoryId}");
}
}
private static void RenameColumnMySql(ISqlSugarClient db, string databaseName, string tableName, string oldColumnName, string newColumnName)
{
var meta = GetMySqlColumnMeta(db, databaseName, tableName, oldColumnName);
if (meta == null)
return;
var ddl = $"ALTER TABLE `{tableName}` CHANGE COLUMN `{oldColumnName}` `{newColumnName}` {BuildColumnDefinition(meta)}";
db.Ado.ExecuteCommand(ddl);
}
private static void RepairPrimaryKeys(ISqlSugarClient db, string databaseName)
{
var tableList = db.DbMaintenance.GetTableInfoList(false);
foreach (var repair in PrimaryKeyRepairs)
{
var tableName = tableList.FirstOrDefault(t => t.Name.Equals(repair.TableName, StringComparison.OrdinalIgnoreCase))?.Name;
if (string.IsNullOrEmpty(tableName))
continue;
var tableColumns = db.DbMaintenance.GetColumnInfosByTableName(tableName, false) ?? [];
if (tableColumns.Count == 0)
continue;
foreach (var shadowColumn in repair.ShadowColumns)
{
if (tableColumns.Any(c => c.DbColumnName.Equals(shadowColumn, StringComparison.Ordinal)))
{
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` DROP COLUMN `{shadowColumn}`");
}
}
SugarParameter[] pkParameters =
[
new SugarParameter("@db", databaseName),
new SugarParameter("@table", tableName)
];
var pkTable = db.Ado.GetDataTable(
"""
SELECT COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = @table
AND CONSTRAINT_NAME = 'PRIMARY'
ORDER BY ORDINAL_POSITION;
""",
pkParameters);
var primaryKeyColumns = pkTable.Rows.Cast()
.Select(r => r["COLUMN_NAME"]?.ToString())
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
if (primaryKeyColumns.Count == 0)
{
primaryKeyColumns = tableColumns
.Where(c => c.IsPrimarykey)
.OrderBy(c => c.DbColumnName)
.Select(c => c.DbColumnName)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => (string?)x)
.ToList();
}
var pkMatches = primaryKeyColumns.Count == 1
&& primaryKeyColumns[0]!.Equals(repair.PkColumnName, StringComparison.OrdinalIgnoreCase);
if (!pkMatches)
{
if (primaryKeyColumns.Count > 0)
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` DROP PRIMARY KEY");
db.Ado.ExecuteCommand($"ALTER TABLE `{tableName}` ADD PRIMARY KEY(`{repair.PkColumnName}`)");
}
if (!repair.IsIdentity)
continue;
var meta = GetMySqlColumnMeta(db, databaseName, tableName, repair.PkColumnName)
?? GetMySqlColumnMeta(db, databaseName, tableName, primaryKeyColumns.FirstOrDefault() ?? repair.PkColumnName);
if (meta == null || ContainsToken(meta.Extra, "auto_increment"))
continue;
var ddl = $"ALTER TABLE `{tableName}` MODIFY COLUMN `{meta.ColumnName}` {BuildColumnDefinition(meta, forceNotNull: true, forceAutoIncrement: true, skipDefault: true, commentOverride: repair.PkComment)}";
db.Ado.ExecuteCommand(ddl);
}
}
private static MySqlColumnMeta? GetMySqlColumnMeta(ISqlSugarClient db, string databaseName, string tableName, string columnName)
{
var sql = """
SELECT
COLUMN_NAME AS ColumnName,
COLUMN_TYPE AS ColumnType,
IS_NULLABLE AS IsNullable,
COLUMN_DEFAULT AS ColumnDefault,
EXTRA AS Extra,
COLUMN_COMMENT AS ColumnComment,
COLLATION_NAME AS CollationName,
CHARACTER_SET_NAME AS CharacterSetName
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = @table
AND BINARY COLUMN_NAME = @column
LIMIT 1;
""";
SugarParameter[] parameters =
[
new SugarParameter("@db", databaseName),
new SugarParameter("@table", tableName),
new SugarParameter("@column", columnName)
];
var table = db.Ado.GetDataTable(sql, parameters);
if (table.Rows.Count == 0)
return null;
var row = table.Rows[0];
return new MySqlColumnMeta(
row["ColumnName"]?.ToString() ?? columnName,
row["ColumnType"]?.ToString() ?? throw new InvalidOperationException($"Missing ColumnType for {tableName}.{columnName}"),
row["IsNullable"]?.ToString() ?? "YES",
row["ColumnDefault"] == DBNull.Value ? null : row["ColumnDefault"]?.ToString(),
row["Extra"] == DBNull.Value ? null : row["Extra"]?.ToString(),
row["ColumnComment"] == DBNull.Value ? null : row["ColumnComment"]?.ToString(),
row["CollationName"] == DBNull.Value ? null : row["CollationName"]?.ToString(),
row["CharacterSetName"] == DBNull.Value ? null : row["CharacterSetName"]?.ToString());
}
private static string BuildColumnDefinition(MySqlColumnMeta meta, bool forceNotNull = false, bool forceAutoIncrement = false, bool skipDefault = false, string? commentOverride = null)
{
var builder = new StringBuilder();
builder.Append(meta.ColumnType);
if (!string.IsNullOrWhiteSpace(meta.CharacterSetName))
builder.Append($" CHARACTER SET {meta.CharacterSetName}");
if (!string.IsNullOrWhiteSpace(meta.CollationName))
builder.Append($" COLLATE {meta.CollationName}");
var isNullable = !forceNotNull && meta.IsNullable.Equals("YES", StringComparison.OrdinalIgnoreCase);
builder.Append(isNullable ? " NULL" : " NOT NULL");
if (!skipDefault && meta.ColumnDefault != null)
{
builder.Append(" DEFAULT ");
builder.Append(FormatDefaultValue(meta.ColumnType, meta.ColumnDefault));
}
else if (!skipDefault && isNullable &&
!ContainsToken(meta.Extra, "auto_increment") &&
!ContainsToken(meta.Extra, "DEFAULT_GENERATED"))
{
builder.Append(" DEFAULT NULL");
}
if (forceAutoIncrement || ContainsToken(meta.Extra, "auto_increment"))
builder.Append(" AUTO_INCREMENT");
if (ContainsToken(meta.Extra, "on update CURRENT_TIMESTAMP"))
builder.Append(" ON UPDATE CURRENT_TIMESTAMP");
var comment = commentOverride ?? meta.ColumnComment;
if (!string.IsNullOrWhiteSpace(comment))
builder.Append($" COMMENT '{EscapeSqlLiteral(comment)}'");
return builder.ToString();
}
private static string FormatDefaultValue(string columnType, string columnDefault)
{
if (columnDefault.Equals("NULL", StringComparison.OrdinalIgnoreCase))
return "NULL";
if (columnDefault.StartsWith("CURRENT_TIMESTAMP", StringComparison.OrdinalIgnoreCase))
return columnDefault;
return IsNumericLikeType(columnType)
? columnDefault
: $"'{EscapeSqlLiteral(columnDefault)}'";
}
private static bool IsNumericLikeType(string columnType)
{
var normalized = columnType.ToLowerInvariant();
return normalized.StartsWith("tinyint") ||
normalized.StartsWith("smallint") ||
normalized.StartsWith("mediumint") ||
normalized.StartsWith("int") ||
normalized.StartsWith("bigint") ||
normalized.StartsWith("decimal") ||
normalized.StartsWith("numeric") ||
normalized.StartsWith("float") ||
normalized.StartsWith("double") ||
normalized.StartsWith("real") ||
normalized.StartsWith("bit") ||
normalized.StartsWith("bool") ||
normalized.StartsWith("boolean");
}
private static bool ContainsToken(string? value, string token)
=> !string.IsNullOrWhiteSpace(value) && value.Contains(token, StringComparison.OrdinalIgnoreCase);
private static string EscapeSqlLiteral(string value)
=> value.Replace("\\", "\\\\").Replace("'", "\\'");
}