diff --git a/components/netsuite/actions/create-invoice/create-invoice.mjs b/components/netsuite/actions/create-invoice/create-invoice.mjs new file mode 100644 index 0000000000000..c078d7a27e646 --- /dev/null +++ b/components/netsuite/actions/create-invoice/create-invoice.mjs @@ -0,0 +1,152 @@ +import app from "../../netsuite.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "netsuite-create-invoice", + name: "Create Invoice", + description: "Creates a new invoice. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-invoice)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + idempotentHint: false, + }, + props: { + app, + customerId: { + propDefinition: [ + app, + "customerId", + ], + }, + tranDate: { + type: "string", + label: "Transaction Date", + description: "The posting date of this invoice (format: `YYYY-MM-DD`). Defaults to today's date if not specified.", + optional: true, + }, + subsidiaryId: { + propDefinition: [ + app, + "subsidiaryId", + ], + optional: true, + }, + items: { + type: "string[]", + label: "Items", + description: `Array of item objects to add to the invoice. Each item should be a JSON object with the following properties: + +**Required:** +- \`item\` - Item ID or reference object (e.g., \`{ "id": "123" }\`) +- \`quantity\` - Quantity to invoice (number) + +**Important Optional:** +- \`rate\` - Price per unit (number) +- \`amount\` - Total line amount (number, usually \`quantity * rate\`) +- \`description\` - Custom description for the line item (string) +- \`taxCode\` - Tax code ID or reference object + +**Example:** +\`\`\`json +[ + { + "item": { "id": "456" }, + "quantity": 2, + "rate": 99.99, + "amount": 199.98, + "description": "Professional services", + "taxCode": { "id": "5" } + } +] +\`\`\``, + optional: true, + }, + memo: { + type: "string", + label: "Memo", + description: "A memo to describe this invoice.", + optional: true, + }, + otherRefNum: { + type: "string", + label: "PO/Reference Number", + description: "Customer's purchase order number or other reference number.", + optional: true, + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional fields to include in the invoice as a JSON object. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html) for available fields.", + optional: true, + }, + replace: { + propDefinition: [ + app, + "replace", + ], + }, + propertyNameValidation: { + propDefinition: [ + app, + "propertyNameValidation", + ], + }, + propertyValueValidation: { + propDefinition: [ + app, + "propertyValueValidation", + ], + }, + }, + async run({ $ }) { + const { + app, + customerId, + tranDate, + subsidiaryId, + items, + memo, + otherRefNum, + additionalFields, + replace, + propertyNameValidation, + propertyValueValidation, + } = this; + + const response = await app.createInvoice({ + $, + headers: { + "X-NetSuite-PropertyNameValidation": propertyNameValidation, + "X-NetSuite-PropertyValueValidation": propertyValueValidation, + }, + params: { + replace, + }, + data: { + entity: { + id: customerId, + }, + tranDate, + ...(subsidiaryId && { + subsidiary: { + id: subsidiaryId, + }, + }), + ...(items && { + item: { + items: utils.parseJson(items), + }, + }), + memo, + otherRefNum, + ...(additionalFields && utils.parseJson(additionalFields)), + }, + }); + + $.export("$summary", "Successfully created invoice"); + return response; + }, +}; diff --git a/components/netsuite/actions/create-sales-order/create-sales-order.mjs b/components/netsuite/actions/create-sales-order/create-sales-order.mjs new file mode 100644 index 0000000000000..3de7014b1460f --- /dev/null +++ b/components/netsuite/actions/create-sales-order/create-sales-order.mjs @@ -0,0 +1,128 @@ +import app from "../../netsuite.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "netsuite-create-sales-order", + name: "Create Sales Order", + description: "Creates a new sales order. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-salesOrder)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + idempotentHint: false, + }, + props: { + app, + customerId: { + propDefinition: [ + app, + "customerId", + ], + }, + tranDate: { + type: "string", + label: "Transaction Date", + description: "The posting date of this sales order (format: `YYYY-MM-DD`). Defaults to today's date if not specified.", + optional: true, + }, + subsidiaryId: { + propDefinition: [ + app, + "subsidiaryId", + ], + optional: true, + }, + items: { + propDefinition: [ + app, + "items", + ], + }, + memo: { + type: "string", + label: "Memo", + description: "A memo to describe this sales order. It will appear on reports such as the 2-line Sales Orders register.", + optional: true, + }, + otherRefNum: { + type: "string", + label: "PO/Check Number", + description: "If your customer is paying by check, enter the number here. If your customer is issuing a purchase order, enter the PO number here.", + optional: true, + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional fields to include in the sales order as a JSON object. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html) for available fields.", + optional: true, + }, + replace: { + propDefinition: [ + app, + "replace", + ], + }, + propertyNameValidation: { + propDefinition: [ + app, + "propertyNameValidation", + ], + }, + propertyValueValidation: { + propDefinition: [ + app, + "propertyValueValidation", + ], + }, + }, + async run({ $ }) { + const { + app, + customerId, + tranDate, + subsidiaryId, + items, + memo, + otherRefNum, + additionalFields, + replace, + propertyNameValidation, + propertyValueValidation, + } = this; + + const response = await app.createSalesOrder({ + $, + headers: { + "X-NetSuite-PropertyNameValidation": propertyNameValidation, + "X-NetSuite-PropertyValueValidation": propertyValueValidation, + }, + params: { + replace, + }, + data: { + entity: { + id: customerId, + }, + tranDate, + ...(subsidiaryId && { + subsidiary: { + id: subsidiaryId, + }, + }), + ...(items && { + item: { + items: utils.parseJson(items), + }, + }), + memo, + otherRefNum, + ...(additionalFields && utils.parseJson(additionalFields)), + }, + }); + + $.export("$summary", "Successfully created sales order"); + return response; + }, +}; diff --git a/components/netsuite/actions/delete-invoice/delete-invoice.mjs b/components/netsuite/actions/delete-invoice/delete-invoice.mjs new file mode 100644 index 0000000000000..c6824c6f6686d --- /dev/null +++ b/components/netsuite/actions/delete-invoice/delete-invoice.mjs @@ -0,0 +1,38 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-delete-invoice", + name: "Delete Invoice", + description: "Deletes an existing invoice. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-invoice)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + invoiceId: { + propDefinition: [ + app, + "invoiceId", + ], + }, + }, + async run({ $ }) { + const { + app, + invoiceId, + } = this; + + const response = await app.deleteInvoice({ + $, + invoiceId, + }); + + $.export("$summary", `Successfully deleted invoice with ID ${invoiceId}`); + return response; + }, +}; diff --git a/components/netsuite/actions/delete-sales-order/delete-sales-order.mjs b/components/netsuite/actions/delete-sales-order/delete-sales-order.mjs new file mode 100644 index 0000000000000..bdba6b9d1daca --- /dev/null +++ b/components/netsuite/actions/delete-sales-order/delete-sales-order.mjs @@ -0,0 +1,38 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-delete-sales-order", + name: "Delete Sales Order", + description: "Deletes an existing sales order in NetSuite. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + salesOrderId: { + propDefinition: [ + app, + "salesOrderId", + ], + }, + }, + async run({ $ }) { + const { + app, + salesOrderId, + } = this; + + const response = await app.deleteSalesOrder({ + $, + salesOrderId, + }); + + $.export("$summary", `Successfully deleted sales order with ID ${salesOrderId}`); + return response; + }, +}; diff --git a/components/netsuite/actions/get-invoice/get-invoice.mjs b/components/netsuite/actions/get-invoice/get-invoice.mjs new file mode 100644 index 0000000000000..f442bbabd406b --- /dev/null +++ b/components/netsuite/actions/get-invoice/get-invoice.mjs @@ -0,0 +1,64 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-get-invoice", + name: "Get Invoice", + description: "Retrieves an invoice by ID. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-invoice)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + invoiceId: { + propDefinition: [ + app, + "invoiceId", + ], + }, + expandSubResources: { + propDefinition: [ + app, + "expandSubResources", + ], + }, + simpleEnumFormat: { + propDefinition: [ + app, + "simpleEnumFormat", + ], + }, + fields: { + propDefinition: [ + app, + "fields", + ], + }, + }, + async run({ $ }) { + const { + app, + invoiceId, + expandSubResources, + simpleEnumFormat, + fields, + } = this; + + const response = await app.getInvoice({ + $, + invoiceId, + params: { + expandSubResources, + simpleEnumFormat, + fields, + }, + }); + + $.export("$summary", `Successfully retrieved invoice with ID ${invoiceId}`); + return response; + }, +}; diff --git a/components/netsuite/actions/get-sales-order/get-sales-order.mjs b/components/netsuite/actions/get-sales-order/get-sales-order.mjs new file mode 100644 index 0000000000000..7d0b638782ad9 --- /dev/null +++ b/components/netsuite/actions/get-sales-order/get-sales-order.mjs @@ -0,0 +1,64 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-get-sales-order", + name: "Get Sales Order", + description: "Retrieves a sales order by ID from NetSuite. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + salesOrderId: { + propDefinition: [ + app, + "salesOrderId", + ], + }, + expandSubResources: { + propDefinition: [ + app, + "expandSubResources", + ], + }, + simpleEnumFormat: { + propDefinition: [ + app, + "simpleEnumFormat", + ], + }, + fields: { + propDefinition: [ + app, + "fields", + ], + }, + }, + async run({ $ }) { + const { + app, + salesOrderId, + expandSubResources, + simpleEnumFormat, + fields, + } = this; + + const response = await app.getSalesOrder({ + $, + salesOrderId, + params: { + expandSubResources, + simpleEnumFormat, + fields, + }, + }); + + $.export("$summary", `Successfully retrieved sales order with ID ${salesOrderId}`); + return response; + }, +}; diff --git a/components/netsuite/actions/list-invoices/list-invoices.mjs b/components/netsuite/actions/list-invoices/list-invoices.mjs new file mode 100644 index 0000000000000..689c3b73020ab --- /dev/null +++ b/components/netsuite/actions/list-invoices/list-invoices.mjs @@ -0,0 +1,61 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-list-invoices", + name: "List Invoices", + description: "Retrieves a list of invoices from NetSuite. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + searchQuery: { + propDefinition: [ + app, + "searchQuery", + ], + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of invoices to return", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + searchQuery, + maxResults, + } = this; + + const params = {}; + if (searchQuery) params.q = searchQuery; + + const items = []; + const paginator = app.paginate({ + fn: app.listInvoices, + fnArgs: { + $, + params: Object.keys(params).length > 0 + ? params + : undefined, + }, + maxResults, + }); + + for await (const item of paginator) { + items.push(item); + } + + $.export("$summary", `Successfully retrieved ${items.length} invoice${items.length === 1 + ? "" + : "s"}`); + return items; + }, +}; diff --git a/components/netsuite/actions/list-sales-orders/list-sales-orders.mjs b/components/netsuite/actions/list-sales-orders/list-sales-orders.mjs new file mode 100644 index 0000000000000..a887f30fd07ab --- /dev/null +++ b/components/netsuite/actions/list-sales-orders/list-sales-orders.mjs @@ -0,0 +1,58 @@ +import app from "../../netsuite.app.mjs"; + +export default { + key: "netsuite-list-sales-orders", + name: "List Sales Orders", + description: "Retrieves a list of sales orders. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-salesOrder)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + searchQuery: { + propDefinition: [ + app, + "searchQuery", + ], + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of sales orders to return", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + searchQuery, + maxResults, + } = this; + + const items = []; + const paginator = app.paginate({ + fn: app.listSalesOrders, + fnArgs: { + $, + params: { + q: searchQuery, + }, + }, + maxResults, + }); + + for await (const item of paginator) { + items.push(item); + } + + $.export("$summary", `Successfully retrieved ${items.length} sales order${items.length === 1 + ? "" + : "s"}`); + return items; + }, +}; diff --git a/components/netsuite/actions/update-invoice/update-invoice.mjs b/components/netsuite/actions/update-invoice/update-invoice.mjs new file mode 100644 index 0000000000000..8d13e0fac2bfb --- /dev/null +++ b/components/netsuite/actions/update-invoice/update-invoice.mjs @@ -0,0 +1,172 @@ +import app from "../../netsuite.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "netsuite-update-invoice", + name: "Update Invoice", + description: "Updates an existing invoice. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-invoice)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + invoiceId: { + propDefinition: [ + app, + "invoiceId", + ], + }, + customerId: { + propDefinition: [ + app, + "customerId", + ], + optional: true, + }, + tranDate: { + type: "string", + label: "Transaction Date", + description: "The posting date of this invoice (format: `YYYY-MM-DD`).", + optional: true, + }, + subsidiaryId: { + propDefinition: [ + app, + "subsidiaryId", + ], + optional: true, + }, + items: { + type: "string[]", + label: "Items", + description: `Array of item objects to add to the invoice. Each item should be a JSON object with the following properties: + +**Required:** +- \`item\` - Item ID or reference object (e.g., \`{ "id": "123" }\`) +- \`quantity\` - Quantity to invoice (number) + +**Important Optional:** +- \`rate\` - Price per unit (number) +- \`amount\` - Total line amount (number, usually \`quantity * rate\`) +- \`description\` - Custom description for the line item (string) +- \`taxCode\` - Tax code ID or reference object + +**Example:** +\`\`\`json +[ + { + "item": { "id": "456" }, + "quantity": 2, + "rate": 99.99, + "amount": 199.98, + "description": "Professional services", + "taxCode": { "id": "5" } + } +] +\`\`\``, + optional: true, + }, + memo: { + type: "string", + label: "Memo", + description: "A memo to describe this invoice.", + optional: true, + }, + otherRefNum: { + type: "string", + label: "PO/Reference Number", + description: "Customer's purchase order number or other reference number.", + optional: true, + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional fields to include in the invoice as a JSON object. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html) for available fields.", + optional: true, + }, + replace: { + propDefinition: [ + app, + "replace", + ], + }, + replaceSelectedFields: { + type: "boolean", + label: "Replace Selected Fields", + description: "If set to true, all fields that should be deleted in the update request, including body fields, must be included in the 'replace' query parameter", + optional: true, + default: false, + }, + propertyNameValidation: { + propDefinition: [ + app, + "propertyNameValidation", + ], + }, + propertyValueValidation: { + propDefinition: [ + app, + "propertyValueValidation", + ], + }, + }, + async run({ $ }) { + const { + app, + invoiceId, + customerId, + tranDate, + subsidiaryId, + items, + memo, + otherRefNum, + additionalFields, + replace, + replaceSelectedFields, + propertyNameValidation, + propertyValueValidation, + } = this; + + const response = await app.updateInvoice({ + $, + invoiceId, + headers: { + "X-NetSuite-PropertyNameValidation": propertyNameValidation, + "X-NetSuite-PropertyValueValidation": propertyValueValidation, + }, + params: { + replace, + replaceSelectedFields, + }, + data: { + ...(customerId && { + entity: { + id: customerId, + }, + }), + tranDate, + ...(subsidiaryId && { + subsidiary: { + id: subsidiaryId, + }, + }), + ...items && { + item: { + items: utils.parseJson(items), + }, + }, + memo, + otherRefNum, + ...additionalFields && utils.parseJson(additionalFields), + }, + }); + + $.export("$summary", `Successfully updated invoice with ID ${invoiceId}`); + return response; + }, +}; diff --git a/components/netsuite/actions/update-sales-order/update-sales-order.mjs b/components/netsuite/actions/update-sales-order/update-sales-order.mjs new file mode 100644 index 0000000000000..8a7819f11be3a --- /dev/null +++ b/components/netsuite/actions/update-sales-order/update-sales-order.mjs @@ -0,0 +1,147 @@ +import app from "../../netsuite.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "netsuite-update-sales-order", + name: "Update Sales Order", + description: "Updates an existing sales order. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html#tag-salesOrder)", + version: "0.0.1", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + idempotentHint: true, + }, + props: { + app, + salesOrderId: { + propDefinition: [ + app, + "salesOrderId", + ], + }, + customerId: { + propDefinition: [ + app, + "customerId", + ], + optional: true, + }, + tranDate: { + type: "string", + label: "Transaction Date", + description: "The posting date of this sales order (format: `YYYY-MM-DD`).", + optional: true, + }, + subsidiaryId: { + propDefinition: [ + app, + "subsidiaryId", + ], + optional: true, + }, + items: { + propDefinition: [ + app, + "items", + ], + }, + memo: { + type: "string", + label: "Memo", + description: "A memo to describe this sales order. It will appear on reports such as the 2-line Sales Orders register.", + optional: true, + }, + otherRefNum: { + type: "string", + label: "PO/Check Number", + description: "If your customer is paying by check, enter the number here. If your customer is issuing a purchase order, enter the PO number here.", + optional: true, + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional fields to include in the sales order as a JSON object. [See the documentation](https://system.netsuite.com/help/helpcenter/en_US/APIs/REST_API_Browser/record/v1/2025.2/index.html) for available fields.", + optional: true, + }, + replace: { + propDefinition: [ + app, + "replace", + ], + }, + replaceSelectedFields: { + type: "boolean", + label: "Replace Selected Fields", + description: "If set to true, all fields that should be deleted in the update request, including body fields, must be included in the **Replace Sublists** query parameter", + optional: true, + }, + propertyNameValidation: { + propDefinition: [ + app, + "propertyNameValidation", + ], + }, + propertyValueValidation: { + propDefinition: [ + app, + "propertyValueValidation", + ], + }, + }, + async run({ $ }) { + const { + app, + salesOrderId, + customerId, + tranDate, + subsidiaryId, + items, + memo, + otherRefNum, + additionalFields, + replace, + replaceSelectedFields, + propertyNameValidation, + propertyValueValidation, + } = this; + + const response = await app.updateSalesOrder({ + $, + salesOrderId, + headers: { + "X-NetSuite-PropertyNameValidation": propertyNameValidation, + "X-NetSuite-PropertyValueValidation": propertyValueValidation, + }, + params: { + replace, + replaceSelectedFields, + }, + data: { + ...(customerId && { + entity: { + id: customerId, + }, + }), + tranDate, + ...(subsidiaryId && { + subsidiary: { + id: subsidiaryId, + }, + }), + ...items && { + item: { + items: utils.parseJson(items), + }, + }, + memo, + otherRefNum, + ...(additionalFields && utils.parseJson(additionalFields)), + }, + }); + + $.export("$summary", `Successfully updated sales order with ID ${salesOrderId}`); + return response; + }, +}; diff --git a/components/netsuite/common/constants.mjs b/components/netsuite/common/constants.mjs new file mode 100644 index 0000000000000..0c9ae9b95a1a3 --- /dev/null +++ b/components/netsuite/common/constants.mjs @@ -0,0 +1,7 @@ +export default { + VALIDATION_OPTIONS: { + ERROR: "Error", + WARNING: "Warning", + IGNORE: "Ignore", + }, +}; diff --git a/components/netsuite/common/utils.mjs b/components/netsuite/common/utils.mjs new file mode 100644 index 0000000000000..2adf04343104f --- /dev/null +++ b/components/netsuite/common/utils.mjs @@ -0,0 +1,44 @@ +const parseJson = (input, maxDepth = 100) => { + const seen = new WeakSet(); + const parse = (value) => { + if (maxDepth <= 0) { + return value; + } + if (typeof(value) === "string") { + // Only parse if the string looks like a JSON object or array + const trimmed = value.trim(); + if ( + (trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]")) + ) { + try { + return parseJson(JSON.parse(value), maxDepth - 1); + } catch (e) { + return value; + } + } + return value; + } else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) { + if (seen.has(value)) { + return value; + } + seen.add(value); + return Object.entries(value) + .reduce((acc, [ + key, + val, + ]) => Object.assign(acc, { + [key]: parse(val), + }), {}); + } else if (Array.isArray(value)) { + return value.map((item) => parse(item)); + } + return value; + }; + + return parse(input); +}; + +export default { + parseJson, +}; diff --git a/components/netsuite/netsuite.app.mjs b/components/netsuite/netsuite.app.mjs index 2b9bb9fdf7913..3aebd12de616a 100644 --- a/components/netsuite/netsuite.app.mjs +++ b/components/netsuite/netsuite.app.mjs @@ -1,11 +1,322 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "netsuite", - propDefinitions: {}, + propDefinitions: { + salesOrderId: { + type: "integer", + label: "Sales Order ID", + description: "The internal identifier of the sales order", + async options({ page }) { + const limit = 20; + const offset = page * limit; + const { items } = await this.listSalesOrders({ + params: { + limit, + offset, + }, + }); + return items?.map((order) => ({ + label: order.tranId || `Order ${order.id}`, + value: order.id, + })) || []; + }, + }, + invoiceId: { + type: "integer", + label: "Invoice ID", + description: "The internal identifier of the invoice", + async options({ page }) { + const limit = 20; + const offset = page * limit; + const { items } = await this.listInvoices({ + params: { + limit, + offset, + }, + }); + return items?.map((invoice) => ({ + label: invoice.tranId || `Invoice ${invoice.id}`, + value: invoice.id, + })) || []; + }, + }, + subsidiaryId: { + type: "string", + label: "Subsidiary ID", + description: "The internal identifier of the subsidiary", + async options({ page }) { + const limit = 20; + const offset = page * limit; + const { items } = await this.listSubsidiaries({ + params: { + limit, + offset, + }, + }); + return items?.map((subsidiary) => ({ + label: subsidiary.name || `Subsidiary ${subsidiary.id}`, + value: subsidiary.id, + })) || []; + }, + }, + customerId: { + type: "string", + label: "Customer ID", + description: "The internal identifier of the customer", + async options({ page }) { + const limit = 20; + const offset = page * limit; + const { items } = await this.listCustomers({ + params: { + limit, + offset, + }, + }); + return items?.map((customer) => ({ + label: customer.entityId || customer.companyName || `Customer ${customer.id}`, + value: customer.id, + })) || []; + }, + }, + searchQuery: { + type: "string", + label: "Search Query", + description: "The search query used to filter results", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "The number of results per page (max 1000)", + optional: true, + default: 100, + }, + offset: { + type: "integer", + label: "Offset", + description: "The offset for selecting a specific page of results", + optional: true, + default: 0, + }, + expandSubResources: { + type: "boolean", + label: "Expand Sub Resources", + description: "Set to true to automatically expand all sublists, sublist lines, and subrecords", + optional: true, + default: false, + }, + simpleEnumFormat: { + type: "boolean", + label: "Simple Enum Format", + description: "Set to true to return enumeration values showing only the internal ID value", + optional: true, + default: false, + }, + fields: { + type: "string", + label: "Fields", + description: "Comma-separated list of field names to return (only selected fields will be returned)", + optional: true, + }, + items: { + type: "string[]", + label: "Items", + description: `Array of item objects to add to the sales order. Each item should be a JSON object with the following properties: + +**Required:** +- \`item\` - Item ID or reference object (e.g., \`{ "id": "123" }\`) +- \`quantity\` - Quantity to order (number) + +**Important Optional:** +- \`rate\` - Price per unit (number) +- \`amount\` - Total line amount (number, usually \`quantity * rate\`) +- \`description\` - Custom description for the line item (string) +- \`location\` - Inventory location ID or reference object + +**Example:** +\`\`\`json +[ + { + "item": { "id": "456" }, + "quantity": 2, + "rate": 99.99, + "amount": 199.98, + "description": "Custom widget - blue", + "location": { "id": "1" } + } +] +\`\`\``, + optional: true, + }, + propertyNameValidation: { + type: "string", + label: "Property Name Validation", + description: "Sets the strictness of property name validation", + options: Object.values(constants.VALIDATION_OPTIONS), + optional: true, + }, + propertyValueValidation: { + type: "string", + label: "Property Value Validation", + description: "Sets the strictness of property value validation", + options: Object.values(constants.VALIDATION_OPTIONS), + optional: true, + }, + replace: { + type: "string", + label: "Replace Sublists", + description: "Comma-separated names of sublists on this record. All sublist lines will be replaced with lines specified in the request.", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `https://${this.$auth.account_id}.suitetalk.api.netsuite.com/services/rest/record/v1${path}`; + }, + _headers(headers = {}) { + return { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + ...headers, + }; + }, + async _makeRequest({ + $ = this, path, headers, ...opts + }) { + return axios($, { + url: this.getUrl(path), + headers: this._headers(headers), + ...opts, + }); + }, + listSalesOrders(opts = {}) { + return this._makeRequest({ + path: "/salesOrder", + ...opts, + }); + }, + getSalesOrder({ + salesOrderId, opts, + } = {}) { + return this._makeRequest({ + path: `/salesOrder/${salesOrderId}`, + ...opts, + }); + }, + createSalesOrder(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/salesOrder", + ...opts, + }); + }, + updateSalesOrder({ + salesOrderId, opts, + } = {}) { + return this._makeRequest({ + method: "PATCH", + path: `/salesOrder/${salesOrderId}`, + ...opts, + }); + }, + deleteSalesOrder({ + salesOrderId, opts, + } = {}) { + return this._makeRequest({ + method: "DELETE", + path: `/salesOrder/${salesOrderId}`, + ...opts, + }); + }, + listInvoices(opts = {}) { + return this._makeRequest({ + path: "/invoice", + ...opts, + }); + }, + getInvoice({ + invoiceId, opts, + } = {}) { + return this._makeRequest({ + path: `/invoice/${invoiceId}`, + ...opts, + }); + }, + createInvoice(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/invoice", + ...opts, + }); + }, + updateInvoice({ + invoiceId, opts, + } = {}) { + return this._makeRequest({ + method: "PATCH", + path: `/invoice/${invoiceId}`, + ...opts, + }); + }, + deleteInvoice({ + invoiceId, opts, + } = {}) { + return this._makeRequest({ + method: "DELETE", + path: `/invoice/${invoiceId}`, + ...opts, + }); + }, + listSubsidiaries(opts = {}) { + return this._makeRequest({ + path: "/subsidiary", + ...opts, + }); + }, + listCustomers(opts = {}) { + return this._makeRequest({ + path: "/customer", + ...opts, + }); + }, + async *paginate({ + fn, fnArgs = {}, maxResults, fieldKey = "items", + }) { + const limit = 100; + let offset = 0; + let total = 0; + + while (true) { + const response = await fn({ + ...fnArgs, + params: { + ...fnArgs.params, + limit, + offset, + }, + }); + + const items = response[fieldKey] || []; + + for (const item of items) { + yield item; + total++; + + if (maxResults && total >= maxResults) { + return; + } + } + + if (items.length < limit) { + return; + } + + offset += limit; + } }, }, }; diff --git a/components/netsuite/package.json b/components/netsuite/package.json index 424a8e8d8cf0d..516c6915482a2 100644 --- a/components/netsuite/package.json +++ b/components/netsuite/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/netsuite", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream NetSuite Components", "main": "netsuite.app.mjs", "keywords": [