From 431d7b98355e3c0331b9d1be02e7de22afe32f08 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Mon, 31 Aug 2026 14:56:52 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E7=94=9F=E4=BA=A7=E6=8E=A5=E6=94=B6?= =?UTF-8?q?=E6=8E=A5=E7=A7=8D=E9=87=8F=E6=96=87=E6=9C=AC=E5=8C=96=E4=B8=8E?= =?UTF-8?q?=E5=86=BB=E5=AD=98=E4=BB=85=E6=97=A5=E6=9C=9F=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按设计拆为字段单测、弹窗、后端 SQL/API、验收四步。 Co-authored-by: Cursor --- ...1-production-reception-inoculation-cryo.md | 426 ++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md diff --git a/docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md b/docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md new file mode 100644 index 00000000..45fcc0ce --- /dev/null +++ b/docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md @@ -0,0 +1,426 @@ +# 生产接收:接种量文本化、冻存时间仅日期 — 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:** 将生产样本接收的「接种量」改为最长 255 的自由文本,将「冻存时间」改为仅日期(选择/展示 `YYYY-MM-DD`)。 + +**Architecture:** 库表 `inoculation_amount` 由 `decimal(18,4)` 改为 `VARCHAR(255)`;前后端按字符串读写。冻存时间列保持 `datetime`,前端 `kind: 'date'` + `el-date-picker type="date"`,展示用 `formatDateOnly`;后端继续 `parse_datetime` 兼容仅日期字符串。弹窗改动集中在共用 `SampleReceptionDialog.vue`。 + +**Tech Stack:** Vue 3 + Element Plus(web-admin)、FastAPI + SQLAlchemy(backend)、MySQL 5.7、node:test(前端纯函数单测) + +**Spec:** `docs/superpowers/specs/2026-08-31-production-reception-inoculation-cryo-design.md` + +--- + +## File map + +| File | Responsibility | +|------|----------------| +| `docs/sql/2026-08-31-production-reception-inoculation-amount-varchar.sql` | 线上/本地 `ALTER` 迁移 | +| `docs/sql/zkxw.sql` | 基线 CREATE 中列类型同步 | +| `backend/app/models/models.py` | ORM 列类型 | +| `backend/app/api/sample_production_reception.py` | Pydantic body 类型 | +| `backend/app/crud/sample_production_reception.py` | 读写作字符串 | +| `web-admin/src/utils/sample-production-reception-fields.ts` | 字段 kind / 类型 / 展示 | +| `web-admin/src/utils/sample-production-reception-fields.test.ts` | 字段定义与展示单测 | +| `web-admin/src/api/sample-production-reception.ts` | Payload TS 类型 | +| `web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue` | 表单控件与回填 | + +--- + +### Task 1: 前端字段定义 + 展示(TDD) + +**Files:** +- Create: `web-admin/src/utils/sample-production-reception-fields.test.ts` +- Modify: `web-admin/src/utils/sample-production-reception-fields.ts` + +- [ ] **Step 1: Write the failing test** + +```ts +import { describe, it } from 'node:test' +import assert from 'node:assert/strict' +import { + resolveProductionReceptionFields, + formatProductionFieldValue, + type ProductionReceptionRecord +} from './sample-production-reception-fields' + +describe('inoculationAmount / cryopreservationTime field kinds', () => { + it('marks inoculationAmount as text and cryopreservationTime as date for UC-MSC profiles', () => { + for (const source of ['cell_storage', 'research_donation'] as const) { + const fields = resolveProductionReceptionFields(source, '脐带间充质干细胞') + const inoc = fields.find((f) => f.key === 'inoculationAmount') + const cryo = fields.find((f) => f.key === 'cryopreservationTime') + assert.ok(inoc) + assert.equal(inoc.kind, 'text') + assert.equal(inoc.maxLength, 255) + assert.ok(cryo) + assert.equal(cryo.kind, 'date') + } + }) + + it('formats free-text inoculation and date-only cryopreservation', () => { + const fields = resolveProductionReceptionFields('cell_storage', '脐带间充质干细胞') + const inoc = fields.find((f) => f.key === 'inoculationAmount')! + const cryo = fields.find((f) => f.key === 'cryopreservationTime')! + const record: ProductionReceptionRecord = { + inoculationAmount: '约 5×10⁶ cells,接种体积 2ml', + cryopreservationTime: '2026-07-14 15:30:00' + } + assert.equal(formatProductionFieldValue(inoc, record), '约 5×10⁶ cells,接种体积 2ml') + assert.equal(formatProductionFieldValue(cryo, record), '2026-07-14') + }) +}) +``` + +- [ ] **Step 2: Run test to verify it fails** + +```bash +cd web-admin && node --import tsx --test src/utils/sample-production-reception-fields.test.ts +``` + +Expected: FAIL(`kind` 仍为 `number`/`datetime`,或 `maxLength` / `date` 分支不存在) + +- [ ] **Step 3: Update field definitions and formatter** + +In `web-admin/src/utils/sample-production-reception-fields.ts`: + +1. Extend kind union: + +```ts +kind: 'number' | 'text' | 'datetime' | 'date' | 'select' | 'integer' +maxLength?: number +``` + +2. Change defs: + +```ts +inoculationAmount: { + key: 'inoculationAmount', + label: '接种量', + kind: 'text', + maxLength: 255 +}, +cryopreservationTime: { + key: 'cryopreservationTime', + label: '冻存时间', + kind: 'date' +}, +``` + +3. Change record type: + +```ts +inoculationAmount?: string +``` + +4. In `formatProductionFieldValue`, after infectious / before or with datetime: + +```ts +if (def.kind === 'datetime') { + return formatDateTimeDisplay(String(raw)) || '—' +} +if (def.kind === 'date') { + return formatDateOnly(String(raw)) || '—' +} +``` + +(`formatDateOnly` 已在本文件顶部从 `@/utils/datetime-display` 导入。) + +- [ ] **Step 4: Run test to verify it passes** + +```bash +cd web-admin && node --import tsx --test src/utils/sample-production-reception-fields.test.ts +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add web-admin/src/utils/sample-production-reception-fields.ts \ + web-admin/src/utils/sample-production-reception-fields.test.ts +git commit -m "$(cat <<'EOF' +feat(web-admin): 生产接收接种量改文本、冻存时间改日期展示 + +字段 kind 与只读格式对齐产品:自由文本接种量、冻存仅 YYYY-MM-DD。 +EOF +)" +``` + +--- + +### Task 2: 弹窗表单与 API 类型 + +**Files:** +- Modify: `web-admin/src/api/sample-production-reception.ts` +- Modify: `web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue` + +- [ ] **Step 1: Update API payload type** + +In `web-admin/src/api/sample-production-reception.ts`: + +```ts +inoculationAmount?: string +``` + +- [ ] **Step 2: Update SampleReceptionDialog template** + +Replace the text / datetime branches so text supports `maxLength`,并增加 `date`: + +```vue + + + + +``` + +- [ ] **Step 3: Fix populateFromProduction for date fields** + +In `populateFromProduction`,对 `date`(及处理时间同类)回填截日期: + +```ts +function populateFromProduction(rec: ProductionReceptionRecord) { + prodForm.processTime = formatProcessTimeDate(rec.processTime) || todayDate() + prodForm.handlerUserId = rec.handlerUserId || '' + prodForm.handlerName = rec.handlerName || '' + prodForm.remark = rec.remark || '' + for (const field of productionFieldDefs.value) { + const raw = rec[field.key as keyof ProductionReceptionRecord] + if (field.kind === 'date') { + prodForm[field.key] = formatProcessTimeDate(raw != null ? String(raw) : '') || '' + } else { + prodForm[field.key] = raw as never + } + } +} +``` + +(`formatProcessTimeDate` 即 `formatDateOnly` 封装,已导入本文件。) + +`resetProdForm` 中 `date` 与 `text` 一样置 `''`(现有 `integer || number ? undefined : ''` 已覆盖)。 + +- [ ] **Step 4: Smoke-check TypeScript on touched files(optional local)** + +```bash +cd web-admin && pnpm exec vue-tsc --noEmit 2>&1 | head -n 40 +``` + +若环境慢可跳过;至少确认无新增明显类型错误。 + +- [ ] **Step 5: Commit** + +```bash +git add web-admin/src/api/sample-production-reception.ts \ + web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue +git commit -m "$(cat <<'EOF' +feat(web-admin): 样本接收弹窗接种量文本输入、冻存仅选日期 + +共用 SampleReceptionDialog,生产工单与我的生产任务一并生效。 +EOF +)" +``` + +--- + +### Task 3: 后端模型 / API / CRUD + SQL + +**Files:** +- Create: `docs/sql/2026-08-31-production-reception-inoculation-amount-varchar.sql` +- Modify: `docs/sql/zkxw.sql`(CREATE 中 `inoculation_amount` 一行) +- Modify: `backend/app/models/models.py` +- Modify: `backend/app/api/sample_production_reception.py` +- Modify: `backend/app/crud/sample_production_reception.py` + +- [ ] **Step 1: Add migration SQL** + +Create `docs/sql/2026-08-31-production-reception-inoculation-amount-varchar.sql`: + +```sql +-- 生产接收接种量:数值 → 自由文本(最长 255) +-- 执行前请备份;MySQL 会将既有 decimal 值转为对应数字字符串。 + +ALTER TABLE biz_sample_production_reception + MODIFY COLUMN inoculation_amount VARCHAR(255) NULL COMMENT '接种量'; +``` + +- [ ] **Step 2: Sync baseline zkxw.sql CREATE column** + +In `docs/sql/zkxw.sql`,将: + +```sql +`inoculation_amount` decimal(18,4) DEFAULT NULL COMMENT '接种量', +``` + +改为: + +```sql +`inoculation_amount` varchar(255) DEFAULT NULL COMMENT '接种量', +``` + +(仅改 CREATE 定义行;不必改历史 INSERT 中的 NULL/数值字面量。) + +- [ ] **Step 3: Update SQLAlchemy model** + +In `backend/app/models/models.py` `BizSampleProductionReception`: + +```python +inoculation_amount = Column(String(255), nullable=True, comment="接种量") +``` + +- [ ] **Step 4: Update API body type** + +In `backend/app/api/sample_production_reception.py` `ProductionReceptionBody`: + +```python +inoculation_amount: Optional[str] = Field(None, alias="inoculationAmount") +``` + +`_body_to_payload` 保持: + +```python +"inoculationAmount": body.inoculation_amount, +``` + +(可选:写入前 `strip`;若在 CRUD 统一 strip 亦可。) + +`cryopreservation_time` 仍 `parse_datetime`——`YYYY-MM-DD` 经 `fromisoformat` 可得当日 0 点,无需改解析。 + +- [ ] **Step 5: Update CRUD read/write** + +In `backend/app/crud/sample_production_reception.py` `_row_to_dict`: + +```python +"inoculationAmount": row.inoculation_amount or "", +``` + +In `_apply_payload_to_row`: + +```python +row.inoculation_amount = ( + (str(data.get("inoculationAmount") or "").strip() or None) + if "inoculationAmount" in allowed + else None +) +``` + +- [ ] **Step 6: Apply migration on local DB(if available)** + +```bash +# 按本地习惯执行 ALTER;示例(需正确库连接): +# mysql ... < docs/sql/2026-08-31-production-reception-inoculation-amount-varchar.sql +``` + +若本机无库,跳过并在验收说明中注明「需执行迁移」。 + +- [ ] **Step 7: Commit** + +```bash +git add docs/sql/2026-08-31-production-reception-inoculation-amount-varchar.sql \ + docs/sql/zkxw.sql \ + backend/app/models/models.py \ + backend/app/api/sample_production_reception.py \ + backend/app/crud/sample_production_reception.py +git commit -m "$(cat <<'EOF' +feat(backend): 生产接收接种量改为 VARCHAR(255) 自由文本 + +同步模型、API、CRUD 与 SQL 迁移/基线,支持数字与说明文案组合录入。 +EOF +)" +``` + +--- + +### Task 4: 图谱更新与验收 + +**Files:** +- 可能更新:`backend/graphify-out/*`、`web-admin/graphify-out/*`(由脚本生成) + +- [ ] **Step 1: Update knowledge graph** + +```bash +./scripts/update-graph.sh all +``` + +- [ ] **Step 2: Re-run frontend unit test** + +```bash +cd web-admin && node --import tsx --test src/utils/sample-production-reception-fields.test.ts +``` + +Expected: PASS + +- [ ] **Step 3: Manual acceptance checklist** + +1. 脐带间充质干细胞:接种量可输入含中文/符号的长文案(≤255),保存回显一致。 +2. 冻存时间选择器无时分秒;保存后只读与再编辑均为 `YYYY-MM-DD`。 +3. 历史冻存带时分秒数据打开弹窗可回填日期部分。 +4. 生产工单与我的生产任务行为一致。 +5. 围产期造血干细胞 profile 数值字段仍为 `DecimalInput`。 + +- [ ] **Step 4: Mark design implemented** + +Update header in `docs/superpowers/specs/2026-08-31-production-reception-inoculation-cryo-design.md`: + +```markdown +> 状态:已实现 +> 完成日期:2026-08-31 +> 实现计划:`docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md` +``` + +- [ ] **Step 5: Commit remaining docs/graph if dirty** + +```bash +git add docs/superpowers/specs/2026-08-31-production-reception-inoculation-cryo-design.md \ + docs/superpowers/plans/2026-08-31-production-reception-inoculation-cryo.md \ + backend/graphify-out web-admin/graphify-out 2>/dev/null || true +git status +# 若有变更: +git commit -m "$(cat <<'EOF' +docs: 标记接种量/冻存时间改造已实现并更新图谱 + +EOF +)" +``` + +--- + +## Spec coverage check + +| Spec 项 | Task | +|---------|------| +| 接种量 VARCHAR(255) + 文本框 | Task 1–3 | +| 冻存仅日期、列仍 datetime | Task 1–2(展示/表单)、Task 3(解析不变) | +| 共用弹窗两页生效 | Task 2 | +| SQL 迁移 + zkxw 基线 | Task 3 | +| 验收场景 | Task 4 | + +## Placeholder scan + +无 TBD /「类似 Task N」占位;类型名与 `inoculationAmount` / `cryopreservationTime` 全计划一致。