@go-labs-sg/bb 2.32.0 → 2.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -232,7 +232,37 @@ Every canonical command is classified by effect: `state-change`, `email`, `exter
232
232
 
233
233
  **Draft-only bills:** Use `bb bill create --draft-only --payload '<bill-json>' --allow-state-change --allow-financial-write` when the user authorizes a draft only. The equivalent payload field is `draftOnly: true`. This creates `DRAFT` for every role, without auto-checking, auto-approval, approval requests, emails, or QuickBooks writes. It cannot be combined with `alreadyPaid: true`; conflicting flag and payload values are rejected. Draft-only creation uses the dedicated `bill.createDraft` API route and fails on older servers without falling back to ordinary creation. Deploy backend support before releasing the updated CLI. Reconciled projects must be moved to Won separately before draft-only creation, because reopening a project can trigger external integrations. Completed projects retain their existing creation restriction.
234
234
 
235
- **Quotation-first supplier bills:** Use `quotationIds` when approved quotations cover disjoint bill lines. When two quotations fund the same bill line, pass `quotationAllocations` entries with `{ budgetItemId, quotationId, amount }` and an exact `lineAmounts` entry for every selected budget item; `lineAmounts` must sum to the bill amount. Every quotation must belong to the bill supplier and project and cover the allocated budget item. Legacy `quotationId` remains valid for one quotation. Supplier bills that qualify for an existing exemption must state that intent with `--quotation-exempt`. Claims share budget allowance but never consume or link quotations, so quotation source fields are rejected for claims.
235
+ ### Bill amounts and GST (create and update)
236
+
237
+ **All bill amount fields below exclude GST.** This applies to ordinary creation, draft-only creation, and updates. The CLI does not convert a GST-inclusive invoice total into a subtotal. BB calculates applicable GST separately using the supplier's GST registration and each line's out-of-scope setting.
238
+
239
+ | Payload field | Meaning |
240
+ | --- | --- |
241
+ | `amount` | Amount being billed now, **before GST**. For a deposit or partial bill, use only the portion being billed. |
242
+ | `extractedAmount` | Full supplier invoice amount **before GST**, as shown on the attachment. For a partial bill, this can exceed `amount`. |
243
+ | `lineAmounts[].amount` | This bill's allocation to each selected budget item, **before GST**. The entries must sum to `amount`. |
244
+ | `quotationAllocations[].amount` | Quotation coverage for each bill line, **before GST**. Use the same tax basis as `lineAmounts`. |
245
+
246
+ For an invoice showing **S$1,800 subtotal + S$162 GST = S$1,962 total**, a full bill uses the following amount fields (fragment only; include the other required bill fields):
247
+
248
+ ```json
249
+ {
250
+ "amount": 1800,
251
+ "extractedAmount": 1800,
252
+ "lineAmounts": [{ "budgetItemId": "item-a", "amount": 1800 }],
253
+ "quotationAllocations": [
254
+ { "budgetItemId": "item-a", "quotationId": "quote-a", "amount": 1800 }
255
+ ]
256
+ }
257
+ ```
258
+
259
+ Do **not** put `1962` in either `amount` or `extractedAmount` for this full bill: BB would treat it as a before-GST value. A 50% partial bill against the same invoice uses `amount: 900`, `extractedAmount: 1800`, and line/quotation allocations totaling `900`; with 9% GST applicable to that line, its total is S$981.
260
+
261
+ Use the invoice's actual subtotal and tax breakdown. Do not blindly divide every invoice total by 1.09: non-GST-registered suppliers, out-of-scope lines, and mixed-tax invoices need their actual breakdown. If the attachment does not establish the before-GST amount, clarify it before creating or updating the bill.
262
+
263
+ After a mutation, re-read the bill and verify `amount`, `extractedAmount`, line amounts, and quotation allocations against the attachment. Report the subtotal, GST, and total separately; do not describe the stored `amount` as the GST-inclusive total. When correcting a gross/net input mistake, also check `extractedAmount` so the full invoice is not accidentally represented as a partial bill.
264
+
265
+ **Quotation-first supplier bills:** Use `quotationIds` when approved quotations cover disjoint bill lines. When two quotations fund the same bill line, pass `quotationAllocations` entries with `{ budgetItemId, quotationId, amount }` and an exact `lineAmounts` entry for every selected budget item; `lineAmounts` must sum to the bill amount. Canonical allocation rows are the stored source of every bill-line quotation relationship. A required zero-value bill line needs one or more allocations with `amount: 0`, which record its approved source(s) without consuming quotation capacity. Every quotation must belong to the bill supplier and project and cover the allocated budget item. Legacy `quotationId` remains valid as single-quotation input and maps to the current selection. Supplier bills that qualify for an existing exemption must state that intent with `--quotation-exempt`. Claims share budget allowance but never consume or link quotations, so quotation source fields are rejected for claims.
236
266
 
237
267
  Every selected bill or claim line must belong to an active budget in `ESTIMATE_ACCEPTED` or `ESTIMATE_CLOSED`, including lines drawn from additional budgets in the project. The server checks this during selection, creation, editing, and approval. An unaccepted budget never qualifies a line for a quotation exemption. Existing pending bills expose budget and source-link blockers in `commitmentReview.blockers`.
238
268
 
@@ -327,7 +357,7 @@ Bill, claim, and quotation mutations accept only suppliers whose approval status
327
357
  | Area | Legacy handler labels (reference only; non-exhaustive) |
328
358
  | --- | --- |
329
359
  | **Budgets** | `list-budgets` (full payload by default; `--summary` or `--includeDetails false` for slim list), `get-budget`, `get-budget-items`, `get-budget-details`, `get-budget-categories`, `get-budget-versions`, `rename-budget-version`, `restore-budget-version`, `update-budget-status` (`ESTIMATE_ACCEPTED` requires win proof, auto-marks `PITCH` or `LOST` projects `WON`, and auto-creates an Asana Event Ops section from Prompt 5 tasks when enabled; Prompt 5 skips quantity-zero items, combines matching non-GO-internal suppliers, keeps each GO internal item separate, removes generated work that repeats another line item or group, and schedules applicable subtasks relative to the project start date; `--projectStatusOnCommercialRejection PITCH\|LOST` is required when rejecting the only accepted/closed budget on a commercial project), `mark-budget-won` (`<budgetId>` + proof file path; `PITCH` or `LOST` projects become `WON` automatically), `create-budget` / `update-budget` (`--payload`; cloned unavailable lines require `unavailableItemReviewAcknowledged: true`), `delete-budget`, `create-budget-approval` (also sends approval request emails), `create-estimate`, `send-estimate-to-contact-person` (same contact-person email workflow as web), `add-budget-items`, `update-budget-item`, `replace-budget-item`, `approve-unavailable-item-exception` (Lead/Admin), `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `mark-budget-items-not-utilized`, `restore-budget-item`, `create-placeholder-bill` (admin recovery), `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `delete-budget-commission`, `update-budget-discount`, `delete-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
330
- | **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`, plus `--quotationIds q1,q2` or legacy `--quotationId q1`; validates the approved supplier and quotation coverage), `link-bill-quotations` (`<billId> --quotationIds q1,q2`; Admin source-link repair for eligible supplier bills), `stage-bill-attachment` (securely uploads invoice/payment-proof files before creation and returns attachment JSON; ownership and one-hour expiry are enforced by a server-side staged-upload record rather than encoded in the object key), `cleanup-staged-bill-attachments`, `create-bill` (`--payload`; set `isClaimable=false` for a bill and provide approved `quotationIds`, or use `--quotation-exempt` for an eligible server-validated exemption; set `isClaimable=true` for a claim without quotations; supplier bills require positive `extractedAmount` and `amount`, with `amount <= extractedAmount`; an already-paid supplier bill sets `alreadyPaid=true` and requires `paymentReference` plus a staged PDF in `paymentProofAttachments`; admin creation automatically queues QBO finalization while other roles remain pending approval), `update-bill` (`--payload`), `update-bill-payment-evidence` (`--payload`; replaces the payment reference and payment-proof PDFs for an already-paid bill), `delete-bill`, `create-bill-approval` (also sends approval request emails), `update-bill-status` (`PAID` requires `--paymentReference`; pass `--paymentProof <receipt.pdf>` to stage and submit a PDF up to 20MB atomically, or omit it only when BB already has payment proof; moving to `PAID` runs the server's paid-bill notification workflow), `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clearing a paid bill's QuickBooks link is not allowed), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
360
+ | **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`, plus `--quotationIds q1,q2` or legacy `--quotationId q1`; validates the approved supplier and quotation coverage), `link-bill-quotations` (`<billId> --quotationIds q1,q2`; Admin source-link repair for eligible supplier bills), `stage-bill-attachment` (securely uploads invoice/payment-proof files before creation and returns attachment JSON; ownership and one-hour expiry are enforced by a server-side staged-upload record rather than encoded in the object key), `cleanup-staged-bill-attachments`, `create-bill` (`--payload`; set `isClaimable=false` for a bill and provide approved `quotationIds`, or use `--quotation-exempt` for an eligible server-validated exemption; set `isClaimable=true` for a claim without quotations; supplier bills require positive **before-GST** `extractedAmount` (full invoice subtotal) and `amount` (portion billed), with `amount <= extractedAmount`; line and quotation allocation amounts also exclude GST; an already-paid supplier bill sets `alreadyPaid=true` and requires `paymentReference` plus a staged PDF in `paymentProofAttachments`; admin creation automatically queues QBO finalization while other roles remain pending approval), `update-bill` (`--payload`), `update-bill-payment-evidence` (`--payload`; replaces the payment reference and payment-proof PDFs for an already-paid bill), `delete-bill`, `create-bill-approval` (also sends approval request emails), `update-bill-status` (`PAID` requires `--paymentReference`; pass `--paymentProof <receipt.pdf>` to stage and submit a PDF up to 20MB atomically, or omit it only when BB already has payment proof; moving to `PAID` runs the server's paid-bill notification workflow), `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clearing a paid bill's QuickBooks link is not allowed), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
331
361
  | **Quotations** | `list-quotations` (supports project, budget, supplier, status, requester, and text-search filters), `get-quotation`, `upload-quotation-attachment` (`<projectId>` + PDF/JPEG/PNG path up to 20MB; returns attachment JSON for payload use), `cleanup-staged-quotation-attachments`, `create-quotation` (`--payload` for `quotation.createDraft`; include `amountWithoutGst` and `gstAmount`), `update-quotation` (`--payload`; updates a `DRAFT` or `REJECTED` quotation), `delete-quotation`, `submit-quotation`, `approve-quotation`, `reject-quotation`, `download-quotation-pdf` (`original`, `staff`, or `final`) |
332
362
  | **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics; `--sortBy totalInvoiceAmount` sorts provider-confirmed totals including GST, with unknown amounts last), `get-customer-invoice`, `get-customer-invoice-email-context`, `preview-qbo-customer-invoice`, `import-qbo-customer-invoice` (BB-only link with reviewed token), `create-customer-invoice`, `discard-customer-invoice`, `delete-customer-invoice`, `void-customer-invoice`, `approve-customer-invoice` (admin), `reject-customer-invoice` (admin), `send-customer-invoice-to-contact-person`, `download-customer-invoice-pdf` (non-admins cannot download while approval is pending), `sync-customer-invoice` |
333
363
  | **Approvals** | `list-approvals` / `get-pending-approvals` (`--type budget\|supplier\|bill\|quotation\|customer_invoice\|all`), `approve-bill` / `reject-bill` (send reply email), `approve-budget` / `reject-budget` (send reply email), `approve-supplier` / `reject-supplier` (send reply email), `approve-quotation` / `reject-quotation`, `approve-customer-invoice` / `reject-customer-invoice` |
@@ -403,7 +433,6 @@ relationships or allocations block approval and require review. Multi-line quota
403
433
  create/update payloads must include `lineAmounts` with `{ budgetItemId, amount }`
404
434
  for every selected line, summing to `amountWithoutGst`. The server revalidates
405
435
  existing quotation links, reviewed usage, and per-line allowance inside the
406
- approval transaction. Multiple source quotations use `BillLineItem.quotationId`;
407
- apply the updated database schema and deploy the API before releasing the CLI.
408
- Legacy bill-wide links remain readable, and unresolved historical overlaps
409
- continue to block approval.
436
+ approval transaction. Multiple source quotations use canonical bill quotation
437
+ allocation rows; apply the updated database schema and deploy the API before
438
+ releasing the CLI. Unresolved historical overlaps continue to block approval.
@@ -2063,7 +2063,7 @@
2063
2063
  {
2064
2064
  "path": ["bill", "create"],
2065
2065
  "legacyAliases": ["create-bill", "create_bill"],
2066
- "summary": "Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.",
2066
+ "summary": "Create a bill or claim with an approved supplier. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.",
2067
2067
  "globalOptions": [
2068
2068
  {
2069
2069
  "name": "--help",
@@ -2237,7 +2237,7 @@
2237
2237
  {
2238
2238
  "path": ["bill", "update"],
2239
2239
  "legacyAliases": ["update-bill", "update_bill"],
2240
- "summary": "Update Bill.",
2240
+ "summary": "Update a bill or claim; payload must include id. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.",
2241
2241
  "globalOptions": [
2242
2242
  {
2243
2243
  "name": "--help",
@@ -347,7 +347,7 @@ Effects: state-change, email.
347
347
 
348
348
  ## `bb bill create`
349
349
 
350
- Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.
350
+ Create a bill or claim with an approved supplier. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.
351
351
 
352
352
  Legacy aliases: `create-bill`, `create_bill`.
353
353
 
@@ -371,7 +371,7 @@ Effects: state-change, financial-write.
371
371
 
372
372
  ## `bb bill update`
373
373
 
374
- Update Bill.
374
+ Update a bill or claim; payload must include id. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.
375
375
 
376
376
  Legacy aliases: `update-bill`, `update_bill`.
377
377
 
package/dist/index.js CHANGED
@@ -20166,8 +20166,8 @@ var parseQuotationReferences = (input2, commandName) => {
20166
20166
  const label = `${commandName} payload.quotationAllocations[${index}]`;
20167
20167
  const allocation = requireObject(raw, label);
20168
20168
  const amount = Number(allocation.amount);
20169
- if (!Number.isFinite(amount) || amount <= 0 || !Number.isInteger(amount * 100)) {
20170
- throw new Error(`${label}.amount must be a positive cent value.`);
20169
+ if (!Number.isFinite(amount) || amount < 0 || !Number.isInteger(amount * 100)) {
20170
+ throw new Error(`${label}.amount must be a non-negative cent value.`);
20171
20171
  }
20172
20172
  return {
20173
20173
  budgetItemId: requiredString(allocation.budgetItemId, `${label}.budgetItemId`),
@@ -20261,6 +20261,9 @@ var validateExplicitBillAllocations = ({
20261
20261
  throw new Error(`${commandName} payload.quotationAllocations references an unselected budget item.`);
20262
20262
  }
20263
20263
  allocatedById.set(allocation.budgetItemId, (allocatedById.get(allocation.budgetItemId) ?? 0) + cents(allocation.amount));
20264
+ if (cents(allocation.amount) === 0 && lineAmountById.get(allocation.budgetItemId) !== 0) {
20265
+ throw new Error(`${commandName} zero quotation allocations are only valid for zero-value bill lines.`);
20266
+ }
20264
20267
  }
20265
20268
  for (const [budgetItemId, allocated] of allocatedById) {
20266
20269
  if (allocated > (lineAmountById.get(budgetItemId) ?? 0)) {
@@ -23441,7 +23444,10 @@ var summaryFor = (legacyTarget) => {
23441
23444
  if (legacyTarget === "whoami")
23442
23445
  return "Show the active API-key identity.";
23443
23446
  if (legacyTarget === "create-bill") {
23444
- return "Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.";
23447
+ return "Create a bill or claim with an approved supplier. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.";
23448
+ }
23449
+ if (legacyTarget === "update-bill") {
23450
+ return "Update a bill or claim; payload must include id. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.";
23445
23451
  }
23446
23452
  if (legacyTarget === "validate-bill-selection") {
23447
23453
  return "Validate the approved supplier and selected bill line items.";
@@ -24131,6 +24137,8 @@ Bills
24131
24137
  list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
24132
24138
  isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
24133
24139
  Create supplier bills from approved quotations from the same supplier and project. Use quotationAllocations plus lineAmounts when quotations split one bill line. Eligible exemptions require explicit --quotation-exempt intent; claims do not consume or link quotations.
24140
+ Amounts for create/update (including drafts) exclude GST: amount = portion billed; extractedAmount = full invoice subtotal; lineAmounts[].amount and quotationAllocations[].amount also exclude GST. The CLI does not strip GST from input.
24141
+ Example: invoice subtotal 1800 + GST 162 = total 1962 -> amount: 1800, extractedAmount: 1800, allocations totaling 1800. BB calculates applicable GST separately. Re-read and verify both amount fields after writing.
24134
24142
  create-bill-approval <billId> (also queues approval request emails)
24135
24143
  create-bill --payload '<json>' [--draft-only] [--quotation-exempt] (bill.create; supplier bills use payload.quotationAllocations for split sources or quotationIds for disjoint coverage; explicit allocations require lineAmounts; claims reject quotation sources)
24136
24144
  validate-bill-selection --payload '<json>' [--quotationIds <csv> | --quotationId <id>] Validate an approved supplier and approved quotation coverage for each supplier-bill line. --quotationId is legacy single-quotation syntax.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "2.32.0",
3
+ "version": "2.33.1",
4
4
  "description": "Budget Builder CLI for AI agents — manage budgets, bills, claims, quotations, and customer invoices with explicit workflow previews for sensitive changes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -129,11 +129,23 @@ The following distinctions are especially important:
129
129
  - Project Hub setup and sync are asynchronous external writes. Read `bb project hub status <project-id>` first, obtain explicit approval, and request the mutation once instead of repeatedly queueing it. Setup readiness remains visible through `status`; commercial sync returns an outbox operation ID for Admin diagnostics.
130
130
  - Bill/claim status commands have separate role rules for `CHECKED`, `APPROVED`, and `PAID`.
131
131
  - `bb bill create --payload` uses `isClaimable=false`; `isClaimable=true` creates a claim. Claims intentionally permit some base-user flows that supplier bills do not.
132
- - Create supplier bills from approved quotations by passing `quotationIds` for disjoint coverage, or `quotationAllocations` plus exact `lineAmounts` when quotations split a bill line. Use `--quotation-exempt` only for an existing server-validated exemption. Claims share budget allowance but never consume or link quotations, and reject quotation source fields.
132
+ - Create supplier bills from approved quotations by passing `quotationIds` for disjoint coverage, or `quotationAllocations` plus exact `lineAmounts` when quotations split a bill line. A required zero-value bill line needs one or more `quotationAllocations` entries with `amount: 0` so its approved source(s) are retained without consuming quotation capacity. Use `--quotation-exempt` only for an existing server-validated exemption. Claims share budget allowance but never consume or link quotations, and reject quotation source fields.
133
133
  - For a draft-only bill or claim, use `bb bill create --draft-only --payload` (or `draftOnly: true` in the payload). This forces `DRAFT` for every role and requires only `--allow-state-change --allow-financial-write`. It sends no approval/email and performs no QuickBooks writes. Do not combine it with `alreadyPaid: true`. An older API without `bill.createDraft` rejects the command; never retry with ordinary creation to bypass that error.
134
134
  - Ordinary unpaid creation does not require `--allow-email`, but remains role-dependent: Accounting auto-checks and Admin auto-approves. It still requires `--allow-external-write`, including for possible placeholder adjustments. Already-paid creation retains the email gate.
135
135
  - Staged attachments belong to the user in the database. Do not reuse, share, or manually construct staged keys.
136
136
 
137
+ ## Bill amounts: always identify the subtotal before writing
138
+
139
+ For bill creation (including `--draft-only`) and updates, `amount`, `extractedAmount`, `lineAmounts[].amount`, and `quotationAllocations[].amount` all **exclude GST**. The CLI does not strip GST from a supplied total. BB calculates applicable GST separately from the supplier and line settings.
140
+
141
+ - `amount` is the portion being billed now; `extractedAmount` is the full invoice subtotal before GST. They are equal for a full bill and may differ for a deposit or partial bill.
142
+ - Example: an invoice for **S$1,800 + S$162 GST = S$1,962** requires `amount: 1800` and `extractedAmount: 1800` for a full bill, with line/quotation allocations totaling `1800`. Entering `1962` as the bill amount charges GST again.
143
+ - Read the attachment's actual subtotal and tax breakdown. Do not assume every invoice includes 9% GST or blindly divide all totals by 1.09. Clarify missing or ambiguous tax information before writing.
144
+ - After creation or correction, re-read the bill and verify both amount fields and the line/quotation allocations. Correcting only `amount` can leave an incorrect `extractedAmount` suggesting a partial bill.
145
+ - Report **subtotal, GST, and total separately**. Keep the original invoice attachment as evidence; recording GST only in a comment does not correct the structured amount fields.
146
+
147
+ See [Bill amounts and GST](README.md#bill-amounts-and-gst-create-and-update) for payload and partial-bill examples.
148
+
137
149
  ## Handling authorization failures
138
150
 
139
151
  On `FORBIDDEN`: