@go-labs-sg/bb 1.3.0 → 1.4.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/dist/commands.js +103 -1
- package/dist/index.js +45 -2
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -44,12 +44,66 @@ export async function getBudget(id) {
|
|
|
44
44
|
out({ budget, items });
|
|
45
45
|
}
|
|
46
46
|
export async function updateBudgetStatus(budgetId, status) {
|
|
47
|
+
if (status === "ESTIMATE_ACCEPTED") {
|
|
48
|
+
throw new Error("Use mark-budget-won <budgetId> <filePath> so proof upload is included before setting ESTIMATE_ACCEPTED.");
|
|
49
|
+
}
|
|
47
50
|
const result = await api.budget.updateBudgetStatus.mutate({
|
|
48
51
|
id: budgetId,
|
|
49
52
|
status,
|
|
50
53
|
});
|
|
51
54
|
out(result);
|
|
52
55
|
}
|
|
56
|
+
const assertBudgetActionReadiness = async ({ budgetId, requireQuickbooksProject, actionLabel, }) => {
|
|
57
|
+
const budget = await api.budget.getBudget.query({ id: budgetId });
|
|
58
|
+
const hasNoItems = budget.budgetItems.length === 0;
|
|
59
|
+
if (hasNoItems) {
|
|
60
|
+
throw new Error(`Cannot ${actionLabel}: there are no line items in this budget.`);
|
|
61
|
+
}
|
|
62
|
+
const hasZeroCostItems = budget.budgetItems.some((item) => Number(item.cost ?? 0) === 0);
|
|
63
|
+
if (hasZeroCostItems) {
|
|
64
|
+
throw new Error(`Cannot ${actionLabel}: all line items must have a cost greater than zero.`);
|
|
65
|
+
}
|
|
66
|
+
const inactiveSuppliers = budget.budgetItems
|
|
67
|
+
.filter((item) => !item.supplier.active)
|
|
68
|
+
.map((item) => item.supplier.name);
|
|
69
|
+
const uniqueInactiveSuppliers = [...new Set(inactiveSuppliers)];
|
|
70
|
+
if (uniqueInactiveSuppliers.length > 0) {
|
|
71
|
+
throw new Error(`Cannot ${actionLabel}: inactive suppliers found (${uniqueInactiveSuppliers.join(", ")}).`);
|
|
72
|
+
}
|
|
73
|
+
if (requireQuickbooksProject && !budget.project.quickbooksProjectId) {
|
|
74
|
+
throw new Error("Cannot create estimate: project is not linked to QuickBooks yet.");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const runBudgetPreflight = async (budgetId, preflight) => {
|
|
78
|
+
if (!preflight)
|
|
79
|
+
return;
|
|
80
|
+
if (preflight.company) {
|
|
81
|
+
await api.company.updateCompany.mutate(preflight.company);
|
|
82
|
+
}
|
|
83
|
+
if (preflight.contactPerson) {
|
|
84
|
+
await api.contactPerson.updateContactPerson.mutate(preflight.contactPerson);
|
|
85
|
+
}
|
|
86
|
+
if (preflight.eventDetails) {
|
|
87
|
+
const budget = await api.budget.getBudget.query({ id: budgetId });
|
|
88
|
+
await api.budget.updateBudget.mutate({
|
|
89
|
+
id: budget.id,
|
|
90
|
+
name: budget.name,
|
|
91
|
+
date: new Date(preflight.eventDetails.date),
|
|
92
|
+
startTime: new Date(preflight.eventDetails.startTime),
|
|
93
|
+
endTime: new Date(preflight.eventDetails.endTime),
|
|
94
|
+
pax: String(budget.pax),
|
|
95
|
+
venue: budget.venue,
|
|
96
|
+
budget: String(budget.budget),
|
|
97
|
+
categoryId: budget.categoryId,
|
|
98
|
+
paymentTerm: budget.paymentTerm,
|
|
99
|
+
insideSalesId: budget.insideSalesId ?? budget.insideSales.id,
|
|
100
|
+
businessDevelopmentId: budget.businessDevelopmentId ?? budget.businessDevelopment.id,
|
|
101
|
+
pipedriveDealId: budget.pipedriveDealId ?? undefined,
|
|
102
|
+
asanaTaskId: budget.asanaTaskId ?? "",
|
|
103
|
+
projectId: budget.projectId,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
53
107
|
export async function listBills(opts) {
|
|
54
108
|
const input = {
|
|
55
109
|
projectId: opts.projectId,
|
|
@@ -264,6 +318,38 @@ export async function uploadBudgetAttachmentFromPath(budgetId, filePath) {
|
|
|
264
318
|
});
|
|
265
319
|
out(confirmed);
|
|
266
320
|
}
|
|
321
|
+
export async function uploadBudgetWinProofFromPath(budgetId, filePath) {
|
|
322
|
+
const buf = await readFile(filePath);
|
|
323
|
+
const fileName = basename(filePath);
|
|
324
|
+
const size = buf.byteLength;
|
|
325
|
+
const { uploadUrl, key } = await api.attachment.requestBudgetWinProofUpload.mutate({
|
|
326
|
+
budgetId,
|
|
327
|
+
fileName,
|
|
328
|
+
size,
|
|
329
|
+
});
|
|
330
|
+
const res = await fetch(uploadUrl, {
|
|
331
|
+
method: "PUT",
|
|
332
|
+
body: buf,
|
|
333
|
+
headers: { "Content-Type": contentTypeHeaderForFileName(fileName) },
|
|
334
|
+
});
|
|
335
|
+
if (!res.ok) {
|
|
336
|
+
throw new Error(`S3 upload failed: HTTP ${res.status} ${(await res.text()).slice(0, 500)}`);
|
|
337
|
+
}
|
|
338
|
+
await api.attachment.confirmBudgetWinProofAttachment.mutate({
|
|
339
|
+
budgetId,
|
|
340
|
+
key,
|
|
341
|
+
name: fileName,
|
|
342
|
+
size,
|
|
343
|
+
});
|
|
344
|
+
const result = await api.budget.updateBudgetStatus.mutate({
|
|
345
|
+
id: budgetId,
|
|
346
|
+
status: "ESTIMATE_ACCEPTED",
|
|
347
|
+
});
|
|
348
|
+
out(result);
|
|
349
|
+
}
|
|
350
|
+
export async function markBudgetWonWithProof(budgetId, filePath) {
|
|
351
|
+
await uploadBudgetWinProofFromPath(budgetId, filePath);
|
|
352
|
+
}
|
|
267
353
|
export async function getBillDetails(id) {
|
|
268
354
|
const result = await api.bill.getById.query({ id });
|
|
269
355
|
out(result);
|
|
@@ -297,7 +383,13 @@ export async function getBudgetVersions(budgetId) {
|
|
|
297
383
|
});
|
|
298
384
|
out(versions);
|
|
299
385
|
}
|
|
300
|
-
export async function createBudgetApproval(budgetId) {
|
|
386
|
+
export async function createBudgetApproval(budgetId, preflight) {
|
|
387
|
+
await runBudgetPreflight(budgetId, preflight);
|
|
388
|
+
await assertBudgetActionReadiness({
|
|
389
|
+
budgetId,
|
|
390
|
+
requireQuickbooksProject: false,
|
|
391
|
+
actionLabel: "request approval",
|
|
392
|
+
});
|
|
301
393
|
const result = await api.budget.createBudgetApproval.mutate({ budgetId });
|
|
302
394
|
const approvalIds = (result.results ?? []).map((item) => ({ id: item.id }));
|
|
303
395
|
let email;
|
|
@@ -322,6 +414,16 @@ export async function createBudgetApproval(budgetId) {
|
|
|
322
414
|
email,
|
|
323
415
|
});
|
|
324
416
|
}
|
|
417
|
+
export async function createEstimate(budgetId, preflight) {
|
|
418
|
+
await runBudgetPreflight(budgetId, preflight);
|
|
419
|
+
await assertBudgetActionReadiness({
|
|
420
|
+
budgetId,
|
|
421
|
+
requireQuickbooksProject: true,
|
|
422
|
+
actionLabel: "create estimate",
|
|
423
|
+
});
|
|
424
|
+
const result = await api.quickbooks.createEstimate.mutate({ budgetId });
|
|
425
|
+
out(result);
|
|
426
|
+
}
|
|
325
427
|
export async function getBudgetDetails(budgetId) {
|
|
326
428
|
const detail = await api.budget.getBudgetDetail.query({ id: budgetId });
|
|
327
429
|
out(detail);
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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, 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, 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, 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
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";
|
|
8
8
|
import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
@@ -76,10 +76,12 @@ Budgets
|
|
|
76
76
|
get-budget-versions <budgetId>
|
|
77
77
|
update-budget-status <id> <status>
|
|
78
78
|
status: ${budgetStatusesForHelp.join(", ")}
|
|
79
|
+
mark-budget-won <budgetId> <filePath> Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED
|
|
79
80
|
create-budget --payload '<json>' (budget.createBudget; ISO dates for date/startTime/endTime)
|
|
80
81
|
update-budget --payload '<json>' (budget.updateBudget; must include id)
|
|
81
82
|
delete-budget <budgetId>
|
|
82
83
|
create-budget-approval <budgetId> (also sends approval request emails)
|
|
84
|
+
create-estimate <budgetId> (creates QuickBooks estimate; supports preflight updates)
|
|
83
85
|
add-budget-items --budgetId --items '[{"itemId":"…","quantity":1,"markup":30},…]'
|
|
84
86
|
update-budget-item --id <budgetItemId> [--description] [--note] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false]
|
|
85
87
|
remove-budget-item <budgetItemId>
|
|
@@ -248,6 +250,14 @@ async function main() {
|
|
|
248
250
|
await uploadBudgetAttachmentFromPath(budgetId, filePath);
|
|
249
251
|
break;
|
|
250
252
|
}
|
|
253
|
+
case "mark-budget-won": {
|
|
254
|
+
const [budgetId, filePath] = positional;
|
|
255
|
+
if (!budgetId || !filePath) {
|
|
256
|
+
throw new Error("mark-budget-won requires <budgetId> <filePath>");
|
|
257
|
+
}
|
|
258
|
+
await markBudgetWonWithProof(budgetId, filePath);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
251
261
|
case "get-budget-items": {
|
|
252
262
|
const id = positional[0];
|
|
253
263
|
if (!id)
|
|
@@ -288,7 +298,40 @@ async function main() {
|
|
|
288
298
|
const id = positional[0];
|
|
289
299
|
if (!id)
|
|
290
300
|
throw new Error("create-budget-approval requires <budgetId>");
|
|
291
|
-
|
|
301
|
+
const companyPayloadRaw = getFlag(flags, "companyPayload");
|
|
302
|
+
const contactPayloadRaw = getFlag(flags, "contactPayload");
|
|
303
|
+
const eventPayloadRaw = getFlag(flags, "eventPayload");
|
|
304
|
+
await createBudgetApproval(id, {
|
|
305
|
+
company: companyPayloadRaw !== undefined
|
|
306
|
+
? parseJsonFlag(String(companyPayloadRaw), "--companyPayload")
|
|
307
|
+
: undefined,
|
|
308
|
+
contactPerson: contactPayloadRaw !== undefined
|
|
309
|
+
? parseJsonFlag(String(contactPayloadRaw), "--contactPayload")
|
|
310
|
+
: undefined,
|
|
311
|
+
eventDetails: eventPayloadRaw !== undefined
|
|
312
|
+
? parseJsonFlag(String(eventPayloadRaw), "--eventPayload")
|
|
313
|
+
: undefined,
|
|
314
|
+
});
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "create-estimate": {
|
|
318
|
+
const id = positional[0];
|
|
319
|
+
if (!id)
|
|
320
|
+
throw new Error("create-estimate requires <budgetId>");
|
|
321
|
+
const companyPayloadRaw = getFlag(flags, "companyPayload");
|
|
322
|
+
const contactPayloadRaw = getFlag(flags, "contactPayload");
|
|
323
|
+
const eventPayloadRaw = getFlag(flags, "eventPayload");
|
|
324
|
+
await createEstimate(id, {
|
|
325
|
+
company: companyPayloadRaw !== undefined
|
|
326
|
+
? parseJsonFlag(String(companyPayloadRaw), "--companyPayload")
|
|
327
|
+
: undefined,
|
|
328
|
+
contactPerson: contactPayloadRaw !== undefined
|
|
329
|
+
? parseJsonFlag(String(contactPayloadRaw), "--contactPayload")
|
|
330
|
+
: undefined,
|
|
331
|
+
eventDetails: eventPayloadRaw !== undefined
|
|
332
|
+
? parseJsonFlag(String(eventPayloadRaw), "--eventPayload")
|
|
333
|
+
: undefined,
|
|
334
|
+
});
|
|
292
335
|
break;
|
|
293
336
|
}
|
|
294
337
|
case "create-budget": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Budget Builder CLI — list budgets, bills, approvals, suppliers; approve bills; change status. For AI agents (e.g. Chuck/OpenClaw).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|