# PRD — General Affairs (Vehicle Service Request) Module

> ## ⛔ ATURAN ABSOLUT #0 — LEGACY FILES ARE **NEVER** COMMITTED TO GIT
>
> **The legacy General-Affairs / Vehicle-Service-Request PHP files must NOT be pushed into git, NO MATTER WHAT.**
>
> - The legacy PHP reference screens live in **the example folder** — `Pages/MenuGeneralAffairs/Just An Example, Do not Link/` — pushed from the user's Mac. They stay **LOCAL ONLY, FOREVER**.
> - **NEVER `git add`** anything under that folder — not `git add .`, not `git add -A`, not any bulk stage. Before **every** commit touching this module, run `git status` and verify **zero** legacy `.php` files are staged.
> - **Files, not knowledge:** summarize a legacy rule in your own words and cite its filename in the README/PRD, but NEVER paste verbatim legacy PHP (whole functions, SQL as-written) into those committed docs — that commits exactly what must stay local-only.
> - **Hard guard — VERIFIED 2026-07-23:** `ColorindoChemtraInertia/.gitignore` carries `/resources/js/Pages/MenuGeneralAffairs/Just An Example, Do not Link/` (`git check-ignore` OK; git sees nothing untracked under `MenuGeneralAffairs`). Do not weaken or remove it.
> - If a legacy file is ever found staged or committed: STOP, tell the user immediately, and wait.

> ## ⚠️ ATURAN ABSOLUT #0b — PARALLEL-SESSION EDIT GUARD
>
> The user runs several Claude sessions on this repo at the same time, on ONE shared git tree + ONE dev DB + ONE build bundle. **Before editing any EXISTING file**, check its mtime via Bash (`date && stat -c '%y  %n' <files>` or `find app routes database resources/js tests -type f -mmin -30`) and **list to the user every target modified < 30 minutes ago** (likely still being edited elsewhere) — unless its latest change is THIS session's own edit. ≥ 30 min → OK. New files: `ls`/`git status` the exact target path immediately before writing. ⚠️ The GA **proto** pages in `Proto/GeneralAffair/` are being actively edited by the user (uncommitted, 2 modified + 4 untracked when this PRD was written) — re-check before every copy/port. Full rules: README §1.

---

## 1. Document Metadata

| Field | Value |
|---|---|
| **Title** | PRD — General Affairs (Vehicle Service Request) Module |
| **Module** | General Affairs — the sales/employee **vehicle service request** workflow: request service for a company vehicle → GA review & pricing → approval (with/without cash bon) → Finance down-payment → GA processing (fixed pricing + GA doc) → Finance settlement → GA completion. Legacy menu label "General Affair"; data lives in the `vehicleservicerequest*` tables. |
| **Status** | **Phases 0–7 SHIPPED 2026-07-23 → 2026-07-24.** The full VSR workflow is wired end to end (`VehicleServiceRequestController`): all 10 stages (My Request · Create/All · Review GA · Approval GA · Finance DP · GA Processing · Finance Payment · GA Completion · My-Request Cancel/Revise · View All + admin Change), the `auto_increment` migration, both uploads → `file_uploads/generalaffair` (gated), the PDF report via **`@react-pdf/renderer`** (NOT dompdf — removed project-wide, see §9 Q9), and the `vehicleservicetype` Pengelolaan CRUD (Phase 7). Menus #263, #266–#275 → RoleID 1. **97 Pest green**; builds clean; Playwright-verified. **NOT built (→ GitHub issues):** the View All XLS export + pivot analytics (Phase-6d). |
| **Owner** | IT — PT Colorindo Chemtra |
| **Date** | 2026-07-23 (data model verified against dev DB `colorindochemtrainertia`) |
| **Stack** | Laravel 13.8 / Inertia.js v3 / React 19.2 / Tailwind v4 / MySQL 8.4 |
| **Document Type** | Hybrid PRD — documents the shipped Phase-0 proto copy, the **DB-verified** data model, and the legacy business rules (extracted in our own words from the example folder), plus the phased roadmap. Nothing beyond user-approved decisions is implied. |

### Locked decisions (user, 2026-07-23)

1. **Structure:** group `Pages/MenuGeneralAffairs/`, **stage subfolders** — `Create/`, `ReviewGa/`, `ApprovalGa/`, `FinanceDp/` (built), plus stubs `MyRequest/`, `GaProcessing/`, `FinancePayment/`, `GaCompletion/`, `ViewAll/`. Proto filenames kept. Render path prefix `MenuGeneralAffairs/<Stage>/<Page>`.
2. **Docs first:** this pass is **docs + a files-only proto copy only** — no routes, no sidebar, no DB, no build (mirrors the Visit Plan Phase-1 path).
3. **Ruling:** the `README.md` in this folder is the Visit Plan ruleset cloned + GA-adapted (rule 14 carried verbatim); the **"example folder"** = `Just An Example, Do not Link/`.
4. **Completion:** documented faithfully from the legacy `GA Completion/` files (the user pushed them 2026-07-23) — see §2.1 / §5 / §7.
5. **DB verification:** read-only `php artisan db:table` + status/count queries were announced and run 2026-07-23 ([[php-via-docker-cci]]).
6. **Naming (locked 2026-07-23):** `VehicleServiceRequestController` / `VehicleServiceRequestPolicy` / `Store·UpdateVehicleServiceRequestRequest` / `tests/Feature/VehicleServiceRequest*`; route group `general-affairs` (`general-affairs.*`); render prefix `MenuGeneralAffairs/<Stage>/<Page>` (resolves §9 Q1).
7. **Access (locked 2026-07-23):** every GA menu leaf → **Administrator (`RoleID = 1`)** via the existing `menu` + `rolemenu` tables (a `GeneralAffairMenuSeeder`); sidebar visibility == the 403 gate (resolves §9 Q2).
8. **Autonomous build run (2026-07-23):** Phases 1–7 are driven by `Just An Example, Do not Link/BUILD PROMPT.md` (local-only, git-ignored) — run without pausing between phases; its §1b locks the remaining §9 answers (auto_increment approved; status IDs hardcoded; totals faithful; owner snapshot; revise cancel-and-clone; uploads `file_uploads/generalaffair`; un-designed stages modeled on siblings).

---

## 2. Overview & Purpose

**General Affairs (Vehicle Service Request)** is the workflow employees use to get a **company vehicle serviced or repaired**. A request names the vehicle, its odometer reading (`KM`), an optional attachment, and one or more **service lines** (each a service type + desired date). The request then moves through a review → approval → finance → processing → finance → completion chain, every step of which appends an audit row to a header-history table and a per-line detail-history table.

**Why it exists:** it is the next legacy PHP workflow being ported to the Laravel/Inertia/React stack, alongside Visit Plan. It is not sales data — it is an internal GA/Finance approval flow, but it reuses the same house patterns (queue list → detail → `DecisionBar`, `<entity>assignment` history, rolemenu-driven Policy, Pest tests).

**Construction lineage (deliberate):** the queue/detail pages are copies of the **NewUI prototype** (`Pages/Proto/GeneralAffair/*.jsx`, already utility-themed, cloning the Sample Order / Quotation grammar — `DecisionBar`, `StatusBadge`, `ListFooter`, `HistoryPopover`). The future backend mirrors the **Sample Order module** (presenter-style read controller, `FormRequest` validation, rolemenu Policy, assignment-row history, Pest feature tests). Reference only — do not modify SO.

### 2.1 What the whole menu is for (screen-by-screen)

Stages, in workflow order (legacy files cited from the example folder; exact role gating is OPEN — §9):

- **My Request / View Request** (`listvehiclerequest*.php`, `vehiclerequestdetails.php`) — an employee's own list of the requests **they created** (`UserIDInput = self`). The detail screen can **Print** the PDF, **Cancel** (only while status 1/2/3 → sets header + lines to **10 Cancel**), or **Revise** (only at status **8** → jumps to the revise re-create).
- **Create Vehicle Service Request** (`createvehicleservicerequest.php`) — an employee raises a request **for one of their own vehicles** (vehicle list scoped to `UserIDOwner = self`): pick the vehicle, enter `KM`, an optional file, a remark, and 1..n service lines (type + date + remark). Saved as header **1 Request**; the **owner is snapshotted from the vehicle** (`UserIDOwner`/`UserNameOwner`), the creator is `UserIDInput`.
  - **Create (All)** (`createvehicleservicerequestall.php`) — identical, but the vehicle list is **all** vehicles (not owner-scoped). Which menu/role gets this is OPEN.
  - **Revise re-create** (`createvehicleservicerequestrevise.php`) — **cancel-and-clone**, never in-place edit: in one transaction it sets the OLD request → **10 Cancel** (+ history on header and every line) and inserts a **brand-new** request at **1 Request** pre-loaded with the old lines.
- **Review GA** (`listvehiclerequestreviewga*.php`, `vehiclerequestreviewga.php`) — queue of status **1 + 11** (new + re-review). GA fills the **estimate** per accepted line (Brand / Place / EstimatedPrice / EstimatedGuarantee) and submits → header **2 Review GA**; each **unchecked** line → **9 Reject**; if nothing is checked, the whole request → **9**. "Revise" → **8**.
- **Approval GA** (`listvehiclerequestapprovalga*.php`, `vehiclerequestapprovalga.php`) — queue of status **2**. The approver picks **With Cash Bon** → **3 Approval GA** (routes to Finance DP first), **Without Cash Bon** → **4 Finance Down Payment** (skips DP, straight to GA Processing), **Re-Review** → **11** (clears each line's estimate + bounces back to Review GA), **Reject** → **9**, or **Revise** → **8**.
- **Finance DP** (`listvehiclerequestfinancedp*.php`, `vehiclerequestfinancedp.php`) — queue of status **3**. Finance records the down-payment (`TotalCashBon`, defaulting to Σ EstimatedPrice) → header **4 Finance Down Payment**. "Revise" → 8.
- **GA Processing** (`listvehiclerequestassignmentga*.php`, `vehiclerequestassignmentga.php`) — queue of status **4**. GA staff record the **fixed** price/guarantee per line, **must upload a GA document** (`GAUpload*`), may add brand-new lines → header **5 GA Processing**. "Revise" → 8.
- **Finance Payment / "Finance Settlement"** (`listvehiclerequestfinancepayment*.php`, `vehiclerequestfinancepayment.php`) — queue of status **5**. Finance records `TotalPayment` (Σ FixedPrice) and `TotalBalance` (Σ FixedPrice − `TotalCashBon`) → header **6 Finance Settlement**. "Revise" → 8.
- **GA Completion** (`listvehiclerequestgaclose*.php`, `vehiclerequestgadone.php`, `listvehiclerequestdetailsgadoneview.php`) — queue of status **6**. GA closes the request → header + lines **7 GA Completion** (+ history), requires a comment. "Revise" → 8. This is the **"Done"** state that the historical-price lookups on every earlier line key on (`header status IN (7)`).
- **View All** (`listvehiclerequestall*.php`, `vehiclerequestdetailsall.php`, `listvehiclerequestviewallexport.php`, `listvsrall.php`, `reportvehicleservicerequest.php`) — every request, **no status filter**; admin detail can **Cancel**, **Print**, **Revise**, or **Change** (an arbitrary status override, comment-required), plus **Export to Excel** and a **pivot** analytics view (spend by plate × service-type × year).

**Verified create-insert shape** (from the three `createvehicleservicerequest*.php` files — for the Phase-2 store pass): one PDO transaction writes **four tables** — `vehicleservicerequest` (header, status 1) + `vehicleservicerequestassignment` (1 header-history row, status 1, `Comment` = remark) **always**, plus **1..n** `vehicleservicerequestdetails` (each line, detail status 1) and **1..n** matching `vehicleservicerequestdetailsassigntment` (per-line history, status 1). Server gate: a vehicle, a `KM`, and ≥ 1 service line. `UserIDInput`/`UserNameInput` = the actor; `UserIDOwner`/`UserNameOwner` = **snapshot copied from the chosen vehicle**; `Total*` untouched (default 0); the requester file (if any) → `Upload*` with a server-generated name `"VhcSvcReq-{Ymd-His}.{ext}"`. Estimated/Fixed prices are NOT set at create (they arrive at Review GA / GA Processing).

---

## 3. Scope

### 3.1 In Scope — Phase 0 (SHIPPED 2026-07-23)
1. `README.md` — the module ruling (Visit Plan clone, GA-adapted).
2. `GeneralAffair PRD.md` — this document.
3. Files-only copy of the 4 built proto stages into stage subfolders (§6), each with a provenance header, mock data intact.

### 3.2 Explicitly NOT in Phase 0
- **No routes** — the copied pages are not renderable (`/general-affair/*` real routes do not exist; the originals remain reachable at `/proto/general-affair/*`). When wired, `Inertia::render` strings must use the prefix `MenuGeneralAffairs/<Stage>/<Page>`.
- **No sidebar / menu / rolemenu rows, no Policy, no controller, no FormRequests, no tests, no migrations.**
- **No `npm run build`.** ⚠️ Coupling hazard: Inertia v3 globs everything under `Pages/**`, so from the **next** build onward (whoever runs it) these copies compile in — their imports (`@/Components/Proto/UI/*`, `HistoryPopover`, `DecisionBar`, mock arrays) become load-bearing. Coordinate before deleting them.
- **No changes** to `routes/web.php`, `PrototypeController`, `Sidebar.jsx`, any model, or any other module.

### 3.3 Out of Scope until explicitly scheduled
All wiring (Phase 1+): the read controller, per-stage queues + action handlers, the create/store, the `auto_increment` migration, file upload, the PDF report, the export/pivot, lookup CRUDs (`vehicleservicetype`), email (if any), edit/delete/restore, and the 5 not-yet-designed proto stages (My Request, GA Processing, Finance Payment, GA Completion, View All).

---

## 4. Users & Permissions (future — pattern locked by precedent, not yet built)

- All routes go inside the `auth` middleware group.
- The GA Policy will mirror `SampleOrderPolicy`/`VisitPlanPolicy`: every ability funnels through the app's `rolemenu`-driven menu-access check (an active `rolemenu` row for the user's RoleID joined to an active `IsShow=1` menu row whose `LinkLaravel` matches exactly). No admin bypass exists — Administrator works only via real grant rows. **Sidebar visibility and the 403 gate must use IDENTICAL grant conditions** (CLAUDE.md).
- New menu leaves (created via the Pengelolaan UI at wiring time) will carry `LinkLaravel` values per stage. **Menu IDs are not reserved yet** — the wiring pass must ask the user which stages get menus and which roles get grants (`new-feature` LANGKAH 0).
- **Row-level scoping is thin in the legacy:** the only in-code scoping is **My Request** (`UserIDInput = self`) and the **Create** vehicle list (`UserIDOwner = self`, relaxed by the "all" variant). Every other stage acts on **all** requests at that stage's status — access is gated purely by *who can open the page* (the absent `header.php`). So the real control is **which role holds each stage's menu** — a user decision (§9 Q2).

---

## 5. Data Model — VERIFIED against dev DB `colorindochemtrainertia`, 2026-07-23

All 5 tables have Eloquent models (`#[Table]`, `#[Fillable]`, `#[ScopedBy(NotDeletedScope)]` except the status lookup, `timestamps: false`, PK `ID`). All tables `ENGINE=InnoDB DEFAULT CHARSET=latin1` (⚠️ no emoji in varchars). Live counts: **vehicleservicerequest 61 · details 163 · assignment 326 · detailsassigntment 811** (all active).

### 5.1 `vehicleservicerequest` (header) — model `App\Models\VehicleServiceRequest` (23 cols)

| Column | Type | Notes |
|---|---|---|
| `ID` | int | PK — **NO `auto_increment` in dev** (§5.6 trap #1) |
| `IsDeleted` | int | 0/1; `creating()` defaults 0 |
| `VehicleServiceRequestStatusID` | int NULL | FK → `vehicleservicerequeststatus` (RESTRICT) |
| `VehicleServiceRequestStatusName` | varchar(200) | **denormalized** status name legacy sets by hand (can drift; prefer the numeric FK) |
| `Tanggal` | datetime | created timestamp |
| `UserIDInput` / `UserNameInput` | int NULL / varchar(200) | creator (FK users RESTRICT) + snapshot name |
| `VehicleID` | int NULL | FK → `vehicle` (RESTRICT) |
| `UserIDOwner` / `UserNameOwner` | int NULL / varchar(200) | **snapshot of the vehicle's owner at create** (FK users RESTRICT); [[userid-sales-snapshot]] caveat applies — ASK before reusing |
| `KM` | int | odometer |
| `TotalCashBon` / `TotalPayment` / `TotalBalance` | decimal(20,2) | set at Finance DP / Finance Payment / Finance Payment |
| `UploadName/Type/Size/Content` | varchar(200)/varchar(300)/int/longblob | **requester** attachment (at create) |
| `GAUploadName/Type/Size/Content` | varchar(200)/varchar(300)/int/longblob | **GA** attachment (at GA Processing) |
| `Remark` | text | request remark |

4 FK constraints, all RESTRICT: UserIDInput→users, UserIDOwner→users, VehicleID→vehicle, VehicleServiceRequestStatusID→vehicleservicerequeststatus.

### 5.2 `vehicleservicerequestdetails` (service lines) — model `VehicleServiceRequestDetails` (14 cols)
`ID` int (no auto_inc), `IsDeleted`, `VehicleServiceRequestID` (FK header), `VehicleServiceTypeID` (FK `vehicleservicetype`), `VehicleServiceTypeName` varchar(200) (denormalized), `VehicleServiceRequestDetailStatus` int NULL (FK → status — the per-line status), `VehicleServiceDate` date, `Brand` varchar(200), `Place` varchar(200), `EstimatedPrice` decimal(20,2), `FixedPrice` decimal(20,2), `EstimatedGuarantee` varchar(200), `FixedGuarantee` varchar(200) (**guarantees are free-text, e.g. "3 bulan" — NOT numeric**), `Remark` text. 3 FKs RESTRICT. Brand/Place/Estimated* set at Review GA; Fixed* at GA Processing. Rejected lines (status 9) are frozen everywhere and shown pink.

### 5.3 `vehicleservicerequestassignment` (header history) — model `VehicleServiceRequestAssignment` (7 cols)
`ID` int (no auto_inc), `IsDeleted`, `VehicleServiceRequestID` (FK), `VehicleServiceRequestStatusID` (FK), `Tanggal` datetime, `UserID` (FK users), `Comment` text. One row per header status event.

### 5.4 `vehicleservicerequestdetailsassigntment` (per-line history) — model `VehicleServiceRequestDetailsAssigntment` (9 cols)
⚠️ **table name is misspelled `…assigntment`** — every legacy query and the model use that exact spelling; **keep it**. `ID` int (no auto_inc), `IsDeleted`, `VehicleServiceRequestID`, `VehicleServiceRequestDetailID` (FK line), `VehicleServiceRequestStatusID` (FK), `Tanggal`, `UserID` (FK users), `Brand` varchar(255), `Remark` text. 4 FKs RESTRICT.

### 5.5 Lookups
- **`vehicleservicerequeststatus`** — `ID` int (no auto_inc), `VehicleServiceStatusName` varchar(200). ⚠️ **column is `VehicleServiceStatusName`** (not `…RequestStatusName`), and the table has **NO `IsDeleted`** column (unusual — the model has no `NotDeletedScope`). **11 rows, all present, matching the legacy hardcoded names exactly** (no drift): **1 Request · 2 Review GA · 3 Approval GA · 4 Finance Down Payment · 5 GA Processing · 6 Finance Settlement · 7 GA Completion · 8 Revise · 9 Reject · 10 Cancel · 11 Re-Review**.
- **`vehicleservicetype`** — `ID`, `IsDeleted`, `VehicleServiceTypeName` varchar(200). Referenced by `vehicleservicerequestdetails.VehicleServiceTypeID`; the future line-type picker + a Pengelolaan CRUD.
- **`vehicle`** (master, 29 cols, `ID` **auto_increment**) — read at create for the owner snapshot + all vehicle metadata (brand/type/color/plate/year/chassis/engine/insurance…). `VehicleBrandTypeID` is the key the "Historical Service Type" panels match on. **Reference only** unless the user says GA owns it.

### 5.6 ⚠️ Schema traps (verified 2026-07-23 — every future write pass MUST handle these)
1. **No `auto_increment`** on `vehicleservicerequest` / `…details` / `…assignment` / `…detailsassigntment` / `…status` / `vehicleservicetype` `ID`s (only `vehicle.ID` has it) — the models declare `incrementing: true`, so Eloquent inserts assume an auto-increment the dev schema lacks. **Resolve with a guarded, reversible migration at the write pass** (add `AUTO_INCREMENT`, wrap in `Schema::withoutForeignKeyConstraints()` for the FK-referenced PKs — MySQL 1833 — exactly the Visit Plan precedent). Re-confirm each with `SHOW CREATE TABLE` first (a user-approved structural change).
2. **Status: numeric is truth.** Use the FK `…StatusID`; the header's denormalized `VehicleServiceRequestStatusName` is set by hand and can drift. Decide at the write pass: hardcode the 11 IDs (SO precedent) or read the lookup. No soft-delete on the status table.
3. **latin1 charset** on all tables — reject/normalize non-latin1 input.
4. **Money = `decimal(20,2)`**; **guarantees = `varchar(200)`** (free text). Currency inputs in legacy are `str_replace(",","")`-stripped (thousands separators UI-only).
5. **Two `longblob` upload slots** — requester `Upload*` (create) + GA `GAUpload*` (GA Processing). Port to the **`file_uploads` disk convention** (CLAUDE.md "File Upload"): `file_uploads/{menu_name}/{upload_in}{id}_{version}.{ext}` — ask the user for `menu_name` (§9 Q11). Legacy download/preview go through absent `downloadfile.php`/`showpdf.php`; new = a gated route + legacy-BLOB fallback (LWR/Visit-Report precedent).
6. **NOT-NULL, no-default columns** every insert must set explicitly (header `…StatusName`, `UserNameInput/Owner`, `KM`, `Upload*`/`GAUpload*` empties, `Total*`=0, `Remark`; assignment `Comment`) — confirm each via `SHOW CREATE TABLE`.
7. **0-vs-NULL sentinels ([[dev-db-fk-drift]]).** Legacy dropdowns use `0` for "none"; the dev FKs are RESTRICT. New writes must use **NULL** for absent FKs; verify existing rows' sentinels before any UPDATE (additive-only, rule 13). Confirm the live 0/NULL state at the write pass.
8. **Snapshots:** `UserIDOwner`/`UserNameOwner` (from vehicle) and `VehicleServiceTypeName`/`UserNameInput` are frozen copies at write time.
9. **Totals ignore line status/IsDeleted:** legacy `SUM(EstimatedPrice)` (Finance DP) and `SUM(FixedPrice)` (Finance Payment) filter neither `IsDeleted` nor status, so rejected (9)/soft-deleted lines are summed into the DP default and the settlement — **a business-rule decision** (§9 Q4).

---

## 6. Phase-0 Page Inventory (what shipped, verbatim behavior)

Copied from `Proto/GeneralAffair/*` into stage subfolders; only delta vs the proto source = a provenance header comment. Mock data, `@/Components/Proto/*` imports, and `general-affair.*` route refs are intact. All use `AppLayout`. **Not routed** — the render-path repoint (`MenuGeneralAffairs/<Stage>/<Page>`) is a wiring-pass task.

- **`Create/VehicleServiceCreate.jsx`** — header form (Vehicle `SearchableSelect`, `KM`, Remark, file upload capped at **2 MB** per the legacy rule) + a multi-line service-lines editor (type / date / remark, add/remove rows). Mock `VEHICLES`/`SERVICE_TYPES`; submit only alerts.
- **`ReviewGa/ReviewGaSearch.jsx`** & **`ReviewGa/ReviewGaDetail.jsx`** — queue (house list grammar: toolbar search + date range + filter pills + reset + ⚙ → clickable list table → `ListFooter`; client-side filtering; `StatusBadge` tones) and detail (breadcrumb + Back, hero badge, Request + Vehicle cards, a **review** line-items table with row-select checkboxes and **no fixed price yet**, `HistoryPopover` chips, history card, `DecisionBar` + confirm dialog).
- **`ApprovalGa/ApprovalGaSearch.jsx`** & **`ApprovalGa/ApprovalGaDetail.jsx`** — same grammar; the detail adds a **Cash Bon** card and the with/without-cash-bon decision set.
- **`FinanceDp/FinanceDpSearch.jsx`** & **`FinanceDp/FinanceDpDetail.jsx`** — same grammar; the detail adds a **Down Payment** card + a Print button.

**Pending proto (stubs, no page yet):** `MyRequest/`, `GaProcessing/`, `FinancePayment/`, `GaCompletion/`, `ViewAll/` — the user is still designing these in the proto.

---

## 7. Legacy stage → file map (the example folder — reference only, never committed)

| Stage | Header status consumed | Key legacy files (example folder) | Writes |
|---|---|---|---|
| My Request | own (`UserIDInput`) | `listvehiclerequest.php`, `…view.php`, `vehiclerequestdetails.php`, `listvehiclerequestdetailsviewmyreq.php` | Cancel→10, Revise→(re-create) |
| Create | — (creates at 1) | `createvehicleservicerequest.php` (own), `…all.php` (all), `…revise.php` (cancel+clone) | header 1 + assignment + details + detail-assignment |
| Review GA | 1, 11 | `listvehiclerequestreviewga.php`, `…view.php`, `vehiclerequestreviewga.php`, `listvehiclerequestdetailsreviewgaview.php` | 2 (+per-line estimate); unchecked→9; revise→8 |
| Approval GA | 2 | `listvehiclerequestapprovalga.php`, `…view.php`, `vehiclerequestapprovalga.php`, `listvehiclerequestdetailsview.php` | 3 / 4 / 11(+clear est) / 9 / 8 |
| Finance DP | 3 | `listvehiclerequestfinancedp.php`, `…view.php`, `vehiclerequestfinancedp.php`, `listvehiclerequestdetailsfinancedpview.php` | 4 (+`TotalCashBon`); revise→8 |
| GA Processing | 4 | `listvehiclerequestassignmentga.php`, `…view.php`, `vehiclerequestassignmentga.php`, `listvehiclerequestdetailsgaprocessingview.php` | 5 (+per-line `FixedPrice`/`FixedGuarantee` + `GAUpload*`); revise→8 |
| Finance Payment | 5 | `listvehiclerequestfinancepayment.php`, `…view.php`, `vehiclerequestfinancepayment.php`, `listvehiclerequestdetailsviewfinancepayment.php` | 6 (+`TotalPayment`/`TotalBalance`); revise→8 |
| GA Completion | 6 | `listvehiclerequestgaclose.php`, `listvehiclerequestgacloseview.php`, `vehiclerequestgadone.php`, `listvehiclerequestdetailsgadoneview.php` | 7 (+per-line 7); revise→8 |
| View All | none (all) | `listvehiclerequestall.php`, `…view.php`, `vehiclerequestdetailsall.php`, `listvehiclerequestdetailsviewall.php`, `listvehiclerequestviewallexport.php`, `listvsrall.php`, `reportvehicleservicerequest.php` | Cancel→10, admin **Change**→any status |
| shared | — | `listvehiclerequesthistoryview.php` (header history include) | — |

**Absent included files (behavior unverified — request or re-implement, never guess):** `header.php` (**the entire auth/session/role model lives here**), `home.php`, `getbrandtype.php`, `downloadfile.php` / `showpdf.php` / `downloadvehiclerequestfile.php` (BLOB download/preview), `reportvehicleservicerequest.php`'s `pdf_js.php` + `fpdf` + `logo.png`, `dbhandler.php` / `dbconnect.php`. **No menu IDs or role IDs appear in any example file** — `List Menu.png` shows the sidebar tree only.

---

## 8. Roadmap (Phases 1–7 run autonomously via `Just An Example, Do not Link/BUILD PROMPT.md`; its §1b locks the §9 answers)

| Phase | Content | Done when |
|---|---|---|
| **0 ✓** | Docs (README + PRD) + files-only proto copy of the 4 built stages | shipped 2026-07-23 |
| **1** | Wiring/reads: routes + `VehicleServiceRequestController` (**My Request**, `UserIDInput=me`) + `VehicleServiceRequestPolicy` + `GeneralAffairMenuSeeder` (all → RoleID 1) + sidebar + Pest read tests | list renders, scoped + gated + tested; built |
| **2** | Create/store: the **auto_increment migration** first; `StoreVehicleServiceRequestRequest`; 4-table insert (header 1 + assignment + N details + N detail-assignment); owner snapshot; vehicle option endpoint (own + all); requester upload → `file_uploads` | a create writes `2+2N` rows at status 1; tested |
| **3** | **Review GA** + **Approval GA** queues + actions (estimate→2, unchecked→9; with/without cash bon→3/4, re-review→11, reject→9, revise→8) | both queues scoped + gated, history correct |
| **4** | **Finance DP** (→4, `TotalCashBon`) + **GA Processing** (→5, fixed price/guarantee, mandatory GA upload, optional add-lines) | both stages write correct status/money/upload |
| **5** | **Finance Payment** (→6, `TotalPayment`/`TotalBalance`) + **GA Completion** (status-6 queue → **7** + per-line 7 + history) | settlement math correct; completion closes to 7 |
| **6** | My-Request Cancel (→10) + Revise (cancel-and-clone) + **View All** (no status filter, admin "Change" override) + PDF report (dompdf) + XLS export + pivot (in-repo ExcelJS/Recharts) | all gated + audited + tested |
| **7** | `vehicleservicetype` Pengelolaan CRUD + module-wide recheck + doc update + follow-up issues | CRUD tested; suite green; docs true |

Per CLAUDE.md: at the end of each phase, file GitHub issues for remaining follow-ups.

---

## 9. Open Questions (resolve from legacy files / user before the relevant pass)

> **Note (2026-07-23):** Q1 (naming), Q2 (roles → Administrator), Q4 (totals faithful), Q8 (auto_increment), Q11 (upload `menu_name=generalaffair`), Q12 (hardcode status), plus the revise / owner-snapshot / un-designed-stage calls are **LOCKED** for the build run — see §1 Locked decisions + `Just An Example, Do not Link/BUILD PROMPT.md` §1b. The rest stay genuinely open.

1. **Naming** — route group + controller/Policy/FormRequest/test class names: `VehicleServiceRequest*` (domain, VP precedent) vs `GeneralAffair*` (menu)? Proto routes are `general-affair.*`. Page H1s say "Vehicle Service Request"; the sidebar label is "General Affair".
2. **Per-stage role/menu grants** — the legacy carries NO role IDs (auth in the absent `header.php`); which roles hold Create / Review GA / Approval GA / Finance DP / GA Processing / Finance Payment / GA Completion / View All? (The real access control.)
3. **`UserIDOwner` snapshot semantics** — display the frozen owner (as stored) or the vehicle's current owner? [[userid-sales-snapshot]] says re-confirm with the user.
4. **Totals summing rejected/soft-deleted lines** (§5.6 #9) — replicate faithfully or filter to active non-rejected lines?
5. **Revise = cancel-and-clone** — confirm we replicate exactly (old→10 + clone at 1); and the revise vehicle-scoping (all vs own, keyed on old `UserIDInput != UserIDOwner`).
6. **Create vehicle scoping** — own (`UserIDOwner = self`) vs the "all vehicles" variant: one page with a permission flag, or two menus?
7. **View All admin "Change"** (arbitrary status override, comment-required) — replicate, and gate to whom?
8. **`auto_increment` migration** (§5.6 #1) — user approval for the structural change.
9. **Absent included files** — re-implement natively: auth → rolemenu Policy; downloads → gated `file_uploads` route + BLOB fallback; the PDF report → **`@react-pdf/renderer`** (client-side). ⚠️ **RESOLVED 2026-07-24: NOT dompdf.** The BUILD PROMPT §1b + this PRD originally said "dompdf", but CLAUDE.md (updated 2026-07-16) records dompdf was `composer remove`d project-wide and is **forbidden** for new PDFs — the sanctioned path is client-side react-pdf (the SampleOrder Surat Jalan precedent). Built as `Components/MenuGeneralAffairs/pdf/VehicleServiceRequestReportPdf.jsx` + `PrintReportButton.jsx`, generated from the detail props (no server byte-processing endpoint needed).
10. **GA Processing "add new lines"** mid-flow (`Remark = "Insert From GA Processing"`, back-fills history for prior statuses) — replicate?
11. **Uploads** — the `file_uploads` `menu_name` / `upload_in` for the two slots (requester + GA).
12. **Status IDs hardcode vs read the lookup** (§5.6 #2).
13. **Display naming** — "General Affair" menu label vs "Vehicle Service Request" page titles.
14. **Export + pivot** (`listvehiclerequestviewallexport.php` XLS + `listvsrall.php` pivot, which loads external CDNs) — re-implement on the in-repo ExcelJS/Recharts foundation (VP precedent) or defer/drop?

---

## 10. Binding Conventions for Every Future Pass

- **Invoke `new-feature` + `inertia-v3-stack`** before writing code (CLAUDE.md hard rule) — TDD-first, toast + inline errors, loading spinner, FormRequest-only validation, Policy + `authorize()`, no axios.
- `SHOW CREATE TABLE` / `php artisan db:table` before touching any model; never trust generated config blindly (§5.6 #1 proves why).
- `Rule::exists(...)->where('IsDeleted', 0)` on every FK; PascalCase columns as-is; `NotDeletedScope` semantics; NULL (never 0) for absent FKs; latin1-safe input.
- Shared code → `app/Support/GeneralFunction.php` / `app/Services/` / `resources/js/lib/` on second use, within modules this user owns ([[shared-function-convention]]).
- Ask-first protocol ([[ask-first-protocol]]); explain every edit and recommend the next step (README rule 9); frontend freeze (README rule 10); ultracode discipline — main agent writes all code, subagents read-only verify (README rule 11).
- Rebuild (`bash nodebuild.sh` from the outer git root) after any JSX change once pages are routed; whole-input pre-build sweep; hard-refresh after (README rule 7).
- Tests: Pest + `DatabaseTransactions` on the dev DB, in-memory `User::factory()->makeOne(['id' => 1])` for `actingAs`, unique `TEST*` prefixes, **never** `migrate:fresh`.
- **Shared-state discipline (README rules 5/7/12/13):** ONE git tree / ONE dev DB / ONE build bundle — never destructive git, never bulk-mutate existing rows, `migrate` only your own file via `--path`, commit by explicit pathspec after `git diff HEAD` per file, stage only what you created this session, never leave files staged.

## 11. References
- Reference module: `Pages/MenuSampleOrders/` + `SampleOrder PRD.md` (structure/patterns — read-only). Sibling port: `Pages/MenuVisitPlans/` (`VisitPlan PRD.md` + `README.md` — the template this module's docs were cloned from).
- Proto originals: `Pages/Proto/GeneralAffair/{VehicleServiceCreate,ReviewGa{Search,Detail},ApprovalGa{Search,Detail},FinanceDp{Search,Detail}}.jsx`; routes `general-affair.*` (`PrototypeController`), reachable at `/proto/general-affair/*`.
- Models: `app/Models/VehicleServiceRequest*.php` (5 files).
- Legacy reference: the **example folder** (`Just An Example, Do not Link/`) — local-only, never committed (Aturan Absolut #0).
- Memories that bind here: `ask-first-protocol`, `php-via-docker-cci`, `frontend-build`, `dev-db-fk-drift`, `userid-sales-snapshot`, `never-read-opt-lampp`, `parallel-session-broad-commits`, `playwright-screenshots`.
