Repository navigation
Build REST API for the Quotation module (Fixes zerp-pk/zerp#25) - #2
Merged
Merged
Conversation
Backs the Flutter app with a JSON API mirroring the web module.
- CRUD for quotations with their line items and per-item taxes, the three
status transitions (draft -> sent -> accepted/rejected), and a dashboard
summary, all under api/quotation behind auth:sanctum with the shared
{success, message, data} envelope.
- Header totals are recalculated server-side from the submitted items, and
the header plus items are written in one DB transaction so a create or
update cannot leave a half-written quotation.
- Same visibility as the web screen: manage-any sees the company, manage-own
sees its own (a client never sees drafts). Writes require the company-owner
relationship, matching the web.
Applying the lessons from the earlier module APIs:
- show/update/destroy and the status routes take an id and query with the
scope rather than route-model binding, which does not run on these routes
(bare api.json middleware, not Laravel's api group, so SubstituteBindings is
absent and a type-hinted model arrives empty). An out-of-scope id 404s.
- customer_id and warehouse_id are scoped to the caller's company, closing the
cross-tenant gap the web's plain exists rules leave open.
- Errors are logged and answered generically; no exception text reaches the client.
Not ported: convert-to-invoice, revision, and duplicate. They reach across into
the accounting module and are heavier than the core CRUD this issue asks for.
This was referenced Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes zerp-pk/zerp#25. Adds a Sanctum-authenticated JSON REST API for the Quotation module, backing the Flutter app, following the HRM pattern.
Endpoints (all under
api/quotation,auth:sanctum)GET /dashboard- counts and value by status, totals, and the 5 most recent.GET /quotations- list, with customer / status / search filters, a safe sort allowlist, pagination.POST /quotations- create, with line items and per-item taxes.GET /quotations/{id}- show (items + taxes + customer + warehouse).PUT /quotations/{id}- update (drafts only).DELETE /quotations/{id}- delete (not while sent).POST /quotations/{id}/sent|approve|reject- the status transitions.Compliance with the shared patterns
ApiResponseTrait{success, message, data}everywhere.App\Http\Requests\ApiFormRequest, so a validation failure keeps the 422 envelope. Literal-array rules.DB::transactionso a create/update can't half-write.manage-anysees the company,manage-ownsees its own (a client never sees drafts). Writes require the company-owner relationship.Scramble::registerApi('quotation', ...)guarded byclass_exists, at/docs/quotation.Lessons from the earlier module APIs, applied up front
show/update/destroyand the status routes take anidand query with the scope, not route-model binding: these routes run bareapi.jsonmiddleware (not Laravel'sapigroup), soSubstituteBindingsnever runs and a type-hinted model arrives empty. An out-of-scope id 404s.customer_idandwarehouse_idare scoped to the caller's company, closing the cross-tenant gap the web's plainexistsleaves open.Not included (follow-up)
convert-to-invoice,create-revision, andduplicate. They reach into the accounting module and are heavier than the core CRUD this issue asks for.Tested live against the running app
Seeded a client, warehouse, and product, then drove every endpoint with a real Sanctum token:
statusreturnsdraft.sent; delete-while-sent 422; update-while-sent 422; approve ->accepted; dashboard then shows accepted value 189.customer_id422; unauthenticated 401./docs/quotation.jsonrenders 200 with all six path groups.Two bugs I caught and fixed while testing (why the live run matters): I had copied
creator_id/created_byonto the line items, butsales_quotation_itemshas neither column (the model's fillable lists them, but they do not exist), so create 500'd until removed; and the create response reportedstatus: nullbecausestatusis a database default the model does not set on the instance, fixed with a refresh before serialising.No package test suite exists here yet (zerp-pk/zerp#48), so verification is the live run above.
🤖 Generated with Claude Code