RELEASE PLAN — PHASE 6
VietGara — Catalogue & Security
Version 6.0 — October 9, 2026 · Cloud: Google Cloud Platform (GCP) · Backend: Go
1. Introduction
- What does the garage sell and stock? Parts have categories, brands, images and units that convert (1 case = 24 cans). Quotes and stock use the same units. Revenue and inventory reports leave as Excel files.
- Who is signing in? Every web console and mobile app gains a second factor, mandatory for platform admins and optionally enforced by owners on their staff.
The Phase 2 apps keep reading and writing base-unit quantities; the new unit fields are optional in the API.
2. Overview of the 2 Releases
| Release | Name | Main Modules | Demoable Value |
|---|---|---|---|
| 6.0 | Units, Part Catalogue & Excel Exports | Inventory, Catalogue, Document | Receive stock by the case and issue by the can; browse parts by category with pictures; export revenue and inventory to Excel |
| 6.1 | Multi-Factor Authentication | Identity, all web and mobile clients | Any user that signs in with e-mail and password can turn on an authenticator app and recovery codes; MFA is optional |
3. Release Details
Release 6.0 — Units, Part Catalogue & Excel Exports
Naming in this release: the part is the
itemstable withitem_idin the API and the database. Release 7.0 renames them topartsandpart_id(anditem_unitsanditem_imagestopart_unitsandpart_images); the names below are the 6.0 ones.
- Module split. The Catalogue module (HLD module #18) is introduced here and owns the part itself (
items, exposed as/parts) together with units, a part’s packaging units and the quantity conversion, the category tree and the part images; it depends on no other module. Inventory keeps only the stock (balance, ledger and, from Phase 7, receipts, issues, counts and cost) and uses Catalogue. Quote and Repair Order use Catalogue to validate a part and to convert the unit of a line. The ownership rule, the allowed foreign keys and the conversion rules are in Database Design Section 5; the endpoints in API Specification Section 8; the business view in Business Procedures P10 to P13. Both modules live in the one monolith, so there is no new deployable. Catalogue gets its own permissions,catalogue.read/catalogue.write, added to the RBAC catalogue and to the default roles (Security Architecture 3.1); the Phase 2 apps are unaffected because they never call part management. Part management moves from…/inventory-itemsto…/parts;…/inventory-itemsstays as the read-only stock-on-hand list. - Units of measure (FR-CAT-01/02).
- A
unitstable per garage (name, abbreviation,is_active) is created beforeitemsin migration 000017 (the Catalogue tables are owned by the Catalogue module, the migration file is shared). It replaces the free-textitems.unitwithitems.base_unit_id, aNOT NULLcomposite foreign key(garage_id, base_unit_id)intounits. The migration is edited in place, so no data conversion is needed; staging is reset and the seeder creates the units. A unit in use is deactivated, never erased. - Per part: a base unit and packaging units with factors (
item_units), validated against cycles and zero factors. The reorder threshold is in the base unit. - Stock is stored in the base unit. Stock receipts, issues and quote and repair-order part lines accept any of the part’s units; the backend converts on save (Catalogue conversion, exact decimals, refused rather than rounded when it does not fit three decimals) and documents show the unit the user chose. A part’s base unit cannot be changed once it has stock, packaging or document lines.
- Compatibility: the built Phase 2 apps, and the technician app’s offline sync of Release 11.1, read and write base-unit quantities. The API replaces the free-text
unitof a part withbaseUnitId(answers also carry the unit’s name and abbreviation) and adds optionalunitIdfields on lines without changing existing quantities.
- A
- Selling price (FR-CAT-09).
items.sale_priceis what the customer pays per base unit (VND, whole dong); it is separate from the cost the garage pays, which arrives with average cost in Phase 7. A packaging unit’s price is the base price times its factor unlessitem_units.sale_priceoverrides it. Part lines on quotes, and later the job picker, prefill from it; the line keeps its own copy, so a price change never alters an existing quote. Cost stays behindinventory.cost.read; the price is visible to anyone who can quote. Part screens warn when the price is below the average cost (once Phase 7 provides it). - Part catalogue (FR-CAT-03/04).
part_categoriesas a tree of up to three levels, and abrandon the part; parts gainis_activeunder the lifecycle rule.- Search and filter by SKU, name, category and brand.
- One to three images per part through the file registry (new public kind
part_image, size limit and resize on upload, attached withPOST …/parts/{id}/images).
- Excel exports (FR-DOC-02). Revenue by period (reads the Reporting module) and the inventory movement report, now showing both the base unit and the chosen packaging unit. The receivables export waits for Phase 13.
- Frontend (web console and owner app): units and conversion screens, category tree management, a part picker with category filters and thumbnails, an upload control on the part, and Excel buttons on the revenue dashboard and the inventory screen.
- Definition of Done: a part has a selling price of 9,000 per can and a case price of 216,000 derived from the factor (or 200,000 overridden); a new quote line prefills from it, and changing the price leaves the earlier quote unchanged. A part “Beer” with base unit can and a case of 24 is received as 10 cases and issued as 30 cans by a repair order; stock on hand reads 210 cans (8.75 cases). Parts are found by category and brand and show their picture. The revenue and inventory Excel files open correctly with the numbers shown on screen. The technician app still syncs offline changes unchanged.
Release 6.1 — Multi-Factor Authentication
- Backend (FR-IAM-09, NFR-SE-04): TOTP enrolment and verification, recovery codes, a second step in the sign-in flow (a short-lived challenge token between the password and the tokens; a refresh does not ask again), a lockout after five wrong codes, and audit entries. MFA is optional for every user and is never enforced. Google sign-in never asks for a code, and a user without a password (Google only) cannot enrol: Google’s 2-step verification protects that sign-in. The TOTP secrets are encrypted with a key from the configuration (
MFA_ENCRYPTION_KEY); without the key the feature is off. - Clients: the second-step screen and enrolment (QR code, recovery codes) in
vietgara-owner-web,vietgara-admin-weband the Flutter core package, then adopted by the owner, admin and technician apps through a newvietgara_coreversion. The customer app signs in with Google only, which Google’s own 2-step verification protects, so it does not offer MFA until it has an e-mail and password sign-in. A Security page lets a user turn MFA on, regenerate the recovery codes and turn it off. - Definition of Done: a user turns MFA on in each client and the next e-mail and password sign-in asks for a code; each recovery code works once; five wrong codes lock the second step for 15 minutes; turning MFA off or regenerating the recovery codes needs a code; a password reset does not turn MFA off. The five clients that sign in with e-mail and password complete the flow against staging.
4. Dependency Diagram
flowchart TD P5(["Phase 5 complete"]) --> R60["Release 6.0<br/>Units, catalogue, Excel"] P5 --> R61["Release 6.1<br/>MFA"] R60 --> DONE(["Phase 6 complete"]) R61 --> DONE
Releases 6.0 and 6.1 touch different modules and can run in parallel. MFA has no dependency on the catalogue.