本文档是 S0迁移.md Batch 2 的细化版,可直接交由 Cursor / AI 编程助手逐步执行。
对比 S0迁移.md Batch 2 部分,本方案做了以下修正:
| # | 原计划描述 | 实际情况 | 修正 |
|---|---|---|---|
| 1 | Batch 2 含 Bom / BomItem(6 个实体) | S0.Domain.Sales/ 只有 Customer、Material、OrderPriorityRule 3 个实体,BOM 不在 Sales 域 |
移除 Bom/BomItem,实体数改为 3 |
| 2 | Customer 仅列 8 个字段 | 实际含 NameEn、CustomerType、ForbidStatus、Currency、IsTaxIncluded、PrimarySales、BackupSales、CustomerLevel 等 20 字段 | 补全 |
| 3 | Material 仅列 10 个字段 | 实际含 30+ 字段(库存、采购、质量、批次管理等参数) | 补全 |
| 4 | BOM 主从页面 | 不属于 Sales 域 | 移除 |
| 5 | S0 菜单根目录 ID = 1321001000000L |
代码实际生成 1321000001000L(1321000000000 + 1*1000) |
修正所有 S0DirId 引用 |
| 6 | Batch 1 需新建 4 实体 + 4 页面 | S0 Platform 与 Admin.NET 的 SysOrg/SysDictType/SysDictData 同构 | 改为复用平台 + 灌入种子数据,无需新建页面 |
/home/yy968/work/s0/s0-operating-modeling/backend/src/
├─ S0.Domain/
│ ├─ Common/BaseEntity.cs ← 公共基类(Id, CompanyId, FactoryId, IsEnabled, IsDeleted, VersionNo, CreatedBy, CreatedAt, UpdatedBy, UpdatedAt)
│ └─ Sales/
│ ├─ Customer.cs ← [Table("sales_customer")]
│ ├─ Material.cs ← [Table("sales_material")]
│ └─ OrderPriorityRule.cs ← [Table("sales_order_priority_rule")]
├─ S0.Application/Sales/
│ ├─ Common/SalesPagedQuery.cs ← 分页基类(Keyword, IsEnabled, PageNo, PageSize)
│ ├─ Customers/
│ │ ├─ CustomerDtos.cs ← CustomerUpsertRequest + CustomerDto
│ │ ├─ ICustomerService.cs
│ │ └─ CustomerService.cs
│ ├─ Materials/
│ │ ├─ MaterialDtos.cs ← MaterialQuery + MaterialUpsertRequest + MaterialDto
│ │ ├─ IMaterialService.cs
│ │ └─ MaterialService.cs
│ └─ OrderPriorityRules/
│ ├─ OrderPriorityRuleDtos.cs ← OrderPriorityRuleQuery + UpsertRequest + Dto
│ ├─ IOrderPriorityRuleService.cs
│ └─ OrderPriorityRuleService.cs
└─ S0.Api/Controllers/Sales/
├─ CustomersController.cs ← api/sales/customers
├─ MaterialsController.cs ← api/sales/materials
└─ OrderPriorityRulesController.cs ← api/sales/order-priority-rules
/home/yy968/work/s0/s0-operating-modeling/src/
├─ api/modules/sales.js ← customersApi / materialsApi / orderPriorityRulesApi
└─ views/
├─ CustomerManagement.vue ← 路由 /sales-modeling/customers
├─ MaterialManagement.vue ← 路由 /sales-modeling/materials
└─ OrderPriorityConfig.vue ← 路由 /sales-modeling/order-priority
server/Plugins/Admin.NET.Plugin.AiDOP/
├─ Entity/S0/Sales/
│ ├─ AdoS0Customer.cs ← ado_s0_sales_customer
│ ├─ AdoS0Material.cs ← ado_s0_sales_material
│ └─ AdoS0OrderPriorityRule.cs ← ado_s0_sales_order_priority_rule
├─ Dto/S0/Sales/
│ └─ AdoS0SalesDtos.cs ← 3 组 QueryDto + UpsertDto
└─ Controllers/S0/Sales/
├─ AdoS0CustomersController.cs
├─ AdoS0MaterialsController.cs
└─ AdoS0OrderPriorityRulesController.cs
server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs ← InitTables 追加 3 实体 + using
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs ← 追加 BuildS0SalesMenus()(注意是插件的 SeedData)
Web/src/views/aidop/s0/
├─ api/
│ └─ s0SalesApi.ts ← 3 组 API 函数 + TS 接口
└─ sales/
├─ CustomerList.vue ← name="aidopS0SalesCustomer"
├─ MaterialList.vue ← name="aidopS0SalesMaterial"
└─ OrderPriorityRuleList.vue ← name="aidopS0SalesOrderPriorityRule"
规则回顾:BaseEntity 字段内联(去掉 IsDeleted / VersionNo / CreatedBy / UpdatedBy);不继承基类;不迁移导航属性。
ado_s0_sales_customernamespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
/// <summary>客户主数据(S0 Sales / sales_customer)</summary>
[SugarTable("ado_s0_sales_customer", "S0 客户主数据")]
public class AdoS0Customer
{
[SugarColumn(ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
public long Id { get; set; }
[SugarColumn(ColumnDescription = "关联公司 ID", ColumnDataType = "bigint")]
public long CompanyRefId { get; set; }
[SugarColumn(ColumnDescription = "关联工厂 ID", ColumnDataType = "bigint")]
public long FactoryRefId { get; set; }
[SugarColumn(ColumnDescription = "客户编码", Length = 100)]
public string Code { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "客户名称", Length = 200)]
public string Name { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "英文名称", Length = 200, IsNullable = true)]
public string? NameEn { get; set; }
[SugarColumn(ColumnDescription = "客户类型", Length = 100, IsNullable = true)]
public string? CustomerType { get; set; }
[SugarColumn(ColumnDescription = "联系人", Length = 100, IsNullable = true)]
public string? ContactPerson { get; set; }
[SugarColumn(ColumnDescription = "联系电话", Length = 50, IsNullable = true)]
public string? ContactPhone { get; set; }
[SugarColumn(ColumnDescription = "地址", Length = 500, IsNullable = true)]
public string? Address { get; set; }
[SugarColumn(ColumnDescription = "禁用状态", Length = 20)]
public string ForbidStatus { get; set; } = "normal";
[SugarColumn(ColumnDescription = "币种", Length = 50, IsNullable = true)]
public string? Currency { get; set; }
[SugarColumn(ColumnDescription = "是否含税", ColumnDataType = "boolean")]
public bool IsTaxIncluded { get; set; } = true;
[SugarColumn(ColumnDescription = "主销售员", Length = 100, IsNullable = true)]
public string? PrimarySales { get; set; }
[SugarColumn(ColumnDescription = "备用销售员", Length = 100, IsNullable = true)]
public string? BackupSales { get; set; }
[SugarColumn(ColumnDescription = "客户等级")]
public int CustomerLevel { get; set; } = 1;
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
public string? Remark { get; set; }
[SugarColumn(ColumnDescription = "启用", ColumnDataType = "boolean")]
public bool IsEnabled { get; set; } = true;
[SugarColumn(ColumnDescription = "创建时间")]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[SugarColumn(ColumnDescription = "更新时间", IsNullable = true)]
public DateTime? UpdatedAt { get; set; }
}
字段来源对照表:
| # | 目标字段 | C# 类型 | 长度/精度 | 默认值 | S0 原列名 | S0 来源 |
|---|---|---|---|---|---|---|
| 1 | Id | long | — | PK 自增 | id | BaseEntity |
| 2 | CompanyRefId | long | — | — | company_id | BaseEntity |
| 3 | FactoryRefId | long | — | — | factory_id | BaseEntity |
| 4 | Code | string | 100 | — | code | Customer |
| 5 | Name | string | 200 | — | name | Customer |
| 6 | NameEn | string? | 200 | — | name_en | Customer |
| 7 | CustomerType | string? | 100 | — | customer_type | Customer |
| 8 | ContactPerson | string? | 100 | — | contact_person | Customer |
| 9 | ContactPhone | string? | 50 | — | contact_phone | Customer |
| 10 | Address | string? | 500 | — | address | Customer |
| 11 | ForbidStatus | string | 20 | "normal" | forbid_status | Customer |
| 12 | Currency | string? | 50 | — | currency | Customer |
| 13 | IsTaxIncluded | bool | — | true | is_tax_included | Customer |
| 14 | PrimarySales | string? | 100 | — | primary_sales | Customer |
| 15 | BackupSales | string? | 100 | — | backup_sales | Customer |
| 16 | CustomerLevel | int | — | 1 | customer_level | Customer |
| 17 | Remark | string? | 500 | — | remark | Customer |
| 18 | IsEnabled | bool | — | true | is_enabled | BaseEntity |
| 19 | CreatedAt | DateTime | — | Now | created_at | BaseEntity |
| 20 | UpdatedAt | DateTime? | — | — | updated_at | BaseEntity |
不迁移字段:IsDeleted、VersionNo、CreatedBy、UpdatedBy(遵循总体原则)
ado_s0_sales_materialnamespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
/// <summary>物料主数据(S0 Sales / sales_material)</summary>
[SugarTable("ado_s0_sales_material", "S0 物料主数据")]
public class AdoS0Material
{
[SugarColumn(ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
public long Id { get; set; }
[SugarColumn(ColumnDescription = "关联公司 ID", ColumnDataType = "bigint")]
public long CompanyRefId { get; set; }
[SugarColumn(ColumnDescription = "关联工厂 ID", ColumnDataType = "bigint")]
public long FactoryRefId { get; set; }
// ── 基本信息 ──
[SugarColumn(ColumnDescription = "物料编码", Length = 100)]
public string Code { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "物料名称", Length = 200)]
public string Name { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "英文名称", Length = 200, IsNullable = true)]
public string? NameEn { get; set; }
[SugarColumn(ColumnDescription = "物料类型", Length = 100, IsNullable = true)]
public string? MaterialType { get; set; }
[SugarColumn(ColumnDescription = "单位", Length = 50, IsNullable = true)]
public string? Unit { get; set; }
[SugarColumn(ColumnDescription = "规格型号", Length = 200, IsNullable = true)]
public string? Spec { get; set; }
[SugarColumn(ColumnDescription = "计划类别", Length = 100, IsNullable = true)]
public string? PlCategory { get; set; }
[SugarColumn(ColumnDescription = "图纸编号", Length = 100, IsNullable = true)]
public string? DrawingNo { get; set; }
[SugarColumn(ColumnDescription = "语言版本", Length = 50, IsNullable = true)]
public string? Language { get; set; }
[SugarColumn(ColumnDescription = "业务版本", Length = 50, IsNullable = true)]
public string? BizVersion { get; set; }
[SugarColumn(ColumnDescription = "产品编码", Length = 100, IsNullable = true)]
public string? ProductCode { get; set; }
[SugarColumn(ColumnDescription = "物料属性", Length = 50, IsNullable = true)]
public string? MaterialAttribute { get; set; }
// ── 库存参数 ──
[SugarColumn(ColumnDescription = "默认库位 ID", ColumnDataType = "bigint", IsNullable = true)]
public long? DefaultLocationId { get; set; }
[SugarColumn(ColumnDescription = "默认货架 ID", ColumnDataType = "bigint", IsNullable = true)]
public long? DefaultRackId { get; set; }
[SugarColumn(ColumnDescription = "库存类型编码", Length = 50, IsNullable = true)]
public string? StockTypeCode { get; set; }
[SugarColumn(ColumnDescription = "安全库存", ColumnDataType = "decimal(18,5)", IsNullable = true)]
public decimal? SafetyStock { get; set; }
[SugarColumn(ColumnDescription = "保质期(天)", IsNullable = true)]
public int? ShelfLifeDays { get; set; }
[SugarColumn(ColumnDescription = "到期预警天数", IsNullable = true)]
public int? ExpireWarningDays { get; set; }
// ── 采购参数 ──
[SugarColumn(ColumnDescription = "采购提前期(天)", IsNullable = true)]
public int? PurchaseLeadDays { get; set; }
[SugarColumn(ColumnDescription = "最小订货量", ColumnDataType = "decimal(18,5)", IsNullable = true)]
public decimal? MinOrderQty { get; set; }
[SugarColumn(ColumnDescription = "最大订货量", ColumnDataType = "decimal(18,5)", IsNullable = true)]
public decimal? MaxOrderQty { get; set; }
[SugarColumn(ColumnDescription = "订货倍数", ColumnDataType = "decimal(18,5)", IsNullable = true)]
public decimal? OrderMultiple { get; set; }
[SugarColumn(ColumnDescription = "备料提前期(天)", IsNullable = true)]
public int? PreparationLeadDays { get; set; }
[SugarColumn(ColumnDescription = "按需采购", ColumnDataType = "boolean")]
public bool IsOnDemand { get; set; }
[SugarColumn(ColumnDescription = "特殊需求类型", Length = 50, IsNullable = true)]
public string? SpecialReqType { get; set; }
// ── 质量 / 管控 ──
[SugarColumn(ColumnDescription = "需检验", ColumnDataType = "boolean")]
public bool IsInspectionRequired { get; set; }
[SugarColumn(ColumnDescription = "检验天数", IsNullable = true)]
public int? InspectionDays { get; set; }
[SugarColumn(ColumnDescription = "关键物料", ColumnDataType = "boolean")]
public bool IsKeyMaterial { get; set; }
[SugarColumn(ColumnDescription = "主要物料", ColumnDataType = "boolean")]
public bool IsMainMaterial { get; set; }
[SugarColumn(ColumnDescription = "需预处理", ColumnDataType = "boolean")]
public bool IsPreprocess { get; set; }
[SugarColumn(ColumnDescription = "自动批次", ColumnDataType = "boolean")]
public bool IsAutoBatch { get; set; }
[SugarColumn(ColumnDescription = "需打标签", ColumnDataType = "boolean")]
public bool IsLabelRequired { get; set; }
// ── 批次管理 ──
[SugarColumn(ColumnDescription = "批次先进先出提醒", ColumnDataType = "boolean")]
public bool IsBatchFifoReminder { get; set; }
[SugarColumn(ColumnDescription = "批次先进先出严格", ColumnDataType = "boolean")]
public bool IsBatchFifoStrict { get; set; }
[SugarColumn(ColumnDescription = "库存周转率", ColumnDataType = "decimal(18,5)", IsNullable = true)]
public decimal? InventoryTurnoverRate { get; set; }
// ── 状态 ──
[SugarColumn(ColumnDescription = "禁用状态", Length = 20)]
public string ForbidStatus { get; set; } = "normal";
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
public string? Remark { get; set; }
[SugarColumn(ColumnDescription = "启用", ColumnDataType = "boolean")]
public bool IsEnabled { get; set; } = true;
[SugarColumn(ColumnDescription = "创建时间")]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[SugarColumn(ColumnDescription = "更新时间", IsNullable = true)]
public DateTime? UpdatedAt { get; set; }
}
字段来源对照表(共 39 字段):
| # | 目标字段 | C# 类型 | S0 原列名 | S0 来源 |
|---|---|---|---|---|
| 1 | Id | long | id | BaseEntity |
| 2 | CompanyRefId | long | company_id | BaseEntity |
| 3 | FactoryRefId | long | factory_id | BaseEntity |
| 4 | Code | string(100) | code | Material |
| 5 | Name | string(200) | name | Material |
| 6 | NameEn | string?(200) | name_en | Material |
| 7 | MaterialType | string?(100) | material_type | Material |
| 8 | Unit | string?(50) | unit | Material |
| 9 | Spec | string?(200) | spec | Material |
| 10 | PlCategory | string?(100) | pl_category | Material |
| 11 | DrawingNo | string?(100) | drawing_no | Material |
| 12 | Language | string?(50) | language | Material |
| 13 | BizVersion | string?(50) | biz_version | Material |
| 14 | ProductCode | string?(100) | product_code | Material |
| 15 | MaterialAttribute | string?(50) | material_attribute | Material |
| 16 | DefaultLocationId | long? | default_location_id | Material |
| 17 | DefaultRackId | long? | default_rack_id | Material |
| 18 | StockTypeCode | string?(50) | stock_type_code | Material |
| 19 | SafetyStock | decimal?(18,5) | safety_stock | Material |
| 20 | ShelfLifeDays | int? | shelf_life_days | Material |
| 21 | ExpireWarningDays | int? | expire_warning_days | Material |
| 22 | PurchaseLeadDays | int? | purchase_lead_days | Material |
| 23 | MinOrderQty | decimal?(18,5) | min_order_qty | Material |
| 24 | MaxOrderQty | decimal?(18,5) | max_order_qty | Material |
| 25 | OrderMultiple | decimal?(18,5) | order_multiple | Material |
| 26 | PreparationLeadDays | int? | preparation_lead_days | Material |
| 27 | IsOnDemand | bool | is_on_demand | Material |
| 28 | SpecialReqType | string?(50) | special_req_type | Material |
| 29 | IsInspectionRequired | bool | is_inspection_required | Material |
| 30 | InspectionDays | int? | inspection_days | Material |
| 31 | IsKeyMaterial | bool | is_key_material | Material |
| 32 | IsMainMaterial | bool | is_main_material | Material |
| 33 | IsPreprocess | bool | is_preprocess | Material |
| 34 | IsAutoBatch | bool | is_auto_batch | Material |
| 35 | IsLabelRequired | bool | is_label_required | Material |
| 36 | IsBatchFifoReminder | bool | is_batch_fifo_reminder | Material |
| 37 | IsBatchFifoStrict | bool | is_batch_fifo_strict | Material |
| 38 | InventoryTurnoverRate | decimal?(18,5) | inventory_turnover_rate | Material |
| 39 | ForbidStatus | string(20) | forbid_status | Material |
| 40 | Remark | string?(500) | remark | Material |
| 41 | IsEnabled | bool | is_enabled | BaseEntity |
| 42 | CreatedAt | DateTime | created_at | BaseEntity |
| 43 | UpdatedAt | DateTime? | updated_at | BaseEntity |
跨 Batch 依赖:DefaultLocationId / DefaultRackId 关联仓储 Location / Rack(Batch 4)。Batch 2 阶段前端用
el-input-number输入 ID;Batch 4 完成后升级为仓储下拉。
ado_s0_sales_order_priority_rulenamespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
/// <summary>订单优先规则(S0 Sales / sales_order_priority_rule)</summary>
[SugarTable("ado_s0_sales_order_priority_rule", "S0 订单优先规则")]
public class AdoS0OrderPriorityRule
{
[SugarColumn(ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
public long Id { get; set; }
[SugarColumn(ColumnDescription = "关联公司 ID", ColumnDataType = "bigint")]
public long CompanyRefId { get; set; }
[SugarColumn(ColumnDescription = "关联工厂 ID", ColumnDataType = "bigint")]
public long FactoryRefId { get; set; }
[SugarColumn(ColumnDescription = "规则编码", Length = 100)]
public string Code { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "规则名称", Length = 200)]
public string Name { get; set; } = string.Empty;
[SugarColumn(ColumnDescription = "优先级别")]
public int PriorityLevel { get; set; }
[SugarColumn(ColumnDescription = "排序方向", Length = 10)]
public string SortDirection { get; set; } = "ASC";
[SugarColumn(ColumnDescription = "来源实体", Length = 200, IsNullable = true)]
public string? SourceEntity { get; set; }
[SugarColumn(ColumnDescription = "来源字段", Length = 200, IsNullable = true)]
public string? SourceField { get; set; }
[SugarColumn(ColumnDescription = "来源字段类型", Length = 100, IsNullable = true)]
public string? SourceFieldType { get; set; }
[SugarColumn(ColumnDescription = "来源关联字段", Length = 200, IsNullable = true)]
public string? SourceLinkField { get; set; }
[SugarColumn(ColumnDescription = "工单字段", Length = 200, IsNullable = true)]
public string? WorkOrderField { get; set; }
[SugarColumn(ColumnDescription = "工单字段类型", Length = 100, IsNullable = true)]
public string? WorkOrderFieldType { get; set; }
[SugarColumn(ColumnDescription = "工单关联字段", Length = 200, IsNullable = true)]
public string? WorkOrderLinkField { get; set; }
[SugarColumn(ColumnDescription = "规则表达式", Length = 1000, IsNullable = true)]
public string? RuleExpr { get; set; }
[SugarColumn(ColumnDescription = "备注", Length = 500, IsNullable = true)]
public string? Remark { get; set; }
[SugarColumn(ColumnDescription = "启用", ColumnDataType = "boolean")]
public bool IsEnabled { get; set; } = true;
[SugarColumn(ColumnDescription = "创建时间")]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[SugarColumn(ColumnDescription = "更新时间", IsNullable = true)]
public DateTime? UpdatedAt { get; set; }
}
字段来源对照表(共 19 字段):
| # | 目标字段 | C# 类型 | S0 原列名 |
|---|---|---|---|
| 1 | Id | long | id |
| 2 | CompanyRefId | long | company_id |
| 3 | FactoryRefId | long | factory_id |
| 4 | Code | string(100) | code |
| 5 | Name | string(200) | name |
| 6 | PriorityLevel | int | priority_level |
| 7 | SortDirection | string(10) | sort_direction |
| 8 | SourceEntity | string?(200) | source_entity |
| 9 | SourceField | string?(200) | source_field |
| 10 | SourceFieldType | string?(100) | source_field_type |
| 11 | SourceLinkField | string?(200) | source_link_field |
| 12 | WorkOrderField | string?(200) | work_order_field |
| 13 | WorkOrderFieldType | string?(100) | work_order_field_type |
| 14 | WorkOrderLinkField | string?(200) | work_order_link_field |
| 15 | RuleExpr | string?(1000) | rule_expr |
| 16 | Remark | string?(500) | remark |
| 17 | IsEnabled | bool | is_enabled |
| 18 | CreatedAt | DateTime | created_at |
| 19 | UpdatedAt | DateTime? | updated_at |
文件:server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S0/Sales/AdoS0SalesDtos.cs
namespace Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
// ══════════════════════════════════════
// Customer
// ══════════════════════════════════════
public class AdoS0CustomerQueryDto
{
public string? Keyword { get; set; } // 模糊搜索 Code / Name
public bool? IsEnabled { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
public class AdoS0CustomerUpsertDto
{
public long CompanyRefId { get; set; }
public long FactoryRefId { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? NameEn { get; set; }
public string? CustomerType { get; set; }
public string? ContactPerson { get; set; }
public string? ContactPhone { get; set; }
public string? Address { get; set; }
public string ForbidStatus { get; set; } = "normal";
public string? Currency { get; set; }
public bool IsTaxIncluded { get; set; } = true;
public string? PrimarySales { get; set; }
public string? BackupSales { get; set; }
public int CustomerLevel { get; set; } = 1;
public string? Remark { get; set; }
public bool IsEnabled { get; set; } = true;
}
// ══════════════════════════════════════
// Material
// ══════════════════════════════════════
public class AdoS0MaterialQueryDto
{
public string? Keyword { get; set; } // 模糊搜索 Code / Name
public string? Code { get; set; }
public string? Name { get; set; }
public string? Spec { get; set; }
public string? DrawingNo { get; set; }
public string? PlCategory { get; set; }
public string? MaterialType { get; set; }
public string? Language { get; set; }
public bool? IsEnabled { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
public class AdoS0MaterialUpsertDto
{
public long CompanyRefId { get; set; }
public long FactoryRefId { get; set; }
// 基本信息
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? NameEn { get; set; }
public string? MaterialType { get; set; }
public string? Unit { get; set; }
public string? Spec { get; set; }
public string? PlCategory { get; set; }
public string? DrawingNo { get; set; }
public string? Language { get; set; }
public string? BizVersion { get; set; }
public string? ProductCode { get; set; }
public string? MaterialAttribute { get; set; }
// 库存参数
public long? DefaultLocationId { get; set; }
public long? DefaultRackId { get; set; }
public string? StockTypeCode { get; set; }
public decimal? SafetyStock { get; set; }
public int? ShelfLifeDays { get; set; }
public int? ExpireWarningDays { get; set; }
// 采购参数
public int? PurchaseLeadDays { get; set; }
public decimal? MinOrderQty { get; set; }
public decimal? MaxOrderQty { get; set; }
public decimal? OrderMultiple { get; set; }
public int? PreparationLeadDays { get; set; }
public bool IsOnDemand { get; set; }
public string? SpecialReqType { get; set; }
// 质量 / 管控
public bool IsInspectionRequired { get; set; }
public int? InspectionDays { get; set; }
public bool IsKeyMaterial { get; set; }
public bool IsMainMaterial { get; set; }
public bool IsPreprocess { get; set; }
public bool IsAutoBatch { get; set; }
public bool IsLabelRequired { get; set; }
// 批次管理
public bool IsBatchFifoReminder { get; set; }
public bool IsBatchFifoStrict { get; set; }
public decimal? InventoryTurnoverRate { get; set; }
// 状态
public string ForbidStatus { get; set; } = "normal";
public bool IsEnabled { get; set; } = true;
public string? Remark { get; set; }
}
// ══════════════════════════════════════
// OrderPriorityRule
// ══════════════════════════════════════
public class AdoS0OrderPriorityRuleQueryDto
{
public string? Keyword { get; set; } // 模糊搜索 Code / Name
public string? SourceEntity { get; set; }
public bool? IsEnabled { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
public class AdoS0OrderPriorityRuleUpsertDto
{
public long CompanyRefId { get; set; }
public long FactoryRefId { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int PriorityLevel { get; set; }
public string SortDirection { get; set; } = "ASC";
public string? SourceEntity { get; set; }
public string? SourceField { get; set; }
public string? SourceFieldType { get; set; }
public string? SourceLinkField { get; set; }
public string? WorkOrderField { get; set; }
public string? WorkOrderFieldType { get; set; }
public string? WorkOrderLinkField { get; set; }
public string? RuleExpr { get; set; }
public string? Remark { get; set; }
public bool IsEnabled { get; set; } = true;
}
路由 api/s0/sales/customers,风格对标 OrderController.cs:
GET /api/s0/sales/customers ← 分页(keyword, isEnabled, page, pageSize)
GET /api/s0/sales/customers/{id:long}
POST /api/s0/sales/customers
PUT /api/s0/sales/customers/{id:long}
DELETE /api/s0/sales/customers/{id:long}
查询过滤逻辑:
.WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
x => x.Code.Contains(q.Keyword!) || x.Name.Contains(q.Keyword!))
.WhereIF(q.IsEnabled.HasValue, x => x.IsEnabled == q.IsEnabled!.Value)
路由 api/s0/sales/materials:
GET /api/s0/sales/materials ← 分页(多条件过滤)
GET /api/s0/sales/materials/{id:long}
POST /api/s0/sales/materials
PUT /api/s0/sales/materials/{id:long}
DELETE /api/s0/sales/materials/{id:long}
查询过滤逻辑:
.WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
x => x.Code.Contains(q.Keyword!) || x.Name.Contains(q.Keyword!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Code), x => x.Code.Contains(q.Code!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Name), x => x.Name.Contains(q.Name!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Spec), x => x.Spec!.Contains(q.Spec!))
.WhereIF(!string.IsNullOrWhiteSpace(q.DrawingNo), x => x.DrawingNo!.Contains(q.DrawingNo!))
.WhereIF(!string.IsNullOrWhiteSpace(q.PlCategory), x => x.PlCategory == q.PlCategory)
.WhereIF(!string.IsNullOrWhiteSpace(q.MaterialType), x => x.MaterialType == q.MaterialType)
.WhereIF(!string.IsNullOrWhiteSpace(q.Language), x => x.Language == q.Language)
.WhereIF(q.IsEnabled.HasValue, x => x.IsEnabled == q.IsEnabled!.Value)
路由 api/s0/sales/order-priority-rules:
GET /api/s0/sales/order-priority-rules ← 分页(keyword, sourceEntity, isEnabled)
GET /api/s0/sales/order-priority-rules/{id:long}
POST /api/s0/sales/order-priority-rules
PUT /api/s0/sales/order-priority-rules/{id:long}
DELETE /api/s0/sales/order-priority-rules/{id:long}
查询过滤逻辑:
.WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
x => x.Code.Contains(q.Keyword!) || x.Name.Contains(q.Keyword!))
.WhereIF(!string.IsNullOrWhiteSpace(q.SourceEntity), x => x.SourceEntity == q.SourceEntity)
.WhereIF(q.IsEnabled.HasValue, x => x.IsEnabled == q.IsEnabled!.Value)
在 server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs 中:
顶部追加 using:
using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
InitTables 追加:
typeof(AdoS0Customer), typeof(AdoS0Material), typeof(AdoS0OrderPriorityRule)
文件:Web/src/views/aidop/s0/api/s0SalesApi.ts
要点:
import service from '/@/utils/request's0CustomersApi、s0MaterialsApi、s0OrderPriorityRulesApilist、get、create、update、delete 方法S0CustomerRow / S0CustomerUpsert、S0MaterialRow / S0MaterialUpsert、S0OrderPriorityRuleRow / S0OrderPriorityRuleUpsertPaged<T> { total, page, pageSize, list }SysOrgApi 按 Type=201)、factoryRefId(下拉 → SysOrgApi 按 Type=501 + Pid 联动)SysDictDataApi code=s0_customer_type)SysDictDataApi code=s0_forbid_status)SysDictDataApi code=s0_currency)import AidopDemoShell from '../../components/AidopDemoShell.vue'el-tabs 分组):
SysOrgApi Type=201)、factoryRefId(下拉 → SysOrgApi Type=501 联动)s0_material_type)、unit、spec、plCategory(下拉 → s0_pl_category)、drawingNo、language、bizVersion、productCode、materialAttribute(下拉 → s0_material_attribute)s0_stock_type)、safetyStock、shelfLifeDays、expireWarningDays、defaultLocationId(输入框,Batch 4 后升级)、defaultRackId(输入框,Batch 4 后升级)s0_special_req_type)s0_forbid_status)、isEnabled(switch)、remark(textarea)下拉选项统一从平台字典 API 获取(
SysDictDataApi.apiSysDictDataDataListCodeGet), 不再硬编码。S0 原MaterialManagement.vue中的 fallback 选项仅作为种子数据参考。
在 server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs 的 HasData() 末尾(BuildAidopSmartOpsSeedMenus 之后)追加:
foreach (var m in BuildS0SalesMenus(ct)) list.Add(m);
私有方法:
private static IEnumerable<SysMenu> BuildS0SalesMenus(DateTime ct)
{
const long S0DirId = 1321000001000L; // 已存在的 S0 根目录(ModuleDefinitions 第 1 项:1321000000000 + 1*1000)
const long SubDirId = 1329002000000L; // 产销建模 子目录
yield return new SysMenu
{
Id = SubDirId, Pid = S0DirId,
Title = "产销建模", Path = "/aidop/s0/sales",
Name = "aidopS0Sales", Component = "Layout",
Icon = "ele-ShoppingCart", Type = MenuTypeEnum.Dir,
CreateTime = ct, OrderNo = 20
};
yield return new SysMenu
{
Id = SubDirId + 1, Pid = SubDirId,
Title = "客户管理", Path = "/aidop/s0/sales/customer",
Name = "aidopS0SalesCustomer",
Component = "/aidop/s0/sales/CustomerList",
Icon = "ele-User", Type = MenuTypeEnum.Menu,
CreateTime = ct, OrderNo = 1
};
yield return new SysMenu
{
Id = SubDirId + 2, Pid = SubDirId,
Title = "物料管理", Path = "/aidop/s0/sales/material",
Name = "aidopS0SalesMaterial",
Component = "/aidop/s0/sales/MaterialList",
Icon = "ele-Box", Type = MenuTypeEnum.Menu,
CreateTime = ct, OrderNo = 2
};
yield return new SysMenu
{
Id = SubDirId + 3, Pid = SubDirId,
Title = "订单优先规则", Path = "/aidop/s0/sales/order-priority-rule",
Name = "aidopS0SalesOrderPriorityRule",
Component = "/aidop/s0/sales/OrderPriorityRuleList",
Icon = "ele-Sort", Type = MenuTypeEnum.Menu,
CreateTime = ct, OrderNo = 3
};
}
前置 确认 Batch 1 种子已就绪 → S0DictSeedData.cs 已创建,平台字典和组织节点已入库
Step 1 阅读本文档 + S0 源文件 → 理解字段定义
Step 2 新建 Entity/S0/Sales/ 下 3 个实体文件 → [SugarTable] + [SugarColumn] 格式
Step 3 新建 Dto/S0/Sales/AdoS0SalesDtos.cs → 3 组 QueryDto + UpsertDto
Step 4 新建 Controllers/S0/Sales/ 下 3 个控制器 → [AllowAnonymous][NonUnify] 插件风格
Step 5 修改 Startup.cs → InitTables 追加 3 实体 + using
Step 6 后端编译验证 → dotnet build 无报错
Step 7 新建 Web/src/views/aidop/s0/api/s0SalesApi.ts
Step 8 新建 Web/src/views/aidop/s0/sales/ 下 3 个 Vue 页面
(下拉调用 SysOrgApi + SysDictDataApi,不硬编码选项)
Step 9 修改插件 SysMenuSeedData.cs → 追加 BuildS0SalesMenus()(S0DirId=1321000001000L)
Step 10 前端编译验证 → pnpm dev 无 TS 错误
Step 11 集成验收 → 按第十一节标准逐项检查
dotnet build 无报错ado_s0_sales_customer、ado_s0_sales_material、ado_s0_sales_order_priority_rule/api/s0/sales/* 共 15 个接口(每实体 5 个 CRUD)pnpm dev 无 TypeScript 类型错误Batch 1 不再新建独立实体/页面,改为向 SysOrg + SysDictType/SysDictData 灌入种子数据。
产销建模页面中的下拉选择器直接调用平台标准 API:
| 下拉项 | 数据源 API | 字典 Code / 过滤条件 |
|---|---|---|
| CompanyRefId(公司) | SysOrgApi 获取列表后按 Type="201" 过滤 |
— |
| FactoryRefId(工厂) | SysOrgApi 获取列表后按 Type="501" + Pid=公司Id 过滤 |
— |
| MaterialType(物料类型) | SysDictDataApi.apiSysDictDataDataListCodeGet |
s0_material_type |
| PlCategory(计划类别) | 同上 | s0_pl_category |
| StockTypeCode(库存类型) | 同上 | s0_stock_type |
| SpecialReqType(特殊需求) | 同上 | s0_special_req_type |
| MaterialAttribute(物料属性) | 同上 | s0_material_attribute |
| CustomerType(客户类型) | 同上 | s0_customer_type |
| Currency(币种) | 同上 | s0_currency |
| ForbidStatus(禁用状态) | 同上 | s0_forbid_status |
| 后续 Batch | 关联点 | 动作 |
|---|---|---|
| Batch 4(仓储) | Material.DefaultLocationId / DefaultRackId | 前端升级为仓储 Location/Rack 下拉 |
| Batch 3(制造) | 制造实体引用 sales_material(物料 FK) | Material 表需先于制造迁移完成 |