## Summary
- Reworked the product creation modal around warp/weft yarn ratio tables.
- Added yarn selection, create-yarn entry points, ratio validation feedback, and ratio-based weight calculation.
- Updated frontend service/types coverage and tests for the product workflow.
## Why
The product creation flow previously did not guide users through structured warp/weft yarn ratios, making it easy to enter incomplete or invalid ratio data. The new flow makes the yarn composition explicit and blocks invalid product submission.
## Impact
Users can select existing yarns or create yarns while adding a product, edit ratio values per row, and calculate batch weights once both yarn sides are valid.
## Validation
- `pnpm test`
- `pnpm build`
Changes from fix-muyu-portal branch:
Frontend (muyu-portal):
- Add create purchase order page with form and route
- Implement create/edit modals for stock check (盘点) and stock adjust (调整)
- Add detail pages for stock checks and adjusts
- Fix supplier modal submit flow (field mapping and reset on close)
- Add regression tests for GROUP_CONCAT NULL bug and reload path behavior
Both list pages had 查看 links with no onClick. Added detail pages showing
header info and line items, with action buttons (确认开始盘点 / 审核通过+驳回).
Also: extended StockCheck/StockAdjust types to include details and extra
fields; added /inventory/checks/:id and /inventory/adjusts/:id routes;
moved columns definitions inside components (module-level columns with
history.push were silently ignored by the browser due to MFSU caching);
switched to Link component for SPA navigation.
Both pages had toolbar buttons with no onClick handler. Added modal forms:
- Checks: date + checker + optional detail rows (product + actual qty)
- Adjusts: date + reason + required detail rows (product + adjust qty, negative to decrease)
Also updated createStockCheck and createStockAdjust API function signatures
from Partial<StockCheck/Adjust> to explicit typed request shapes.
Fixed a go-zero validation bug: non-optional fields (checker, adjustReason)
must be present in the JSON body; antd Form returns undefined for untouched
inputs which JSON.stringify omits, causing 400. Solved by setting default
empty strings in Form initialValues.
The "创建采购订单" button navigated to /purchase/orders/new which matched
the /:id detail route with id="new", causing the detail page to request a
non-existent order and render blank.
Added a dedicated new.tsx form page (supplier select, date, dynamic detail
rows with product autocomplete and computed line totals) and registered
/purchase/orders/new in .umirc.ts before the /:id route so it takes
precedence.
destroyOnClose unmounts the Form on close, disconnecting the useForm
instance so validateFields throws on reopen. Replaced with afterOpenChange
to reset fields without unmounting.
Also moved setModalOpen(false) and reload() before message.success so that
antd v5's static message API throwing outside an <App> context no longer
prevents the modal from closing after a successful create or update.
- 'renders summary table without crash when product has empty colors and locations':
mocks getProductSummary returning a product with colors/locations='', verifying the
page does not crash and calls the API (reproduces the previously-broken scenario).
- 'closes dialog after successful product creation (reload branch reached)':
verifies that a successful form submission calls createProduct and the ModalForm
closes, confirming onFinish returned true — the same code branch that triggers
summaryRef.reload().