# 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: `换料次数`.