@go-labs-sg/bb 1.6.1 → 1.7.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 +6 -4
- package/dist/commands.js +81 -10
- package/dist/index.js +74 -17
- package/dist/parse-cli-enums.js +20 -1
- package/dist/parse-mutation-payload.js +1 -1
- package/dist/prisma-enums.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Command-line interface for **Budget Builder**. Call budgets, bills, claims, appr
|
|
|
4
4
|
|
|
5
5
|
For AI agents: bills and claims are stored as the same underlying bill records. Use the `isClaimable` column/flag to distinguish them: `isClaimable=false` means a normal bill, while `isClaimable=true` means a reimbursable claim.
|
|
6
6
|
|
|
7
|
+
For AI agents using this CLI: always check whether a newer `@go-labs-sg/bb` version is available before running Budget Builder commands, then update the CLI if needed so command flags and API behavior stay current.
|
|
8
|
+
|
|
7
9
|
**Registry:** [`@go-labs-sg/bb`](https://www.npmjs.com/package/@go-labs-sg/bb)
|
|
8
10
|
|
|
9
11
|
## Requirements
|
|
@@ -41,7 +43,7 @@ The CLI talks to the production API: `https://budget-builder.getout.events`.
|
|
|
41
43
|
bb help
|
|
42
44
|
bb list-budgets
|
|
43
45
|
bb get-budget <budget-id>
|
|
44
|
-
bb update-budget-status <budget-id> <status>
|
|
46
|
+
bb update-budget-status <budget-id> <status> [--markProjectWon true]
|
|
45
47
|
bb list-bills
|
|
46
48
|
bb approve-bill <bill-id>
|
|
47
49
|
bb list-approvals
|
|
@@ -58,12 +60,12 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
|
|
|
58
60
|
|
|
59
61
|
| Area | Commands (non-exhaustive) |
|
|
60
62
|
| --- | --- |
|
|
61
|
-
| **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`, `update-budget-status
|
|
63
|
+
| **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`, `update-budget-status` (`--markProjectWon true` can move the parent project to `WON` for accepted/closed estimates), `mark-budget-won` (`<budgetId>` + proof file path; optional `--markProjectWon true`), `create-budget` / `update-budget` (`--payload`), `delete-budget`, `create-budget-approval` (also sends approval request emails), `add-budget-items`, `update-budget-item`, `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `update-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
|
|
62
64
|
| **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `create-bill` (`--payload`; set `isClaimable=false` for a bill or `isClaimable=true` for a claim), `update-bill` (`--payload`), `delete-bill`, `create-bill-approval` (also sends approval request emails), `update-bill-status`, `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clear with `--clearPaymentTrackingUrl` / `--clearPaymentReference` / `--clearQuickbooksBillId` / `--clearPaymentDate` `true`), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
|
|
63
65
|
| **Approvals** | `list-approvals` / `get-pending-approvals`, `approve-bill` / `reject-bill` (send reply email), `approve-budget` / `reject-budget` (send reply email), `approve-supplier` / `reject-supplier` (send reply email) |
|
|
64
|
-
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--
|
|
66
|
+
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--asanaTaskId`, `--slackChannelId`, `--slackChannelUrl`, `--slackChannelName`, `--startDate` as ISO datetime for project/window start; optional `--pax`; optional `--endDate`; optional `--description`; optional `--requestQboAccountantNotification false` to skip QBO accountant emails), `update-project` (`--payload` with `dateRange.from` / `dateRange.to` for the project window; optional `requestQboAccountantNotification` in JSON), `delete-project`, `update-project-status` (`<id>` `<status>`: `PITCH` \| `WON` \| `COMPLETED` \| `LOST`; for `PITCH` → `WON` also pass `--projectManagerId` or `--projectManagerName`; when marking `WON` without an accepted/closed budget or proof, pass `--wonOverrideReason`) |
|
|
65
67
|
| **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
|
|
66
|
-
| **Suppliers & items** | `list-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), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details
|
|
68
|
+
| **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`, `list-items`, `create-item` (`--payload`), `update-item` (`--payload`), `delete-item`, `get-item`, `list-item-categories`, `create-item-category`, `update-item-category`, `delete-item-categories` (`--ids` CSV; admin) |
|
|
67
69
|
| **Dashboard & users** | `list-users`, `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` |
|
|
68
70
|
| **Errors** | `get-recent-errors`, `get-error-metrics` |
|
|
69
71
|
| **Historical / benchmarks** | `get-approved-budgets`, `get-budget-category-benchmarks`, `get-item-pricing-history`, `get-supplier-pricing-history` |
|
package/dist/commands.js
CHANGED
|
@@ -48,13 +48,13 @@ export async function getBudget(id) {
|
|
|
48
48
|
]);
|
|
49
49
|
out({ budget, items });
|
|
50
50
|
}
|
|
51
|
-
export async function updateBudgetStatus(budgetId, status) {
|
|
52
|
-
if (status === "ESTIMATE_ACCEPTED") {
|
|
53
|
-
throw new Error("Use mark-budget-won <budgetId> <filePath> so proof upload is included before setting ESTIMATE_ACCEPTED.");
|
|
54
|
-
}
|
|
51
|
+
export async function updateBudgetStatus(budgetId, status, opts) {
|
|
55
52
|
const result = await api.budget.updateBudgetStatus.mutate({
|
|
56
53
|
id: budgetId,
|
|
57
54
|
status,
|
|
55
|
+
...(opts?.markProjectWon !== undefined && {
|
|
56
|
+
markProjectWon: opts.markProjectWon,
|
|
57
|
+
}),
|
|
58
58
|
});
|
|
59
59
|
out(result);
|
|
60
60
|
}
|
|
@@ -110,7 +110,7 @@ const runBudgetPreflight = async (budgetId, preflight) => {
|
|
|
110
110
|
insideSalesId: project.insideSalesId ?? "",
|
|
111
111
|
businessDevelopmentId: project.businessDevelopmentId ?? "",
|
|
112
112
|
venue: project.venue ?? "",
|
|
113
|
-
pax: String(project.pax),
|
|
113
|
+
pax: String(project.pax ?? ""),
|
|
114
114
|
...(project.slackChannelId &&
|
|
115
115
|
project.slackChannelUrl &&
|
|
116
116
|
project.slackChannelName
|
|
@@ -217,8 +217,14 @@ export async function listApprovals(opts) {
|
|
|
217
217
|
export async function listSuppliers(opts) {
|
|
218
218
|
const result = await api.supplier.getSuppliers.query({
|
|
219
219
|
page: opts.page ?? 1,
|
|
220
|
-
perPage: opts.perPage ??
|
|
220
|
+
perPage: opts.perPage ?? 10,
|
|
221
|
+
sort: opts.sort ?? [{ id: "createdAt", desc: true }],
|
|
221
222
|
name: opts.name,
|
|
223
|
+
createdBy: opts.createdBy,
|
|
224
|
+
gstRegistered: opts.gstRegistered ?? null,
|
|
225
|
+
status: opts.status,
|
|
226
|
+
supplierTags: opts.supplierTags,
|
|
227
|
+
active: opts.active ?? true,
|
|
222
228
|
});
|
|
223
229
|
out(result);
|
|
224
230
|
}
|
|
@@ -338,7 +344,7 @@ export async function uploadBudgetAttachmentFromPath(budgetId, filePath) {
|
|
|
338
344
|
});
|
|
339
345
|
out(confirmed);
|
|
340
346
|
}
|
|
341
|
-
export async function uploadBudgetWinProofFromPath(budgetId, filePath) {
|
|
347
|
+
export async function uploadBudgetWinProofFromPath(budgetId, filePath, opts) {
|
|
342
348
|
const buf = await readFile(filePath);
|
|
343
349
|
const fileName = basename(filePath);
|
|
344
350
|
const size = buf.byteLength;
|
|
@@ -364,11 +370,14 @@ export async function uploadBudgetWinProofFromPath(budgetId, filePath) {
|
|
|
364
370
|
const result = await api.budget.updateBudgetStatus.mutate({
|
|
365
371
|
id: budgetId,
|
|
366
372
|
status: "ESTIMATE_ACCEPTED",
|
|
373
|
+
...(opts?.markProjectWon !== undefined && {
|
|
374
|
+
markProjectWon: opts.markProjectWon,
|
|
375
|
+
}),
|
|
367
376
|
});
|
|
368
377
|
out(result);
|
|
369
378
|
}
|
|
370
|
-
export async function markBudgetWonWithProof(budgetId, filePath) {
|
|
371
|
-
await uploadBudgetWinProofFromPath(budgetId, filePath);
|
|
379
|
+
export async function markBudgetWonWithProof(budgetId, filePath, opts) {
|
|
380
|
+
await uploadBudgetWinProofFromPath(budgetId, filePath, opts);
|
|
372
381
|
}
|
|
373
382
|
export async function getBillDetails(id) {
|
|
374
383
|
const result = await api.bill.getById.query({ id });
|
|
@@ -508,6 +517,10 @@ export async function deleteSuppliersByIds(ids) {
|
|
|
508
517
|
const result = await api.supplier.deleteSuppliers.mutate({ ids });
|
|
509
518
|
out(result);
|
|
510
519
|
}
|
|
520
|
+
export async function reactivateSuppliersByIds(ids) {
|
|
521
|
+
const result = await api.supplier.reactivateSuppliers.mutate({ ids });
|
|
522
|
+
out(result);
|
|
523
|
+
}
|
|
511
524
|
export async function createSupplierCertification(name) {
|
|
512
525
|
const result = await api.supplier.createCertification.mutate({ name });
|
|
513
526
|
out(result);
|
|
@@ -742,7 +755,62 @@ export async function getSupplierDetails(supplierId) {
|
|
|
742
755
|
const supplier = await api.supplier.getSupplierById.query({
|
|
743
756
|
id: supplierId,
|
|
744
757
|
});
|
|
745
|
-
|
|
758
|
+
const respondedStatuses = new Set([
|
|
759
|
+
ExtendedApprovalStatus.APPROVED,
|
|
760
|
+
ExtendedApprovalStatus.REJECTED,
|
|
761
|
+
]);
|
|
762
|
+
const latestRespondedApproval = [
|
|
763
|
+
...supplier.approvals.filter((approval) => respondedStatuses.has(approval.status)),
|
|
764
|
+
].sort((a, b) => new Date(b.respondedAt ?? b.updatedAt).getTime() -
|
|
765
|
+
new Date(a.respondedAt ?? a.updatedAt).getTime() ||
|
|
766
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
|
|
767
|
+
const pendingApprovals = supplier.approvals.filter((approval) => approval.status === ExtendedApprovalStatus.PENDING_APPROVAL);
|
|
768
|
+
const supersededApprovals = supplier.approvals.filter((approval) => approval.status === ExtendedApprovalStatus.SUPERSEDED);
|
|
769
|
+
const respondedByApproval = latestRespondedApproval ?? null;
|
|
770
|
+
const responseUser = respondedByApproval
|
|
771
|
+
? (respondedByApproval.responder ?? respondedByApproval.approver)
|
|
772
|
+
: null;
|
|
773
|
+
out({
|
|
774
|
+
...supplier,
|
|
775
|
+
supplierApprovalSummary: {
|
|
776
|
+
pendingApprovalCount: pendingApprovals.length,
|
|
777
|
+
pendingApprovers: pendingApprovals.map((approval) => ({
|
|
778
|
+
approvalId: approval.id,
|
|
779
|
+
approverId: approval.approverId,
|
|
780
|
+
name: approval.approver.name,
|
|
781
|
+
email: approval.approver.email,
|
|
782
|
+
createdAt: approval.createdAt,
|
|
783
|
+
updatedAt: approval.updatedAt,
|
|
784
|
+
})),
|
|
785
|
+
supersededApprovalCount: supersededApprovals.length,
|
|
786
|
+
supersededApprovers: supersededApprovals.map((approval) => ({
|
|
787
|
+
approvalId: approval.id,
|
|
788
|
+
approverId: approval.approverId,
|
|
789
|
+
name: approval.approver.name,
|
|
790
|
+
email: approval.approver.email,
|
|
791
|
+
respondedAt: approval.respondedAt,
|
|
792
|
+
responder: approval.responder
|
|
793
|
+
? {
|
|
794
|
+
id: approval.responder.id,
|
|
795
|
+
name: approval.responder.name,
|
|
796
|
+
email: approval.responder.email,
|
|
797
|
+
}
|
|
798
|
+
: null,
|
|
799
|
+
})),
|
|
800
|
+
respondedBy: respondedByApproval && responseUser
|
|
801
|
+
? {
|
|
802
|
+
approvalId: respondedByApproval.id,
|
|
803
|
+
status: respondedByApproval.status,
|
|
804
|
+
approverId: respondedByApproval.approverId,
|
|
805
|
+
responderId: respondedByApproval.responderId,
|
|
806
|
+
name: responseUser.name,
|
|
807
|
+
email: responseUser.email,
|
|
808
|
+
respondedAt: respondedByApproval.respondedAt,
|
|
809
|
+
updatedAt: respondedByApproval.updatedAt,
|
|
810
|
+
}
|
|
811
|
+
: null,
|
|
812
|
+
},
|
|
813
|
+
});
|
|
746
814
|
}
|
|
747
815
|
export async function approveSupplier(supplierId) {
|
|
748
816
|
const notifications = await api.budget.getNotifications.query();
|
|
@@ -910,6 +978,9 @@ export async function updateProjectStatus(id, status, opts) {
|
|
|
910
978
|
...(opts?.projectManagerName && {
|
|
911
979
|
projectManagerName: opts.projectManagerName,
|
|
912
980
|
}),
|
|
981
|
+
...(opts?.wonOverrideReason && {
|
|
982
|
+
wonOverrideReason: opts.wonOverrideReason,
|
|
983
|
+
}),
|
|
913
984
|
});
|
|
914
985
|
out(result);
|
|
915
986
|
}
|
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, approveSupplier, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createEstimate, createItemCategory, createItemFromPayload, createProject, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteCompanyById, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteSuppliersByIds, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, rejectBill, rejectBudget, rejectSupplier, removeBudgetItem, reorderBudgetItemsCli, updateBillFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBudgetAttachmentFromPath, } from "./commands.js";
|
|
5
|
+
import { addBudgetItems, approveBill, approveBudget, approveSupplier, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createEstimate, createItemCategory, createItemFromPayload, createProject, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteCompanyById, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteSuppliersByIds, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, rejectBill, rejectBudget, rejectSupplier, removeBudgetItem, reorderBudgetItemsCli, updateBillFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBudgetAttachmentFromPath, } from "./commands.js";
|
|
6
6
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
7
|
-
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedIds, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, projectStatusesForHelp, } from "./parse-cli-enums.js";
|
|
7
|
+
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedIds, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, projectStatusesForHelp, } 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)
|
|
@@ -45,6 +45,45 @@ function parseListBillsFlags(flags) {
|
|
|
45
45
|
pageSize: parsePositiveIntFlag(getFlag(flags, "pageSize"), "--pageSize"),
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
const SUPPLIER_SORT_FIELDS = [
|
|
49
|
+
"createdAt",
|
|
50
|
+
"updatedAt",
|
|
51
|
+
"name",
|
|
52
|
+
"email",
|
|
53
|
+
"phone",
|
|
54
|
+
"gstRegistered",
|
|
55
|
+
"status",
|
|
56
|
+
"active",
|
|
57
|
+
];
|
|
58
|
+
function parseOptionalSupplierSortBy(raw) {
|
|
59
|
+
if (raw === undefined || raw === "")
|
|
60
|
+
return undefined;
|
|
61
|
+
if (!SUPPLIER_SORT_FIELDS.includes(raw)) {
|
|
62
|
+
throw new Error(`Invalid --sortBy "${raw}". Use one of: ${SUPPLIER_SORT_FIELDS.join(", ")}.`);
|
|
63
|
+
}
|
|
64
|
+
return raw;
|
|
65
|
+
}
|
|
66
|
+
function parseListSuppliersFlags(flags) {
|
|
67
|
+
const createdByRaw = getFlag(flags, "createdBy");
|
|
68
|
+
const statusRaw = getFlag(flags, "status");
|
|
69
|
+
const supplierTagsRaw = getFlag(flags, "supplierTags");
|
|
70
|
+
const sortBy = parseOptionalSupplierSortBy(getFlag(flags, "sortBy"));
|
|
71
|
+
const sortDirRaw = getFlag(flags, "sortDir");
|
|
72
|
+
const sortDir = parseOptionalBillListSortDir(sortDirRaw !== undefined ? String(sortDirRaw) : undefined);
|
|
73
|
+
return {
|
|
74
|
+
name: getFlag(flags, "name"),
|
|
75
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
76
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
77
|
+
sort: sortBy !== undefined || sortDir !== undefined
|
|
78
|
+
? [{ id: sortBy ?? "createdAt", desc: (sortDir ?? "desc") === "desc" }]
|
|
79
|
+
: undefined,
|
|
80
|
+
createdBy: parseCommaSeparatedIds(createdByRaw !== undefined ? String(createdByRaw) : undefined),
|
|
81
|
+
gstRegistered: parseOptionalBoolFlag(flags, "gstRegistered"),
|
|
82
|
+
status: parseCommaSeparatedSupplierStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
|
|
83
|
+
supplierTags: parseCommaSeparatedIds(supplierTagsRaw !== undefined ? String(supplierTagsRaw) : undefined),
|
|
84
|
+
active: parseOptionalBoolFlag(flags, "active"),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
48
87
|
function parseDashboardQueryFlags(flags) {
|
|
49
88
|
const roleRaw = getFlag(flags, "role");
|
|
50
89
|
const timeFrameRaw = getFlag(flags, "timeFrame");
|
|
@@ -74,9 +113,9 @@ Budgets
|
|
|
74
113
|
get-budget-details <id> Full detail (margins, suppliers, etc.)
|
|
75
114
|
get-budget-categories
|
|
76
115
|
get-budget-versions <budgetId>
|
|
77
|
-
update-budget-status <id> <status>
|
|
116
|
+
update-budget-status <id> <status> [--markProjectWon true|false]
|
|
78
117
|
status: ${budgetStatusesForHelp.join(", ")}
|
|
79
|
-
mark-budget-won <budgetId> <filePath> Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED
|
|
118
|
+
mark-budget-won <budgetId> <filePath> [--markProjectWon true|false] Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED
|
|
80
119
|
create-budget --payload '<json>' (budget.createBudget; Asana deal card is on the project)
|
|
81
120
|
update-budget --payload '<json>' (budget.updateBudget; must include id)
|
|
82
121
|
delete-budget <budgetId>
|
|
@@ -128,11 +167,12 @@ Companies & projects
|
|
|
128
167
|
delete-company <id>
|
|
129
168
|
list-projects [--companyId] [--name] [--status] [--page] [--perPage]
|
|
130
169
|
get-project <id>
|
|
131
|
-
create-project --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --
|
|
170
|
+
create-project --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --asanaTaskId --slackChannelId --slackChannelUrl --slackChannelName --startDate <ISO> [--pax] [--endDate <ISO>] [--description] [--requestQboAccountantNotification false]
|
|
132
171
|
update-project --payload '<json>' (project.updateProject; optional requestQboAccountantNotification; default notify like web)
|
|
133
172
|
delete-project <id>
|
|
134
|
-
update-project-status <id> <status> [--projectManagerId <id>] [--projectManagerName <name>]
|
|
173
|
+
update-project-status <id> <status> [--projectManagerId <id>] [--projectManagerName <name>] [--wonOverrideReason <text>]
|
|
135
174
|
Required when moving PITCH → WON: supply --projectManagerId or --projectManagerName.
|
|
175
|
+
Required when moving to WON without accepted/closed budget or win proof: --wonOverrideReason (10+ chars).
|
|
136
176
|
status: ${projectStatusesForHelp.join(", ")}
|
|
137
177
|
|
|
138
178
|
Contacts
|
|
@@ -141,10 +181,12 @@ Contacts
|
|
|
141
181
|
update-contact-person --payload '<json>' (contactPerson.updateContactPerson; must include id)
|
|
142
182
|
|
|
143
183
|
Suppliers & items
|
|
144
|
-
list-suppliers [--name] [--page] [--perPage]
|
|
184
|
+
list-suppliers [--name] [--page] [--perPage] [--sortBy createdAt|updatedAt|name|email|phone|gstRegistered|status|active] [--sortDir asc|desc] [--createdBy <csv>] [--gstRegistered true|false] [--status CSV] [--supplierTags <csv>] [--active true|false]
|
|
185
|
+
Defaults: --active true, --perPage 10, --sortBy createdAt, --sortDir desc (same as the web list); pass --active false for archived suppliers.
|
|
145
186
|
create-supplier --payload '<json>' (supplier.createSupplier; auto-requests approval + emails when status is PENDING_APPROVAL)
|
|
146
187
|
update-supplier --payload '<json>' (supplier.updateSupplier; must include id; auto-requests approval + emails when status is PENDING_APPROVAL)
|
|
147
|
-
delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin)
|
|
188
|
+
delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin; archives/deactivates supplier items)
|
|
189
|
+
reactivate-suppliers --ids <csv> (supplier.reactivateSuppliers; admin; reactivates supplier items)
|
|
148
190
|
create-certification --name (supplier.createCertification)
|
|
149
191
|
create-payment-method --name (supplier.createPaymentMethod)
|
|
150
192
|
create-supplier-role --name (supplier.createSupplierRole)
|
|
@@ -257,7 +299,9 @@ async function main() {
|
|
|
257
299
|
if (!budgetId || !filePath) {
|
|
258
300
|
throw new Error("mark-budget-won requires <budgetId> <filePath>");
|
|
259
301
|
}
|
|
260
|
-
await markBudgetWonWithProof(budgetId, filePath
|
|
302
|
+
await markBudgetWonWithProof(budgetId, filePath, {
|
|
303
|
+
markProjectWon: parseOptionalBoolFlag(flags, "markProjectWon"),
|
|
304
|
+
});
|
|
261
305
|
break;
|
|
262
306
|
}
|
|
263
307
|
case "get-budget-items": {
|
|
@@ -293,7 +337,9 @@ async function main() {
|
|
|
293
337
|
if (!isBudgetStatusUpdate(status)) {
|
|
294
338
|
throw new Error(`Invalid status. Use one of: ${budgetStatusesForHelp.join(", ")}`);
|
|
295
339
|
}
|
|
296
|
-
await updateBudgetStatus(id, status
|
|
340
|
+
await updateBudgetStatus(id, status, {
|
|
341
|
+
markProjectWon: parseOptionalBoolFlag(flags, "markProjectWon"),
|
|
342
|
+
});
|
|
297
343
|
break;
|
|
298
344
|
}
|
|
299
345
|
case "create-budget-approval": {
|
|
@@ -751,13 +797,12 @@ async function main() {
|
|
|
751
797
|
!insideSalesId ||
|
|
752
798
|
!businessDevelopmentId ||
|
|
753
799
|
!venue ||
|
|
754
|
-
!pax ||
|
|
755
800
|
!asanaTaskId ||
|
|
756
801
|
!slackChannelId ||
|
|
757
802
|
!slackChannelUrl ||
|
|
758
803
|
!slackChannelName ||
|
|
759
804
|
!startDate) {
|
|
760
|
-
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --
|
|
805
|
+
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --asanaTaskId --slackChannelId --slackChannelUrl --slackChannelName --startDate (ISO/datetime flags as appropriate); optional --pax and --endDate");
|
|
761
806
|
}
|
|
762
807
|
await createProject({
|
|
763
808
|
name,
|
|
@@ -801,9 +846,13 @@ async function main() {
|
|
|
801
846
|
}
|
|
802
847
|
const pmIdFlag = getFlag(flags, "projectManagerId");
|
|
803
848
|
const pmNameFlag = getFlag(flags, "projectManagerName");
|
|
849
|
+
const wonOverrideReasonFlag = getFlag(flags, "wonOverrideReason");
|
|
804
850
|
await updateProjectStatus(id, parseProjectStatusForUpdate(statusRaw), {
|
|
805
851
|
...(pmIdFlag && { projectManagerId: String(pmIdFlag) }),
|
|
806
852
|
...(pmNameFlag && { projectManagerName: String(pmNameFlag) }),
|
|
853
|
+
...(wonOverrideReasonFlag && {
|
|
854
|
+
wonOverrideReason: String(wonOverrideReasonFlag),
|
|
855
|
+
}),
|
|
807
856
|
});
|
|
808
857
|
break;
|
|
809
858
|
}
|
|
@@ -833,11 +882,7 @@ async function main() {
|
|
|
833
882
|
break;
|
|
834
883
|
}
|
|
835
884
|
case "list-suppliers": {
|
|
836
|
-
await listSuppliers(
|
|
837
|
-
name: getFlag(flags, "name"),
|
|
838
|
-
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
839
|
-
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
840
|
-
});
|
|
885
|
+
await listSuppliers(parseListSuppliersFlags(flags));
|
|
841
886
|
break;
|
|
842
887
|
}
|
|
843
888
|
case "create-supplier": {
|
|
@@ -870,6 +915,18 @@ async function main() {
|
|
|
870
915
|
await deleteSuppliersByIds(ids);
|
|
871
916
|
break;
|
|
872
917
|
}
|
|
918
|
+
case "reactivate-suppliers": {
|
|
919
|
+
const idsRaw = getFlag(flags, "ids");
|
|
920
|
+
if (idsRaw === undefined) {
|
|
921
|
+
throw new Error("reactivate-suppliers requires --ids <csv>");
|
|
922
|
+
}
|
|
923
|
+
const ids = parseCommaSeparatedIds(String(idsRaw));
|
|
924
|
+
if (!ids?.length) {
|
|
925
|
+
throw new Error("reactivate-suppliers requires non-empty --ids");
|
|
926
|
+
}
|
|
927
|
+
await reactivateSuppliersByIds(ids);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
873
930
|
case "create-certification": {
|
|
874
931
|
const name = getFlag(flags, "name");
|
|
875
932
|
if (!name)
|
package/dist/parse-cli-enums.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BudgetRole, Deals, ExtendedBillStatus, ExtendedErrorSeverity, ExtendedErrorStatus, ExtendedProjectStatus, TimeFrame, } from "./filter-enums.js";
|
|
1
|
+
import { BudgetRole, Deals, ExtendedApprovalStatus, ExtendedBillStatus, ExtendedErrorSeverity, ExtendedErrorStatus, ExtendedProjectStatus, TimeFrame, } from "./filter-enums.js";
|
|
2
2
|
import { BillStatus, BudgetStatus, ProjectStatus } from "./prisma-enums.js";
|
|
3
3
|
const BUDGET_STATUS_VALUES = new Set(Object.values(BudgetStatus));
|
|
4
4
|
const BILL_STATUS_VALUES = new Set(Object.values(BillStatus));
|
|
@@ -20,6 +20,7 @@ const SUPPLIER_ANALYTICS_TF = new Set([
|
|
|
20
20
|
"LAST_3_MONTHS",
|
|
21
21
|
"CUSTOM",
|
|
22
22
|
]);
|
|
23
|
+
const SUPPLIER_APPROVAL_STATUS_VALUES = new Set(Object.values(ExtendedApprovalStatus));
|
|
23
24
|
const EXT_ERROR_SEVERITY_VALUES = new Set(Object.values(ExtendedErrorSeverity));
|
|
24
25
|
const EXT_ERROR_STATUS_VALUES = new Set(Object.values(ExtendedErrorStatus));
|
|
25
26
|
export function isBudgetStatusListToken(s) {
|
|
@@ -144,6 +145,24 @@ export function parseCommaSeparatedBudgetStatuses(raw) {
|
|
|
144
145
|
}
|
|
145
146
|
return out;
|
|
146
147
|
}
|
|
148
|
+
export function parseCommaSeparatedSupplierStatuses(raw) {
|
|
149
|
+
if (raw === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const parts = raw
|
|
152
|
+
.split(",")
|
|
153
|
+
.map((x) => x.trim())
|
|
154
|
+
.filter(Boolean);
|
|
155
|
+
if (parts.length === 0)
|
|
156
|
+
return undefined;
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const p of parts) {
|
|
159
|
+
if (!SUPPLIER_APPROVAL_STATUS_VALUES.has(p)) {
|
|
160
|
+
throw new Error(`Invalid supplier approval status "${p}". Use one of: ${[...SUPPLIER_APPROVAL_STATUS_VALUES].join(", ")}.`);
|
|
161
|
+
}
|
|
162
|
+
out.push(p);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
147
166
|
export function parseBillStatusFlag(raw) {
|
|
148
167
|
if (raw === undefined)
|
|
149
168
|
return undefined;
|
|
@@ -214,7 +214,7 @@ export function parseUpdateProjectPayload(raw) {
|
|
|
214
214
|
insideSalesId: requiredString(o.insideSalesId, "update-project payload.insideSalesId"),
|
|
215
215
|
businessDevelopmentId: requiredString(o.businessDevelopmentId, "update-project payload.businessDevelopmentId"),
|
|
216
216
|
venue: requiredString(o.venue, "update-project payload.venue"),
|
|
217
|
-
pax:
|
|
217
|
+
pax: optionalString(o.pax),
|
|
218
218
|
dateRange: {
|
|
219
219
|
from: toDate(range.from, "dateRange.from"),
|
|
220
220
|
to: range.to === undefined || range.to === null
|
package/dist/prisma-enums.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Budget Builder CLI for AI agents — manage budgets, bills, and claims; bill records use isClaimable=false for bills and isClaimable=true for claims.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|