# System-Wide Paper Document Phase 0-1 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:** Build the reusable system-wide paper-document foundation and apply it to the highest-priority warehouse documents: special inbound/outbound, production material outbound, and production ledger inbound settlement. **Architecture:** Add generic `Document*` form components under `frontend/src/components/documentForms`, then make existing warehouse wrappers delegate to them so current warehouse documents do not break. Convert only the P0 warehouse forms in `InventoryLedgerView.vue` to the shared paper control language, preserving all existing data models, computed values, validation, submit functions, archive behavior, and backend APIs. **Tech Stack:** Vue 3 SFC, Vite, project CSS in `frontend/src/styles/main.css`, Node built-in test runner. --- ## Scope This plan covers: - Phase 0: system-wide paper document component foundation. - Phase 1: warehouse P0 documents. - P0 warehouse documents: `特殊入库 / 特殊出库`, `生产出库`, `生产台账入库结算单`. This plan does not cover: - Reworking sales, purchase, receipt, quality, delivery, return pages beyond ensuring the new generic foundation does not break them. - Backend archive schema changes. Existing `document_archives` and document archive routes remain unchanged. - Business logic, inventory calculation, production calculation, PDF generation rules, or database migrations. ## File Structure - Create `frontend/src/components/documentForms/DocumentFormShell.vue`: generic form shell around `DocumentPaper`. - Create `frontend/src/components/documentForms/DocumentSection.vue`: generic paper section wrapper. - Create `frontend/src/components/documentForms/DocumentActionBar.vue`: generic action bar that teleports to the paper bottom. - Create `frontend/src/components/documentForms/DocumentField.vue`: generic paper field wrapper for input/select/textarea/read-only slots. - Create `frontend/src/components/documentForms/DocumentSelectorPanel.vue`: paper-native panel for searchable select/multi-select controls. - Create `frontend/src/components/documentForms/DocumentInlineSummary.vue`: paper-native summary strip. - Create `frontend/src/components/documentForms/DocumentSettlePanel.vue`: paper-native settle/exception panel. - Modify `frontend/src/components/documentForms/DocumentPaper.vue`: add generic action host while keeping warehouse-compatible host. - Modify `frontend/src/components/documentForms/WarehouseDocumentFormShell.vue`: delegate to `DocumentFormShell`. - Modify `frontend/src/components/documentForms/WarehouseDocumentSection.vue`: delegate to `DocumentSection`. - Modify `frontend/src/components/documentForms/WarehouseDocumentActionBar.vue`: delegate to `DocumentActionBar`. - Modify `frontend/src/views/InventoryLedgerView.vue`: convert P0 warehouse forms. - Modify `frontend/src/styles/main.css`: add generic paper document styles and keep compatibility aliases. - Modify `frontend/src/views/InventoryLedgerView.test.js`: add P0 regression checks. - Create `frontend/src/components/documentForms/documentForms.test.js`: component foundation source-level regression checks. ## Task 1: Lock Generic Document Foundation Tests **Files:** - Create: `frontend/src/components/documentForms/documentForms.test.js` - Read: `frontend/src/components/documentForms/DocumentPaper.vue` - Read: `frontend/src/components/documentForms/WarehouseDocumentFormShell.vue` - Read: `frontend/src/styles/main.css` - [ ] **Step 1: Write the failing test** Create `frontend/src/components/documentForms/documentForms.test.js`: ```js import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); function readComponent(name) { return readFileSync(join(__dirname, name), "utf8"); } const mainCss = readFileSync(resolve(__dirname, "../../styles/main.css"), "utf8"); describe("document form foundation", () => { it("provides generic system-wide paper form components", () => { [ "DocumentFormShell.vue", "DocumentSection.vue", "DocumentActionBar.vue", "DocumentField.vue", "DocumentSelectorPanel.vue", "DocumentInlineSummary.vue", "DocumentSettlePanel.vue" ].forEach((name) => { const source = readComponent(name); assert.match(source, /document-/); }); }); it("keeps warehouse wrappers as compatibility adapters over generic document components", () => { assert.match(readComponent("WarehouseDocumentFormShell.vue"), /import DocumentFormShell/); assert.match(readComponent("WarehouseDocumentSection.vue"), /import DocumentSection/); assert.match(readComponent("WarehouseDocumentActionBar.vue"), /import DocumentActionBar/); }); it("exposes a generic action host without removing the existing warehouse host", () => { const paper = readComponent("DocumentPaper.vue"); assert.match(paper, /data-document-action-host/); assert.match(paper, /data-warehouse-document-action-host/); }); it("defines generic document paper controls and keeps warehouse aliases compatible", () => { assert.match(mainCss, /\.document-form-shell/); assert.match(mainCss, /\.document-section-grid/); assert.match(mainCss, /\.document-paper-field/); assert.match(mainCss, /\.document-selector-panel/); assert.match(mainCss, /\.document-inline-summary/); assert.match(mainCss, /\.document-settle-panel/); assert.match(mainCss, /\.warehouse-paper-control-grid/); }); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: ```bash cd frontend node --test src/components/documentForms/documentForms.test.js ``` Expected: FAIL because `DocumentFormShell.vue`, `DocumentSection.vue`, `DocumentActionBar.vue`, `DocumentField.vue`, `DocumentSelectorPanel.vue`, `DocumentInlineSummary.vue`, and `DocumentSettlePanel.vue` do not exist yet. ## Task 2: Add Generic Document Components **Files:** - Create: `frontend/src/components/documentForms/DocumentFormShell.vue` - Create: `frontend/src/components/documentForms/DocumentSection.vue` - Create: `frontend/src/components/documentForms/DocumentActionBar.vue` - Create: `frontend/src/components/documentForms/DocumentField.vue` - Create: `frontend/src/components/documentForms/DocumentSelectorPanel.vue` - Create: `frontend/src/components/documentForms/DocumentInlineSummary.vue` - Create: `frontend/src/components/documentForms/DocumentSettlePanel.vue` - Modify: `frontend/src/components/documentForms/DocumentPaper.vue` - [ ] **Step 1: Add `DocumentFormShell.vue`** Create `frontend/src/components/documentForms/DocumentFormShell.vue`: ```vue ``` - [ ] **Step 2: Add `DocumentSection.vue`** Create `frontend/src/components/documentForms/DocumentSection.vue`: ```vue ``` - [ ] **Step 3: Add `DocumentActionBar.vue`** Create `frontend/src/components/documentForms/DocumentActionBar.vue`: ```vue ``` - [ ] **Step 4: Add `DocumentField.vue`** Create `frontend/src/components/documentForms/DocumentField.vue`: ```vue ``` - [ ] **Step 5: Add `DocumentSelectorPanel.vue`** Create `frontend/src/components/documentForms/DocumentSelectorPanel.vue`: ```vue ``` - [ ] **Step 6: Add `DocumentInlineSummary.vue`** Create `frontend/src/components/documentForms/DocumentInlineSummary.vue`: ```vue ``` - [ ] **Step 7: Add `DocumentSettlePanel.vue`** Create `frontend/src/components/documentForms/DocumentSettlePanel.vue`: ```vue ``` - [ ] **Step 8: Add generic and legacy action hosts to `DocumentPaper.vue`** Modify the action host near the end of the template: ```vue
``` - [ ] **Step 9: Run component test** Run: ```bash cd frontend node --test src/components/documentForms/documentForms.test.js ``` Expected: FAIL on CSS assertions because the generic `document-*` styles are not defined yet. ## Task 3: Convert Warehouse Wrappers To Generic Adapters **Files:** - Modify: `frontend/src/components/documentForms/WarehouseDocumentFormShell.vue` - Modify: `frontend/src/components/documentForms/WarehouseDocumentSection.vue` - Modify: `frontend/src/components/documentForms/WarehouseDocumentActionBar.vue` - [ ] **Step 1: Replace `WarehouseDocumentFormShell.vue` with adapter** Use: ```vue ``` - [ ] **Step 2: Replace `WarehouseDocumentSection.vue` with adapter** Use: ```vue ``` - [ ] **Step 3: Replace `WarehouseDocumentActionBar.vue` with adapter** Use: ```vue ``` - [ ] **Step 4: Run component test again** Run: ```bash cd frontend node --test src/components/documentForms/documentForms.test.js ``` Expected: still FAIL on CSS assertions only. ## Task 4: Add Generic Paper Document CSS And Compatibility Aliases **Files:** - Modify: `frontend/src/styles/main.css` - [ ] **Step 1: Add generic style block near existing paper document styles** Add this block before the existing warehouse-specific paper form overrides: ```css /* System-wide paper document form primitives */ .document-form-shell { display: grid; gap: 14px; width: 100%; min-width: 0; padding-bottom: 86px; } .document-form-shell .document-paper-shell { max-width: 1080px; padding: 18px 12px 26px; } .document-form-shell .document-paper-sheet { min-height: 0; padding-bottom: 34px; } .document-form-shell .document-paper-title { font-size: clamp(34px, 4.2vw, 48px); } .document-form-shell .document-number-stamp { min-width: 196px; } .document-form-meta { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 9px 12px; color: rgba(23, 18, 10, 0.72); border: 2px solid rgba(20, 14, 6, 0.82); background: linear-gradient(90deg, rgba(31, 122, 74, 0.12), transparent 42%), rgba(255, 250, 240, 0.54); font-family: "Songti SC", "SimSun", var(--ui-font-body); font-size: 13px; font-weight: 800; letter-spacing: 0.08em; } .document-form-meta strong { display: inline-flex; align-items: center; min-height: 26px; padding: 0 10px; color: #145c39; border: 1.5px solid rgba(31, 122, 74, 0.64); background: rgba(232, 246, 232, 0.62); } .document-form-body { display: grid; gap: 14px; } .document-section { border: 2px solid rgba(20, 14, 6, 0.88); background: rgba(255, 250, 240, 0.38); } .document-section-head { display: flex; align-items: center; gap: 10px; min-height: 46px; padding: 9px 12px; border-bottom: 2px solid rgba(20, 14, 6, 0.88); background: linear-gradient(180deg, rgba(255, 255, 255, 0.26), transparent), rgba(227, 207, 161, 0.72); } .document-section-index { display: inline-grid; place-items: center; min-width: 30px; height: 30px; color: #145c39; border: 1.5px solid rgba(31, 122, 74, 0.64); background: rgba(232, 246, 232, 0.62); font-family: var(--ui-font-display); font-size: 12px; font-weight: 900; } .document-section-head strong { color: #17120a; font-family: var(--ui-font-display); font-size: 16px; font-weight: 900; letter-spacing: 0.06em; } .document-section-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; min-width: 0; padding: 14px; } .document-section-grid > .document-control-grid, .document-section-grid > .document-selector-panel, .document-section-grid > .document-source-table, .document-section-grid > .document-inline-summary, .document-section-grid > .document-settle-panel, .document-section-grid > .document-paper-field { grid-column: 1 / -1; width: 100%; min-width: 0; } .document-control-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 2px solid rgba(20, 14, 6, 0.88); border-left: 2px solid rgba(20, 14, 6, 0.88); background: rgba(255, 250, 240, 0.34); } .document-paper-field { display: grid; grid-template-rows: auto minmax(54px, auto); min-width: 0; margin: 0; border-right: 2px solid rgba(20, 14, 6, 0.88); border-bottom: 2px solid rgba(20, 14, 6, 0.88); background: rgba(255, 250, 240, 0.44); } .document-paper-field-wide { grid-column: span 2; } .document-paper-field-full { grid-column: 1 / -1; } .document-paper-field > span { display: flex; align-items: center; min-height: 32px; padding: 7px 10px; color: rgba(23, 18, 10, 0.78); border-bottom: 1px solid rgba(20, 14, 6, 0.56); background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), transparent), rgba(227, 207, 161, 0.62); font-family: var(--ui-font-display); font-size: 13px; font-weight: 900; letter-spacing: 0.08em; } .document-paper-field input, .document-paper-field select, .document-paper-field textarea { width: 100%; min-width: 0; min-height: 48px; padding: 10px 11px; color: #17120a; border: 0; border-radius: 0; outline: 0; background: transparent; box-shadow: none; font-family: "Songti SC", "SimSun", var(--ui-font-body); font-size: 15px; font-weight: 800; line-height: 1.45; } .document-paper-field textarea { min-height: 104px; resize: vertical; } .document-paper-field input:focus, .document-paper-field select:focus, .document-paper-field textarea:focus { background: linear-gradient(90deg, rgba(31, 122, 74, 0.12), transparent 42%), rgba(255, 255, 255, 0.48); box-shadow: inset 0 0 0 2px rgba(31, 122, 74, 0.42); } .document-paper-readonly-value { display: flex; align-items: center; min-height: 48px; margin: 0; padding: 10px 11px; color: #145c39; background: repeating-linear-gradient(-45deg, rgba(31, 122, 74, 0.06) 0 5px, transparent 5px 10px), rgba(232, 246, 232, 0.32); font-family: "Songti SC", "SimSun", var(--ui-font-body); font-size: 14px; font-weight: 900; line-height: 1.5; overflow-wrap: anywhere; word-break: break-word; } .document-selector-panel { display: grid; grid-template-columns: 220px minmax(0, 1fr); min-width: 0; border-top: 2px solid rgba(20, 14, 6, 0.88); border-left: 2px solid rgba(20, 14, 6, 0.88); background: rgba(255, 250, 240, 0.44); } .document-selector-heading, .document-selector-body { min-width: 0; border-right: 2px solid rgba(20, 14, 6, 0.88); border-bottom: 2px solid rgba(20, 14, 6, 0.88); } .document-selector-heading { display: grid; grid-template-rows: auto 1fr; } .document-selector-heading span { display: flex; align-items: center; min-height: 32px; padding: 7px 10px; color: rgba(23, 18, 10, 0.78); border-bottom: 1px solid rgba(20, 14, 6, 0.56); background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), transparent), rgba(227, 207, 161, 0.62); font-family: var(--ui-font-display); font-size: 13px; font-weight: 900; letter-spacing: 0.08em; } .document-selector-heading strong, .document-selector-body { padding: 10px 11px; font-family: "Songti SC", "SimSun", var(--ui-font-body); font-weight: 900; } .document-inline-summary { min-width: 0; padding: 12px 14px; color: #145c39; border: 2px solid rgba(20, 14, 6, 0.88); background: linear-gradient(90deg, rgba(31, 122, 74, 0.12), transparent 42%), rgba(232, 246, 232, 0.52); font-family: "Songti SC", "SimSun", var(--ui-font-body); font-size: 15px; font-weight: 900; } .document-settle-panel { display: grid; gap: 0; min-width: 0; border-top: 2px solid rgba(20, 14, 6, 0.88); border-left: 2px solid rgba(20, 14, 6, 0.88); background: linear-gradient(90deg, rgba(180, 83, 9, 0.08), transparent 48%), rgba(255, 250, 240, 0.5); } .document-action-anchor { display: none; } .document-action-bar { position: relative; z-index: 1; display: flex; justify-content: flex-end; align-items: center; width: fit-content; max-width: 1080px; margin: 0 0 0 auto; padding: 10px; border: 1px solid rgba(31, 122, 74, 0.24); border-radius: 18px; background: linear-gradient(90deg, rgba(232, 246, 232, 0.92), rgba(255, 250, 240, 0.96)), rgba(255, 255, 255, 0.92); box-shadow: 0 -12px 28px rgba(5, 11, 18, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.68); backdrop-filter: blur(14px); } .document-action-bar--with-state { justify-content: space-between; width: min(100%, 1080px); } .document-action-state { min-width: 0; color: var(--document-paper-muted, #6a5631); font-size: 13px; font-weight: 900; line-height: 1.45; } .document-action-buttons { display: inline-flex; flex: 0 0 auto; flex-wrap: wrap; gap: 10px; justify-content: flex-end; } @media (max-width: 960px) { .document-form-meta, .document-section-grid, .document-control-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .document-selector-panel { grid-template-columns: 1fr; } } @media (max-width: 620px) { .document-form-shell .document-paper-shell { padding-inline: 0; } .document-form-meta, .document-section-grid, .document-control-grid { grid-template-columns: 1fr; } .document-paper-field, .document-paper-field-wide, .document-paper-field-full { grid-column: 1 / -1; } .document-action-bar, .document-action-bar--with-state, .document-action-state, .document-action-buttons { width: 100%; } } ``` - [ ] **Step 2: Add warehouse compatibility aliases below generic styles** Add: ```css .warehouse-document-operation-form, .warehouse-document-form { display: grid; gap: 14px; width: 100%; min-width: 0; } .warehouse-document-form-meta { display: contents; } .warehouse-document-body { display: contents; } .warehouse-document-section { display: block; } .warehouse-document-section-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; min-width: 0; padding: 14px; } .warehouse-paper-control-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); } ``` - [ ] **Step 3: Run component test to verify generic foundation passes** Run: ```bash cd frontend node --test src/components/documentForms/documentForms.test.js ``` Expected: PASS. ## Task 5: Add P0 Warehouse Regression Tests **Files:** - Modify: `frontend/src/views/InventoryLedgerView.test.js` - [ ] **Step 1: Add assertions for P0 document conversion** Append these tests inside the existing `describe("InventoryLedgerView warehouse action layout", () => { })` block: ```js it("keeps special adjustment documents inside the paper document language", () => { assert.match(source, /special-adjustment-paper-grid/); assert.match(source, /special-adjustment-paper-warning/); assert.match(source, /special-adjustment-paper-lines/); assert.match(source, /special-adjustment-paper-line-card/); assert.doesNotMatch(source, /
[\s\S]*?specialAdjustmentForm\.reason/); assert.match(mainCss, /\.special-adjustment-paper-grid/); assert.match(mainCss, /\.special-adjustment-paper-line-card/); }); it("keeps production outbound document controls paper-native", () => { assert.match(source, /production-out-paper-grid/); assert.match(source, /production-out-source-lot-panel/); assert.match(source, /production-out-source-lot-table/); assert.doesNotMatch(source, /
选择原材料库存批次出库/); assert.match(mainCss, /\.production-out-source-lot-panel/); }); it("keeps production ledger inbound settlement controls paper-native", () => { assert.match(source, /production-ledger-inbound-paper-grid/); assert.match(source, /production-ledger-inbound-summary/); assert.match(source, /production-ledger-inbound-metrics/); assert.doesNotMatch(source, /
[\s\S]*?productionWorkOrderInboundForm\.finished_qty/); assert.match(mainCss, /\.production-ledger-inbound-metrics/); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: ```bash cd frontend node --test src/views/InventoryLedgerView.test.js ``` Expected: FAIL because the P0 class names are not in `InventoryLedgerView.vue` or CSS yet. ## Task 6: Convert Special Inbound / Special Outbound Paper Form **Files:** - Modify: `frontend/src/views/InventoryLedgerView.vue` - Modify: `frontend/src/styles/main.css` - [ ] **Step 1: Replace the special adjustment warning and header fields** In the `specialAdjustmentDrawerOpen` drawer, replace: ```vue
特殊调整会直接改变库存明细 保存后写入库存流水和特殊调整记录,请仅在补录、纠错、线下已处理等特殊场景使用。
``` with: ```vue
特殊调整会直接改变库存明细 保存后写入库存流水和特殊调整记录,请仅在补录、纠错、线下已处理等特殊场景使用。
``` - [ ] **Step 2: Replace add-line action area** Replace: ```vue
请先填写调整说明
``` with: ```vue
请先填写调整说明 每条明细都会写入库存流水,调整说明会同步进入变更记录。
``` - [ ] **Step 3: Rename special adjustment line containers** Replace class names: ```vue class="special-adjustment-lines" class="special-adjustment-line-card" ``` with: ```vue class="special-adjustment-paper-lines special-adjustment-lines" class="special-adjustment-paper-line-card special-adjustment-line-card" ``` Keep existing child classes such as `special-adjustment-card-grid`, `special-adjustment-card-field`, and `special-adjustment-card-preview` for now because they already use a paper-grid look. - [ ] **Step 4: Add CSS aliases** Add near the existing special adjustment styles: ```css .special-adjustment-paper-warning { display: grid; gap: 5px; color: #78350f; background: linear-gradient(90deg, rgba(180, 83, 9, 0.13), transparent 50%), rgba(255, 250, 240, 0.64); } .special-adjustment-paper-warning strong { color: #78350f; font-size: 15px; } .special-adjustment-paper-warning span, .special-adjustment-paper-toolbar span { font-size: 12px; font-weight: 900; } .special-adjustment-paper-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } .special-adjustment-paper-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .special-adjustment-paper-lines { display: grid; grid-column: 1 / -1; gap: 14px; min-width: 0; } .special-adjustment-paper-line-card { position: relative; overflow: hidden; border: 1.5px solid rgba(20, 14, 6, 0.72); background: linear-gradient(90deg, rgba(31, 122, 74, 0.06), transparent 44%), rgba(255, 250, 240, 0.72); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5), 0 8px 18px rgba(52, 39, 15, 0.08); } ``` - [ ] **Step 5: Run focused test** Run: ```bash cd frontend node --test src/views/InventoryLedgerView.test.js ``` Expected: special adjustment test passes; production out and production ledger inbound tests still fail. ## Task 7: Convert Production Out Paper Form **Files:** - Modify: `frontend/src/views/InventoryLedgerView.vue` - Modify: `frontend/src/styles/main.css` - [ ] **Step 1: Replace production out tip and product selector** In the `productionDrawerOpen` drawer, replace: ```vue
选择原材料库存批次出库,后续小程序报工按库存批次号推进。
``` with: ```vue
选择原材料库存批次出库,后续小程序报工按库存批次号推进。一次生产出库只能选择一个材料库存批次。
``` - [ ] **Step 2: Replace production stock lot selector** Replace: ```vue ``` with: ```vue
库存批次号 搜索并选择一个材料库存批次号
``` - [ ] **Step 3: Replace selected lot table wrapper** Change: ```vue
``` to: ```vue
``` - [ ] **Step 4: Replace issue weight/reference quantity group** Replace the production out `double-field` group with: ```vue
``` - [ ] **Step 5: Add CSS** Add: ```css .production-out-paper-tip { color: #145c39; } .production-out-paper-grid, .production-out-source-lot-panel, .production-out-source-lot-table { grid-column: 1 / -1; } .production-out-source-lot-panel .stock-lot-control { min-height: 50px; border: 0; border-radius: 0; background: rgba(255, 255, 255, 0.32); box-shadow: inset 0 0 0 1.5px rgba(31, 122, 74, 0.28); } .production-out-source-lot-table { margin: 0; overflow-x: auto; border-top: 2px solid rgba(20, 14, 6, 0.88); border-left: 2px solid rgba(20, 14, 6, 0.88); border-radius: 0; background: rgba(255, 250, 240, 0.46); box-shadow: none; } ``` - [ ] **Step 6: Run focused test** Run: ```bash cd frontend node --test src/views/InventoryLedgerView.test.js ``` Expected: special adjustment and production out tests pass; production ledger inbound test still fails. ## Task 8: Convert Production Ledger Inbound Settlement Paper Form **Files:** - Modify: `frontend/src/views/InventoryLedgerView.vue` - Modify: `frontend/src/styles/main.css` - [ ] **Step 1: Replace settlement workflow tip and ledger select** In `productionWorkOrderInboundDrawerOpen`, replace: ```vue
选择生产台账后,系统按领料重量、单件毛重和已入库成品数量填入理论余料、理论废料。理论值只是辅助参考,实际称重可修改;偏差超过上下15%时需要填写说明。
``` with: ```vue
选择生产台账后,系统按领料重量、单件毛重和已入库成品数量填入理论余料、理论废料。理论值只是辅助参考,实际称重可修改;偏差超过上下15%时需要填写说明。
``` - [ ] **Step 2: Add settlement summary class to preview grid** Change: ```vue
``` to: ```vue
``` - [ ] **Step 3: Replace the triple-field input group** Replace: ```vue
``` with: ```vue
``` - [ ] **Step 4: Replace theoretical metric grid class** Change: ```vue
``` to: ```vue
``` Then change each nested `
` to use: ```vue
``` Keep the labels and computed values unchanged. - [ ] **Step 5: Add CSS** Add: ```css .production-ledger-inbound-tip, .production-ledger-inbound-paper-grid, .production-ledger-inbound-summary, .production-ledger-inbound-metrics { grid-column: 1 / -1; } .production-ledger-inbound-tip { color: #145c39; } .production-ledger-inbound-metrics { border-top: 2px solid rgba(20, 14, 6, 0.88); border-left: 2px solid rgba(20, 14, 6, 0.88); } ``` - [ ] **Step 6: Run focused test** Run: ```bash cd frontend node --test src/views/InventoryLedgerView.test.js ``` Expected: all `InventoryLedgerView` tests pass. ## Task 9: Full Verification **Files:** - Test: `frontend/src/components/documentForms/documentForms.test.js` - Test: `frontend/src/views/InventoryLedgerView.test.js` - Build: `frontend/package.json` - [ ] **Step 1: Run document component tests** Run: ```bash cd frontend node --test src/components/documentForms/documentForms.test.js ``` Expected: PASS. - [ ] **Step 2: Run warehouse view tests** Run: ```bash cd frontend node --test src/views/InventoryLedgerView.test.js ``` Expected: PASS. - [ ] **Step 3: Run frontend build** Run: ```bash cd frontend npm run build ``` Expected: build succeeds. Existing Vite chunk-size warning is acceptable. - [ ] **Step 4: Browser verify P0 documents** Using the in-app browser at `http://localhost:5173/inventory-ledger`, verify: - `原材料库 -> 特殊入库`: warning, reason, warehouse, add-line toolbar, and line cards visually belong to the paper document. - `原材料库 -> 特殊出库`: same as special inbound. - `原材料库 -> 生产出库`: product selector, source lot selector, selected lot table, and issue weight/reference quantity are paper-native. - `工具 -> 生产台账入库`: ledger selector, preview summary, finished/surplus/scrap inputs, theoretical metrics, settle checkbox, and abnormal explanation area are paper-native. - `原材料库 -> 客料入库`: approved template does not regress. - `采购订单` and `销售订单`: existing paper documents still render. ## Task 10: Completion Review **Files:** - Inspect: `git diff --stat` - Inspect: `git status --short` - [ ] **Step 1: Check whitespace** Run: ```bash git diff --check ``` Expected: no output. - [ ] **Step 2: Check changed files** Run: ```bash git status --short ``` Expected changed files should be limited to: ```text frontend/src/components/documentForms/DocumentActionBar.vue frontend/src/components/documentForms/DocumentField.vue frontend/src/components/documentForms/DocumentFormShell.vue frontend/src/components/documentForms/DocumentInlineSummary.vue frontend/src/components/documentForms/DocumentPaper.vue frontend/src/components/documentForms/DocumentSection.vue frontend/src/components/documentForms/DocumentSelectorPanel.vue frontend/src/components/documentForms/DocumentSettlePanel.vue frontend/src/components/documentForms/WarehouseDocumentActionBar.vue frontend/src/components/documentForms/WarehouseDocumentFormShell.vue frontend/src/components/documentForms/WarehouseDocumentSection.vue frontend/src/components/documentForms/documentForms.test.js frontend/src/styles/main.css frontend/src/views/InventoryLedgerView.test.js frontend/src/views/InventoryLedgerView.vue ``` Plus any already-existing dirty files that predated execution. - [ ] **Step 3: Summarize residual risks** Report: - Which P0 documents were browser-verified. - Whether build passed. - Whether any browser verification was limited by narrow viewport. - That backend APIs and business logic were intentionally not changed. ## Self-Review - Spec coverage: covers Phase 0 generic foundation and Phase 1 P0 warehouse documents from `2026-06-13-system-wide-paper-document-unification-design.md`. - Placeholder scan: no placeholder markers or fill-later wording. - Type consistency: component names are consistently `DocumentFormShell`, `DocumentSection`, `DocumentActionBar`, `DocumentField`, `DocumentSelectorPanel`, `DocumentInlineSummary`, `DocumentSettlePanel`. - Scope guard: does not alter backend archive routes, inventory calculation, production calculation, or database schema.