@go-labs-sg/bb 2.17.0 → 2.19.0
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 +7 -5
- package/command-manifest.json +96 -1
- package/command-reference.md +17 -1
- package/dist/commands.js +60 -60
- package/dist/index.js +39 -10
- package/dist/parse-mutation-payload.js +7 -0
- package/dist/registry/index.js +12 -0
- package/package.json +1 -1
- package/role-aware-agent-guide.md +3 -1
package/README.md
CHANGED
|
@@ -193,11 +193,13 @@ Every canonical command is classified by effect: `state-change`, `email`, `exter
|
|
|
193
193
|
|
|
194
194
|
**Locked estimate budget changes:** Legacy flat commands retain their additional interactive `yes` prompt when the current budget is locked. Canonical v2 commands use the single effect-aware confirmation gate; non-interactive execution requires `--allow-state-change` plus any other effects declared for that command.
|
|
195
195
|
|
|
196
|
-
**Mutations with `--payload`:** Commands such as `bb budget create`, `bb bill create`, and `bb supplier update` take a single JSON object (`--payload '<json>'`) matching the corresponding tRPC procedure input. Use ISO strings for date/datetime fields; the CLI coerces them where needed. The API still validates the full shape. Plain `description` fields for item create/update are converted to `descriptionRichText`; pass `descriptionRichText` directly when formatted Tiptap JSON is required. For **`bb project update`**, the project window is `dateRange.from` and `dateRange.to` (optional end); there are no separate event-date fields on the project payload. **`bb budget create` / `bb budget update`** do not accept `asanaTaskId`; configure the deal card on the project (`bb project update` / project settings).
|
|
196
|
+
**Mutations with `--payload`:** Commands such as `bb budget create`, `bb bill create`, and `bb supplier update` take a single JSON object (`--payload '<json>'`) matching the corresponding tRPC procedure input. Use ISO strings for date/datetime fields; the CLI coerces them where needed. The API still validates the full shape. Plain `description` fields for item create/update are converted to `descriptionRichText`; pass `descriptionRichText` directly when formatted Tiptap JSON is required. For **`bb project update`**, the project window is `dateRange.from` and `dateRange.to` (optional end); there are no separate event-date fields on the project payload. **`bb budget create` / `bb budget update`** do not accept `asanaTaskId`; configure the deal card on the project (`bb project update` / project settings). When `bb budget create` clones a `sourceBudgetId` containing unavailable catalogue lines, the API preserves those snapshots for review and rejects the clone until the caller explicitly sets `unavailableItemReviewAcknowledged: true`.
|
|
197
|
+
|
|
198
|
+
**Unavailable catalogue lines:** Budget Builder-only `bb item archive --ids <csv>` may retire catalogue items referenced by open estimates. The estimate snapshots remain intact, but sending or accepting is blocked until each unavailable line is resolved with `bb budget item replace --budgetItemId <id> --replacementItemId <active-item-id>`, `bb budget item remove <id>`, or the Lead/Admin-only `bb budget item exception approve --budgetItemId <id> --reason <text>`. The approved-exception reason is audited. `bb item delete <id>` also attempts QuickBooks deactivation and therefore remains blocked while an open estimate references the item.
|
|
197
199
|
|
|
198
200
|
**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.
|
|
199
201
|
|
|
200
|
-
**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` uses the same protected email workflow as the web composer
|
|
202
|
+
**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.
|
|
201
203
|
|
|
202
204
|
For agent-driven invoice work, use this read-before-write sequence:
|
|
203
205
|
|
|
@@ -233,14 +235,14 @@ Project and budget query payloads keep the Asana identities explicit: `asanaTask
|
|
|
233
235
|
|
|
234
236
|
| Area | Legacy handler labels (reference only; non-exhaustive) |
|
|
235
237
|
| --- | --- |
|
|
236
|
-
| **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`), `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`, `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`) |
|
|
237
|
-
| **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`;
|
|
238
|
+
| **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`) |
|
|
239
|
+
| **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` |
|
|
238
240
|
| **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`) |
|
|
239
241
|
| **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` |
|
|
240
242
|
| **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` |
|
|
241
243
|
| **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`) |
|
|
242
244
|
| **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
|
|
243
|
-
| **Suppliers & items** | `list-suppliers` (defaults to active suppliers, `--perPage 10`, sorted by `createdAt` desc; supports `--name`, `--sortBy` for scalar supplier fields, `--sortDir`, `--createdBy`, `--gstRegistered`, `--status`, `--supplierTags`, `--active false` for archived suppliers), `create-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `update-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `delete-suppliers` (`--ids` CSV; admin; archives/deactivates related items), `reactivate-suppliers` (`--ids` CSV; admin; reactivates related items), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details` (includes `supplierApprovalSummary` for pending approvers, superseded approvers, and the actual responder/respondedAt metadata), `get-supplier-analytics` (admin), `list-items
|
|
245
|
+
| **Suppliers & items** | `list-suppliers` (defaults to active suppliers, `--perPage 10`, sorted by `createdAt` desc; supports `--name`, `--sortBy` for scalar supplier fields, `--sortDir`, `--createdBy`, `--gstRegistered`, `--status`, `--supplierTags`, `--active false` for archived suppliers), `create-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `update-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `delete-suppliers` (`--ids` CSV; admin; archives/deactivates related items), `reactivate-suppliers` (`--ids` CSV; admin; reactivates related items), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details` (includes `supplierApprovalSummary` for pending approvers, superseded approvers, and the actual responder/respondedAt metadata), `get-supplier-analytics` (admin), `list-items` (`--includeQuickBooksDeactivationPending true` discovers interrupted QuickBooks deactivations for Admin retry), `create-item` (`--payload`), `update-item` (`--payload`), `archive-items` (`--ids` CSV, max 100; Budget Builder-only; open estimates retain unavailable snapshot lines for resolution), `delete-item` (admin; archives in Budget Builder and makes the item inactive in QuickBooks; blocked while an open estimate references it), `get-item`, `list-item-categories`, `create-item-category` / `update-item-category` / `delete-item-categories` (admin; `--ids` CSV for delete) |
|
|
244
246
|
| **Dashboard & users** | `whoami` (current API-key owner identity and role), `list-users`, `create-user` (admin; provisions an API-only service identity with no Google sign-in; `--email`, optional `--name`, optional `--role` defaulting to `USER`), `create-api-key` (`--name`; defaults to the caller; admin-only `--userId` for another user; raw key shown once), `list-api-keys` (defaults to the caller; admin-only `--userId` for another user), `revoke-api-key` (key ID; admin-only `--userId` for another user), `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` (performance/dashboard commands are admin-only) |
|
|
245
247
|
| **Errors (admin)** | `get-recent-errors`, `get-error-metrics` |
|
|
246
248
|
| **Automation (admin)** | `list-integration-operations`, `retry-integration-operation` (Resend resolution requires `--confirmExternalStateReconciled true` plus `--outboundEmailResolution ACCEPTED\|NOT_ACCEPTED_RETRY`; include `--providerMessageId` when accepted) |
|
package/command-manifest.json
CHANGED
|
@@ -970,6 +970,101 @@
|
|
|
970
970
|
"effects": ["state-change", "delete"],
|
|
971
971
|
"legacyTarget": "remove-budget-item"
|
|
972
972
|
},
|
|
973
|
+
{
|
|
974
|
+
"path": ["budget", "item", "replace"],
|
|
975
|
+
"legacyAliases": ["replace-budget-item", "replace_budget_item"],
|
|
976
|
+
"summary": "Replace Budget Item.",
|
|
977
|
+
"globalOptions": [
|
|
978
|
+
{
|
|
979
|
+
"name": "--help",
|
|
980
|
+
"description": "Show help for this command."
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
"name": "--quiet",
|
|
984
|
+
"description": "Suppress non-error diagnostics."
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
"name": "--debug",
|
|
988
|
+
"description": "Emit sanitized diagnostic traces."
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
"name": "--api-url",
|
|
992
|
+
"description": "Override the Budget Builder API base URL."
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"name": "--allow-state-change",
|
|
996
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"name": "--allow-email",
|
|
1000
|
+
"description": "Allow sending email in non-interactive use."
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
"name": "--allow-external-write",
|
|
1004
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
"name": "--allow-delete",
|
|
1008
|
+
"description": "Allow deleting data in non-interactive use."
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
"name": "--allow-financial-write",
|
|
1012
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
1013
|
+
}
|
|
1014
|
+
],
|
|
1015
|
+
"argumentMode": "legacy-passthrough",
|
|
1016
|
+
"effects": ["state-change", "financial-write"],
|
|
1017
|
+
"legacyTarget": "replace-budget-item"
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
"path": ["budget", "item", "exception", "approve"],
|
|
1021
|
+
"legacyAliases": [
|
|
1022
|
+
"approve-unavailable-item-exception",
|
|
1023
|
+
"approve_unavailable_item_exception"
|
|
1024
|
+
],
|
|
1025
|
+
"summary": "Approve Unavailable Item Exception.",
|
|
1026
|
+
"globalOptions": [
|
|
1027
|
+
{
|
|
1028
|
+
"name": "--help",
|
|
1029
|
+
"description": "Show help for this command."
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
"name": "--quiet",
|
|
1033
|
+
"description": "Suppress non-error diagnostics."
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
"name": "--debug",
|
|
1037
|
+
"description": "Emit sanitized diagnostic traces."
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
"name": "--api-url",
|
|
1041
|
+
"description": "Override the Budget Builder API base URL."
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
"name": "--allow-state-change",
|
|
1045
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
"name": "--allow-email",
|
|
1049
|
+
"description": "Allow sending email in non-interactive use."
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
"name": "--allow-external-write",
|
|
1053
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
"name": "--allow-delete",
|
|
1057
|
+
"description": "Allow deleting data in non-interactive use."
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
"name": "--allow-financial-write",
|
|
1061
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
1062
|
+
}
|
|
1063
|
+
],
|
|
1064
|
+
"argumentMode": "legacy-passthrough",
|
|
1065
|
+
"effects": ["state-change"],
|
|
1066
|
+
"legacyTarget": "approve-unavailable-item-exception"
|
|
1067
|
+
},
|
|
973
1068
|
{
|
|
974
1069
|
"path": ["budget", "item", "reorder"],
|
|
975
1070
|
"legacyAliases": ["reorder-budget-items", "reorder_budget_items"],
|
|
@@ -3403,7 +3498,7 @@
|
|
|
3403
3498
|
"send-customer-invoice-to-contact-person",
|
|
3404
3499
|
"send_customer_invoice_to_contact_person"
|
|
3405
3500
|
],
|
|
3406
|
-
"summary": "Send
|
|
3501
|
+
"summary": "Send a customer invoice and update the project billing email.",
|
|
3407
3502
|
"globalOptions": [
|
|
3408
3503
|
{
|
|
3409
3504
|
"name": "--help",
|
package/command-reference.md
CHANGED
|
@@ -184,6 +184,22 @@ Legacy aliases: `remove-budget-item`, `remove_budget_item`.
|
|
|
184
184
|
|
|
185
185
|
Effects: state-change, delete.
|
|
186
186
|
|
|
187
|
+
## `bb budget item replace`
|
|
188
|
+
|
|
189
|
+
Replace Budget Item.
|
|
190
|
+
|
|
191
|
+
Legacy aliases: `replace-budget-item`, `replace_budget_item`.
|
|
192
|
+
|
|
193
|
+
Effects: state-change, financial-write.
|
|
194
|
+
|
|
195
|
+
## `bb budget item exception approve`
|
|
196
|
+
|
|
197
|
+
Approve Unavailable Item Exception.
|
|
198
|
+
|
|
199
|
+
Legacy aliases: `approve-unavailable-item-exception`, `approve_unavailable_item_exception`.
|
|
200
|
+
|
|
201
|
+
Effects: state-change.
|
|
202
|
+
|
|
187
203
|
## `bb budget item reorder`
|
|
188
204
|
|
|
189
205
|
Reorder Budget Items.
|
|
@@ -602,7 +618,7 @@ Effects: state-change, email, external-write, financial-write.
|
|
|
602
618
|
|
|
603
619
|
## `bb customer-invoice send`
|
|
604
620
|
|
|
605
|
-
Send
|
|
621
|
+
Send a customer invoice and update the project billing email.
|
|
606
622
|
|
|
607
623
|
Legacy aliases: `send-customer-invoice-to-contact-person`, `send_customer_invoice_to_contact_person`.
|
|
608
624
|
|
package/dist/commands.js
CHANGED
|
@@ -35,16 +35,35 @@ const getAllNotifications = async () => {
|
|
|
35
35
|
} while (cursor);
|
|
36
36
|
return notifications;
|
|
37
37
|
};
|
|
38
|
-
const createApprovalEmailDeliveryReport = (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const createApprovalEmailDeliveryReport = (delivery) => {
|
|
39
|
+
if (!delivery) {
|
|
40
|
+
return {
|
|
41
|
+
queued: false,
|
|
42
|
+
sent: false,
|
|
43
|
+
count: 0,
|
|
44
|
+
queuedCount: 0,
|
|
45
|
+
sentCount: 0,
|
|
46
|
+
skippedCount: 0,
|
|
47
|
+
error: "No approval email delivery was queued",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const { queuedCount, sentCount, skippedCount, message, operationIds } = delivery;
|
|
51
|
+
return {
|
|
52
|
+
queued: queuedCount > 0,
|
|
53
|
+
sent: false,
|
|
54
|
+
count: queuedCount,
|
|
55
|
+
queuedCount,
|
|
56
|
+
sentCount,
|
|
57
|
+
skippedCount,
|
|
58
|
+
operationIds,
|
|
59
|
+
message,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
45
62
|
const createApprovalEmailDeliveryFailure = (error) => ({
|
|
63
|
+
queued: false,
|
|
46
64
|
sent: false,
|
|
47
65
|
count: 0,
|
|
66
|
+
queuedCount: 0,
|
|
48
67
|
sentCount: 0,
|
|
49
68
|
skippedCount: 0,
|
|
50
69
|
error,
|
|
@@ -533,16 +552,11 @@ export async function approveBill(billId) {
|
|
|
533
552
|
}
|
|
534
553
|
let email;
|
|
535
554
|
try {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
});
|
|
539
|
-
email = { sent: true };
|
|
555
|
+
const delivery = result.emailDelivery;
|
|
556
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
540
557
|
}
|
|
541
558
|
catch (error) {
|
|
542
|
-
email =
|
|
543
|
-
sent: false,
|
|
544
|
-
error: error instanceof Error ? error.message : String(error),
|
|
545
|
-
};
|
|
559
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
546
560
|
}
|
|
547
561
|
out({
|
|
548
562
|
...result,
|
|
@@ -629,7 +643,7 @@ export async function createBillApproval(billId) {
|
|
|
629
643
|
}
|
|
630
644
|
else {
|
|
631
645
|
try {
|
|
632
|
-
const delivery =
|
|
646
|
+
const delivery = result.emailDelivery;
|
|
633
647
|
email = createApprovalEmailDeliveryReport(delivery);
|
|
634
648
|
}
|
|
635
649
|
catch (error) {
|
|
@@ -1037,6 +1051,16 @@ export async function removeBudgetItem(budgetItemId) {
|
|
|
1037
1051
|
});
|
|
1038
1052
|
out(result);
|
|
1039
1053
|
}
|
|
1054
|
+
export const replaceBudgetItem = async (input) => {
|
|
1055
|
+
await confirmLockedBudgetChangeByBudgetItemId(input.budgetItemId, "Replace unavailable budget item");
|
|
1056
|
+
const result = await api.budgetItem.replaceBudgetItem.mutate(input);
|
|
1057
|
+
out(result);
|
|
1058
|
+
};
|
|
1059
|
+
export const approveUnavailableItemException = async (input) => {
|
|
1060
|
+
await confirmLockedBudgetChangeByBudgetItemId(input.budgetItemId, "Approve unavailable item exception");
|
|
1061
|
+
const result = await api.budgetItem.approveUnavailableItemException.mutate(input);
|
|
1062
|
+
out(result);
|
|
1063
|
+
};
|
|
1040
1064
|
export async function getBudgetCategories() {
|
|
1041
1065
|
const categories = await api.budget.getBudgetCategories.query();
|
|
1042
1066
|
out(categories);
|
|
@@ -1068,7 +1092,7 @@ export async function createBudgetApproval(budgetId, preflight) {
|
|
|
1068
1092
|
}
|
|
1069
1093
|
else {
|
|
1070
1094
|
try {
|
|
1071
|
-
const delivery =
|
|
1095
|
+
const delivery = result.emailDelivery;
|
|
1072
1096
|
email = createApprovalEmailDeliveryReport(delivery);
|
|
1073
1097
|
}
|
|
1074
1098
|
catch (error) {
|
|
@@ -1425,7 +1449,7 @@ export const syncCustomerInvoice = async (invoiceId) => {
|
|
|
1425
1449
|
});
|
|
1426
1450
|
out(result);
|
|
1427
1451
|
};
|
|
1428
|
-
export const describeCustomerInvoiceEmailConfirmation = (input) => `emails the QBO invoice PDF to ${input.to}; requested CC ${input.cc.length > 0 ? input.cc.join(", ") : "none"}; reply-to ${input.replyTo}; subject "${input.subject}"; server-required admin, creator, business-development, and inside-sales CC recipients are added; success marks the invoice SENT; do not retry blindly after an ambiguous delivery failure`;
|
|
1452
|
+
export const describeCustomerInvoiceEmailConfirmation = (input) => `emails the QBO invoice PDF to ${input.to} and saves that address as the project's billing email; requested CC ${input.cc.length > 0 ? input.cc.join(", ") : "none"}; reply-to ${input.replyTo}; subject "${input.subject}"; server-required admin, creator, business-development, and inside-sales CC recipients are added; success marks the invoice SENT; do not retry blindly after an ambiguous delivery failure`;
|
|
1429
1453
|
export const sendCustomerInvoiceToContactPersonFromPayload = async (raw) => {
|
|
1430
1454
|
const input = parseSendCustomerInvoiceToContactPersonPayload(raw);
|
|
1431
1455
|
await assertSensitiveWorkflowConfirmed({
|
|
@@ -1478,7 +1502,7 @@ export async function createSupplierFromPayload(raw) {
|
|
|
1478
1502
|
}
|
|
1479
1503
|
else {
|
|
1480
1504
|
try {
|
|
1481
|
-
const delivery =
|
|
1505
|
+
const delivery = approval.emailDelivery;
|
|
1482
1506
|
emailResult = createApprovalEmailDeliveryReport(delivery);
|
|
1483
1507
|
}
|
|
1484
1508
|
catch (error) {
|
|
@@ -1524,7 +1548,7 @@ export async function updateSupplierFromPayload(raw) {
|
|
|
1524
1548
|
}
|
|
1525
1549
|
else {
|
|
1526
1550
|
try {
|
|
1527
|
-
const delivery =
|
|
1551
|
+
const delivery = approval.emailDelivery;
|
|
1528
1552
|
approvalResult = {
|
|
1529
1553
|
requested: true,
|
|
1530
1554
|
approvalCount: approvalIds.length,
|
|
@@ -1604,16 +1628,11 @@ export async function approveBudget(budgetId) {
|
|
|
1604
1628
|
});
|
|
1605
1629
|
let email;
|
|
1606
1630
|
try {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
});
|
|
1610
|
-
email = { sent: true };
|
|
1631
|
+
const delivery = result.emailDelivery;
|
|
1632
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
1611
1633
|
}
|
|
1612
1634
|
catch (error) {
|
|
1613
|
-
email =
|
|
1614
|
-
sent: false,
|
|
1615
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1616
|
-
};
|
|
1635
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
1617
1636
|
}
|
|
1618
1637
|
out({
|
|
1619
1638
|
...result,
|
|
@@ -1643,16 +1662,11 @@ export async function rejectBudget(budgetId, reason) {
|
|
|
1643
1662
|
});
|
|
1644
1663
|
let email;
|
|
1645
1664
|
try {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
});
|
|
1649
|
-
email = { sent: true };
|
|
1665
|
+
const delivery = result.emailDelivery;
|
|
1666
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
1650
1667
|
}
|
|
1651
1668
|
catch (error) {
|
|
1652
|
-
email =
|
|
1653
|
-
sent: false,
|
|
1654
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1655
|
-
};
|
|
1669
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
1656
1670
|
}
|
|
1657
1671
|
out({
|
|
1658
1672
|
...result,
|
|
@@ -1741,16 +1755,11 @@ export async function approveSupplier(supplierId) {
|
|
|
1741
1755
|
});
|
|
1742
1756
|
let email;
|
|
1743
1757
|
try {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
});
|
|
1747
|
-
email = { sent: true };
|
|
1758
|
+
const delivery = result.emailDelivery;
|
|
1759
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
1748
1760
|
}
|
|
1749
1761
|
catch (error) {
|
|
1750
|
-
email =
|
|
1751
|
-
sent: false,
|
|
1752
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1753
|
-
};
|
|
1762
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
1754
1763
|
}
|
|
1755
1764
|
out({
|
|
1756
1765
|
...result,
|
|
@@ -1779,16 +1788,11 @@ export async function rejectSupplier(supplierId, reason) {
|
|
|
1779
1788
|
});
|
|
1780
1789
|
let email;
|
|
1781
1790
|
try {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
});
|
|
1785
|
-
email = { sent: true };
|
|
1791
|
+
const delivery = result.emailDelivery;
|
|
1792
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
1786
1793
|
}
|
|
1787
1794
|
catch (error) {
|
|
1788
|
-
email =
|
|
1789
|
-
sent: false,
|
|
1790
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1791
|
-
};
|
|
1795
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
1792
1796
|
}
|
|
1793
1797
|
out({
|
|
1794
1798
|
...result,
|
|
@@ -1821,16 +1825,11 @@ export async function rejectBill(billId, reason) {
|
|
|
1821
1825
|
}
|
|
1822
1826
|
let email;
|
|
1823
1827
|
try {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
});
|
|
1827
|
-
email = { sent: true };
|
|
1828
|
+
const delivery = result.emailDelivery;
|
|
1829
|
+
email = createApprovalEmailDeliveryReport(delivery);
|
|
1828
1830
|
}
|
|
1829
1831
|
catch (error) {
|
|
1830
|
-
email =
|
|
1831
|
-
sent: false,
|
|
1832
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1833
|
-
};
|
|
1832
|
+
email = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
1834
1833
|
}
|
|
1835
1834
|
out({
|
|
1836
1835
|
...result,
|
|
@@ -2097,6 +2096,7 @@ export async function listContacts(companyId) {
|
|
|
2097
2096
|
// --- Items ---
|
|
2098
2097
|
export async function listItems(opts) {
|
|
2099
2098
|
const result = await api.item.getItems.query({
|
|
2099
|
+
includeQuickBooksDeactivationPending: opts.includeQuickBooksDeactivationPending,
|
|
2100
2100
|
page: opts.page ?? 1,
|
|
2101
2101
|
perPage: opts.perPage ?? 20,
|
|
2102
2102
|
name: opts.name,
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { resolve } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { BUDGET_BUILDER_API_BASE_URL, configureBudgetBuilderApiBaseUrl, DEFAULT_BUDGET_BUILDER_API_BASE_URL, requireApiKey, } from "./api-client.js";
|
|
7
7
|
import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
|
|
8
|
-
import { addBudgetItems, approveBill, approveBudget, approveCustomerInvoice, approveQuotation, approveSupplier, archiveItemsByIds, checkCustomerInvoiceReadiness, checkProjectReconciliation, cleanupStagedBillAttachments, cleanupStagedQuotationAttachments, completeProject, createApiKeyForUser, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createCustomerInvoice, createEstimate, createInboundSource, createItemCategory, createItemFromPayload, createPlaceholderBillForBudgetItem, createProject, createQuotationFromPayload, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, createUser, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteBudgetCommission, deleteBudgetDiscount, deleteCompanyById, deleteCustomerInvoice, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteQuotationById, deleteSuppliersByIds, discardCreatingCustomerInvoice, downloadCustomerInvoicePdf, downloadQuotationPdf, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getCustomerInvoice, getCustomerInvoiceEmailContext, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getInboundSource, getInboundSubmission, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getProjectHubStatus, getQuotationDetails, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, importQuickBooksProjectId, listApiKeysForUser, listApprovals, listBills, listBudgets, listCompanies, listContacts, listCustomerInvoices, listEligibleCustomerInvoiceBudgets, listInboundSources, listInboundSubmissions, listIntegrationOperations, listItemCategories, listItems, listProjectHubs, listProjects, listQuotations, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, reconcileProject, rejectBill, rejectBudget, rejectCustomerInvoice, rejectQuotation, rejectSupplier, removeBudgetItem, renameBudgetVersion, reorderBudgetItemsCli, restoreBudgetVersion, retryIntegrationOperation, revokeApiKeyForUser, saveInboundAutomationConfig, saveInboundMappingVersion, sendCustomerInvoiceToContactPersonFromPayload, sendEstimateToContactPersonFromPayload, setBudgetItemsNotUtilized, setupProjectHub, stageBillAttachmentsFromPaths, submitQuotation, syncCustomerInvoice, syncProjectHubCommercialDocuments, updateBillFromPayload, updateBillPaymentEvidenceFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateInboundProcessing, updateInboundSource, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateQuotationFromPayload, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBillAttachmentsFromPaths, uploadBillDocumentsFromPaths, uploadBudgetAttachmentFromPath, uploadQuotationAttachmentFromPath, validateBillSelectionFromPayload, voidCustomerInvoice, whoAmI, } from "./commands.js";
|
|
8
|
+
import { addBudgetItems, approveBill, approveBudget, approveCustomerInvoice, approveQuotation, approveSupplier, approveUnavailableItemException, archiveItemsByIds, checkCustomerInvoiceReadiness, checkProjectReconciliation, cleanupStagedBillAttachments, cleanupStagedQuotationAttachments, completeProject, createApiKeyForUser, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createCustomerInvoice, createEstimate, createInboundSource, createItemCategory, createItemFromPayload, createPlaceholderBillForBudgetItem, createProject, createQuotationFromPayload, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, createUser, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteBudgetCommission, deleteBudgetDiscount, deleteCompanyById, deleteCustomerInvoice, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteQuotationById, deleteSuppliersByIds, discardCreatingCustomerInvoice, downloadCustomerInvoicePdf, downloadQuotationPdf, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getCustomerInvoice, getCustomerInvoiceEmailContext, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getInboundSource, getInboundSubmission, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getProjectHubStatus, getQuotationDetails, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, importQuickBooksProjectId, listApiKeysForUser, listApprovals, listBills, listBudgets, listCompanies, listContacts, listCustomerInvoices, listEligibleCustomerInvoiceBudgets, listInboundSources, listInboundSubmissions, listIntegrationOperations, listItemCategories, listItems, listProjectHubs, listProjects, listQuotations, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, reconcileProject, rejectBill, rejectBudget, rejectCustomerInvoice, rejectQuotation, rejectSupplier, removeBudgetItem, renameBudgetVersion, reorderBudgetItemsCli, replaceBudgetItem, restoreBudgetVersion, retryIntegrationOperation, revokeApiKeyForUser, saveInboundAutomationConfig, saveInboundMappingVersion, sendCustomerInvoiceToContactPersonFromPayload, sendEstimateToContactPersonFromPayload, setBudgetItemsNotUtilized, setupProjectHub, stageBillAttachmentsFromPaths, submitQuotation, syncCustomerInvoice, syncProjectHubCommercialDocuments, updateBillFromPayload, updateBillPaymentEvidenceFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateInboundProcessing, updateInboundSource, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateQuotationFromPayload, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBillAttachmentsFromPaths, uploadBillDocumentsFromPaths, uploadBudgetAttachmentFromPath, uploadQuotationAttachmentFromPath, validateBillSelectionFromPayload, voidCustomerInvoice, whoAmI, } from "./commands.js";
|
|
9
9
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
10
10
|
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedCustomerInvoiceStatuses, parseCommaSeparatedIds, parseCommaSeparatedIntegrationOperationStatuses, parseCommaSeparatedProjectStatuses, parseCommaSeparatedQuotationStatuses, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalProjectHubSortBy, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, parseUserRole, projectStatusesForHelp, userRolesForHelp, } from "./parse-cli-enums.js";
|
|
11
11
|
import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
@@ -173,15 +173,17 @@ Budgets
|
|
|
173
173
|
ESTIMATE_REJECTED requires --projectStatusOnCommercialRejection when rejecting the only accepted/closed budget on a commercial project.
|
|
174
174
|
status: ${budgetStatusesForHelp.join(", ")}
|
|
175
175
|
mark-budget-won <budgetId> <filePath> [--projectManagerId <id>] [--invoiceSettings <json>] Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED; first acceptance requires invoice settings when customer invoicing is enabled
|
|
176
|
-
create-budget --payload '<json>' (budget.createBudget;
|
|
176
|
+
create-budget --payload '<json>' (budget.createBudget; when sourceBudgetId contains unavailable items, set unavailableItemReviewAcknowledged=true after explicit review)
|
|
177
177
|
update-budget --payload '<json>' (budget.updateBudget; must include id)
|
|
178
178
|
delete-budget <budgetId>
|
|
179
|
-
create-budget-approval <budgetId> (also
|
|
179
|
+
create-budget-approval <budgetId> (also queues approval request emails)
|
|
180
180
|
create-estimate <budgetId> (creates QuickBooks estimate; supports preflight updates)
|
|
181
181
|
send-estimate-to-contact-person --payload '<json>' Send using the web contact-person workflow; payload includes recipient fields plus HTML content/signature.
|
|
182
182
|
add-budget-items --budgetId --items '[{"itemId":"…","quantity":1,"markup":30},…]'
|
|
183
183
|
update-budget-item --id <budgetItemId> [--description plain-text] [--note plain-text] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false] [--gstOutOfScope true|false]
|
|
184
184
|
remove-budget-item <budgetItemId>
|
|
185
|
+
replace-budget-item --budgetItemId <id> --replacementItemId <activeCatalogueItemId>
|
|
186
|
+
approve-unavailable-item-exception --budgetItemId <id> --reason <text> Lead/Admin only; reason must be 5-1000 characters.
|
|
185
187
|
reorder-budget-items <budgetId> --itemIds <csv>
|
|
186
188
|
update-budget-item-supplier --budgetItemId --supplierId
|
|
187
189
|
create-budget-category --name <text>
|
|
@@ -200,10 +202,10 @@ Bills
|
|
|
200
202
|
list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
|
|
201
203
|
list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
|
|
202
204
|
isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
|
|
203
|
-
|
|
204
|
-
create-bill-approval <billId> (also
|
|
205
|
+
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, including already-paid bills.
|
|
206
|
+
create-bill-approval <billId> (also queues approval request emails)
|
|
205
207
|
create-bill --payload '<json>' (bill.create; payload.isClaimable false = bill, true = claim)
|
|
206
|
-
validate-bill-selection --payload '<json>' Check
|
|
208
|
+
validate-bill-selection --payload '<json>' Check project and supplier validity separately, then verify approved quotation links for each budgetItemId; alreadyPaid never bypasses quotation checks.
|
|
207
209
|
stage-bill-attachment <projectId> <filePath...> [--file <path>] [--files <csv>] Upload files before create-bill; returns attachment JSON for attachments/paymentProofAttachments
|
|
208
210
|
cleanup-staged-bill-attachments <projectId> --keys <csv> Delete unattached staged bill/claim uploads.
|
|
209
211
|
update-bill --payload '<json>' (bill.update; must include id)
|
|
@@ -249,7 +251,7 @@ Customer invoices
|
|
|
249
251
|
void-customer-invoice <batchId> Void the invoice batch in QuickBooks.
|
|
250
252
|
approve-customer-invoice <batchId> Admin approval; rejects voided invoices, notifies the creator, and closes the estimate at cumulative 100% coverage.
|
|
251
253
|
reject-customer-invoice <batchId> --reason <text> Admin rejection; voids QBO invoices and notifies the creator.
|
|
252
|
-
send-customer-invoice-to-contact-person --payload '<json>'
|
|
254
|
+
send-customer-invoice-to-contact-person --payload '<json>' Queues same composer workflow as web; requires invoiceId, to, cc, replyTo, subject, HTML content, and HTML signature.
|
|
253
255
|
download-customer-invoice-pdf <invoiceId> [--output <path>]
|
|
254
256
|
sync-customer-invoice <invoiceId> Refresh local status and balance from QuickBooks; paid invoices become PAID, reversals restore delivery/approval, and QBO voids can reopen an estimate.
|
|
255
257
|
|
|
@@ -305,11 +307,11 @@ Suppliers & items
|
|
|
305
307
|
create-supplier-tag --name (supplier.createSupplierTag)
|
|
306
308
|
create-item --payload '<json>' (item.createItem)
|
|
307
309
|
update-item --payload '<json>' (item.updateItem; must include id)
|
|
308
|
-
archive-items --ids <csv> (item.archiveItems; Budget Builder only; max 100; users can archive their own items, leads/admins any item;
|
|
309
|
-
delete-item <id> (item.deleteItem; admin; archives in Budget Builder and makes the item inactive in QuickBooks)
|
|
310
|
+
archive-items --ids <csv> (item.archiveItems; Budget Builder only; max 100; users can archive their own items, leads/admins any item; open estimates retain snapshot lines and require resolution before send/acceptance)
|
|
311
|
+
delete-item <id> (item.deleteItem; admin; archives in Budget Builder and makes the item inactive in QuickBooks; blocked while used by an open estimate)
|
|
310
312
|
get-supplier-details <supplierId>
|
|
311
313
|
get-supplier-analytics [--name] [--page] [--perPage] [--timeFrame ALL|LAST_YEAR|…] (admin)
|
|
312
|
-
list-items [--name] [--page] [--perPage]
|
|
314
|
+
list-items [--name] [--page] [--perPage] [--includeQuickBooksDeactivationPending true]
|
|
313
315
|
get-item <id>
|
|
314
316
|
list-item-categories [--page] [--perPage]
|
|
315
317
|
create-item-category --name (admin)
|
|
@@ -845,6 +847,30 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
|
|
|
845
847
|
await removeBudgetItem(id);
|
|
846
848
|
break;
|
|
847
849
|
}
|
|
850
|
+
case "replace-budget-item": {
|
|
851
|
+
const budgetItemId = getFlag(flags, "budgetItemId");
|
|
852
|
+
const replacementItemId = getFlag(flags, "replacementItemId");
|
|
853
|
+
if (!budgetItemId || !replacementItemId) {
|
|
854
|
+
throw new Error("replace-budget-item requires --budgetItemId and --replacementItemId");
|
|
855
|
+
}
|
|
856
|
+
await replaceBudgetItem({
|
|
857
|
+
budgetItemId: String(budgetItemId),
|
|
858
|
+
replacementItemId: String(replacementItemId),
|
|
859
|
+
});
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
case "approve-unavailable-item-exception": {
|
|
863
|
+
const budgetItemId = getFlag(flags, "budgetItemId");
|
|
864
|
+
const reason = getFlag(flags, "reason");
|
|
865
|
+
if (!budgetItemId || !reason) {
|
|
866
|
+
throw new Error("approve-unavailable-item-exception requires --budgetItemId and --reason");
|
|
867
|
+
}
|
|
868
|
+
await approveUnavailableItemException({
|
|
869
|
+
budgetItemId: String(budgetItemId),
|
|
870
|
+
reason: String(reason),
|
|
871
|
+
});
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
848
874
|
case "reorder-budget-items": {
|
|
849
875
|
const budgetId = positional[0];
|
|
850
876
|
const itemIdsRaw = getFlag(flags, "itemIds");
|
|
@@ -1840,6 +1866,9 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
|
|
|
1840
1866
|
}
|
|
1841
1867
|
case "list-items": {
|
|
1842
1868
|
await listItems({
|
|
1869
|
+
includeQuickBooksDeactivationPending: flags.includeQuickBooksDeactivationPending === true
|
|
1870
|
+
? true
|
|
1871
|
+
: parseOptionalBoolFlag(flags, "includeQuickBooksDeactivationPending"),
|
|
1843
1872
|
name: getFlag(flags, "name"),
|
|
1844
1873
|
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
1845
1874
|
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
@@ -76,6 +76,10 @@ export const parseSendCustomerInvoiceToContactPersonPayload = (raw) => {
|
|
|
76
76
|
/** Matches budget.createBudget — optional pipedriveDealId; Asana deal is project.asanaTaskId. */
|
|
77
77
|
export function parseCreateBudgetPayload(raw) {
|
|
78
78
|
const o = requireObject(raw, "create-budget payload");
|
|
79
|
+
if (o.unavailableItemReviewAcknowledged !== undefined &&
|
|
80
|
+
typeof o.unavailableItemReviewAcknowledged !== "boolean") {
|
|
81
|
+
throw new Error("create-budget payload.unavailableItemReviewAcknowledged must be a boolean when set.");
|
|
82
|
+
}
|
|
79
83
|
return {
|
|
80
84
|
name: requiredString(o.name, "create-budget payload.name"),
|
|
81
85
|
budget: requiredString(o.budget, "create-budget payload.budget"),
|
|
@@ -83,6 +87,9 @@ export function parseCreateBudgetPayload(raw) {
|
|
|
83
87
|
categoryId: requiredString(o.categoryId, "create-budget payload.categoryId"),
|
|
84
88
|
projectId: requiredString(o.projectId, "create-budget payload.projectId"),
|
|
85
89
|
sourceBudgetId: optionalString(o.sourceBudgetId),
|
|
90
|
+
...(o.unavailableItemReviewAcknowledged !== undefined && {
|
|
91
|
+
unavailableItemReviewAcknowledged: o.unavailableItemReviewAcknowledged,
|
|
92
|
+
}),
|
|
86
93
|
pipedriveDealId: optionalString(o.pipedriveDealId),
|
|
87
94
|
};
|
|
88
95
|
}
|
package/dist/registry/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const mutationPrefixes = [
|
|
|
51
51
|
"reconcile-",
|
|
52
52
|
"reject-",
|
|
53
53
|
"remove-",
|
|
54
|
+
"replace-",
|
|
54
55
|
"rename-",
|
|
55
56
|
"reorder-",
|
|
56
57
|
"restore-",
|
|
@@ -149,6 +150,7 @@ const financialWriteTargets = new Set([
|
|
|
149
150
|
"reject-customer-invoice",
|
|
150
151
|
"restore-budget-item",
|
|
151
152
|
"restore-budget-version",
|
|
153
|
+
"replace-budget-item",
|
|
152
154
|
"sync-customer-invoice",
|
|
153
155
|
"update-bill",
|
|
154
156
|
"update-bill-payment-evidence",
|
|
@@ -195,6 +197,9 @@ const summaryFor = (legacyTarget) => {
|
|
|
195
197
|
if (legacyTarget === "download-customer-invoice-pdf") {
|
|
196
198
|
return "Download a customer invoice PDF when permitted.";
|
|
197
199
|
}
|
|
200
|
+
if (legacyTarget === "send-customer-invoice-to-contact-person") {
|
|
201
|
+
return "Send a customer invoice and update the project billing email.";
|
|
202
|
+
}
|
|
198
203
|
if (legacyTarget.startsWith("list-")) {
|
|
199
204
|
return `List ${legacyTarget.slice("list-".length).replaceAll("-", " ")}.`;
|
|
200
205
|
}
|
|
@@ -239,6 +244,13 @@ const registry = [
|
|
|
239
244
|
legacyCommand("add-budget-items", ["budget", "item", "add"]),
|
|
240
245
|
legacyCommand("update-budget-item", ["budget", "item", "update"]),
|
|
241
246
|
legacyCommand("remove-budget-item", ["budget", "item", "remove"]),
|
|
247
|
+
legacyCommand("replace-budget-item", ["budget", "item", "replace"]),
|
|
248
|
+
legacyCommand("approve-unavailable-item-exception", [
|
|
249
|
+
"budget",
|
|
250
|
+
"item",
|
|
251
|
+
"exception",
|
|
252
|
+
"approve",
|
|
253
|
+
]),
|
|
242
254
|
legacyCommand("reorder-budget-items", ["budget", "item", "reorder"]),
|
|
243
255
|
legacyCommand("update-budget-item-supplier", [
|
|
244
256
|
"budget",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
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",
|
|
@@ -53,7 +53,7 @@ Do not treat Inside Sales as Lead or Admin. Budget and supplier decisions still
|
|
|
53
53
|
|
|
54
54
|
### `LEAD`
|
|
55
55
|
|
|
56
|
-
Apply the base resource rules, plus Lead-stage budget and supplier workflows. Lead-created suppliers are auto-approved,
|
|
56
|
+
Apply the base resource rules, plus Lead-stage budget and supplier workflows. Lead-created suppliers are auto-approved, Leads can handle budget/supplier approvals that the API assigns to them, and Leads may approve an audited exception for an unavailable catalogue line after reviewing the exact estimate impact and reason.
|
|
57
57
|
|
|
58
58
|
Do not use Lead credentials for Admin-only dashboards, user/service-identity administration, errors, integration retries, final bill payment, or admin customer-invoice actions.
|
|
59
59
|
|
|
@@ -76,6 +76,7 @@ Admin credentials may perform global and final-control operations, including:
|
|
|
76
76
|
- Cross-user `bb user api-key create`, `bb user api-key list`, and `bb user api-key revoke` with `--userId`.
|
|
77
77
|
- Global dashboard/performance, error-log, and integration-operation commands.
|
|
78
78
|
- Admin-only supplier/category recovery and archive operations.
|
|
79
|
+
- Approving audited unavailable-item exceptions after confirming that replacement or removal is not appropriate.
|
|
79
80
|
- Final bill approval/payment and Admin customer-invoice decisions/payment.
|
|
80
81
|
|
|
81
82
|
Admin is not a force flag. State transitions, evidence, consistency checks, external-operation locks, and interactive confirmation rules still apply.
|
|
@@ -90,6 +91,7 @@ This table highlights the role-sensitive command groups an agent is most likely
|
|
|
90
91
|
| `bb auth whoami`; own `bb user api-key create`, `bb user api-key list`, and `bb user api-key revoke` | Any active role; omit `--userId` to act as the current user. |
|
|
91
92
|
| Cross-user API-key commands; `bb user create` | `ADMIN`. |
|
|
92
93
|
| Budget/project mutations, estimates, and budget attachments | `ADMIN`, budget creator, or project assignment as Business Development, Inside Sales, or Project Manager, depending on the endpoint. |
|
|
94
|
+
| `bb budget item exception approve` | `LEAD` or `ADMIN`; requires an unavailable line and a specific audited reason. |
|
|
93
95
|
| `bb project hub status`, `bb project hub setup`, and `bb project hub sync` | `ADMIN`, project creator, or project assignment as Business Development, Inside Sales, or Project Manager. Setup and sync also enforce Project Hub lifecycle/readiness checks. |
|
|
94
96
|
| Edit/delete a bill or quotation | Usually creator or `ADMIN`, with a supported status. |
|
|
95
97
|
| `bb budget approve`, `bb budget reject`, `bb supplier approve`, and `bb supplier reject` | Current user must be the pending approver and currently have role `LEAD` or `ADMIN`. |
|