From ec0782681b5a4939ce0184f0c36903edc97ee835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E7=9D=BF=20=E5=BC=A0?= Date: Thu, 15 Oct 2026 17:28:03 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=E8=B4=A7=E6=BA=90?= =?UTF-8?q?=E6=94=B9=E9=80=A0=E5=90=8E=E7=9A=84=E6=96=87=E6=A1=A3=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E5=85=B6=E4=B8=8E=E5=AE=9E=E9=99=85=20schema=20?= =?UTF-8?q?=E5=8F=8A=E5=B9=82=E7=AD=89=E5=8F=A3=E5=BE=84=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 数据库表结构与关系说明:materials 删除 supplier_id/manufacturer_id/ manufacturer_model 三列,新增 material_manufacturers 表,material_suppliers 改为三元货源口径,补单列外键政策;删除指向不存在的 scripts/upgrades 的升级指引 - 设计文档:§8 第 5 条完整性校验标为货源功能的必做项;修正 init.sql 「可重复执行」的错误表述(整脚本不可重复执行,仅新增语句各自幂等) - 实施计划:修正字符集表述(持久表实为 utf8mb4_0900_ai_ci)与「完成后的状态」 的幂等口径,补入货源功能完成前提 --- ...026-10-15-material-sources-db-migration.md | 15 ++++--- ...5-material-supplier-manufacturer-design.md | 10 +++-- docs/数据库表结构与关系说明.md | 41 +++++++++++++------ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md b/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md index d731a9e3..879f7bc3 100644 --- a/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md +++ b/docs/superpowers/plans/2026-10-15-material-sources-db-migration.md @@ -13,7 +13,7 @@ ## Global Constraints - MySQL 8.0,`CHECK` 约束依赖 8.0.16+。 -- 字符集统一 `utf8mb4` / `utf8mb4_unicode_ci`。 +- 字符集:库默认 `utf8mb4` / `utf8mb4_unicode_ci`(`CREATE DATABASE ... COLLATE utf8mb4_unicode_ci`),但**每张持久表(含本次新增的两张)实际落在 `utf8mb4_0900_ai_ci`**——建表语句只写了 `DEFAULT CHARSET=utf8mb4`、没有 `COLLATE`,MySQL 会采用该字符集的默认排序规则而非库默认值。这是全库既有现象,本次不处理,统一排序规则另开任务。 - **所有外键均为单列**,不引入任何复合外键(spec 的 D7)。公司隔离与物料一致性由应用层校验。 - `status` 取值固定为 `active` / `obsolete`(D8),不用数字。 - 货源表表名沿用 `material_suppliers`,不改名(D5)。 @@ -411,14 +411,18 @@ SELECT COUNT(*) AS materials_with_two FROM ( 预期:`32`(这 32 条各 2 行;其余物料若有供应商则各 1 行)。 -- [ ] **Step 7: 确认脚本仍可重复执行** +- [ ] **Step 7: 确认新增语句自身幂等** + +同 Task 1 Step 13:`init.sql` **整体不可重复执行**(见 Global Constraints),所以**不要**在原库重跑整个脚本——第二遍会在第 8 行以 `Table 'users' already exists` 中止,证明不了任何事。 + +若要实证新增的 32 条语句幂等,用 `mysql --force` 跑完整个脚本(忽略建表段那批既有报错),确认新增语句本身一条都没报错、且计数稳定: ```bash -sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql -u root -p1234 +sed -e "s/CREATE DATABASE IF NOT EXISTS wmms/CREATE DATABASE IF NOT EXISTS wms_verify/" -e "s/^USE wmms;/USE wms_verify;/" scripts/init.sql | mysql --force -u root -p1234 mysql -u root -p1234 wms_verify -e "SELECT COUNT(*) FROM material_suppliers;" ``` -预期:不报错,仍为 `161`。 +预期:计数仍为 `161`,且输出中**没有**任何来自新增 32 条语句的错误。 - [ ] **Step 8: 提交** @@ -434,5 +438,6 @@ git commit -F /tmp/msg.txt - `materials`:199 条,已无 `supplier_id` / `manufacturer_id` / `manufacturer_model`。 - `material_manufacturers`:10 条。 - `material_suppliers`:161 条(129 基础 + 32 补入)。 -- `init.sql` 仍是单文件、可重复执行、空库一次跑通。 +- `init.sql` 仍是单文件,可从空库一次重建;**整脚本不可重复执行**(见 Global Constraints),只有本次新增的分流/补入语句各自幂等。 - 后端与前端**尚未适配**,此时启动应用会因查询旧列而失败——这是预期状态,应用层改造另行计划。 +- **货源功能的完成前提:** 应用层必须实现 D7 所需的四条完整性校验(数据库已移除复合外键,无法代劳);清单见设计文档第 8 节第 5 条。在其全部落地前,货源功能不算完成。 diff --git a/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md b/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md index aa615059..a3829bf7 100644 --- a/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md +++ b/docs/superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md @@ -237,7 +237,7 @@ CREATE TABLE material_suppliers ( ## 7. init.sql 的连带改动与数据回填 -`scripts/init.sql` 同时是结构定义与数据载体(空库初始化、可重复执行)。删改 `materials` 的列会波及文件内多处,必须一并处理,否则脚本无法执行。 +`scripts/init.sql` 同时是结构定义与数据载体(空库一次性初始化;整脚本**不可重复执行**,普通建表没有 `IF NOT EXISTS`)。删改 `materials` 的列会波及文件内多处,必须一并处理,否则脚本无法执行。 ### 7.1 必须同步修改的位置 @@ -269,7 +269,7 @@ CREATE TABLE material_suppliers ( 两个必须守住的点: - **顺序不能变。** 第 5 步依赖第 3、4 步已经落库(要 JOIN 出 `material_id` 和 `material_manufacturer_id`),所以 `materials` 与 `material_manufacturers` 必须先完成写入。 -- **每一步都要保持幂等。** 该脚本的既有约定是可重复执行,`materials` 用 `ON DUPLICATE KEY UPDATE`(子句中已无那两个字段),`material_manufacturers` 与 `material_suppliers` 也要用 `ON DUPLICATE KEY UPDATE` 或 `NOT EXISTS`,否则重跑会撞唯一键失败。 +- **每一步都要各自保持幂等。** `init.sql` **整脚本不可重复执行**(普通建表没有 `IF NOT EXISTS`,第二次执行会在头部报错中止;见计划 Global Constraints),但本次**新增的这几条分流语句必须各自幂等**:`materials` 用 `ON DUPLICATE KEY UPDATE`(子句中已无那两个字段),`material_manufacturers` 与 `material_suppliers` 也要用 `ON DUPLICATE KEY UPDATE` 或 `NOT EXISTS`。 收益是字面量只写一次且保持原样,129 条基础货源由 SQL 自动派生,避免手工搬运的错漏;多出的 32 条拆分来源见 7.4。代价是脚本里多一段临时表逻辑。 @@ -325,12 +325,14 @@ CREATE TABLE material_suppliers ( ## 8. 后续(应用层,本文不覆盖) -以下事项由本次结构变更推导而来,需要另行设计,此处仅作记录: +以下事项由本次结构变更推导而来,需要另行设计,此处仅作记录。**唯一例外是第 5 条:它是货源功能的必做项,不是可选记录——见该条下方的说明。** 1. **写入模式必须是差异对比,不能是整表替换。** 软停用(D6)意味着"提交列表有、库中没有"→ INSERT,"库中有、提交列表没有"→ UPDATE 为 `obsolete`,两侧都有 → UPDATE 采购条件。原本可照抄 `user_roles` 的 delete-then-insert 模式(`internal/repository/user.go:132-170`)会直接物理删除停用记录,与 D6 冲突。 2. **判重规则失效。** `FindExactDuplicates` 目前把 `supplier_id` / `manufacturer_id` 作为精确比对维度(`internal/repository/material.go:315-321`),而这两列将被删除。需要重新定义判重维度。 3. **Excel 导入表达不了三元组。** 模板的供应商/制造商两列是文本(`internal/service/material_import_excel.go:32`),且导入坚持"只匹配已有伙伴、绝不新建"(`internal/service/material_import.go:225-261`)。需要决定导入是否只填默认货源。 4. **列表与详情展示多值。** 当前列表列宽 `min-width="140"` 渲染单值文本(`frontend/src/views/Materials.vue:68-77`),详情页用 `el-descriptions` 单行(`:172-173`)。 -5. **接口层必须承接的完整性校验(对应 D7)。** 每条货源写入前依次校验:伙伴存在且属于当前公司;伙伴具备对应身份(`is_supplier` / `is_manufacturer`,可复用 `internal/repository/material.go:59-67` 的 `IsPartnerForRole`);货源指向的制造商条目存在且属于**同一个物料**、同一公司;`material_manufacturers` 的物料属于当前公司。 +5. **接口层必须承接的完整性校验(对应 D7)—— 必做项,不是可选记录。** 每条货源写入前依次校验:伙伴存在且属于当前公司;伙伴具备对应身份(`is_supplier` / `is_manufacturer`,可复用 `internal/repository/material.go:59-67` 的 `IsPartnerForRole`);货源指向的制造商条目存在且属于**同一个物料**、同一公司;`material_manufacturers` 的物料属于当前公司。 + + > **本项是货源功能的完成前提。** D7 已移除复合外键,数据库不再拦截这些脏引用,这四条校验是数据库**唯一无法兜底**的地方。**必须全部实现后,货源功能才可视为完成**;否则数据库会静默接受跨公司、跨物料的脏数据。 6. **物料日志快照。** `material_logs` 的 JSON 需纳入货源集合,否则变更追溯会丢失。 7. **顺手改进 SQL 别名可读性。** `internal/repository/material.go:255-258` 目前用 `sop` / `sb` / `mop` / `mb` 这套别名,从名字看不出分别对应哪张表。物料列表查询在本次改造中必然要重写(`materials` 的旧列被删,供应商名改为从货源表取),届时一并换成 `supplier_op` / `supplier_bp` / `manufacturer_op` / `manufacturer_bp` 这类表意别名。 diff --git a/docs/数据库表结构与关系说明.md b/docs/数据库表结构与关系说明.md index 0cb36d2a..f93818b7 100644 --- a/docs/数据库表结构与关系说明.md +++ b/docs/数据库表结构与关系说明.md @@ -1,8 +1,8 @@ # 物料管理系统数据库表说明 -更新时间:2026-10-14。本文前半部分保留已有库的结构说明;第 11 节说明公司隔离升级后的运行结构。表数量随升级增加审计、迁移版本及对账表,以目标库 `information_schema` 查询为准。 +更新时间:2026-10-15。本文前半部分保留已有库的结构说明;第 11 节说明公司隔离升级后的运行结构。表数量随升级增加审计、迁移版本及对账表,以目标库 `information_schema` 查询为准。 -本文按照“表、保存什么、主要字段”介绍每张表的作用,后面单独列出表之间的关系。公司隔离发布使用 `scripts/init.sql`(空库)或 `scripts/upgrades/2026-10-08-company-master-data-isolation.sql`(已有库),不以旧库快照代替升级后的结构。 +本文按照“表、保存什么、主要字段”介绍每张表的作用,后面单独列出表之间的关系。本仓库的结构以 `scripts/init.sql` 为准:脚本从空库一次建成,仓库里没有 `scripts/upgrades/` 增量升级脚本,已有库只能删库后用 `scripts/init.sql` 重建,不以旧库快照代替升级后的结构。 通用字段:`id` 为记录主键;`organization_id` 为本方经营组织 ID;`created_at`、`updated_at` 分别为创建、更新时间。表已创建与对应业务功能已完整实现是两件事,未接入当前业务流程的结构在第 9 节说明。 @@ -24,7 +24,7 @@ | 表 | 保存什么 | 主要字段 | | --------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `materials` | 物料基础资料,用于识别和描述物料;库存余额另存。 | 物料ID(`id`)、所属组织(`organization_id`)、物料编码(`material_code`)、名称(`name`)、图片(`material_image`)、规格(`spec`)、厂家型号(`manufacturer_model`)、来源(`source`)、备注(`remark`)、默认供应商关联ID(`supplier_id`)、制造商关联ID(`manufacturer_id`)、分类ID(`category_id`)、基本单位ID(`base_unit_id`)、单位文本(`unit`)、状态(`status`)、资料版本(`version`)、`created_at`、`updated_at` | +| `materials` | 物料基础资料,用于识别和描述物料;库存余额另存。供应商与制造商不再直接挂在物料上,改由 `material_suppliers`、`material_manufacturers` 两张关系表承载。 | 物料ID(`id`)、所属组织(`organization_id`)、物料编码(`material_code`)、名称(`name`)、图片(`material_image`)、规格(`spec`)、来源(`source`)、备注(`remark`)、分类ID(`category_id`)、基本单位ID(`base_unit_id`)、单位文本(`unit`)、状态(`status`)、资料版本(`version`)、`created_at`、`updated_at` | | `material_categories` | 物料大类、子类及分类定义,形成分类树。 | 分类ID(`id`)、所属组织(`organization_id`)、编码(`code`)、名称(`name`)、分类说明(`description`)、父分类ID(`parent_id`)、状态(`status`) | | `units` | 个、瓶、包、升等计量单位字典及数量精度。 | 单位ID(`id`)、所属组织(`organization_id`)、单位编码(`code`)、单位名称(`name`)、小数位数(`precision_scale`)、状态(`status`) | | `code_rules` | 按组织和物料分类配置的编码生成规则。 | 规则ID(`id`)、所属组织(`organization_id`)、分类ID(`category_id`)、编码前缀(`prefix`)、流水号长度(`sequence_length`)、下一个流水号(`next_sequence`)、状态(`status`) | @@ -37,12 +37,15 @@ | ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `business_partners` | 企业基本资料,多个本方组织可以共用。 | 企业ID(`id`)、企业名称(`legal_name`)、统一社会信用代码(`unified_credit_code`)、联系人(`contact_name`)、电话(`contact_phone`)、地址(`address`)、`created_at`、`updated_at` | | `organization_partners` | 当前组织如何与这家企业合作。 | 关系ID(`id`)、所属组织(`organization_id`)、企业ID(`business_partner_id`)、伙伴编码(`partner_code`)、供应商身份(`is_supplier`)、制造商身份(`is_manufacturer`)、客户身份(`is_customer`)、供应商状态(`supplier_status`)、客户状态(`customer_status`)、供应商分类(`supplier_categories`)、客户分类(`customer_categories`)、结算币种(`settlement_currency`)、付款条款(`payment_terms`)、`created_at`、`updated_at` | -| `material_suppliers` | 某个物料由哪个供应商供货,以及报价和供货条件。 | 记录ID(`id`)、所属组织(`organization_id`)、物料ID(`material_id`)、组织伙伴关系ID(`organization_partner_id`)、价格(`price`)、币种(`currency`)、最小订购量(`minimum_order_quantity`)、供货周期天数(`lead_time_days`)、首选标记(`is_preferred`)、生效日期(`effective_from`)、失效日期(`effective_to`)、状态(`status`) | +| `material_suppliers` | 一条货源:某供应商供某物料、供的是哪个制造商条目,以及该货源的采购条件。 | 记录ID(`id`)、所属组织(`organization_id`)、物料ID(`material_id`)、供应商关联ID(`supplier_id`,可空)、制造商条目ID(`material_manufacturer_id`,可空)、价格(`price`)、币种(`currency`)、最小订购量(`minimum_order_quantity`)、供货周期天数(`lead_time_days`)、首选标记(`is_preferred`)、状态(`status`)、`created_at`、`updated_at` | +| `material_manufacturers` | 某物料有哪些可用制造商,以及各自的厂家型号。 | 记录ID(`id`)、所属组织(`organization_id`)、物料ID(`material_id`)、制造商关联ID(`manufacturer_id`)、厂家型号(`model`)、状态(`status`)、`created_at`、`updated_at` | 同一外部企业可以与北京、武汉分别建立合作关系。企业名称、联系人、地址等基础资料共用;客户/供应商身份、状态和分类按组织分别保存。同一关系可同时具有客户和供应商身份。 `supplier_categories` 与 `customer_categories` 均支持多选:`material` 表示“物料级(生产)”,`finished_product` 表示“成品级(经营买卖)”;空集合表示未分类。 +物料与供应商、制造商的关系自 2026-10-15 起由两张关系表承载:`material_suppliers` 保存“物料 × 供应商 × 指向的制造商条目 × 采购条件”(价格、币种、最小订购量、供货周期、首选标记),`material_manufacturers` 保存“物料 × 制造商 × 厂家型号”。一条货源的供应商与制造商条目都可为空,但不能同时为空(`CHECK` 约束至少指向一方);两者为同一伙伴时(原厂直销)记在同一行。“只登记供应商、不填制造商”是主流形态,因此制造商条目维度可空。这两张表与全库一致,**只使用单列外键**:数据库仅保证被引用记录存在,公司与物料的一致性(伙伴属于同一公司、货源指向的制造商条目属于同一物料与公司)由应用层校验,不再使用 `(organization_id, …)` 复合外键。 + ## 4. 仓库、库位与库存 | 表 | 保存什么 | 主要字段 | @@ -117,9 +120,13 @@ | `inventory_balances.warehouse_id` | `warehouses.id` | | `material_categories.organization_id` | `organizations.id` | | `material_categories.parent_id` | `material_categories.id` | +| `material_manufacturers.manufacturer_id` | `organization_partners.id` | +| `material_manufacturers.material_id` | `materials.id` | +| `material_manufacturers.organization_id` | `organizations.id` | | `material_suppliers.material_id` | `materials.id` | +| `material_suppliers.material_manufacturer_id` | `material_manufacturers.id` | | `material_suppliers.organization_id` | `organizations.id` | -| `material_suppliers.organization_partner_id` | `organization_partners.id` | +| `material_suppliers.supplier_id` | `organization_partners.id` | | `material_unit_conversions.from_unit_id` | `units.id` | | `material_unit_conversions.material_id` | `materials.id` | | `material_unit_conversions.organization_id` | `organizations.id` | @@ -175,6 +182,9 @@ | `business_partners` | `unified_credit_code` | 已填写的信用代码唯一,允许为空;企业名称没有唯一约束。 | | `organization_partners` | `(organization_id, business_partner_id)`;`(organization_id, partner_code)` | 同一组织与同一企业只有一条合作关系;伙伴编码在组织内唯一。 | | `materials` | `(organization_id, material_code)` | 物料编码在组织内唯一。 | +| `material_manufacturers` | `(organization_id, material_id, manufacturer_id)` | 同一物料同一制造商条目唯一。 | +| `material_suppliers` | `(organization_id, material_id, supplier_key, manufacturer_entry_key)` | 同一物料同一“供应商 + 制造商条目”的货源唯一;两列 `_key` 是 `COALESCE(...,0)` 生成列,用于把 NULL 维度纳入唯一键。 | +| `material_suppliers` | `(organization_id, material_id, preferred_row)` | 同一物料至多一条生效的首选货源(`preferred_row` 仅在 `is_preferred` 且 `status='active'` 时为 1)。 | | `material_categories`、`units`、`warehouses` | `(organization_id, code)` | 分类、单位、仓库编码分别在组织内唯一。 | | `warehouse_locations` | `(warehouse_id, code)` | 库位编码在仓库内唯一。 | | `code_rules` | `(organization_id, category_id)` | 同一组织同一分类只有一条编码规则。 | @@ -193,7 +203,7 @@ | ------- | ----------------------------------------------------------------------------------------- | | 令牌撤销 | `revoked_tokens` 结构已建,当前认证代码未查询该表;退出登录主要由客户端清除令牌。 | | 物料单位换算 | `material_unit_conversions` 结构已建,当前后端未见该表业务读写路径;库存余额支持小数,但出入库、申请明细及流水数量仍为 INT。 | -| 物料供应商条件 | `material_suppliers` 结构已建,用于供货、报价等需求;当前后端未见完整维护路径。 | +| 物料货源条件 | `material_suppliers`(三元货源)与 `material_manufacturers` 结构已建,用于供货、报价等需求;当前后端未见完整维护路径,应用层改造另行计划。 | | 实物追踪 | `tags` 只有物料和标签关系,没有库位、单据明细或实物流转状态;物料表没有序列号开关。 | | 组织范围 | 多数业务表有组织字段;当前申请表、库存流水和物料日志没有直接组织字段。角色分配也没有按组织分别保存。 | | 伙伴资料共享 | 多个组织引用同一企业ID时,修改企业名称、联系人等基础资料会影响共享记录;合作身份、分类、状态分别保存。当前新建接口仍直接新增企业记录。 | @@ -244,20 +254,25 @@ 武汉目前没有历史数据(2026-10-09 用户确认),本次没有武汉历史副本检查或授权回填对象;空库初始化及合成历史夹具升级分别验证。部署、验收证据及回退限制见 [公司隔离验收与交付](company-master-data-isolation-acceptance.md)。 -## 12. 物料供应商与制造商(2026-10-12) +## 12. 物料供应商与制造商(2026-10-15 改为三元货源模型) -企业资料继续共用 `business_partners`,供应商、制造商、客户身份保存在 `organization_partners`。新增 `is_manufacturer BOOLEAN NOT NULL DEFAULT FALSE`,三个身份可同时存在;旧数据不会自动变成制造商。 +企业资料继续共用 `business_partners`,供应商、制造商、客户身份保存在 `organization_partners`(`is_manufacturer` 等身份字段可同时存在,旧数据不会自动变成制造商);这些伙伴身份字段不受本次改造影响。 -`materials.supplier_id` 表示默认供应商,`materials.manufacturer_id` 表示制造商,两个字段都是可空 BIGINT,引用 `organization_partners.id`,不是 `business_partners.id`。`manufacturer_model` 表示厂家型号,即制造商为其产品指定的型号或订货识别型号;它是物料上的文本信息,不是制造商关联 ID。它与 `spec`(物料规格)含义不同,例如规格可填写“M8×30”,厂家型号可填写制造商提供的产品型号或编码。 +2026-10-15 起,供应商与制造商不再作为 `materials` 上的单值字段保存,改为两张关系表: -组织关联表增加唯一键 `(organization_id,id)`;物料通过 `(organization_id,supplier_id)` 和 `(organization_id,manufacturer_id)` 复合外键保证引用属于同一组织,删除或修改被引用的关联键会被拒绝。角色匹配需要由业务接口校验,外键本身不验证 `is_supplier` / `is_manufacturer`。 +- `material_manufacturers`(新建):某物料有哪些可用制造商及其厂家型号,等价于原 `materials.manufacturer_model` 下移到“物料 × 制造商”维度。 +- `material_suppliers`(由空表改造):每条记录一条货源——“物料 × 供应商 × 指向的制造商条目 + 采购条件(价格、币种、最小订购量、供货周期、首选标记)”。 -现有 `material_suppliers` 继续保存多个供应商的价格与供货条件;物料的 `supplier_id` 只表示默认选择。已有库使用 `scripts/upgrades/2026-10-12-material-partner-roles.sql` 升级,空库使用 `scripts/init.sql`。 +`materials` 删除 `supplier_id`、`manufacturer_id`、`manufacturer_model` 三列,随之删除 `fk_materials_supplier`、`fk_materials_manufacturer` 两个复合外键及 `idx_materials_org_supplier`、`idx_materials_org_manufacturer` 两个索引;`organization_partners` 上专为这两个复合外键而建的 `uk_organization_partners_org_id(organization_id,id)` 也一并删除,此后无消费者。厂商型号(原 `manufacturer_model`)与物料规格(`spec`)含义不同:规格如“M8×30”,厂家型号是制造商提供的产品型号或编码,现保存在 `material_manufacturers.model`。 + +本次改造后**全库所有外键均为单列**:数据库只保证被引用的记录存在,公司与物料的一致性(伙伴属于同一公司、货源指向的制造商条目属于同一物料与公司)全部交由应用层校验,与分类父级、基本单位等既有单列外键的处理一致;不再有 `(organization_id, supplier_id)` 这类复合外键,也不再依赖 `(organization_id,id)` 唯一键。 + +详细决策(D1–D10)与列清单见 [物料多供应商与多制造商关系设计](superpowers/specs/2026-10-15-material-supplier-manufacturer-design.md)。本仓库没有增量升级脚本,已有库请删库后用 `scripts/init.sql` 重建。 ## 13. 物料来源与备注(2026-10-14) -`materials.source` 为可空来源字段,允许值为「外购」或「外协」;`materials.remark` 为可空备注,最多 500 字。旧记录升级后两列均保持 `NULL`,新建和批量导入也允许留空。已有库使用 `scripts/upgrades/2026-10-14-material-source-remark.sql` 升级,空库使用 `scripts/init.sql`。 +`materials.source` 为可空来源字段,允许值为「外购」或「外协」;`materials.remark` 为可空备注,最多 500 字。旧记录升级后两列均保持 `NULL`,新建和批量导入也允许留空。结构以 `scripts/init.sql` 为准;本仓库没有增量升级脚本,已有库删库后用 `scripts/init.sql` 重建。 ## 14. 物料日志操作人用户 ID(2026-10-14) -`material_logs.operator_id` 保存当前登录用户 ID,允许为空以兼容旧日志和系统生成日志;`operator` 名称继续保存并用于展示。该列不设置用户外键,保留用户被删除后的历史标识。已有库使用 `scripts/upgrades/2026-10-14-material-log-operator-id.sql` 升级,空库使用 `scripts/init.sql`。 +`material_logs.operator_id` 保存当前登录用户 ID,允许为空以兼容旧日志和系统生成日志;`operator` 名称继续保存并用于展示。该列不设置用户外键,保留用户被删除后的历史标识。结构以 `scripts/init.sql` 为准;本仓库没有增量升级脚本,已有库删库后用 `scripts/init.sql` 重建。