@go-labs-sg/bb 2.40.0 → 2.42.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 +12 -0
- package/command-manifest.json +25 -1
- package/command-reference.md +121 -1
- package/dist/index.js +601 -315
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6577,6 +6577,87 @@ var api2 = new Proxy({}, {
|
|
|
6577
6577
|
get: (_target, property, receiver) => Reflect.get(activeClient, property, receiver)
|
|
6578
6578
|
});
|
|
6579
6579
|
|
|
6580
|
+
// src/prisma-enums.ts
|
|
6581
|
+
var ProjectStatus = {
|
|
6582
|
+
PITCH: "PITCH",
|
|
6583
|
+
WON: "WON",
|
|
6584
|
+
COMPLETED: "COMPLETED",
|
|
6585
|
+
RECONCILED: "RECONCILED",
|
|
6586
|
+
LOST: "LOST"
|
|
6587
|
+
};
|
|
6588
|
+
var SupplierQuotationStatus = {
|
|
6589
|
+
DRAFT: "DRAFT",
|
|
6590
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6591
|
+
APPROVED: "APPROVED",
|
|
6592
|
+
REJECTED: "REJECTED",
|
|
6593
|
+
SUPERSEDED: "SUPERSEDED"
|
|
6594
|
+
};
|
|
6595
|
+
var BudgetStatus = {
|
|
6596
|
+
DRAFT: "DRAFT",
|
|
6597
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6598
|
+
APPROVED: "APPROVED",
|
|
6599
|
+
REJECTED: "REJECTED",
|
|
6600
|
+
ESTIMATE_CREATED: "ESTIMATE_CREATED",
|
|
6601
|
+
ESTIMATE_SENT: "ESTIMATE_SENT",
|
|
6602
|
+
ESTIMATE_ACCEPTED: "ESTIMATE_ACCEPTED",
|
|
6603
|
+
ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
|
|
6604
|
+
ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
|
|
6605
|
+
};
|
|
6606
|
+
var ApprovalStatus = {
|
|
6607
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6608
|
+
APPROVED: "APPROVED",
|
|
6609
|
+
REJECTED: "REJECTED",
|
|
6610
|
+
SUPERSEDED: "SUPERSEDED"
|
|
6611
|
+
};
|
|
6612
|
+
var ApprovalType = {
|
|
6613
|
+
BUDGET: "BUDGET",
|
|
6614
|
+
SUPPLIER: "SUPPLIER",
|
|
6615
|
+
BILL: "BILL",
|
|
6616
|
+
CUSTOMER_INVOICE: "CUSTOMER_INVOICE",
|
|
6617
|
+
QUOTATION: "QUOTATION"
|
|
6618
|
+
};
|
|
6619
|
+
var UserRole = {
|
|
6620
|
+
USER: "USER",
|
|
6621
|
+
LEAD: "LEAD",
|
|
6622
|
+
ADMIN: "ADMIN",
|
|
6623
|
+
INSIDE_SALES: "INSIDE_SALES",
|
|
6624
|
+
ACCOUNTING_TEAM: "ACCOUNTING_TEAM"
|
|
6625
|
+
};
|
|
6626
|
+
var BillStatus = {
|
|
6627
|
+
DRAFT: "DRAFT",
|
|
6628
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6629
|
+
CHECKED: "CHECKED",
|
|
6630
|
+
APPROVED: "APPROVED",
|
|
6631
|
+
PAID: "PAID",
|
|
6632
|
+
REJECTED: "REJECTED"
|
|
6633
|
+
};
|
|
6634
|
+
var BillVerificationStatus = {
|
|
6635
|
+
PENDING: "PENDING",
|
|
6636
|
+
PASS: "PASS",
|
|
6637
|
+
FAIL: "FAIL",
|
|
6638
|
+
MANUAL_REVIEW: "MANUAL_REVIEW"
|
|
6639
|
+
};
|
|
6640
|
+
var SupplierQuotationFileRole = {
|
|
6641
|
+
ORIGINAL: "ORIGINAL",
|
|
6642
|
+
STAFF_SIGNED: "STAFF_SIGNED",
|
|
6643
|
+
FINAL_SIGNED: "FINAL_SIGNED"
|
|
6644
|
+
};
|
|
6645
|
+
var InboundProcessingMode = {
|
|
6646
|
+
COLLECT_ONLY: "COLLECT_ONLY",
|
|
6647
|
+
SHADOW: "SHADOW",
|
|
6648
|
+
ACTIVE: "ACTIVE"
|
|
6649
|
+
};
|
|
6650
|
+
var AttachmentType = {
|
|
6651
|
+
GLOBAL_ATTACHMENT: "GLOBAL_ATTACHMENT",
|
|
6652
|
+
BUDGET: "BUDGET",
|
|
6653
|
+
BUDGET_WIN_PROOF: "BUDGET_WIN_PROOF",
|
|
6654
|
+
ITEM: "ITEM",
|
|
6655
|
+
SUPPLIER: "SUPPLIER",
|
|
6656
|
+
BILL: "BILL",
|
|
6657
|
+
BILL_PAYMENT_PROOF: "BILL_PAYMENT_PROOF",
|
|
6658
|
+
QUOTATION: "QUOTATION"
|
|
6659
|
+
};
|
|
6660
|
+
|
|
6580
6661
|
// src/commands/index.ts
|
|
6581
6662
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6582
6663
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
@@ -6588,14 +6669,14 @@ var EstimationMode = {
|
|
|
6588
6669
|
MICRO: "MICRO",
|
|
6589
6670
|
MANPOWER_SPECIFIED: "MANPOWER_SPECIFIED"
|
|
6590
6671
|
};
|
|
6591
|
-
var
|
|
6672
|
+
var ProjectStatus2 = {
|
|
6592
6673
|
PITCH: "PITCH",
|
|
6593
6674
|
WON: "WON",
|
|
6594
6675
|
COMPLETED: "COMPLETED",
|
|
6595
6676
|
RECONCILED: "RECONCILED",
|
|
6596
6677
|
LOST: "LOST"
|
|
6597
6678
|
};
|
|
6598
|
-
var
|
|
6679
|
+
var BudgetStatus2 = {
|
|
6599
6680
|
DRAFT: "DRAFT",
|
|
6600
6681
|
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6601
6682
|
APPROVED: "APPROVED",
|
|
@@ -6606,13 +6687,13 @@ var BudgetStatus = {
|
|
|
6606
6687
|
ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
|
|
6607
6688
|
ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
|
|
6608
6689
|
};
|
|
6609
|
-
var
|
|
6690
|
+
var ApprovalStatus2 = {
|
|
6610
6691
|
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6611
6692
|
APPROVED: "APPROVED",
|
|
6612
6693
|
REJECTED: "REJECTED",
|
|
6613
6694
|
SUPERSEDED: "SUPERSEDED"
|
|
6614
6695
|
};
|
|
6615
|
-
var
|
|
6696
|
+
var ApprovalType2 = {
|
|
6616
6697
|
BUDGET: "BUDGET",
|
|
6617
6698
|
SUPPLIER: "SUPPLIER",
|
|
6618
6699
|
BILL: "BILL",
|
|
@@ -6639,7 +6720,15 @@ var CustomerInvoiceBatchStatus = {
|
|
|
6639
6720
|
EXPIRED_VOIDED: "EXPIRED_VOIDED",
|
|
6640
6721
|
PARTIAL_QBO_FAILURE: "PARTIAL_QBO_FAILURE"
|
|
6641
6722
|
};
|
|
6642
|
-
var
|
|
6723
|
+
var CustomerInvoiceStatus = {
|
|
6724
|
+
CREATED_IN_QBO: "CREATED_IN_QBO",
|
|
6725
|
+
APPROVED: "APPROVED",
|
|
6726
|
+
SENT: "SENT",
|
|
6727
|
+
PAID: "PAID",
|
|
6728
|
+
VOIDED: "VOIDED",
|
|
6729
|
+
QBO_SYNC_ERROR: "QBO_SYNC_ERROR"
|
|
6730
|
+
};
|
|
6731
|
+
var BillStatus2 = {
|
|
6643
6732
|
DRAFT: "DRAFT",
|
|
6644
6733
|
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6645
6734
|
CHECKED: "CHECKED",
|
|
@@ -6650,23 +6739,23 @@ var BillStatus = {
|
|
|
6650
6739
|
|
|
6651
6740
|
// ../utils/src/filter-enums.ts
|
|
6652
6741
|
var ExtendedApprovalStatus = {
|
|
6653
|
-
...
|
|
6742
|
+
...ApprovalStatus2,
|
|
6654
6743
|
ALL: "ALL"
|
|
6655
6744
|
};
|
|
6656
6745
|
var ExtendedApprovalType = {
|
|
6657
|
-
...
|
|
6746
|
+
...ApprovalType2,
|
|
6658
6747
|
ALL: "ALL"
|
|
6659
6748
|
};
|
|
6660
6749
|
var ExtendedProjectStatus = {
|
|
6661
|
-
...
|
|
6750
|
+
...ProjectStatus2,
|
|
6662
6751
|
ALL: "ALL"
|
|
6663
6752
|
};
|
|
6664
6753
|
var ExtendedBudgetStatus = {
|
|
6665
|
-
...
|
|
6754
|
+
...BudgetStatus2,
|
|
6666
6755
|
ALL: "ALL"
|
|
6667
6756
|
};
|
|
6668
6757
|
var ExtendedBillStatus = {
|
|
6669
|
-
...
|
|
6758
|
+
...BillStatus2,
|
|
6670
6759
|
ALL: "ALL"
|
|
6671
6760
|
};
|
|
6672
6761
|
var ExtendedEstimationMode = {
|
|
@@ -6700,6 +6789,186 @@ var ExtendedErrorStatus = {
|
|
|
6700
6789
|
...ErrorStatus
|
|
6701
6790
|
};
|
|
6702
6791
|
|
|
6792
|
+
// src/runtime/error.ts
|
|
6793
|
+
var invocationErrorCodes = new Set([
|
|
6794
|
+
"USAGE",
|
|
6795
|
+
"VALIDATION",
|
|
6796
|
+
"CONFIRMATION_REQUIRED"
|
|
6797
|
+
]);
|
|
6798
|
+
var trpcCodeMap = {
|
|
6799
|
+
BAD_REQUEST: "VALIDATION",
|
|
6800
|
+
CONFLICT: "CONFLICT",
|
|
6801
|
+
FORBIDDEN: "AUTHORIZATION",
|
|
6802
|
+
INTERNAL_SERVER_ERROR: "API",
|
|
6803
|
+
METHOD_NOT_SUPPORTED: "API",
|
|
6804
|
+
NOT_FOUND: "NOT_FOUND",
|
|
6805
|
+
PARSE_ERROR: "VALIDATION",
|
|
6806
|
+
PAYLOAD_TOO_LARGE: "VALIDATION",
|
|
6807
|
+
PRECONDITION_FAILED: "CONFLICT",
|
|
6808
|
+
TIMEOUT: "NETWORK",
|
|
6809
|
+
TOO_MANY_REQUESTS: "API",
|
|
6810
|
+
UNAUTHORIZED: "AUTHENTICATION",
|
|
6811
|
+
UNPROCESSABLE_CONTENT: "VALIDATION"
|
|
6812
|
+
};
|
|
6813
|
+
var cliErrorCodes = new Set([
|
|
6814
|
+
"USAGE",
|
|
6815
|
+
"VALIDATION",
|
|
6816
|
+
"CONFIRMATION_REQUIRED",
|
|
6817
|
+
"AUTHENTICATION",
|
|
6818
|
+
"AUTHORIZATION",
|
|
6819
|
+
"NOT_FOUND",
|
|
6820
|
+
"CONFLICT",
|
|
6821
|
+
"API",
|
|
6822
|
+
"NETWORK",
|
|
6823
|
+
"ABORTED",
|
|
6824
|
+
"INTERRUPTED",
|
|
6825
|
+
"INTERNAL"
|
|
6826
|
+
]);
|
|
6827
|
+
|
|
6828
|
+
class CliRuntimeError extends Error {
|
|
6829
|
+
code;
|
|
6830
|
+
details;
|
|
6831
|
+
requestId;
|
|
6832
|
+
constructor(code, message, options = {}) {
|
|
6833
|
+
super(message);
|
|
6834
|
+
this.name = "CliRuntimeError";
|
|
6835
|
+
this.code = code;
|
|
6836
|
+
this.details = options.details;
|
|
6837
|
+
this.requestId = options.requestId;
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6840
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6841
|
+
var asNonEmptyString = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
6842
|
+
var formatValidationMessage = (message, data) => {
|
|
6843
|
+
const zodError = isRecord(data?.zodError) ? data.zodError : undefined;
|
|
6844
|
+
const fieldErrors = isRecord(zodError?.fieldErrors) ? zodError.fieldErrors : undefined;
|
|
6845
|
+
const fields = fieldErrors ? Object.entries(fieldErrors).flatMap(([field, errors]) => Array.isArray(errors) ? errors.filter((entry) => typeof entry === "string").map((entry) => `${field}: ${entry}`) : []) : [];
|
|
6846
|
+
const formErrors = Array.isArray(zodError?.formErrors) ? zodError.formErrors.filter((entry) => typeof entry === "string") : [];
|
|
6847
|
+
const validationIssues = [...fields, ...formErrors];
|
|
6848
|
+
if (validationIssues.length > 0)
|
|
6849
|
+
return validationIssues.join("; ");
|
|
6850
|
+
let parsed = message;
|
|
6851
|
+
for (let depth = 0;depth < 3 && typeof parsed === "string"; depth++) {
|
|
6852
|
+
try {
|
|
6853
|
+
parsed = JSON.parse(parsed);
|
|
6854
|
+
} catch {
|
|
6855
|
+
return message;
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isRecord))
|
|
6859
|
+
return message;
|
|
6860
|
+
const issues = parsed.map((issue) => {
|
|
6861
|
+
if (!Array.isArray(issue.path) || typeof issue.message !== "string" || typeof issue.code !== "string")
|
|
6862
|
+
return;
|
|
6863
|
+
const path = issue.path.filter((part) => typeof part === "string" || typeof part === "number").join(".") || "payload";
|
|
6864
|
+
if (issue.code === "invalid_type" && typeof issue.expected === "string" && issue.message.toLowerCase().includes("required")) {
|
|
6865
|
+
return `${path} must be a ${issue.expected}${path === "cost" ? ', e.g. "19"' : ""}`;
|
|
6866
|
+
}
|
|
6867
|
+
if (issue.code === "invalid_format" && issue.format === "regex" && path === "name" && String(issue.pattern ?? issue.message).includes("A-Za-z0-9,.?@&!#'~* _;+-")) {
|
|
6868
|
+
return `${path} contains characters that are not allowed. Allowed: letters, numbers, spaces and , . ? @ & ! # ' ~ * _ ; + -`;
|
|
6869
|
+
}
|
|
6870
|
+
return `${path}: ${issue.message}`;
|
|
6871
|
+
});
|
|
6872
|
+
return issues.every((issue) => issue !== undefined) ? issues.join("; ") : message;
|
|
6873
|
+
};
|
|
6874
|
+
var asDetails = (value) => {
|
|
6875
|
+
if (!isRecord(value))
|
|
6876
|
+
return;
|
|
6877
|
+
const details = {};
|
|
6878
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
6879
|
+
if (isJsonValue(nestedValue))
|
|
6880
|
+
details[key] = nestedValue;
|
|
6881
|
+
}
|
|
6882
|
+
return Object.keys(details).length > 0 ? details : undefined;
|
|
6883
|
+
};
|
|
6884
|
+
var isJsonValue = (value) => {
|
|
6885
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
6886
|
+
return true;
|
|
6887
|
+
}
|
|
6888
|
+
if (Array.isArray(value))
|
|
6889
|
+
return value.every(isJsonValue);
|
|
6890
|
+
return isRecord(value) && Object.values(value).every(isJsonValue);
|
|
6891
|
+
};
|
|
6892
|
+
var mapErrorCode = (value) => {
|
|
6893
|
+
if (typeof value !== "string")
|
|
6894
|
+
return;
|
|
6895
|
+
if (cliErrorCodes.has(value))
|
|
6896
|
+
return value;
|
|
6897
|
+
if (value in trpcCodeMap)
|
|
6898
|
+
return trpcCodeMap[value];
|
|
6899
|
+
return;
|
|
6900
|
+
};
|
|
6901
|
+
var networkMessagePattern = /\b(fetch failed|network error|unable to connect|econnrefused|econnreset|enotfound|etimedout|socket hang up)\b/i;
|
|
6902
|
+
var isNetworkError = (error, depth = 0) => {
|
|
6903
|
+
if (depth > 3 || !isRecord(error))
|
|
6904
|
+
return false;
|
|
6905
|
+
const message = asNonEmptyString(error.message);
|
|
6906
|
+
if (message !== undefined && networkMessagePattern.test(message))
|
|
6907
|
+
return true;
|
|
6908
|
+
const code = asNonEmptyString(error.code);
|
|
6909
|
+
if (code !== undefined && networkMessagePattern.test(code))
|
|
6910
|
+
return true;
|
|
6911
|
+
return isNetworkError(error.cause, depth + 1);
|
|
6912
|
+
};
|
|
6913
|
+
var normalizeCliError = (error) => {
|
|
6914
|
+
if (error instanceof CliRuntimeError) {
|
|
6915
|
+
return {
|
|
6916
|
+
code: error.code,
|
|
6917
|
+
message: error.message,
|
|
6918
|
+
...error.details === undefined ? {} : { details: error.details },
|
|
6919
|
+
...error.requestId === undefined ? {} : { requestId: error.requestId }
|
|
6920
|
+
};
|
|
6921
|
+
}
|
|
6922
|
+
if (isNetworkError(error)) {
|
|
6923
|
+
return {
|
|
6924
|
+
code: "NETWORK",
|
|
6925
|
+
message: error instanceof Error ? error.message : "The Budget Builder API could not be reached."
|
|
6926
|
+
};
|
|
6927
|
+
}
|
|
6928
|
+
if (isRecord(error)) {
|
|
6929
|
+
const data = isRecord(error.data) ? error.data : undefined;
|
|
6930
|
+
const transportCode = mapErrorCode(data?.code ?? error.code);
|
|
6931
|
+
if (error instanceof Error && data === undefined && transportCode === undefined) {
|
|
6932
|
+
return { code: "INTERNAL", message: error.message };
|
|
6933
|
+
}
|
|
6934
|
+
const code = transportCode ?? "API";
|
|
6935
|
+
const rawMessage = asNonEmptyString(error.message) ?? "The Budget Builder API request failed.";
|
|
6936
|
+
const message = code === "VALIDATION" ? formatValidationMessage(rawMessage, data) : rawMessage;
|
|
6937
|
+
const requestId = asNonEmptyString(data?.requestId) ?? asNonEmptyString(data?.requestID) ?? asNonEmptyString(error.requestId);
|
|
6938
|
+
const details = asDetails(data?.details ?? error.details);
|
|
6939
|
+
return {
|
|
6940
|
+
code,
|
|
6941
|
+
message,
|
|
6942
|
+
...details === undefined ? {} : { details },
|
|
6943
|
+
...requestId === undefined ? {} : { requestId }
|
|
6944
|
+
};
|
|
6945
|
+
}
|
|
6946
|
+
if (error instanceof Error) {
|
|
6947
|
+
return { code: "INTERNAL", message: error.message };
|
|
6948
|
+
}
|
|
6949
|
+
return { code: "INTERNAL", message: "An unexpected CLI error occurred." };
|
|
6950
|
+
};
|
|
6951
|
+
var exitCodeForCliError = (error) => {
|
|
6952
|
+
if (error.code === "INTERRUPTED")
|
|
6953
|
+
return 130;
|
|
6954
|
+
return invocationErrorCodes.has(error.code) ? 2 : 1;
|
|
6955
|
+
};
|
|
6956
|
+
|
|
6957
|
+
// src/budget-create-outcome.ts
|
|
6958
|
+
var isP2028Error = (value, depth = 0) => {
|
|
6959
|
+
if (depth > 3 || typeof value !== "object" || value === null)
|
|
6960
|
+
return false;
|
|
6961
|
+
const error = value;
|
|
6962
|
+
return error.code === "P2028" || typeof error.message === "string" && /\bP2028\b/.test(error.message) || isP2028Error(error.cause, depth + 1) || isP2028Error(error.data, depth + 1) || isP2028Error(error.error, depth + 1);
|
|
6963
|
+
};
|
|
6964
|
+
var budgetCreateUncertainError = (error, input) => {
|
|
6965
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6966
|
+
const normalized = normalizeCliError(error);
|
|
6967
|
+
if (!isP2028Error(error) && normalized.code !== "NETWORK")
|
|
6968
|
+
return;
|
|
6969
|
+
return new CliRuntimeError(normalized.code === "NETWORK" ? "NETWORK" : "API", `Budget creation outcome is uncertain (the write may have committed). Run list-budgets --projectId ${input.projectId} and check for ${JSON.stringify(input.name)} before retrying; matching names alone may be ambiguous. Original error: ${message}`);
|
|
6970
|
+
};
|
|
6971
|
+
|
|
6703
6972
|
// src/commands/invoice-splits.ts
|
|
6704
6973
|
var invoiceSplitSetupNextAction = (budgetId) => ({
|
|
6705
6974
|
prompt: "Review the uninvoiced balance, choose all or part of the remaining percentage, then supply invoice and due dates, billing address, and customer message. Non-admin creation requests admin approval; admin creation auto-approves. Sending remains separate.",
|
|
@@ -6712,58 +6981,16 @@ var invoiceSplitSetupNextAction = (budgetId) => ({
|
|
|
6712
6981
|
var customerInvoiceLifecycleStatuses = [
|
|
6713
6982
|
...Object.values(CustomerInvoiceBatchStatus),
|
|
6714
6983
|
"PARTIALLY_SENT",
|
|
6715
|
-
|
|
6984
|
+
CustomerInvoiceStatus.SENT,
|
|
6716
6985
|
"PARTIALLY_PAID",
|
|
6717
|
-
|
|
6718
|
-
|
|
6986
|
+
CustomerInvoiceStatus.PAID,
|
|
6987
|
+
CustomerInvoiceStatus.VOIDED
|
|
6719
6988
|
];
|
|
6720
6989
|
|
|
6721
|
-
// src/prisma-enums.ts
|
|
6722
|
-
var ProjectStatus2 = {
|
|
6723
|
-
PITCH: "PITCH",
|
|
6724
|
-
WON: "WON",
|
|
6725
|
-
COMPLETED: "COMPLETED",
|
|
6726
|
-
RECONCILED: "RECONCILED",
|
|
6727
|
-
LOST: "LOST"
|
|
6728
|
-
};
|
|
6729
|
-
var SupplierQuotationStatus = {
|
|
6730
|
-
DRAFT: "DRAFT",
|
|
6731
|
-
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6732
|
-
APPROVED: "APPROVED",
|
|
6733
|
-
REJECTED: "REJECTED",
|
|
6734
|
-
SUPERSEDED: "SUPERSEDED"
|
|
6735
|
-
};
|
|
6736
|
-
var BudgetStatus2 = {
|
|
6737
|
-
DRAFT: "DRAFT",
|
|
6738
|
-
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6739
|
-
APPROVED: "APPROVED",
|
|
6740
|
-
REJECTED: "REJECTED",
|
|
6741
|
-
ESTIMATE_CREATED: "ESTIMATE_CREATED",
|
|
6742
|
-
ESTIMATE_SENT: "ESTIMATE_SENT",
|
|
6743
|
-
ESTIMATE_ACCEPTED: "ESTIMATE_ACCEPTED",
|
|
6744
|
-
ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
|
|
6745
|
-
ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
|
|
6746
|
-
};
|
|
6747
|
-
var UserRole = {
|
|
6748
|
-
USER: "USER",
|
|
6749
|
-
LEAD: "LEAD",
|
|
6750
|
-
ADMIN: "ADMIN",
|
|
6751
|
-
INSIDE_SALES: "INSIDE_SALES",
|
|
6752
|
-
ACCOUNTING_TEAM: "ACCOUNTING_TEAM"
|
|
6753
|
-
};
|
|
6754
|
-
var BillStatus2 = {
|
|
6755
|
-
DRAFT: "DRAFT",
|
|
6756
|
-
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
6757
|
-
CHECKED: "CHECKED",
|
|
6758
|
-
APPROVED: "APPROVED",
|
|
6759
|
-
PAID: "PAID",
|
|
6760
|
-
REJECTED: "REJECTED"
|
|
6761
|
-
};
|
|
6762
|
-
|
|
6763
6990
|
// src/parse-cli-enums.ts
|
|
6764
|
-
var BUDGET_STATUS_VALUES = new Set(Object.values(
|
|
6765
|
-
var BILL_STATUS_VALUES = new Set(Object.values(
|
|
6766
|
-
var PROJECT_STATUS_VALUES = new Set(Object.values(
|
|
6991
|
+
var BUDGET_STATUS_VALUES = new Set(Object.values(BudgetStatus));
|
|
6992
|
+
var BILL_STATUS_VALUES = new Set(Object.values(BillStatus));
|
|
6993
|
+
var PROJECT_STATUS_VALUES = new Set(Object.values(ProjectStatus));
|
|
6767
6994
|
var SUPPLIER_QUOTATION_STATUS_VALUES = new Set(Object.values(SupplierQuotationStatus));
|
|
6768
6995
|
var CUSTOMER_INVOICE_LIFECYCLE_STATUS_VALUES = new Set(customerInvoiceLifecycleStatuses);
|
|
6769
6996
|
var USER_ROLE_VALUES = new Set(Object.values(UserRole));
|
|
@@ -6810,7 +7037,7 @@ function parseBillStatusForUpdate(raw) {
|
|
|
6810
7037
|
if (!BILL_STATUS_VALUES.has(raw)) {
|
|
6811
7038
|
throw new Error(`Invalid bill status "${raw}". Use one of: ${[...BILL_STATUS_VALUES].join(", ")}.`);
|
|
6812
7039
|
}
|
|
6813
|
-
if (raw ===
|
|
7040
|
+
if (raw === BillStatus.PENDING_APPROVAL) {
|
|
6814
7041
|
throw new Error("Cannot set PENDING_APPROVAL via update-bill-status. Use create-bill-approval <billId> instead.");
|
|
6815
7042
|
}
|
|
6816
7043
|
return raw;
|
|
@@ -7049,10 +7276,10 @@ function parseApprovalTypeFlag(raw) {
|
|
|
7049
7276
|
}
|
|
7050
7277
|
return u;
|
|
7051
7278
|
}
|
|
7052
|
-
var budgetStatusesForHelp = Object.values(
|
|
7053
|
-
var billStatusesForHelp = Object.values(
|
|
7054
|
-
var billStatusesForUpdateHelp = Object.values(
|
|
7055
|
-
var projectStatusesForHelp = Object.values(
|
|
7279
|
+
var budgetStatusesForHelp = Object.values(BudgetStatus);
|
|
7280
|
+
var billStatusesForHelp = Object.values(BillStatus);
|
|
7281
|
+
var billStatusesForUpdateHelp = Object.values(BillStatus).filter((s) => s !== BillStatus.PENDING_APPROVAL);
|
|
7282
|
+
var projectStatusesForHelp = Object.values(ProjectStatus);
|
|
7056
7283
|
var userRolesForHelp = Object.values(UserRole);
|
|
7057
7284
|
|
|
7058
7285
|
// ../utils/src/rich-text.ts
|
|
@@ -7088,142 +7315,11 @@ var normalizePlainTextRichTextField = ({
|
|
|
7088
7315
|
};
|
|
7089
7316
|
};
|
|
7090
7317
|
|
|
7091
|
-
// src/runtime/error.ts
|
|
7092
|
-
var invocationErrorCodes = new Set([
|
|
7093
|
-
"USAGE",
|
|
7094
|
-
"VALIDATION",
|
|
7095
|
-
"CONFIRMATION_REQUIRED"
|
|
7096
|
-
]);
|
|
7097
|
-
var trpcCodeMap = {
|
|
7098
|
-
BAD_REQUEST: "VALIDATION",
|
|
7099
|
-
CONFLICT: "CONFLICT",
|
|
7100
|
-
FORBIDDEN: "AUTHORIZATION",
|
|
7101
|
-
INTERNAL_SERVER_ERROR: "API",
|
|
7102
|
-
METHOD_NOT_SUPPORTED: "API",
|
|
7103
|
-
NOT_FOUND: "NOT_FOUND",
|
|
7104
|
-
PARSE_ERROR: "VALIDATION",
|
|
7105
|
-
PAYLOAD_TOO_LARGE: "VALIDATION",
|
|
7106
|
-
PRECONDITION_FAILED: "CONFLICT",
|
|
7107
|
-
TIMEOUT: "NETWORK",
|
|
7108
|
-
TOO_MANY_REQUESTS: "API",
|
|
7109
|
-
UNAUTHORIZED: "AUTHENTICATION",
|
|
7110
|
-
UNPROCESSABLE_CONTENT: "VALIDATION"
|
|
7111
|
-
};
|
|
7112
|
-
var cliErrorCodes = new Set([
|
|
7113
|
-
"USAGE",
|
|
7114
|
-
"VALIDATION",
|
|
7115
|
-
"CONFIRMATION_REQUIRED",
|
|
7116
|
-
"AUTHENTICATION",
|
|
7117
|
-
"AUTHORIZATION",
|
|
7118
|
-
"NOT_FOUND",
|
|
7119
|
-
"CONFLICT",
|
|
7120
|
-
"API",
|
|
7121
|
-
"NETWORK",
|
|
7122
|
-
"ABORTED",
|
|
7123
|
-
"INTERRUPTED",
|
|
7124
|
-
"INTERNAL"
|
|
7125
|
-
]);
|
|
7126
|
-
|
|
7127
|
-
class CliRuntimeError extends Error {
|
|
7128
|
-
code;
|
|
7129
|
-
details;
|
|
7130
|
-
requestId;
|
|
7131
|
-
constructor(code, message, options = {}) {
|
|
7132
|
-
super(message);
|
|
7133
|
-
this.name = "CliRuntimeError";
|
|
7134
|
-
this.code = code;
|
|
7135
|
-
this.details = options.details;
|
|
7136
|
-
this.requestId = options.requestId;
|
|
7137
|
-
}
|
|
7138
|
-
}
|
|
7139
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7140
|
-
var asNonEmptyString = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
7141
|
-
var asDetails = (value) => {
|
|
7142
|
-
if (!isRecord(value))
|
|
7143
|
-
return;
|
|
7144
|
-
const details = {};
|
|
7145
|
-
for (const [key, nestedValue] of Object.entries(value)) {
|
|
7146
|
-
if (isJsonValue(nestedValue))
|
|
7147
|
-
details[key] = nestedValue;
|
|
7148
|
-
}
|
|
7149
|
-
return Object.keys(details).length > 0 ? details : undefined;
|
|
7150
|
-
};
|
|
7151
|
-
var isJsonValue = (value) => {
|
|
7152
|
-
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
7153
|
-
return true;
|
|
7154
|
-
}
|
|
7155
|
-
if (Array.isArray(value))
|
|
7156
|
-
return value.every(isJsonValue);
|
|
7157
|
-
return isRecord(value) && Object.values(value).every(isJsonValue);
|
|
7158
|
-
};
|
|
7159
|
-
var mapErrorCode = (value) => {
|
|
7160
|
-
if (typeof value !== "string")
|
|
7161
|
-
return;
|
|
7162
|
-
if (cliErrorCodes.has(value))
|
|
7163
|
-
return value;
|
|
7164
|
-
if (value in trpcCodeMap)
|
|
7165
|
-
return trpcCodeMap[value];
|
|
7166
|
-
return;
|
|
7167
|
-
};
|
|
7168
|
-
var networkMessagePattern = /\b(fetch failed|network error|unable to connect|econnrefused|econnreset|enotfound|etimedout|socket hang up)\b/i;
|
|
7169
|
-
var isNetworkError = (error, depth = 0) => {
|
|
7170
|
-
if (depth > 3 || !isRecord(error))
|
|
7171
|
-
return false;
|
|
7172
|
-
const message = asNonEmptyString(error.message);
|
|
7173
|
-
if (message !== undefined && networkMessagePattern.test(message))
|
|
7174
|
-
return true;
|
|
7175
|
-
const code = asNonEmptyString(error.code);
|
|
7176
|
-
if (code !== undefined && networkMessagePattern.test(code))
|
|
7177
|
-
return true;
|
|
7178
|
-
return isNetworkError(error.cause, depth + 1);
|
|
7179
|
-
};
|
|
7180
|
-
var normalizeCliError = (error) => {
|
|
7181
|
-
if (error instanceof CliRuntimeError) {
|
|
7182
|
-
return {
|
|
7183
|
-
code: error.code,
|
|
7184
|
-
message: error.message,
|
|
7185
|
-
...error.details === undefined ? {} : { details: error.details },
|
|
7186
|
-
...error.requestId === undefined ? {} : { requestId: error.requestId }
|
|
7187
|
-
};
|
|
7188
|
-
}
|
|
7189
|
-
if (isNetworkError(error)) {
|
|
7190
|
-
return {
|
|
7191
|
-
code: "NETWORK",
|
|
7192
|
-
message: error instanceof Error ? error.message : "The Budget Builder API could not be reached."
|
|
7193
|
-
};
|
|
7194
|
-
}
|
|
7195
|
-
if (isRecord(error)) {
|
|
7196
|
-
const data = isRecord(error.data) ? error.data : undefined;
|
|
7197
|
-
const transportCode = mapErrorCode(data?.code ?? error.code);
|
|
7198
|
-
if (error instanceof Error && data === undefined && transportCode === undefined) {
|
|
7199
|
-
return { code: "INTERNAL", message: error.message };
|
|
7200
|
-
}
|
|
7201
|
-
const code = transportCode ?? "API";
|
|
7202
|
-
const message = asNonEmptyString(error.message) ?? "The Budget Builder API request failed.";
|
|
7203
|
-
const requestId = asNonEmptyString(data?.requestId) ?? asNonEmptyString(data?.requestID) ?? asNonEmptyString(error.requestId);
|
|
7204
|
-
const details = asDetails(data?.details ?? error.details);
|
|
7205
|
-
return {
|
|
7206
|
-
code,
|
|
7207
|
-
message,
|
|
7208
|
-
...details === undefined ? {} : { details },
|
|
7209
|
-
...requestId === undefined ? {} : { requestId }
|
|
7210
|
-
};
|
|
7211
|
-
}
|
|
7212
|
-
if (error instanceof Error) {
|
|
7213
|
-
return { code: "INTERNAL", message: error.message };
|
|
7214
|
-
}
|
|
7215
|
-
return { code: "INTERNAL", message: "An unexpected CLI error occurred." };
|
|
7216
|
-
};
|
|
7217
|
-
var exitCodeForCliError = (error) => {
|
|
7218
|
-
if (error.code === "INTERRUPTED")
|
|
7219
|
-
return 130;
|
|
7220
|
-
return invocationErrorCodes.has(error.code) ? 2 : 1;
|
|
7221
|
-
};
|
|
7222
|
-
|
|
7223
7318
|
// src/parse-mutation-payload.ts
|
|
7319
|
+
var validationError = (message) => new CliRuntimeError("VALIDATION", message);
|
|
7224
7320
|
function requireObject(raw, label) {
|
|
7225
7321
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7226
|
-
throw
|
|
7322
|
+
throw validationError(`${label} must be a JSON object.`);
|
|
7227
7323
|
}
|
|
7228
7324
|
return raw;
|
|
7229
7325
|
}
|
|
@@ -7236,7 +7332,7 @@ function toDate(value, field) {
|
|
|
7236
7332
|
if (!Number.isNaN(d.getTime()))
|
|
7237
7333
|
return d;
|
|
7238
7334
|
}
|
|
7239
|
-
throw
|
|
7335
|
+
throw validationError(`${field} must be an ISO date/datetime string.`);
|
|
7240
7336
|
}
|
|
7241
7337
|
function optionalString(v) {
|
|
7242
7338
|
if (v === undefined || v === null)
|
|
@@ -7250,14 +7346,14 @@ var parseQuotationReferences = (input, commandName) => {
|
|
|
7250
7346
|
let quotationAllocations;
|
|
7251
7347
|
if (rawAllocations !== undefined && rawAllocations !== null) {
|
|
7252
7348
|
if (!Array.isArray(rawAllocations) || rawAllocations.length === 0) {
|
|
7253
|
-
throw
|
|
7349
|
+
throw validationError(`${commandName} payload.quotationAllocations must be a non-empty array.`);
|
|
7254
7350
|
}
|
|
7255
7351
|
quotationAllocations = rawAllocations.map((raw, index) => {
|
|
7256
7352
|
const label = `${commandName} payload.quotationAllocations[${index}]`;
|
|
7257
7353
|
const allocation = requireObject(raw, label);
|
|
7258
7354
|
const amount = Number(allocation.amount);
|
|
7259
7355
|
if (!Number.isFinite(amount) || amount < 0 || !Number.isInteger(amount * 100)) {
|
|
7260
|
-
throw
|
|
7356
|
+
throw validationError(`${label}.amount must be a non-negative cent value.`);
|
|
7261
7357
|
}
|
|
7262
7358
|
return {
|
|
7263
7359
|
budgetItemId: requiredString(allocation.budgetItemId, `${label}.budgetItemId`),
|
|
@@ -7267,22 +7363,22 @@ var parseQuotationReferences = (input, commandName) => {
|
|
|
7267
7363
|
});
|
|
7268
7364
|
const keys = quotationAllocations.map((allocation) => `${allocation.budgetItemId}\x00${allocation.quotationId}`);
|
|
7269
7365
|
if (new Set(keys).size !== keys.length) {
|
|
7270
|
-
throw
|
|
7366
|
+
throw validationError(`${commandName} payload.quotationAllocations must not repeat a budget-item/quotation pair.`);
|
|
7271
7367
|
}
|
|
7272
7368
|
}
|
|
7273
7369
|
const rawQuotationIds = input.quotationIds;
|
|
7274
7370
|
let quotationIds;
|
|
7275
7371
|
if (rawQuotationIds !== undefined && rawQuotationIds !== null) {
|
|
7276
7372
|
if (!Array.isArray(rawQuotationIds)) {
|
|
7277
|
-
throw
|
|
7373
|
+
throw validationError(`${commandName} payload.quotationIds must be a string array.`);
|
|
7278
7374
|
}
|
|
7279
7375
|
quotationIds = rawQuotationIds.map((id, index) => requiredString(id, `${commandName} payload.quotationIds[${index}]`));
|
|
7280
7376
|
if (new Set(quotationIds).size !== quotationIds.length) {
|
|
7281
|
-
throw
|
|
7377
|
+
throw validationError(`${commandName} payload.quotationIds must not contain duplicates.`);
|
|
7282
7378
|
}
|
|
7283
7379
|
}
|
|
7284
7380
|
if (quotationId && quotationIds && (quotationIds.length !== 1 || quotationIds[0] !== quotationId)) {
|
|
7285
|
-
throw
|
|
7381
|
+
throw validationError(`${commandName} payload.quotationId conflicts with payload.quotationIds.`);
|
|
7286
7382
|
}
|
|
7287
7383
|
if (quotationAllocations && quotationIds) {
|
|
7288
7384
|
const allocationIds = [
|
|
@@ -7290,7 +7386,7 @@ var parseQuotationReferences = (input, commandName) => {
|
|
|
7290
7386
|
].sort();
|
|
7291
7387
|
const selectedIds = [...quotationIds].sort();
|
|
7292
7388
|
if (allocationIds.join("\x00") !== selectedIds.join("\x00")) {
|
|
7293
|
-
throw
|
|
7389
|
+
throw validationError(`${commandName} payload.quotationIds conflicts with payload.quotationAllocations.`);
|
|
7294
7390
|
}
|
|
7295
7391
|
}
|
|
7296
7392
|
return {
|
|
@@ -7303,14 +7399,14 @@ var parseBillLineAmounts = (input, commandName) => {
|
|
|
7303
7399
|
if (input.lineAmounts === undefined || input.lineAmounts === null)
|
|
7304
7400
|
return;
|
|
7305
7401
|
if (!Array.isArray(input.lineAmounts) || input.lineAmounts.length === 0) {
|
|
7306
|
-
throw
|
|
7402
|
+
throw validationError(`${commandName} payload.lineAmounts must be a non-empty array.`);
|
|
7307
7403
|
}
|
|
7308
7404
|
const lineAmounts = input.lineAmounts.map((raw, index) => {
|
|
7309
7405
|
const label = `${commandName} payload.lineAmounts[${index}]`;
|
|
7310
7406
|
const line = requireObject(raw, label);
|
|
7311
7407
|
const amount = Number(line.amount);
|
|
7312
7408
|
if (!Number.isFinite(amount) || amount < 0 || !Number.isInteger(amount * 100)) {
|
|
7313
|
-
throw
|
|
7409
|
+
throw validationError(`${label}.amount must be a non-negative cent value.`);
|
|
7314
7410
|
}
|
|
7315
7411
|
return {
|
|
7316
7412
|
budgetItemId: requiredString(line.budgetItemId, `${label}.budgetItemId`),
|
|
@@ -7318,7 +7414,7 @@ var parseBillLineAmounts = (input, commandName) => {
|
|
|
7318
7414
|
};
|
|
7319
7415
|
});
|
|
7320
7416
|
if (new Set(lineAmounts.map((line) => line.budgetItemId)).size !== lineAmounts.length) {
|
|
7321
|
-
throw
|
|
7417
|
+
throw validationError(`${commandName} payload.lineAmounts must contain each budget item once.`);
|
|
7322
7418
|
}
|
|
7323
7419
|
return lineAmounts;
|
|
7324
7420
|
};
|
|
@@ -7333,14 +7429,14 @@ var validateExplicitBillAllocations = ({
|
|
|
7333
7429
|
if (!lineAmounts && !quotationAllocations)
|
|
7334
7430
|
return;
|
|
7335
7431
|
if (!lineAmounts) {
|
|
7336
|
-
throw
|
|
7432
|
+
throw validationError(`${commandName} payload.lineAmounts is required with quotationAllocations.`);
|
|
7337
7433
|
}
|
|
7338
7434
|
const selected = new Set(budgetItemIds);
|
|
7339
7435
|
if (lineAmounts.length !== selected.size || lineAmounts.some((line) => !selected.has(line.budgetItemId))) {
|
|
7340
|
-
throw
|
|
7436
|
+
throw validationError(`${commandName} payload.lineAmounts must contain each selected budget item exactly once.`);
|
|
7341
7437
|
}
|
|
7342
7438
|
if (lineAmounts.reduce((sum, line) => sum + cents(line.amount), 0) !== cents(amount)) {
|
|
7343
|
-
throw
|
|
7439
|
+
throw validationError(`${commandName} payload.lineAmounts must sum to payload.amount.`);
|
|
7344
7440
|
}
|
|
7345
7441
|
if (!quotationAllocations)
|
|
7346
7442
|
return;
|
|
@@ -7348,27 +7444,27 @@ var validateExplicitBillAllocations = ({
|
|
|
7348
7444
|
const allocatedById = new Map;
|
|
7349
7445
|
for (const allocation of quotationAllocations) {
|
|
7350
7446
|
if (!selected.has(allocation.budgetItemId)) {
|
|
7351
|
-
throw
|
|
7447
|
+
throw validationError(`${commandName} payload.quotationAllocations references an unselected budget item.`);
|
|
7352
7448
|
}
|
|
7353
7449
|
allocatedById.set(allocation.budgetItemId, (allocatedById.get(allocation.budgetItemId) ?? 0) + cents(allocation.amount));
|
|
7354
7450
|
if (cents(allocation.amount) === 0 && lineAmountById.get(allocation.budgetItemId) !== 0) {
|
|
7355
|
-
throw
|
|
7451
|
+
throw validationError(`${commandName} zero quotation allocations are only valid for zero-value bill lines.`);
|
|
7356
7452
|
}
|
|
7357
7453
|
}
|
|
7358
7454
|
for (const [budgetItemId, allocated] of allocatedById) {
|
|
7359
7455
|
if (allocated > (lineAmountById.get(budgetItemId) ?? 0)) {
|
|
7360
|
-
throw
|
|
7456
|
+
throw validationError(`${commandName} quotation allocations exceed the line amount for ${budgetItemId}.`);
|
|
7361
7457
|
}
|
|
7362
7458
|
}
|
|
7363
7459
|
};
|
|
7364
7460
|
function requiredString(value, fieldLabel) {
|
|
7365
7461
|
if (value === undefined || value === null) {
|
|
7366
|
-
throw
|
|
7462
|
+
throw validationError(`${fieldLabel} is required.`);
|
|
7367
7463
|
}
|
|
7368
7464
|
const s = typeof value === "string" ? value : String(value);
|
|
7369
7465
|
const t = s.trim();
|
|
7370
7466
|
if (t === "") {
|
|
7371
|
-
throw
|
|
7467
|
+
throw validationError(`${fieldLabel} is required.`);
|
|
7372
7468
|
}
|
|
7373
7469
|
return t;
|
|
7374
7470
|
}
|
|
@@ -7376,13 +7472,13 @@ var EMAIL_ADDRESS_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
7376
7472
|
var requiredEmail = (value, fieldLabel) => {
|
|
7377
7473
|
const email = requiredString(value, fieldLabel);
|
|
7378
7474
|
if (!EMAIL_ADDRESS_PATTERN.test(email)) {
|
|
7379
|
-
throw
|
|
7475
|
+
throw validationError(`${fieldLabel} must be a valid email address.`);
|
|
7380
7476
|
}
|
|
7381
7477
|
return email;
|
|
7382
7478
|
};
|
|
7383
7479
|
var parseContactPersonEmailPayload = (input, commandName) => {
|
|
7384
7480
|
if (!Array.isArray(input.cc)) {
|
|
7385
|
-
throw
|
|
7481
|
+
throw validationError(`${commandName} payload.cc must be an email array.`);
|
|
7386
7482
|
}
|
|
7387
7483
|
return {
|
|
7388
7484
|
to: requiredEmail(input.to, `${commandName} payload.to`),
|
|
@@ -7412,7 +7508,7 @@ var parseSendCustomerInvoiceToContactPersonPayload = (raw) => {
|
|
|
7412
7508
|
function parseCreateBudgetPayload(raw) {
|
|
7413
7509
|
const o = requireObject(raw, "create-budget payload");
|
|
7414
7510
|
if (o.unavailableItemReviewAcknowledged !== undefined && typeof o.unavailableItemReviewAcknowledged !== "boolean") {
|
|
7415
|
-
throw
|
|
7511
|
+
throw validationError("create-budget payload.unavailableItemReviewAcknowledged must be a boolean when set.");
|
|
7416
7512
|
}
|
|
7417
7513
|
return {
|
|
7418
7514
|
name: requiredString(o.name, "create-budget payload.name"),
|
|
@@ -7446,7 +7542,7 @@ function parseBudgetDiscountPayload(raw) {
|
|
|
7446
7542
|
const o = requireObject(raw, "update-budget-discount payload");
|
|
7447
7543
|
const type = o.type;
|
|
7448
7544
|
if (type !== "percentage" && type !== "amount") {
|
|
7449
|
-
throw
|
|
7545
|
+
throw validationError('update-budget-discount payload.type must be "percentage" or "amount".');
|
|
7450
7546
|
}
|
|
7451
7547
|
return {
|
|
7452
7548
|
budgetId: requiredString(o.budgetId, "update-budget-discount payload.budgetId"),
|
|
@@ -7470,28 +7566,28 @@ function parseCreateBillPayload(raw, draftOnlyFlag, quotationExempt = false, req
|
|
|
7470
7566
|
const o = requireObject(raw, "create-bill payload");
|
|
7471
7567
|
const isClaimable = o.isClaimable;
|
|
7472
7568
|
if (typeof isClaimable !== "boolean") {
|
|
7473
|
-
throw
|
|
7569
|
+
throw validationError("create-bill payload.isClaimable must be boolean.");
|
|
7474
7570
|
}
|
|
7475
7571
|
const budgetItemIds = o.budgetItemIds;
|
|
7476
7572
|
if (!Array.isArray(budgetItemIds) || budgetItemIds.length === 0) {
|
|
7477
|
-
throw
|
|
7573
|
+
throw validationError("create-bill payload.budgetItemIds must be a non-empty string array.");
|
|
7478
7574
|
}
|
|
7479
7575
|
const attachmentsRaw = o.attachments;
|
|
7480
7576
|
const attachments = Array.isArray(attachmentsRaw) ? attachmentsRaw.map((x, i) => billAttachment(x, i)) : [];
|
|
7481
7577
|
const draftOnlyRaw = o.draftOnly;
|
|
7482
7578
|
if (draftOnlyRaw !== undefined && typeof draftOnlyRaw !== "boolean") {
|
|
7483
|
-
throw
|
|
7579
|
+
throw validationError("create-bill payload.draftOnly must be boolean.");
|
|
7484
7580
|
}
|
|
7485
7581
|
if (draftOnlyFlag !== undefined && draftOnlyRaw !== undefined && draftOnlyFlag !== draftOnlyRaw) {
|
|
7486
|
-
throw
|
|
7582
|
+
throw validationError("--draft-only conflicts with payload.draftOnly.");
|
|
7487
7583
|
}
|
|
7488
7584
|
const draftOnly = draftOnlyFlag ?? draftOnlyRaw;
|
|
7489
7585
|
const alreadyPaidRaw = o.alreadyPaid;
|
|
7490
7586
|
if (draftOnly && alreadyPaidRaw === true) {
|
|
7491
|
-
throw
|
|
7587
|
+
throw validationError("Draft-only creation cannot be combined with alreadyPaid: true.");
|
|
7492
7588
|
}
|
|
7493
7589
|
if (alreadyPaidRaw !== undefined && alreadyPaidRaw !== null && typeof alreadyPaidRaw !== "boolean") {
|
|
7494
|
-
throw
|
|
7590
|
+
throw validationError("create-bill payload.alreadyPaid must be boolean.");
|
|
7495
7591
|
}
|
|
7496
7592
|
const paymentProofAttachmentsRaw = o.paymentProofAttachments;
|
|
7497
7593
|
const paymentProofAttachments = Array.isArray(paymentProofAttachmentsRaw) ? paymentProofAttachmentsRaw.map((x, i) => billAttachment(x, i, "paymentProofAttachments")) : [];
|
|
@@ -7500,8 +7596,8 @@ function parseCreateBillPayload(raw, draftOnlyFlag, quotationExempt = false, req
|
|
|
7500
7596
|
if (vr !== undefined && vr !== null) {
|
|
7501
7597
|
const v = requireObject(vr, "verificationResults");
|
|
7502
7598
|
const verificationResultStatus = v.status;
|
|
7503
|
-
if (verificationResultStatus !==
|
|
7504
|
-
throw
|
|
7599
|
+
if (verificationResultStatus !== BillVerificationStatus.PENDING && verificationResultStatus !== BillVerificationStatus.PASS && verificationResultStatus !== BillVerificationStatus.FAIL && verificationResultStatus !== BillVerificationStatus.MANUAL_REVIEW) {
|
|
7600
|
+
throw validationError("create-bill payload.verificationResults.status must be PENDING | PASS | FAIL | MANUAL_REVIEW.");
|
|
7505
7601
|
}
|
|
7506
7602
|
verificationResults = {
|
|
7507
7603
|
status: verificationResultStatus,
|
|
@@ -7513,14 +7609,14 @@ function parseCreateBillPayload(raw, draftOnlyFlag, quotationExempt = false, req
|
|
|
7513
7609
|
}
|
|
7514
7610
|
const verificationStatus = o.verificationStatus;
|
|
7515
7611
|
let vs;
|
|
7516
|
-
if (verificationStatus ===
|
|
7612
|
+
if (verificationStatus === BillVerificationStatus.PENDING || verificationStatus === BillVerificationStatus.PASS || verificationStatus === BillVerificationStatus.FAIL || verificationStatus === BillVerificationStatus.MANUAL_REVIEW) {
|
|
7517
7613
|
vs = verificationStatus;
|
|
7518
7614
|
} else if (verificationStatus !== undefined && verificationStatus !== null) {
|
|
7519
|
-
throw
|
|
7615
|
+
throw validationError("create-bill payload.verificationStatus must be PENDING | PASS | FAIL | MANUAL_REVIEW.");
|
|
7520
7616
|
}
|
|
7521
7617
|
const amount = Number(o.amount);
|
|
7522
7618
|
if (!Number.isFinite(amount) || amount < 0) {
|
|
7523
|
-
throw
|
|
7619
|
+
throw validationError("create-bill payload.amount must be a number >= 0.");
|
|
7524
7620
|
}
|
|
7525
7621
|
const quotationReferences = parseQuotationReferences(o, "create-bill");
|
|
7526
7622
|
const parsedBudgetItemIds = budgetItemIds.map((id, i) => requiredString(id, `create-bill payload.budgetItemIds[${i}]`));
|
|
@@ -7533,16 +7629,16 @@ function parseCreateBillPayload(raw, draftOnlyFlag, quotationExempt = false, req
|
|
|
7533
7629
|
quotationAllocations: quotationReferences.quotationAllocations
|
|
7534
7630
|
});
|
|
7535
7631
|
if (quotationExempt && isClaimable) {
|
|
7536
|
-
throw
|
|
7632
|
+
throw validationError("--quotation-exempt is only valid for supplier bills.");
|
|
7537
7633
|
}
|
|
7538
7634
|
if (quotationExempt && (quotationReferences.quotationId || (quotationReferences.quotationIds?.length ?? 0) > 0 || (quotationReferences.quotationAllocations?.length ?? 0) > 0)) {
|
|
7539
|
-
throw
|
|
7635
|
+
throw validationError("--quotation-exempt cannot be combined with payload quotation IDs.");
|
|
7540
7636
|
}
|
|
7541
7637
|
if (isClaimable && (quotationReferences.quotationId || (quotationReferences.quotationIds?.length ?? 0) > 0 || (quotationReferences.quotationAllocations?.length ?? 0) > 0)) {
|
|
7542
|
-
throw
|
|
7638
|
+
throw validationError("Employee claims cannot include quotation source inputs.");
|
|
7543
7639
|
}
|
|
7544
7640
|
if (requireSupplierQuotation && !quotationExempt && !isClaimable && !quotationReferences.quotationId && (quotationReferences.quotationIds?.length ?? 0) === 0 && (quotationReferences.quotationAllocations?.length ?? 0) === 0) {
|
|
7545
|
-
throw
|
|
7641
|
+
throw validationError("create-bill supplier bills require payload.quotationAllocations, payload.quotationIds, or legacy payload.quotationId.");
|
|
7546
7642
|
}
|
|
7547
7643
|
return {
|
|
7548
7644
|
budgetId: requiredString(o.budgetId, "create-bill payload.budgetId"),
|
|
@@ -7571,7 +7667,7 @@ function parseUpdateBillPayload(raw) {
|
|
|
7571
7667
|
const o = requireObject(raw, "update-bill payload");
|
|
7572
7668
|
const { draftOnly, ...input } = parseCreateBillPayload(raw, undefined, false, false);
|
|
7573
7669
|
if (draftOnly !== undefined) {
|
|
7574
|
-
throw
|
|
7670
|
+
throw validationError("draftOnly is only supported when creating a bill.");
|
|
7575
7671
|
}
|
|
7576
7672
|
return {
|
|
7577
7673
|
...input,
|
|
@@ -7585,21 +7681,21 @@ function parseValidateBillSelectionPayload(raw, quotationFlags = {}) {
|
|
|
7585
7681
|
const payloadIds = payloadReferences.quotationIds ?? (payloadReferences.quotationId ? [payloadReferences.quotationId] : undefined);
|
|
7586
7682
|
const flagIds = flagReferences.quotationIds ?? (flagReferences.quotationId ? [flagReferences.quotationId] : undefined);
|
|
7587
7683
|
if (payloadIds && flagIds && payloadIds.join("\x00") !== flagIds.join("\x00")) {
|
|
7588
|
-
throw
|
|
7684
|
+
throw validationError("validate-bill-selection quotation flags conflict with the payload quotation IDs.");
|
|
7589
7685
|
}
|
|
7590
7686
|
const isClaimable = input.isClaimable;
|
|
7591
7687
|
if (isClaimable !== undefined && typeof isClaimable !== "boolean") {
|
|
7592
|
-
throw
|
|
7688
|
+
throw validationError("validate-bill-selection payload.isClaimable must be boolean.");
|
|
7593
7689
|
}
|
|
7594
7690
|
if (isClaimable === true && (payloadReferences.quotationId || (payloadReferences.quotationIds?.length ?? 0) > 0 || (payloadReferences.quotationAllocations?.length ?? 0) > 0 || !!flagReferences.quotationId || (flagReferences.quotationIds?.length ?? 0) > 0)) {
|
|
7595
|
-
throw
|
|
7691
|
+
throw validationError("Employee claims cannot include quotation source inputs.");
|
|
7596
7692
|
}
|
|
7597
7693
|
const lineAmounts = parseBillLineAmounts(input, "validate-bill-selection");
|
|
7598
7694
|
const budgetItemIds = Array.isArray(input.budgetItemIds) ? input.budgetItemIds.map((id, index) => requiredString(id, `validate-bill-selection payload.budgetItemIds[${index}]`)) : [];
|
|
7599
7695
|
const amount = input.amount === undefined || input.amount === null ? undefined : Number(input.amount);
|
|
7600
7696
|
if (lineAmounts || payloadReferences.quotationAllocations) {
|
|
7601
7697
|
if (amount === undefined || !Number.isFinite(amount) || budgetItemIds.length === 0) {
|
|
7602
|
-
throw
|
|
7698
|
+
throw validationError("validate-bill-selection explicit allocations require payload.amount and payload.budgetItemIds.");
|
|
7603
7699
|
}
|
|
7604
7700
|
validateExplicitBillAllocations({
|
|
7605
7701
|
amount,
|
|
@@ -7622,10 +7718,10 @@ function parseLinkBillQuotationsPayload(billId, raw) {
|
|
|
7622
7718
|
const input = requireObject(raw, "link-bill-quotations payload");
|
|
7623
7719
|
const references = parseQuotationReferences(input, "link-bill-quotations");
|
|
7624
7720
|
if (references.quotationId) {
|
|
7625
|
-
throw
|
|
7721
|
+
throw validationError("link-bill-quotations payload uses quotationIds or quotationAllocations; legacy quotationId is not supported.");
|
|
7626
7722
|
}
|
|
7627
7723
|
if ((references.quotationIds?.length ?? 0) === 0 && (references.quotationAllocations?.length ?? 0) === 0) {
|
|
7628
|
-
throw
|
|
7724
|
+
throw validationError("link-bill-quotations payload requires quotationIds or quotationAllocations.");
|
|
7629
7725
|
}
|
|
7630
7726
|
return {
|
|
7631
7727
|
billId: requiredString(billId, "billId"),
|
|
@@ -7678,14 +7774,14 @@ function parseUpdateQuotationPayload(raw) {
|
|
|
7678
7774
|
var parseCreateCustomerInvoicePayload = (raw) => {
|
|
7679
7775
|
const input = requireObject(raw, "create-customer-invoice payload");
|
|
7680
7776
|
if (!Array.isArray(input.splits) || input.splits.length !== 1) {
|
|
7681
|
-
throw
|
|
7777
|
+
throw validationError("create-customer-invoice payload.splits must contain exactly one invoice.");
|
|
7682
7778
|
}
|
|
7683
7779
|
if (input.isCustomPercentage !== undefined && typeof input.isCustomPercentage !== "boolean") {
|
|
7684
|
-
throw
|
|
7780
|
+
throw validationError("create-customer-invoice payload.isCustomPercentage must be a boolean.");
|
|
7685
7781
|
}
|
|
7686
7782
|
const details = input.details === undefined ? undefined : requireObject(input.details, "invoice details");
|
|
7687
7783
|
if (details && typeof details.billingAddress !== "string")
|
|
7688
|
-
throw
|
|
7784
|
+
throw validationError("invoice details.billingAddress must be a string.");
|
|
7689
7785
|
return {
|
|
7690
7786
|
...input.requestId === undefined ? {} : { requestId: requiredString(input.requestId, "requestId") },
|
|
7691
7787
|
...input.invoiceDate === undefined ? {} : { invoiceDate: toDate(input.invoiceDate, "invoiceDate") },
|
|
@@ -7701,7 +7797,7 @@ var parseCreateCustomerInvoicePayload = (raw) => {
|
|
|
7701
7797
|
const split = requireObject(rawSplit, `create-customer-invoice payload.splits[${index}]`);
|
|
7702
7798
|
const percentage = Number(split.percentage);
|
|
7703
7799
|
if (!Number.isFinite(percentage) || percentage <= 0 || percentage > 100 || Math.round(percentage * 100) !== percentage * 100) {
|
|
7704
|
-
throw
|
|
7800
|
+
throw validationError(`create-customer-invoice payload.splits[${index}].percentage must be between 0.01 and 100 with at most two decimal places.`);
|
|
7705
7801
|
}
|
|
7706
7802
|
return {
|
|
7707
7803
|
dueDate: toDate(split.dueDate, `create-customer-invoice payload.splits[${index}].dueDate`),
|
|
@@ -7715,7 +7811,7 @@ function parseUpdateProjectPayload(raw) {
|
|
|
7715
7811
|
const o = requireObject(raw, "update-project payload");
|
|
7716
7812
|
const dr = o.dateRange;
|
|
7717
7813
|
if (dr === null || typeof dr !== "object" || Array.isArray(dr)) {
|
|
7718
|
-
throw
|
|
7814
|
+
throw validationError("update-project payload.dateRange must be an object.");
|
|
7719
7815
|
}
|
|
7720
7816
|
const range = dr;
|
|
7721
7817
|
const notification = o.requestQboAccountantNotification;
|
|
@@ -7723,14 +7819,14 @@ function parseUpdateProjectPayload(raw) {
|
|
|
7723
7819
|
if (typeof notification === "boolean") {
|
|
7724
7820
|
requestQboAccountantNotification = notification;
|
|
7725
7821
|
} else if (notification !== undefined && notification !== null) {
|
|
7726
|
-
throw
|
|
7822
|
+
throw validationError("update-project payload.requestQboAccountantNotification must be a boolean when set.");
|
|
7727
7823
|
}
|
|
7728
7824
|
const estimateInvalidation = o.confirmEstimateInvalidation;
|
|
7729
7825
|
let confirmEstimateInvalidation;
|
|
7730
7826
|
if (typeof estimateInvalidation === "boolean") {
|
|
7731
7827
|
confirmEstimateInvalidation = estimateInvalidation;
|
|
7732
7828
|
} else if (estimateInvalidation !== undefined && estimateInvalidation !== null) {
|
|
7733
|
-
throw
|
|
7829
|
+
throw validationError("update-project payload.confirmEstimateInvalidation must be a boolean when set.");
|
|
7734
7830
|
}
|
|
7735
7831
|
const slackChannelId = optionalString(o.slackChannelId);
|
|
7736
7832
|
const slackChannelUrl = optionalString(o.slackChannelUrl);
|
|
@@ -7773,21 +7869,30 @@ function parseSupplierUpdatePayload(raw) {
|
|
|
7773
7869
|
requireObject(raw, "update-supplier payload");
|
|
7774
7870
|
return raw;
|
|
7775
7871
|
}
|
|
7776
|
-
|
|
7777
|
-
|
|
7872
|
+
var normalizeItemPayload = (input, command) => {
|
|
7873
|
+
if (typeof input.name === "string") {
|
|
7874
|
+
const invalidCharacters = [
|
|
7875
|
+
...new Set([...input.name].filter((character) => !/^[A-Za-z0-9,.?@&!#'~* _;+-]$/.test(character)))
|
|
7876
|
+
];
|
|
7877
|
+
if (invalidCharacters.length > 0) {
|
|
7878
|
+
throw validationError(`${command} payload.name contains characters that are not allowed: ${invalidCharacters.join(" ")}. Allowed: letters, numbers, spaces and , . ? @ & ! # ' ~ * _ ; + -`);
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7881
|
+
const normalized = {
|
|
7882
|
+
...input,
|
|
7883
|
+
...typeof input.cost === "number" && Number.isFinite(input.cost) ? { cost: String(input.cost) } : {}
|
|
7884
|
+
};
|
|
7778
7885
|
return normalizePlainTextRichTextField({
|
|
7779
|
-
input,
|
|
7886
|
+
input: normalized,
|
|
7780
7887
|
textField: "description",
|
|
7781
7888
|
richTextField: "descriptionRichText"
|
|
7782
7889
|
});
|
|
7890
|
+
};
|
|
7891
|
+
function parseItemCreatePayload(raw) {
|
|
7892
|
+
return normalizeItemPayload(requireObject(raw, "create-item payload"), "create-item");
|
|
7783
7893
|
}
|
|
7784
7894
|
function parseItemUpdatePayload(raw) {
|
|
7785
|
-
|
|
7786
|
-
return normalizePlainTextRichTextField({
|
|
7787
|
-
input,
|
|
7788
|
-
textField: "description",
|
|
7789
|
-
richTextField: "descriptionRichText"
|
|
7790
|
-
});
|
|
7895
|
+
return normalizeItemPayload(requireObject(raw, "update-item payload"), "update-item");
|
|
7791
7896
|
}
|
|
7792
7897
|
function parseContactCreatePayload(raw) {
|
|
7793
7898
|
requireObject(raw, "create-contact-person payload");
|
|
@@ -8004,11 +8109,11 @@ var confirmCurrentCommand = async (input) => {
|
|
|
8004
8109
|
};
|
|
8005
8110
|
// src/commands/index.ts
|
|
8006
8111
|
import { contentType } from "mime-types";
|
|
8007
|
-
var BUDGET =
|
|
8008
|
-
var BILL =
|
|
8009
|
-
var SUPPLIER =
|
|
8010
|
-
var QUOTATION =
|
|
8011
|
-
var CUSTOMER_INVOICE =
|
|
8112
|
+
var BUDGET = ApprovalType.BUDGET;
|
|
8113
|
+
var BILL = ApprovalType.BILL;
|
|
8114
|
+
var SUPPLIER = ApprovalType.SUPPLIER;
|
|
8115
|
+
var QUOTATION = ApprovalType.QUOTATION;
|
|
8116
|
+
var CUSTOMER_INVOICE = ApprovalType.CUSTOMER_INVOICE;
|
|
8012
8117
|
var ASANA_WON_LOST_SECTION_GIDS = new Set([
|
|
8013
8118
|
"1211678338364908",
|
|
8014
8119
|
"1211678338364907"
|
|
@@ -8063,7 +8168,7 @@ var createApprovalEmailDeliveryFailure = (error) => ({
|
|
|
8063
8168
|
error
|
|
8064
8169
|
});
|
|
8065
8170
|
function isPendingRequest(n) {
|
|
8066
|
-
return n.notificationType === "request" && n.status ===
|
|
8171
|
+
return n.notificationType === "request" && n.status === ApprovalStatus.PENDING_APPROVAL;
|
|
8067
8172
|
}
|
|
8068
8173
|
var out = (data) => {
|
|
8069
8174
|
emitCommandResult(data);
|
|
@@ -8164,10 +8269,10 @@ var formatAsanaCandidateMetadata = (candidate) => {
|
|
|
8164
8269
|
};
|
|
8165
8270
|
var promptForAsanaLeadCandidate = async (candidates) => {
|
|
8166
8271
|
if (candidates.length === 0) {
|
|
8167
|
-
throw new Error("
|
|
8272
|
+
throw new Error("create-project requires an existing Asana deal task in the Deals project; none matched. For a new direct client, create the deal task using the website form first, then pass --asanaTaskId or --asanaSearch. The Slack channel comes from that task.");
|
|
8168
8273
|
}
|
|
8169
8274
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
8170
|
-
throw new Error("create-project requires --asanaTaskId in non-interactive runs.
|
|
8275
|
+
throw new Error("create-project requires an existing Asana deal task: pass --asanaTaskId in non-interactive runs. Create the deal task using the website form first for a new direct client. The Slack channel comes from that task; interactive runs can choose from matching tasks.");
|
|
8171
8276
|
}
|
|
8172
8277
|
console.error("Select the Asana lead task to link to this project:");
|
|
8173
8278
|
candidates.forEach((candidate, index) => {
|
|
@@ -8270,11 +8375,11 @@ var quotationAttachmentContentTypeForFileName = (fileName) => {
|
|
|
8270
8375
|
throw new Error(`Unsupported quotation attachment type: ${fileName}. Use PDF, JPEG, or PNG.`);
|
|
8271
8376
|
};
|
|
8272
8377
|
var LOCKED_BUDGET_STATUSES_REQUIRING_CONFIRMATION = new Set([
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8378
|
+
BudgetStatus.ESTIMATE_CREATED,
|
|
8379
|
+
BudgetStatus.ESTIMATE_SENT,
|
|
8380
|
+
BudgetStatus.ESTIMATE_ACCEPTED,
|
|
8381
|
+
BudgetStatus.ESTIMATE_REJECTED,
|
|
8382
|
+
BudgetStatus.ESTIMATE_CLOSED
|
|
8278
8383
|
]);
|
|
8279
8384
|
var lockedBudgetStatusLabels = {
|
|
8280
8385
|
ESTIMATE_CREATED: "Estimate Created",
|
|
@@ -8444,7 +8549,7 @@ async function updateBudgetStatus(budgetId, status, opts) {
|
|
|
8444
8549
|
});
|
|
8445
8550
|
out({
|
|
8446
8551
|
...result,
|
|
8447
|
-
invoiceSetup: result.newStatus ===
|
|
8552
|
+
invoiceSetup: result.newStatus === BudgetStatus.ESTIMATE_ACCEPTED && result.hasQuickbooksEstimate ? invoiceSplitSetupNextAction(budgetId) : null
|
|
8448
8553
|
});
|
|
8449
8554
|
}
|
|
8450
8555
|
var assertBudgetActionReadiness = async ({
|
|
@@ -8541,7 +8646,7 @@ async function approveBill(billId, overBudgetOverrideReason) {
|
|
|
8541
8646
|
}
|
|
8542
8647
|
const result = await api2.bill.updateApproval.mutate({
|
|
8543
8648
|
billApprovalId: approval.id,
|
|
8544
|
-
status:
|
|
8649
|
+
status: ApprovalStatus.APPROVED,
|
|
8545
8650
|
overBudgetOverrideReason
|
|
8546
8651
|
});
|
|
8547
8652
|
if (result.processing) {
|
|
@@ -8637,10 +8742,10 @@ async function createBillApproval(billId) {
|
|
|
8637
8742
|
});
|
|
8638
8743
|
}
|
|
8639
8744
|
var validateBillStatusUpdateOptions = (opts) => {
|
|
8640
|
-
if (opts.status ===
|
|
8745
|
+
if (opts.status === BillStatus.PAID && !opts.paymentReference?.trim()) {
|
|
8641
8746
|
throw new Error("update-bill-status requires --paymentReference when status is PAID.");
|
|
8642
8747
|
}
|
|
8643
|
-
if (opts.status !==
|
|
8748
|
+
if (opts.status !== BillStatus.PAID && opts.paymentProofPath !== undefined) {
|
|
8644
8749
|
throw new Error("--paymentProof can only be used when status is PAID.");
|
|
8645
8750
|
}
|
|
8646
8751
|
if (opts.paymentProofPath !== undefined && !opts.paymentProofPath.trim()) {
|
|
@@ -8793,11 +8898,11 @@ var cleanupStagedBillAttachments = async (projectId, keys) => {
|
|
|
8793
8898
|
});
|
|
8794
8899
|
out(result);
|
|
8795
8900
|
};
|
|
8796
|
-
async function uploadBillAttachmentFromPath(billId, filePath, type =
|
|
8901
|
+
async function uploadBillAttachmentFromPath(billId, filePath, type = AttachmentType.BILL) {
|
|
8797
8902
|
const confirmed = await uploadSingleBillAttachmentFromPath(billId, filePath, type);
|
|
8798
8903
|
out(confirmed);
|
|
8799
8904
|
}
|
|
8800
|
-
var uploadSingleBillAttachmentFromPath = async (billId, filePath, type =
|
|
8905
|
+
var uploadSingleBillAttachmentFromPath = async (billId, filePath, type = AttachmentType.BILL) => {
|
|
8801
8906
|
const buf = await readFile2(filePath);
|
|
8802
8907
|
const fileName = basename(filePath);
|
|
8803
8908
|
const size = buf.byteLength;
|
|
@@ -8824,7 +8929,7 @@ var uploadSingleBillAttachmentFromPath = async (billId, filePath, type = "BILL")
|
|
|
8824
8929
|
type
|
|
8825
8930
|
});
|
|
8826
8931
|
};
|
|
8827
|
-
async function uploadBillAttachmentsFromPaths(billId, filePaths, type =
|
|
8932
|
+
async function uploadBillAttachmentsFromPaths(billId, filePaths, type = AttachmentType.BILL) {
|
|
8828
8933
|
if (filePaths.length === 0) {
|
|
8829
8934
|
throw new Error("At least one bill attachment file path is required");
|
|
8830
8935
|
}
|
|
@@ -8851,11 +8956,11 @@ async function uploadBillDocumentsFromPaths({
|
|
|
8851
8956
|
}
|
|
8852
8957
|
const invoiceResults = [];
|
|
8853
8958
|
for (const filePath of invoicePaths) {
|
|
8854
|
-
invoiceResults.push(await uploadSingleBillAttachmentFromPath(billId, filePath,
|
|
8959
|
+
invoiceResults.push(await uploadSingleBillAttachmentFromPath(billId, filePath, AttachmentType.BILL));
|
|
8855
8960
|
}
|
|
8856
8961
|
const paymentProofResults = [];
|
|
8857
8962
|
for (const filePath of paymentProofPaths) {
|
|
8858
|
-
paymentProofResults.push(await uploadSingleBillAttachmentFromPath(billId, filePath,
|
|
8963
|
+
paymentProofResults.push(await uploadSingleBillAttachmentFromPath(billId, filePath, AttachmentType.BILL_PAYMENT_PROOF));
|
|
8859
8964
|
}
|
|
8860
8965
|
out({
|
|
8861
8966
|
success: true,
|
|
@@ -8963,7 +9068,7 @@ async function uploadBudgetWinProofFromPath(budgetId, filePath, opts) {
|
|
|
8963
9068
|
});
|
|
8964
9069
|
const result = await api2.budget.updateBudgetStatus.mutate({
|
|
8965
9070
|
id: budgetId,
|
|
8966
|
-
status:
|
|
9071
|
+
status: BudgetStatus.ESTIMATE_ACCEPTED,
|
|
8967
9072
|
...opts?.projectManagerId !== undefined && {
|
|
8968
9073
|
projectManagerId: opts.projectManagerId
|
|
8969
9074
|
},
|
|
@@ -9094,7 +9199,15 @@ async function getBudgetDetails(budgetId) {
|
|
|
9094
9199
|
}
|
|
9095
9200
|
async function createBudgetFromPayload(raw) {
|
|
9096
9201
|
const input = parseCreateBudgetPayload(raw);
|
|
9097
|
-
|
|
9202
|
+
let result;
|
|
9203
|
+
try {
|
|
9204
|
+
result = await api2.budget.createBudget.mutate(input);
|
|
9205
|
+
} catch (error) {
|
|
9206
|
+
const uncertain = budgetCreateUncertainError(error, input);
|
|
9207
|
+
if (uncertain)
|
|
9208
|
+
throw uncertain;
|
|
9209
|
+
throw error;
|
|
9210
|
+
}
|
|
9098
9211
|
out(result);
|
|
9099
9212
|
}
|
|
9100
9213
|
async function updateBudgetFromPayload(raw) {
|
|
@@ -9496,31 +9609,44 @@ async function deleteBillById(id) {
|
|
|
9496
9609
|
}
|
|
9497
9610
|
async function createSupplierFromPayload(raw) {
|
|
9498
9611
|
const input = parseSupplierCreatePayload(raw);
|
|
9499
|
-
|
|
9612
|
+
await assertSensitiveWorkflowConfirmed({
|
|
9613
|
+
action: "Create supplier and request approval if required",
|
|
9614
|
+
entity: `supplier ${input.name}`,
|
|
9615
|
+
details: "creates the supplier first; pending-approval suppliers also request approval and send emails"
|
|
9616
|
+
});
|
|
9617
|
+
let created;
|
|
9618
|
+
try {
|
|
9619
|
+
created = await api2.supplier.createSupplier.mutate(input);
|
|
9620
|
+
} catch (error) {
|
|
9621
|
+
const normalized = normalizeCliError(error);
|
|
9622
|
+
if (normalized.code !== "NETWORK" && normalized.code !== "API") {
|
|
9623
|
+
throw error;
|
|
9624
|
+
}
|
|
9625
|
+
throw new CliRuntimeError(normalized.code, `Supplier creation outcome is uncertain for ${JSON.stringify(input.name)}. Run list-suppliers and inspect matching supplier details before retrying; names may not be unique, so do not retry create-supplier until you can verify that no supplier was created. Original error: ${normalized.message}`);
|
|
9626
|
+
}
|
|
9500
9627
|
const supplier = created.result;
|
|
9501
|
-
if (supplier.status !==
|
|
9628
|
+
if (supplier.status !== ApprovalStatus.PENDING_APPROVAL) {
|
|
9502
9629
|
out(created);
|
|
9503
9630
|
return;
|
|
9504
9631
|
}
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
}
|
|
9632
|
+
let approval;
|
|
9633
|
+
try {
|
|
9634
|
+
approval = await api2.supplier.createSupplierApproval.mutate({
|
|
9635
|
+
supplierId: supplier.id
|
|
9636
|
+
});
|
|
9637
|
+
} catch (error) {
|
|
9638
|
+
throw new CliRuntimeError("API", `Supplier ${supplier.id} was created, but the approval request did not complete or its outcome is uncertain. Run get-supplier-details ${supplier.id} and inspect its approvals before retrying; do not run create-supplier again. Original error: ${normalizeCliError(error).message}`);
|
|
9639
|
+
}
|
|
9513
9640
|
let emailResult;
|
|
9514
9641
|
const approvalIds = (approval.results ?? []).map((item) => ({ id: item.id }));
|
|
9515
9642
|
if (approvalIds.length === 0) {
|
|
9516
|
-
|
|
9517
|
-
}
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
}
|
|
9643
|
+
throw new CliRuntimeError("API", `Supplier ${supplier.id} was created, but the approval request returned no approval IDs. Run get-supplier-details ${supplier.id} and inspect its approvals before retrying the approval request; do not run create-supplier again.`);
|
|
9644
|
+
}
|
|
9645
|
+
try {
|
|
9646
|
+
const delivery = approval.emailDelivery;
|
|
9647
|
+
emailResult = createApprovalEmailDeliveryReport(delivery);
|
|
9648
|
+
} catch (error) {
|
|
9649
|
+
emailResult = createApprovalEmailDeliveryFailure(error instanceof Error ? error.message : String(error));
|
|
9524
9650
|
}
|
|
9525
9651
|
out({
|
|
9526
9652
|
...created,
|
|
@@ -9944,7 +10070,7 @@ var reconcileProject = async (projectId) => {
|
|
|
9944
10070
|
out(result);
|
|
9945
10071
|
};
|
|
9946
10072
|
var completeProject = async (projectId) => {
|
|
9947
|
-
await updateProjectStatus(projectId,
|
|
10073
|
+
await updateProjectStatus(projectId, ProjectStatus.COMPLETED);
|
|
9948
10074
|
};
|
|
9949
10075
|
var importQuickBooksProjectId = async (projectId, projectUrl) => {
|
|
9950
10076
|
const result = await api2.project.importQboProjectId.mutate({
|
|
@@ -10265,24 +10391,167 @@ function getFlag(flags, key, parse) {
|
|
|
10265
10391
|
// src/parse-json-flag.ts
|
|
10266
10392
|
function parseJsonFlag(raw, label) {
|
|
10267
10393
|
if (raw === undefined || raw === "") {
|
|
10268
|
-
throw new
|
|
10394
|
+
throw new CliRuntimeError("VALIDATION", `${label} is required (JSON string).`);
|
|
10269
10395
|
}
|
|
10270
10396
|
try {
|
|
10271
10397
|
return JSON.parse(raw);
|
|
10272
10398
|
} catch {
|
|
10273
|
-
throw new
|
|
10399
|
+
throw new CliRuntimeError("VALIDATION", `${label} must be valid JSON.`);
|
|
10274
10400
|
}
|
|
10275
10401
|
}
|
|
10402
|
+
function parseJsonObjectFlag(raw, label) {
|
|
10403
|
+
const value = parseJsonFlag(raw, label);
|
|
10404
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
10405
|
+
throw new CliRuntimeError("VALIDATION", `${label} must be a JSON object.`);
|
|
10406
|
+
}
|
|
10407
|
+
return value;
|
|
10408
|
+
}
|
|
10276
10409
|
function parseOptionalNumber(raw, label) {
|
|
10277
10410
|
if (raw === undefined || raw === "")
|
|
10278
10411
|
return;
|
|
10279
10412
|
const n = typeof raw === "number" ? raw : Number.parseFloat(String(raw).trim());
|
|
10280
10413
|
if (!Number.isFinite(n)) {
|
|
10281
|
-
throw new
|
|
10414
|
+
throw new CliRuntimeError("VALIDATION", `${label} must be a number.`);
|
|
10282
10415
|
}
|
|
10283
10416
|
return n;
|
|
10284
10417
|
}
|
|
10285
10418
|
|
|
10419
|
+
// src/registry/payload-help.ts
|
|
10420
|
+
var payloadHelp = {
|
|
10421
|
+
"create-budget": {
|
|
10422
|
+
required: "name: string; budget: decimal string; paymentTerm: string; categoryId: string; projectId: string",
|
|
10423
|
+
optional: "sourceBudgetId: string; unavailableItemReviewAcknowledged: boolean (only after review); pipedriveDealId: string",
|
|
10424
|
+
example: '{"name":"Event budget","budget":"1500","paymentTerm":"Net 30","categoryId":"<category-id>","projectId":"<project-id>"}'
|
|
10425
|
+
},
|
|
10426
|
+
"update-budget": {
|
|
10427
|
+
required: "id: string; name: string; budget: decimal string; paymentTerm: string; categoryId: string; projectId: string",
|
|
10428
|
+
optional: "sourceBudgetId: string; pipedriveDealId: string",
|
|
10429
|
+
example: '{"id":"<budget-id>","name":"Event budget","budget":"1500","paymentTerm":"Net 30","categoryId":"<category-id>","projectId":"<project-id>"}'
|
|
10430
|
+
},
|
|
10431
|
+
"create-item": {
|
|
10432
|
+
required: "name: string (QuickBooks-safe characters); categoryId: string; supplierId: string; cost: decimal string or number",
|
|
10433
|
+
optional: "description: string; gstInclusive: boolean; gstOutOfScope: boolean; goLabsProduct: boolean; attachments: array",
|
|
10434
|
+
example: '{"name":"Event Crew per hour","categoryId":"<category-id>","supplierId":"<supplier-id>","cost":19}'
|
|
10435
|
+
},
|
|
10436
|
+
"update-item": {
|
|
10437
|
+
required: "id: string; name: string; categoryId: string; supplierId: string; cost: decimal string or number",
|
|
10438
|
+
optional: "description: string; gstInclusive: boolean; gstOutOfScope: boolean; goLabsProduct: boolean; attachments: array",
|
|
10439
|
+
example: '{"id":"<item-id>","name":"Event Crew per hour","categoryId":"<category-id>","supplierId":"<supplier-id>","cost":"19"}'
|
|
10440
|
+
},
|
|
10441
|
+
"create-contact-person": {
|
|
10442
|
+
required: "companyId: string; title: MR|MRS|MS|DR; firstName: string; lastName: string|null; email: string; mobile: string",
|
|
10443
|
+
optional: "address: {streetAddress1?, city?, state?, postalCode?, country?}",
|
|
10444
|
+
example: '{"companyId":"<company-id>","title":"MS","firstName":"Jane","lastName":"Tan","email":"jane@example.com","mobile":"+65 9123 4567"}'
|
|
10445
|
+
},
|
|
10446
|
+
"update-contact-person": {
|
|
10447
|
+
required: "id: string; companyId: string; title: MR|MRS|MS|DR; firstName: string; lastName: string|null; email: string; mobile: string",
|
|
10448
|
+
optional: "address: object",
|
|
10449
|
+
example: '{"id":"<contact-id>","companyId":"<company-id>","title":"MS","firstName":"Jane","lastName":"Tan","email":"jane@example.com","mobile":"+65 9123 4567"}'
|
|
10450
|
+
},
|
|
10451
|
+
"update-company": {
|
|
10452
|
+
required: "id: string; name: string; website: URL string",
|
|
10453
|
+
optional: "confirmDistinct: boolean",
|
|
10454
|
+
example: '{"id":"<company-id>","name":"Example Co","website":"https://example.com"}'
|
|
10455
|
+
},
|
|
10456
|
+
"update-project": {
|
|
10457
|
+
required: "id: string; name: string; companyId: string; contactPersonId: string; asanaTaskId: string; insideSalesId: string; businessDevelopmentId: string; venue: string; dateRange: {from: ISO date}",
|
|
10458
|
+
optional: "dateRange.to: ISO date; projectManagerId: string; pax: string; description: string; slackChannelId/slackChannelUrl/slackChannelName: strings (supply all three); requestQboAccountantNotification: boolean; confirmEstimateInvalidation: boolean",
|
|
10459
|
+
example: '{"id":"<project-id>","name":"Launch","companyId":"<company-id>","contactPersonId":"<contact-id>","asanaTaskId":"<asana-task-id>","insideSalesId":"<user-id>","businessDevelopmentId":"<user-id>","venue":"Hall","dateRange":{"from":"2026-10-01T09:00:00Z"}}'
|
|
10460
|
+
},
|
|
10461
|
+
"create-supplier": {
|
|
10462
|
+
required: "name: string; primaryContactPerson: string; email: string; phone: string; paymentMethodId: string; paymentTerm: string; roleId: string",
|
|
10463
|
+
optional: "gstRegistered: boolean; tags: string[]; website: URL string; address: object; attachments: array; bank and PayNow fields",
|
|
10464
|
+
example: '{"name":"Example Supplier","primaryContactPerson":"Jane Tan","email":"jane@example.com","phone":"91234567","paymentMethodId":"<method-id>","paymentTerm":"Net 30","roleId":"<role-id>"}'
|
|
10465
|
+
},
|
|
10466
|
+
"update-supplier": {
|
|
10467
|
+
required: "id: string; name: string; primaryContactPerson: string; email: string; phone: string; paymentMethodId: string; paymentTerm: string; roleId: string",
|
|
10468
|
+
optional: "gstRegistered: boolean; tags: string[]; website: URL string; address: object; attachments: array; bank and PayNow fields",
|
|
10469
|
+
example: '{"id":"<supplier-id>","name":"Example Supplier","primaryContactPerson":"Jane Tan","email":"jane@example.com","phone":"91234567","paymentMethodId":"<method-id>","paymentTerm":"Net 30","roleId":"<role-id>"}'
|
|
10470
|
+
},
|
|
10471
|
+
"update-budget-commission": {
|
|
10472
|
+
required: "budgetId: string; name: string; percentage: decimal string; totalSellingAfterDiscount: number",
|
|
10473
|
+
optional: "none",
|
|
10474
|
+
example: '{"budgetId":"<budget-id>","name":"Sales commission","percentage":"10","totalSellingAfterDiscount":1500}'
|
|
10475
|
+
},
|
|
10476
|
+
"update-budget-discount": {
|
|
10477
|
+
required: "budgetId: string; name: string; type: percentage|amount; value: decimal string; totalSellingBeforeDiscount: number",
|
|
10478
|
+
optional: "none",
|
|
10479
|
+
example: '{"budgetId":"<budget-id>","name":"Discount","type":"amount","value":"100","totalSellingBeforeDiscount":1500}'
|
|
10480
|
+
},
|
|
10481
|
+
"send-estimate-to-contact-person": {
|
|
10482
|
+
required: "estimateId: string; budgetId: string; to: email; cc: email[]; replyTo: email; subject: string; content: HTML string; signature: HTML string",
|
|
10483
|
+
optional: "estimateDocNumber: string",
|
|
10484
|
+
example: '{"estimateId":"<estimate-id>","budgetId":"<budget-id>","to":"client@example.com","cc":[],"replyTo":"team@example.com","subject":"Estimate","content":"<p>Attached estimate</p>","signature":"<p>Team</p>"}'
|
|
10485
|
+
},
|
|
10486
|
+
"create-bill": {
|
|
10487
|
+
required: "budgetId: string; projectId: string; supplierId: string; amount: number (ex-GST); budgetItemIds: string[]; isClaimable: boolean; supplier bills: quotationIds: string[] or quotationAllocations: {budgetItemId, quotationId, amount}[] (unless --quotation-exempt)",
|
|
10488
|
+
optional: "lineAmounts: {budgetItemId, amount}[] (required with quotationAllocations); attachments: {id,name,key,size}[]; invoiceNumber: string; invoiceDate: ISO date; draftOnly: boolean; alreadyPaid: boolean; extractedAmount: number",
|
|
10489
|
+
example: '{"budgetId":"<budget-id>","projectId":"<project-id>","supplierId":"<supplier-id>","amount":1800,"budgetItemIds":["<budget-item-id>"],"isClaimable":false,"quotationIds":["<quotation-id>"],"attachments":[]}'
|
|
10490
|
+
},
|
|
10491
|
+
"update-bill": {
|
|
10492
|
+
required: "id: string; budgetId: string; projectId: string; supplierId: string; amount: number (ex-GST); budgetItemIds: string[]; isClaimable: boolean",
|
|
10493
|
+
optional: "quotationIds: string[]; quotationAllocations: {budgetItemId, quotationId, amount}[]; lineAmounts: {budgetItemId, amount}[]; attachments: {id,name,key,size}[]; invoiceNumber: string; invoiceDate: ISO date",
|
|
10494
|
+
example: '{"id":"<bill-id>","budgetId":"<budget-id>","projectId":"<project-id>","supplierId":"<supplier-id>","amount":1800,"budgetItemIds":["<budget-item-id>"],"isClaimable":false,"quotationIds":["<quotation-id>"],"attachments":[]}'
|
|
10495
|
+
},
|
|
10496
|
+
"validate-bill-selection": {
|
|
10497
|
+
required: "projectId: string; supplierId: string; budgetItemIds: string[]",
|
|
10498
|
+
optional: "quotationIds: string[] for supplier bills; isClaimable: boolean; amount: number; lineAmounts: {budgetItemId, amount}[]; quotationAllocations: {budgetItemId, quotationId, amount}[] (requires amount and lineAmounts)",
|
|
10499
|
+
example: '{"projectId":"<project-id>","supplierId":"<supplier-id>","budgetItemIds":["<budget-item-id>"],"quotationIds":["<quotation-id>"]}'
|
|
10500
|
+
},
|
|
10501
|
+
"link-bill-quotations": {
|
|
10502
|
+
required: "positional <billId>; quotationIds: string[] or quotationAllocations: {budgetItemId, quotationId, amount}[]",
|
|
10503
|
+
optional: "none (legacy quotationId is not supported)",
|
|
10504
|
+
example: '{"quotationIds":["<quotation-id>"]}'
|
|
10505
|
+
},
|
|
10506
|
+
"update-bill-payment-evidence": {
|
|
10507
|
+
required: "id: string; paymentReference: string; paymentProofAttachments: {id,name (PDF),key,size}[] (at least one)",
|
|
10508
|
+
optional: "none",
|
|
10509
|
+
example: '{"id":"<bill-id>","paymentReference":"TRANSFER-123","paymentProofAttachments":[{"id":"<attachment-id>","name":"receipt.pdf","key":"<staged-key>","size":1000}]}'
|
|
10510
|
+
},
|
|
10511
|
+
"create-quotation": {
|
|
10512
|
+
required: "projectId: string; budgetId: string; supplierId: string; budgetItemIds: string[]; amountWithoutGst: number; gstAmount: number; attachments: array (uploaded attachment)",
|
|
10513
|
+
optional: "lineAmounts: {budgetItemId, amount}[] (required for multiple budget items)",
|
|
10514
|
+
example: '{"projectId":"<project-id>","budgetId":"<budget-id>","supplierId":"<supplier-id>","budgetItemIds":["<budget-item-id>"],"amountWithoutGst":1200,"gstAmount":0,"attachments":[{"id":"<attachment-id>","name":"quote.pdf","key":"<staged-key>","size":1000}]}'
|
|
10515
|
+
},
|
|
10516
|
+
"update-quotation": {
|
|
10517
|
+
required: "id: string; projectId: string; budgetId: string; supplierId: string; budgetItemIds: string[]; amountWithoutGst: number; gstAmount: number; attachments: array",
|
|
10518
|
+
optional: "lineAmounts: {budgetItemId, amount}[] (required for multiple budget items)",
|
|
10519
|
+
example: '{"id":"<quotation-id>","projectId":"<project-id>","budgetId":"<budget-id>","supplierId":"<supplier-id>","budgetItemIds":["<budget-item-id>"],"amountWithoutGst":1200,"gstAmount":0,"attachments":[{"id":"<attachment-id>","name":"quote.pdf","key":"<staged-key>","size":1000}]}'
|
|
10520
|
+
},
|
|
10521
|
+
"create-customer-invoice": {
|
|
10522
|
+
required: "budgetId: string; splits: [{label: string, percentage: number, dueDate: ISO date}] (exactly one)",
|
|
10523
|
+
optional: "requestId: UUID string (recommended); invoiceDate: ISO date; details: {billingAddress: string, customerMemo: string}; isCustomPercentage: boolean",
|
|
10524
|
+
example: '{"budgetId":"<budget-id>","splits":[{"label":"Deposit","percentage":50,"dueDate":"2026-10-15"}],"details":{"billingAddress":"1 Example St","customerMemo":"Thanks"}}'
|
|
10525
|
+
},
|
|
10526
|
+
"send-customer-invoice-to-contact-person": {
|
|
10527
|
+
required: "invoiceId: string; to: email; cc: email[]; replyTo: email; subject: string; content: HTML string; signature: HTML string",
|
|
10528
|
+
optional: "none",
|
|
10529
|
+
example: '{"invoiceId":"<invoice-id>","to":"client@example.com","cc":[],"replyTo":"team@example.com","subject":"Invoice","content":"<p>Attached invoice</p>","signature":"<p>Team</p>"}'
|
|
10530
|
+
},
|
|
10531
|
+
"save-inbound-mapping": {
|
|
10532
|
+
required: "positional <sourceId>; payload: [{canonicalField: enum, typeformFieldId: string, required?: boolean, typeformFieldRef?: string|null}] (1-50 entries)",
|
|
10533
|
+
optional: "required defaults false; typeformFieldRef may be omitted",
|
|
10534
|
+
example: '[{"canonicalField":"CONTACT_FIRST_NAME","typeformFieldId":"<typeform-field-id>"}]'
|
|
10535
|
+
},
|
|
10536
|
+
"save-inbound-automation-config": {
|
|
10537
|
+
required: "positional <sourceId>; asanaCustomFields: object; asanaDefaultAssigneeId: string|null; asanaDefaultAssigneeName: string|null; asanaFollowerIds: string[]; asanaProjectId: string; asanaSectionId: string; asanaWonSectionId: string; asanaLostSectionId: string; googleCalendarId: string; googleCalendarTimeZone: string; goshBriefBaseUrl: URL; slackDefaultAssigneeId: string|null; slackInboundChannelId: string; slackInviteUserIds: string[]; slackPitchChannelPrefix: string",
|
|
10538
|
+
optional: "asanaDiscussionSectionId, asanaFirstContactSectionId, asanaFirstProposalSentSectionId, asanaProposalWipSectionId: string|null; asanaCustomFields.*: mapped IDs",
|
|
10539
|
+
example: '{"asanaCustomFields":{},"asanaDefaultAssigneeId":null,"asanaDefaultAssigneeName":null,"asanaFollowerIds":[],"asanaProjectId":"<asana-project-id>","asanaSectionId":"<asana-section-id>","asanaWonSectionId":"<asana-won-id>","asanaLostSectionId":"<asana-lost-id>","googleCalendarId":"team@example.com","googleCalendarTimeZone":"Asia/Singapore","goshBriefBaseUrl":"https://example.com/brief","slackDefaultAssigneeId":null,"slackInboundChannelId":"<slack-channel-id>","slackInviteUserIds":[],"slackPitchChannelPrefix":"pitch"}'
|
|
10540
|
+
}
|
|
10541
|
+
};
|
|
10542
|
+
var formatPayloadHelp = (command) => {
|
|
10543
|
+
const help = payloadHelp[command];
|
|
10544
|
+
if (!help)
|
|
10545
|
+
return [];
|
|
10546
|
+
return [
|
|
10547
|
+
"Payload fields (--payload JSON)",
|
|
10548
|
+
` Required: ${help.required}`,
|
|
10549
|
+
` Optional: ${help.optional}`,
|
|
10550
|
+
` Example: bb ${command}${command === "link-bill-quotations" ? " <billId>" : command.startsWith("save-inbound-") ? " <sourceId>" : ""} --payload '${help.example}'`,
|
|
10551
|
+
""
|
|
10552
|
+
];
|
|
10553
|
+
};
|
|
10554
|
+
|
|
10286
10555
|
// src/registry/index.ts
|
|
10287
10556
|
var globalCommandOptions = [
|
|
10288
10557
|
{ name: "--help", description: "Show help for this command." },
|
|
@@ -10483,6 +10752,8 @@ var summaryFor = (legacyTarget) => {
|
|
|
10483
10752
|
return "Reuse a company in BB and QuickBooks, creating only its missing counterpart. Supply --companyId <bbId> and/or --quickbooksId <qboId>; both explicitly connect records with different names. --website <url> fills a missing QBO import website only. --confirmDistinct true confirms no similar record represents this entity.";
|
|
10484
10753
|
if (legacyTarget === "create-company")
|
|
10485
10754
|
return "Create or reuse a company in BB and QuickBooks. --name <name> --website <url>. Similar names require --confirmDistinct true; inactive companies are never reactivated.";
|
|
10755
|
+
if (legacyTarget === "create-project")
|
|
10756
|
+
return "Create a project linked to an existing Asana Deals lead task. Supply --asanaTaskId in non-interactive use; interactive use searches for and selects a lead task (optionally --asanaSearch). The Slack channel is resolved from the selected Asana task unless all three Slack channel flags are supplied. For a new direct client, create the lead task using the website form first.";
|
|
10486
10757
|
if (legacyTarget === "preview-qbo-customer-invoice") {
|
|
10487
10758
|
return "Preview an available QuickBooks invoice selected for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.";
|
|
10488
10759
|
}
|
|
@@ -10540,6 +10811,20 @@ var legacyCommand = (legacyTarget, path, aliases = [], commandOptions = []) => (
|
|
|
10540
10811
|
effects: effectsFor(legacyTarget),
|
|
10541
10812
|
legacyTarget
|
|
10542
10813
|
});
|
|
10814
|
+
var budgetPreflightOptions = [
|
|
10815
|
+
{
|
|
10816
|
+
name: "--companyPayload <json>",
|
|
10817
|
+
description: "Company update JSON object with id, name, and website fields."
|
|
10818
|
+
},
|
|
10819
|
+
{
|
|
10820
|
+
name: "--contactPayload <json>",
|
|
10821
|
+
description: "Contact update JSON object with companyId, title, firstName, lastName, email, and mobile fields."
|
|
10822
|
+
},
|
|
10823
|
+
{
|
|
10824
|
+
name: "--eventPayload <json>",
|
|
10825
|
+
description: "Event timing JSON object with date, startTime, and endTime fields."
|
|
10826
|
+
}
|
|
10827
|
+
];
|
|
10543
10828
|
var authCommand = (action, summary) => ({
|
|
10544
10829
|
path: ["auth", action],
|
|
10545
10830
|
legacyAliases: [],
|
|
@@ -10564,8 +10849,8 @@ var registry2 = [
|
|
|
10564
10849
|
legacyCommand("rename-budget-version", ["budget", "version", "rename"]),
|
|
10565
10850
|
legacyCommand("restore-budget-version", ["budget", "version", "restore"]),
|
|
10566
10851
|
legacyCommand("update-budget-status", ["budget", "status", "update"]),
|
|
10567
|
-
legacyCommand("create-budget-approval", ["budget", "approval", "create"]),
|
|
10568
|
-
legacyCommand("create-estimate", ["budget", "estimate", "create"]),
|
|
10852
|
+
legacyCommand("create-budget-approval", ["budget", "approval", "create"], [], budgetPreflightOptions),
|
|
10853
|
+
legacyCommand("create-estimate", ["budget", "estimate", "create"], [], budgetPreflightOptions),
|
|
10569
10854
|
legacyCommand("send-estimate-to-contact-person", ["budget", "estimate", "send"], ["send-estimate-email"]),
|
|
10570
10855
|
legacyCommand("create-budget", ["budget", "create"]),
|
|
10571
10856
|
legacyCommand("update-budget", ["budget", "update"]),
|
|
@@ -10999,6 +11284,7 @@ var createHumanHelp = (command) => {
|
|
|
10999
11284
|
...command.options.map((option) => ` ${option.name}
|
|
11000
11285
|
${option.description}`),
|
|
11001
11286
|
"",
|
|
11287
|
+
...formatPayloadHelp(command.legacyTarget ?? ""),
|
|
11002
11288
|
"Use `bb help --legacy` for additional command arguments."
|
|
11003
11289
|
].join(`
|
|
11004
11290
|
`);
|
|
@@ -11114,7 +11400,7 @@ function parseCommercialRejectionProjectStatus(flags) {
|
|
|
11114
11400
|
throw new Error("--projectStatusOnCommercialRejection requires PITCH or LOST.");
|
|
11115
11401
|
}
|
|
11116
11402
|
const status = String(raw).toUpperCase();
|
|
11117
|
-
if (status ===
|
|
11403
|
+
if (status === ProjectStatus.PITCH || status === ProjectStatus.LOST) {
|
|
11118
11404
|
return status;
|
|
11119
11405
|
}
|
|
11120
11406
|
throw new Error("--projectStatusOnCommercialRejection must be PITCH or LOST.");
|
|
@@ -11885,9 +12171,9 @@ Waiting for approval\u2026
|
|
|
11885
12171
|
const contactPayloadRaw = getFlag(flags, "contactPayload");
|
|
11886
12172
|
const eventPayloadRaw = getFlag(flags, "eventPayload");
|
|
11887
12173
|
await createBudgetApproval(id, {
|
|
11888
|
-
company: companyPayloadRaw !== undefined ?
|
|
11889
|
-
contactPerson: contactPayloadRaw !== undefined ?
|
|
11890
|
-
eventDetails: eventPayloadRaw !== undefined ?
|
|
12174
|
+
company: companyPayloadRaw !== undefined ? parseJsonObjectFlag(String(companyPayloadRaw), "--companyPayload") : undefined,
|
|
12175
|
+
contactPerson: contactPayloadRaw !== undefined ? parseJsonObjectFlag(String(contactPayloadRaw), "--contactPayload") : undefined,
|
|
12176
|
+
eventDetails: eventPayloadRaw !== undefined ? parseJsonObjectFlag(String(eventPayloadRaw), "--eventPayload") : undefined
|
|
11891
12177
|
});
|
|
11892
12178
|
break;
|
|
11893
12179
|
}
|
|
@@ -11899,9 +12185,9 @@ Waiting for approval\u2026
|
|
|
11899
12185
|
const contactPayloadRaw = getFlag(flags, "contactPayload");
|
|
11900
12186
|
const eventPayloadRaw = getFlag(flags, "eventPayload");
|
|
11901
12187
|
await createEstimate(id, {
|
|
11902
|
-
company: companyPayloadRaw !== undefined ?
|
|
11903
|
-
contactPerson: contactPayloadRaw !== undefined ?
|
|
11904
|
-
eventDetails: eventPayloadRaw !== undefined ?
|
|
12188
|
+
company: companyPayloadRaw !== undefined ? parseJsonObjectFlag(String(companyPayloadRaw), "--companyPayload") : undefined,
|
|
12189
|
+
contactPerson: contactPayloadRaw !== undefined ? parseJsonObjectFlag(String(contactPayloadRaw), "--contactPayload") : undefined,
|
|
12190
|
+
eventDetails: eventPayloadRaw !== undefined ? parseJsonObjectFlag(String(eventPayloadRaw), "--eventPayload") : undefined
|
|
11905
12191
|
});
|
|
11906
12192
|
break;
|
|
11907
12193
|
}
|
|
@@ -12336,7 +12622,7 @@ Waiting for approval\u2026
|
|
|
12336
12622
|
if (!billId || filePaths.length === 0) {
|
|
12337
12623
|
throw new Error(`${cmd} requires <billId> <filePath...> or --file/--files`);
|
|
12338
12624
|
}
|
|
12339
|
-
const attachmentType = cmd === "attach-bill-payment-receipt" ?
|
|
12625
|
+
const attachmentType = cmd === "attach-bill-payment-receipt" ? AttachmentType.BILL_PAYMENT_PROOF : AttachmentType.BILL;
|
|
12340
12626
|
const [firstFilePath] = filePaths;
|
|
12341
12627
|
if (filePaths.length === 1 && firstFilePath !== undefined) {
|
|
12342
12628
|
await uploadBillAttachmentFromPath(billId, firstFilePath, attachmentType);
|
|
@@ -12481,7 +12767,7 @@ Waiting for approval\u2026
|
|
|
12481
12767
|
throw new Error("download-quotation-pdf requires <quotationId> [original|staff|final]");
|
|
12482
12768
|
}
|
|
12483
12769
|
const roleRaw = (positional[1] ?? "final").toLowerCase();
|
|
12484
|
-
const fileRole = roleRaw === "original" ?
|
|
12770
|
+
const fileRole = roleRaw === "original" ? SupplierQuotationFileRole.ORIGINAL : roleRaw === "staff" ? SupplierQuotationFileRole.STAFF_SIGNED : roleRaw === "final" ? SupplierQuotationFileRole.FINAL_SIGNED : null;
|
|
12485
12771
|
if (!fileRole) {
|
|
12486
12772
|
throw new Error("download-quotation-pdf role must be original, staff, or final");
|
|
12487
12773
|
}
|
|
@@ -13331,7 +13617,7 @@ Waiting for approval\u2026
|
|
|
13331
13617
|
const sourceId = positional[0];
|
|
13332
13618
|
const mode = getFlag(flags, "mode");
|
|
13333
13619
|
const enabled = parseOptionalBoolFlag(flags, "enabled");
|
|
13334
|
-
if (!sourceId || !mode || enabled === undefined || !
|
|
13620
|
+
if (!sourceId || !mode || enabled === undefined || !Object.values(InboundProcessingMode).includes(String(mode))) {
|
|
13335
13621
|
throw new Error("update-inbound-processing requires <sourceId> --mode COLLECT_ONLY|SHADOW|ACTIVE --enabled true|false");
|
|
13336
13622
|
}
|
|
13337
13623
|
await updateInboundProcessing({
|