# Continuous Die Changeover Count 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:** Add the special stamping method `连续模`, require/report `换料次数` for continuous die report items, and carry it through worker reporting, admin review, records, manager dashboard, and Excel export. **Architecture:** Treat `连续模` as a special stamping method recognized by exact trimmed text, parallel to `清洗` but without changing normal work-time, device, beat, workload, lock, or wage rules. Store `换料次数` on `production_report_items` because it belongs to one product/process detail, not the whole report. Keep product `冲压方式` free-text with two suggested values, so field usage remains flexible for current shop-floor terms. **Tech Stack:** FastAPI, SQLAlchemy, Pydantic, MySQL, openpyxl, WeChat Mini Program WXML/WXSS/JS. --- ## File Map Backend: - Create: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/continuous_die.py` - Create: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/scripts/migrate_continuous_die_changeover_count.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/models.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/schemas.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/serializers.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reports.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reviews.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/dashboard.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/excel_export.py` - Test: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/tests/test_continuous_die.py` Frontend: - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/utils/api.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.wxss` Important non-goal: - Do not include `换料次数` in `参考工资` yet. Current wage formula is `成品数量 * 工序单价`; there is no maintained `换料单价`, so calculating extra pay now would require a hidden hard-coded price. This task only records and exports the count for later wage integration. --- ### Task 1: Backend Continuous Die Recognition And Schema Field **Files:** - Create: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/continuous_die.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/models.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/schemas.py` - Create: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/scripts/migrate_continuous_die_changeover_count.py` - Test: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/tests/test_continuous_die.py` - [ ] **Step 1: Write the helper test** Add this to `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/tests/test_continuous_die.py`: ```python from app.services.continuous_die import ( CONTINUOUS_DIE_STAMPING_METHOD, is_continuous_die_stamping_method, ) def test_continuous_die_stamping_method_is_trimmed_exact_match(): assert CONTINUOUS_DIE_STAMPING_METHOD == "连续模" assert is_continuous_die_stamping_method("连续模") is True assert is_continuous_die_stamping_method(" 连续模 ") is True assert is_continuous_die_stamping_method("连续模具") is False assert is_continuous_die_stamping_method("清洗") is False assert is_continuous_die_stamping_method(None) is False ``` - [ ] **Step 2: Run the new test and verify it fails** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python -m pytest tests/test_continuous_die.py -q ``` Expected: FAIL because `app.services.continuous_die` does not exist. - [ ] **Step 3: Create the backend helper** Create `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/continuous_die.py`: ```python CONTINUOUS_DIE_STAMPING_METHOD = "连续模" def clean_text(value: str | None) -> str: return str(value or "").strip() def is_continuous_die_stamping_method(value: str | None) -> bool: return clean_text(value) == CONTINUOUS_DIE_STAMPING_METHOD def is_continuous_die_product(product) -> bool: return bool(product) and is_continuous_die_stamping_method(getattr(product, "stamping_method", None)) def is_continuous_die_item(item) -> bool: return is_continuous_die_stamping_method(getattr(item, "stamping_method", None)) ``` - [ ] **Step 4: Add the DB model field** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/models.py`, add this field to `ProductionReportItem` immediately after `process_unit_price_yuan`: ```python changeover_count: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False, default=0) ``` - [ ] **Step 5: Add the migration script** Create `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/scripts/migrate_continuous_die_changeover_count.py`: ```python from sqlalchemy import text from app.database import engine def main() -> None: with engine.begin() as conn: exists = conn.execute( text( """ SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'production_report_items' AND column_name = 'changeover_count' """ ) ).scalar() if not exists: conn.execute( text( """ ALTER TABLE production_report_items ADD COLUMN changeover_count DECIMAL(12, 2) NOT NULL DEFAULT 0 AFTER process_unit_price_yuan """ ) ) print("migrate_continuous_die_changeover_count done") if __name__ == "__main__": main() ``` - [ ] **Step 6: Add schema fields** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/schemas.py`, add these exact fields to the existing classes: ```python # class MoldNameOut is_continuous_die: bool = False # class ProductOption is_continuous_die: bool = False # class ReportDraftItem changeover_count: float = 0 is_continuous_die: bool = False # class ReportDraftBlock is_continuous_die: bool = False # class ReportSubmitItem changeover_count: float = Field(default=0, ge=0) is_continuous_die: bool = False # class ReportItemOut changeover_count: float = 0 is_continuous_die: bool = False # class ReportItemCorrection changeover_count: float | None = Field(default=None, ge=0) # class DeviceQrOut is_continuous_die: bool = False # class DashboardRow changeover_count: float = 0 is_continuous_die: bool = False ``` - [ ] **Step 7: Run the helper test** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python -m pytest tests/test_continuous_die.py -q ``` Expected: PASS. --- ### Task 2: Backend Report Flow, Review Flow, Dashboard, And Export **Files:** - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/serializers.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reports.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reviews.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/dashboard.py` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/excel_export.py` - Test: `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/tests/test_continuous_die.py` - [ ] **Step 1: Extend backend serialization tests** Append this test to `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/tests/test_continuous_die.py`: ```python from types import SimpleNamespace from app.services.serializers import product_option def test_product_option_marks_continuous_die(): product = SimpleNamespace( attendance_point_name="嘉恒", project_no="P001", product_name="测试产品", profile_no=None, material_code=None, material_name=None, supplier=None, product_net_weight_kg=None, product_gross_weight_kg=None, scrap_loss_rate=None, waste_price_yuan_per_kg=None, device_no="", process_name="1", stamping_method=" 连续模 ", operator_count=1, process_unit_price_yuan=0, standard_beat=10, standard_workload=0, ) option = product_option(product) assert option.stamping_method == " 连续模 " assert option.is_continuous_die is True assert option.is_cleaning is False ``` - [ ] **Step 2: Run the test and verify it fails** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python -m pytest tests/test_continuous_die.py -q ``` Expected: FAIL because `ProductOption` is not yet populated with `is_continuous_die`. - [ ] **Step 3: Update serializers** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/serializers.py`: Add import: ```python from app.services.continuous_die import is_continuous_die_item, is_continuous_die_product ``` Update `product_option`: ```python def product_option(product: Product) -> ProductOption: data = product_out(product).model_dump() data["is_misc"] = is_misc_product(product) data["is_multi_person"] = is_multi_person_product(product) data["is_continuous_die"] = is_continuous_die_product(product) return ProductOption(**data) ``` Update `report_out` item serialization so every `ReportItemOut` includes: ```python changeover_count=as_float(item.changeover_count), is_continuous_die=is_continuous_die_item(item), ``` Update correction maps so `changeover_count` is treated as numeric: ```python numeric_fields = { "operator_count", "process_unit_price_yuan", "good_qty", "defect_qty", "scrap_qty", "changeover_count", } ``` - [ ] **Step 4: Update report draft and submit** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reports.py`: Add import: ```python from app.services.continuous_die import is_continuous_die_item, is_continuous_die_product ``` When building `ReportDraftBlock`, compute: ```python block_is_continuous_die = any(option.is_continuous_die for option in options) ``` Set: ```python is_continuous_die=block_is_continuous_die, ``` When building `ReportDraftItem`, set: ```python changeover_count=0, is_continuous_die=is_continuous_die_product(first) if first else False, ``` When submitting normal report items, compute: ```python item_is_continuous_die = is_continuous_die_product(product) or is_continuous_die_item(item) changeover_count = as_float(item.changeover_count) if not item_is_continuous_die: changeover_count = 0 if changeover_count < 0: raise HTTPException(status_code=400, detail="换料次数不能为负数") ``` Inside each `ProductionReportItem(` constructor in normal report submission, add this keyword argument: ```python changeover_count=changeover_count, ``` In cleaning submit path, set: ```python changeover_count=0, ``` - [ ] **Step 5: Update admin review** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/reviews.py`: Add import: ```python from app.services.continuous_die import is_continuous_die_item, is_continuous_die_product ``` In `_snapshot`, include: ```python "changeover_count": float(item.changeover_count or 0), ``` When product correction changes an item, set: ```python item.changeover_count = item.changeover_count if is_continuous_die_product(product) else 0 ``` When applying item corrections, add: ```python if correction.changeover_count is not None: next_changeover_count = as_float(correction.changeover_count) if next_changeover_count < 0: raise HTTPException(status_code=400, detail="换料次数不能为负数") if not is_continuous_die_item(item): next_changeover_count = 0 if next_changeover_count != as_float(item.changeover_count): item.changeover_count = next_changeover_count has_correction = True ``` In system auto-submit validation, continuous die follows normal production validation; do not skip device or quantity validation. - [ ] **Step 6: Update dashboard aggregation** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/routers/dashboard.py`: Add import: ```python from app.services.continuous_die import is_continuous_die_item, is_continuous_die_product ``` Add correction label: ```python "changeover_count": "换料次数", ``` Inside each item loop: ```python is_continuous_die = is_continuous_die_item(item) or is_continuous_die_product(product) ``` Initialize group fields: ```python "changeover_count": 0.0, "is_continuous_die": is_continuous_die, ``` Accumulate: ```python group["changeover_count"] += as_float(item.changeover_count) group["is_continuous_die"] = group["is_continuous_die"] or is_continuous_die ``` Set `DashboardRow` fields: ```python changeover_count=round2(group["changeover_count"]), is_continuous_die=group["is_continuous_die"], ``` - [ ] **Step 7: Update Excel export** In `/Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint/app/services/excel_export.py`, add `换料次数` after `冲压方式`: ```python "冲压方式", "换料次数", "操作人数", ``` Add the row value: ```python row.stamping_method, row.changeover_count, row.operator_count, ``` Add report flag: ```python if getattr(row, "is_continuous_die", False): report_flags.append("连续模") ``` Leave `reference_wage` as-is: ```python # reference_wage remains good_qty * process_unit_price_yuan until a changeover unit price is maintained. ``` - [ ] **Step 8: Run backend tests** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python -m pytest tests/test_continuous_die.py tests/test_metrics.py tests/test_review_corrections.py -q ``` Expected: all selected tests PASS. --- ### Task 3: Frontend API Normalization **Files:** - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/utils/api.js` - [ ] **Step 1: Add frontend continuous die helpers** Near the existing cleaning helper in `/Users/souplearn/Gitlab/app/JhHardwareWRS/utils/api.js`, add: ```javascript const CONTINUOUS_DIE_STAMPING_METHOD = '连续模' const isContinuousDieStampingMethod = value => String(value || '').trim() === CONTINUOUS_DIE_STAMPING_METHOD const isContinuousDieRow = row => ( !!row && ( !!row.is_continuous_die || !!row.isContinuousDie || isContinuousDieStampingMethod(row.stamping_method || row.stampingMethod) ) ) ``` - [ ] **Step 2: Add continuous die to product and mold mapping** In `toCamelProduct`, add: ```javascript isContinuousDie: isContinuousDieRow(row), ``` In `toCamelMoldOption`, add: ```javascript isContinuousDie: !!row.is_continuous_die || isContinuousDieRow(row), ``` - [ ] **Step 3: Add continuous die to report item mapping** In `toCamelReportItem`, add: ```javascript const isContinuousDie = isContinuousDieRow(row) ``` Return these fields: ```javascript isContinuousDie, changeoverCount: row.changeover_count === null || row.changeover_count === undefined ? 0 : Number(row.changeover_count || 0), ``` Add correction display: ```javascript correctionPair(corrections, 'changeover_count', '换料次数', row.changeover_count || 0), ``` - [ ] **Step 4: Add continuous die to draft mapping and submit payload** In `emptyDraftItem`, `optionToDraftItem`, and `apiDraftItemToCamel`, add: ```javascript isContinuousDie: false, changeoverCount: '', changeoverCountError: false, ``` For product-derived items: ```javascript isContinuousDie: option ? !!option.isContinuousDie : false, changeoverCount: '', ``` In `toSubmitPayload`, add: ```javascript changeover_count: item.isContinuousDie ? Number(item.changeoverCount || 0) : 0, is_continuous_die: !!item.isContinuousDie, ``` - [ ] **Step 5: Add dashboard mapping** In `listDashboardReports` row mapping, add: ```javascript isContinuousDie: isContinuousDieRow(row), changeoverCount: row.changeover_count || 0, ``` - [ ] **Step 6: Export helpers** At the bottom module export object, export: ```javascript isContinuousDieStampingMethod, isContinuousDieRow, ``` --- ### Task 4: Product List Input Suggestions And Continuous Die Watermark **Files:** - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.wxss` - [ ] **Step 1: Add product page state and handlers** In `data`, add: ```javascript stampingMethodSuggestions: ['清洗', '连续模'], stampingSuggestionVisible: false, ``` In `onInput`, trim stamping method only on save, not while typing. Add: ```javascript if (field === 'stampingMethod') { this.setData({ stampingSuggestionVisible: false, [`form.${field}`]: value, }) return } ``` Add handlers: ```javascript onStampingFocus() { this.setData({ stampingSuggestionVisible: true }) }, chooseStampingSuggestion(e) { const value = e.currentTarget.dataset.value this.setData({ 'form.stampingMethod': value, stampingSuggestionVisible: false, }) }, hideStampingSuggestion() { setTimeout(() => { this.setData({ stampingSuggestionVisible: false }) }, 150) }, ``` In `save`, normalize before validation: ```javascript form.stampingMethod = String(form.stampingMethod || '').trim() ``` Keep this rule unchanged except for trimming: ```javascript if (['清洗', '处理杂活'].includes(form.stampingMethod)) { form.standardBeat = 0 } ``` Continuous die must still show and require standard beat. - [ ] **Step 2: Update product page WXML** Replace the `冲压方式` input block with: ```xml 冲压方式 {{item}} ``` On product cards, add: ```xml 连续模 ``` - [ ] **Step 3: Add product page CSS** Add styles to `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/manageProducts/manageProducts.wxss`: ```css .stamping-field { position: relative; } .stamping-suggestion-panel { position: absolute; left: 0; right: 0; top: 112rpx; z-index: 20; background: #fff; border: 1rpx solid #d9e2f2; border-radius: 8rpx; box-shadow: 0 12rpx 32rpx rgba(16, 35, 72, 0.12); overflow: hidden; } .stamping-suggestion-option { padding: 22rpx 24rpx; font-size: 28rpx; color: #1f2a44; border-bottom: 1rpx solid #eef2f7; } .stamping-suggestion-option:last-child { border-bottom: 0; } .continuous-die-stamp { position: absolute; right: 24rpx; top: 24rpx; transform: rotate(12deg); color: #c26b00; border: 2rpx solid rgba(194, 107, 0, 0.55); border-radius: 6rpx; padding: 6rpx 12rpx; font-size: 24rpx; font-weight: 700; background: rgba(255, 244, 224, 0.88); } ``` --- ### Task 5: Worker Reporting Form **Files:** - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.wxss` - [ ] **Step 1: Add validation for changeover count** In `validateDraft`, after raw material batch validation and before quantity validation, add: ```javascript if (item.isContinuousDie) { const text = String(item.changeoverCount || '').trim() const value = Number(text) if (!text || !Number.isInteger(value) || value < 0) { this.setData({ [`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].changeoverCountError`]: true, }) wx.showToast({ title: `${blockDisplay(block)}请填写非负整数换料次数`, icon: 'none', }) return false } } ``` Add a generic input handler or reuse `onQtyInput` with `data-field="changeoverCount"`: ```javascript if (field === 'changeoverCount') { this.setData({ [`draft.deviceBlocks[${blockIndex}].items[${itemIndex}].changeoverCountError`]: false, }) } ``` - [ ] **Step 2: Add WXML field** Inside each non-misc report item, place this between `材料库存批次号` and the readonly product grid: ```xml 换料次数 ``` Add watermarks: ```xml 连续模 ``` - [ ] **Step 3: Add report form CSS** In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportForm/reportForm.wxss`, add: ```css .continuous-item-stamp { position: absolute; right: 20rpx; top: 20rpx; transform: rotate(12deg); padding: 6rpx 12rpx; border-radius: 6rpx; border: 2rpx solid rgba(194, 107, 0, 0.55); color: #b45f00; background: rgba(255, 244, 224, 0.9); font-size: 22rpx; font-weight: 700; } .lower-item-stamp { top: 72rpx; } ``` --- ### Task 6: Admin Review, Records, Dashboard, QR, And Result UI **Files:** - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/review/review.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxss` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.js` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.wxml` - Modify: `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.wxss` - [ ] **Step 1: Review page JS** In `makeEditItem`, include: ```javascript isContinuousDie: !!detail.isContinuousDie, changeoverCount: detail.changeoverCount || 0, changeoverCountError: false, ``` In `productToEditItem`, after product replacement: ```javascript isContinuousDie: !!product.isContinuousDie, changeoverCount: product.isContinuousDie ? (item.changeoverCount || 0) : 0, ``` In `restoreProduct`, carry: ```javascript isContinuousDie: product.isContinuousDie === undefined ? original.isContinuousDie : product.isContinuousDie, changeoverCount: product.isContinuousDie ? (item.changeoverCount || 0) : 0, ``` In `onItemInput`, recalculate only quantity fields as today, but allow `changeoverCount` to update and clear error: ```javascript if (field === 'changeoverCount') { updates[`rows[${reportIndex}].editItems[${itemIndex}].changeoverCountError`] = false } ``` In `approve`, validate: ```javascript const invalidChangeoverItemIndex = (row.editItems || []).findIndex(item => { if (!item.isContinuousDie) { return false } const value = Number(item.changeoverCount) return !Number.isInteger(value) || value < 0 }) if (invalidChangeoverItemIndex >= 0) { const reportIndex = this.data.rows.findIndex(item => String(item.id) === String(id)) this.setData({ [`rows[${reportIndex}].editItems[${invalidChangeoverItemIndex}].changeoverCountError`]: true, }) wx.showToast({ title: '请填写非负整数换料次数', icon: 'none' }) return } ``` In approve patch item corrections, include: ```javascript changeover_count: item.isContinuousDie ? Number(item.changeoverCount || 0) : 0, ``` - [ ] **Step 2: Review page WXML** Add report-level watermark: ```xml 连续模 ``` Add detail-level watermark: ```xml 连续模 ``` Add display under non-cleaning/non-misc detail metrics: ```xml 换料次数 {{detail.changeoverCount || 0}} ``` Add editable field in the review form grid: ```xml 换料次数 ``` In product replacement modal cards, add: ```xml 连续模 ``` - [ ] **Step 3: Records page** In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxml`, add report and detail watermarks plus a detail metric: ```xml 连续模 连续模 换料次数 {{detail.changeoverCount || 0}} ``` - [ ] **Step 4: Dashboard page** In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxml`, add: ```xml 连续模 换料次数 {{item.changeoverCount || 0}} ``` - [ ] **Step 5: QR page** In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxml`, add tags in single-select and batch-select rows: ```xml 连续模 ``` - [ ] **Step 6: Report result page** In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.js`, include item result fields: ```javascript isContinuousDie: !!item.isContinuousDie, changeoverCount: item.changeoverCount || 0, ``` In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/reportResult/reportResult.wxml`, add: ```xml 连续模 换料次数 {{result.changeoverCount || 0}} ``` - [ ] **Step 7: Shared CSS pattern** Add orange continuous die styles to each page CSS touched in this task: ```css .continuous-die-watermark, .detail-continuous-stamp, .result-continuous-stamp { position: absolute; right: 24rpx; top: 24rpx; transform: rotate(12deg); padding: 6rpx 12rpx; border-radius: 6rpx; border: 2rpx solid rgba(194, 107, 0, 0.55); color: #b45f00; background: rgba(255, 244, 224, 0.9); font-size: 22rpx; font-weight: 700; } .continuous-tag { color: #b45f00; background: #fff4e0; } ``` Use the class names shown in this task's WXML snippets. If a page already has `.lower-watermark`, use that existing class for vertical offset; otherwise add only the continuous die classes shown above. --- ### Task 7: Manual Verification And Release Steps **Files:** - Verify changed frontend files above. - Verify backend files above. - [ ] **Step 1: Run backend migration locally** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python scripts/migrate_continuous_die_changeover_count.py ``` Expected output: ```text migrate_continuous_die_changeover_count done ``` - [ ] **Step 2: Run backend tests** Run: ```bash cd /Users/souplearn/Gitlab/app/JhHardwareWRS_BackPoint .venv/bin/python -m pytest tests/test_continuous_die.py tests/test_metrics.py tests/test_review_corrections.py -q ``` Expected: all tests PASS. - [ ] **Step 3: Product list verification** Manual test in WeChat devtools: - Open 产品清单. - Click 新增产品. - Click 冲压方式 input. - Verify suggestions show only `清洗` and `连续模`. - Select `连续模`. - Verify 标准节拍 is still visible and required. - Save a product with `冲压方式= 连续模 ` including spaces. - Reopen the product and verify it is recognized as `连续模`. - Verify product card shows orange `连续模` watermark. - [ ] **Step 4: Worker reporting verification** Manual test: - Scan a normal single-person `连续模` product QR. - Start work. - Finish work. - In 下班报工, verify the product card shows `连续模` watermark. - Verify `换料次数` input appears between `材料库存批次号` and product readonly information. - Try submitting empty value. Expected: toast `请填写非负整数换料次数`. - Enter `2`, fill production quantity and device, submit. - Verify result page shows `换料次数 2`. - [ ] **Step 5: Admin review verification** Manual test: - Open 报工审核. - Expand the continuous die report. - Verify detail card shows `连续模` watermark and `换料次数`. - Change `换料次数` from `2` to `3`. - Approve. - Reopen record and verify: - Card has `更改` watermark. - Correction display shows `换料次数 2 -> 3`. - Work time, standard workload, actual beat still recalculate from time and quantity only. - [ ] **Step 6: Manager dashboard and export verification** Manual test: - Open 经理报工看板. - Find the approved continuous die row. - Verify orange `连续模` watermark. - Verify `换料次数 3` displays. - Export Excel. - Verify first sheet has `换料次数` column. - Verify the row has `报工标记=连续模`. - Verify `参考工资` remains `成品数量 * 工序单价`, without adding changeover pay. - [ ] **Step 7: QR verification** Manual test: - Open 模具二维码. - Search the continuous die product. - Verify single-select and batch modal rows show `连续模` tag. - Generate QR. - Verify bottom annotation includes product name / process / stamping method; for continuous die it includes `连续模`. - [ ] **Step 8: Server release** Backend release order: ```bash cd /software/JhHardwareWRS_BackPoint git pull .venv/bin/python scripts/migrate_continuous_die_changeover_count.py ``` If backend is running with reload, verify logs show reload. If not, restart the backend service. Frontend release: - Upload WeChat mini-program version after devtools verification. - This feature needs frontend release because worker/admin/manager pages all need new visible fields. --- ## Self-Review Spec coverage: - `连续模` special judgment by trimmed exact stamping method: Task 1 and Task 3. - Product `冲压方式` remains free input with suggested `清洗` and `连续模`: Task 4. - Continuous die watermark in product list, QR, report, review, records, dashboard, result: Tasks 4 and 6. - Worker report requires `换料次数`: Task 5. - Admin review displays and can edit `换料次数`: Task 6 plus backend Task 2. - Manager dashboard and Excel export carry `换料次数`: Task 2 and Task 6. - Dashboard aggregation sums `换料次数`: Task 2. - No forced dictionary-maintenance page: preserved by Task 4. - Wage calculation is not changed because no changeover unit price exists: File Map non-goal and Task 7. Placeholder scan: - No unresolved placeholder markers remain. - Every modified behavior has a concrete file target and exact code shape or verification step. Type consistency: - Backend snake_case: `changeover_count`, `is_continuous_die`. - Frontend camelCase: `changeoverCount`, `isContinuousDie`. - Display label: `换料次数`.