Skip to content

RELEASE PLAN — PHASE 7

VietGara — Inventory & Job Catalogue

Version 7.3 — October 20, 2026 · Cloud: Google Cloud Platform (GCP) · Backend: Go

1. Introduction

What a garage’s stock needs, compared with what exists today (Releases 1.3 and 1.4 of the MVP, plus the units and catalogue of Phase 6):

NeedTodayAfter this phase
Goods receiptOne part per receipt, no supplier or numberA multi-line document with supplier, number, date, notes; draft → completed → cancelled
CostA unit cost on each receipt onlyWeighted-average cost per part; cost snapshot on every issue; valuation report
Who sees costAnyone who can see stockA separate permission inventory.cost.read; technicians and advisors see quantities only
DifferencesNoneStock count document with variance and an adjustment; write-off; return to supplier; return of unused parts from a repair order
EntryTypingBarcode scanning on the phone; Excel import for parts, receipts and counts
QuotingFree-form lines; stock is only checked when an order closesA job catalogue that fills the labor line and suggested parts; every repair order gets a draft stock issue that reserves its parts and posts when it closes; hidden consumables; an order lacking stock waits on parts; lines of an open order can change and the order can be cancelled

Ideas from the same review that fit later phases: purchase orders and supplier payables (Phase 13), barcode label printing (Phase 14) and transfers between an owner’s garages (Phase 15). Warehouse zones inside one garage, batch/serial/expiry tracking and a separate “combo” item type are not planned (the job catalogue covers service packages).

2. Overview of the 4 Releases

ReleaseNameMain ModulesDemoable Value
7.0Goods Receipt Documents & CostInventory, ReportingReceive a delivery of several parts on one numbered document; see average cost and stock value; hide cost from technicians
7.1Adjustments & Stock CountInventoryCount the shelf, see matched, over and short lines, complete it, and stock and value follow; write off or return a part
7.2Barcode & Excel ImportInventory, owner web and appScan a part to add a line; import a garage’s parts and opening stock from Excel
7.3Job Catalogue & Stock ReservationCatalogue, Quote, Repair Order, InventoryPick “Oil change” on a quote and the labor line and suggested parts appear; the parts are reserved when the order is made, an order lacking stock waits on parts, and a cancelled order frees its reservation

3. Release Details

Release 7.0 — Goods Receipt Documents & Cost

Status: implemented. The backend, the contract (vietgara-proto v1.12.0) and the API are done; the web console and owner app screens ship with their own Release 7.0 pull requests. Deviations from the plan below are listed at the end of this section.

  • Goods receipt document (FR-INV-06). goods_receipts and goods_receipt_lines: number from the garage’s numbering template, date, supplier delivery note number, notes, status draft → completed → cancelled. Each line carries its supplier (a garage-owned supplier, Release 5.0): the header supplier is a default that new lines inherit and may override, a part’s default supplier (below) pre-fills it, and the receipt cannot be completed while a line has no supplier (SUPPLIER_REQUIRED). A single delivery from several suppliers is therefore one document. Lines are added by part search; barcode (Release 7.2) and Excel import (Release 7.2) come later. Completing it creates the stock transactions in the part’s units (Release 6.0), each stamped with its line’s supplier, and updates stock and cost. Cancelling a completed receipt reverses its transactions and is audited; it is refused with INSUFFICIENT_STOCK_TO_REVERSE when the reversal would take a part below zero. The single-line receipt of Release 1.3 becomes a one-line document (the migration is edited in place; no data to convert).
  • Part suppliers (FR-INV-19). part_suppliers lists a part’s usual suppliers with the supplier’s own code, the last cost paid and a default flag (at most one default). Completing a receipt updates the last cost; purchases by supplier and period are reported from completed receipts (Excel too).
  • Weighted-average cost (FR-INV-07). stock_balance.average_cost (NUMERIC(18,4), owned by Inventory) and the quantity are updated on each completed receipt; every stock issue line stores a cost snapshot (stock_transactions.unit_cost) so past figures never change. Each movement also stores the quantity and average cost right after it (quantity_after, average_cost_after), which is how the valuation report answers “at a date”. A valuation report (quantity and value per part and per category, at a date) with Excel export. The stock issue lines keep the cost of the parts consumed, which the profit report of FR-RPT-05 (Phase 13) uses.
  • Stock issue document (FR-INV-20). Creating a repair order creates a numbered draft stock issue (phiếu xuất kho) with one line per part line of the order, printable like the other documents (PDF). The draft reserves its parts: quantityReserved and quantityAvailable show it on the stock level. Stock is not deducted at creation, because orders get cancelled and the parts have not been taken. Posting writes the stock movements (ledger rows with the cost snapshot, Database Design Section 5.4). By default it posts when the order closes, as stock is deducted today; inventory staff can post earlier (“issue now”) when parts leave the shelf, and a garage setting stock_issue_mode can post when work starts (the first move of a line out of open). After posting, corrections are returns and adjustments (Release 7.1). repair_order.closed now posts the issue instead of writing ledger rows itself, idempotently. Events: repair_order.created, repair_order.started and repair_order.closed carry the order’s part lines with their line ids; the posting publishes stock.issued.
  • Cost visibility (FR-INV-08). New permission inventory.cost.read (see cost, average cost and valuation). Default roles: manager, accountant and inventory staff have it; advisor and technician do not. Answers omit the cost fields when the permission is missing; the purchases and valuation reports, and their Excel exports, are refused with PERMISSION_DENIED instead. inventory.adjust and inventory.count come with Release 7.1.
  • Part naming (Release 7.0). The items table becomes parts (with part_units and part_images), item_id becomes part_id in the database and in the API, the stock-on-hand list moves from /inventory-items to /stock-levels, the stock transaction fields follow (part_id, part_sku, part_name), and POST /stock-receipts is removed: goods receipts replace it.
  • Numbering and settings. numbering.goodsReceipt and numbering.stockIssue templates (defaults PN-{YYYY}{MM}-{SEQ:4} and PX-{YYYY}{MM}-{SEQ:4}), and the garage’s stockIssueMode (ON_CLOSE by default, or ON_START).
  • Documents. Goods receipt PDF, stock issue PDF, purchases Excel and stock valuation Excel (FR-DOC-01, FR-DOC-02).
  • Frontend (web console, owner app): receipt list and editor with draft save, supplier picker, running totals, PDF and Excel; valuation screen. These are delivered with the Release 7.0 pull requests of those repositories.
  • Definition of Done: creating a repair order with two part lines creates a draft stock issue that reserves them (available drops, on hand does not); closing the order posts it and stock falls; “issue now” posts earlier. A delivery of three parts from two suppliers is received on one document with a supplier on each line, saved as a draft, then completed (a line without a supplier blocks completion), and stock and average cost update; a second delivery at a different price changes the average; a technician sees quantities but no cost; cancelling the receipt restores the previous stock and cost.
  • Deviations and known gaps in Release 7.0.
    • Lines changed on a repair order after it is created do not reach its draft yet: the draft is made from the part lines at creation (or at closing, when no draft exists), so the issue posted at closing can differ from the order’s lines. Closed in Release 7.3, which adds line editing and keeps the draft in line.
    • Cancelling a repair order, which would release its reservation, does not exist yet: there is no repair order cancel action, and the stock issue has no cancel path of its own (its draft would be cancelled by the order). Closed in Release 7.3.
    • The reservation does not move a repair order to waiting_on_parts. Done in Release 7.3.
    • Barcode scanning, Excel import and the template files come with Release 7.2 (not in 7.0).

Release 7.1 — Adjustments & Stock Count

Status: implemented in the backend (vietgara-backend v1.11.0) against the contract vietgara-proto v1.13.0. The web console and owner app screens ship with their own Release 7.1 pull requests. Deviations from the plan below are listed at the end of this section.

  • Stock count document (FR-INV-12). stock_counts and stock_count_lines: lines added by search, category, barcode or import; the counted quantity is typed per line; each line shows matched, over or short against the system quantity at the time of counting. Status draft (saved; several drafts can be merged) → completed. Completing posts adjustment transactions so stock equals the count, records the value of the difference at average cost, and cannot be edited afterwards; a cancel posts a reversal. Excel export for review.
  • Write-off (FR-INV-09). A document with a reason (damaged, lost, expired, internal use, warranty replacement) that reduces stock at average cost.
  • Return to supplier (FR-INV-10). A document that reduces stock and records the supplier credit, linked to the supplier and to the original receipt where known.
  • Return of unused parts from a repair order (FR-INV-11). A repair order can return a part line, restoring stock and reversing its cost, with the reason recorded.
  • Audit and permissions. Every adjustment is an immutable stock transaction with the user, document and reason; inventory.adjust and inventory.count are separate permissions.
  • Frontend (web console, owner app): the count screen is built for the phone (large numeric field, scan, jump to the next uncounted line).
  • Definition of Done: counting 20 parts with 3 differences creates one document with matched, over and short lines; completing it makes stock equal the count and shows the value of the difference; a write-off, a supplier return and a returned repair-order part each change stock and appear in the part’s stock card.
  • Rules settled in Release 7.1.
    • A merge keeps the first count’s id; a part on several drafts gets one line whose counted quantity is the sum, in the base unit (the packaging unit is dropped); an uncounted line gives way to a counted one, and the later count’s system quantity is kept. The other drafts become cancelled with the note “Merged into ”.
    • A count’s adjustments are valued at the average cost of the moment and never change the average cost.
    • Write-offs and supplier returns refuse a quantity beyond the stock on hand (INSUFFICIENT_STOCK); a return to supplier also refuses a receipt that is not completed (GOODS_RECEIPT_NOT_COMPLETED) or that has no line from that supplier.
    • Permissions: inventory.count for counts; inventory.adjust for write-offs, returns to supplier and repair-order part returns. The manager and inventory staff hold both by default. A repair-order part return is not a repair_order.write action.
    • Numbering: numbering.stockCount (KK-{YYYY}{MM}-{SEQ:4}), numbering.writeOff (XH-{YYYY}{MM}-{SEQ:4}) and numbering.supplierReturn (TN-{YYYY}{MM}-{SEQ:4}) by default.
    • Stock card entries for these documents carry the document number, the reason (write-offs and part returns), the user who made the change, and the balance after the movement.
  • Deviations and known gaps in Release 7.1.
    • The count screen built for the phone, and the other owner screens, are delivered with the Release 7.1 pull requests of the web console and owner app, not in the backend.
    • Excel import of counts and barcode scanning into a count are Release 7.2, as planned. The Release 7.0 gaps of the stock issue draft (lines changed on a repair order after creation, and no repair order cancellation) are unchanged.
  • Deferred after Release 7.1 (recorded, not scheduled in 7.1).
    • PDF print documents for write-offs and supplier returns. A supplier return needs one to send to the supplier; a future feature.
    • Billing effect of returned repair order parts. Whether a returned part should reduce what the customer is billed is undecided; in 7.1 a part return changes neither the repair order nor its billing.
    • Profit and cost of goods sold: returns should net against cost in the reports. The profit report itself is Phase 13 (FR-RPT-05); Release 7.3 only keeps the cost on the stock issue lines, hidden consumables included.

Release 7.2 — Barcode & Excel Import

Status: implemented in the backend (vietgara-backend v1.12.0) against the contract vietgara-proto v1.14.0, in the owner web (v1.12.0) and in the owner app (v0.4.0). Deviations from the plan below are listed at the end of this section.

  • Barcodes (FR-INV-13). part_barcodes allows several barcodes per part (up to 20); each is for the base unit or for a packaging unit, so a case barcode stands for a case. Scanning with the phone camera in the owner app (and a USB scanner on the web, which types into the field) adds a line to a receipt or a count, or opens the part. Printing barcode labels waits for Release 14.0.
  • Excel import (FR-INV-14). Import parts (with units, categories, brand, barcodes and opening quantity and cost), goods receipts and stock counts from a template file: upload, preview, a row-by-row error report, then confirm. Opening stock is posted as a completed receipt so history stays consistent. This also serves garages moving from desktop software.
  • Template files for each import are downloadable and documented (English and Vietnamese instructions on a second sheet).
  • Definition of Done: a 500-row parts file imports with a clear error report for 5 bad rows; after fixing, the parts, units and opening stock appear; scanning a barcode on a phone adds the right line to a receipt and to a count.
  • Rules settled in Release 7.2.
    • A barcode is unique in the garage, compared without regard to case, and is stored as typed after trimming (1 to 64 letters, digits, spaces and - . / + $ %). It names the base unit or one packaging unit of its own part. Deleting a part frees its codes. A packaging unit that has a barcode cannot be removed (PACKAGING_UNIT_HAS_BARCODE) but can be edited.
    • A scan is looked up by GET …/barcodes:lookup; a code the garage cannot store simply is not found. An inactive part is found, so the screen can say it is inactive.
    • A file is imported as a whole or not at all. The preview lists every problem of the file at once (row, column, a stable code); confirming a file with any problem imports nothing (IMPORT_FILE_INVALID). Existing SKUs and barcodes are errors: the import creates, it never updates.
    • The same file content cannot be confirmed twice for the same garage and type (IMPORT_FILE_ALREADY_IMPORTED); changing one cell makes it a new file. Concurrent confirmations of one file run one after the other.
    • Limits: 5 MB, 5,000 data rows; at most 1,000 errors are listed (errorsTruncated) and 20 rows are previewed.
    • Parts file: one part per row. Missing units and part categories are created. Packaging units is Name=Factor;…; Barcodes is Code;Code@Unit;… where @Unit names one of the row’s packaging units. A row with an Opening quantity needs a Supplier (an existing supplier); all such rows are posted as one completed goods receipt with each line’s supplier, and an Unit cost needs inventory.cost.read.
    • Goods receipt and stock count files make one draft document, which the user reviews and completes. The code column takes a SKU or a barcode; a case barcode implies its unit and a conflicting Unit column is UNIT_MISMATCH. The document-level columns (receipt date, delivery note, notes, count name) must be the same wherever filled (HEADER_MISMATCH).
    • Permissions follow the file: parts need catalogue.write, plus inventory.write for opening stock and inventory.cost.read for costs; goods receipts inventory.write (costs need inventory.cost.read); stock counts inventory.count. A column the caller may not use is a row-0 error (PERMISSION_REQUIRED).
    • Every confirmed import is audited (import.completed) and recorded in import_runs.
  • Deviations and known gaps in Release 7.2.
    • The owner app scans but does not import and does not edit barcodes: its stock list opens the scanned part, a stock count adds it and moves the cursor to its counted quantity, and the part picker of counts, write-offs and returns picks it. It does not add receipt lines, because it has no multi-line receipt screen. The unit a case barcode stands for is used by the web console only.
    • The Excel workbook is uploaded as base64 in the JSON body, so a 5 MB file travels as about 7 MB; REST routes other than imports stay limited to 6 MiB. Every gateway in front of the backend must allow bodies of 8 MB: Kong’s KONG_NGINX_HTTP_CLIENT_MAX_BODY_SIZE was raised from 4m to 8m in the backend, blackbox, e2e and staging compose files (a production ingress needs the same).
    • A parts file with opening stock is posted through the goods receipt path line by line: 5,000 parts with about 4,300 opening-stock lines take about 20 seconds. A 5,000-line stock count takes under a second.
    • Parts categories are created flat (no A > B paths), and the file cannot set a sale price or a reorder threshold; edit those in the Catalogue afterwards.
    • Re-uploading a file whose draft document was deleted is still refused (IMPORT_FILE_ALREADY_IMPORTED); change the file to import it again.

Release 7.3 — Job Catalogue & Stock Reservation

Status: implemented in the backend (vietgara-backend) against the contract vietgara-proto v1.15.0, and in the owner web and the owner app. Deviations from the plan below are listed at the end of this section.

  • Backend (FR-CAT-05/06): jobs per garage: name, category, standard labor price and duration, is_active; job_parts: suggested part, default quantity and unit (Release 6.0’s units). Add, edit and delete follow the lifecycle rule (delete when unused, otherwise deactivate). Quote lines gain an optional jobId and a nonBillable mark so a line remembers where it came from; the free-form lines of Release 1.2 stay possible.
  • Frontend (web console and owner app):
    • a Jobs screen to manage the catalogue and its suggested parts (web console);
    • on the quote form, a job picker with search by name and category that adds one labor line and the part lines (quantity, unit and price from the catalogue and the garage’s price list), all editable;
    • inactive jobs hidden from the picker but still shown on old quotes.
  • Dependency: Release 6.0 (units, part catalogue).
  • Definition of Done: an advisor creates the job “Oil change” with one labor charge and 4 litres of engine oil, picks it on a quote, changes the quantity to 5 litres, and the quote totals are right. A job used on a quote is deactivated when deleted; an unused one is removed.
  • Hidden consumables (FR-CAT-08). A suggested part on a job can be marked non-billable (grease, screws, cleaning fluid): its stock is deducted when the job is used and its cost counts in the profit report, but the customer’s quote and PDFs do not show it.
  • Stock reservation (FR-INV-15). The draft stock issue of Release 7.0 is the reservation, so there is no separate reservation table. The part shows on hand, reserved and available; a repair order that lacks available stock moves to waiting_on_parts, which gives that status its first automatic trigger. The reservation is released when the repair order is cancelled and consumed when the issue is posted.
  • Repair order lines and cancellation (FR-REP-07, FR-REP-08). Release 7.0 recorded two gaps that the reservation needs closed: lines could not change after an order was created, and an order could not be cancelled. Release 7.3 adds both.
  • Definition of Done (reservation): an order for 4 L of oil with 1 L on hand waits on parts; receiving 10 L brings it back to open; an order added after it waits behind it until the first is posted or cancelled; cancelling the first order frees the stock for the second.

Rules settled in Release 7.3.

  • Jobs. A job has a name (unique in the garage, compared without regard to case), a free-text category used to group and filter the picker, a labor price (whole VND, zero allowed), a duration in minutes and up to 30 suggested parts, each part once. A suggestion has a quantity above zero typed in the part’s base unit or one of its packaging units. Sending parts on an update replaces the whole list. Reads need quote.read, changes quote.write: jobs are quoting data.
  • Protection. A part that a live job suggests is deactivated, not deleted, and its base unit is locked; a packaging unit that a job suggests cannot be removed from the part (PACKAGING_UNIT_USED_BY_JOB) but its factor can change. A job that a quote line came from is only deactivated when deleted; an unused job is deleted and its name freed.
  • Adding a job to a quote (POST …/quotes/{id}/lines:fromJob) works on a draft only. It appends a labor line named after the job at the labor price, then one part line per suggestion whose part is still active, at the part’s current sale price (or the packaging unit’s price) in the suggested unit. An inactive job is refused (JOB_INACTIVE). The lines count toward the 100-line limit and are then ordinary lines, editable like any other; they keep the job’s id.
  • Hidden consumables. A suggestion marked non-billable becomes a part line with nonBillable: its price is stored as 0, it is left out of the quote total, the public approval page, the quote PDF and the approval decisions (the customer decides on billable lines only). Only a catalogue part line can be hidden, and a quote or an order needs at least one billable line. When the quote converts, a hidden consumable goes to the repair order if its job has an approved billable line (or it belongs to no job and the quote is approved); it is deducted from stock like any part, never holds the order back (it needs no progress), and is not printed.
  • Repair order lines. On an order that is not closed or cancelled, lines can be added, edited (description, part, quantity, unit, price) and removed, and the last billable line stays. The type of a line never changes. The order’s total and status follow. A part line change reaches the draft stock issue through repair_order.lines_changed; once the issue is posted the parts have left stock, so adding, removing or changing a part line is refused (STOCK_ISSUE_POSTED) while labor lines and prices stay editable.
  • Cancelling an order (POST …/repair-orders/{id}/cancellation) is allowed while it is open or waiting on parts and no line has started. Its draft stock issue is cancelled, which releases the reservation and may bring other waiting orders back; a cancelled order is read-only and cannot be closed. The quote it came from stays converted, so it cannot be converted again: the customer gets a new quote. Once work has started, the order is completed, closed, already cancelled, or its stock issue is posted, cancelling is refused.
  • Waiting on parts. Reservations are served first come, first served per part, in the order the stock issues were created. A draft issue whose lines the stock on hand cannot cover is marked short (isShort, with a shortQuantity per line) and its order is waiting_on_parts. The mark is rechecked whenever a draft issue is created, changed, cancelled or posted and whenever any stock movement of one of its parts is written (a receipt, a count, a return, a write-off), so stock that arrives brings the order back by itself. An order that is in progress and short shows waiting on parts too; one whose work is finished does not. The mark never blocks anything: the order can still be worked on, and posting an issue never fails for lack of stock.

Deviations and known gaps in Release 7.3.

  • Job categories are free text, not a category tree like the parts’ (a planned category_id became category); the job picker filters by it.
  • “Reserved on approval” in the release table is the reservation of Release 7.0: the draft issue exists when the order is made from the approved quote, not when the quote is approved. A quote that has not been converted holds no stock.
  • The owner app does not manage jobs; it picks a job on the quote form. The phone has no repair order editor, so line editing and cancellation are in the web console.
  • Hidden consumables are not on the technician’s list (nobody is assigned to them), and the order’s detail in the web console shows them to staff marked as hidden.
  • The profit report that uses the stock issue’s cost belongs to Phase 13.

4. Dependency Diagram

flowchart TD
  P6(["Phase 6 complete"]) --> R70["Release 7.0<br/>Goods receipt documents & cost"]
  R70 --> R71["Release 7.1<br/>Adjustments & stock count"]
  R70 --> R72["Release 7.2<br/>Barcode & Excel import"]
  R71 --> R73["Release 7.3<br/>Job catalogue & stock reservation"]
  R71 --> DONE(["Phase 7 complete"])
  R72 --> DONE
  R73 --> DONE

Phase 7 depends on units and the part catalogue (Release 6.0), on suppliers (Release 5.0) and on the file registry (Release 5.0) for attachments. Release 7.3 needs the average cost of 7.0 (profit reports use it) and the stock balance of 7.1. Its blackbox and E2E scenarios are written inside the phase (Phase 4 rule). Phase 7 comes before Security & Support Access (Phase 8) and Production Readiness (Phase 9) because it is used every day; open launch still waits for Phase 9.