@go-labs-sg/bb 1.17.0 → 1.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 +14 -8
- package/dist/commands.js +385 -51
- package/dist/index.js +375 -24
- package/dist/parse-cli-enums.js +41 -3
- package/dist/parse-mutation-payload.js +52 -2
- package/dist/prisma-enums.js +16 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import "./load-env.js";
|
|
3
3
|
import { requireApiKey } from "./api-client.js";
|
|
4
4
|
import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
|
|
5
|
-
import { addBudgetItems, approveBill, approveBudget, approveQuotation, approveSupplier, createApiKeyForUser, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createEstimate, createItemCategory, createItemFromPayload, createProject, createQuotationFromPayload, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, createUser, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteCompanyById, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteSuppliersByIds, downloadQuotationPdf, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getQuotationDetails, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, listApiKeysForUser, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listQuotations, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, rejectBill, rejectBudget, rejectQuotation, rejectSupplier, removeBudgetItem, reorderBudgetItemsCli, revokeApiKeyForUser, submitQuotation, updateBillFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBillAttachmentsFromPaths, uploadBillDocumentsFromPaths, uploadBudgetAttachmentFromPath, uploadQuotationAttachmentFromPath, } from "./commands.js";
|
|
5
|
+
import { addBudgetItems, approveBill, approveBudget, approveCustomerInvoice, approveQuotation, approveSupplier, checkCustomerInvoiceReadiness, checkProjectReconciliation, cleanupStagedBillAttachments, cleanupStagedQuotationAttachments, completeProject, createApiKeyForUser, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createCustomerInvoice, createEstimate, 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, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getQuotationDetails, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, importQuickBooksProjectId, listApiKeysForUser, listApprovals, listBills, listBudgets, listCompanies, listContacts, listCustomerInvoices, listIntegrationOperations, listItemCategories, listItems, listProjects, listQuotations, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, reconcileProject, rejectBill, rejectBudget, rejectCustomerInvoice, rejectQuotation, rejectSupplier, removeBudgetItem, renameBudgetVersion, reorderBudgetItemsCli, restoreBudgetVersion, retryIntegrationOperation, revokeApiKeyForUser, setBudgetItemsNotUtilized, stageBillAttachmentsFromPaths, submitQuotation, syncCustomerInvoice, updateBillFromPayload, updateBillPaymentEvidenceFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateQuotationFromPayload, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBillAttachmentsFromPaths, uploadBillDocumentsFromPaths, uploadBudgetAttachmentFromPath, uploadQuotationAttachmentFromPath, validateBillSelectionFromPayload, voidCustomerInvoice, } from "./commands.js";
|
|
6
6
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
7
|
-
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedIds, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, parseUserRole, projectStatusesForHelp, userRolesForHelp, } from "./parse-cli-enums.js";
|
|
7
|
+
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedCustomerInvoiceStatuses, parseCommaSeparatedIds, parseCommaSeparatedQuotationStatuses, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, parseUserRole, projectStatusesForHelp, userRolesForHelp, } from "./parse-cli-enums.js";
|
|
8
8
|
import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
9
9
|
function parsePositiveIntFlag(value, flagName) {
|
|
10
10
|
if (value === undefined)
|
|
@@ -26,6 +26,35 @@ function parseOptionalBoolFlag(flags, key) {
|
|
|
26
26
|
return false;
|
|
27
27
|
throw new Error(`--${key} must be true or false.`);
|
|
28
28
|
}
|
|
29
|
+
const CUSTOMER_INVOICE_SORT_FIELDS = [
|
|
30
|
+
"createdAt",
|
|
31
|
+
"status",
|
|
32
|
+
"totalNetAmount",
|
|
33
|
+
"approvalExpiresAt",
|
|
34
|
+
];
|
|
35
|
+
function parseCustomerInvoiceSortBy(value) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return "createdAt";
|
|
38
|
+
if (!CUSTOMER_INVOICE_SORT_FIELDS.includes(value)) {
|
|
39
|
+
throw new Error(`Invalid --sortBy "${value}". Use one of: ${CUSTOMER_INVOICE_SORT_FIELDS.join(", ")}.`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
const INTEGRATION_OPERATION_STATUSES = [
|
|
44
|
+
"PENDING",
|
|
45
|
+
"PROCESSING",
|
|
46
|
+
"FAILED",
|
|
47
|
+
"COMPLETED",
|
|
48
|
+
];
|
|
49
|
+
function parseIntegrationOperationStatus(value) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const normalized = value.toUpperCase();
|
|
53
|
+
if (!INTEGRATION_OPERATION_STATUSES.includes(normalized)) {
|
|
54
|
+
throw new Error(`Invalid --status "${value}". Use one of: ${INTEGRATION_OPERATION_STATUSES.join(", ")}.`);
|
|
55
|
+
}
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|
|
29
58
|
function parseCommercialRejectionProjectStatus(flags) {
|
|
30
59
|
const raw = flags.projectStatusOnCommercialRejection;
|
|
31
60
|
if (raw === undefined)
|
|
@@ -120,8 +149,9 @@ Global: --quiet | -q | BB_CLI_QUIET=1 — hide action logs (default: log command
|
|
|
120
149
|
Auth: Set BB_API_KEY in the environment (same API key as MCP / Goracle).
|
|
121
150
|
|
|
122
151
|
Sensitive workflow changes: approval decisions/requests, entity status updates,
|
|
123
|
-
mark-budget-won, submit-quotation, and
|
|
124
|
-
require an interactive CONFIRM prompt
|
|
152
|
+
customer invoices, integration retries, mark-budget-won, submit-quotation, and
|
|
153
|
+
supplier approval request side effects require an interactive CONFIRM prompt
|
|
154
|
+
and abort in non-interactive shells.
|
|
125
155
|
AI agents must inspect the entity first, summarize the exact entity, target
|
|
126
156
|
state, and side effects, then wait for explicit user confirmation before running
|
|
127
157
|
the sensitive command.
|
|
@@ -133,6 +163,8 @@ Budgets
|
|
|
133
163
|
get-budget-details <id> Full detail (margins, suppliers, etc.)
|
|
134
164
|
get-budget-categories
|
|
135
165
|
get-budget-versions <budgetId>
|
|
166
|
+
rename-budget-version <versionId> --name <text>
|
|
167
|
+
restore-budget-version <versionId> Replace current budget data and return it to DRAFT.
|
|
136
168
|
update-budget-status <id> <status> [--projectManagerId <id>] [--markProjectWon true|false] [--projectStatusOnCommercialRejection PITCH|LOST]
|
|
137
169
|
Budget-changing commands require interactive "yes" confirmation when the current budget is ESTIMATE_CREATED, ESTIMATE_SENT, ESTIMATE_ACCEPTED, ESTIMATE_REJECTED, or ESTIMATE_CLOSED.
|
|
138
170
|
ESTIMATE_ACCEPTED requires win proof, auto-marks PITCH or LOST projects WON, requires --projectManagerId when the project has no PM, and auto-creates the Asana project/section when enabled.
|
|
@@ -145,7 +177,7 @@ Budgets
|
|
|
145
177
|
create-budget-approval <budgetId> (also sends approval request emails)
|
|
146
178
|
create-estimate <budgetId> (creates QuickBooks estimate; supports preflight updates)
|
|
147
179
|
add-budget-items --budgetId --items '[{"itemId":"…","quantity":1,"markup":30},…]'
|
|
148
|
-
update-budget-item --id <budgetItemId> [--description plain-text] [--note plain-text] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false]
|
|
180
|
+
update-budget-item --id <budgetItemId> [--description plain-text] [--note plain-text] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false] [--gstOutOfScope true|false]
|
|
149
181
|
remove-budget-item <budgetItemId>
|
|
150
182
|
reorder-budget-items <budgetId> --itemIds <csv>
|
|
151
183
|
update-budget-item-supplier --budgetItemId --supplierId
|
|
@@ -154,6 +186,11 @@ Budgets
|
|
|
154
186
|
delete-budget-category <id>
|
|
155
187
|
update-budget-commission --payload '<json>' (budget.updateBudgetCommission)
|
|
156
188
|
update-budget-discount --payload '<json>' (budget.updateBudgetDiscount; type percentage|amount)
|
|
189
|
+
delete-budget-commission <budgetId> Remove commission and return the budget to DRAFT.
|
|
190
|
+
delete-budget-discount <budgetId> Remove discount and return the budget to DRAFT.
|
|
191
|
+
mark-budget-items-not-utilized --ids <csv> Remove unused lines from reconciliation and clear QBO placeholders.
|
|
192
|
+
restore-budget-item <budgetItemId> Restore one Not Utilized line and its QBO placeholder when required.
|
|
193
|
+
create-placeholder-bill <budgetItemId> Admin-only placeholder recovery.
|
|
157
194
|
upload-budget-attachment <budgetId> <filePath> Presigned S3 upload + confirm
|
|
158
195
|
|
|
159
196
|
Bills
|
|
@@ -162,11 +199,16 @@ Bills
|
|
|
162
199
|
isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
|
|
163
200
|
New supplier bills from 1 Jul 2026 00:00 SGT require approved quotation coverage for each non-legacy line item.
|
|
164
201
|
create-bill-approval <billId> (also sends approval request emails)
|
|
165
|
-
create-bill --payload '<json>' (bill.create; payload.isClaimable false = bill, true = claim
|
|
202
|
+
create-bill --payload '<json>' (bill.create; payload.isClaimable false = bill, true = claim)
|
|
203
|
+
validate-bill-selection --payload '<json>' Check projectId, supplierId, budgetItemIds, and optional alreadyPaid before creation.
|
|
204
|
+
stage-bill-attachment <projectId> <filePath...> [--file <path>] [--files <csv>] Upload files before create-bill; returns attachment JSON for attachments/paymentProofAttachments
|
|
205
|
+
cleanup-staged-bill-attachments <projectId> --keys <csv> Delete unattached staged bill/claim uploads.
|
|
166
206
|
update-bill --payload '<json>' (bill.update; must include id)
|
|
207
|
+
update-bill-payment-evidence --payload '<json>' Replace paymentReference/paymentProofAttachments for an already-paid bill.
|
|
167
208
|
delete-bill <billId>
|
|
168
209
|
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference]
|
|
169
210
|
status (${billStatusesForUpdateHelp.join(", ")}) — not PENDING_APPROVAL; use create-bill-approval
|
|
211
|
+
Moving a bill or claim to PAID sends creator and supplier emails, CC m@getout.sg and BCC noah@getout.sg; duplicate claim recipients receive one email.
|
|
170
212
|
patch-bill-payment <billId> [--paymentTrackingUrl] [--paymentReference] [--quickbooksBillId] [--paymentDate <ISO>]
|
|
171
213
|
Clear a field: --clearPaymentTrackingUrl true | --clearPaymentReference true | --clearQuickbooksBillId true | --clearPaymentDate true
|
|
172
214
|
patch-bill-invoice-number <billId> <invoiceNumber>
|
|
@@ -177,17 +219,33 @@ Bills
|
|
|
177
219
|
get-bill-details <billId>
|
|
178
220
|
|
|
179
221
|
Quotations
|
|
180
|
-
list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--page] [--pageSize]
|
|
222
|
+
list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--requesterIds CSV] [--search] [--page] [--pageSize]
|
|
181
223
|
get-quotation <quotationId>
|
|
182
|
-
upload-quotation-attachment <projectId> <filePath>
|
|
183
|
-
|
|
224
|
+
upload-quotation-attachment <projectId> <filePath> Upload one PDF, JPEG, or PNG up to 20MB; returns attachment JSON for a quotation payload.
|
|
225
|
+
cleanup-staged-quotation-attachments <projectId> --keys <csv> Delete unattached staged quotation uploads.
|
|
226
|
+
create-quotation --payload '<json>' (quotation.createDraft; requires amountWithoutGst and gstAmount)
|
|
227
|
+
update-quotation --payload '<json>' Update a DRAFT or REJECTED quotation; same payload plus id.
|
|
228
|
+
delete-quotation <quotationId>
|
|
184
229
|
submit-quotation <quotationId>
|
|
185
230
|
approve-quotation <quotationId>
|
|
186
231
|
reject-quotation <quotationId> --reason <text>
|
|
187
232
|
download-quotation-pdf <quotationId> [original|staff|final]
|
|
188
233
|
|
|
234
|
+
Customer invoices
|
|
235
|
+
check-customer-invoice-readiness <budgetId>
|
|
236
|
+
list-customer-invoices <budgetId> [--status CSV] [--search] [--sortBy createdAt|status|totalNetAmount|approvalExpiresAt] [--sortDir asc|desc] [--page] [--perPage]
|
|
237
|
+
get-customer-invoice <batchId>
|
|
238
|
+
create-customer-invoice --payload '<json>' budgetId + one split with label, percentage, and dueDate.
|
|
239
|
+
discard-customer-invoice <batchId> Discard a reserved CREATING batch with no created QBO invoices.
|
|
240
|
+
delete-customer-invoice <batchId> Delete QBO invoices and the local batch.
|
|
241
|
+
void-customer-invoice <batchId> Void the invoice batch in QuickBooks.
|
|
242
|
+
approve-customer-invoice <batchId> Admin approval; notifies the creator.
|
|
243
|
+
reject-customer-invoice <batchId> --reason <text> Admin rejection; voids QBO invoices and notifies the creator.
|
|
244
|
+
download-customer-invoice-pdf <invoiceId> [--output <path>]
|
|
245
|
+
sync-customer-invoice <invoiceId> Refresh local status and balance from QuickBooks.
|
|
246
|
+
|
|
189
247
|
Approvals
|
|
190
|
-
list-approvals | get-pending-approvals [--type budget|supplier|bill|quotation|all]
|
|
248
|
+
list-approvals | get-pending-approvals [--type budget|supplier|bill|quotation|customer_invoice|all]
|
|
191
249
|
approve-bill <billId> (also sends requester reply email)
|
|
192
250
|
reject-bill <billId> --reason <text> (also sends requester reply email)
|
|
193
251
|
approve-budget <budgetId> (also sends requester reply email)
|
|
@@ -205,11 +263,16 @@ Companies & projects
|
|
|
205
263
|
list-projects [--companyId] [--name] [--status] [--active true|false] [--page] [--perPage]
|
|
206
264
|
Omit --active to include both active and inactive projects.
|
|
207
265
|
get-project <id>
|
|
208
|
-
|
|
209
|
-
|
|
266
|
+
Budget overview totalRevenue includes Estimate Accepted and Estimate Closed budgets only.
|
|
267
|
+
create-project --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --startDate <ISO> [--projectManagerId <userId>] [--asanaTaskId] [--asanaSearch <text>] [--slackChannelId --slackChannelUrl --slackChannelName] [--pax] [--endDate <ISO>] [--description]
|
|
268
|
+
update-project --payload '<json>' (project.updateProject; optional projectManagerId and requestQboAccountantNotification; default notify like web)
|
|
210
269
|
delete-project <id>
|
|
211
|
-
|
|
212
|
-
|
|
270
|
+
check-project-reconciliation <id> Run the same live reconciliation review as the web app without changing status.
|
|
271
|
+
reconcile-project <id> Rerun all checks transactionally and mark an eligible WON project RECONCILED.
|
|
272
|
+
complete-project <id> Rerun reconciliation validation, mark a RECONCILED project COMPLETED, and queue QBO placeholder cleanup.
|
|
273
|
+
import-qbo-project <projectId> --url <quickBooksProjectUrl>
|
|
274
|
+
update-project-status <id> <status> [--projectManagerId <userId>] [--projectManagerEmail <email>] [--wonOverrideReason <text>]
|
|
275
|
+
Required when moving PITCH → WON: supply --projectManagerId or --projectManagerEmail.
|
|
213
276
|
Required when moving to WON without accepted/closed budget or win proof: --wonOverrideReason (10+ chars).
|
|
214
277
|
status: ${projectStatusesForHelp.join(", ")}
|
|
215
278
|
|
|
@@ -258,6 +321,10 @@ Errors
|
|
|
258
321
|
get-recent-errors [--page] [--perPage] [--severity] [--status]
|
|
259
322
|
get-error-metrics
|
|
260
323
|
|
|
324
|
+
Automation (admin)
|
|
325
|
+
list-integration-operations [--destination] [--status PENDING|PROCESSING|FAILED|COMPLETED] [--page] [--perPage]
|
|
326
|
+
retry-integration-operation <operationId> [--confirmExternalStateReconciled true|false]
|
|
327
|
+
|
|
261
328
|
Historical / benchmarks
|
|
262
329
|
get-approved-budgets [--projectId] [--limit] [--minRevenue] [--maxRevenue]
|
|
263
330
|
get-budget-category-benchmarks <categoryId> [--limit]
|
|
@@ -374,6 +441,23 @@ async function main() {
|
|
|
374
441
|
await getBudgetVersions(id);
|
|
375
442
|
break;
|
|
376
443
|
}
|
|
444
|
+
case "rename-budget-version": {
|
|
445
|
+
const versionId = positional[0];
|
|
446
|
+
const newName = getFlag(flags, "name");
|
|
447
|
+
if (!versionId || newName === undefined) {
|
|
448
|
+
throw new Error("rename-budget-version requires <versionId> --name <text>");
|
|
449
|
+
}
|
|
450
|
+
await renameBudgetVersion(versionId, String(newName));
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "restore-budget-version": {
|
|
454
|
+
const versionId = positional[0];
|
|
455
|
+
if (!versionId) {
|
|
456
|
+
throw new Error("restore-budget-version requires <versionId>");
|
|
457
|
+
}
|
|
458
|
+
await restoreBudgetVersion(versionId);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
377
461
|
case "update-budget-status": {
|
|
378
462
|
const [id, status] = positional;
|
|
379
463
|
if (!id || !status) {
|
|
@@ -478,6 +562,7 @@ async function main() {
|
|
|
478
562
|
unitPrice: parseOptNum(getFlag(flags, "unitPrice"), "--unitPrice"),
|
|
479
563
|
isFreeOfCharge: parseOptionalBoolFlag(flags, "isFreeOfCharge"),
|
|
480
564
|
gstInclusive: parseOptionalBoolFlag(flags, "gstInclusive"),
|
|
565
|
+
gstOutOfScope: parseOptionalBoolFlag(flags, "gstOutOfScope"),
|
|
481
566
|
});
|
|
482
567
|
break;
|
|
483
568
|
}
|
|
@@ -556,6 +641,46 @@ async function main() {
|
|
|
556
641
|
await updateBudgetDiscountFromPayload(raw);
|
|
557
642
|
break;
|
|
558
643
|
}
|
|
644
|
+
case "delete-budget-commission": {
|
|
645
|
+
const budgetId = positional[0];
|
|
646
|
+
if (!budgetId) {
|
|
647
|
+
throw new Error("delete-budget-commission requires <budgetId>");
|
|
648
|
+
}
|
|
649
|
+
await deleteBudgetCommission(budgetId);
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
case "delete-budget-discount": {
|
|
653
|
+
const budgetId = positional[0];
|
|
654
|
+
if (!budgetId) {
|
|
655
|
+
throw new Error("delete-budget-discount requires <budgetId>");
|
|
656
|
+
}
|
|
657
|
+
await deleteBudgetDiscount(budgetId);
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "mark-budget-items-not-utilized": {
|
|
661
|
+
const budgetItemIds = parseCommaSeparatedIds(getFlag(flags, "ids"));
|
|
662
|
+
if (!budgetItemIds?.length) {
|
|
663
|
+
throw new Error("mark-budget-items-not-utilized requires --ids <csv>");
|
|
664
|
+
}
|
|
665
|
+
await setBudgetItemsNotUtilized(budgetItemIds, true);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case "restore-budget-item": {
|
|
669
|
+
const budgetItemId = positional[0];
|
|
670
|
+
if (!budgetItemId) {
|
|
671
|
+
throw new Error("restore-budget-item requires <budgetItemId>");
|
|
672
|
+
}
|
|
673
|
+
await setBudgetItemsNotUtilized([budgetItemId], false);
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
case "create-placeholder-bill": {
|
|
677
|
+
const budgetItemId = positional[0];
|
|
678
|
+
if (!budgetItemId) {
|
|
679
|
+
throw new Error("create-placeholder-bill requires <budgetItemId>");
|
|
680
|
+
}
|
|
681
|
+
await createPlaceholderBillForBudgetItem(budgetItemId);
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
559
684
|
case "list-bills": {
|
|
560
685
|
await listBills(parseListBillsFlags(flags));
|
|
561
686
|
break;
|
|
@@ -583,6 +708,15 @@ async function main() {
|
|
|
583
708
|
await createBillFromPayload(raw);
|
|
584
709
|
break;
|
|
585
710
|
}
|
|
711
|
+
case "validate-bill-selection": {
|
|
712
|
+
const payloadRaw = getFlag(flags, "payload");
|
|
713
|
+
if (!payloadRaw) {
|
|
714
|
+
throw new Error("validate-bill-selection requires --payload '<json>'");
|
|
715
|
+
}
|
|
716
|
+
const raw = parseJsonFlag(String(payloadRaw), "--payload");
|
|
717
|
+
await validateBillSelectionFromPayload(raw);
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
586
720
|
case "update-bill": {
|
|
587
721
|
const payloadRaw = getFlag(flags, "payload");
|
|
588
722
|
if (!payloadRaw) {
|
|
@@ -592,6 +726,15 @@ async function main() {
|
|
|
592
726
|
await updateBillFromPayload(raw);
|
|
593
727
|
break;
|
|
594
728
|
}
|
|
729
|
+
case "update-bill-payment-evidence": {
|
|
730
|
+
const payloadRaw = getFlag(flags, "payload");
|
|
731
|
+
if (!payloadRaw) {
|
|
732
|
+
throw new Error("update-bill-payment-evidence requires --payload '<json>'");
|
|
733
|
+
}
|
|
734
|
+
const raw = parseJsonFlag(String(payloadRaw), "--payload");
|
|
735
|
+
await updateBillPaymentEvidenceFromPayload(raw);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
595
738
|
case "delete-bill": {
|
|
596
739
|
const id = positional[0];
|
|
597
740
|
if (!id)
|
|
@@ -690,6 +833,35 @@ async function main() {
|
|
|
690
833
|
await getBillAttachments(id);
|
|
691
834
|
break;
|
|
692
835
|
}
|
|
836
|
+
case "stage-bill-attachment": {
|
|
837
|
+
const [projectId, ...positionalFilePaths] = positional;
|
|
838
|
+
const fileFlag = getFlag(flags, "file");
|
|
839
|
+
const filesFlag = getFlag(flags, "files");
|
|
840
|
+
const filePaths = [
|
|
841
|
+
...positionalFilePaths,
|
|
842
|
+
...(fileFlag !== undefined ? [String(fileFlag)] : []),
|
|
843
|
+
...(filesFlag !== undefined
|
|
844
|
+
? String(filesFlag)
|
|
845
|
+
.split(",")
|
|
846
|
+
.map((value) => value.trim())
|
|
847
|
+
.filter(Boolean)
|
|
848
|
+
: []),
|
|
849
|
+
];
|
|
850
|
+
if (!projectId || filePaths.length === 0) {
|
|
851
|
+
throw new Error("stage-bill-attachment requires <projectId> <filePath...> or --file/--files");
|
|
852
|
+
}
|
|
853
|
+
await stageBillAttachmentsFromPaths(projectId, filePaths);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "cleanup-staged-bill-attachments": {
|
|
857
|
+
const projectId = positional[0];
|
|
858
|
+
const keys = parseCommaSeparatedIds(getFlag(flags, "keys"));
|
|
859
|
+
if (!projectId || !keys) {
|
|
860
|
+
throw new Error("cleanup-staged-bill-attachments requires <projectId> --keys <csv>");
|
|
861
|
+
}
|
|
862
|
+
await cleanupStagedBillAttachments(projectId, keys);
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
693
865
|
case "upload-bill-attachment":
|
|
694
866
|
case "attach-bill-attachment":
|
|
695
867
|
case "attach-bill-payment-receipt": {
|
|
@@ -770,12 +942,9 @@ async function main() {
|
|
|
770
942
|
projectId: getFlag(flags, "projectId"),
|
|
771
943
|
budgetId: getFlag(flags, "budgetId"),
|
|
772
944
|
supplierId: getFlag(flags, "supplierId"),
|
|
773
|
-
status: statusRaw
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.map((value) => value.trim().toUpperCase())
|
|
777
|
-
.filter(Boolean)
|
|
778
|
-
: undefined,
|
|
945
|
+
status: parseCommaSeparatedQuotationStatuses(statusRaw),
|
|
946
|
+
requesterIds: parseCommaSeparatedIds(getFlag(flags, "requesterIds")),
|
|
947
|
+
search: getFlag(flags, "search"),
|
|
779
948
|
page: getFlag(flags, "page") !== undefined
|
|
780
949
|
? Number(getFlag(flags, "page"))
|
|
781
950
|
: undefined,
|
|
@@ -800,6 +969,15 @@ async function main() {
|
|
|
800
969
|
await uploadQuotationAttachmentFromPath(projectId, filePath);
|
|
801
970
|
break;
|
|
802
971
|
}
|
|
972
|
+
case "cleanup-staged-quotation-attachments": {
|
|
973
|
+
const projectId = positional[0];
|
|
974
|
+
const keys = parseCommaSeparatedIds(getFlag(flags, "keys"));
|
|
975
|
+
if (!projectId || !keys) {
|
|
976
|
+
throw new Error("cleanup-staged-quotation-attachments requires <projectId> --keys <csv>");
|
|
977
|
+
}
|
|
978
|
+
await cleanupStagedQuotationAttachments(projectId, keys);
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
803
981
|
case "create-quotation": {
|
|
804
982
|
const payloadRaw = getFlag(flags, "payload");
|
|
805
983
|
if (!payloadRaw) {
|
|
@@ -809,6 +987,22 @@ async function main() {
|
|
|
809
987
|
await createQuotationFromPayload(raw);
|
|
810
988
|
break;
|
|
811
989
|
}
|
|
990
|
+
case "update-quotation": {
|
|
991
|
+
const payloadRaw = getFlag(flags, "payload");
|
|
992
|
+
if (!payloadRaw) {
|
|
993
|
+
throw new Error("update-quotation requires --payload '<json>'");
|
|
994
|
+
}
|
|
995
|
+
const raw = parseJsonFlag(String(payloadRaw), "--payload");
|
|
996
|
+
await updateQuotationFromPayload(raw);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
case "delete-quotation": {
|
|
1000
|
+
const id = positional[0];
|
|
1001
|
+
if (!id)
|
|
1002
|
+
throw new Error("delete-quotation requires <quotationId>");
|
|
1003
|
+
await deleteQuotationById(id);
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
812
1006
|
case "submit-quotation": {
|
|
813
1007
|
const id = positional[0];
|
|
814
1008
|
if (!id)
|
|
@@ -842,10 +1036,114 @@ async function main() {
|
|
|
842
1036
|
? "ORIGINAL"
|
|
843
1037
|
: roleRaw === "staff"
|
|
844
1038
|
? "STAFF_SIGNED"
|
|
845
|
-
: "
|
|
1039
|
+
: roleRaw === "final"
|
|
1040
|
+
? "FINAL_SIGNED"
|
|
1041
|
+
: null;
|
|
1042
|
+
if (!fileRole) {
|
|
1043
|
+
throw new Error("download-quotation-pdf role must be original, staff, or final");
|
|
1044
|
+
}
|
|
846
1045
|
await downloadQuotationPdf(id, fileRole);
|
|
847
1046
|
break;
|
|
848
1047
|
}
|
|
1048
|
+
case "check-customer-invoice-readiness": {
|
|
1049
|
+
const budgetId = positional[0];
|
|
1050
|
+
if (!budgetId) {
|
|
1051
|
+
throw new Error("check-customer-invoice-readiness requires <budgetId>");
|
|
1052
|
+
}
|
|
1053
|
+
await checkCustomerInvoiceReadiness(budgetId);
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
case "list-customer-invoices": {
|
|
1057
|
+
const budgetId = positional[0];
|
|
1058
|
+
if (!budgetId) {
|
|
1059
|
+
throw new Error("list-customer-invoices requires <budgetId>");
|
|
1060
|
+
}
|
|
1061
|
+
const statusRaw = getFlag(flags, "status");
|
|
1062
|
+
const sortDir = parseOptionalBillListSortDir(getFlag(flags, "sortDir"));
|
|
1063
|
+
await listCustomerInvoices({
|
|
1064
|
+
budgetId,
|
|
1065
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
|
|
1066
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 12,
|
|
1067
|
+
q: getFlag(flags, "search") ?? "",
|
|
1068
|
+
statuses: parseCommaSeparatedCustomerInvoiceStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
|
|
1069
|
+
sortBy: parseCustomerInvoiceSortBy(getFlag(flags, "sortBy")),
|
|
1070
|
+
sortDir: sortDir ?? "desc",
|
|
1071
|
+
});
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
case "get-customer-invoice": {
|
|
1075
|
+
const batchId = positional[0];
|
|
1076
|
+
if (!batchId) {
|
|
1077
|
+
throw new Error("get-customer-invoice requires <batchId>");
|
|
1078
|
+
}
|
|
1079
|
+
await getCustomerInvoice(batchId);
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case "create-customer-invoice": {
|
|
1083
|
+
const payloadRaw = getFlag(flags, "payload");
|
|
1084
|
+
if (!payloadRaw) {
|
|
1085
|
+
throw new Error("create-customer-invoice requires --payload '<json>'");
|
|
1086
|
+
}
|
|
1087
|
+
await createCustomerInvoice(parseJsonFlag(String(payloadRaw), "--payload"));
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
case "discard-customer-invoice": {
|
|
1091
|
+
const batchId = positional[0];
|
|
1092
|
+
if (!batchId) {
|
|
1093
|
+
throw new Error("discard-customer-invoice requires <batchId>");
|
|
1094
|
+
}
|
|
1095
|
+
await discardCreatingCustomerInvoice(batchId);
|
|
1096
|
+
break;
|
|
1097
|
+
}
|
|
1098
|
+
case "delete-customer-invoice": {
|
|
1099
|
+
const batchId = positional[0];
|
|
1100
|
+
if (!batchId) {
|
|
1101
|
+
throw new Error("delete-customer-invoice requires <batchId>");
|
|
1102
|
+
}
|
|
1103
|
+
await deleteCustomerInvoice(batchId);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
case "void-customer-invoice": {
|
|
1107
|
+
const batchId = positional[0];
|
|
1108
|
+
if (!batchId) {
|
|
1109
|
+
throw new Error("void-customer-invoice requires <batchId>");
|
|
1110
|
+
}
|
|
1111
|
+
await voidCustomerInvoice(batchId);
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
case "approve-customer-invoice": {
|
|
1115
|
+
const batchId = positional[0];
|
|
1116
|
+
if (!batchId) {
|
|
1117
|
+
throw new Error("approve-customer-invoice requires <batchId>");
|
|
1118
|
+
}
|
|
1119
|
+
await approveCustomerInvoice(batchId);
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
case "reject-customer-invoice": {
|
|
1123
|
+
const batchId = positional[0];
|
|
1124
|
+
const reason = getFlag(flags, "reason");
|
|
1125
|
+
if (!batchId || reason === undefined) {
|
|
1126
|
+
throw new Error("reject-customer-invoice requires <batchId> --reason <text>");
|
|
1127
|
+
}
|
|
1128
|
+
await rejectCustomerInvoice(batchId, String(reason));
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
case "download-customer-invoice-pdf": {
|
|
1132
|
+
const invoiceId = positional[0];
|
|
1133
|
+
if (!invoiceId) {
|
|
1134
|
+
throw new Error("download-customer-invoice-pdf requires <invoiceId> [--output <path>]");
|
|
1135
|
+
}
|
|
1136
|
+
await downloadCustomerInvoicePdf(invoiceId, getFlag(flags, "output"));
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
case "sync-customer-invoice": {
|
|
1140
|
+
const invoiceId = positional[0];
|
|
1141
|
+
if (!invoiceId) {
|
|
1142
|
+
throw new Error("sync-customer-invoice requires <invoiceId>");
|
|
1143
|
+
}
|
|
1144
|
+
await syncCustomerInvoice(invoiceId);
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
849
1147
|
case "list-approvals":
|
|
850
1148
|
case "get-pending-approvals": {
|
|
851
1149
|
const typeRaw = getFlag(flags, "type");
|
|
@@ -976,6 +1274,7 @@ async function main() {
|
|
|
976
1274
|
const contactPersonId = getFlag(flags, "contactPersonId");
|
|
977
1275
|
const insideSalesId = getFlag(flags, "insideSalesId");
|
|
978
1276
|
const businessDevelopmentId = getFlag(flags, "businessDevelopmentId");
|
|
1277
|
+
const projectManagerId = getFlag(flags, "projectManagerId");
|
|
979
1278
|
const venue = getFlag(flags, "venue");
|
|
980
1279
|
const pax = getFlag(flags, "pax");
|
|
981
1280
|
const asanaTaskId = getFlag(flags, "asanaTaskId");
|
|
@@ -994,7 +1293,7 @@ async function main() {
|
|
|
994
1293
|
!businessDevelopmentId ||
|
|
995
1294
|
!venue ||
|
|
996
1295
|
!startDate) {
|
|
997
|
-
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --startDate (ISO/datetime flags as appropriate); optional --asanaTaskId, --asanaSearch, --pax and --endDate");
|
|
1296
|
+
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --startDate (ISO/datetime flags as appropriate); optional --projectManagerId, --asanaTaskId, --asanaSearch, --pax and --endDate");
|
|
998
1297
|
}
|
|
999
1298
|
await createProject({
|
|
1000
1299
|
name,
|
|
@@ -1002,6 +1301,7 @@ async function main() {
|
|
|
1002
1301
|
contactPersonId,
|
|
1003
1302
|
insideSalesId,
|
|
1004
1303
|
businessDevelopmentId,
|
|
1304
|
+
projectManagerId,
|
|
1005
1305
|
venue,
|
|
1006
1306
|
pax,
|
|
1007
1307
|
asanaTaskId,
|
|
@@ -1031,17 +1331,50 @@ async function main() {
|
|
|
1031
1331
|
await deleteProjectById(id);
|
|
1032
1332
|
break;
|
|
1033
1333
|
}
|
|
1334
|
+
case "check-project-reconciliation": {
|
|
1335
|
+
const id = positional[0];
|
|
1336
|
+
if (!id) {
|
|
1337
|
+
throw new Error("check-project-reconciliation requires <id>");
|
|
1338
|
+
}
|
|
1339
|
+
await checkProjectReconciliation(id);
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
case "reconcile-project": {
|
|
1343
|
+
const id = positional[0];
|
|
1344
|
+
if (!id)
|
|
1345
|
+
throw new Error("reconcile-project requires <id>");
|
|
1346
|
+
await reconcileProject(id);
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case "complete-project": {
|
|
1350
|
+
const id = positional[0];
|
|
1351
|
+
if (!id)
|
|
1352
|
+
throw new Error("complete-project requires <id>");
|
|
1353
|
+
await completeProject(id);
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
case "import-qbo-project": {
|
|
1357
|
+
const projectId = positional[0];
|
|
1358
|
+
const projectUrl = getFlag(flags, "url");
|
|
1359
|
+
if (!projectId || projectUrl === undefined) {
|
|
1360
|
+
throw new Error("import-qbo-project requires <projectId> --url <quickBooksProjectUrl>");
|
|
1361
|
+
}
|
|
1362
|
+
await importQuickBooksProjectId(projectId, String(projectUrl));
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1034
1365
|
case "update-project-status": {
|
|
1035
1366
|
const [id, statusRaw] = positional;
|
|
1036
1367
|
if (!id || !statusRaw) {
|
|
1037
1368
|
throw new Error("update-project-status requires <id> <status>");
|
|
1038
1369
|
}
|
|
1039
1370
|
const pmIdFlag = getFlag(flags, "projectManagerId");
|
|
1040
|
-
const
|
|
1371
|
+
const pmEmailFlag = getFlag(flags, "projectManagerEmail");
|
|
1041
1372
|
const wonOverrideReasonFlag = getFlag(flags, "wonOverrideReason");
|
|
1042
1373
|
await updateProjectStatus(id, parseProjectStatusForUpdate(statusRaw), {
|
|
1043
1374
|
...(pmIdFlag && { projectManagerId: String(pmIdFlag) }),
|
|
1044
|
-
...(
|
|
1375
|
+
...(pmEmailFlag && {
|
|
1376
|
+
projectManagerEmail: String(pmEmailFlag),
|
|
1377
|
+
}),
|
|
1045
1378
|
...(wonOverrideReasonFlag && {
|
|
1046
1379
|
wonOverrideReason: String(wonOverrideReasonFlag),
|
|
1047
1380
|
}),
|
|
@@ -1316,6 +1649,24 @@ async function main() {
|
|
|
1316
1649
|
await getErrorMetrics();
|
|
1317
1650
|
break;
|
|
1318
1651
|
}
|
|
1652
|
+
case "list-integration-operations": {
|
|
1653
|
+
await listIntegrationOperations({
|
|
1654
|
+
destination: getFlag(flags, "destination"),
|
|
1655
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
|
|
1656
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 25,
|
|
1657
|
+
status: parseIntegrationOperationStatus(getFlag(flags, "status")),
|
|
1658
|
+
});
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
case "retry-integration-operation": {
|
|
1662
|
+
const operationId = positional[0];
|
|
1663
|
+
if (!operationId) {
|
|
1664
|
+
throw new Error("retry-integration-operation requires <operationId>");
|
|
1665
|
+
}
|
|
1666
|
+
await retryIntegrationOperation(operationId, parseOptionalBoolFlag(flags, "confirmExternalStateReconciled") ??
|
|
1667
|
+
false);
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1319
1670
|
case "get-approved-budgets": {
|
|
1320
1671
|
await getApprovedBudgets({
|
|
1321
1672
|
projectId: getFlag(flags, "projectId"),
|