T8.6 - D6: the material list uploads the way the location list does
CR-013 accepted free text because the master workbook never arrived; the
Aug 18 call was the CR-005 call again - build the upload path now.
THE component, extracted: T5.4's paste-or-file machinery (file read in the
browser, ONE parser on the server; dry-run check; a report naming every
rejected row with its source line; an editable list that deactivates rather
than deletes) moved from the location-specific functions into
html/wp-list-import.js. The location list and the new material list are both
instances of it - the done-when's "against the same component, not beside it"
made literally true. The loc* names survive as thin delegates because row
handlers, step entry and the probes call them; locations_check re-pointed its
fetch-count assertion to where the fetches now live and still demands every
read and write reach the server.
The material list itself: description, unit, optional code - one new table
(Alembic a1b8c6d4e2f9, additive), GET/import/POST/PATCH routes on the CR-005
pattern, deactivate-never-delete, reactivation reuses the same row so nothing
referencing it orphans. The sample rows are obviously fake (SAMPLE-EMT-075).
NO inventory, price, stock or warehouse field anywhere - the probe walks the
model's columns by regex. The wizard hosts it on step 11 beside the location
list, optional by design: a project with no list still raises free-text
requests (T8.5 wires that).
Parser bug caught by the probe's first run: strip(',;') ate a LEADING comma,
so ',FT' - an empty description - was accepted as a material named FT.
rstrip only, now; the empty first column is rejected with its line number.
Verification (each probe run alone): NEW tests/materials_check.py 17/17.
Regression: locations_check 58/58 through the shared component.
Items: D6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,32 @@ class AppSetting(Base):
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
|
||||
|
||||
|
||||
class MaterialItem(Base):
|
||||
"""One line of a project's material list - D6 / T8.6, the CR-005 call made
|
||||
again: build the upload path now rather than wait for the master workbook.
|
||||
Deliberately small: description, unit, an optional code. NO inventory level,
|
||||
NO price, NO warehouse id - a project-scoped list the project uploaded is
|
||||
not the deferred parts catalog, and the moment a stock count appears here it
|
||||
has crossed the line IMPLEMENTATION.md section 7 draws. `active` rather than
|
||||
delete, same as everything else: a request already referencing a line must
|
||||
keep rendering it."""
|
||||
__tablename__ = "material_items"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(40), primary_key=True)
|
||||
project_id: Mapped[str] = mapped_column(String(40), index=True)
|
||||
code: Mapped[str] = mapped_column(String(80), default="")
|
||||
description: Mapped[str] = mapped_column(String(300), default="")
|
||||
unit: Mapped[str] = mapped_column(String(20), default="")
|
||||
active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
sort: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"id": self.id, "project_id": self.project_id, "code": self.code,
|
||||
"description": self.description, "unit": self.unit,
|
||||
"active": self.active, "sort": self.sort}
|
||||
|
||||
|
||||
class WpFile(Base):
|
||||
"""CR-007 / D8: a drawing uploaded onto a work package. The BYTES live here,
|
||||
in the same database as everything else - settled Aug 18: a backup that
|
||||
|
||||
Reference in New Issue
Block a user