@go-labs-sg/bb 2.42.0 → 2.44.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 +46 -1
- package/command-manifest.json +127 -5
- package/command-reference.md +21 -5
- package/dist/index.js +258 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,27 @@ The shared API rechecks existing records at submission. Canonical matches reuse
|
|
|
22
22
|
|
|
23
23
|
`bb company use` and `bb company create` are state-changing, potentially external-writing commands. In non-interactive mode their existing permission gates require `--allow-state-change --allow-external-write`. Deploy the backend before publishing this CLI change. See [company discovery details](../api/docs/company-discovery.md) for limits and retry behaviour.
|
|
24
24
|
|
|
25
|
+
## Record a payment made outside Budget Builder
|
|
26
|
+
|
|
27
|
+
Admins can record an already-made payment on an APPROVED bill or claim without transferring money or emailing the supplier/claimant:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bb bill payment accounts
|
|
31
|
+
bb bill payment record-external <billId> \
|
|
32
|
+
--paymentReference 2343190532 \
|
|
33
|
+
--paymentDate 2026-09-01 \
|
|
34
|
+
--paymentAccount <quickbooks-account-id> \
|
|
35
|
+
--paymentProof receipt.pdf
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Choose the bank or card account actually used (including `Events Requisitions`); no account or date is defaulted. Omit `--paymentProof` if a payment-proof PDF is already attached. In non-interactive use, add `--allow-state-change --allow-external-write --allow-financial-write`; no `--allow-email` is needed. The UI offers the same **Record external payment** action next to ordinary **Mark paid**.
|
|
39
|
+
|
|
40
|
+
BB marks the bill PAID immediately and queues durable QuickBooks reconciliation. A settled zero-balance QBO bill reuses its existing BillPayment IDs. Otherwise reconciliation creates a payment with the specified date/account and a stable provider request ID. Partially settled or inconsistent QBO bills require manual reconciliation; BB never guesses. Inspect failed/pending work in Integration Queue and **do not pay the bill again**. Exact retries with the same reference/date/account return the recorded settlement; changed details conflict. Ordinary Mark paid keeps its existing payment-advice workflow.
|
|
41
|
+
|
|
42
|
+
`bb update-bill --payload '{"id":"<billId>","comment":"Already paid; do not pay again"}'` now accepts a partial payload. Omitted fields are preserved; comment-only updates do not change financial data or sync QBO, and are allowed on approved/paid bills for the creator or an admin. Financial edits retain their draft/rejected restrictions.
|
|
43
|
+
|
|
44
|
+
Deployment prerequisite: apply the Budget Builder Prisma schema additions (`Bill.externalPayment` JSON and `EXTERNAL_PAYMENT` operation enum) before deploying the API/web/workers; deploy the backend before publishing the CLI. This feature does not modify existing paid bills or repair historical QBO entries automatically.
|
|
45
|
+
|
|
25
46
|
## Payload help and uncertain writes
|
|
26
47
|
|
|
27
48
|
Run `bb <command> --help` (for example, `bb create-item --help`) to see required and optional `--payload` fields and an example JSON object. Item creation and updates accept a finite numeric `cost` and convert it to the API's decimal-string input. Validation errors identify the affected field; unsupported QuickBooks name characters are listed explicitly.
|
|
@@ -42,7 +63,7 @@ See the [supplier review and rollout policy](../api/docs/supplier-edit-review.md
|
|
|
42
63
|
|
|
43
64
|
## Runtime and installation
|
|
44
65
|
|
|
45
|
-
The CLI requires [Bun](https://bun.com/docs/installation) 1.4.0 or newer and a Budget Builder account with any active user role.
|
|
66
|
+
The same CLI package supports macOS and Windows. It requires [Bun](https://bun.com/docs/installation) 1.4.0 or newer and a Budget Builder account with any active user role. Windows users can run the commands below in native PowerShell; Bash and `export PATH=...` are not required.
|
|
46
67
|
|
|
47
68
|
Check whether a compatible Bun version is already available:
|
|
48
69
|
|
|
@@ -85,6 +106,25 @@ Or run without installing:
|
|
|
85
106
|
bunx @go-labs-sg/bb <command>
|
|
86
107
|
```
|
|
87
108
|
|
|
109
|
+
### Historical budget reads (Admin only)
|
|
110
|
+
|
|
111
|
+
Budget reads are active-only by default. The API enforces the historical-read permission; the CLI does not grant access based on the flag alone.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Active budgets only (default)
|
|
115
|
+
bb list-budgets
|
|
116
|
+
bb get-budget <budget-id>
|
|
117
|
+
|
|
118
|
+
# Include inactive budgets; each form requires an ADMIN identity
|
|
119
|
+
bb list-budgets --active false
|
|
120
|
+
bb list-budgets --active all
|
|
121
|
+
bb get-budget <budget-id> --includeInactive
|
|
122
|
+
bb get-budget-items <budget-id> --includeInactive
|
|
123
|
+
bb get-budget-details <budget-id> --includeInactive
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`--active false` lists inactive budgets only. `--active all` lists active and inactive budgets. `--includeInactive` allows the three budget/detail/item commands to read inactive history. If the authenticated identity is not an Admin, the API rejects the historical request before reading budget data. For `get-budget`, the CLI requests the budget and its line items concurrently; each API read independently enforces the same active-only default and Admin-only historical opt-in.
|
|
127
|
+
|
|
88
128
|
## Authentication
|
|
89
129
|
|
|
90
130
|
Interactive users authorize the CLI through Budget Builder's normal Google-backed
|
|
@@ -102,6 +142,11 @@ credentials through `Bun.secrets`. It never stores Google credentials. Use
|
|
|
102
142
|
`bb auth logout` to revoke the interactive session. For SSH or containers, pass
|
|
103
143
|
`--no-browser` and open the printed URL on another device.
|
|
104
144
|
|
|
145
|
+
Credentials use macOS Keychain or Windows Credential Manager, not plaintext files.
|
|
146
|
+
The per-user lock directory contains only random ownership markers used to serialize
|
|
147
|
+
login, refresh, and logout. macOS/Linux enforce owner-only POSIX permissions there;
|
|
148
|
+
Windows uses its native filesystem access controls rather than POSIX mode checks.
|
|
149
|
+
|
|
105
150
|
Budget Builder API keys remain supported for MCP, CI, and non-interactive
|
|
106
151
|
automation. When `BB_API_KEY` is present it takes precedence over the interactive
|
|
107
152
|
session for that process. Create and manage automation keys under **API Keys** or
|
package/command-manifest.json
CHANGED
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
{
|
|
200
200
|
"path": ["budget", "list"],
|
|
201
201
|
"legacyAliases": ["list-budgets", "list_budgets"],
|
|
202
|
-
"summary": "List budgets.",
|
|
202
|
+
"summary": "List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.",
|
|
203
203
|
"globalOptions": [
|
|
204
204
|
{
|
|
205
205
|
"name": "--help",
|
|
@@ -240,6 +240,10 @@
|
|
|
240
240
|
{
|
|
241
241
|
"name": "--allow-financial-write",
|
|
242
242
|
"description": "Allow financial-record changes in non-interactive use."
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "--active <true|false|all>",
|
|
246
|
+
"description": "Omit for active budgets only; false or all requires Admin access."
|
|
243
247
|
}
|
|
244
248
|
],
|
|
245
249
|
"argumentMode": "legacy-passthrough",
|
|
@@ -249,7 +253,7 @@
|
|
|
249
253
|
{
|
|
250
254
|
"path": ["budget", "get"],
|
|
251
255
|
"legacyAliases": ["get-budget", "get_budget"],
|
|
252
|
-
"summary": "Get budget.",
|
|
256
|
+
"summary": "Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
|
|
253
257
|
"globalOptions": [
|
|
254
258
|
{
|
|
255
259
|
"name": "--help",
|
|
@@ -290,6 +294,10 @@
|
|
|
290
294
|
{
|
|
291
295
|
"name": "--allow-financial-write",
|
|
292
296
|
"description": "Allow financial-record changes in non-interactive use."
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "--includeInactive",
|
|
300
|
+
"description": "Include inactive historical budgets; Admin only."
|
|
293
301
|
}
|
|
294
302
|
],
|
|
295
303
|
"argumentMode": "legacy-passthrough",
|
|
@@ -399,7 +407,7 @@
|
|
|
399
407
|
{
|
|
400
408
|
"path": ["budget", "item", "list"],
|
|
401
409
|
"legacyAliases": ["get-budget-items", "get_budget_items"],
|
|
402
|
-
"summary": "Get
|
|
410
|
+
"summary": "Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
|
|
403
411
|
"globalOptions": [
|
|
404
412
|
{
|
|
405
413
|
"name": "--help",
|
|
@@ -440,6 +448,10 @@
|
|
|
440
448
|
{
|
|
441
449
|
"name": "--allow-financial-write",
|
|
442
450
|
"description": "Allow financial-record changes in non-interactive use."
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"name": "--includeInactive",
|
|
454
|
+
"description": "Include inactive historical budgets; Admin only."
|
|
443
455
|
}
|
|
444
456
|
],
|
|
445
457
|
"argumentMode": "legacy-passthrough",
|
|
@@ -449,7 +461,7 @@
|
|
|
449
461
|
{
|
|
450
462
|
"path": ["budget", "detail", "get"],
|
|
451
463
|
"legacyAliases": ["get-budget-details", "get_budget_details"],
|
|
452
|
-
"summary": "Get budget details.",
|
|
464
|
+
"summary": "Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
|
|
453
465
|
"globalOptions": [
|
|
454
466
|
{
|
|
455
467
|
"name": "--help",
|
|
@@ -490,6 +502,10 @@
|
|
|
490
502
|
{
|
|
491
503
|
"name": "--allow-financial-write",
|
|
492
504
|
"description": "Allow financial-record changes in non-interactive use."
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"name": "--includeInactive",
|
|
508
|
+
"description": "Include inactive historical budgets; Admin only."
|
|
493
509
|
}
|
|
494
510
|
],
|
|
495
511
|
"argumentMode": "legacy-passthrough",
|
|
@@ -2261,7 +2277,7 @@
|
|
|
2261
2277
|
{
|
|
2262
2278
|
"path": ["bill", "update"],
|
|
2263
2279
|
"legacyAliases": ["update-bill", "update_bill"],
|
|
2264
|
-
"summary": "Update a bill or claim
|
|
2280
|
+
"summary": "Update a bill or claim with a partial payload including id; omitted fields are preserved. Comment-only updates are allowed on approved/paid bills without altering financial data or QuickBooks. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.",
|
|
2265
2281
|
"globalOptions": [
|
|
2266
2282
|
{
|
|
2267
2283
|
"name": "--help",
|
|
@@ -2461,6 +2477,112 @@
|
|
|
2461
2477
|
"effects": ["state-change", "email", "financial-write"],
|
|
2462
2478
|
"legacyTarget": "update-bill-status"
|
|
2463
2479
|
},
|
|
2480
|
+
{
|
|
2481
|
+
"path": ["bill", "payment", "record-external"],
|
|
2482
|
+
"legacyAliases": [
|
|
2483
|
+
"record-external-bill-payment",
|
|
2484
|
+
"record_external_bill_payment"
|
|
2485
|
+
],
|
|
2486
|
+
"summary": "Record an already-made payment on an APPROVED bill or claim, with no email and no money transfer. Requires <billId> --paymentReference --paymentDate YYYY-MM-DD --paymentAccount <QBO account ID> [--paymentProof receipt.pdf]. Reuses settled QBO payment links or queues one dated payment from the selected account. Exact retries are safe; do not pay again while reconciliation is pending.",
|
|
2487
|
+
"globalOptions": [
|
|
2488
|
+
{
|
|
2489
|
+
"name": "--help",
|
|
2490
|
+
"description": "Show help for this command."
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
"name": "--quiet",
|
|
2494
|
+
"description": "Suppress non-error diagnostics."
|
|
2495
|
+
},
|
|
2496
|
+
{
|
|
2497
|
+
"name": "--debug",
|
|
2498
|
+
"description": "Emit sanitized diagnostic traces."
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
"name": "--api-url",
|
|
2502
|
+
"description": "Override the Budget Builder API base URL."
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
"name": "--no-browser",
|
|
2506
|
+
"description": "Print the authorization URL without opening a browser."
|
|
2507
|
+
},
|
|
2508
|
+
{
|
|
2509
|
+
"name": "--allow-state-change",
|
|
2510
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
2511
|
+
},
|
|
2512
|
+
{
|
|
2513
|
+
"name": "--allow-email",
|
|
2514
|
+
"description": "Allow sending email in non-interactive use."
|
|
2515
|
+
},
|
|
2516
|
+
{
|
|
2517
|
+
"name": "--allow-external-write",
|
|
2518
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
"name": "--allow-delete",
|
|
2522
|
+
"description": "Allow deleting data in non-interactive use."
|
|
2523
|
+
},
|
|
2524
|
+
{
|
|
2525
|
+
"name": "--allow-financial-write",
|
|
2526
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
2527
|
+
}
|
|
2528
|
+
],
|
|
2529
|
+
"argumentMode": "legacy-passthrough",
|
|
2530
|
+
"effects": ["state-change", "external-write", "financial-write"],
|
|
2531
|
+
"legacyTarget": "record-external-bill-payment"
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
"path": ["bill", "payment", "accounts"],
|
|
2535
|
+
"legacyAliases": [
|
|
2536
|
+
"list-external-payment-accounts",
|
|
2537
|
+
"list_external_payment_accounts"
|
|
2538
|
+
],
|
|
2539
|
+
"summary": "List active QuickBooks bank and credit-card account IDs for recording external payments (admin only).",
|
|
2540
|
+
"globalOptions": [
|
|
2541
|
+
{
|
|
2542
|
+
"name": "--help",
|
|
2543
|
+
"description": "Show help for this command."
|
|
2544
|
+
},
|
|
2545
|
+
{
|
|
2546
|
+
"name": "--quiet",
|
|
2547
|
+
"description": "Suppress non-error diagnostics."
|
|
2548
|
+
},
|
|
2549
|
+
{
|
|
2550
|
+
"name": "--debug",
|
|
2551
|
+
"description": "Emit sanitized diagnostic traces."
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
"name": "--api-url",
|
|
2555
|
+
"description": "Override the Budget Builder API base URL."
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
"name": "--no-browser",
|
|
2559
|
+
"description": "Print the authorization URL without opening a browser."
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
"name": "--allow-state-change",
|
|
2563
|
+
"description": "Allow a Budget Builder state change in non-interactive use."
|
|
2564
|
+
},
|
|
2565
|
+
{
|
|
2566
|
+
"name": "--allow-email",
|
|
2567
|
+
"description": "Allow sending email in non-interactive use."
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
"name": "--allow-external-write",
|
|
2571
|
+
"description": "Allow writes to external systems in non-interactive use."
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
"name": "--allow-delete",
|
|
2575
|
+
"description": "Allow deleting data in non-interactive use."
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
"name": "--allow-financial-write",
|
|
2579
|
+
"description": "Allow financial-record changes in non-interactive use."
|
|
2580
|
+
}
|
|
2581
|
+
],
|
|
2582
|
+
"argumentMode": "legacy-passthrough",
|
|
2583
|
+
"effects": [],
|
|
2584
|
+
"legacyTarget": "list-external-payment-accounts"
|
|
2585
|
+
},
|
|
2464
2586
|
{
|
|
2465
2587
|
"path": ["bill", "payment", "patch"],
|
|
2466
2588
|
"legacyAliases": ["patch-bill-payment", "patch_bill_payment"],
|
package/command-reference.md
CHANGED
|
@@ -51,7 +51,7 @@ Effects: none.
|
|
|
51
51
|
|
|
52
52
|
## `bb budget list`
|
|
53
53
|
|
|
54
|
-
List budgets.
|
|
54
|
+
List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.
|
|
55
55
|
|
|
56
56
|
Legacy aliases: `list-budgets`, `list_budgets`.
|
|
57
57
|
|
|
@@ -59,7 +59,7 @@ Effects: none.
|
|
|
59
59
|
|
|
60
60
|
## `bb budget get`
|
|
61
61
|
|
|
62
|
-
Get budget.
|
|
62
|
+
Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
|
|
63
63
|
|
|
64
64
|
Legacy aliases: `get-budget`, `get_budget`.
|
|
65
65
|
|
|
@@ -83,7 +83,7 @@ Effects: state-change, external-write, financial-write.
|
|
|
83
83
|
|
|
84
84
|
## `bb budget item list`
|
|
85
85
|
|
|
86
|
-
Get
|
|
86
|
+
Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
|
|
87
87
|
|
|
88
88
|
Legacy aliases: `get-budget-items`, `get_budget_items`.
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ Effects: none.
|
|
|
91
91
|
|
|
92
92
|
## `bb budget detail get`
|
|
93
93
|
|
|
94
|
-
Get budget details.
|
|
94
|
+
Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
|
|
95
95
|
|
|
96
96
|
Legacy aliases: `get-budget-details`, `get_budget_details`.
|
|
97
97
|
|
|
@@ -411,7 +411,7 @@ Payload fields (--payload JSON)
|
|
|
411
411
|
|
|
412
412
|
## `bb bill update`
|
|
413
413
|
|
|
414
|
-
Update a bill or claim
|
|
414
|
+
Update a bill or claim with a partial payload including id; omitted fields are preserved. Comment-only updates are allowed on approved/paid bills without altering financial data or QuickBooks. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.
|
|
415
415
|
|
|
416
416
|
Legacy aliases: `update-bill`, `update_bill`.
|
|
417
417
|
|
|
@@ -451,6 +451,22 @@ Legacy aliases: `update-bill-status`, `update_bill_status`.
|
|
|
451
451
|
|
|
452
452
|
Effects: state-change, email, financial-write.
|
|
453
453
|
|
|
454
|
+
## `bb bill payment record-external`
|
|
455
|
+
|
|
456
|
+
Record an already-made payment on an APPROVED bill or claim, with no email and no money transfer. Requires <billId> --paymentReference --paymentDate YYYY-MM-DD --paymentAccount <QBO account ID> [--paymentProof receipt.pdf]. Reuses settled QBO payment links or queues one dated payment from the selected account. Exact retries are safe; do not pay again while reconciliation is pending.
|
|
457
|
+
|
|
458
|
+
Legacy aliases: `record-external-bill-payment`, `record_external_bill_payment`.
|
|
459
|
+
|
|
460
|
+
Effects: state-change, external-write, financial-write.
|
|
461
|
+
|
|
462
|
+
## `bb bill payment accounts`
|
|
463
|
+
|
|
464
|
+
List active QuickBooks bank and credit-card account IDs for recording external payments (admin only).
|
|
465
|
+
|
|
466
|
+
Legacy aliases: `list-external-payment-accounts`, `list_external_payment_accounts`.
|
|
467
|
+
|
|
468
|
+
Effects: none.
|
|
469
|
+
|
|
454
470
|
## `bb bill payment patch`
|
|
455
471
|
|
|
456
472
|
Patch Bill Payment.
|
package/dist/index.js
CHANGED
|
@@ -6071,7 +6071,7 @@ var ensurePrivateCredentialLockDirectory = async () => {
|
|
|
6071
6071
|
return directoryStat;
|
|
6072
6072
|
};
|
|
6073
6073
|
const directoryStat = await inspectDirectory();
|
|
6074
|
-
if ((directoryStat.mode & 63) !== 0) {
|
|
6074
|
+
if (process.platform !== "win32" && (directoryStat.mode & 63) !== 0) {
|
|
6075
6075
|
await chmod(directory, 448);
|
|
6076
6076
|
const securedDirectoryStat = await inspectDirectory();
|
|
6077
6077
|
if ((securedDirectoryStat.mode & 63) !== 0) {
|
|
@@ -7665,14 +7665,110 @@ function parseCreateBillPayload(raw, draftOnlyFlag, quotationExempt = false, req
|
|
|
7665
7665
|
}
|
|
7666
7666
|
function parseUpdateBillPayload(raw) {
|
|
7667
7667
|
const o = requireObject(raw, "update-bill payload");
|
|
7668
|
-
|
|
7669
|
-
if (draftOnly !== undefined) {
|
|
7668
|
+
if (o.draftOnly !== undefined) {
|
|
7670
7669
|
throw validationError("draftOnly is only supported when creating a bill.");
|
|
7671
7670
|
}
|
|
7672
|
-
|
|
7673
|
-
...input,
|
|
7671
|
+
const input = {
|
|
7674
7672
|
id: requiredString(o.id, "update-bill payload.id")
|
|
7675
7673
|
};
|
|
7674
|
+
const knownFields = new Set([
|
|
7675
|
+
"id",
|
|
7676
|
+
"budgetId",
|
|
7677
|
+
"projectId",
|
|
7678
|
+
"supplierId",
|
|
7679
|
+
"quotationId",
|
|
7680
|
+
"quotationIds",
|
|
7681
|
+
"quotationAllocations",
|
|
7682
|
+
"overBudgetOverrideReason",
|
|
7683
|
+
"amount",
|
|
7684
|
+
"budgetItemIds",
|
|
7685
|
+
"lineAmounts",
|
|
7686
|
+
"comment",
|
|
7687
|
+
"isClaimable",
|
|
7688
|
+
"attachments",
|
|
7689
|
+
"alreadyPaid",
|
|
7690
|
+
"paymentReference",
|
|
7691
|
+
"paymentProofAttachments",
|
|
7692
|
+
"extractedSupplierName",
|
|
7693
|
+
"extractedAmount",
|
|
7694
|
+
"invoiceNumber",
|
|
7695
|
+
"invoiceDate",
|
|
7696
|
+
"verificationStatus",
|
|
7697
|
+
"verificationResults"
|
|
7698
|
+
]);
|
|
7699
|
+
for (const key of Object.keys(o))
|
|
7700
|
+
if (!knownFields.has(key))
|
|
7701
|
+
throw validationError(`Unknown update-bill payload field: ${key}`);
|
|
7702
|
+
for (const key of [
|
|
7703
|
+
"budgetId",
|
|
7704
|
+
"projectId",
|
|
7705
|
+
"supplierId",
|
|
7706
|
+
"comment",
|
|
7707
|
+
"paymentReference",
|
|
7708
|
+
"extractedSupplierName",
|
|
7709
|
+
"invoiceNumber",
|
|
7710
|
+
"overBudgetOverrideReason"
|
|
7711
|
+
]) {
|
|
7712
|
+
if (o[key] === undefined)
|
|
7713
|
+
continue;
|
|
7714
|
+
if (typeof o[key] !== "string")
|
|
7715
|
+
throw validationError(`update-bill payload.${key} must be a string.`);
|
|
7716
|
+
input[key] = o[key];
|
|
7717
|
+
}
|
|
7718
|
+
for (const key of ["amount", "extractedAmount"]) {
|
|
7719
|
+
if (o[key] === undefined)
|
|
7720
|
+
continue;
|
|
7721
|
+
const amount = Number(o[key]);
|
|
7722
|
+
if (!Number.isFinite(amount) || amount < 0)
|
|
7723
|
+
throw validationError(`update-bill payload.${key} must be a number >= 0.`);
|
|
7724
|
+
input[key] = amount;
|
|
7725
|
+
}
|
|
7726
|
+
for (const key of ["isClaimable", "alreadyPaid"]) {
|
|
7727
|
+
if (o[key] === undefined)
|
|
7728
|
+
continue;
|
|
7729
|
+
if (typeof o[key] !== "boolean")
|
|
7730
|
+
throw validationError(`update-bill payload.${key} must be boolean.`);
|
|
7731
|
+
input[key] = o[key];
|
|
7732
|
+
}
|
|
7733
|
+
for (const key of ["attachments", "paymentProofAttachments"]) {
|
|
7734
|
+
const attachments = o[key];
|
|
7735
|
+
if (attachments === undefined)
|
|
7736
|
+
continue;
|
|
7737
|
+
if (!Array.isArray(attachments))
|
|
7738
|
+
throw validationError(`update-bill payload.${key} must be an array.`);
|
|
7739
|
+
input[key] = attachments.map((value, index) => billAttachment(value, index, key));
|
|
7740
|
+
}
|
|
7741
|
+
if (o.budgetItemIds !== undefined) {
|
|
7742
|
+
if (!Array.isArray(o.budgetItemIds) || !o.budgetItemIds.length)
|
|
7743
|
+
throw validationError("update-bill payload.budgetItemIds must be a non-empty array.");
|
|
7744
|
+
input.budgetItemIds = o.budgetItemIds.map((id, index) => requiredString(id, `update-bill payload.budgetItemIds[${index}]`));
|
|
7745
|
+
}
|
|
7746
|
+
Object.assign(input, parseQuotationReferences(o, "update-bill"));
|
|
7747
|
+
if (o.lineAmounts !== undefined)
|
|
7748
|
+
input.lineAmounts = parseBillLineAmounts(o, "update-bill");
|
|
7749
|
+
if (o.invoiceDate !== undefined)
|
|
7750
|
+
input.invoiceDate = toDate(o.invoiceDate, "invoiceDate");
|
|
7751
|
+
if (o.verificationStatus !== undefined) {
|
|
7752
|
+
if (!Object.values(BillVerificationStatus).includes(o.verificationStatus))
|
|
7753
|
+
throw validationError("Invalid bill verification status.");
|
|
7754
|
+
input.verificationStatus = o.verificationStatus;
|
|
7755
|
+
}
|
|
7756
|
+
if (o.verificationResults !== undefined) {
|
|
7757
|
+
const value = requireObject(o.verificationResults, "verificationResults");
|
|
7758
|
+
const status = value.status;
|
|
7759
|
+
if (status !== BillVerificationStatus.PENDING && status !== BillVerificationStatus.PASS && status !== BillVerificationStatus.FAIL && status !== BillVerificationStatus.MANUAL_REVIEW)
|
|
7760
|
+
throw validationError("Invalid verificationResults.status.");
|
|
7761
|
+
input.verificationResults = {
|
|
7762
|
+
status,
|
|
7763
|
+
extractedAmount: value.extractedAmount == null ? null : Number(value.extractedAmount),
|
|
7764
|
+
difference: value.difference == null ? null : Number(value.difference),
|
|
7765
|
+
confidence: value.confidence == null ? null : Number(value.confidence),
|
|
7766
|
+
failureReason: value.failureReason == null ? null : String(value.failureReason)
|
|
7767
|
+
};
|
|
7768
|
+
}
|
|
7769
|
+
if (Object.keys(input).length === 1)
|
|
7770
|
+
throw validationError("Supply at least one field to update.");
|
|
7771
|
+
return input;
|
|
7676
7772
|
}
|
|
7677
7773
|
function parseValidateBillSelectionPayload(raw, quotationFlags = {}) {
|
|
7678
7774
|
const input = requireObject(raw, "validate-bill-selection payload");
|
|
@@ -8508,6 +8604,7 @@ async function listBudgets(opts) {
|
|
|
8508
8604
|
createdBy: opts.createdBy,
|
|
8509
8605
|
dateFrom: opts.dateFrom,
|
|
8510
8606
|
dateTo: opts.dateTo,
|
|
8607
|
+
active: opts.active,
|
|
8511
8608
|
sortBy: opts.sortBy,
|
|
8512
8609
|
sortDir: opts.sortDir,
|
|
8513
8610
|
page: opts.page ?? 1,
|
|
@@ -8517,10 +8614,16 @@ async function listBudgets(opts) {
|
|
|
8517
8614
|
const result = await api2.budget.getAllBudgets.query(input);
|
|
8518
8615
|
out(result);
|
|
8519
8616
|
}
|
|
8520
|
-
async function getBudget(id) {
|
|
8617
|
+
async function getBudget(id, opts) {
|
|
8521
8618
|
const [budget, items] = await Promise.all([
|
|
8522
|
-
api2.budget.getBudget.query({
|
|
8523
|
-
|
|
8619
|
+
api2.budget.getBudget.query({
|
|
8620
|
+
id,
|
|
8621
|
+
includeInactive: opts?.includeInactive ?? false
|
|
8622
|
+
}),
|
|
8623
|
+
api2.budget.getBudgetItems.query({
|
|
8624
|
+
budgetId: id,
|
|
8625
|
+
includeInactive: opts?.includeInactive ?? false
|
|
8626
|
+
})
|
|
8524
8627
|
]);
|
|
8525
8628
|
out({ budget, items });
|
|
8526
8629
|
}
|
|
@@ -8836,6 +8939,42 @@ async function updateBillStatus(opts) {
|
|
|
8836
8939
|
} : undefined);
|
|
8837
8940
|
}
|
|
8838
8941
|
}
|
|
8942
|
+
var listExternalBillPaymentAccounts = async () => {
|
|
8943
|
+
out(await api2.bill.getExternalPaymentAccounts.query());
|
|
8944
|
+
};
|
|
8945
|
+
var recordExternalBillPayment = async (opts) => {
|
|
8946
|
+
if (!opts.paymentReference.trim() || !opts.paymentAccount.trim() || !/^\d{4}-\d{2}-\d{2}$/.test(opts.paymentDate) || new Date(`${opts.paymentDate}T00:00:00Z`).toISOString().slice(0, 10) !== opts.paymentDate)
|
|
8947
|
+
throw new Error("Supply --paymentReference, --paymentDate YYYY-MM-DD, and --paymentAccount <QBO account ID>.");
|
|
8948
|
+
await assertSensitiveWorkflowConfirmed({
|
|
8949
|
+
action: "Record external payment (no email)",
|
|
8950
|
+
entity: `bill ${opts.id}`,
|
|
8951
|
+
details: `already paid on ${opts.paymentDate}; reference ${opts.paymentReference}; QBO account ${opts.paymentAccount}. No money is transferred.`
|
|
8952
|
+
});
|
|
8953
|
+
const bill = await api2.bill.getById.query({ id: opts.id });
|
|
8954
|
+
const proof = !bill.externalPayment && opts.paymentProofPath ? await stageBillPaymentProof(bill.projectId, opts.paymentProofPath) : undefined;
|
|
8955
|
+
try {
|
|
8956
|
+
out(await api2.bill.recordExternalPayment.mutate({
|
|
8957
|
+
id: opts.id,
|
|
8958
|
+
paymentReference: opts.paymentReference,
|
|
8959
|
+
paymentDate: opts.paymentDate,
|
|
8960
|
+
paymentAccount: opts.paymentAccount,
|
|
8961
|
+
paymentProofAttachments: proof ? [proof] : undefined
|
|
8962
|
+
}));
|
|
8963
|
+
} catch (error) {
|
|
8964
|
+
try {
|
|
8965
|
+
const latest = await api2.bill.getById.query({ id: opts.id });
|
|
8966
|
+
if (latest.externalPayment?.paymentAccount === opts.paymentAccount && latest.paymentReference === opts.paymentReference.trim() && latest.paymentDate?.toISOString().slice(0, 10) === opts.paymentDate) {
|
|
8967
|
+
out({
|
|
8968
|
+
...latest,
|
|
8969
|
+
supplierNotified: false,
|
|
8970
|
+
quickbooksReconciliationPending: !latest.externalPayment.quickbooksBillPaymentIds.length
|
|
8971
|
+
});
|
|
8972
|
+
return;
|
|
8973
|
+
}
|
|
8974
|
+
} catch {}
|
|
8975
|
+
throw new Error("External payment result could not be confirmed. Inspect the bill, then retry the same reference/date/account; do not pay again. Any uploaded proof is retained.", { cause: error });
|
|
8976
|
+
}
|
|
8977
|
+
};
|
|
8839
8978
|
async function patchBillPayment(opts) {
|
|
8840
8979
|
const input = { id: opts.id };
|
|
8841
8980
|
if (opts.paymentTrackingUrl !== undefined)
|
|
@@ -9091,8 +9230,11 @@ async function getBillDetails(id) {
|
|
|
9091
9230
|
const result = await api2.bill.getById.query({ id });
|
|
9092
9231
|
out(result);
|
|
9093
9232
|
}
|
|
9094
|
-
async function getBudgetItemsOnly(budgetId) {
|
|
9095
|
-
const items = await api2.budget.getBudgetItems.query({
|
|
9233
|
+
async function getBudgetItemsOnly(budgetId, opts) {
|
|
9234
|
+
const items = await api2.budget.getBudgetItems.query({
|
|
9235
|
+
budgetId,
|
|
9236
|
+
includeInactive: opts?.includeInactive ?? false
|
|
9237
|
+
});
|
|
9096
9238
|
out(items);
|
|
9097
9239
|
}
|
|
9098
9240
|
async function addBudgetItems(opts) {
|
|
@@ -9193,8 +9335,11 @@ var sendEstimateToContactPersonFromPayload = async (raw) => {
|
|
|
9193
9335
|
const result = await api2.email.sendEstimateToContactPerson.mutate(input);
|
|
9194
9336
|
out(result);
|
|
9195
9337
|
};
|
|
9196
|
-
async function getBudgetDetails(budgetId) {
|
|
9197
|
-
const detail = await api2.budget.getBudgetDetail.query({
|
|
9338
|
+
async function getBudgetDetails(budgetId, opts) {
|
|
9339
|
+
const detail = await api2.budget.getBudgetDetail.query({
|
|
9340
|
+
id: budgetId,
|
|
9341
|
+
includeInactive: opts?.includeInactive ?? false
|
|
9342
|
+
});
|
|
9198
9343
|
out(detail);
|
|
9199
9344
|
}
|
|
9200
9345
|
async function createBudgetFromPayload(raw) {
|
|
@@ -10602,6 +10747,7 @@ var mutationPrefixes = [
|
|
|
10602
10747
|
"patch-",
|
|
10603
10748
|
"reactivate-",
|
|
10604
10749
|
"reconcile-",
|
|
10750
|
+
"record-",
|
|
10605
10751
|
"reject-",
|
|
10606
10752
|
"remove-",
|
|
10607
10753
|
"replace-",
|
|
@@ -10642,6 +10788,7 @@ var emailTargets = new Set([
|
|
|
10642
10788
|
"update-supplier"
|
|
10643
10789
|
]);
|
|
10644
10790
|
var externalWriteTargets = new Set([
|
|
10791
|
+
"record-external-bill-payment",
|
|
10645
10792
|
"approve-bill",
|
|
10646
10793
|
"approve-customer-invoice",
|
|
10647
10794
|
"approve-supplier",
|
|
@@ -10687,6 +10834,7 @@ var externalWriteTargets = new Set([
|
|
|
10687
10834
|
"void-customer-invoice"
|
|
10688
10835
|
]);
|
|
10689
10836
|
var financialWriteTargets = new Set([
|
|
10837
|
+
"record-external-bill-payment",
|
|
10690
10838
|
"import-qbo-customer-invoice",
|
|
10691
10839
|
"link-bill-quotations",
|
|
10692
10840
|
"approve-bill",
|
|
@@ -10768,11 +10916,27 @@ var summaryFor = (legacyTarget) => {
|
|
|
10768
10916
|
}
|
|
10769
10917
|
if (legacyTarget === "whoami")
|
|
10770
10918
|
return "Show the active API-key identity.";
|
|
10919
|
+
if (legacyTarget === "list-budgets") {
|
|
10920
|
+
return "List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.";
|
|
10921
|
+
}
|
|
10922
|
+
if (legacyTarget === "get-budget") {
|
|
10923
|
+
return "Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
|
|
10924
|
+
}
|
|
10925
|
+
if (legacyTarget === "get-budget-items") {
|
|
10926
|
+
return "Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
|
|
10927
|
+
}
|
|
10928
|
+
if (legacyTarget === "get-budget-details") {
|
|
10929
|
+
return "Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
|
|
10930
|
+
}
|
|
10771
10931
|
if (legacyTarget === "create-bill") {
|
|
10772
10932
|
return "Create a bill or claim with an approved supplier. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.";
|
|
10773
10933
|
}
|
|
10934
|
+
if (legacyTarget === "record-external-bill-payment")
|
|
10935
|
+
return "Record an already-made payment on an APPROVED bill or claim, with no email and no money transfer. Requires <billId> --paymentReference --paymentDate YYYY-MM-DD --paymentAccount <QBO account ID> [--paymentProof receipt.pdf]. Reuses settled QBO payment links or queues one dated payment from the selected account. Exact retries are safe; do not pay again while reconciliation is pending.";
|
|
10936
|
+
if (legacyTarget === "list-external-payment-accounts")
|
|
10937
|
+
return "List active QuickBooks bank and credit-card account IDs for recording external payments (admin only).";
|
|
10774
10938
|
if (legacyTarget === "update-bill") {
|
|
10775
|
-
return "Update a bill or claim
|
|
10939
|
+
return "Update a bill or claim with a partial payload including id; omitted fields are preserved. Comment-only updates are allowed on approved/paid bills without altering financial data or QuickBooks. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. Re-read and verify both amount fields and allocations after correcting a gross/net mistake.";
|
|
10776
10940
|
}
|
|
10777
10941
|
if (legacyTarget === "validate-bill-selection") {
|
|
10778
10942
|
return "Validate the approved supplier and selected bill line items.";
|
|
@@ -10838,12 +11002,32 @@ var registry2 = [
|
|
|
10838
11002
|
authCommand("status", "Show the active CLI authentication method and session status."),
|
|
10839
11003
|
authCommand("logout", "Revoke the interactive CLI session and remove its local credential."),
|
|
10840
11004
|
legacyCommand("whoami", ["auth", "whoami"]),
|
|
10841
|
-
legacyCommand("list-budgets", ["budget", "list"]
|
|
10842
|
-
|
|
11005
|
+
legacyCommand("list-budgets", ["budget", "list"], [], [
|
|
11006
|
+
{
|
|
11007
|
+
name: "--active <true|false|all>",
|
|
11008
|
+
description: "Omit for active budgets only; false or all requires Admin access."
|
|
11009
|
+
}
|
|
11010
|
+
]),
|
|
11011
|
+
legacyCommand("get-budget", ["budget", "get"], [], [
|
|
11012
|
+
{
|
|
11013
|
+
name: "--includeInactive",
|
|
11014
|
+
description: "Include inactive historical budgets; Admin only."
|
|
11015
|
+
}
|
|
11016
|
+
]),
|
|
10843
11017
|
legacyCommand("upload-budget-attachment", ["budget", "attachment", "upload"]),
|
|
10844
11018
|
legacyCommand("mark-budget-won", ["budget", "won", "mark"]),
|
|
10845
|
-
legacyCommand("get-budget-items", ["budget", "item", "list"]
|
|
10846
|
-
|
|
11019
|
+
legacyCommand("get-budget-items", ["budget", "item", "list"], [], [
|
|
11020
|
+
{
|
|
11021
|
+
name: "--includeInactive",
|
|
11022
|
+
description: "Include inactive historical budgets; Admin only."
|
|
11023
|
+
}
|
|
11024
|
+
]),
|
|
11025
|
+
legacyCommand("get-budget-details", ["budget", "detail", "get"], [], [
|
|
11026
|
+
{
|
|
11027
|
+
name: "--includeInactive",
|
|
11028
|
+
description: "Include inactive historical budgets; Admin only."
|
|
11029
|
+
}
|
|
11030
|
+
]),
|
|
10847
11031
|
legacyCommand("get-budget-categories", ["budget", "category", "list"]),
|
|
10848
11032
|
legacyCommand("get-budget-versions", ["budget", "version", "list"]),
|
|
10849
11033
|
legacyCommand("rename-budget-version", ["budget", "version", "rename"]),
|
|
@@ -10932,6 +11116,16 @@ var registry2 = [
|
|
|
10932
11116
|
]),
|
|
10933
11117
|
legacyCommand("delete-bill", ["bill", "delete"]),
|
|
10934
11118
|
legacyCommand("update-bill-status", ["bill", "status", "update"]),
|
|
11119
|
+
legacyCommand("record-external-bill-payment", [
|
|
11120
|
+
"bill",
|
|
11121
|
+
"payment",
|
|
11122
|
+
"record-external"
|
|
11123
|
+
]),
|
|
11124
|
+
legacyCommand("list-external-payment-accounts", [
|
|
11125
|
+
"bill",
|
|
11126
|
+
"payment",
|
|
11127
|
+
"accounts"
|
|
11128
|
+
]),
|
|
10935
11129
|
legacyCommand("patch-bill-payment", ["bill", "payment", "patch"]),
|
|
10936
11130
|
legacyCommand("patch-bill-invoice-number", [
|
|
10937
11131
|
"bill",
|
|
@@ -11377,6 +11571,22 @@ function parseOptionalBoolFlag(flags, key) {
|
|
|
11377
11571
|
return false;
|
|
11378
11572
|
throw new Error(`--${key} must be true or false.`);
|
|
11379
11573
|
}
|
|
11574
|
+
function parseBudgetActiveFlag(flags) {
|
|
11575
|
+
const raw = flags.active;
|
|
11576
|
+
if (raw === undefined)
|
|
11577
|
+
return;
|
|
11578
|
+
if (raw === true) {
|
|
11579
|
+
throw new Error("--active requires true, false, or all.");
|
|
11580
|
+
}
|
|
11581
|
+
const value = String(raw).toLowerCase();
|
|
11582
|
+
if (value === "all")
|
|
11583
|
+
return "all";
|
|
11584
|
+
if (value === "true" || value === "1")
|
|
11585
|
+
return true;
|
|
11586
|
+
if (value === "false" || value === "0")
|
|
11587
|
+
return false;
|
|
11588
|
+
throw new Error("--active must be true, false, or all.");
|
|
11589
|
+
}
|
|
11380
11590
|
var CUSTOMER_INVOICE_SORT_FIELDS = [
|
|
11381
11591
|
"createdAt",
|
|
11382
11592
|
"status",
|
|
@@ -12070,6 +12280,7 @@ Waiting for approval\u2026
|
|
|
12070
12280
|
createdBy: getFlag(flags, "createdBy"),
|
|
12071
12281
|
dateFrom: getFlag(flags, "dateFrom"),
|
|
12072
12282
|
dateTo: getFlag(flags, "dateTo"),
|
|
12283
|
+
active: parseBudgetActiveFlag(flags),
|
|
12073
12284
|
sortBy: getFlag(flags, "sortBy"),
|
|
12074
12285
|
sortDir: getFlag(flags, "sortDir"),
|
|
12075
12286
|
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
@@ -12082,7 +12293,9 @@ Waiting for approval\u2026
|
|
|
12082
12293
|
const id = positional[0];
|
|
12083
12294
|
if (!id)
|
|
12084
12295
|
throw new Error("get-budget requires <id>");
|
|
12085
|
-
await getBudget(id
|
|
12296
|
+
await getBudget(id, {
|
|
12297
|
+
includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
|
|
12298
|
+
});
|
|
12086
12299
|
break;
|
|
12087
12300
|
}
|
|
12088
12301
|
case "upload-budget-attachment": {
|
|
@@ -12109,14 +12322,18 @@ Waiting for approval\u2026
|
|
|
12109
12322
|
const id = positional[0];
|
|
12110
12323
|
if (!id)
|
|
12111
12324
|
throw new Error("get-budget-items requires <budgetId>");
|
|
12112
|
-
await getBudgetItemsOnly(id
|
|
12325
|
+
await getBudgetItemsOnly(id, {
|
|
12326
|
+
includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
|
|
12327
|
+
});
|
|
12113
12328
|
break;
|
|
12114
12329
|
}
|
|
12115
12330
|
case "get-budget-details": {
|
|
12116
12331
|
const id = positional[0];
|
|
12117
12332
|
if (!id)
|
|
12118
12333
|
throw new Error("get-budget-details requires <budgetId>");
|
|
12119
|
-
await getBudgetDetails(id
|
|
12334
|
+
await getBudgetDetails(id, {
|
|
12335
|
+
includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
|
|
12336
|
+
});
|
|
12120
12337
|
break;
|
|
12121
12338
|
}
|
|
12122
12339
|
case "get-budget-categories": {
|
|
@@ -12508,6 +12725,27 @@ Waiting for approval\u2026
|
|
|
12508
12725
|
});
|
|
12509
12726
|
break;
|
|
12510
12727
|
}
|
|
12728
|
+
case "list-external-payment-accounts": {
|
|
12729
|
+
await listExternalBillPaymentAccounts();
|
|
12730
|
+
break;
|
|
12731
|
+
}
|
|
12732
|
+
case "record-external-bill-payment": {
|
|
12733
|
+
const id = positional[0];
|
|
12734
|
+
const paymentReference = getFlag(flags, "paymentReference");
|
|
12735
|
+
const paymentDate = getFlag(flags, "paymentDate");
|
|
12736
|
+
const paymentAccount = getFlag(flags, "paymentAccount");
|
|
12737
|
+
const paymentProofPath = getFlag(flags, "paymentProof");
|
|
12738
|
+
if (!id || !paymentReference || !paymentDate || !paymentAccount)
|
|
12739
|
+
throw new Error("record-external-bill-payment requires <billId> --paymentReference --paymentDate YYYY-MM-DD --paymentAccount <QBO account ID> [--paymentProof receipt.pdf]");
|
|
12740
|
+
await recordExternalBillPayment({
|
|
12741
|
+
id,
|
|
12742
|
+
paymentReference,
|
|
12743
|
+
paymentDate,
|
|
12744
|
+
paymentAccount,
|
|
12745
|
+
paymentProofPath
|
|
12746
|
+
});
|
|
12747
|
+
break;
|
|
12748
|
+
}
|
|
12511
12749
|
case "patch-bill-payment": {
|
|
12512
12750
|
const id = positional[0];
|
|
12513
12751
|
if (!id)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.44.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",
|