/* ───────────────────────────────────────────────────────────── 本地开发库(AML-local-dev.bak 还原后)的架构补齐脚本 .bak 是某个时间点的库快照,没有 __EFMigrationsHistory,DbMigrator 无从判断 增量 → 后续后端迁移新增的表 / 列在本地库里全部缺失,调用相关接口会 500。 本脚本把这些 DDL 按迁移原样补上(全部带存在性判断,可重复执行): 20260718121851_PaygOfflineOrderManage 隨付即用(PAYG)下单与线下订单管理 20260719120000_AddAgentUserSettingAndPaymentMethod 代理归属国家 + 支持的支付网关 20260720000000_SplitOrderTypeAddOrderSource 订单来源与业务意图拆分 20260723000000_AddTenantIndustry 行业配置表(含 10 条行业种子) 20260724000000_AddTenantIndustryNameTranslations 行业名称多语言 JSON 列 另外补上 IsDeleted 的 DEFAULT 0 约束:.bak 还原后部分表丢了默认值约束, 而 EF 的 INSERT 并不显式带该列 → 报 "Cannot insert the value NULL into column 'IsDeleted'"。 用法: docker exec -i aml-mssql /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P 'Aml@Local2026' -C -d AbpAML -i /dev/stdin < local-schema-catchup.sql ───────────────────────────────────────────────────────────── */ SET NOCOUNT ON; /* ── 1) IsDeleted 默认值约束(.bak 还原丢失)───────────────── */ DECLARE @sql nvarchar(max) = N''; SELECT @sql = @sql + N'ALTER TABLE [' + t.name + N'] ADD CONSTRAINT [DF_' + t.name + N'_IsDeleted] DEFAULT 0 FOR [IsDeleted];' + CHAR(10) FROM sys.tables t JOIN sys.columns c ON c.object_id = t.object_id AND c.name = 'IsDeleted' WHERE c.default_object_id = 0; EXEC sp_executesql @sql; /* ── 2) PaygOfflineOrderManage ─────────────────────────────── */ IF COL_LENGTH('AMLPortal_Orders', 'ContactEmail') IS NULL ALTER TABLE AMLPortal_Orders ADD ContactEmail nvarchar(max) NULL; IF COL_LENGTH('AMLPortal_Orders', 'SubjectName') IS NULL ALTER TABLE AMLPortal_Orders ADD SubjectName nvarchar(max) NULL; IF COL_LENGTH('AMLPortal_Orders', 'SubjectType') IS NULL ALTER TABLE AMLPortal_Orders ADD SubjectType nvarchar(max) NULL; IF OBJECT_ID('AML_ConsumerLinks') IS NOT NULL AND COL_LENGTH('AML_ConsumerLinks', 'PaidOrderId') IS NULL ALTER TABLE AML_ConsumerLinks ADD PaidOrderId uniqueidentifier NULL; IF OBJECT_ID('AML_OrderProcessLogs') IS NULL CREATE TABLE AML_OrderProcessLogs ( Id uniqueidentifier NOT NULL CONSTRAINT PK_AML_OrderProcessLogs PRIMARY KEY, OrderKind int NOT NULL, OrderId uniqueidentifier NOT NULL, Action nvarchar(max) NULL, Remark nvarchar(max) NULL, OperatorName nvarchar(max) NULL, ExtraProperties nvarchar(max) NULL, ConcurrencyStamp nvarchar(40) NULL, CreationTime datetime2 NOT NULL, CreatorId uniqueidentifier NULL, LastModificationTime datetime2 NULL, LastModifierId uniqueidentifier NULL, IsDeleted bit NOT NULL CONSTRAINT DF_AML_OrderProcessLogs_IsDeleted DEFAULT 0, DeleterId uniqueidentifier NULL, DeletionTime datetime2 NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AML_OrderProcessLogs_OrderId') CREATE INDEX IX_AML_OrderProcessLogs_OrderId ON AML_OrderProcessLogs(OrderId); /* ── 3) AddAgentUserSettingAndPaymentMethod ────────────────── */ IF OBJECT_ID('AMLPortal_AgentUserSettings') IS NULL CREATE TABLE AMLPortal_AgentUserSettings ( Id uniqueidentifier NOT NULL CONSTRAINT PK_AMLPortal_AgentUserSettings PRIMARY KEY, AgentUserId uniqueidentifier NOT NULL, CountryCode nvarchar(max) NULL, ExtraProperties nvarchar(max) NULL, ConcurrencyStamp nvarchar(40) NULL, CreationTime datetime2 NOT NULL, CreatorId uniqueidentifier NULL, LastModificationTime datetime2 NULL, LastModifierId uniqueidentifier NULL, IsDeleted bit NOT NULL CONSTRAINT DF_AMLPortal_AgentUserSettings_IsDeleted DEFAULT 0, DeleterId uniqueidentifier NULL, DeletionTime datetime2 NULL ); IF OBJECT_ID('AMLPortal_AgentUserPaymentMethods') IS NULL CREATE TABLE AMLPortal_AgentUserPaymentMethods ( Id uniqueidentifier NOT NULL CONSTRAINT PK_AMLPortal_AgentUserPaymentMethods PRIMARY KEY, AgentUserId uniqueidentifier NOT NULL, PaymentGateway int NOT NULL, -- PaymentGatewayEnums:100=线下 / 200=QFPay ExtraProperties nvarchar(max) NULL, ConcurrencyStamp nvarchar(40) NULL, CreationTime datetime2 NOT NULL, CreatorId uniqueidentifier NULL, LastModificationTime datetime2 NULL, LastModifierId uniqueidentifier NULL, IsDeleted bit NOT NULL CONSTRAINT DF_AMLPortal_AgentUserPaymentMethods_IsDeleted DEFAULT 0, DeleterId uniqueidentifier NULL, DeletionTime datetime2 NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AMLPortal_AgentUserSettings_AgentUserId') CREATE INDEX IX_AMLPortal_AgentUserSettings_AgentUserId ON AMLPortal_AgentUserSettings(AgentUserId); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AMLPortal_AgentUserPaymentMethods_AgentUserId') CREATE INDEX IX_AMLPortal_AgentUserPaymentMethods_AgentUserId ON AMLPortal_AgentUserPaymentMethods(AgentUserId); /* ── 4) SplitOrderTypeAddOrderSource ───────────────────────── */ IF COL_LENGTH('AMLPortal_Orders', 'OrderSource') IS NULL BEGIN ALTER TABLE AMLPortal_Orders ADD OrderSource int NOT NULL CONSTRAINT DF_AMLPortal_Orders_OrderSource DEFAULT 1; -- 存量线下手工补录单:OrderType=300(OfflineManual) → 续费(200) + 来源后台手工补录(2) -- 走动态 SQL:同一批次里新加的列,静态语句在编译期就会报「列名无效」 EXEC(N'UPDATE AMLPortal_Orders SET OrderType = 200, OrderSource = 2 WHERE OrderType = 300;'); END /* ── 5) AddTenantIndustry ──────────────────────────────────── */ IF OBJECT_ID('AML_TenantIndustrys') IS NULL CREATE TABLE AML_TenantIndustrys ( Id uniqueidentifier NOT NULL CONSTRAINT PK_AML_TenantIndustrys PRIMARY KEY, Code nvarchar(64) NOT NULL, Name nvarchar(256) NULL, EditionId uniqueidentifier NULL, -- 行业→Edition 映射,购买时用;由后台维护 DisplayOrder int NOT NULL, Enabled bit NOT NULL, ExtraProperties nvarchar(max) NULL, ConcurrencyStamp nvarchar(40) NULL, CreationTime datetime2 NOT NULL, CreatorId uniqueidentifier NULL, LastModificationTime datetime2 NULL, LastModifierId uniqueidentifier NULL, IsDeleted bit NOT NULL CONSTRAINT DF_AML_TenantIndustrys_IsDeleted DEFAULT 0, DeleterId uniqueidentifier NULL, DeletionTime datetime2 NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UIX_AML_TenantIndustrys_Code') CREATE UNIQUE INDEX UIX_AML_TenantIndustrys_Code ON AML_TenantIndustrys(Code); -- 迁移里的 10 条行业种子(原 TenantIndustry.json),EditionId 留空 MERGE AML_TenantIndustrys AS t USING (VALUES ('A1B2C3D4-0001-4A01-9A01-000000000001', 'Crypto', 'Crypto - crypto currency', 10), ('A1B2C3D4-0002-4A02-9A02-000000000002', 'DPMS', 'DPMS - precious metal and stone', 20), ('A1B2C3D4-0003-4A03-9A03-000000000003', 'Insurance', 'Insurance', 30), ('A1B2C3D4-0004-4A04-9A04-000000000004', 'MSO', 'MSO - Money Services Operator', 40), ('A1B2C3D4-0005-4A05-9A05-000000000005', 'NGO', 'NGO - Non-profit Organization', 50), ('A1B2C3D4-0006-4A06-9A06-000000000006', 'RealEstate', 'Real Estate - real estate agents', 60), ('A1B2C3D4-0007-4A07-9A07-000000000007', 'SecuritiesTrades', 'Securities Trades', 70), ('A1B2C3D4-0008-4A08-9A08-000000000008', 'TCSP', 'TCSP - Trust and Company Services', 80), ('A1B2C3D4-0009-4A09-9A09-000000000009', 'CPA', 'CPA', 90), ('A1B2C3D4-0010-4A10-9A10-000000000010', 'Others', 'Others', 100) ) AS s (Id, Code, Name, DisplayOrder) ON t.Code = s.Code WHEN NOT MATCHED THEN INSERT (Id, Code, Name, DisplayOrder, Enabled, CreationTime, IsDeleted) VALUES (CONVERT(uniqueidentifier, s.Id), s.Code, s.Name, s.DisplayOrder, 1, '2026-07-23T00:00:00', 0); /* ── 6) AddTenantIndustryNameTranslations ──────────────────── */ -- 行业名称多语言 JSON:{"zh-Hans":"","zh-Hant":"","en":"","ja":""},可空 IF COL_LENGTH('AML_TenantIndustrys', 'NameTranslations') IS NULL ALTER TABLE AML_TenantIndustrys ADD NameTranslations nvarchar(max) NULL; PRINT '本地库架构补齐完成';