@go-labs-sg/bb 2.30.0 → 2.31.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 +45 -1
- package/command-manifest.json +106 -0
- package/command-reference.md +16 -0
- package/dist/index.js +75 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,6 +244,38 @@ Bill creation determines its required permissions from the parsed input, for bot
|
|
|
244
244
|
|
|
245
245
|
**Contact-person estimate email:** `bb budget estimate send --payload '<json>'` calls the same `email.sendEstimateToContactPerson` procedure as the web composer. The payload requires `budgetId`, `estimateId`, optional `estimateDocNumber`, `to`, `cc`, `replyTo`, `subject`, HTML `content`, and HTML `signature`. It durably queues worker delivery of the QuickBooks PDF, standard terms, and Budget Builder budget attachments, and returns the outbound-email and operation IDs immediately. Worker retries reuse one provider idempotency key; the budget becomes `ESTIMATE_SENT` only after Resend accepts the email. The command requires interactive `CONFIRM`; inspect the budget, recipients, and HTML first.
|
|
246
246
|
|
|
247
|
+
### Link an existing QuickBooks invoice
|
|
248
|
+
|
|
249
|
+
**Release status:** these CLI commands are pending release. The repository package version remains `2.31.0`; that version number alone does not establish that a published CLI contains them. Check `bb help` for `customer-invoice preview-qbo` and `customer-invoice import-qbo` after the normal CLI release. This code delivery does not publish the CLI, deploy the API, or perform a production import. The backend must expose `customerInvoice.previewExistingInvoice` and `customerInvoice.linkExistingInvoice` (implemented in PR #1067).
|
|
250
|
+
|
|
251
|
+
Authenticate and run `bb auth whoami` first. Preview and import use the same project management permission as the web flow: with user access checks enabled, Admins or the project's creator, business-development owner, inside-sales owner, or project manager can act. A finance/Lead role alone does not grant access to an unrelated project. The server applies its current access-check configuration and rechecks permission during confirmation.
|
|
252
|
+
|
|
253
|
+
Use the **BB budget ID** whose existing estimate should receive the invoice, and the numeric **QBO invoice transaction ID**, not the displayed invoice number or an estimate number. Accepted and closed estimates are supported; linking does not reopen them. Fully invoiced means all estimate coverage has been billed; it does not mean the invoices are fully paid.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
bb customer-invoice preview-qbo <budgetId> <qboInvoiceId>
|
|
257
|
+
bb customer-invoice import-qbo <budgetId> <qboInvoiceId> --token <previewToken>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Preview performs read-only QBO access and returns JSON under `data`, including `token`, project/estimate, invoice number/identity, currency, dates, total, outstanding `balance`, allocation `percentage`, lines, `alreadyLinked`, and other unlinked invoices. Review all of it before confirming. Import requires the exact `data.token` from that preview; it does not silently generate a fresh token. In an interactive terminal, confirm the named invoice and budget when prompted. For an explicitly authorized non-interactive import:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
bb customer-invoice import-qbo <budgetId> <qboInvoiceId> \
|
|
264
|
+
--token <previewToken> --allow-state-change --allow-financial-write
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Import writes BB financial records only and needs neither `--allow-external-write` nor `--allow-email`. It calls the same server validation as the UI: project/customer/currency/estimate/allocation checks, sibling invoice coverage, and financial drift checks. It preserves the original invoice evidence and does not create, update, void, delete, send, or match anything in QBO. A successful response returns `batchId`, `invoiceId` (the new BB invoice ID), and `alreadyLinked: false`. Repeating the same link returns the original IDs with `alreadyLinked: true`, provided upstream financial evidence still agrees. A conflicting budget, ambiguous allocation, or financial mismatch fails; a stale preview requires another reviewed preview. Do not retry a conflict with a different budget to bypass it.
|
|
268
|
+
|
|
269
|
+
To refresh payments, use the returned **BB invoice ID**:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
bb customer-invoice sync <invoiceId>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
For `QBO_IMPORT` invoices, this existing command reads QBO and updates BB balance, payment status, and sync evidence only. Settlement becomes `PAID`; a reversal restores `APPROVED`; original invoice evidence is retained. Financial drift is reported separately as `qboFinancialDrift` and blocks further billing. The shared sync command also supports native invoices, so its generic automation permissions remain `--allow-state-change --allow-financial-write --allow-external-write`; imported records still take the server's BB-only path. Native creation continues to use `bb customer-invoice create --payload '<json>'` and needs no import token or fields.
|
|
276
|
+
|
|
277
|
+
The Seatrium example must be previewed against live QBO again before an operator imports it. Previously observed invoice numbers and paid/open balances are not authorization or current-state evidence.
|
|
278
|
+
|
|
247
279
|
**Customer-invoice workflow parity:** `bb customer-invoice list` uses the same global/project list procedure and metrics as the web pages; omit filters for the global list or use `--projectId` for project scope. `bb customer-invoice send --payload '<json>'` uses the same protected email workflow as the web composer, sends to the payload's `to` address, saves that address as the project's configured billing email after a successful send, and marks the invoice `SENT`. `bb customer-invoice approve` and `bb customer-invoice reject` are Admin-only batch operations: callers select a batch ID, and the API resolves its internal pending Admin approval record. `bb customer-invoice sync` follows QuickBooks' paid state and zero balance, restoring `SENT` or `APPROVED` if that payment is reversed. Invoice approval refuses voided invoices and closes the estimate only when approved invoice coverage totals 100%; deletion, voiding, rejection, expiry, and QBO synchronization use the same estimate-reopening and live-payment guards as the web app.
|
|
248
280
|
|
|
249
281
|
For agent-driven invoice work, use this read-before-write sequence:
|
|
@@ -285,7 +317,7 @@ Bill, claim, and quotation mutations accept only suppliers whose approval status
|
|
|
285
317
|
| **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`) |
|
|
286
318
|
| **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`; validates the project and supplier separately, then checks approved quotation links for each selected budget line item independent of the bill supplier; `alreadyPaid` never bypasses the checks), `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, `isClaimable=true` for a claim; 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; non-legacy supplier bills from 1 Jul 2026 00:00 SGT require approved quotation coverage linked to every selected line item, independent of the bill supplier, even when already paid), `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` |
|
|
287
319
|
| **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`) |
|
|
288
|
-
| **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics), `get-customer-invoice`, `get-customer-invoice-email-context`, `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` |
|
|
320
|
+
| **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` |
|
|
289
321
|
| **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` |
|
|
290
322
|
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project` (its budget overview returns `totalRevenue`, calculated only from Estimate Accepted and Estimate Closed budgets), `get-project-hub-status`, `setup-project-hub`, `sync-project-hub-commercial-documents`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--startDate` as ISO datetime for project/window start; when `--asanaTaskId` is omitted, the CLI searches open Asana lead tasks in the Deals project, prompts for one of the top five matches, and resolves Slack channel fields from the selected deal card; optional `--asanaSearch`, `--pax`, `--endDate`, `--description`, `--projectManagerId`; always requests QBO project import like the web app), `update-project` (`--payload` with `dateRange.from` / `dateRange.to` for the project window; optional `projectManagerId` and `requestQboAccountantNotification` in JSON), `delete-project`, `check-project-reconciliation <id>` (runs the web app's live checks without changing status), `reconcile-project <id>` (reruns checks transactionally and marks an eligible project `RECONCILED`), `complete-project <id>` (reruns validation, marks a reconciled project `COMPLETED`, and queues QuickBooks placeholder cleanup), `import-qbo-project`, `update-project-status` (`<id>` `<status>`: `PITCH` \| `WON` \| `COMPLETED` \| `RECONCILED` \| `LOST`; close-out follows `WON` → `RECONCILED` → `COMPLETED`, and reconciliation requires every accepted/closed budget line to have an Approved/Paid bill or claim or be explicitly Not Utilized; marking `COMPLETED` queues deletion of every remaining project placeholder bill from QuickBooks; for `PITCH` → `WON` also pass a Budget Builder user `--projectManagerId` or `--projectManagerEmail`; when marking `WON` without an accepted/closed budget or proof, pass `--wonOverrideReason`) |
|
|
291
323
|
| **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
|
|
@@ -349,3 +381,15 @@ The list also accepts `PARTIALLY_SENT`, `PARTIALLY_PAID`, `PAID`, and `VOIDED`,
|
|
|
349
381
|
alongside existing approval states such as `APPROVED` and `PENDING_APPROVAL`.
|
|
350
382
|
`--sortBy status` sorts by lifecycle; `lifecycleStatus` in list/detail output is
|
|
351
383
|
separate from the stored approval/operational `status`.
|
|
384
|
+
|
|
385
|
+
### Shared allowance decisions
|
|
386
|
+
|
|
387
|
+
Quotation approval/submission and bill approval/status commands accept an optional
|
|
388
|
+
`--overBudgetOverrideReason "<reason>"`. Bill create/update JSON payloads accept
|
|
389
|
+
`overBudgetOverrideReason`. Only admins may override an assessed overrun; missing
|
|
390
|
+
relationships or allocations block approval and require review. Multi-line quotation
|
|
391
|
+
create/update payloads must include `lineAmounts` with `{ budgetItemId, amount }`
|
|
392
|
+
for every selected line, summing to `amountWithoutGst`. The server revalidates
|
|
393
|
+
existing quotation links, reviewed usage, and per-line allowance inside the
|
|
394
|
+
approval transaction. No new database fields or relationship declarations are
|
|
395
|
+
required; unresolved historical overlaps continue to block approval.
|
package/command-manifest.json
CHANGED
|
@@ -3643,6 +3643,112 @@
|
|
|
3643
3643
|
"effects": [],
|
|
3644
3644
|
"legacyTarget": "get-customer-invoice-email-context"
|
|
3645
3645
|
},
|
|
3646
|
+
{
|
|
3647
|
+
"path": ["customer-invoice", "preview-qbo"],
|
|
3648
|
+
"legacyAliases": [
|
|
3649
|
+
"preview-qbo-customer-invoice",
|
|
3650
|
+
"preview_qbo_customer_invoice"
|
|
3651
|
+
],
|
|
3652
|
+
"summary": "Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.",
|
|
3653
|
+
"globalOptions": [
|
|
3654
|
+
{
|
|
3655
|
+
"name": "--help",
|
|
3656
|
+
"description": "Show help for this command."
|
|
3657
|
+
},
|
|
3658
|
+
{
|
|
3659
|
+
"name": "--quiet",
|
|
3660
|
+
"description": "Suppress non-error diagnostics."
|
|
3661
|
+
},
|
|
3662
|
+
{
|
|
3663
|
+
"name": "--debug",
|
|
3664
|
+
"description": "Emit sanitized diagnostic traces."
|
|
3665
|
+
},
|
|
3666
|
+
{
|
|
3667
|
+
"name": "--api-url",
|
|
3668
|
+
"description": "Override the Budget Builder API base URL."
|
|
3669
|
+
},
|
|
3670
|
+
{
|
|
3671
|
+
"name": "--no-browser",
|
|
3672
|
+
"description": "Print the authorization URL without opening a browser."
|
|
3673
|
+
},
|
|
3674
|
+
{
|
|
3675
|
+
"name": "--allow-state-change",
|
|
3676
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
3677
|
+
},
|
|
3678
|
+
{
|
|
3679
|
+
"name": "--allow-email",
|
|
3680
|
+
"description": "Allow sending email in non-interactive use."
|
|
3681
|
+
},
|
|
3682
|
+
{
|
|
3683
|
+
"name": "--allow-external-write",
|
|
3684
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
"name": "--allow-delete",
|
|
3688
|
+
"description": "Allow deleting data in non-interactive use."
|
|
3689
|
+
},
|
|
3690
|
+
{
|
|
3691
|
+
"name": "--allow-financial-write",
|
|
3692
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
3693
|
+
}
|
|
3694
|
+
],
|
|
3695
|
+
"argumentMode": "legacy-passthrough",
|
|
3696
|
+
"effects": [],
|
|
3697
|
+
"legacyTarget": "preview-qbo-customer-invoice"
|
|
3698
|
+
},
|
|
3699
|
+
{
|
|
3700
|
+
"path": ["customer-invoice", "import-qbo"],
|
|
3701
|
+
"legacyAliases": [
|
|
3702
|
+
"import-qbo-customer-invoice",
|
|
3703
|
+
"import_qbo_customer_invoice"
|
|
3704
|
+
],
|
|
3705
|
+
"summary": "Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.",
|
|
3706
|
+
"globalOptions": [
|
|
3707
|
+
{
|
|
3708
|
+
"name": "--help",
|
|
3709
|
+
"description": "Show help for this command."
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
"name": "--quiet",
|
|
3713
|
+
"description": "Suppress non-error diagnostics."
|
|
3714
|
+
},
|
|
3715
|
+
{
|
|
3716
|
+
"name": "--debug",
|
|
3717
|
+
"description": "Emit sanitized diagnostic traces."
|
|
3718
|
+
},
|
|
3719
|
+
{
|
|
3720
|
+
"name": "--api-url",
|
|
3721
|
+
"description": "Override the Budget Builder API base URL."
|
|
3722
|
+
},
|
|
3723
|
+
{
|
|
3724
|
+
"name": "--no-browser",
|
|
3725
|
+
"description": "Print the authorization URL without opening a browser."
|
|
3726
|
+
},
|
|
3727
|
+
{
|
|
3728
|
+
"name": "--allow-state-change",
|
|
3729
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
3730
|
+
},
|
|
3731
|
+
{
|
|
3732
|
+
"name": "--allow-email",
|
|
3733
|
+
"description": "Allow sending email in non-interactive use."
|
|
3734
|
+
},
|
|
3735
|
+
{
|
|
3736
|
+
"name": "--allow-external-write",
|
|
3737
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
3738
|
+
},
|
|
3739
|
+
{
|
|
3740
|
+
"name": "--allow-delete",
|
|
3741
|
+
"description": "Allow deleting data in non-interactive use."
|
|
3742
|
+
},
|
|
3743
|
+
{
|
|
3744
|
+
"name": "--allow-financial-write",
|
|
3745
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
3746
|
+
}
|
|
3747
|
+
],
|
|
3748
|
+
"argumentMode": "legacy-passthrough",
|
|
3749
|
+
"effects": ["state-change", "financial-write"],
|
|
3750
|
+
"legacyTarget": "import-qbo-customer-invoice"
|
|
3751
|
+
},
|
|
3646
3752
|
{
|
|
3647
3753
|
"path": ["customer-invoice", "create"],
|
|
3648
3754
|
"legacyAliases": ["create-customer-invoice", "create_customer_invoice"],
|
package/command-reference.md
CHANGED
|
@@ -593,6 +593,22 @@ Legacy aliases: `get-customer-invoice-email-context`, `get_customer_invoice_emai
|
|
|
593
593
|
|
|
594
594
|
Effects: none.
|
|
595
595
|
|
|
596
|
+
## `bb customer-invoice preview-qbo`
|
|
597
|
+
|
|
598
|
+
Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.
|
|
599
|
+
|
|
600
|
+
Legacy aliases: `preview-qbo-customer-invoice`, `preview_qbo_customer_invoice`.
|
|
601
|
+
|
|
602
|
+
Effects: none.
|
|
603
|
+
|
|
604
|
+
## `bb customer-invoice import-qbo`
|
|
605
|
+
|
|
606
|
+
Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.
|
|
607
|
+
|
|
608
|
+
Legacy aliases: `import-qbo-customer-invoice`, `import_qbo_customer_invoice`.
|
|
609
|
+
|
|
610
|
+
Effects: state-change, financial-write.
|
|
611
|
+
|
|
596
612
|
## `bb customer-invoice create`
|
|
597
613
|
|
|
598
614
|
Create Customer Invoice.
|
package/dist/index.js
CHANGED
|
@@ -21326,7 +21326,7 @@ async function listBills(opts) {
|
|
|
21326
21326
|
const result = await api2.bill.getAll.query(input2);
|
|
21327
21327
|
out(result);
|
|
21328
21328
|
}
|
|
21329
|
-
async function approveBill(billId) {
|
|
21329
|
+
async function approveBill(billId, overBudgetOverrideReason) {
|
|
21330
21330
|
await assertSensitiveWorkflowConfirmed({
|
|
21331
21331
|
action: "Approve bill",
|
|
21332
21332
|
entity: `bill ${billId}`,
|
|
@@ -21340,7 +21340,8 @@ async function approveBill(billId) {
|
|
|
21340
21340
|
}
|
|
21341
21341
|
const result = await api2.bill.updateApproval.mutate({
|
|
21342
21342
|
billApprovalId: approval.id,
|
|
21343
|
-
status: "APPROVED"
|
|
21343
|
+
status: "APPROVED",
|
|
21344
|
+
overBudgetOverrideReason
|
|
21344
21345
|
});
|
|
21345
21346
|
if (result.processing) {
|
|
21346
21347
|
out(result);
|
|
@@ -21519,7 +21520,8 @@ async function updateBillStatus(opts) {
|
|
|
21519
21520
|
rejectionReason: opts.rejectionReason,
|
|
21520
21521
|
paymentTrackingUrl: opts.paymentTrackingUrl,
|
|
21521
21522
|
paymentReference: opts.paymentReference,
|
|
21522
|
-
paymentProofAttachments: paymentProof ? [paymentProof] : undefined
|
|
21523
|
+
paymentProofAttachments: paymentProof ? [paymentProof] : undefined,
|
|
21524
|
+
overBudgetOverrideReason: opts.overBudgetOverrideReason
|
|
21523
21525
|
});
|
|
21524
21526
|
out(result);
|
|
21525
21527
|
} catch (error61) {
|
|
@@ -22088,12 +22090,15 @@ var deleteQuotationById = async (id) => {
|
|
|
22088
22090
|
const result = await api2.quotation.delete.mutate({ id });
|
|
22089
22091
|
out(result);
|
|
22090
22092
|
};
|
|
22091
|
-
async function submitQuotation(id) {
|
|
22093
|
+
async function submitQuotation(id, overBudgetOverrideReason) {
|
|
22092
22094
|
await assertSensitiveWorkflowConfirmed({
|
|
22093
22095
|
action: "Submit quotation for approval",
|
|
22094
22096
|
entity: `quotation ${id}`
|
|
22095
22097
|
});
|
|
22096
|
-
const result = await api2.quotation.submitForApproval.mutate({
|
|
22098
|
+
const result = await api2.quotation.submitForApproval.mutate({
|
|
22099
|
+
id,
|
|
22100
|
+
overBudgetOverrideReason
|
|
22101
|
+
});
|
|
22097
22102
|
out(result);
|
|
22098
22103
|
}
|
|
22099
22104
|
async function downloadQuotationPdf(id, fileRole) {
|
|
@@ -22129,6 +22134,19 @@ var getCustomerInvoiceEmailContext = async (batchId) => {
|
|
|
22129
22134
|
});
|
|
22130
22135
|
out(result);
|
|
22131
22136
|
};
|
|
22137
|
+
var previewQboCustomerInvoice = async (input2) => {
|
|
22138
|
+
const result = await api2.customerInvoice.previewExistingInvoice.mutate(input2);
|
|
22139
|
+
out(result);
|
|
22140
|
+
};
|
|
22141
|
+
var importQboCustomerInvoice = async (input2) => {
|
|
22142
|
+
await confirmCurrentCommand({
|
|
22143
|
+
action: "Link existing QuickBooks invoice",
|
|
22144
|
+
target: `QuickBooks invoice ${input2.qboInvoiceId} to budget ${input2.budgetId}`,
|
|
22145
|
+
details: "records the reviewed invoice in Budget Builder only; QuickBooks access is read-only; preserves original evidence and revalidates the preview token"
|
|
22146
|
+
});
|
|
22147
|
+
const result = await api2.customerInvoice.linkExistingInvoice.mutate(input2);
|
|
22148
|
+
out(result);
|
|
22149
|
+
};
|
|
22132
22150
|
var createCustomerInvoice = async (raw) => {
|
|
22133
22151
|
const input2 = parseCreateCustomerInvoicePayload(raw);
|
|
22134
22152
|
await assertSensitiveWorkflowConfirmed({
|
|
@@ -22523,7 +22541,7 @@ async function rejectBill(billId, reason) {
|
|
|
22523
22541
|
email: email3
|
|
22524
22542
|
});
|
|
22525
22543
|
}
|
|
22526
|
-
async function approveQuotation(quotationId) {
|
|
22544
|
+
async function approveQuotation(quotationId, overBudgetOverrideReason) {
|
|
22527
22545
|
await assertSensitiveWorkflowConfirmed({
|
|
22528
22546
|
action: "Approve quotation",
|
|
22529
22547
|
entity: `quotation ${quotationId}`
|
|
@@ -22535,7 +22553,8 @@ async function approveQuotation(quotationId) {
|
|
|
22535
22553
|
throw new Error("No pending quotation approval found.");
|
|
22536
22554
|
}
|
|
22537
22555
|
const result = await api2.quotation.approve.mutate({
|
|
22538
|
-
quotationApprovalId: approval.id
|
|
22556
|
+
quotationApprovalId: approval.id,
|
|
22557
|
+
overBudgetOverrideReason
|
|
22539
22558
|
});
|
|
22540
22559
|
out(result);
|
|
22541
22560
|
}
|
|
@@ -23160,6 +23179,7 @@ var externalWriteTargets = new Set([
|
|
|
23160
23179
|
"void-customer-invoice"
|
|
23161
23180
|
]);
|
|
23162
23181
|
var financialWriteTargets = new Set([
|
|
23182
|
+
"import-qbo-customer-invoice",
|
|
23163
23183
|
"approve-bill",
|
|
23164
23184
|
"approve-customer-invoice",
|
|
23165
23185
|
"complete-project",
|
|
@@ -23217,6 +23237,12 @@ var effectsFor = (legacyTarget) => {
|
|
|
23217
23237
|
};
|
|
23218
23238
|
var titleCase = (value) => value.split("-").map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join(" ");
|
|
23219
23239
|
var summaryFor = (legacyTarget) => {
|
|
23240
|
+
if (legacyTarget === "preview-qbo-customer-invoice") {
|
|
23241
|
+
return "Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.";
|
|
23242
|
+
}
|
|
23243
|
+
if (legacyTarget === "import-qbo-customer-invoice") {
|
|
23244
|
+
return "Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.";
|
|
23245
|
+
}
|
|
23220
23246
|
if (legacyTarget === "whoami")
|
|
23221
23247
|
return "Show the active API-key identity.";
|
|
23222
23248
|
if (legacyTarget === "create-bill") {
|
|
@@ -23402,6 +23428,14 @@ var registry2 = [
|
|
|
23402
23428
|
"email-context",
|
|
23403
23429
|
"get"
|
|
23404
23430
|
]),
|
|
23431
|
+
legacyCommand("preview-qbo-customer-invoice", [
|
|
23432
|
+
"customer-invoice",
|
|
23433
|
+
"preview-qbo"
|
|
23434
|
+
]),
|
|
23435
|
+
legacyCommand("import-qbo-customer-invoice", [
|
|
23436
|
+
"customer-invoice",
|
|
23437
|
+
"import-qbo"
|
|
23438
|
+
]),
|
|
23405
23439
|
legacyCommand("create-customer-invoice", ["customer-invoice", "create"]),
|
|
23406
23440
|
legacyCommand("discard-customer-invoice", ["customer-invoice", "discard"]),
|
|
23407
23441
|
legacyCommand("delete-customer-invoice", ["customer-invoice", "delete"]),
|
|
@@ -23711,6 +23745,7 @@ function parseOptionalBoolFlag(flags, key) {
|
|
|
23711
23745
|
var CUSTOMER_INVOICE_SORT_FIELDS = [
|
|
23712
23746
|
"createdAt",
|
|
23713
23747
|
"status",
|
|
23748
|
+
"totalInvoiceAmount",
|
|
23714
23749
|
"totalNetAmount",
|
|
23715
23750
|
"approvalExpiresAt"
|
|
23716
23751
|
];
|
|
@@ -23886,7 +23921,7 @@ Bills
|
|
|
23886
23921
|
update-bill --payload '<json>' (bill.update; must include id)
|
|
23887
23922
|
update-bill-payment-evidence --payload '<json>' Replace paymentReference/paymentProofAttachments for an already-paid bill.
|
|
23888
23923
|
delete-bill <billId>
|
|
23889
|
-
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference] [--paymentProof <receipt.pdf>]
|
|
23924
|
+
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference] [--paymentProof <receipt.pdf>] [--overBudgetOverrideReason <text>]
|
|
23890
23925
|
status (${billStatusesForUpdateHelp.join(", ")}) \u2014 not PENDING_APPROVAL; use create-bill-approval
|
|
23891
23926
|
PAID requires --paymentReference and a payment-proof PDF. Use --paymentProof to stage and submit the PDF atomically, or omit it only when BB already has a BILL_PAYMENT_PROOF attachment.
|
|
23892
23927
|
Moving a bill or claim to PAID runs the server's paid-bill notification workflow.
|
|
@@ -23908,18 +23943,20 @@ Quotations
|
|
|
23908
23943
|
create-quotation --payload '<json>' (quotation.createDraft; requires amountWithoutGst and gstAmount)
|
|
23909
23944
|
update-quotation --payload '<json>' Update a DRAFT or REJECTED quotation; same payload plus id.
|
|
23910
23945
|
delete-quotation <quotationId>
|
|
23911
|
-
submit-quotation <quotationId>
|
|
23912
|
-
approve-quotation <quotationId>
|
|
23946
|
+
submit-quotation <quotationId> [--overBudgetOverrideReason <text>]
|
|
23947
|
+
approve-quotation <quotationId> [--overBudgetOverrideReason <text>]
|
|
23913
23948
|
reject-quotation <quotationId> --reason <text>
|
|
23914
23949
|
download-quotation-pdf <quotationId> [original|staff|final]
|
|
23915
23950
|
|
|
23916
23951
|
Customer invoices
|
|
23917
23952
|
check-customer-invoice-readiness <budgetId>
|
|
23918
23953
|
list-eligible-customer-invoice-budgets <projectId> Accepted budgets that are ready and still have invoice coverage remaining.
|
|
23919
|
-
list-customer-invoices [budgetId] [--projectId <id>] [--companyIds CSV] [--projectIds CSV] [--budgetIds CSV] [--createdByIds CSV] [--status CSV] [--search] [--sortBy createdAt|status|
|
|
23954
|
+
list-customer-invoices [budgetId] [--projectId <id>] [--companyIds CSV] [--projectIds CSV] [--budgetIds CSV] [--createdByIds CSV] [--status CSV] [--search] [--sortBy createdAt|status|totalInvoiceAmount|approvalExpiresAt] [--sortDir asc|desc] [--page] [--perPage]
|
|
23920
23955
|
No scope flag lists invoices globally. --projectId uses the project scope. A positional budgetId is a backward-compatible shortcut for --budgetIds.
|
|
23921
23956
|
get-customer-invoice <batchId>
|
|
23922
23957
|
get-customer-invoice-email-context <batchId> Contact, required CC, budget, and project context for composing an invoice email.
|
|
23958
|
+
preview-qbo-customer-invoice <budgetId> <qboInvoiceId> Preview linking an existing QBO transaction (numeric ID, not invoice number); works on accepted/closed estimates.
|
|
23959
|
+
import-qbo-customer-invoice <budgetId> <qboInvoiceId> --token <previewToken> Confirm the reviewed BB-only link; requires --allow-state-change --allow-financial-write in automation.
|
|
23923
23960
|
create-customer-invoice --payload '<json>' budgetId + one split with label, percentage, and dueDate.
|
|
23924
23961
|
discard-customer-invoice <batchId> Discard a reserved CREATING batch with no created QBO invoices.
|
|
23925
23962
|
delete-customer-invoice <batchId> Delete QBO invoices and the local batch.
|
|
@@ -23932,7 +23969,7 @@ Customer invoices
|
|
|
23932
23969
|
|
|
23933
23970
|
Approvals
|
|
23934
23971
|
list-approvals | get-pending-approvals [--type budget|supplier|bill|quotation|customer_invoice|all]
|
|
23935
|
-
approve-bill <billId>
|
|
23972
|
+
approve-bill <billId> [--overBudgetOverrideReason <text>] (also sends requester reply email)
|
|
23936
23973
|
reject-bill <billId> --reason <text> (also sends requester reply email)
|
|
23937
23974
|
approve-budget <budgetId> (also sends requester reply email)
|
|
23938
23975
|
reject-budget <budgetId> --reason <text> (also sends requester reply email)
|
|
@@ -24364,7 +24401,7 @@ Waiting for approval\u2026
|
|
|
24364
24401
|
}
|
|
24365
24402
|
configureAccessToken(accessToken);
|
|
24366
24403
|
}
|
|
24367
|
-
if (!resolvedCommand.isLegacyAlias && cmd !== "create-bill" && resolvedCommand.command.effects.length > 0) {
|
|
24404
|
+
if (!resolvedCommand.isLegacyAlias && cmd !== "create-bill" && cmd !== "import-qbo-customer-invoice" && resolvedCommand.command.effects.length > 0) {
|
|
24368
24405
|
await confirmCurrentCommand({
|
|
24369
24406
|
action: resolvedCommand.command.path.join(" "),
|
|
24370
24407
|
target: commandConfirmationTarget(resolvedCommand),
|
|
@@ -24785,7 +24822,8 @@ Waiting for approval\u2026
|
|
|
24785
24822
|
rejectionReason: getFlag(flags, "rejectionReason"),
|
|
24786
24823
|
paymentTrackingUrl: getFlag(flags, "paymentTrackingUrl"),
|
|
24787
24824
|
paymentReference: getFlag(flags, "paymentReference"),
|
|
24788
|
-
paymentProofPath: paymentProofFlag
|
|
24825
|
+
paymentProofPath: paymentProofFlag,
|
|
24826
|
+
overBudgetOverrideReason: getFlag(flags, "overBudgetOverrideReason")
|
|
24789
24827
|
});
|
|
24790
24828
|
break;
|
|
24791
24829
|
}
|
|
@@ -25023,14 +25061,14 @@ Waiting for approval\u2026
|
|
|
25023
25061
|
const id = positional[0];
|
|
25024
25062
|
if (!id)
|
|
25025
25063
|
throw new Error("submit-quotation requires <quotationId>");
|
|
25026
|
-
await submitQuotation(id);
|
|
25064
|
+
await submitQuotation(id, getFlag(flags, "overBudgetOverrideReason"));
|
|
25027
25065
|
break;
|
|
25028
25066
|
}
|
|
25029
25067
|
case "approve-quotation": {
|
|
25030
25068
|
const id = positional[0];
|
|
25031
25069
|
if (!id)
|
|
25032
25070
|
throw new Error("approve-quotation requires <quotationId>");
|
|
25033
|
-
await approveQuotation(id);
|
|
25071
|
+
await approveQuotation(id, getFlag(flags, "overBudgetOverrideReason"));
|
|
25034
25072
|
break;
|
|
25035
25073
|
}
|
|
25036
25074
|
case "reject-quotation": {
|
|
@@ -25115,6 +25153,26 @@ Waiting for approval\u2026
|
|
|
25115
25153
|
await getCustomerInvoiceEmailContext(batchId);
|
|
25116
25154
|
break;
|
|
25117
25155
|
}
|
|
25156
|
+
case "preview-qbo-customer-invoice":
|
|
25157
|
+
case "import-qbo-customer-invoice": {
|
|
25158
|
+
const [budgetId, qboInvoiceId] = positional;
|
|
25159
|
+
if (!budgetId?.trim() || !qboInvoiceId || positional.length !== 2) {
|
|
25160
|
+
throw new Error(`${cmd} requires <budgetId> <qboInvoiceId>`);
|
|
25161
|
+
}
|
|
25162
|
+
if (!/^\d+$/.test(qboInvoiceId)) {
|
|
25163
|
+
throw new Error("qboInvoiceId must be the numeric QuickBooks transaction ID, not the invoice number");
|
|
25164
|
+
}
|
|
25165
|
+
if (cmd === "preview-qbo-customer-invoice") {
|
|
25166
|
+
await previewQboCustomerInvoice({ budgetId, qboInvoiceId });
|
|
25167
|
+
} else {
|
|
25168
|
+
const token = getFlag(flags, "token");
|
|
25169
|
+
if (!token || !/^[a-f0-9]{64}$/.test(token)) {
|
|
25170
|
+
throw new Error("import-qbo-customer-invoice requires --token <64-character preview token> from a reviewed preview-qbo result");
|
|
25171
|
+
}
|
|
25172
|
+
await importQboCustomerInvoice({ budgetId, qboInvoiceId, token });
|
|
25173
|
+
}
|
|
25174
|
+
break;
|
|
25175
|
+
}
|
|
25118
25176
|
case "create-customer-invoice": {
|
|
25119
25177
|
const payloadRaw = getFlag(flags, "payload");
|
|
25120
25178
|
if (!payloadRaw) {
|
|
@@ -25200,7 +25258,7 @@ Waiting for approval\u2026
|
|
|
25200
25258
|
const id = positional[0];
|
|
25201
25259
|
if (!id)
|
|
25202
25260
|
throw new Error("approve-bill requires <billId>");
|
|
25203
|
-
await approveBill(id);
|
|
25261
|
+
await approveBill(id, getFlag(flags, "overBudgetOverrideReason"));
|
|
25204
25262
|
break;
|
|
25205
25263
|
}
|
|
25206
25264
|
case "reject-bill": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.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",
|