Skip to content

Latest commit

 

History

History
487 lines (400 loc) · 60.2 KB

File metadata and controls

487 lines (400 loc) · 60.2 KB

YS Heng API Reference

The backend is a .NET 10 minimal API served from services/api/src/YSHeng.Api. JSON enum values are serialized as strings.

Base local URL:

http://localhost:5000

Health

Method Path Auth Purpose
GET /health Public Lightweight service health.
GET /health/ready Public Readiness check including PostgreSQL connectivity.

Authentication

ASP.NET Identity cookie authentication is mounted under /api/auth.

Method Path Auth Purpose
POST /api/auth/login?useCookies=true Public Staff login through Identity cookie auth.
POST /api/auth/logout Authenticated Sign out current staff session.
GET /api/auth/me Authenticated Return current staff identity and roles.

Public Website

Public endpoints are unauthenticated and must not expose purchase price, refurbishment, commission, audit, or internal workflow data.

Method Path Purpose
GET /api/public/vehicles List public Available vehicles.
GET /api/public/vehicle-catalog/models List active, admin-maintained make/model options for public vehicle filters.
GET /api/public/vehicles/{id} Fetch one public available vehicle.
GET /api/public/vehicles/{id}/photo Return the latest public thumbnail/photo for a public available vehicle.
GET /api/public/vehicles/{id}/photos List public gallery photo metadata for a public available vehicle.
GET /api/public/vehicles/{id}/photos/{photoId} Return one full public gallery photo for a public available vehicle.
POST /api/public/leads Create a public lead for a visible available vehicle.
POST /api/public/contact-enquiries Create a general website contact enquiry for Sales triage.
POST /api/public/showroom-enquiries Create a no-login in-store QR showroom enquiry. The API records the server-owned in-store-qr source and stores vehicle preferences in the lead message for Sales triage.

GET /api/public/vehicles returns the compact inventory DTO. GET /api/public/vehicles/{id} additionally returns optional descriptionMarkdown, the staff-authored public listing description. It is returned only after the existing visible-and-available vehicle filter; it must contain marketing copy only and never internal vehicle, customer, finance, repair, audit, or workflow information.

Public lead payload:

{
  "vehicleId": "guid",
  "customerName": "Buyer name",
  "phone": "012-3456789",
  "message": "Optional enquiry",
  "sourcePage": "/vehicles/guid?utm_source=facebook",
  "sourceReferrer": "https://facebook.com/",
  "sourceCampaign": "utm_source=facebook&utm_campaign=vios"
}

sourcePage, sourceReferrer, and sourceCampaign are optional public enquiry attribution fields. The API trims them, stores at most 500 characters each, and keeps them on the lead record for Sales triage. They must not contain internal back-office URLs or private workflow data.

Public contact-enquiry payload:

{
  "customerName": "Buyer name",
  "phone": "012-3456789",
  "message": "I would like help choosing a vehicle.",
  "sourcePage": "/contact?utm_source=facebook",
  "sourceReferrer": "https://facebook.com/",
  "sourceCampaign": "utm_source=facebook&utm_campaign=showroom"
}

customerName, phone, and message are required. message is limited to 2,000 characters. General contact enquiries use no vehicle link and appear in the Sales lead queue as Website contact enquiry; the public response returns only the new enquiry ID.

Showroom QR enquiry payload:

{
  "vehicleType": "SUV",
  "preferredBrand": "Toyota",
  "preferredModel": "Harrier",
  "budgetRange": "RM50k–RM80k",
  "customerName": "Buyer name",
  "phone": "012-3456789",
  "email": "buyer@example.com"
}

vehicleType, budgetRange, customerName, and phone are required. The email is optional. This endpoint does not accept an arbitrary source value: it writes the stable /showroom-enquiry page and in-store-qr source for Sales triage, and its response returns only the new enquiry ID.

Back-Office Role Policies

All /api/* back-office routes require the broad BackOffice role policy first. Module policies then narrow access:

Policy Roles
BossAdmin BossAdmin
Dashboard BossAdmin
Vehicles BossAdmin, Sales
VehicleRead BossAdmin, Sales, Loan, Delivery, Finance, Repair
CustomerRead BossAdmin, Sales, Loan, Finance
CustomerProfile BossAdmin, Sales, Loan, Delivery, Finance
OwnerRead BossAdmin, Sales, Finance
Sales BossAdmin, Sales
Repairs BossAdmin, Repair
Loans BossAdmin, Loan
Deliveries BossAdmin, Delivery
Finance BossAdmin, Finance
CashCustody BossAdmin, Sales, Finance
HrSalary BossAdmin, HrSalary

Vehicle Intake And Contacts

Method Path Policy Purpose
GET /api/vehicles Vehicles Full vehicle records for Boss/Admin and Sales, including read-only repairCost: final repair-job cost when present, otherwise the intake refurbishment total.
GET /api/vehicle-catalog/models Vehicles List active and inactive make/model catalogue entries.
POST /api/vehicle-catalog/models Vehicles Add a make/model option for public filters.
PUT /api/vehicle-catalog/models/{id} Vehicles Edit or deactivate a make/model option without changing existing vehicle records.
POST /api/vehicles Vehicles Create an available vehicle intake, including optional chassis and engine identifiers. Sales-created vehicles remain pending and private; only Boss/Admin can submit approval.
POST /api/vehicle-intakes Vehicles Multipart intake with request JSON, the reviewed seller NRIC image in identityCard, and optional reviewed VOC PDF/image in voc. Validates both files before writes, then atomically creates the available vehicle, seller-owned documents, optional confirmed new previous owner, and optional unpaid seller-settlement reminder. A new owner must match the vehicle owner ID and have a confirmed IC number; including the settlement requires Finance or Boss/Admin access.
POST /api/owner-intakes/identity-card-preview Vehicles Analyze an in-memory seller NRIC image, return editable OCR values, and return an exact existing-owner match when the normalized IC number is already recorded. The preview image is not persisted by this endpoint.
POST /api/vehicle-intakes/voc-preview Vehicles Analyze a multipart file VOC/car-card PDF or image before vehicle creation and return { result }. Uses the configured OCR provider, quota and content validation. Does not create a vehicle, Owner, document or OCR job; quota and preview audit records remain.
PUT /api/vehicles/{id} Vehicles Update vehicle intake, chassis/engine identifiers, and public status without changing its workflow-owned status. Sales may correct price while an intake is unapproved, but only Boss/Admin may reprice approved stock. Changing the selling price always revokes management approval and public visibility in that request; Boss/Admin must approve the unchanged new price in a later action. Once any Finance receivable exists, the selling price is immutable, while unrelated vehicle details remain editable. Other approval changes require Boss/Admin, and unapproved vehicles are always private.
GET /api/vehicle-lookup VehicleRead Plate/year/make/model/status and linked customer ID lookup for authorized workflow selectors and customer-profile hand-off.
GET /api/vehicles/{id}/stock-movements VehicleRead List stock owner, status, location, selling-price, approval, and visibility movement history with actor, timestamp, previous value, new value, and reason.
GET /api/customers CustomerRead Customer lookup/list.
GET /api/customers/profile-options CustomerProfile Minimal canonical customer ID and name choices for the Customer 360 selector. Delivery-only users receive only customers with a linked delivery schedule.
GET /api/customers/{id}/profile CustomerProfile Read-only Customer 360 aggregate over linked source records. Identity, loan, delivery, finance, enquiries, and document metadata are returned only for sections the caller's role is already allowed to access. Delivery-only users receive a 404 unless the customer has a linked delivery schedule. Delivery evidence must match both a delivery shown in the profile and that customer. Document and receipt content remains on its existing protected download URL.
POST /api/customers Vehicles Create customer.
PUT /api/customers/{id} Vehicles Update customer.
GET /api/owners OwnerRead Previous-owner lookup/list.
POST /api/owners Vehicles Create previous owner. Normalized phone and non-empty IC numbers must be unique.
PUT /api/owners/{id} Vehicles Update previous owner while preserving normalized phone and IC uniqueness.
GET /api/purchase-invoices PurchaseAccountingRead List purchase invoices with current classified lines. Owner-acquisition invoices additionally include source type, Owner ID, current revision number and the current revision snapshot; PDF bytes are excluded from JSON.
POST /api/purchase-invoices Vehicles Retained legacy supplier-record route. Requires an active supplier and staff-provided number/dates; classified lines must equal the total. Does not generate a PDF or accept owner-acquisition or revision metadata. New Owner-based invoices use the generate route.
PUT /api/purchase-invoices/{id} Vehicles Update a legacy draft purchase invoice and replace its classified lines. Finance-confirmed legacy invoices remain immutable. Generated Owner invoices must use the revision route.
POST /api/vehicles/{vehicleId}/purchase-invoice/generate Vehicles Issue the vehicle's Owner-based formal invoice and revision 1 PDF atomically from canonical Owner/intake data. Requires a Boss-confirmed intake, positive purchase price and valid Owner name/phone. The request contains expectedOwnerId, expectedPurchasePrice and expectedIntakeDate; stale source review is rejected. Uses a separate server number and Singapore issue date. Returns the existing generated invoice on retry.
POST /api/purchase-invoices/{id}/revisions Vehicles Correct a generated invoice using expectedRevision, a mandatory reason, dates, seller display details and classified lines. Creates the next full snapshot and PDF, retains previous versions, and resets current Finance confirmation. Source vehicle/Owner IDs and official number stay fixed; master records are not changed.
GET /api/purchase-invoices/{id}/revisions PurchaseAccountingRead List version snapshots and classified lines with author, timestamp, correction reason and version-specific Finance review evidence. PDF bytes are excluded.
GET /api/purchase-invoices/{id}/revisions/{revisionNumber}/content PurchaseAccountingRead Download the exact retained PDF revision. Access is authenticated and audited; later corrections do not replace these bytes.
POST /api/purchase-invoices/{id}/confirm-accounting Finance Confirm the reviewed current invoice and lines for accounting review/export. Generated invoices require the query parameter expectedRevision; stale confirmation is rejected. Legacy calls may omit it. Confirmation evidence stays associated with the reviewed revision.

New Vehicle intake VOC scanning is optional and appears before the required vehicle fields in the first step. It accepts English PDF, JPG, PNG or WebP within the existing 10 MB document limit. A PDF must have consistent MIME/extension, parse successfully, be unencrypted and contain 1-15 pages; rejected PDFs return voc_pdf_malformed, voc_pdf_encrypted, voc_pdf_page_count_invalid, voc_pdf_page_limit_exceeded, voc_mime_mismatch or voc_extension_mismatch as applicable. Image validation retains existing ocr_image_* errors. The seller NRIC preview remains image-only.

The preview fills only blank draft fields after staff explicitly apply the reviewed values. A different existing value requires an explicit per-field replacement choice. Registered Owner text is reference-only. Nothing is saved or approved by preview/apply; the final intake persists the selected original evidence. Empty manual-entry fallback is not a successful OCR result, and preview/intake does not fabricate a persistent OCR review job from client-supplied extraction data.

VOC extraction recognizes JPJ Malay field headings and paired label/value columns, including observed OCR variants with omitted slashes, leading colons and watermark text. Slashless make/model values require a known catalog make; unsupported or ambiguous fields remain available for manual entry. Manufacture year is kept separate from the registration date. These rules do not remove the staff review step or guarantee every VOC layout.

IC address extraction excludes standalone card-number lines, including IC/NRIC/NO labels and MyKad or Kad Pengenalan headings. Real street numbers remain intact. A no-house-number locality address requires a recognized locality marker, five-digit postcode and supported Malaysian state; incomplete or unsupported locality forms remain available for manual entry rather than being inferred.

Generated Purchase Invoices use OwnerAcquisition; existing supplier records remain LegacySupplier without automatic conversion or historical PDF backfill. A correction keeps the official number and increments the visible revision. The invoice total is derived from positive classified lines, and seller corrections affect only that invoice snapshot. Issuance and correction do not approve, pay, settle or reconcile anything. The AutoCount workbook remains a manual review artifact: Owner-source rows identify the Owner snapshot and require Finance creditor mapping rather than automatically creating a supplier or guessing its code.

System numbering skips normalized existing legacy numbers and reserves the YSH-PINV-YYYY-NNNNNN format against new legacy writes. A previously used legacy number may remain unchanged. Stale expected Owner/price/intake inputs return HTTP 409 when canonical source data is otherwise valid; missing or invalid source data returns HTTP 400.

Owner-invoice revision requests allow at most 100 classified lines, each with a recognized PurchaseInvoiceLineType, a description up to 500 characters and a positive amount up to RM10 million. Canonical issue prices and revision line amounts must have at most two decimal places; fractional cents return structured precision errors without issuing or silently rounding a PDF. The reason is at most 500 characters; seller fields and payment reference are at most 200, and seller address at most 1,000. Validation is performed before creating a revision. Version metadata includes readable createdBy and accountingConfirmedBy labels, their timestamps and separate staff user-ID fields for audit correlation. Historical PDFs and prior-version confirmation evidence are retained.

Shared Operations Calendar

Method Path Policy Purpose
GET /api/operations-calendar?from=YYYY-MM-DD&to=YYYY-MM-DD BackOffice Shared Dashboard/HR calendar for authenticated staff. The inclusive date range must be ordered and contain at most 93 days.

Each event contains id, kind, title, startDate, endDate, time, status, customerName, customerContact, and customerAccess. Delivery events use the delivery ID, plate number, scheduled date/time, and delivery status; cancelled deliveries are excluded. Staff with CustomerRead receive the linked customer's name and contact. Other authenticated staff receive only Customer linked with no contact, while deliveries without a linked customer return No customer linked. Approved leave and business trips appear only as staff availability (Busy), with no reason, medical information, trip location, customer, or Finance details. Busy events have null time and status, and null customer fields.

The portal shows a selected-day agenda beside the calendar on desktop and in a bottom drawer after selecting a date on mobile. Only Boss/Admin and Delivery users receive an Open delivery action. It opens the exact delivery record; Delivery API authorization is unchanged. A calendar read does not grant access to the management dashboard or private delivery details.

Uploads

Vehicle photos and documents are stored in PostgreSQL blobs with metadata, checksum, uploader, MIME type, and linked vehicle. Repair invoices, payment evidence, and delivery evidence may also be linked to their exact workflow record. Vehicle photos generate cached thumbnails. ASP.NET multipart parsing has a small overhead allowance above the 10 MB document payload ceiling, then endpoint-specific validation enforces the 10 MB document and stricter 5 MB photo limits.

Method Path Policy Purpose
POST /api/vehicles/{id}/photos Vehicles Upload vehicle photo, max 5 MB.
DELETE /api/vehicles/{id}/photos/{photoId} Vehicles Permanently delete a saved vehicle photo and its thumbnail.
GET /api/vehicles/{id}/photos BackOffice List photo metadata.
PUT /api/vehicles/{id}/photos/order Vehicles Save the complete display order for the vehicle's website photos.
GET /api/vehicles/{id}/photos/{photoId}/content BackOffice Download original photo content.
POST /api/vehicles/{id}/documents?category={FileCategory}&repairJobId={id}&paymentRecordId={id}&collectionTransactionId={id}&deliveryScheduleId={id} Category-specific role Upload document, max 10 MB. Repair and delivery workflow links are exclusive. Collection evidence requires both paymentRecordId and collectionTransactionId; delivery categories require deliveryScheduleId. The server verifies each linked record, route vehicle, and locked customer before storing the evidence.
GET /api/vehicles/{id}/documents Category-specific role List document metadata visible to the signed-in department.
DELETE /api/vehicles/{id}/loan-documents/{documentId} Category-specific role Remove a document from the selected vehicle's Loan checklist. The server rejects cross-vehicle and non-Loan categories, removes linked OCR jobs, and records an audit entry.
GET /api/vehicles/{id}/documents/{documentId}/content Category-specific role Download document content visible to the signed-in department.
POST /api/documents/{documentId}/ocr-jobs Category-specific role Start Google Document AI analysis for the authorized uploaded document category, including IC, VOC, invoice, and receipt review.
GET /api/ocr-jobs/{jobId} Category-specific role Read OCR job status, progress, original extracted draft fields, saved reviewed values, field-level changes, and reviewer audit data.
PUT /api/ocr-jobs/{jobId}/review Category-specific role Save staff-reviewed OCR fields and line items. The server preserves every original-versus-reviewed difference, reviewer identity, timestamp, and field-accuracy counts before values are applied to operational records.
GET /api/vehicles/{id}/ocr-jobs Category-specific role List captured OCR data for uploaded vehicle documents visible to the signed-in department.

OCR runtime:

  • IC extraction returns customer name, IC number, and address. VOC extraction returns registration, chassis, engine, make, model, year, and registered-owner suggestions. Invoice and receipt extraction retains the existing finance and supplier fields. Repair-invoice extraction also proposes repair-part and repair-detail values from recognized line items so the reviewer can populate the Repair task form.

  • The back-office review drawer shows field confidence and pre-fills a current master value when it conflicts with AI output. Staff edit the final value directly and save one review; there is no accept/reject choice. The server keeps the original output, reviewed result, and every field/line-item difference. OCR field accuracy is calculated from all non-empty extracted or reviewed values: unchanged values are correct; changed, added, and removed values are corrections.

  • Google Document AI is the only runtime OCR provider. Configure Ocr__GoogleDocumentAi__ProjectId, Location, and DefaultProcessorId; the deployment environment uses the equivalent GOOGLE_DOCUMENT_AI_* values.

  • Configure InvoiceProcessorId for purchase, repair, and payment invoices and ExpenseProcessorId for payment receipts. When either specialized processor is absent, OCR falls back to DefaultProcessorId and adds a review warning.

  • Authentication uses Google Application Default Credentials and the cloud-platform OAuth scope. The production container reads a least-privilege credential from /run/secrets/google-document-ai.json; never store credential JSON in source control or an environment-file value.

  • The backend sends validated image bytes to Google Document AI. VOC accepts JPG, PNG, WebP and validated English PDFs in both intake preview and existing-vehicle document review; PDFs are sent as the original application/pdf raw document, not converted to images. The shared existing-vehicle picker enforces the existing 10 MB document limit and the 1-15 readable, unencrypted page VOC contract. IC remains image-only. Keep explicit review because valid extraction can still be semantically wrong; uploading evidence does not itself apply or approve master data. Empty extraction is not presented as ready. Partial review/target-save failures retain the reviewed values and expose a retry of the remaining step.

  • Local and production OCR both require Google Document AI configuration and Application Default Credentials; there is no local/mock runtime fallback.

  • Before OCR calls an external provider, the API reserves one usage unit against the server-side OCR limits. Exhausted or disabled limits return 429 with a structured message; a provider-attempted request remains counted even if the provider later fails.

Document upload ownership:

Category Uploader roles
PurchaseInvoice, Voc, IdentityCard, ApDocument, StatusReceipt BossAdmin, Sales
LoanDocument BossAdmin, Loan
DeliveryDocument, HandoverPhoto, SignedHandover, Policy, RoadTaxReceipt, InspectionReport, WindscreenPolicy BossAdmin, Delivery
RepairInvoice BossAdmin, Repair
PaymentReceipt, PaymentInvoice BossAdmin, Finance
MedicalCertificate BossAdmin, HrSalary

VehiclePhoto is rejected on the document endpoint and must use the photo endpoint.

When supplied, repairJobId must reference a repair for the route vehicle and the category must be RepairInvoice. paymentRecordId must reference a payment record for the route vehicle and the category must be PaymentReceipt or PaymentInvoice. Every delivery evidence upload must supply deliveryScheduleId; it must reference the same route vehicle and the delivery's locked customer. Delivery evidence accepts detected PDF, JPEG, or PNG content, while HandoverPhoto also accepts WebP. The server stores the detected MIME type instead of trusting the multipart declaration.

Workflow Modules

Method Path Policy Purpose
GET /api/loans Loans List loan applications.
POST /api/loans BossAdmin Create an exceptional/manual loan workflow record. An active loan establishes or verifies the vehicle's canonical customer. Manual Rejected records require a rejection reason; decision actor/time are server-owned.
POST /api/loans/{id}/decision Loans Record Approved or Rejected for a Pending loan. Rejection requires a reason. The server sets decision actor/time and normalizes LOU flags.
PUT /api/loans/{id} Loans Update non-decision loan workflow fields. Approval/rejection must use the decision action, terminal Rejected/Done records are review-only, and Done requires the current buyer's full vehicle-scoped document set. General updates and decisions lock the loan row so a stale update cannot overwrite a recorded decision.
GET /api/loans/{id}/document-check Loans Check VOC/AP/status receipt/loan document completeness.
GET /api/deliveries Deliveries List delivery schedules.
GET /api/deliveries/workboard Deliveries Return delivery rows with their server-derived stage, next action, blocker, Finance clearance, locked customer/PIC, and delivery-owned evidence.
GET /api/deliveries/pic-options Deliveries Return active Delivery or Boss/Admin staff choices for PIC assignment.
POST /api/deliveries Deliveries Create one active delivery plan for a vehicle with an existing canonical buyer and a valid staff PIC. The server locks the buyer and starts the internal status at BookingInspection.
PUT /api/deliveries/{id} Deliveries Update an active delivery plan. Vehicle, buyer, and internal status are server-owned; schedule changes require a reschedule reason.
POST /api/deliveries/{id}/correct-buyer BossAdmin Correct the vehicle and active delivery buyer with a required reason. Once a Finance V2 receivable exists, only a safe repair to that receivable customer is allowed; the server also rejects evidence already owned by someone else.
GET /api/deliveries/{id}/activity Deliveries List append-only delivery activity with the staff actor and server timestamp.
GET /api/deliveries/{id}/release-readiness Deliveries Check the exact delivery checklist, required delivery-owned documents, and release evidence metadata.
POST /api/deliveries/{id}/request-invoice-update Deliveries Record a reasoned pre-issuance request for Finance; it does not edit invoice or payment data and is rejected after the immutable Finance V2 invoice exists.
GET /api/deliveries/invoice-update-requests Finance List open Delivery invoice-update requests with the vehicle, locked customer, reason, and request time for Finance follow-up.
POST /api/deliveries/{id}/release Deliveries Release a ready vehicle after exact evidence and reconciled Finance clearance pass server validation. In the same transaction, one open canonical-buyer lead closes Sold, preferring the vehicle's existing sales attribution and then the latest assigned enquiry; every other open lead for that vehicle closes Lost. Already-closed and unrelated leads remain unchanged, and Delivery PIC remains separate from sales ownership.
POST /api/deliveries/{id}/cancel Deliveries Cancel an active delivery plan with a required reason.
GET /api/repairs Repairs List repair jobs.
POST /api/repairs Repairs Create repair job.
POST /api/repairs/from-receipt Repairs Accept nested repair, invoice and receipt commands after OCR review. Atomically create a repair job, supplier invoice, linked repair receipt and its confirmed receipt items from an unlinked vehicle repair-invoice upload. Vehicle/document locks serialize creation. An equivalent retry returns the existing records (200); changed confirmed details conflict (409). A new command returns 201. High-cost approval remains server-owned.
PUT /api/repairs/{id} Repairs Update repair job.
POST /api/repairs/{id}/approval BossAdmin Approve a repair with the authenticated Boss/Admin actor and server timestamp. Repair CRUD cannot supply an approval; material repair changes reset it.
GET /api/repairs/{id}/receipts Repairs List confirmed repair receipts and their child items.
POST /api/repairs/{id}/receipts/confirm Repairs Confirm one uploaded repair receipt and its reviewed child items for an existing repair job. Equivalent retries return the saved receipt/items without repeated writes or audits; changed confirmed values conflict.
GET /api/suppliers Repairs Derived supplier master summary from supplier invoices.
GET /api/supplier-master SupplierRead List supplier master records with address, phone, TIN, AutoCount creditor code, and Active/Inactive operational status.
POST / PUT /api/supplier-master Repairs Create an immediately usable Active supplier, or edit an existing supplier and set Active/Inactive status. Activation and deactivation are audited. Historical approval metadata is preserved. Only Active suppliers may be newly selected; historical records retain inactive supplier references. Repair high-cost and Finance payment approval are unchanged.
GET /api/supplier-invoices Repairs List supplier invoices.
GET /api/supplier-invoices/aging Repairs Supplier invoice aging view for unmatched, due-soon, overdue, and paid states.
POST /api/supplier-invoices Repairs Create supplier invoice. Amount must be positive with no more than two decimal places; when supplied, due and paid dates cannot precede the invoice date.
PUT /api/supplier-invoices/{id} Repairs Update supplier invoice with the same amount and date validation. Legacy records may retain no invoice date.
GET /api/leads Sales List public and back-office leads.
PUT /api/leads/{id} Sales Update lead/customer link/status.
GET /api/sales/workboard?agentUserId={id} Sales Return Sold this month and assigned cars for the Cars I’m Handling view, with the current process and combined current handoff (responsible department plus next action). Sales is server-scoped to the signed-in agent; Boss/Admin may select an agent.

Lead ownership: the first staff member who moves a lead out of New is recorded as the taker. After that, only the same staff member may mutate the lead; Boss/Admin retains the management override.

The delivery workboard presents four active stages: Plan delivery, Prepare car, Clear documents, and Handover. Completed and Cancelled are terminal views. The stage, one next action, and blocker are derived by the server from the saved checklist, exact delivery evidence, expiry dates, customer-notice state, and Finance clearance. Clients do not set the workboard stage directly.

Delivery release-readiness responses include:

  • isReady: true only when the release checklist, exact delivery-owned documents, and reconciled Finance clearance are all complete.
  • financeCleared: read-only Boolean showing whether the vehicle has cleared Finance. V2 requires an invoice, approved NCD/price adjustments, and fully reconciled collections; legacy records retain their reconciled-status rule. No Finance amounts or references are returned.
  • missingCategories: required release document categories still missing.
  • missingEvidence: required handover-photo or signed-handover uploads still missing.
  • expiredDocuments: delivery-critical expiry blockers for insurance and road tax. Windscreen expiry is not release-critical.
  • evidence: one item for each required release document category (DeliveryDocument, InspectionReport, HandoverPhoto, SignedHandover, Policy, and RoadTaxReceipt), with category, isPresent, and latest uploaded document metadata when present: documentId, fileName, mimeType, checksum, uploadedBy, and uploadedAt. WindscreenPolicy is retained in storage for compatibility and is excluded from Delivery evidence and release requirements.

For delivery release, upload every required file against the exact delivery schedule. Evidence linked only to the vehicle, another buyer, or an older delivery does not satisfy readiness. The files retain checksum, uploader, detected MIME type, timestamp, and protected download behavior; vehicle inventory photos remain separate media.

Only one active delivery plan is allowed per vehicle. A delivery locks the vehicle's canonical buyer when it is created and uses an active staff account for its PIC; ordinary updates cannot reassign the vehicle, buyer, or internal status. Once a non-cancelled delivery exists, ordinary vehicle edits also cannot replace that canonical buyer. Historical rows without a locked buyer are not silently backfilled: Customer 360 may show a conservative read-only association, while the Delivery workboard shows Buyer not locked and ordinary update, evidence upload, and release remain blocked. Boss/Admin may lock the vehicle's current canonical buyer through the reasoned correction action while the record is active and no evidence belongs to another buyer. An outstation delivery additionally records its destination address and transport method. Released and cancelled plans reject further changes, and a Sold vehicle or a vehicle with released delivery history cannot start another plan. Invoice updates remain a Finance responsibility, so Delivery can request a change with a reason but cannot edit Finance records. Finance sees open requests in a dedicated queue and explicitly marks each request resolved; unrelated payment edits do not close it. An open invoice-update request keeps the delivery in Clear documents and blocks release until Finance resolves it.

Workflow integrity:

  • Vehicle intake create/update cannot set LoanProcessing or Sold; loan and payment workflow updates derive those states on the server.
  • Loan approval and rejection are explicit server-audited decisions available only from Pending. Rejection requires a reason, preserves the linked customer and documents, and returns the vehicle to Available while keeping it private when no other workflow owns its state.
  • A loan can become Done only when StatusReceipt, Voc, ApDocument, and LoanDocument all belong to its exact vehicle and canonical buyer. The validation response uses loan_documents_incomplete and names the missing categories.
  • Legacy documents uploaded before buyer ownership was recorded remain available for reference but intentionally do not satisfy loan completion. Staff must re-upload the required documents from the loan checklist after the canonical buyer is linked; the system does not guess or backfill document ownership.
  • Delivery creation/update and payment reconciliation require that the vehicle has a CustomerId pointing to an existing canonical customer. Cash sales remain supported and do not require a loan.
  • Vehicle Sold state requires both Finance clearance and a released delivery. V2 clearance requires an invoice, approved NCD/price adjustments, and fully reconciled collections; legacy records retain their reconciled-status rule. Reconciliation alone leaves the car in its private in-progress state; a later Finance correction that removes clearance recalculates that state on the server. A physically released car remains assigned to Finance in Cars I’m Handling until clearance is restored, even when its stored vehicle status still says Sold.
  • Loan and payment records keep their vehicle identity after creation. Their workflow changes, Delivery release, and vehicle buyer edits share the same vehicle-scoped serialization so one department cannot overwrite a newer cross-department state.
  • Closing a vehicle lead as Sold records the responsible Sales agent on the vehicle. GET /api/sales/workboard uses that server-owned assignment for the agent's monthly sold count and Cars I’m Handling current-process list.

Finance

Finance V2 uses one receivable per vehicle, one immutable YS Heng invoice snapshot, and multiple partial collection rows. Creating the receivable locks the buyer identity, requires a Boss/Admin-approved vehicle, and copies the locked vehicle selling price instead of trusting the submitted amount. A stale or different submitted sales price is rejected. Manual nett-price variances and every positive NCD remain pending until maker-checker approval. An active Delivery invoice-update request must be resolved or cancelled before the immutable invoice is issued, and no new request can be opened or closed after issuance. All finance endpoints require the Finance policy except the Boss/Admin-only legacy management review, nett-price/NCD approval, and collection reversal actions.

First-deploy assumptions: no pre-existing Finance V2 invoice can already have an open Delivery invoice-update request. Historical positive-NCD records without a recorded reason, requester, and request time remain blocked from approval and collection until explicitly reviewed and remediated; the API does not infer a maker for them. An invoiced V2 record with an unapproved adjustment shows AttentionNeeded and cannot clear Delivery or appear completed in Cars I’m Handling, even if older collections were reconciled. Review affected historical records with Finance before operational use; this release does not fabricate approval metadata or rewrite stored transactions. If invalid data is imported later, the API rejects invoice issuance and request resolution instead of claiming that an immutable PDF was changed.

Method Path Purpose
GET /api/finance/vehicle-options Return Boss/Admin-approved Finance-only car plate choices with canonical customer, selling price, and additional charges used to prefill invoice preparation. Purchase and refurbishment values are excluded.
GET /api/payments List legacy and V2 payment records. Each row includes the linked invoice, collection history, collected amount, balance, amount still available to allocate, and plain-language receivable status.
POST /api/payments Create a legacy payment record for compatibility and Cash Custody. The vehicle must be approved, the nett total must exactly equal its approved selling price, all charge/NCD/paid-on-behalf components must be zero, and the server snapshots the vehicle selling price. New sales with any price component or controlled adjustment should use /api/payments/finance-sale.
POST /api/payments/finance-sale Create a Finance V2 sale from the locked, approved vehicle selling price. The submitted sales price must match exactly. The server calculates nett price and issues the invoice immediately only when neither an NCD nor a manual nett-price variance needs approval.
GET /api/payments/export Export payment CSV after Finance/Admin authorization and audit logging.
GET /api/payments/export-autocount?from=YYYY-MM-DD&to=YYYY-MM-DD Export the AutoCount Excel review workbook with period-scoped sales-invoice and collection sheets. A collection keeps its invoice number even when that invoice was issued before the selected period. This remains a manual mapping aid, not a verified direct-import template.
PUT /api/payments/{id} Update a legacy payment workflow or references while preserving Cash Custody compatibility. V2 rows reject this general route, and legacy financial terms are immutable after creation; requests also cannot alter server-owned customer, formula, variance, approval, or workflow-version fields.
POST /api/payments/{id}/management-review Boss/Admin marks a legacy payment as management-reviewed. Payment CRUD cannot self-assert this review, and material legacy edits reset it.
POST /api/payments/{id}/nett-price-override/approve Boss/Admin approves a V2 positive NCD or manual nett-price variance and atomically issues the invoice. The requester cannot approve their own adjustment.
POST /api/payments/{id}/invoice Idempotently issue or recover an eligible V2 invoice after any active Delivery invoice-update request is resolved or cancelled.
POST /api/payments/{id}/collections Add one non-cash partial collection without exceeding the unallocated invoice balance. Any NCD or nett-price variance must already have maker-checker approval, including on historical invoice rows. New clients supply an idempotencyKey; an exact retry returns the existing aggregate, while reuse with different details is rejected. Every allowed method requires a traceable reference.
POST /api/collection-transactions/{id}/financing-status Record the external bank progression from Pending to Approved to Disbursed. A bank-disbursement collection always starts at Pending.
POST /api/collection-transactions/{id}/reconcile Finance or Boss/Admin reconciles a collection after confirming the funds. The same eligible user may record the collection, attach its evidence, and reconcile it; the server audit records the reconciliation actor and time. Evidence must be linked to that exact collection, any NCD or nett-price variance must retain its separate maker-checker approval, and only reconciled collections reduce balance. Successful non-cash reconciliation atomically and idempotently issues one protected official Customer Receipt PDF for that collection.
POST /api/collection-transactions/{id}/reverse Boss/Admin reverses a collection with a required reason; no collection row is deleted. Any linked Customer Receipt is retained and marked void with the same actor, time, and reason.
GET /api/collection-transactions/{id}/official-receipt/content Download the Finance-protected official Customer Receipt PDF issued for a reconciled collection.
POST /api/deliveries/{id}/resolve-invoice-update Finance closes a legacy pre-issuance Delivery invoice-update request after handling it; this records the Finance actor and server timestamp. Resolution is rejected after an immutable Finance V2 invoice exists.
GET /api/finance-invoices/{invoiceId}/content Download the protected YS Heng sales-invoice PDF and record the authenticated Finance actor in the audit log before content is returned.
GET / POST /api/settlement-reminders List/create seller settlements. Create takes vehicleId, nonnegative bankDebtAmount, optional expectedPurchasePrice and deadline; canonical Owner and positive purchase price are read from the vehicle. The server snapshots purchase price and calculates direction and absolute difference. Client amount, owner and completion values cannot override it.
GET /api/settlement-drafts Return Finance-only previous-owner and purchase-price intake values used to prefill settlement review.
PUT /api/settlement-reminders/{id} Edit open settlement bank debt/deadline only, recomputing from the stored purchase-price snapshot. Historical settlement amount/Owner are preserved. Requires expectedAmount, expectedDirection, expectedBankDebtAmount (null for legacy), expectedDeadline, and expectedIsPaid; stale snapshots return 409. A completed record must be reopened separately.
POST /api/settlement-reminders/{id}/status Confirm payment/receipt/offset or reopen, with isPaid and the same expected snapshot as term edits. Status changes do not accept new monetary terms. Stale or repeated transitions return 409; authenticated actor and transition are audited. No funds are transferred or posted to AutoCount.
GET / POST /api/daily-spends List/create daily spend rows.
PUT /api/daily-spends/{id} Update daily spend row.
GET / POST /api/broker-commissions List/create broker commission rows.
PUT /api/broker-commissions/{id} Update broker commission row.
GET / POST /api/debt-recoveries List/create debt recovery cases.
PUT /api/debt-recoveries/{id} Update debt recovery case.
GET / POST /api/payment-vouchers List/create payment vouchers.
PUT /api/payment-vouchers/{id} Update payment voucher.
GET /api/payment-vouchers/{id}/pdf Download the finance-controlled standard Payment Voucher PDF. Pending vouchers are marked draft; every download is audited.

Finance V2 nett price is calculated to two decimal places:

calculatedNettPrice = salesPrice + interestAdditionalCharges + windscreenCharges - ncdAmount
nettPriceVariance = agreedNettPrice - calculatedNettPrice

The agreed nett price may differ from the calculation only with a reason and approval from a different Boss/Admin user. A positive NCD is also a total-reducing component, so it requires the same reason and different-user approval even when the agreed nett price exactly matches the calculated amount. Invoice issuance rechecks that the vehicle remains approved and its current selling price still matches the receivable snapshot. After any receivable exists, its vehicle selling price cannot be changed; after a V2 receivable exists, the confirmed buyer also cannot be reassigned through ordinary vehicle or legacy-payment updates. Collection creation and reconciliation recheck that the vehicle, receivable, and immutable invoice snapshot still identify the same buyer and that every controlled price adjustment is approved. A V2 receivable is finance-settled only after an invoice exists and reconciled, non-reversed collections reduce the balance to zero. Pending allocations reserve available balance but do not count as collected. A vehicle becomes Sold only when that Finance clearance and a released delivery are both present.

Collection requests are serialized per receivable. Active collection references are normalized and unique per payment method across sales, and the database enforces this invariant for concurrent requests. Upload PaymentReceipt or PaymentInvoice evidence with both paymentRecordId and collectionTransactionId; the collection must belong to that payment and vehicle. Collection-linked evidence is accepted only while the locked collection is Pending. Uploads serialize with reconciliation and reversal using the vehicle advisory lock, then collection and payment row locks. A terminal-state upload returns HTTP 409 with collection_document_not_pending, without inserting a document or upload audit. Previously uploaded evidence remains readable under its existing permissions. Evidence MIME type and filename extension must match the detected content; PDFs are parsed strictly with PdfPig, while images are decoded and dimension-bounded before storage. Invoice issuance and collection mutations are audit logged.

PaymentRecord.OutstationDeliveryDate is a compatibility field derived from the active outstation delivery schedule during legacy payment create/update. Client-supplied Finance values do not override the Delivery-owned schedule date. Finance V2 partial cash collections are created through Cash Custody with a retry key and a one-to-one collection link; a pending custody allocation reserves the available balance without counting as collected.

Cash Custody And Official Receipts

Cash custody has its own CashCustody policy. Sales can see and act on only their own handovers; Finance and BossAdmin can monitor all handovers. Sales records cash received and requests the handover. Finance records physical receipt, then a different Finance or BossAdmin checker accepts or rejects it. The collector cannot receive, accept, or reject their own handover, and the Finance receiver cannot decide their own handover. The server derives and rechecks the payment, collection, vehicle, customer, actors, timestamps, and amount links rather than trusting client-supplied values.

Method Path Policy Purpose
GET /api/cash-handovers CashCustody List custody records; Sales receives only their own rows.
GET /api/cash-handovers/payment-lookup CashCustody Minimal payment, customer, and vehicle lookup for recording a cash handover.
POST /api/cash-handovers Sales Record cash received. Legacy cash must match the full nett price; Finance V2 accepts a positive partial amount up to the available invoice balance and requires an idempotency key.
POST /api/cash-handovers/{id}/request-handover Sales Recorded collector marks cash as pending handover.
POST /api/cash-handovers/{id}/hand-over Finance Finance records physical receipt from the salesperson.
POST /api/cash-handovers/{id}/accept Finance Independent checker accepts custody, generates one idempotent official receipt PDF, and atomically reconciles a linked Finance V2 collection.
POST /api/cash-handovers/{id}/reject Finance Independent checker rejects custody with a required reason and atomically reverses a linked Finance V2 allocation.
GET /api/cash-handovers/{id}/official-receipt/content CashCustody Download the official receipt for Finance/BossAdmin or the recorded salesperson.

Only one handover may exist per legacy payment, each Finance V2 cash collection may have only one handover, and each handover may have only one official receipt. Multiple partial Finance V2 cash handovers may be recorded while an invoice has available balance. Generic collection reconcile, reverse, and evidence actions reject Cash so the custody workflow cannot be bypassed. Legacy receipt creation remains separate from legacy payment reconciliation. Authorized staff download the protected receipt and attach it to a customer email; WhatsApp dispatch is intentionally deferred to the notification engine in FOO-40.

HR And Salary

All HR endpoints require authenticated back-office access. Staff can access their own attendance, leave, MC, balance, payroll profile, pay-period, and payslip records. HR/Salary and Admin users can review and manage all staff HR records. Boss/Admin alone can view the HR-specific /api/hr/boss-calendar and manage office attendance-network ranges. The separate shared operations calendar is available to all authenticated staff and exposes availability only, without leave reasons or medical details.

Method Path Purpose
GET /api/hr/staff HR/Admin list of existing staff users for HR selectors.
GET /api/hr/attendance List attendance records scoped to the current staff user, or all staff for HR/Admin.
GET /api/hr/boss-calendar?from=YYYY-MM-DD&to=YYYY-MM-DD Boss/Admin only. Approved leave days as staff-name Unavailable entries; excludes leave reason, MC, and medical details.
GET /api/hr/attendance-networks Boss/Admin only. List the office CIDR allow-list.
POST /api/hr/attendance-networks Boss/Admin only. Add an office CIDR range with label and active status.
PUT /api/hr/attendance-networks/{id} Boss/Admin only. Update or disable an office CIDR range.
POST /api/hr/attendance/check-in Create or update today's check-in for the current staff user.
POST /api/hr/attendance/check-out Create or update today's check-out for the current staff user.
GET /api/hr/dashboard Role-scoped attendance counts for today's QR, manual, open-session, and outstation activity plus pending/upcoming trip counts.
GET /api/hr/availability-calendar Role-scoped approved leave and outstation availability; other staff details are reduced to busy status.
GET /api/hr/reminder-policies Read attendance reminder switches and lead-hour settings.
PUT /api/hr/reminder-policies/{type} HR/Admin update an attendance reminder policy.
GET /api/hr/reminders Role-scoped active reminders for pending approvals, upcoming outstation duty, and missing check-out.
POST /api/hr/attendance/qr/challenges HR/Admin create a five-minute rotating office QR challenge; the raw token is returned only for display and only its hash is stored.
POST /api/hr/attendance/qr/redeem Authenticated staff redeem the office QR for one Check In or Check Out action; each staff member can use a challenge once per action.
POST /api/hr/attendance/outstation/start Reserved for the future outstation workflow; currently refuses attendance bypass.
POST /api/hr/attendance/outstation/end Reserved for the future outstation workflow; currently refuses attendance bypass.
PUT /api/hr/attendance/{id} HR/Admin correction with a required note; records manual verification and audit history.
GET /api/hr/business-trips List business trip and urgent outstation requests scoped to self, or all staff for HR/Admin.
POST /api/hr/business-trips Submit a business trip or urgent outstation exception request; it remains pending until HR/Admin approval.
PUT /api/hr/business-trips/{id}/decision HR/Admin approve or reject a pending business trip request. Approved trips do not consume leave balance.
POST /api/hr/business-trips/{id}/cancel Staff cancel their own pending/approved request, or HR/Admin cancel any request.
GET /api/hr/leave-requests List leave and MC requests scoped to the current staff user, or all staff for HR/Admin.
POST /api/hr/leave-requests Submit a leave request.
PUT /api/hr/leave-requests/{id}/decision HR/Admin approve or reject a leave request. A staff member cannot approve their own request.
PUT /api/hr/leave-requests/{id}/cancel Staff cancel their own pending leave request; HR/Admin can cancel any pending staff leave request.
POST /api/hr/leave-requests/{id}/mc Upload a medical certificate document for the leave request, max 10 MB.
GET /api/hr/leave-requests/{id}/mc/content Download the medical certificate for the owner or HR/Admin.
GET /api/hr/leave-balances List AL/MC balances scoped to self, or all staff for HR/Admin.
PUT /api/hr/leave-balances/{staffUserId} HR/Admin apply/reset a staff AL/MC balance, usually from a role policy.
GET /api/hr/leave-policies HR/Admin list default AL/MC entitlements by role.
PUT /api/hr/leave-policies/{role} HR/Admin create or update default AL/MC entitlement for a role.
GET /api/hr/leave-adjustments List leave adjustment history scoped to self, or all staff for HR/Admin.
POST /api/hr/leave-adjustments HR/Admin increase or decrease one staff member's AL/MC balance with a reason and audit log.
GET /api/hr/payroll-profiles List payroll profiles scoped to self, or all staff for HR/Admin.
PUT /api/hr/payroll-profiles/{staffUserId} HR/Admin create or update Monthly or Hourly employment profile, salary/rate, allowances, and manual deductions.
GET /api/hr/pay-periods List pay periods and configured working days.
POST /api/hr/pay-periods HR/Admin create a working-day pay period.
GET /api/hr/payslips List payslips scoped to self, or all staff for HR/Admin.
GET /api/hr/payslips/{id}/pdf Download a one-page payslip PDF when the signed-in staff member owns it or has HR/Admin access; download is audited.
POST /api/hr/pay-periods/{id}/generate-payslips HR/Admin generate or update payslips for a pay period.

Payslip formula:

dailySalary = monthlyBaseSalary / workingDays
unpaidLeaveDeduction = dailySalary * approvedUnpaidLeaveDays
grossPay = monthlyBaseSalary + overtimePay + allowances
netPay = grossPay - unpaidLeaveDeduction - manualDeductions

hourlyWorkedHours = completed Present, Late, and HalfDay check-out minus check-in within the pay period
hourlyGrossPay = (hourlyWorkedHours * hourlyRate) + allowances
hourlyNetPay = hourlyGrossPay - manualDeductions

Attendance dates and payroll-period boundaries use Asia/Kuala_Lumpur. Check-in and check-out require the client address supplied by the trusted Caddy proxy to match an active office CIDR range; raw IP history is not stored. Remote and outstation exceptions are not available in this release.

Statutory EPF, SOCSO, EIS, and PCB calculations are excluded from this MVP.

Dashboard, Audit, And Admin

Method Path Policy Purpose
GET /api/dashboard/summary?from=YYYY-MM-DD&to=YYYY-MM-DD Dashboard Boss/Admin operational metrics. from and to are optional but must be supplied together as an inclusive analytics period. Live stock, loan, collection, settlement, purchase cost, repair cost, and aging metrics remain current; the period scopes sales, actual profit, lead, refurbishment, and aggregate OCR activity analysis. The response preserves totalProfit and estimatedProfit, and adds purchaseCost, totalSales, actualProfit, outstandingCollection, settlementDueAmount, refurbishment, and aiDocumentProcessing. OCR reporting contains aggregate category counts, reviewed outcomes, low-confidence and failed counts, the live pending-review backlog, and current quota capacity only; it never returns document IDs, file names, images, identity data, raw OCR text, or extracted values.
GET /api/dashboard/reminders?type={type}&due={All|Overdue|DueToday|DueSoon|Upcoming} Dashboard Reminder inbox, optionally filtered. DueSoon applies only to unpaid Daily Spend due from tomorrow through the next 10 calendar days.
GET /api/priority-actions BackOffice Role-scoped operational queue. Returns only items the signed-in user's roles may action: Sales leads, Repair work, Loan follow-up, Delivery preparation, Finance follow-up, and HR leave approvals. Boss/Admin receives the combined management queue.
GET /api/audit-log?actor=&action=&entityName= BossAdmin Filterable audit history.
GET /api/admin/ai-limits/ocr BossAdmin Read the OCR enabled state, monthly and per-staff daily limits, and current-month usage.
PUT /api/admin/ai-limits/ocr BossAdmin Update the server-enforced OCR enabled state, monthly request limit, and per-staff daily request limit.
GET /api/admin/users BossAdmin List staff users and roles.
POST /api/admin/users BossAdmin Create staff user.
PUT /api/admin/users/{id} BossAdmin Update staff display name.
PUT /api/admin/users/{id}/password BossAdmin Reset staff password.
PUT /api/admin/users/{id}/status BossAdmin Enable/disable staff user.
PUT /api/admin/users/{id}/roles BossAdmin Replace staff role assignments.

Enum Values

  • StockOwner: YSHeng, KS
  • VehicleStatus: Available, LoanProcessing, Sold
  • LeadStatus: New, Contacted, Closed
  • LeadClosureOutcome: Sold, Lost, Invalid
  • LoanStatus: Draft, Pending, Approved, Rejected, Done
  • DeliveryStatus: BookingInspection, Scheduled, Inspection, PreparingDocuments, CarPreparation, ReadyForRelease, Released, Cancelled
  • DeliveryType: Standard, Outstation
  • DeliveryStage: PlanDelivery, PrepareCar, ClearDocuments, Handover, Completed, Cancelled
  • PaymentStatus: Pending, Approved, Disbursed, Reconciled
  • CollectionStatus: Pending, Reconciled, Reversed
  • CollectionMethod: BookingDeposit, DownPayment, BankTransfer, BankDisbursement, Cheque, Card, TradeInCredit, Other, Cash
  • FinancingStatus: NotApplicable, Pending, Approved, Disbursed
  • ReceivableStatus: Draft, WaitingForApproval, ReadyToCollect, PartiallyPaid, Paid, AttentionNeeded
  • PaymentVoucherStatus: Pending, Approved, Paid
  • CashHandoverStatus: ReceivedBySales, PendingHandover, HandedOver, Rejected, Receipted
  • DebtRecoveryStatus: Open, FollowedUp, Closed
  • SettlementDirection: LegacyPaySeller, PaySeller, CollectFromSeller, InternalOffset
  • SupplierStatus: Active, Inactive
  • RepairApprovalStatus: Pending, Approved, Rejected
  • SupplierInvoiceAgingStatus: Unmatched, DueSoon, Overdue, Paid
  • HrAttendanceStatus: Present, Late, HalfDay, Absent
  • HrLeaveType: AnnualLeave, MedicalLeave, EmergencyLeave, UnpaidLeave
  • HrLeaveStatus: Pending, Approved, Rejected, Cancelled
  • HrPayslipStatus: Draft, Generated
  • HrEmploymentType: Monthly, Hourly
  • HrAttendanceVerificationMethod: Manual, OfficeQr, Outstation, ManualException, OfficeIp
  • FileCategory: VehiclePhoto, PurchaseInvoice, Voc, IdentityCard, ApDocument, StatusReceipt, LoanDocument, DeliveryDocument, HandoverPhoto, SignedHandover, Policy, RoadTaxReceipt, RepairInvoice, PaymentReceipt, PaymentInvoice, MedicalCertificate, InspectionReport, WindscreenPolicy
  • OcrJobStatus: Queued, Analyzing, NeedsReview, Failed, Reviewed
  • OcrReviewDecision: Pending, Accepted, Rejected, Reviewed
  • AiService: Ocr
  • AiUsageStatus: Reserved, Succeeded, Failed

Error Shape

Validation failures usually return one of these shapes:

{
  "errors": [
    { "code": "plate_required", "message": "Car plate is required." }
  ]
}
{
  "message": "Route vehicle id does not match body id."
}

Back-office mutations write audit records with the authenticated staff email. Public lead creation writes a public actor audit record.