Skip to content

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

ReleaseNameMain ModulesDemoable Value
6.0Units, Part Catalogue & Excel ExportsInventory, Catalogue, DocumentReceive stock by the case and issue by the can; browse parts by category with pictures; export revenue and inventory to Excel
6.1Multi-Factor AuthenticationIdentity, all web and mobile clientsAny 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 items table with item_id in the API and the database. Release 7.0 renames them to parts and part_id (and item_units and item_images to part_units and part_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-items to …/parts; …/inventory-items stays as the read-only stock-on-hand list.
  • Units of measure (FR-CAT-01/02).
    • A units table per garage (name, abbreviation, is_active) is created before items in migration 000017 (the Catalogue tables are owned by the Catalogue module, the migration file is shared). It replaces the free-text items.unit with items.base_unit_id, a NOT NULL composite foreign key (garage_id, base_unit_id) into units. 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 unit of a part with baseUnitId (answers also carry the unit’s name and abbreviation) and adds optional unitId fields on lines without changing existing quantities.
  • Selling price (FR-CAT-09). items.sale_price is 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 unless item_units.sale_price overrides 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 behind inventory.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_categories as a tree of up to three levels, and a brand on the part; parts gain is_active under 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 with POST …/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-web and the Flutter core package, then adopted by the owner, admin and technician apps through a new vietgara_core version. 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.