@go-labs-sg/bb 1.0.4 → 1.1.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 CHANGED
@@ -55,7 +55,8 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
55
55
  | `list-budgets` | List budgets. Flags: `--projectId`, `--name`, `--status` (comma-separated), `--page`, `--perPage` |
56
56
  | `get-budget <id>` | Budget details and line items |
57
57
  | `update-budget-status <id> <status>` | Change budget status (e.g. `APPROVED`, `REJECTED`, `DRAFT`, …) |
58
- | `list-bills` | List bills. Flags: `--projectId`, `--budgetId`, `--status`, `--page`, `--pageSize` |
58
+ | `list-bills` | List bills and/or claims. Flags: `--projectId`, `--budgetId`, `--status` (CSV, like `DRAFT,PAID` or `ALL`), `--search`, `--isClaimable` (`true`\|`false`), `--createdByIds` (CSV), `--sortBy` (`createdAt`\|`amount`\|`status`), `--sortDir` (`asc`\|`desc`), `--page`, `--pageSize` |
59
+ | `list-claims` | Reimbursable claims only; same flags as `list-bills` except `--isClaimable` is ignored (always true) |
59
60
  | `approve-bill <id>` | Approve a pending bill (matches your pending bill approvals) |
60
61
  | `list-approvals` | Pending approvals. Flag: `--type` = `budget`, `supplier`, `bill`, or `all` |
61
62
  | `list-suppliers` | List suppliers. Flags: `--name`, `--page`, `--perPage` |
package/dist/commands.js CHANGED
@@ -1,13 +1,12 @@
1
- import { api } from "./api-client.js";
1
+ import { BudgetRole, Deals, ExtendedApprovalStatus, ExtendedApprovalType, ExtendedBudgetStatus, TimeFrame, } from "@bb/utils/filter-enums";
2
+ import { api } from "./api-client";
3
+ import { billStatusesForApi, } from "./parse-cli-enums";
2
4
  const BUDGET = "BUDGET";
3
5
  const BILL = "BILL";
4
6
  const SUPPLIER = "SUPPLIER";
5
7
  function isPendingRequest(n) {
6
8
  return n.notificationType === "request" && n.status === "PENDING_APPROVAL";
7
9
  }
8
- function isApprovalType(s) {
9
- return s === BUDGET || s === BILL || s === SUPPLIER;
10
- }
11
10
  const out = (data) => {
12
11
  console.log(JSON.stringify(data, null, 2));
13
12
  };
@@ -16,6 +15,11 @@ export async function listBudgets(opts) {
16
15
  projectId: opts.projectId,
17
16
  name: opts.name,
18
17
  statuses: opts.statuses,
18
+ createdBy: opts.createdBy,
19
+ dateFrom: opts.dateFrom,
20
+ dateTo: opts.dateTo,
21
+ sortBy: opts.sortBy,
22
+ sortDir: opts.sortDir,
19
23
  page: opts.page ?? 1,
20
24
  perPage: opts.perPage ?? 20,
21
25
  };
@@ -30,18 +34,24 @@ export async function getBudget(id) {
30
34
  out({ budget, items });
31
35
  }
32
36
  export async function updateBudgetStatus(budgetId, status) {
33
- const input = {
37
+ const result = await api.budget.updateBudgetStatus.mutate({
34
38
  id: budgetId,
35
- status: status,
36
- };
37
- const result = await api.budget.updateBudgetStatus.mutate(input);
39
+ status,
40
+ });
38
41
  out(result);
39
42
  }
40
43
  export async function listBills(opts) {
41
44
  const input = {
42
45
  projectId: opts.projectId,
43
46
  budgetId: opts.budgetId,
44
- status: opts.status,
47
+ statuses: billStatusesForApi(opts.statuses),
48
+ search: opts.search?.trim() || undefined,
49
+ isClaimable: opts.isClaimable,
50
+ createdByIds: opts.createdByIds && opts.createdByIds.length > 0
51
+ ? opts.createdByIds
52
+ : undefined,
53
+ sortBy: opts.sortBy,
54
+ sortDir: opts.sortDir,
45
55
  page: opts.page ?? 1,
46
56
  pageSize: opts.pageSize ?? 20,
47
57
  };
@@ -61,19 +71,17 @@ export async function approveBill(billId) {
61
71
  billApprovalId: approval.id,
62
72
  status: "APPROVED",
63
73
  });
64
- out({ ...result, success: true, message: "Bill approved" });
74
+ out(result);
65
75
  }
66
76
  export async function listApprovals(opts) {
67
77
  const notifications = await api.budget.getNotifications.query();
68
78
  const pending = notifications.filter(isPendingRequest);
69
- const raw = opts.type?.toUpperCase();
70
- if (raw !== undefined && raw !== "ALL" && !isApprovalType(raw)) {
71
- throw new Error("Invalid --type. Use one of: budget, supplier, bill, all.");
72
- }
73
- const typeFilter = !opts.type || opts.type.toLowerCase() === "all"
79
+ const typeFilter = opts.type === undefined || opts.type === "ALL"
74
80
  ? (_n) => true
75
- : (n) => raw !== undefined && isApprovalType(raw) && n.type === raw;
76
- const filtered = pending.filter(typeFilter);
81
+ : (n) => n.type === opts.type;
82
+ const filtered = pending
83
+ .filter(typeFilter)
84
+ .filter((n) => n.type !== BUDGET || !n.budget?.quickbooksEstimateId);
77
85
  const items = filtered.map((n) => {
78
86
  let id;
79
87
  let name;
@@ -116,3 +124,377 @@ export async function listSuppliers(opts) {
116
124
  });
117
125
  out(result);
118
126
  }
127
+ // --- Bills (MCP parity) ---
128
+ export async function createBillApproval(billId) {
129
+ const result = await api.bill.createApproval.mutate({ billId });
130
+ out(result);
131
+ }
132
+ export async function updateBillStatus(opts) {
133
+ const result = await api.bill.updateStatus.mutate({
134
+ id: opts.id,
135
+ status: opts.status,
136
+ rejectionReason: opts.rejectionReason,
137
+ paymentTrackingUrl: opts.paymentTrackingUrl,
138
+ paymentReference: opts.paymentReference,
139
+ });
140
+ out(result);
141
+ }
142
+ export async function patchBillPayment(opts) {
143
+ const result = await api.bill.patchPayment.mutate({
144
+ id: opts.id,
145
+ paymentTrackingUrl: opts.paymentTrackingUrl,
146
+ paymentReference: opts.paymentReference,
147
+ quickbooksBillId: opts.quickbooksBillId,
148
+ });
149
+ out(result);
150
+ }
151
+ export async function patchBillInvoiceNumber(id, invoiceNumber) {
152
+ const result = await api.bill.patchInvoiceNumber.mutate({
153
+ id,
154
+ invoiceNumber,
155
+ });
156
+ out(result);
157
+ }
158
+ export async function getBillAttachments(id) {
159
+ const result = await api.bill.getAttachments.query({ id });
160
+ out(result);
161
+ }
162
+ export async function getBillDetails(id) {
163
+ const result = await api.bill.getById.query({ id });
164
+ out(result);
165
+ }
166
+ // --- Budgets (MCP parity) ---
167
+ export async function getBudgetItemsOnly(budgetId) {
168
+ const items = await api.budget.getBudgetItems.query({ budgetId });
169
+ out(items);
170
+ }
171
+ export async function addBudgetItems(opts) {
172
+ const result = await api.budgetItem.addBudgetItems.mutate(opts);
173
+ out(result);
174
+ }
175
+ export async function updateBudgetItem(input) {
176
+ const result = await api.budgetItem.updateBudgetItem.mutate(input);
177
+ out(result);
178
+ }
179
+ export async function removeBudgetItem(budgetItemId) {
180
+ const result = await api.budgetItem.removeBudgetItem.mutate({
181
+ budgetItemId,
182
+ });
183
+ out(result);
184
+ }
185
+ export async function getBudgetCategories() {
186
+ const categories = await api.budget.getBudgetCategories.query();
187
+ out(categories);
188
+ }
189
+ export async function getBudgetVersions(budgetId) {
190
+ const versions = await api.budgetVersion.getBudgetVersions.query({
191
+ budgetId,
192
+ });
193
+ out(versions);
194
+ }
195
+ export async function createBudgetApproval(budgetId) {
196
+ const result = await api.budget.createBudgetApproval.mutate({ budgetId });
197
+ out(result);
198
+ }
199
+ export async function getBudgetDetails(budgetId) {
200
+ const detail = await api.budget.getBudgetDetail.query({ id: budgetId });
201
+ out(detail);
202
+ }
203
+ // --- Approvals: budgets / suppliers / bills (MCP parity) ---
204
+ export async function approveBudget(budgetId) {
205
+ const notifications = await api.budget.getNotifications.query();
206
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
207
+ n.status === ExtendedApprovalStatus.PENDING_APPROVAL &&
208
+ n.type === ExtendedApprovalType.BUDGET &&
209
+ (n.budgetId === budgetId ||
210
+ n.budget?.id === budgetId));
211
+ const approval = pending[0];
212
+ if (!approval) {
213
+ throw new Error("No pending budget approval found for this budget and approver.");
214
+ }
215
+ await api.budget.updateBudgetApproval.mutate({
216
+ budgetApprovalId: approval.id,
217
+ status: ExtendedApprovalStatus.APPROVED,
218
+ });
219
+ out({ success: true, message: "Budget approved successfully" });
220
+ }
221
+ export async function rejectBudget(budgetId, reason) {
222
+ const notifications = await api.budget.getNotifications.query();
223
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
224
+ n.status === ExtendedApprovalStatus.PENDING_APPROVAL &&
225
+ n.type === ExtendedApprovalType.BUDGET &&
226
+ (n.budgetId === budgetId ||
227
+ n.budget?.id === budgetId));
228
+ const approval = pending[0];
229
+ if (!approval) {
230
+ throw new Error("No pending budget approval found for this budget and approver.");
231
+ }
232
+ await api.budget.updateBudgetApproval.mutate({
233
+ budgetApprovalId: approval.id,
234
+ status: ExtendedApprovalStatus.REJECTED,
235
+ rejectionReason: reason,
236
+ });
237
+ out({ success: true, message: "Budget rejected" });
238
+ }
239
+ export async function getSupplierDetails(supplierId) {
240
+ const supplier = await api.supplier.getSupplierById.query({
241
+ id: supplierId,
242
+ });
243
+ out(supplier);
244
+ }
245
+ export async function approveSupplier(supplierId) {
246
+ const notifications = await api.budget.getNotifications.query();
247
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
248
+ n.status === ExtendedApprovalStatus.PENDING_APPROVAL &&
249
+ n.type === ExtendedApprovalType.SUPPLIER &&
250
+ (n.supplierId === supplierId || n.supplier?.id === supplierId));
251
+ const approval = pending[0];
252
+ if (!approval) {
253
+ throw new Error("No pending supplier approval found.");
254
+ }
255
+ await api.supplier.updateSupplierApproval.mutate({
256
+ supplierApprovalId: approval.id,
257
+ status: ExtendedApprovalStatus.APPROVED,
258
+ });
259
+ out({ success: true, message: "Supplier approved successfully" });
260
+ }
261
+ export async function rejectSupplier(supplierId, reason) {
262
+ const notifications = await api.budget.getNotifications.query();
263
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
264
+ n.status === ExtendedApprovalStatus.PENDING_APPROVAL &&
265
+ n.type === ExtendedApprovalType.SUPPLIER &&
266
+ (n.supplierId === supplierId || n.supplier?.id === supplierId));
267
+ const approval = pending[0];
268
+ if (!approval) {
269
+ throw new Error("No pending supplier approval found.");
270
+ }
271
+ await api.supplier.updateSupplierApproval.mutate({
272
+ supplierApprovalId: approval.id,
273
+ status: ExtendedApprovalStatus.REJECTED,
274
+ rejectionReason: reason,
275
+ });
276
+ out({ success: true, message: "Supplier rejected" });
277
+ }
278
+ export async function rejectBill(billId, reason) {
279
+ const notifications = await api.budget.getNotifications.query();
280
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
281
+ n.status === ExtendedApprovalStatus.PENDING_APPROVAL &&
282
+ n.type === ExtendedApprovalType.BILL &&
283
+ (n.billId === billId || n.bill?.id === billId));
284
+ const approval = pending[0];
285
+ if (!approval) {
286
+ throw new Error("No pending bill approval found.");
287
+ }
288
+ await api.bill.updateApproval.mutate({
289
+ billApprovalId: approval.id,
290
+ status: ExtendedApprovalStatus.REJECTED,
291
+ rejectionReason: reason,
292
+ });
293
+ out({ success: true, message: "Bill rejected" });
294
+ }
295
+ // --- Companies & projects ---
296
+ export async function listCompanies(opts) {
297
+ const result = await api.company.getCompanies.query({
298
+ name: opts.name,
299
+ createdBy: opts.createdBy,
300
+ page: opts.page ?? 1,
301
+ perPage: opts.perPage ?? 20,
302
+ });
303
+ out(result);
304
+ }
305
+ export async function getCompany(id) {
306
+ const company = await api.company.getCompanyById.query({ id });
307
+ out(company);
308
+ }
309
+ export async function createCompany(opts) {
310
+ const result = await api.company.createCompany.mutate(opts);
311
+ out(result);
312
+ }
313
+ export async function listProjects(opts) {
314
+ const result = await api.project.getAllProjects.query({
315
+ companyId: opts.companyId,
316
+ name: opts.name,
317
+ status: opts.status,
318
+ page: opts.page ?? 1,
319
+ perPage: opts.perPage ?? 20,
320
+ });
321
+ out(result);
322
+ }
323
+ export async function getProject(id) {
324
+ const [project, budgetsOverview, billsOverview] = await Promise.all([
325
+ api.project.getProjectDetailsById.query({ id }),
326
+ api.project.getProjectBudgetsOverview.query({ projectId: id }),
327
+ api.project.getProjectBillsOverview.query({ projectId: id }),
328
+ ]);
329
+ out({ project, budgetsOverview, billsOverview });
330
+ }
331
+ export async function createProject(opts) {
332
+ const result = await api.project.createProject.mutate({
333
+ name: opts.name,
334
+ description: opts.description,
335
+ companyId: opts.companyId,
336
+ contactPersonId: opts.contactPersonId,
337
+ dateRange: {
338
+ from: new Date(opts.startDate),
339
+ to: opts.endDate ? new Date(opts.endDate) : undefined,
340
+ },
341
+ });
342
+ out(result);
343
+ }
344
+ export async function updateProjectStatus(id, status) {
345
+ const result = await api.project.updateProjectStatus.mutate({ id, status });
346
+ out(result);
347
+ }
348
+ // --- Contacts ---
349
+ export async function listContacts(companyId) {
350
+ const contacts = await api.contactPerson.getContactPersonByCompanyId.query({
351
+ companyId,
352
+ });
353
+ out(contacts);
354
+ }
355
+ // --- Items ---
356
+ export async function listItems(opts) {
357
+ const result = await api.item.getItems.query({
358
+ page: opts.page ?? 1,
359
+ perPage: opts.perPage ?? 20,
360
+ name: opts.name,
361
+ });
362
+ out(result);
363
+ }
364
+ export async function getItem(id) {
365
+ const item = await api.item.getItem.query({ id });
366
+ out(item);
367
+ }
368
+ export async function listItemCategories(opts) {
369
+ const result = await api.itemCategory.getItemCategories.query({
370
+ page: opts.page ?? 1,
371
+ perPage: opts.perPage ?? 50,
372
+ });
373
+ out(result);
374
+ }
375
+ // --- Supplier analytics ---
376
+ export async function getSupplierAnalytics(opts) {
377
+ const result = await api.dashboard.getSupplierTableData.query({
378
+ page: opts.page ?? 1,
379
+ perPage: opts.perPage ?? 20,
380
+ sort: [],
381
+ name: opts.name,
382
+ timeFrame: (opts.timeFrame ?? "ALL"),
383
+ });
384
+ out(result);
385
+ }
386
+ // --- Dashboard / analytics ---
387
+ export async function listUsers() {
388
+ const users = await api.user.getAllUsers.query();
389
+ out(users);
390
+ }
391
+ export async function getUserPerformance(userId) {
392
+ const result = await api.dashboard.getUserPerformance.query({ userId });
393
+ out(result);
394
+ }
395
+ export async function getDashboard(input) {
396
+ const result = await api.dashboard.getDashboard.query({
397
+ userId: input.userId,
398
+ role: input.role ?? "ALL",
399
+ deals: input.deals ?? "ALL",
400
+ timeFrame: input.timeFrame ?? "ALL",
401
+ startDate: input.startDate,
402
+ endDate: input.endDate,
403
+ });
404
+ out(result);
405
+ }
406
+ export async function getFinancialOverview(input) {
407
+ const result = await api.dashboard.getFinancialOverview.query({
408
+ userId: input.userId,
409
+ role: input.role ?? BudgetRole.ALL,
410
+ deals: input.deals ?? Deals.ALL,
411
+ timeFrame: input.timeFrame ?? TimeFrame.ALL,
412
+ startDate: input.startDate,
413
+ endDate: input.endDate,
414
+ });
415
+ out(result);
416
+ }
417
+ // --- Error logs ---
418
+ export async function getRecentErrors(opts) {
419
+ const result = await api.errorLog.getRecentErrors.query({
420
+ page: opts.page ?? 1,
421
+ perPage: opts.perPage ?? 10,
422
+ severity: opts.severity,
423
+ status: opts.status,
424
+ });
425
+ out(result);
426
+ }
427
+ export async function getErrorMetrics() {
428
+ const result = await api.errorLog.getErrorMetrics.query({});
429
+ out(result);
430
+ }
431
+ // --- Historical / benchmarks ---
432
+ export async function getApprovedBudgets(opts) {
433
+ const perPage = Math.min(opts.limit ?? 50, 100);
434
+ const { budgets, totalCount } = await api.budget.getAllBudgets.query({
435
+ projectId: opts.projectId,
436
+ statuses: [
437
+ ExtendedBudgetStatus.APPROVED,
438
+ ExtendedBudgetStatus.ESTIMATE_ACCEPTED,
439
+ ExtendedBudgetStatus.ESTIMATE_CLOSED,
440
+ ],
441
+ page: 1,
442
+ perPage,
443
+ });
444
+ const items = (budgets ?? []).map((b) => {
445
+ const calc = b;
446
+ const revenue = calc.totalSellingAfterDiscount ?? 0;
447
+ const cost = calc.totalCostWithoutGst ?? 0;
448
+ const gp = calc.gpPercentage ?? 0;
449
+ const date = b.date instanceof Date ? b.date.toISOString() : String(b.date);
450
+ const createdAt = b.createdAt instanceof Date
451
+ ? b.createdAt.toISOString()
452
+ : String(b.createdAt);
453
+ return {
454
+ id: b.id,
455
+ name: b.name,
456
+ categoryId: b.categoryId,
457
+ company: b.project?.company?.name,
458
+ date,
459
+ pax: b.pax,
460
+ revenue: Number(revenue.toFixed(2)),
461
+ cost: Number(cost.toFixed(2)),
462
+ gpPercentage: Number(gp.toFixed(2)),
463
+ createdAt,
464
+ };
465
+ });
466
+ let filtered = items;
467
+ if (opts.minRevenue != null) {
468
+ const minR = opts.minRevenue;
469
+ filtered = filtered.filter((i) => i.revenue >= minR);
470
+ }
471
+ if (opts.maxRevenue != null) {
472
+ const maxR = opts.maxRevenue;
473
+ filtered = filtered.filter((i) => i.revenue <= maxR);
474
+ }
475
+ out({ budgets: filtered, count: filtered.length, totalCount });
476
+ }
477
+ export async function getBudgetCategoryBenchmarks(opts) {
478
+ const result = await api.budget.getBudgetCategoryBenchmarks.query({
479
+ categoryId: opts.categoryId,
480
+ limit: opts.limit ?? 100,
481
+ });
482
+ out(result);
483
+ }
484
+ export async function getItemPricingHistory(opts) {
485
+ const result = await api.budget.getItemPricingHistory.query({
486
+ itemName: opts.itemName,
487
+ categoryId: opts.categoryId,
488
+ supplierId: opts.supplierId,
489
+ limit: opts.limit ?? 100,
490
+ });
491
+ out(result);
492
+ }
493
+ export async function getSupplierPricingHistory(opts) {
494
+ const result = await api.budget.getSupplierPricingHistory.query({
495
+ supplierId: opts.supplierId,
496
+ itemCategoryId: opts.itemCategoryId,
497
+ limit: opts.limit ?? 100,
498
+ });
499
+ out(result);
500
+ }