docs: add qc material month import design and implementation plan
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
6957952b4c
commit
f7ee984d04
2 files changed
+1045
No files matched your search
@@ -0,0 +1,810 @@
|
||||
# 质检物料库 · 月初固定导入 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 在质检物料库提供可维护的「月初入库模板」与「月初导入」预览确认入库,减少每月固定物品的重复录入。
|
||||
|
||||
**Architecture:** 新增租户级模板表与 CRUD/preview/confirm API;confirm 同事务 `ensure` 物资后复用现有 `batch_stock_in`。前端在批次列表工具栏增加两个入口,弹窗拆成独立组件,避免继续膨胀 `index.vue`。
|
||||
|
||||
**Tech Stack:** FastAPI / SQLAlchemy / Pydantic v2 / Vue 3 / Element Plus;权限沿用 `biz:qcMaterial:list`。
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-08-19-qc-material-month-import-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| File | Responsibility |
|
||||
|------|----------------|
|
||||
| Create: `docs/sql/2026-08-19-qc-material-month-template.sql` | 建表 SQL |
|
||||
| Modify: `backend/app/models/qc_material.py` | 增加 `BizQcMaterialMonthTemplate` |
|
||||
| Create: `backend/app/crud/qc_material_stock_in_no.py` | 入库编号生成纯函数 + 查库取下一序号 |
|
||||
| Create: `backend/tests/test_qc_material_stock_in_no.py` | 编号生成单测 |
|
||||
| Create: `backend/app/crud/qc_material_month_template.py` | 模板 CRUD、preview、confirm |
|
||||
| Create: `backend/tests/test_qc_material_month_template.py` | 模板校验 / ensure 物资纯逻辑单测 |
|
||||
| Modify: `backend/app/crud/qc_material.py` | `get_material_by_code`;`create_material(..., commit=True)` 支持不提交 |
|
||||
| Modify: `backend/app/api/qc.py` | 模板 list/create/update/delete/preview/confirm 路由与 Body |
|
||||
| Create: `web-admin/src/api/qc-material-month-template.ts` | 前端 API |
|
||||
| Create: `web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthTemplateDialog.vue` | 维护模板弹窗 |
|
||||
| Create: `web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthImportDialog.vue` | 月初导入预览弹窗 |
|
||||
| Modify: `web-admin/src/views/quality-inspection/qc-material/index.vue` | 工具栏按钮 + 挂载两弹窗 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: SQL 建表
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/2026-08-19-qc-material-month-template.sql`
|
||||
|
||||
- [ ] **Step 1: Write SQL**
|
||||
|
||||
```sql
|
||||
-- 质检物料库:月初入库模板
|
||||
-- MySQL 5.7 / utf8mb4;可重复执行需人工判断表是否已存在
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `biz_qc_material_month_template` (
|
||||
`id` varchar(64) NOT NULL COMMENT '主键ID',
|
||||
`material_id` varchar(64) DEFAULT NULL COMMENT '关联 biz_qc_material.id,可空',
|
||||
`material_code` varchar(64) NOT NULL COMMENT '物料编码',
|
||||
`material_name` varchar(200) NOT NULL COMMENT '物料名称',
|
||||
`spec_model` varchar(200) DEFAULT NULL COMMENT '规格型号',
|
||||
`unit` varchar(32) DEFAULT NULL COMMENT '单位',
|
||||
`catalog_no` varchar(128) NOT NULL COMMENT '货号',
|
||||
`batch_no` varchar(128) NOT NULL COMMENT '批号',
|
||||
`manufacturer` varchar(200) NOT NULL COMMENT '厂家',
|
||||
`unit_price` decimal(18,2) NOT NULL COMMENT '单价(元)',
|
||||
`amount` decimal(18,2) NOT NULL COMMENT '金额(元)',
|
||||
`in_qty` decimal(18,4) NOT NULL COMMENT '默认入库数量',
|
||||
`expire_date` date NOT NULL COMMENT '默认过期日期',
|
||||
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
|
||||
`order_num` int(11) DEFAULT 0 COMMENT '排序号',
|
||||
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
|
||||
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人id',
|
||||
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) DEFAULT NULL COMMENT '更新人id',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_qc_mmt_tenant` (`tenant_no`, `del_flag`, `order_num`),
|
||||
KEY `idx_qc_mmt_material` (`material_id`, `del_flag`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='质检物料月初入库模板';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/sql/2026-08-19-qc-material-month-template.sql
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs(sql): add qc material month import template table
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: SQLAlchemy 模型
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/models/qc_material.py`
|
||||
- Modify: `backend/app/models/models.py`
|
||||
|
||||
- [ ] **Step 1: Append model class**(放在 `BizQcMaterial` 之后、`BizQcMaterialStockBatch` 之前)
|
||||
|
||||
```python
|
||||
class BizQcMaterialMonthTemplate(Base):
|
||||
__tablename__ = "biz_qc_material_month_template"
|
||||
|
||||
id = Column(String(64), primary_key=True, comment="主键ID")
|
||||
material_id = Column(String(64), nullable=True, comment="关联物料ID")
|
||||
material_code = Column(String(64), nullable=False, comment="物料编码")
|
||||
material_name = Column(String(200), nullable=False, comment="物料名称")
|
||||
spec_model = Column(String(200), nullable=True, comment="规格型号")
|
||||
unit = Column(String(32), nullable=True, comment="单位")
|
||||
catalog_no = Column(String(128), nullable=False, comment="货号")
|
||||
batch_no = Column(String(128), nullable=False, comment="批号")
|
||||
manufacturer = Column(String(200), nullable=False, comment="厂家")
|
||||
unit_price = Column(Numeric(18, 2), nullable=False, comment="单价(元)")
|
||||
amount = Column(Numeric(18, 2), nullable=False, comment="金额(元)")
|
||||
in_qty = Column(Numeric(18, 4), nullable=False, comment="默认入库数量")
|
||||
expire_date = Column(Date, nullable=False, comment="默认过期日期")
|
||||
tenant_no = Column(String(64), nullable=False, default="zkxw", comment="租户编号")
|
||||
order_num = Column(Integer, nullable=True, comment="排序号")
|
||||
remark = Column(String(512), nullable=True, comment="备注")
|
||||
create_by = Column(String(64), nullable=True, comment="创建人id")
|
||||
create_dept_id = Column(String(64), nullable=False, default="", comment="创建人部门ID")
|
||||
create_time = Column(DateTime, nullable=False, default=datetime.now, comment="创建时间")
|
||||
update_by = Column(String(64), nullable=True, comment="更新人id")
|
||||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
||||
del_flag = Column(SmallInteger, nullable=False, default=0, comment="删除标记")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 `backend/app/models/models.py` 末尾 import 旁补充导出**
|
||||
|
||||
找到:
|
||||
|
||||
```python
|
||||
from app.models.qc_material import BizQcMaterial, BizQcMaterialStockBatch, BizQcMaterialUsage # noqa: E402,F401
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```python
|
||||
from app.models.qc_material import ( # noqa: E402,F401
|
||||
BizQcMaterial,
|
||||
BizQcMaterialMonthTemplate,
|
||||
BizQcMaterialStockBatch,
|
||||
BizQcMaterialUsage,
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/models/qc_material.py backend/app/models/models.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(backend): add BizQcMaterialMonthTemplate model
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 入库编号生成(TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/app/crud/qc_material_stock_in_no.py`
|
||||
- Create: `backend/tests/test_qc_material_stock_in_no.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
# backend/tests/test_qc_material_stock_in_no.py
|
||||
"""质检物料入库编号:RK + YYYYMMDD + 3位序号。"""
|
||||
import unittest
|
||||
from datetime import date
|
||||
|
||||
from app.crud.qc_material_stock_in_no import (
|
||||
format_stock_in_no,
|
||||
max_seq_from_stock_in_nos,
|
||||
next_stock_in_nos,
|
||||
)
|
||||
|
||||
|
||||
class TestFormatStockInNo(unittest.TestCase):
|
||||
def test_format(self):
|
||||
self.assertEqual(format_stock_in_no(date(2026, 8, 19), 1), "RK20260819001")
|
||||
self.assertEqual(format_stock_in_no(date(2026, 8, 19), 12), "RK20260819012")
|
||||
self.assertEqual(format_stock_in_no(date(2026, 8, 19), 999), "RK20260819999")
|
||||
|
||||
|
||||
class TestMaxSeq(unittest.TestCase):
|
||||
def test_empty(self):
|
||||
self.assertEqual(max_seq_from_stock_in_nos("RK20260819", []), 0)
|
||||
|
||||
def test_parses_matching_prefix(self):
|
||||
nos = ["RK20260819001", "RK20260819007", "OTHER", "RK20260818099"]
|
||||
self.assertEqual(max_seq_from_stock_in_nos("RK20260819", nos), 7)
|
||||
|
||||
def test_ignores_bad_suffix(self):
|
||||
nos = ["RK20260819ABC", "RK20260819100"]
|
||||
self.assertEqual(max_seq_from_stock_in_nos("RK20260819", nos), 100)
|
||||
|
||||
|
||||
class TestNextStockInNos(unittest.TestCase):
|
||||
def test_allocates_contiguous(self):
|
||||
got = next_stock_in_nos(date(2026, 8, 19), count=3, existing_nos=["RK20260819002"])
|
||||
self.assertEqual(got, ["RK20260819003", "RK20260819004", "RK20260819005"])
|
||||
|
||||
def test_zero_count(self):
|
||||
self.assertEqual(next_stock_in_nos(date(2026, 8, 19), count=0, existing_nos=[]), [])
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_qc_material_stock_in_no.py -v
|
||||
```
|
||||
|
||||
Expected: FAIL(模块不存在)
|
||||
|
||||
- [ ] **Step 3: Implement**
|
||||
|
||||
```python
|
||||
# backend/app/crud/qc_material_stock_in_no.py
|
||||
"""质检物料入库编号生成:RK + YYYYMMDD + 3位序号。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import date
|
||||
from typing import Iterable, List
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.qc_material import BizQcMaterialStockBatch
|
||||
|
||||
_SUFFIX_RE = re.compile(r"^(\d{3})$")
|
||||
|
||||
|
||||
def format_stock_in_no(d: date, seq: int) -> str:
|
||||
if seq < 1 or seq > 999:
|
||||
raise ValueError(f"入库编号序号超出范围: {seq}")
|
||||
return f"RK{d.strftime('%Y%m%d')}{seq:03d}"
|
||||
|
||||
|
||||
def prefix_for_date(d: date) -> str:
|
||||
return f"RK{d.strftime('%Y%m%d')}"
|
||||
|
||||
|
||||
def max_seq_from_stock_in_nos(prefix: str, nos: Iterable[str]) -> int:
|
||||
max_seq = 0
|
||||
for raw in nos:
|
||||
s = (raw or "").strip()
|
||||
if not s.startswith(prefix):
|
||||
continue
|
||||
suffix = s[len(prefix) :]
|
||||
m = _SUFFIX_RE.match(suffix)
|
||||
if not m:
|
||||
continue
|
||||
max_seq = max(max_seq, int(m.group(1)))
|
||||
return max_seq
|
||||
|
||||
|
||||
def next_stock_in_nos(
|
||||
d: date,
|
||||
*,
|
||||
count: int,
|
||||
existing_nos: Iterable[str],
|
||||
) -> List[str]:
|
||||
if count <= 0:
|
||||
return []
|
||||
prefix = prefix_for_date(d)
|
||||
start = max_seq_from_stock_in_nos(prefix, existing_nos) + 1
|
||||
if start + count - 1 > 999:
|
||||
raise ValueError("当日入库编号序号已用尽(上限 999)")
|
||||
return [format_stock_in_no(d, start + i) for i in range(count)]
|
||||
|
||||
|
||||
def list_existing_stock_in_nos_for_prefix(
|
||||
db: Session, *, tenant_no: str, prefix: str
|
||||
) -> List[str]:
|
||||
like = f"{prefix}%"
|
||||
rows = (
|
||||
db.query(BizQcMaterialStockBatch.stock_in_no)
|
||||
.filter(
|
||||
BizQcMaterialStockBatch.tenant_no == tenant_no,
|
||||
BizQcMaterialStockBatch.del_flag == 0,
|
||||
BizQcMaterialStockBatch.stock_in_no.like(like),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return [r[0] for r in rows if r[0]]
|
||||
|
||||
|
||||
def allocate_stock_in_nos(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
stock_in_date: date,
|
||||
count: int,
|
||||
) -> List[str]:
|
||||
prefix = prefix_for_date(stock_in_date)
|
||||
existing = list_existing_stock_in_nos_for_prefix(db, tenant_no=tenant_no, prefix=prefix)
|
||||
return next_stock_in_nos(stock_in_date, count=count, existing_nos=existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_qc_material_stock_in_no.py -v
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/crud/qc_material_stock_in_no.py backend/tests/test_qc_material_stock_in_no.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(backend): add RK+date stock-in number allocator for qc materials
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 物资 ensure 前置 — create_material(commit=False)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/crud/qc_material.py`
|
||||
|
||||
- [ ] **Step 1: Add `get_material_by_code`**
|
||||
|
||||
在 `get_material` 旁增加:
|
||||
|
||||
```python
|
||||
def get_material_by_code(
|
||||
db: Session, material_code: str, *, tenant_no: str
|
||||
) -> Optional[BizQcMaterial]:
|
||||
code = (material_code or "").strip()
|
||||
if not code:
|
||||
return None
|
||||
return (
|
||||
db.query(BizQcMaterial)
|
||||
.filter(
|
||||
BizQcMaterial.tenant_no == tenant_no,
|
||||
BizQcMaterial.material_code == code,
|
||||
BizQcMaterial.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `create_material` 增加 `commit: bool = True`**
|
||||
|
||||
`db.add(obj)` 后改为:
|
||||
|
||||
```python
|
||||
if commit:
|
||||
db.commit()
|
||||
db.refresh(obj)
|
||||
else:
|
||||
db.flush()
|
||||
return obj
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/crud/qc_material.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(backend): support non-committing qc material create and lookup by code
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 模板 CRUD + preview + confirm
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/app/crud/qc_material_month_template.py`
|
||||
- Create: `backend/tests/test_qc_material_month_template.py`
|
||||
|
||||
- [ ] **Step 1: Write unit tests for field validation**
|
||||
|
||||
测试 `_require_template_fields`:缺货号、数量≤0、缺编码/名称 → `ValueError`;合法 payload 返回规范化 dict。
|
||||
|
||||
- [ ] **Step 2: Implement CRUD module**
|
||||
|
||||
关键签名(须完整实现):
|
||||
|
||||
```python
|
||||
# backend/app/crud/qc_material_month_template.py
|
||||
"""质检物料月初入库模板 CRUD / preview / confirm。"""
|
||||
|
||||
def _row_to_dict(row: BizQcMaterialMonthTemplate) -> dict: ...
|
||||
|
||||
def list_templates(db, *, tenant_no: str) -> list: ...
|
||||
def create_template(db, *, data: dict, tenant_no: str, user_id: Optional[str], create_dept_id: str = "") -> dict: ...
|
||||
def update_template(db, row_id: str, *, data: dict, tenant_no: str, user_id: Optional[str]) -> dict: ...
|
||||
def soft_delete_template(db, row_id: str, *, tenant_no: str) -> None: ...
|
||||
|
||||
def preview_templates(db, *, tenant_no: str, stock_in_date: date) -> list:
|
||||
"""加载模板行,分配默认 stock_in_no,返回预览明细。"""
|
||||
|
||||
def ensure_material_id(
|
||||
db,
|
||||
*,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
material_id: Optional[str],
|
||||
material_code: str,
|
||||
material_name: str,
|
||||
spec_model: Optional[str],
|
||||
unit: Optional[str],
|
||||
) -> str:
|
||||
"""
|
||||
1) material_id 有值:存在且未删;status!=1 则 ValueError「物资已停用」
|
||||
2) 否则按 code 查找;找到则返回 id(不覆盖主数据名称)
|
||||
3) 否则 create_material(..., commit=False)
|
||||
"""
|
||||
|
||||
def confirm_month_import(
|
||||
db,
|
||||
*,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
stock_in_date: date,
|
||||
items: list,
|
||||
remark: Optional[str] = None,
|
||||
operate_time: Optional[datetime] = None,
|
||||
) -> int:
|
||||
"""ensure 物资后调用 batch_stock_in;失败由其 rollback,未提交 insert 一并回滚。"""
|
||||
```
|
||||
|
||||
`preview_templates` 返回驼峰字段:`templateId`, `materialId`, `materialCode`, `materialName`, `specModel`, `unit`, `stockInNo`, `stockInDate`, `expireDate`, `catalogNo`, `batchNo`, `manufacturer`, `unitPrice`, `amount`, `qty`, `remark`。
|
||||
|
||||
- [ ] **Step 3: Run tests**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_qc_material_month_template.py tests/test_qc_material_stock_in_no.py -v
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/crud/qc_material_month_template.py backend/tests/test_qc_material_month_template.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(backend): add qc material month template CRUD preview and confirm
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: API 路由
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/api/qc.py`
|
||||
|
||||
- [ ] **Step 1: 在 `BatchStockInBody` 附近增加 Pydantic models**
|
||||
|
||||
```python
|
||||
class QcMonthTemplateBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
material_id: Optional[str] = Field(None, alias="materialId")
|
||||
material_code: str = Field(..., alias="materialCode")
|
||||
material_name: str = Field(..., alias="materialName")
|
||||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||||
unit: Optional[str] = None
|
||||
catalog_no: str = Field(..., alias="catalogNo")
|
||||
batch_no: str = Field(..., alias="batchNo")
|
||||
manufacturer: str
|
||||
unit_price: float = Field(..., alias="unitPrice")
|
||||
amount: float
|
||||
in_qty: float = Field(..., alias="inQty", gt=0)
|
||||
expire_date: str = Field(..., alias="expireDate")
|
||||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
class QcMonthTemplatePreviewBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
stock_in_date: str = Field(..., alias="stockInDate")
|
||||
|
||||
|
||||
class QcMonthTemplateConfirmItem(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
material_id: Optional[str] = Field(None, alias="materialId")
|
||||
material_code: str = Field(..., alias="materialCode")
|
||||
material_name: str = Field(..., alias="materialName")
|
||||
spec_model: Optional[str] = Field(None, alias="specModel")
|
||||
unit: Optional[str] = None
|
||||
stock_in_no: str = Field(..., alias="stockInNo")
|
||||
expire_date: str = Field(..., alias="expireDate")
|
||||
catalog_no: str = Field(..., alias="catalogNo")
|
||||
batch_no: str = Field(..., alias="batchNo")
|
||||
manufacturer: str
|
||||
unit_price: float = Field(..., alias="unitPrice")
|
||||
amount: float
|
||||
qty: float = Field(..., gt=0)
|
||||
remark: Optional[str] = None
|
||||
|
||||
|
||||
class QcMonthTemplateConfirmBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
stock_in_date: str = Field(..., alias="stockInDate")
|
||||
items: List[QcMonthTemplateConfirmItem] = Field(..., min_length=1)
|
||||
remark: Optional[str] = None
|
||||
stock_in_time: Optional[str] = Field(None, alias="stockInTime")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 增加 6 个路由**(均 `require_menu_code("biz:qcMaterial:list")`)
|
||||
|
||||
| Method | Path |
|
||||
|--------|------|
|
||||
| GET | `/qc-material-month-templates` |
|
||||
| POST | `/qc-material-month-templates` |
|
||||
| PUT | `/qc-material-month-templates/{id}` |
|
||||
| DELETE | `/qc-material-month-templates/{id}` |
|
||||
| POST | `/qc-material-month-templates/preview` |
|
||||
| POST | `/qc-material-month-templates/confirm` |
|
||||
|
||||
`confirm`:解析日期 → `confirm_month_import` → `success_response({"count": n})`。金额字段与批量入库一致调用 `_resolve_stock_in_amounts`(若适用)。
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/api/qc.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(api): expose qc material month template endpoints
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 前端 API 模块
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/api/qc-material-month-template.ts`
|
||||
|
||||
- [ ] **Step 1: Implement**
|
||||
|
||||
```typescript
|
||||
import request from '@/utils/http'
|
||||
|
||||
export interface QcMonthTemplateRow {
|
||||
id: string
|
||||
materialId?: string
|
||||
materialCode: string
|
||||
materialName: string
|
||||
specModel?: string
|
||||
unit?: string
|
||||
catalogNo: string
|
||||
batchNo: string
|
||||
manufacturer: string
|
||||
unitPrice: number
|
||||
amount: number
|
||||
inQty: number
|
||||
expireDate: string
|
||||
orderNum?: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export interface QcMonthImportPreviewLine {
|
||||
templateId?: string
|
||||
materialId?: string
|
||||
materialCode: string
|
||||
materialName: string
|
||||
specModel?: string
|
||||
unit?: string
|
||||
stockInNo: string
|
||||
stockInDate: string
|
||||
expireDate: string
|
||||
catalogNo: string
|
||||
batchNo: string
|
||||
manufacturer: string
|
||||
unitPrice: number
|
||||
amount: number
|
||||
qty: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
export function fetchQcMonthTemplateList() {
|
||||
return request.get<QcMonthTemplateRow[]>({
|
||||
url: '/web/admin/qc-material-month-templates'
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchCreateQcMonthTemplate(data: Record<string, any>) {
|
||||
return request.post<QcMonthTemplateRow>({
|
||||
url: '/web/admin/qc-material-month-templates',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchUpdateQcMonthTemplate(id: string, data: Record<string, any>) {
|
||||
return request.put<QcMonthTemplateRow>({
|
||||
url: `/web/admin/qc-material-month-templates/${id}`,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchDeleteQcMonthTemplate(id: string) {
|
||||
return request.delete<void>({
|
||||
url: `/web/admin/qc-material-month-templates/${id}`
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPreviewQcMonthImport(stockInDate: string) {
|
||||
return request.post<QcMonthImportPreviewLine[]>({
|
||||
url: '/web/admin/qc-material-month-templates/preview',
|
||||
data: { stockInDate }
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchConfirmQcMonthImport(data: {
|
||||
stockInDate: string
|
||||
items: QcMonthImportPreviewLine[]
|
||||
remark?: string
|
||||
stockInTime?: string
|
||||
}) {
|
||||
return request.post<{ count: number }>({
|
||||
url: '/web/admin/qc-material-month-templates/confirm',
|
||||
data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/api/qc-material-month-template.ts
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add qc month template API client
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 维护模板弹窗组件
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthTemplateDialog.vue`
|
||||
|
||||
- [ ] **Step 1: 实现弹窗**
|
||||
|
||||
要点:
|
||||
|
||||
- `el-dialog`:`class="dialog-form-layout"`、`append-to-body`、`modal-class="protocol-dialog-mask"`、宽约 `960px`、`align-center`
|
||||
- `v-model`;打开时 `fetchQcMonthTemplateList`
|
||||
- 表格 + 新增/编辑子表单(`label-position="top"`,`form-grid-2`)
|
||||
- 可选关联启用物资(`fetchQcMaterialOptions`)
|
||||
- 字段:物资、编码、名称、规格、单位、货号、批号、厂家、单价、金额、数量、过期日期、备注
|
||||
- 金额默认 `unitPrice * inQty`(未手工改时联动)
|
||||
- 删除:`ElMessageBox.confirm` 后调删除 API
|
||||
- 底栏 Close / Check 图标;空列表 `el-empty`
|
||||
- Props/Emits:`modelValue` / `update:modelValue` / `saved`
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthTemplateDialog.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add qc month template maintenance dialog
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: 月初导入预览弹窗
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthImportDialog.vue`
|
||||
|
||||
- [ ] **Step 1: 实现弹窗**
|
||||
|
||||
要点:
|
||||
|
||||
- 打开后调 `fetchPreviewQcMonthImport(stockInDate)`
|
||||
- 顶部入库日期变更时重新 preview(保证序号与库一致)
|
||||
- 明细可编辑;行删除仅本地;不提供预览新增行
|
||||
- 确认:`fetchConfirmQcMonthImport`;成功后 `emit('success')`
|
||||
- 前端校验对齐批量入库必填
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthImportDialog.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add qc month import preview dialog
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: 挂到质检物料库页
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-material/index.vue`
|
||||
|
||||
- [ ] **Step 1: 工具栏增加按钮**(建议在批量废弃与导出之间)
|
||||
|
||||
```vue
|
||||
<el-button class="btn-ghost" @click="handleMonthImport">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
月初导入
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" @click="monthTemplateVisible = true">
|
||||
<el-icon><Setting /></el-icon>
|
||||
维护月初模板
|
||||
</el-button>
|
||||
```
|
||||
|
||||
显式 import `Calendar`、`Setting`(若页面未自动导入)。
|
||||
|
||||
- [ ] **Step 2: `handleMonthImport`**
|
||||
|
||||
先拉模板列表;空则 `ElMessage.warning('请先维护月初模板')` 并打开维护弹窗;否则打开导入弹窗。解包方式与本页现有 `request` 返回习惯一致。
|
||||
|
||||
- [ ] **Step 3: 挂载组件**
|
||||
|
||||
```vue
|
||||
<QcMaterialMonthTemplateDialog v-model="monthTemplateVisible" />
|
||||
<QcMaterialMonthImportDialog
|
||||
v-model="monthImportVisible"
|
||||
@success="handleMonthImportSuccess"
|
||||
/>
|
||||
```
|
||||
|
||||
成功回调刷新批次列表(现有 `loadData` / 等价函数)。
|
||||
|
||||
- [ ] **Step 4: 本地 UI 冒烟**
|
||||
|
||||
1. 打开质检物料库
|
||||
2. 维护模板 1~2 行
|
||||
3. 月初导入 → 改日期看编号 → 确认
|
||||
4. 列表出现新批次;同月再导一次仍成功
|
||||
|
||||
- [ ] **Step 5: 更新图谱**
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh all
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/quality-inspection/qc-material/index.vue \
|
||||
web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthTemplateDialog.vue \
|
||||
web-admin/src/views/quality-inspection/qc-material/QcMaterialMonthImportDialog.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): wire month import into qc material library toolbar
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: 验收对照 Spec
|
||||
|
||||
- [ ] **Step 1: 对照清单**
|
||||
|
||||
| Spec 项 | 验证 |
|
||||
|---------|------|
|
||||
| 独立模板表 | SQL + model |
|
||||
| 维护弹窗 CRUD | Task 8 |
|
||||
| 两步预览确认 | Task 9–10 |
|
||||
| RK 编号 | Task 3 + preview |
|
||||
| 自动建物资 | confirm ensure |
|
||||
| 同月可多次 | 手工冒烟 |
|
||||
| 复用批量入库 | confirm → batch_stock_in |
|
||||
| 仅质检 | 无其它模块改动 |
|
||||
|
||||
- [ ] **Step 2: 跑测试**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_qc_material_stock_in_no.py tests/test_qc_material_month_template.py -v
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 全部通过后可将 Spec 状态改为「已实现」**
|
||||
|
||||
---
|
||||
|
||||
## Spec coverage self-check
|
||||
|
||||
| Spec 章节 | Plan Task |
|
||||
|-----------|-----------|
|
||||
| §3 模板表 | Task 1–2 |
|
||||
| §4.1 维护 | Task 5–6, 8, 10 |
|
||||
| §4.2 导入预览确认 | Task 5–6, 9–10 |
|
||||
| §4.3 编号 | Task 3 |
|
||||
| §5 confirm 薄封装 | Task 5–6 |
|
||||
| §6 工具栏 / 弹窗 | Task 8–10 |
|
||||
| §7 停用物资 / 回滚 | Task 5 ensure + batch_stock_in |
|
||||
| §8 测试 | Task 3, 5, 11 |
|
||||
| 非目标 | 无对应 Task(刻意) |
|
||||
|
||||
## Type consistency
|
||||
|
||||
- 预览/确认:`stockInNo`, `expireDate`, `catalogNo`, `batchNo`, `unitPrice`, `qty`
|
||||
- 模板数量字段:`inQty`;确认映射为 `qty`
|
||||
@@ -0,0 +1,235 @@
|
||||
# 质检物料库 · 月初固定导入设计
|
||||
|
||||
> 日期: 2026-08-19
|
||||
> 状态: 已实现(待库表执行与联调验收)
|
||||
> 父模块: 质量检测 → 质检物料库
|
||||
> 相关: `docs/superpowers/specs/2026-06-11-qc-material-batch-inbound-design.md`
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 现状
|
||||
|
||||
质检物料库(`/#/quality-group/quality-base/qc-material`)已支持:
|
||||
|
||||
- 单条入库 / 批量入库(入库批次:编号、日期、货号、批号、厂家、单价、金额、数量、过期日期等)
|
||||
- 批量出库、批量废弃
|
||||
- 批次列表、变动记录、库存盘点等
|
||||
|
||||
业务侧每月初会固定入库一批物品,字段大体相同,目前需反复手工录入,效率低。
|
||||
|
||||
### 目标
|
||||
|
||||
1. 提供租户可维护的 **月初入库模板**(固定那一批物品,用户可自行增删改)
|
||||
2. 提供 **月初导入**:按模板展开预览 → 用户可改 → 确认后走现有批量入库链路,生成入库批次与变动流水
|
||||
3. 预览时按规则 **默认生成入库编号**(可改);物资不存在时可按模板信息 **自动创建**
|
||||
|
||||
### 已确认决策
|
||||
|
||||
| 问题 | 决策 |
|
||||
|------|------|
|
||||
| 导入本质 | 模板驱动的批量入库(非 Excel 文件导入) |
|
||||
| 模板字段完整度 | 尽量带齐现有入库字段;每月主要改入库日期 / 编号,其余可微调 |
|
||||
| 维护入口 | 仍在质检物料库页:工具栏「月初导入」+「维护月初模板」弹窗 |
|
||||
| 同月重复导入 | 允许随时多次执行,系统不拦截 |
|
||||
| 入库编号 | 预览时默认生成 `RK + YYYYMMDD + 3位序号`,用户可改(现网手工录入,本期补生成能力) |
|
||||
| 物资关系 | 可关联已有物资;也可只填物资信息,确认入库时若不存在则自动创建 |
|
||||
| 执行交互 | 两步:打开预览(可改)→ 确认才真正入库 |
|
||||
| 本期范围 | 仅质检物料库 |
|
||||
| 实现路径 | 独立模板表 + 复用现有批量入库 API |
|
||||
|
||||
### 非目标
|
||||
|
||||
- 定时/cron 自动入库
|
||||
- 生产物料库 / 市场物料库同步改造
|
||||
- 同月幂等或「每月只能导一次」
|
||||
- 改造现有单条入库弹窗的编号生成(可后续复用同一生成函数)
|
||||
- Excel 文件上传导入
|
||||
- 模板行携带质检附件 URL
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构与数据流
|
||||
|
||||
```
|
||||
维护月初模板 (CRUD)
|
||||
↓
|
||||
biz_qc_material_month_template(租户级模板行)
|
||||
↓
|
||||
月初导入预览(选入库日期 → 展开 + 生成编号 → 可改可删行)
|
||||
↓
|
||||
现有批量入库 API(必要时同事务 ensure 物资)
|
||||
↓
|
||||
biz_qc_material_stock_batch + biz_qc_material.stock_qty + biz_qc_material_usage
|
||||
```
|
||||
|
||||
原则:
|
||||
|
||||
- **模板不直接改库存**;只有确认入库才写批次与流水
|
||||
- **预览中的增删改不回写模板**(仅影响本次导入)
|
||||
- 库存一致性完全依赖现有 `batch_stock_in` 事务语义(任一行失败整单回滚)
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
### 3.1 新表 `biz_qc_material_month_template`
|
||||
|
||||
一行 = 模板中的一条固定入库明细。字段对齐入库批次(不含入库编号 / 入库日期)。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `id` | VARCHAR(64) UUID PK |
|
||||
| `material_id` | 可空;关联已有 `biz_qc_material.id` |
|
||||
| `material_code` | 物资编码(关联时冗余;待建物资时必填) |
|
||||
| `material_name` | 物资名称 |
|
||||
| `spec_model` | 规格型号 |
|
||||
| `unit` | 单位 |
|
||||
| `catalog_no` | 货号(必填,与入库一致) |
|
||||
| `batch_no` | 批号(必填) |
|
||||
| `manufacturer` | 厂家(必填) |
|
||||
| `unit_price` | 单价(元,必填) |
|
||||
| `amount` | 金额(元,必填;维护时可按单价×数量默认) |
|
||||
| `in_qty` | 默认入库数量(>0) |
|
||||
| `expire_date` | 默认过期日期(必填,与现网批次一致) |
|
||||
| `remark` | 备注 |
|
||||
| 公共字段 | `tenant_no`, `order_num`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` |
|
||||
|
||||
**规则:**
|
||||
|
||||
- 租户隔离:所有读写按当前用户 `tenant_no` 过滤 / 写入
|
||||
- 软删除:`del_flag=1`
|
||||
- 列表默认按 `order_num ASC, create_time ASC`
|
||||
- 若填写 `material_id`:保存时校验物资存在且未删除;建议同步冗余编码/名称/规格/单位(物资主数据变更后,模板冗余可被用户手动刷新或下次编辑覆盖)
|
||||
- 若未填 `material_id`:`material_code`、`material_name` 必填;导入确认时按 `tenant_no + material_code` 查找启用物资,找不到则创建(`stock_qty=0`,`status=1`)
|
||||
|
||||
SQL 脚本落在 `docs/sql/2026-08-19-qc-material-month-template.sql`(实现阶段)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 业务流程
|
||||
|
||||
### 4.1 维护月初模板
|
||||
|
||||
入口:质检物料库 · 批次列表工具栏「维护月初模板」。
|
||||
|
||||
- 弹窗内表格展示当前租户全部模板行
|
||||
- 支持新增一行、编辑、删除(软删)、调整顺序(`order_num`)
|
||||
- 选择已有物资:下拉选启用物资,带出编码/名称/规格/单位
|
||||
- 手填新物资:清空 `material_id`,填编码/名称等;编码在模板内建议唯一(同租户模板行不重复 `material_code`,避免混淆)
|
||||
- 不维护入库编号、入库日期
|
||||
|
||||
### 4.2 月初导入
|
||||
|
||||
1. 用户点击「月初导入」
|
||||
2. 若模板行数为 0:提示先维护模板,并引导打开维护弹窗;不打开空预览
|
||||
3. 打开预览弹窗:
|
||||
- 顶部「本次入库日期」默认今天,可改
|
||||
- 调用 preview:按模板行展开明细,并为每行生成默认 `stock_in_no`
|
||||
- 用户可改任意字段、删除本行(不回写模板);**本期不要求在预览中新增模板外行**(需要额外物资用普通入库/批量入库)
|
||||
4. 确认入库:
|
||||
- 对每行:`ensure_material`(已有 id 则用;否则按编码查/建)
|
||||
- 组装与现有批量入库相同的 payload
|
||||
- 调用现有批量入库逻辑(同一事务)
|
||||
5. 成功:关闭弹窗、刷新批次列表、提示成功条数
|
||||
|
||||
### 4.3 入库编号生成
|
||||
|
||||
格式:`RK` + `YYYYMMDD`(取自本次入库日期)+ **3 位十进制序号**(左补零),例:`RK20260819001`。
|
||||
|
||||
算法(preview 与最终提交前均可调用):
|
||||
|
||||
1. 取租户内当日(按入库日期对应的 `YYYYMMDD`)已存在且匹配 `RK{YYYYMMDD}%` 的 `stock_in_no`,解析最大序号
|
||||
2. 从 `max+1` 起为预览中的 N 行连续分配
|
||||
3. 提交时仍做租户内唯一校验;若冲突(并发入库),返回明确错误,用户改编号后重试
|
||||
|
||||
说明:现网单条/批量入库仍为手工录入;本能力仅服务月初导入预览默认值。生成函数宜放在后端可复用处,便于日后单条入库复用。
|
||||
|
||||
---
|
||||
|
||||
## 5. API 设计
|
||||
|
||||
前缀与现有质检物料一致:`/web/admin/...`,经现有鉴权 / 租户中间件。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/qc-material-month-templates` | 当前租户模板列表 |
|
||||
| POST | `/qc-material-month-templates` | 新增一行 |
|
||||
| PUT | `/qc-material-month-templates/{id}` | 更新 |
|
||||
| DELETE | `/qc-material-month-templates/{id}` | 软删 |
|
||||
| POST | `/qc-material-month-templates/preview` | Body: `{ stockInDate }` → 展开行 + 默认编号 |
|
||||
| POST | `/qc-materials/batch-stock-in`(已有) | 确认入库;前端也可走等价 `/qc-material-batches/batch-stock-in` |
|
||||
|
||||
**确认入库包装(推荐实现方式):**
|
||||
|
||||
- 新增薄封装 `POST /qc-material-month-templates/confirm`:接收预览编辑后的明细列表 + `stockInDate`;服务端 `ensure_material` 后调用已有 `batch_stock_in`
|
||||
- 或:前端先调 ensure/创建物资 API 再调批量入库——**不推荐**(非原子)
|
||||
|
||||
本设计采用 **`confirm` 薄封装**,保证「建物资 + 入库」同事务。
|
||||
|
||||
**权限:** 与质检物料库入库相同(能入库即可维护模板与月初导入);不新增独立菜单。按钮可见性跟随现有入库能力。
|
||||
|
||||
---
|
||||
|
||||
## 6. 前端交互
|
||||
|
||||
页面:`web-admin/src/views/quality-inspection/qc-material/index.vue`(批次列表工具栏)。
|
||||
|
||||
- 工具栏顺序建议:已选 N 项 → **入库**(唯一 `type="primary"`)→ 批量出库 / 批量废弃(现有)→ **月初导入** → **维护月初模板**
|
||||
- 两个弹窗:对齐现有批量入库弹窗风格(`label-position="top"` / 表格明细 / 无 backdrop blur)
|
||||
- 维护弹窗:保存单行即时调 API,或底部统一保存——实现阶段优先 **单行保存 + 删除确认**,与现有 CRUD 习惯一致
|
||||
- 预览弹窗:金额随单价×数量联动规则对齐现有入库(未手工改金额则重算)
|
||||
- 按钮须带 Element Plus 图标(工程 UI 约定)
|
||||
|
||||
界面模式:对照批量入库弹窗(P 系列选型在实现计划中引用 `docs/web-admin-ui-pattern-index.md`)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 校验与错误处理
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 模板为空点导入 | 警告提示,不打开预览 |
|
||||
| 模板行缺必填 | 保存模板时拦截 |
|
||||
| 关联物资已删除/停用 | 维护保存时警告;预览时标出该行,确认前须改绑或去掉该行 |
|
||||
| 待建物资编码已存在 | `ensure` 时绑定已有物资,不重复创建 |
|
||||
| 待建物资编码冲突名称不同 | 绑定已有物资并以已有主数据为准;备注中可不强制覆盖名称(避免静默改主数据) |
|
||||
| 入库编号重复 | 整单失败回滚,提示冲突编号 |
|
||||
| 数量 ≤ 0 / 缺必填批次字段 | 与现有批量入库一致,整单失败 |
|
||||
| 并发同日多用户预览 | 允许;提交时唯一校验兜底 |
|
||||
|
||||
`source_type`:确认入库产生的流水仍为现有手动入库类型(如 `manual_in`);若需区分「月初导入」,可新增 `month_template_in`——**本期默认仍用 `manual_in`**,避免报表分叉;若产品后续要统计,再加枚举。
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试要点
|
||||
|
||||
- 模板 CRUD 租户隔离与软删
|
||||
- 空模板导入被拦截
|
||||
- 预览编号:同日多行连续;跨日日期切换后编号日期段正确
|
||||
- 关联已有物资导入成功,库存与流水正确
|
||||
- 仅填编码的新物资:首次导入自动创建并入库;第二次导入复用同一物资
|
||||
- 预览删行不影响模板
|
||||
- 人为制造编号冲突时整单回滚、库存不变
|
||||
- 停用物资关联行:确认被拒绝或强制用户处理
|
||||
|
||||
---
|
||||
|
||||
## 9. 组件边界(便于实现拆分)
|
||||
|
||||
| 单元 | 职责 | 依赖 |
|
||||
|------|------|------|
|
||||
| 模板 CRUD API/CRUD | 模板行持久化 | `BizQcMaterialMonthTemplate` |
|
||||
| preview 服务 | 展开 + 编号生成 | 模板表 + 已有 `stock_in_no` |
|
||||
| confirm 服务 | ensure 物资 + 调 `batch_stock_in` | `qc_material` + `qc_material_stock_batch` |
|
||||
| 前端维护弹窗 | 模板编辑 UX | 模板 API |
|
||||
| 前端导入弹窗 | 预览编辑 UX | preview + confirm API |
|
||||
|
||||
---
|
||||
|
||||
## 10. 开放问题(实现前可再定,有默认)
|
||||
|
||||
| 项 | 默认 |
|
||||
|----|------|
|
||||
| 预览是否允许临时加行 | 否(用普通批量入库) |
|
||||
| 流水 `source_type` | 继续 `manual_in` |
|
||||
| 单条入库是否同步默认编号 | 否(仅月初导入) |
|
||||
Reference in new issue
Block a user