1168 lines
34 KiB
Markdown
1168 lines
34 KiB
Markdown
# 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
|
|
<view class="form-item stamping-field">
|
|
<text class="label">冲压方式</text>
|
|
<input
|
|
class="input"
|
|
value="{{form.stampingMethod}}"
|
|
data-field="stampingMethod"
|
|
bindfocus="onStampingFocus"
|
|
bindblur="hideStampingSuggestion"
|
|
bindinput="onInput"
|
|
placeholder="可输入,或选择清洗/连续模"
|
|
/>
|
|
<view wx:if="{{stampingSuggestionVisible}}" class="stamping-suggestion-panel">
|
|
<view
|
|
wx:for="{{stampingMethodSuggestions}}"
|
|
wx:key="*this"
|
|
class="stamping-suggestion-option"
|
|
data-value="{{item}}"
|
|
catchtap="chooseStampingSuggestion"
|
|
>{{item}}</view>
|
|
</view>
|
|
</view>
|
|
```
|
|
|
|
On product cards, add:
|
|
|
|
```xml
|
|
<text wx:if="{{item.isContinuousDie}}" class="continuous-die-stamp {{item.isCleaning || item.isMisc || item.isMultiPerson ? 'lower-product-stamp' : ''}}">连续模</text>
|
|
```
|
|
|
|
- [ ] **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
|
|
<view wx:if="{{reportItem.isContinuousDie}}" class="form-item">
|
|
<text class="label">换料次数</text>
|
|
<input
|
|
class="input number-input {{reportItem.changeoverCountError ? 'field-error' : ''}}"
|
|
type="number"
|
|
value="{{reportItem.changeoverCount}}"
|
|
placeholder="请输入换料次数"
|
|
data-block-index="{{blockIndex}}"
|
|
data-item-index="{{itemIndex}}"
|
|
data-field="changeoverCount"
|
|
bindinput="onQtyInput"
|
|
/>
|
|
</view>
|
|
```
|
|
|
|
Add watermarks:
|
|
|
|
```xml
|
|
<text wx:if="{{reportItem.isContinuousDie}}" class="continuous-item-stamp {{reportItem.isMultiPerson ? 'lower-item-stamp' : ''}}">连续模</text>
|
|
```
|
|
|
|
- [ ] **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
|
|
<text wx:if="{{item.hasContinuousDie}}" class="continuous-die-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified || item.hasMisc || item.isCleaning || item.hasMultiPerson ? 'lower-watermark' : ''}}">连续模</text>
|
|
```
|
|
|
|
Add detail-level watermark:
|
|
|
|
```xml
|
|
<text wx:if="{{detail.isContinuousDie}}" class="detail-continuous-stamp">连续模</text>
|
|
```
|
|
|
|
Add display under non-cleaning/non-misc detail metrics:
|
|
|
|
```xml
|
|
<view wx:if="{{detail.isContinuousDie}}">
|
|
<text class="label">换料次数</text>
|
|
<text class="value">{{detail.changeoverCount || 0}}</text>
|
|
</view>
|
|
```
|
|
|
|
Add editable field in the review form grid:
|
|
|
|
```xml
|
|
<view wx:if="{{detail.isContinuousDie}}" class="form-item">
|
|
<text class="label">换料次数</text>
|
|
<input
|
|
class="input number-input {{detail.changeoverCountError ? 'field-error' : ''}}"
|
|
type="number"
|
|
value="{{detail.changeoverCount}}"
|
|
data-report-index="{{reportIndex}}"
|
|
data-item-index="{{itemIndex}}"
|
|
data-field="changeoverCount"
|
|
bindinput="onItemInput"
|
|
/>
|
|
</view>
|
|
```
|
|
|
|
In product replacement modal cards, add:
|
|
|
|
```xml
|
|
<text wx:if="{{item.isContinuousDie}}" class="continuous-die-watermark product-select-stamp">连续模</text>
|
|
```
|
|
|
|
- [ ] **Step 3: Records page**
|
|
|
|
In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/records/records.wxml`, add report and detail watermarks plus a detail metric:
|
|
|
|
```xml
|
|
<text wx:if="{{item.hasContinuousDie}}" class="continuous-die-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified || item.hasMisc || item.isCleaning || item.hasMultiPerson ? 'lower-watermark' : ''}}">连续模</text>
|
|
<text wx:if="{{detail.isContinuousDie}}" class="detail-continuous-stamp">连续模</text>
|
|
<view wx:if="{{detail.isContinuousDie}}">
|
|
<text class="label">换料次数</text>
|
|
<text class="value">{{detail.changeoverCount || 0}}</text>
|
|
</view>
|
|
```
|
|
|
|
- [ ] **Step 4: Dashboard page**
|
|
|
|
In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/dashboard/dashboard.wxml`, add:
|
|
|
|
```xml
|
|
<text wx:if="{{item.isContinuousDie}}" class="continuous-die-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.correctionPairs.length || item.isMisc || item.isCleaning || item.isMultiPerson ? 'lower-watermark' : ''}}">连续模</text>
|
|
<view wx:if="{{item.isContinuousDie}}">
|
|
<text class="label">换料次数</text>
|
|
<text class="value">{{item.changeoverCount || 0}}</text>
|
|
</view>
|
|
```
|
|
|
|
- [ ] **Step 5: QR page**
|
|
|
|
In `/Users/souplearn/Gitlab/app/JhHardwareWRS/pages/deviceQr/deviceQr.wxml`, add tags in single-select and batch-select rows:
|
|
|
|
```xml
|
|
<text wx:if="{{item.isContinuousDie}}" class="mold-option-tag continuous-tag">连续模</text>
|
|
```
|
|
|
|
- [ ] **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
|
|
<text wx:if="{{result.isContinuousDie}}" class="result-continuous-stamp">连续模</text>
|
|
<view wx:if="{{result.isContinuousDie}}">
|
|
<text class="label">换料次数</text>
|
|
<text class="value">{{result.changeoverCount || 0}}</text>
|
|
</view>
|
|
```
|
|
|
|
- [ ] **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: `换料次数`.
|