diff --git a/components/monta/.impeccable/hook.cache.json b/components/monta/.impeccable/hook.cache.json new file mode 100644 index 0000000000000..ebc7c5d0edadd --- /dev/null +++ b/components/monta/.impeccable/hook.cache.json @@ -0,0 +1 @@ +{"version":1,"sessions":{}} \ No newline at end of file diff --git a/components/monta/actions/add-order-colli/add-order-colli.mjs b/components/monta/actions/add-order-colli/add-order-colli.mjs new file mode 100644 index 0000000000000..893975c01d81b --- /dev/null +++ b/components/monta/actions/add-order-colli/add-order-colli.mjs @@ -0,0 +1,99 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-add-order-colli", + name: "Add Order Colli", + description: "Register a collo (parcel) on an order, including its dimensions and tracking details. Use this to record how an order was packed; inspect the result with **List Order Colli**. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1colli/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + number: { + type: "integer", + label: "Number", + description: "The collo number within the order (e.g. `1` for the first parcel)", + }, + weightGrammes: { + type: "integer", + label: "Weight (grammes)", + description: "The weight of the collo in grammes (e.g. `1234` for 1.234 kg)", + optional: true, + }, + lengthMm: { + type: "integer", + label: "Length (mm)", + description: "The length of the collo in millimetres", + optional: true, + }, + widthMm: { + type: "integer", + label: "Width (mm)", + description: "The width of the collo in millimetres", + optional: true, + }, + heightMm: { + type: "integer", + label: "Height (mm)", + description: "The height of the collo in millimetres", + optional: true, + }, + trackAndTraceCode: { + type: "string", + label: "Track and Trace Code", + description: "The track and trace code for the collo", + optional: true, + }, + trackAndTraceLink: { + type: "string", + label: "Track and Trace Link", + description: "The track and trace link for the collo (e.g. `https://carrier.example/track/ABC123`)", + optional: true, + }, + packageDescription: { + type: "string", + label: "Package Description", + description: "A description of the package", + optional: true, + }, + additionalFields: { + propDefinition: [ + monta, + "additionalFields", + ], + description: "Additional collo properties to send in the request body, using Monta's request-body casing (e.g. `{ \"IsParent\": true }`)", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.monta.createOrderColli({ + $, + orderId: this.orderId, + data: { + ...this.additionalFields, + Number: this.number, + WeightGrammes: this.weightGrammes, + LengthMm: this.lengthMm, + WidthMm: this.widthMm, + HeightMm: this.heightMm, + TrackAndTraceCode: this.trackAndTraceCode, + TrackAndTraceLink: this.trackAndTraceLink, + PackageDescription: this.packageDescription, + }, + }); + + $.export("$summary", `Successfully added collo \`${this.number}\` to order \`${this.orderId}\``); + + return response; + }, +}; diff --git a/components/monta/actions/approve-inbound-forecasts/approve-inbound-forecasts.mjs b/components/monta/actions/approve-inbound-forecasts/approve-inbound-forecasts.mjs new file mode 100644 index 0000000000000..d12e71919ac4d --- /dev/null +++ b/components/monta/actions/approve-inbound-forecasts/approve-inbound-forecasts.mjs @@ -0,0 +1,35 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-approve-inbound-forecasts", + name: "Approve Inbound Forecasts", + description: "Approve multiple inbound forecasts at once by their IDs. Obtain forecast IDs from **List Inbound Forecasts by Product SKU** or **Get Inbound Forecast Group** (the `InboundForecastId` field). [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1approve/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + inboundForecastIds: { + type: "integer[]", + label: "Inbound Forecast IDs", + description: "The IDs of the inbound forecasts to approve (e.g. `[123, 456]`)", + }, + }, + async run({ $ }) { + const ids = this.inboundForecastIds.map(Number); + const response = await this.monta.approveInboundForecasts({ + $, + data: ids, + }); + + $.export("$summary", `Successfully approved ${ids.length} inbound forecast${ids.length === 1 + ? "" + : "s"}`); + + return response; + }, +}; diff --git a/components/monta/actions/cancel-order/cancel-order.mjs b/components/monta/actions/cancel-order/cancel-order.mjs new file mode 100644 index 0000000000000..6404a84d993b5 --- /dev/null +++ b/components/monta/actions/cancel-order/cancel-order.mjs @@ -0,0 +1,43 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-cancel-order", + name: "Cancel Order", + description: "Cancel (delete) an order. Monta rejects this once picking has started (error 18), after the order has shipped (error 19), or when returns exist (error 25); the API error is surfaced to you. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D/delete)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + note: { + type: "string", + label: "Note", + description: "The reason for cancelling the order", + }, + }, + async run({ $ }) { + await this.monta.cancelOrder({ + $, + orderId: this.orderId, + data: { + Note: this.note, + }, + }); + + $.export("$summary", `Successfully cancelled order \`${this.orderId}\``); + + return { + success: true, + }; + }, +}; diff --git a/components/monta/actions/create-inbound-forecast-group/create-inbound-forecast-group.mjs b/components/monta/actions/create-inbound-forecast-group/create-inbound-forecast-group.mjs new file mode 100644 index 0000000000000..80dd958af303e --- /dev/null +++ b/components/monta/actions/create-inbound-forecast-group/create-inbound-forecast-group.mjs @@ -0,0 +1,103 @@ +import monta from "../../monta.app.mjs"; +import { parseJsonObjects } from "../../common/utils.mjs"; + +export default { + key: "monta-create-inbound-forecast-group", + name: "Create Inbound Forecast Group", + description: "Create a new inbound forecast group describing stock expected at the warehouse. Manage the group afterwards with **Update Inbound Forecast Group**, **Get Inbound Forecast Group**, or **Delete Inbound Forecast Group**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + reference: { + propDefinition: [ + monta, + "reference", + ], + description: "A unique reference for the new inbound forecast group", + }, + inboundForecasts: { + propDefinition: [ + monta, + "inboundForecasts", + ], + }, + supplierCode: { + propDefinition: [ + monta, + "supplierCode", + ], + optional: true, + }, + comment: { + propDefinition: [ + monta, + "comment", + ], + optional: true, + }, + warehouseDisplayName: { + propDefinition: [ + monta, + "warehouseDisplayName", + ], + optional: true, + }, + allocateStockOnDelivery: { + propDefinition: [ + monta, + "allocateStockOnDelivery", + ], + optional: true, + }, + expectedDeliveryDate: { + propDefinition: [ + monta, + "expectedDeliveryDate", + ], + optional: true, + }, + deliveryDate: { + propDefinition: [ + monta, + "deliveryDate", + ], + optional: true, + }, + additionalFields: { + propDefinition: [ + monta, + "additionalFields", + ], + description: "Additional properties to send in the request body, using Monta's request-body casing (e.g. `{ \"UniqueId\": \"...\" }`)", + optional: true, + }, + }, + async run({ $ }) { + const inboundForecasts = parseJsonObjects(this.inboundForecasts, "Inbound Forecast"); + + const response = await this.monta.createInboundForecastGroup({ + $, + data: { + ...this.additionalFields, + Reference: this.reference, + InboundForecasts: inboundForecasts, + SupplierCode: this.supplierCode, + Comment: this.comment, + WarehouseDisplayName: this.warehouseDisplayName, + AllocateStockOnDelivery: this.allocateStockOnDelivery, + ExpectedDeliveryDate: this.expectedDeliveryDate, + DeliveryDate: this.deliveryDate, + }, + }); + + $.export("$summary", `Successfully created inbound forecast group \`${this.reference}\``); + + return response; + }, +}; diff --git a/components/monta/actions/create-order/create-order.mjs b/components/monta/actions/create-order/create-order.mjs new file mode 100644 index 0000000000000..078393ae0890e --- /dev/null +++ b/components/monta/actions/create-order/create-order.mjs @@ -0,0 +1,191 @@ +import monta from "../../monta.app.mjs"; +import { parseJsonObjects } from "../../common/utils.mjs"; + +export default { + key: "monta-create-order", + name: "Create Order", + description: "Create a new order in Monta for fulfillment. Provide the recipient's delivery address (which needs a Company or Last Name) and at least one order line; validate the address first with **Validate Address** if needed. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + webshopOrderId: { + type: "string", + label: "Webshop Order ID", + description: "The unique ID of the order in your webshop", + }, + b2b: { + type: "boolean", + label: "B2B", + description: "Whether this is a business-to-business order", + default: false, + }, + street: { + propDefinition: [ + monta, + "street", + ], + label: "Delivery Street", + }, + city: { + propDefinition: [ + monta, + "city", + ], + label: "Delivery City", + }, + countryCode: { + propDefinition: [ + monta, + "countryCode", + ], + label: "Delivery Country Code", + }, + lines: { + type: "string[]", + label: "Order Lines", + description: "The order lines. Each entry is a JSON object with at least `Sku` and `OrderedQuantity` (e.g. `{\"Sku\":\"ABC-123\",\"OrderedQuantity\":2}`)", + }, + houseNumber: { + propDefinition: [ + monta, + "houseNumber", + ], + label: "Delivery House Number", + optional: true, + }, + houseNumberAddition: { + propDefinition: [ + monta, + "houseNumberAddition", + ], + label: "Delivery House Number Addition", + optional: true, + }, + postalCode: { + propDefinition: [ + monta, + "postalCode", + ], + label: "Delivery Postal Code", + optional: true, + }, + state: { + propDefinition: [ + monta, + "state", + ], + label: "Delivery State", + optional: true, + }, + company: { + propDefinition: [ + monta, + "company", + ], + label: "Delivery Company", + optional: true, + }, + firstName: { + propDefinition: [ + monta, + "firstName", + ], + label: "Delivery First Name", + optional: true, + }, + middleName: { + propDefinition: [ + monta, + "middleName", + ], + label: "Delivery Middle Name", + optional: true, + }, + lastName: { + propDefinition: [ + monta, + "lastName", + ], + label: "Delivery Last Name", + optional: true, + }, + phoneNumber: { + propDefinition: [ + monta, + "phoneNumber", + ], + label: "Delivery Phone Number", + optional: true, + }, + emailAddress: { + propDefinition: [ + monta, + "emailAddress", + ], + label: "Delivery Email Address", + optional: true, + }, + reference: { + type: "string", + label: "Reference", + description: "A reference for the order", + optional: true, + }, + comment: { + type: "string", + label: "Comment", + description: "A comment for the order", + optional: true, + }, + additionalFields: { + propDefinition: [ + monta, + "additionalFields", + ], + description: "Additional order properties to send in the request body, using Monta's request-body casing (e.g. `{ \"ShipperCode\": \"...\" }`)", + optional: true, + }, + }, + async run({ $ }) { + const lines = parseJsonObjects(this.lines, "Order Line"); + + const response = await this.monta.createOrder({ + $, + data: { + ...this.additionalFields, + WebshopOrderId: this.webshopOrderId, + Reference: this.reference, + Comment: this.comment, + Lines: lines, + ConsumerDetails: { + B2B: this.b2b, + DeliveryAddress: { + Street: this.street, + City: this.city, + CountryCode: this.countryCode, + HouseNumber: this.houseNumber, + HouseNumberAddition: this.houseNumberAddition, + PostalCode: this.postalCode, + State: this.state, + Company: this.company, + FirstName: this.firstName, + MiddleName: this.middleName, + LastName: this.lastName, + PhoneNumber: this.phoneNumber, + EmailAddress: this.emailAddress, + }, + }, + }, + }); + + $.export("$summary", `Successfully created order \`${this.webshopOrderId}\``); + + return response; + }, +}; diff --git a/components/monta/actions/create-rma-link/create-rma-link.mjs b/components/monta/actions/create-rma-link/create-rma-link.mjs new file mode 100644 index 0000000000000..014301954fb80 --- /dev/null +++ b/components/monta/actions/create-rma-link/create-rma-link.mjs @@ -0,0 +1,70 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-create-rma-link", + name: "Create RMA Link", + description: "Create an RMA (return merchandise authorization) link for an order, so a customer can start a return. Review existing returns for the order with **List Order Returns**. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1rmalinks/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + url: { + type: "string", + label: "URL", + description: "The URL of the RMA link (e.g. `https://returns.example.com/rma/abc`)", + optional: true, + }, + validUntil: { + type: "string", + label: "Valid Until", + description: "The expiry date and time of the RMA link in ISO 8601 format (e.g. `2026-07-31T23:59:59Z`)", + optional: true, + }, + isWarranty: { + type: "boolean", + label: "Is Warranty", + description: "Whether the RMA link is for a warranty claim", + optional: true, + }, + isFree: { + type: "boolean", + label: "Is Free", + description: "Whether the return is free of charge", + optional: true, + }, + guid: { + type: "string", + label: "GUID", + description: "A unique identifier for the RMA link. Leave blank to let Monta generate one, or supply your own UUID (e.g. `123e4567-e89b-12d3-a456-426614174000`)", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.monta.createRmaLink({ + $, + orderId: this.orderId, + data: { + Url: this.url, + ValidUntil: this.validUntil, + IsWarranty: this.isWarranty, + IsFree: this.isFree, + Guid: this.guid, + }, + }); + + $.export("$summary", `Successfully created RMA link for order \`${this.orderId}\``); + + return response; + }, +}; diff --git a/components/monta/actions/create-shipping-label/create-shipping-label.mjs b/components/monta/actions/create-shipping-label/create-shipping-label.mjs new file mode 100644 index 0000000000000..be253d8d968bd --- /dev/null +++ b/components/monta/actions/create-shipping-label/create-shipping-label.mjs @@ -0,0 +1,45 @@ +import monta from "../../monta.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "monta-create-shipping-label", + name: "Create Shipping Label", + description: "Generate a shipping label for an order in a supported output format (`pdf` or `zpl`). Use this when an order is ready to ship and needs a carrier label, then use **List Shipping Labels** to enumerate the generated labels and **Download Shipping Label** to fetch the file. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1shippinglabels/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + labelFileType: { + type: "string", + label: "Label File Type", + description: "The file type to generate the label in", + options: constants.LABEL_FILE_TYPES, + }, + }, + async run({ $ }) { + const labels = await this.monta.createShippingLabel({ + $, + orderId: this.orderId, + params: { + labelfiletype: this.labelFileType, + }, + }); + + $.export("$summary", `Successfully created shipping label${labels.length === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return labels; + }, +}; diff --git a/components/monta/actions/delete-inbound-forecast-group/delete-inbound-forecast-group.mjs b/components/monta/actions/delete-inbound-forecast-group/delete-inbound-forecast-group.mjs new file mode 100644 index 0000000000000..1ee53a8d07951 --- /dev/null +++ b/components/monta/actions/delete-inbound-forecast-group/delete-inbound-forecast-group.mjs @@ -0,0 +1,49 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-delete-inbound-forecast-group", + name: "Delete Inbound Forecast Group", + description: "Delete an inbound forecast group, or a single SKU within it when a SKU is provided. Find references with **List Inbound Forecast Groups**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group~1%7Breference%7D/delete)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + reference: { + propDefinition: [ + monta, + "reference", + ], + description: "The reference of the inbound forecast group to delete. Use the **List Inbound Forecast Groups** action to find available references.", + }, + sku: { + propDefinition: [ + monta, + "sku", + ], + description: "If provided, only the forecast for this SKU is deleted instead of the entire group", + optional: true, + }, + }, + async run({ $ }) { + await this.monta.deleteInboundForecastGroup({ + $, + reference: this.reference, + params: { + sku: this.sku, + }, + }); + + $.export("$summary", this.sku + ? `Successfully deleted SKU \`${this.sku}\` from inbound forecast group \`${this.reference}\`` + : `Successfully deleted inbound forecast group \`${this.reference}\``); + + return { + success: true, + }; + }, +}; diff --git a/components/monta/actions/download-shipping-label/download-shipping-label.mjs b/components/monta/actions/download-shipping-label/download-shipping-label.mjs new file mode 100644 index 0000000000000..28a41764ad9f7 --- /dev/null +++ b/components/monta/actions/download-shipping-label/download-shipping-label.mjs @@ -0,0 +1,53 @@ +import fs from "fs"; +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-download-shipping-label", + name: "Download Shipping Label", + description: "Download a single shipping label file for an order and save it to the `/tmp` directory. Get the file name from **List Shipping Labels**, or generate labels first with **Create Shipping Label**. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1shippinglabels~1%7Bfilename%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + filename: { + type: "string", + label: "Filename", + description: "The shipping label file name. Use the **List Shipping Labels** action to find available file names.", + }, + syncDir: { + type: "dir", + accessMode: "write", + sync: true, + }, + }, + async run({ $ }) { + const data = await this.monta.downloadShippingLabel({ + $, + orderId: this.orderId, + filename: this.filename, + responseType: "arraybuffer", + }); + + const outputFilename = this.filename.split("/").pop(); + const path = `/tmp/${outputFilename}`; + fs.writeFileSync(path, Buffer.from(data)); + + $.export("$summary", `Successfully downloaded shipping label \`${outputFilename}\` for order \`${this.orderId}\``); + + return { + path, + filename: outputFilename, + }; + }, +}; diff --git a/components/monta/actions/forget-order/forget-order.mjs b/components/monta/actions/forget-order/forget-order.mjs new file mode 100644 index 0000000000000..01408feb83f47 --- /dev/null +++ b/components/monta/actions/forget-order/forget-order.mjs @@ -0,0 +1,51 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-forget-order", + name: "Forget Order", + description: "Anonymize an order for GDPR erasure. This permanently removes personal data and cannot be undone. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1forget/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + invoice: { + type: "boolean", + label: "Anonymize Invoice", + description: "Whether to also anonymize invoice data", + optional: true, + }, + shipping: { + type: "boolean", + label: "Anonymize Shipping", + description: "Whether to also anonymize shipping data", + optional: true, + }, + }, + async run({ $ }) { + await this.monta.forgetOrder({ + $, + orderId: this.orderId, + params: { + invoice: this.invoice, + shipping: this.shipping, + }, + }); + + $.export("$summary", `Successfully anonymized order \`${this.orderId}\``); + + return { + success: true, + }; + }, +}; diff --git a/components/monta/actions/get-inbound-forecast-group/get-inbound-forecast-group.mjs b/components/monta/actions/get-inbound-forecast-group/get-inbound-forecast-group.mjs new file mode 100644 index 0000000000000..9192524048f81 --- /dev/null +++ b/components/monta/actions/get-inbound-forecast-group/get-inbound-forecast-group.mjs @@ -0,0 +1,33 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-get-inbound-forecast-group", + name: "Get Inbound Forecast Group", + description: "Retrieve an inbound forecast group and its forecasts by reference. Find references with **List Inbound Forecast Groups**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group~1%7Breference%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + reference: { + propDefinition: [ + monta, + "reference", + ], + }, + }, + async run({ $ }) { + const response = await this.monta.getInboundForecastGroup({ + $, + reference: this.reference, + }); + + $.export("$summary", `Successfully retrieved inbound forecast group \`${this.reference}\``); + + return response; + }, +}; diff --git a/components/monta/actions/get-inbound-forecast/get-inbound-forecast.mjs b/components/monta/actions/get-inbound-forecast/get-inbound-forecast.mjs new file mode 100644 index 0000000000000..59a27a0182fcb --- /dev/null +++ b/components/monta/actions/get-inbound-forecast/get-inbound-forecast.mjs @@ -0,0 +1,41 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-get-inbound-forecast", + name: "Get Inbound Forecast", + description: "Retrieve a single inbound forecast from a group by reference and SKU. Find references with **List Inbound Forecast Groups**, or use **List Inbound Forecasts by Product SKU** to find forecasts across groups. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group~1%7Breference%7D~1%7Bsku%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + reference: { + propDefinition: [ + monta, + "reference", + ], + }, + sku: { + propDefinition: [ + monta, + "sku", + ], + description: "The product SKU of the forecast to retrieve", + }, + }, + async run({ $ }) { + const response = await this.monta.getInboundForecast({ + $, + reference: this.reference, + sku: this.sku, + }); + + $.export("$summary", `Successfully retrieved inbound forecast for SKU \`${this.sku}\` in group \`${this.reference}\``); + + return response; + }, +}; diff --git a/components/monta/actions/get-order/get-order.mjs b/components/monta/actions/get-order/get-order.mjs index 1389e6dcb1a33..6f3b4f68bfa27 100644 --- a/components/monta/actions/get-order/get-order.mjs +++ b/components/monta/actions/get-order/get-order.mjs @@ -4,7 +4,7 @@ export default { key: "monta-get-order", name: "Get Order", description: "Get an order by ID. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D/get)", - version: "0.0.3", + version: "0.0.4", type: "action", annotations: { destructiveHint: false, diff --git a/components/monta/actions/get-return/get-return.mjs b/components/monta/actions/get-return/get-return.mjs index 71a1bceb04674..f26480553fd3c 100644 --- a/components/monta/actions/get-return/get-return.mjs +++ b/components/monta/actions/get-return/get-return.mjs @@ -4,7 +4,7 @@ export default { key: "monta-get-return", name: "Get Return", description: "Get a return by ID. [See the documentation](https://api-v6.monta.nl/index.html#tag/Return/paths/~1return~1%7Bid%7D/get)", - version: "0.0.3", + version: "0.0.4", type: "action", annotations: { destructiveHint: false, diff --git a/components/monta/actions/list-inbound-forecast-events/list-inbound-forecast-events.mjs b/components/monta/actions/list-inbound-forecast-events/list-inbound-forecast-events.mjs new file mode 100644 index 0000000000000..6ffb9aef960f2 --- /dev/null +++ b/components/monta/actions/list-inbound-forecast-events/list-inbound-forecast-events.mjs @@ -0,0 +1,34 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-inbound-forecast-events", + name: "List Inbound Forecast Events", + description: "List inbound forecast change events created after the provided cursor ID. Use this for reliable incremental syncing of inbound forecast changes by repeatedly polling with the last event ID. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1events~1since_id~1%7Bid%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + sinceId: { + type: "integer", + label: "Since ID", + description: "Return events created after this event ID. Use `0` to start from the beginning.", + }, + }, + async run({ $ }) { + const events = await this.monta.listInboundForecastEvents({ + $, + id: this.sinceId, + }); + + $.export("$summary", `Successfully retrieved ${events.length} inbound forecast event${events.length === 1 + ? "" + : "s"}`); + + return events; + }, +}; diff --git a/components/monta/actions/list-inbound-forecast-groups/list-inbound-forecast-groups.mjs b/components/monta/actions/list-inbound-forecast-groups/list-inbound-forecast-groups.mjs new file mode 100644 index 0000000000000..b26dd8d1946f8 --- /dev/null +++ b/components/monta/actions/list-inbound-forecast-groups/list-inbound-forecast-groups.mjs @@ -0,0 +1,88 @@ +import monta from "../../monta.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "monta-list-inbound-forecast-groups", + name: "List Inbound Forecast Groups", + description: "List inbound forecast groups matching the provided filters. Use this to find group references for **Get Inbound Forecast Group**, **Update Inbound Forecast Group**, or **Delete Inbound Forecast Group**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + createdSince: { + type: "string", + label: "Created Since", + description: "Only return groups created on or after this ISO 8601 date and time (e.g. `2026-07-24T14:30:00Z`)", + optional: true, + }, + createdUntil: { + type: "string", + label: "Created Until", + description: "Only return groups created on or before this ISO 8601 date and time (e.g. `2026-07-24T14:30:00Z`)", + optional: true, + }, + approved: { + type: "boolean", + label: "Approved", + description: "Filter by approval status", + optional: true, + }, + sku: { + propDefinition: [ + monta, + "sku", + ], + description: "Only return groups containing this product SKU", + optional: true, + }, + reference: { + propDefinition: [ + monta, + "reference", + ], + description: "Only return groups matching this reference", + optional: true, + }, + page: { + type: "integer", + label: "Page", + description: "The page of results to retrieve", + min: 0, + optional: true, + }, + pageSize: { + type: "integer", + label: "Page Size", + description: `The number of groups to return per page (Monta's default is ${constants.DEFAULT_PAGE_SIZE})`, + min: constants.MIN_PAGE_SIZE, + max: constants.MAX_PAGE_SIZE, + default: constants.DEFAULT_PAGE_SIZE, + optional: true, + }, + }, + async run({ $ }) { + const groups = await this.monta.listInboundForecastGroups({ + $, + params: { + created_since: this.createdSince, + created_until: this.createdUntil, + approved: this.approved, + sku: this.sku, + reference: this.reference, + page: this.page, + page_size: this.pageSize, + }, + }); + + $.export("$summary", `Successfully retrieved ${groups.length} inbound forecast group${groups.length === 1 + ? "" + : "s"}`); + + return groups; + }, +}; diff --git a/components/monta/actions/list-inbound-forecasts-by-product-sku/list-inbound-forecasts-by-product-sku.mjs b/components/monta/actions/list-inbound-forecasts-by-product-sku/list-inbound-forecasts-by-product-sku.mjs new file mode 100644 index 0000000000000..0cc3882e4327a --- /dev/null +++ b/components/monta/actions/list-inbound-forecasts-by-product-sku/list-inbound-forecasts-by-product-sku.mjs @@ -0,0 +1,34 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-inbound-forecasts-by-product-sku", + name: "List Inbound Forecasts by Product SKU", + description: "List all inbound forecasts for a given product SKU across groups. Use this to check expected incoming stock for a SKU, then approve entries with **Approve Inbound Forecasts** or inspect one with **Get Inbound Forecast**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group~1byproductsku~1%7Bproductsku%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + productSku: { + type: "string", + label: "Product SKU", + description: "The product SKU to list inbound forecasts for", + }, + }, + async run({ $ }) { + const forecasts = await this.monta.listInboundForecastsByProductSku({ + $, + productSku: this.productSku, + }); + + $.export("$summary", `Successfully retrieved ${forecasts.length} inbound forecast${forecasts.length === 1 + ? "" + : "s"} for SKU \`${this.productSku}\``); + + return forecasts; + }, +}; diff --git a/components/monta/actions/list-inbounds/list-inbounds.mjs b/components/monta/actions/list-inbounds/list-inbounds.mjs new file mode 100644 index 0000000000000..24676aeeb52de --- /dev/null +++ b/components/monta/actions/list-inbounds/list-inbounds.mjs @@ -0,0 +1,37 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-inbounds", + name: "List Inbounds", + description: "List inbound shipments expected at the warehouse. Use this to review incoming stock, paging forward with the Since ID cursor to walk through large result sets; relate to **List Inbound Forecast Groups** for grouped forecast data. [See the documentation](https://api-v6.monta.nl/index.html#tag/Inbounds/paths/~1inbounds/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + sinceId: { + type: "integer", + label: "Since ID", + description: "Only return inbounds with an ID greater than this value", + optional: true, + }, + }, + async run({ $ }) { + const inbounds = await this.monta.listInbounds({ + $, + params: { + sinceid: this.sinceId, + }, + }); + + $.export("$summary", `Successfully retrieved ${inbounds.length} inbound${inbounds.length === 1 + ? "" + : "s"}`); + + return inbounds; + }, +}; diff --git a/components/monta/actions/list-order-batches/list-order-batches.mjs b/components/monta/actions/list-order-batches/list-order-batches.mjs new file mode 100644 index 0000000000000..996a51fc1c67b --- /dev/null +++ b/components/monta/actions/list-order-batches/list-order-batches.mjs @@ -0,0 +1,36 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-order-batches", + name: "List Order Batches", + description: "List the batch (lot) lines shipped for an order. Use this for batch traceability when you need to know which lots were used to fulfill an order. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1batches/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const response = await this.monta.listOrderBatches({ + $, + orderId: this.orderId, + }); + const count = response.BatchLines?.length ?? 0; + + $.export("$summary", `Successfully retrieved ${count} batch line${count === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return response; + }, +}; diff --git a/components/monta/actions/list-order-colli/list-order-colli.mjs b/components/monta/actions/list-order-colli/list-order-colli.mjs new file mode 100644 index 0000000000000..8f3fb304820f8 --- /dev/null +++ b/components/monta/actions/list-order-colli/list-order-colli.mjs @@ -0,0 +1,36 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-order-colli", + name: "List Order Colli", + description: "List the colli (parcels) that make up an order. Use this to inspect the parcel and tracking breakdown for a shipment, for example after registering parcels with the **Add Order Colli** action. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1colli/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const response = await this.monta.listOrderColli({ + $, + orderId: this.orderId, + }); + const boxes = response.BoxesShipped ?? 0; + + $.export("$summary", `Successfully retrieved colli for order \`${this.orderId}\` (${boxes} box${boxes === 1 + ? "" + : "es"} shipped)`); + + return response; + }, +}; diff --git a/components/monta/actions/list-order-events-since-id/list-order-events-since-id.mjs b/components/monta/actions/list-order-events-since-id/list-order-events-since-id.mjs new file mode 100644 index 0000000000000..6d766be8b7446 --- /dev/null +++ b/components/monta/actions/list-order-events-since-id/list-order-events-since-id.mjs @@ -0,0 +1,43 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-order-events-since-id", + name: "List Order Events Since ID", + description: "List order change events created after the provided cursor ID, which is Monta's recommended method for reliable status-change polling. Use this for incremental syncing across all orders; see **List Order Events** for a single order's history or **List Updated Orders** for datetime-based bulk syncing. [See the documentation](https://api-v6.monta.nl/index.html#tag/OrderEvent/paths/~1orderevents~1since_id~1%7Bid%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + sinceId: { + type: "integer", + label: "Since ID", + description: "Return events created after this event ID. Use `0` to start from the beginning.", + }, + includeWmsEvents: { + type: "boolean", + label: "Include WMS Events", + description: "Whether to include warehouse management system events", + optional: true, + }, + }, + async run({ $ }) { + const events = await this.monta.listOrderEventsSinceId({ + $, + id: this.sinceId, + params: { + includeWMSEvents: this.includeWmsEvents, + }, + }); + + $.export("$summary", `Successfully retrieved ${events.length} order event${events.length === 1 + ? "" + : "s"}`); + + return events; + }, +}; diff --git a/components/monta/actions/list-order-events/list-order-events.mjs b/components/monta/actions/list-order-events/list-order-events.mjs index 530b6bfbf20a6..346f2f47bc2e4 100644 --- a/components/monta/actions/list-order-events/list-order-events.mjs +++ b/components/monta/actions/list-order-events/list-order-events.mjs @@ -4,7 +4,7 @@ export default { key: "monta-list-order-events", name: "List Order Events", description: "List order events for an order. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1events/get)", - version: "0.0.3", + version: "0.0.4", type: "action", annotations: { destructiveHint: false, diff --git a/components/monta/actions/list-order-id-options/list-order-id-options.mjs b/components/monta/actions/list-order-id-options/list-order-id-options.mjs index 8399054e2991b..3a5c37f71964a 100644 --- a/components/monta/actions/list-order-id-options/list-order-id-options.mjs +++ b/components/monta/actions/list-order-id-options/list-order-id-options.mjs @@ -4,7 +4,7 @@ export default { key: "monta-list-order-id-options", name: "List Order ID Options", description: "Retrieves available options for the Order ID field.", - version: "0.0.2", + version: "0.0.3", type: "action", annotations: { destructiveHint: false, diff --git a/components/monta/actions/list-order-returns/list-order-returns.mjs b/components/monta/actions/list-order-returns/list-order-returns.mjs new file mode 100644 index 0000000000000..810f47714deb2 --- /dev/null +++ b/components/monta/actions/list-order-returns/list-order-returns.mjs @@ -0,0 +1,36 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-order-returns", + name: "List Order Returns", + description: "List the return records for an order. Use this to see all returns registered against an order, then call **Get Return** for the full details of a single return. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1return/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const response = await this.monta.listReturns({ + $, + orderId: this.orderId, + }); + const returns = response.Returns ?? []; + + $.export("$summary", `Successfully retrieved ${returns.length} return${returns.length === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return returns; + }, +}; diff --git a/components/monta/actions/list-product-stock-changes/list-product-stock-changes.mjs b/components/monta/actions/list-product-stock-changes/list-product-stock-changes.mjs index 0f5b499356297..c4bb6114d7d3b 100644 --- a/components/monta/actions/list-product-stock-changes/list-product-stock-changes.mjs +++ b/components/monta/actions/list-product-stock-changes/list-product-stock-changes.mjs @@ -5,7 +5,7 @@ export default { name: "List Product Stock Changes", description: "List products whose stock changed since a specified date and time. [See the documentation](https://api-v6.monta.nl/index.html#tag/Product/paths/~1product~1updated_since~1%7BupdatedSince%7D/get)", - version: "0.0.1", + version: "0.0.2", type: "action", annotations: { destructiveHint: false, diff --git a/components/monta/actions/list-return-forecasts/list-return-forecasts.mjs b/components/monta/actions/list-return-forecasts/list-return-forecasts.mjs new file mode 100644 index 0000000000000..4ab753ee04e81 --- /dev/null +++ b/components/monta/actions/list-return-forecasts/list-return-forecasts.mjs @@ -0,0 +1,35 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-return-forecasts", + name: "List Return Forecasts", + description: "List the expected (forecasted) returns for an order, as opposed to the actual return records from **List Order Returns**. Use this to anticipate inbound returns before they physically arrive. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1returnforecasts/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const forecasts = await this.monta.listReturnForecasts({ + $, + orderId: this.orderId, + }); + + $.export("$summary", `Successfully retrieved ${forecasts.length} return forecast${forecasts.length === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return forecasts; + }, +}; diff --git a/components/monta/actions/list-return-labels/list-return-labels.mjs b/components/monta/actions/list-return-labels/list-return-labels.mjs new file mode 100644 index 0000000000000..c4bc248c48271 --- /dev/null +++ b/components/monta/actions/list-return-labels/list-return-labels.mjs @@ -0,0 +1,35 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-return-labels", + name: "List Return Labels", + description: "List the return labels for an order (labels for inbound returns), as opposed to outbound shipping labels. Use this when handling a customer return that needs a prepaid inbound label; see **List Order Returns** for the associated return records. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1returnlabels/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const labels = await this.monta.listReturnLabels({ + $, + orderId: this.orderId, + }); + + $.export("$summary", `Successfully retrieved ${labels.length} return label${labels.length === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return labels; + }, +}; diff --git a/components/monta/actions/list-shipping-labels/list-shipping-labels.mjs b/components/monta/actions/list-shipping-labels/list-shipping-labels.mjs new file mode 100644 index 0000000000000..92f8e86b6db5a --- /dev/null +++ b/components/monta/actions/list-shipping-labels/list-shipping-labels.mjs @@ -0,0 +1,35 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-list-shipping-labels", + name: "List Shipping Labels", + description: "List the shipping labels of an order. Use this to retrieve the label file names, which serve as inputs to the **Download Shipping Label** action, typically after generating labels with **Create Shipping Label**. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D~1shippinglabels/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + }, + async run({ $ }) { + const labels = await this.monta.listShippingLabels({ + $, + orderId: this.orderId, + }); + + $.export("$summary", `Successfully retrieved ${labels.length} shipping label${labels.length === 1 + ? "" + : "s"} for order \`${this.orderId}\``); + + return labels; + }, +}; diff --git a/components/monta/actions/list-updated-orders/list-updated-orders.mjs b/components/monta/actions/list-updated-orders/list-updated-orders.mjs new file mode 100644 index 0000000000000..c03e5f5b37291 --- /dev/null +++ b/components/monta/actions/list-updated-orders/list-updated-orders.mjs @@ -0,0 +1,46 @@ +import monta from "../../monta.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "monta-list-updated-orders", + name: "List Updated Orders", + description: "List orders whose status changed since a specified date and time, including orders deleted since then. Use this for reliable bulk order syncing; for cursor-based incremental polling of individual change events, use **List Order Events Since ID** instead. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1updated_since~1%7BupdatedSince%7D/get)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + updatedSince: { + type: "string", + label: "Updated Since", + description: "The start date and time in ISO 8601 format (e.g. `2026-07-24T00:00:00Z`)", + }, + status: { + type: "string", + label: "Status", + description: "Filter orders by their deleted status", + options: constants.ORDER_UPDATED_SINCE_STATUSES, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.monta.listUpdatedOrders({ + $, + updatedSince: this.updatedSince, + params: { + status: this.status, + }, + }); + const orders = response.Orders ?? []; + + $.export("$summary", `Successfully retrieved ${orders.length} updated order${orders.length === 1 + ? "" + : "s"}`); + + return orders; + }, +}; diff --git a/components/monta/actions/update-inbound-forecast-group/update-inbound-forecast-group.mjs b/components/monta/actions/update-inbound-forecast-group/update-inbound-forecast-group.mjs new file mode 100644 index 0000000000000..7b0ef603333d2 --- /dev/null +++ b/components/monta/actions/update-inbound-forecast-group/update-inbound-forecast-group.mjs @@ -0,0 +1,107 @@ +import monta from "../../monta.app.mjs"; +import { parseJsonObjects } from "../../common/utils.mjs"; + +export default { + key: "monta-update-inbound-forecast-group", + name: "Update Inbound Forecast Group", + description: "Update an existing inbound forecast group by its reference. Find references with **List Inbound Forecast Groups** and inspect a group with **Get Inbound Forecast Group**. [See the documentation](https://api-v6.monta.nl/index.html#tag/InboundForecast/paths/~1inboundforecast~1group~1%7Breference%7D/put)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + reference: { + propDefinition: [ + monta, + "reference", + ], + description: "The reference of the inbound forecast group to update. Use the **List Inbound Forecast Groups** action to find available references.", + }, + inboundForecasts: { + propDefinition: [ + monta, + "inboundForecasts", + ], + optional: true, + }, + supplierCode: { + propDefinition: [ + monta, + "supplierCode", + ], + optional: true, + }, + comment: { + propDefinition: [ + monta, + "comment", + ], + optional: true, + }, + warehouseDisplayName: { + propDefinition: [ + monta, + "warehouseDisplayName", + ], + optional: true, + }, + allocateStockOnDelivery: { + propDefinition: [ + monta, + "allocateStockOnDelivery", + ], + optional: true, + }, + expectedDeliveryDate: { + propDefinition: [ + monta, + "expectedDeliveryDate", + ], + optional: true, + }, + deliveryDate: { + propDefinition: [ + monta, + "deliveryDate", + ], + optional: true, + }, + additionalFields: { + propDefinition: [ + monta, + "additionalFields", + ], + description: "Additional properties to send in the request body, using Monta's request-body casing (e.g. `{ \"UniqueId\": \"...\" }`)", + optional: true, + }, + }, + async run({ $ }) { + const inboundForecasts = this.inboundForecasts + ? parseJsonObjects(this.inboundForecasts, "Inbound Forecast") + : undefined; + + const response = await this.monta.updateInboundForecastGroup({ + $, + reference: this.reference, + data: { + ...this.additionalFields, + Reference: this.reference, + InboundForecasts: inboundForecasts, + SupplierCode: this.supplierCode, + Comment: this.comment, + WarehouseDisplayName: this.warehouseDisplayName, + AllocateStockOnDelivery: this.allocateStockOnDelivery, + ExpectedDeliveryDate: this.expectedDeliveryDate, + DeliveryDate: this.deliveryDate, + }, + }); + + $.export("$summary", `Successfully updated inbound forecast group \`${this.reference}\``); + + return response; + }, +}; diff --git a/components/monta/actions/update-order/update-order.mjs b/components/monta/actions/update-order/update-order.mjs new file mode 100644 index 0000000000000..d258ee610ee7b --- /dev/null +++ b/components/monta/actions/update-order/update-order.mjs @@ -0,0 +1,182 @@ +import monta from "../../monta.app.mjs"; + +function cleanObject(obj) { + const entries = Object.entries(obj).filter(([ + , value, + ]) => value !== undefined); + return entries.length + ? Object.fromEntries(entries) + : undefined; +} + +export default { + key: "monta-update-order", + name: "Update Order", + description: "Update an order, for example to correct a customer's delivery address before it is picked. Validate a new address first with **Validate Address** if needed. When changing the delivery address, supply the full address (at least Street, City, and Country Code), since Monta replaces the entire address. Monta rejects address changes once picking has started (error 17) or after the order has shipped (error 19); the API error is surfaced to you. [See the documentation](https://api-v6.monta.nl/index.html#tag/Order/paths/~1order~1%7Bwebshoporderid%7D/put)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + monta, + orderId: { + propDefinition: [ + monta, + "orderId", + ], + }, + street: { + propDefinition: [ + monta, + "street", + ], + label: "Delivery Street", + optional: true, + }, + houseNumber: { + propDefinition: [ + monta, + "houseNumber", + ], + label: "Delivery House Number", + optional: true, + }, + houseNumberAddition: { + propDefinition: [ + monta, + "houseNumberAddition", + ], + label: "Delivery House Number Addition", + optional: true, + }, + postalCode: { + propDefinition: [ + monta, + "postalCode", + ], + label: "Delivery Postal Code", + optional: true, + }, + city: { + propDefinition: [ + monta, + "city", + ], + label: "Delivery City", + optional: true, + }, + state: { + propDefinition: [ + monta, + "state", + ], + label: "Delivery State", + optional: true, + }, + countryCode: { + propDefinition: [ + monta, + "countryCode", + ], + label: "Delivery Country Code", + optional: true, + }, + company: { + propDefinition: [ + monta, + "company", + ], + label: "Delivery Company", + optional: true, + }, + firstName: { + propDefinition: [ + monta, + "firstName", + ], + label: "Delivery First Name", + optional: true, + }, + middleName: { + propDefinition: [ + monta, + "middleName", + ], + label: "Delivery Middle Name", + optional: true, + }, + lastName: { + propDefinition: [ + monta, + "lastName", + ], + label: "Delivery Last Name", + optional: true, + }, + phoneNumber: { + propDefinition: [ + monta, + "phoneNumber", + ], + label: "Delivery Phone Number", + optional: true, + }, + emailAddress: { + propDefinition: [ + monta, + "emailAddress", + ], + label: "Delivery Email Address", + optional: true, + }, + additionalFields: { + propDefinition: [ + monta, + "additionalFields", + ], + description: "Additional order properties to send in the request body, using Monta's request-body casing (e.g. `{ \"Comment\": \"...\" }`)", + optional: true, + }, + }, + async run({ $ }) { + const deliveryAddress = cleanObject({ + Street: this.street, + HouseNumber: this.houseNumber, + HouseNumberAddition: this.houseNumberAddition, + PostalCode: this.postalCode, + City: this.city, + State: this.state, + CountryCode: this.countryCode, + Company: this.company, + FirstName: this.firstName, + MiddleName: this.middleName, + LastName: this.lastName, + PhoneNumber: this.phoneNumber, + EmailAddress: this.emailAddress, + }); + + const consumerDetails = cleanObject({ + ...this.additionalFields?.ConsumerDetails, + DeliveryAddress: deliveryAddress, + }); + + const data = cleanObject({ + ...this.additionalFields, + WebshopOrderId: this.orderId, + ConsumerDetails: consumerDetails, + }); + + const response = await this.monta.updateOrder({ + $, + orderId: this.orderId, + data, + }); + + $.export("$summary", `Successfully updated order \`${this.orderId}\``); + + return response; + }, +}; diff --git a/components/monta/actions/validate-address/validate-address.mjs b/components/monta/actions/validate-address/validate-address.mjs new file mode 100644 index 0000000000000..e3ca522fd3404 --- /dev/null +++ b/components/monta/actions/validate-address/validate-address.mjs @@ -0,0 +1,131 @@ +import monta from "../../monta.app.mjs"; + +export default { + key: "monta-validate-address", + name: "Validate Address", + description: "Validate a delivery address before submitting it, for example ahead of **Create Order** or **Update Order**. Monta returns structured error codes when the address is invalid; a complete address also needs a recipient (Company or Last Name), Postal Code, and House Number. [See the documentation](https://api-v6.monta.nl/index.html#tag/Address/paths/~1address/post)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + monta, + street: { + propDefinition: [ + monta, + "street", + ], + }, + city: { + propDefinition: [ + monta, + "city", + ], + }, + countryCode: { + propDefinition: [ + monta, + "countryCode", + ], + }, + houseNumber: { + propDefinition: [ + monta, + "houseNumber", + ], + optional: true, + }, + houseNumberAddition: { + propDefinition: [ + monta, + "houseNumberAddition", + ], + optional: true, + }, + postalCode: { + propDefinition: [ + monta, + "postalCode", + ], + optional: true, + }, + state: { + propDefinition: [ + monta, + "state", + ], + optional: true, + }, + company: { + propDefinition: [ + monta, + "company", + ], + optional: true, + }, + firstName: { + propDefinition: [ + monta, + "firstName", + ], + optional: true, + }, + middleName: { + propDefinition: [ + monta, + "middleName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + monta, + "lastName", + ], + optional: true, + }, + phoneNumber: { + propDefinition: [ + monta, + "phoneNumber", + ], + optional: true, + }, + emailAddress: { + propDefinition: [ + monta, + "emailAddress", + ], + optional: true, + }, + }, + async run({ $ }) { + await this.monta.validateAddress({ + $, + data: { + Street: this.street, + City: this.city, + CountryCode: this.countryCode, + HouseNumber: this.houseNumber, + HouseNumberAddition: this.houseNumberAddition, + PostalCode: this.postalCode, + State: this.state, + Company: this.company, + FirstName: this.firstName, + MiddleName: this.middleName, + LastName: this.lastName, + PhoneNumber: this.phoneNumber, + EmailAddress: this.emailAddress, + }, + }); + + $.export("$summary", "Address is valid"); + + return { + valid: true, + }; + }, +}; diff --git a/components/monta/common/constants.mjs b/components/monta/common/constants.mjs new file mode 100644 index 0000000000000..54fd5ff46ee21 --- /dev/null +++ b/components/monta/common/constants.mjs @@ -0,0 +1,21 @@ +const ORDER_UPDATED_SINCE_STATUSES = [ + "NonDeleted", + "Deleted", +]; + +const MIN_PAGE_SIZE = 1; +const MAX_PAGE_SIZE = 1000; +const DEFAULT_PAGE_SIZE = 30; + +const LABEL_FILE_TYPES = [ + "pdf", + "zpl", +]; + +export default { + ORDER_UPDATED_SINCE_STATUSES, + MIN_PAGE_SIZE, + MAX_PAGE_SIZE, + DEFAULT_PAGE_SIZE, + LABEL_FILE_TYPES, +}; diff --git a/components/monta/common/utils.mjs b/components/monta/common/utils.mjs new file mode 100644 index 0000000000000..4287319514d0d --- /dev/null +++ b/components/monta/common/utils.mjs @@ -0,0 +1,25 @@ +import { ConfigurationError } from "@pipedream/platform"; + +/** + * Parses an array of JSON-object props, validating each is a plain object. + * + * @param {Array} items - The prop values (JSON strings or objects) + * @param {string} label - A human-readable name for the field, used in errors + * @returns {Array} The parsed objects + */ +export function parseJsonObjects(items, label) { + return items.map((item) => { + let parsed = item; + if (typeof item === "string") { + try { + parsed = JSON.parse(item); + } catch (error) { + throw new ConfigurationError(`Each ${label} must be a valid JSON object. Could not parse: \`${item}\``); + } + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new ConfigurationError(`Each ${label} must be a JSON object.`); + } + return parsed; + }); +} diff --git a/components/monta/monta.app.mjs b/components/monta/monta.app.mjs index 4eee81972d3c3..852598e733071 100644 --- a/components/monta/monta.app.mjs +++ b/components/monta/monta.app.mjs @@ -34,6 +34,121 @@ export default { })); }, }, + reference: { + type: "string", + label: "Reference", + description: "The reference of the inbound forecast group (e.g. `PO-12345`). Use the **List Inbound Forecast Groups** action to find available references.", + }, + sku: { + type: "string", + label: "SKU", + description: "A product SKU (e.g. `ABC-123`)", + }, + inboundForecasts: { + type: "string[]", + label: "Inbound Forecasts", + description: "The forecasts in the group. Each entry is a JSON object with `Sku`, `Quantity`, and a `DeliveryDate` (ISO 8601), e.g. `{\"Sku\":\"ABC-123\",\"Quantity\":10,\"DeliveryDate\":\"2026-07-31T00:00:00Z\"}`", + }, + supplierCode: { + type: "string", + label: "Supplier Code", + description: "The code of the supplier delivering this inbound", + }, + comment: { + type: "string", + label: "Comment", + description: "A comment for the inbound forecast group", + }, + warehouseDisplayName: { + type: "string", + label: "Warehouse Display Name", + description: "The display name of the warehouse the inbound is expected at", + }, + allocateStockOnDelivery: { + type: "boolean", + label: "Allocate Stock On Delivery", + description: "Whether to allocate stock to backorders on delivery", + }, + expectedDeliveryDate: { + type: "string", + label: "Expected Delivery Date", + description: "The expected delivery date in ISO 8601 format (e.g. `2026-07-24T14:30:00Z`)", + }, + deliveryDate: { + type: "string", + label: "Delivery Date", + description: "The delivery date in ISO 8601 format (e.g. `2026-07-24T14:30:00Z`)", + }, + street: { + type: "string", + label: "Street", + description: "The street name", + }, + houseNumber: { + type: "string", + label: "House Number", + description: "The house number", + }, + houseNumberAddition: { + type: "string", + label: "House Number Addition", + description: "An addition to the house number", + }, + postalCode: { + type: "string", + label: "Postal Code", + description: "The postal code", + }, + city: { + type: "string", + label: "City", + description: "The city", + }, + state: { + type: "string", + label: "State", + description: "The state or province", + }, + countryCode: { + type: "string", + label: "Country Code", + description: "The ISO 3166-1 alpha-2 country code (e.g. `NL`)", + }, + company: { + type: "string", + label: "Company", + description: "The company name", + }, + firstName: { + type: "string", + label: "First Name", + description: "The recipient's first name", + }, + middleName: { + type: "string", + label: "Middle Name", + description: "The recipient's middle name", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The recipient's last name", + }, + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The recipient's phone number", + }, + emailAddress: { + type: "string", + label: "Email Address", + description: "The recipient's email address", + }, + additionalFields: { + type: "object", + label: "Additional Fields", + description: "Additional properties to send in the request body, using Monta's request-body casing", + }, }, methods: { _baseUrl() { @@ -55,7 +170,7 @@ export default { orderId, ...opts }) { return this._makeRequest({ - path: `/order/${orderId}`, + path: `/order/${encodeURIComponent(orderId)}`, ...opts, }); }, @@ -63,7 +178,7 @@ export default { returnId, ...opts }) { return this._makeRequest({ - path: `/return/${returnId}`, + path: `/return/${encodeURIComponent(returnId)}`, ...opts, }); }, @@ -77,7 +192,7 @@ export default { orderId, ...opts }) { return this._makeRequest({ - path: `/order/${orderId}/return`, + path: `/order/${encodeURIComponent(orderId)}/return`, ...opts, }); }, @@ -85,7 +200,7 @@ export default { orderId, ...opts }) { return this._makeRequest({ - path: `/order/${orderId}/events`, + path: `/order/${encodeURIComponent(orderId)}/events`, ...opts, }); }, @@ -104,5 +219,391 @@ export default { path: `/product/updated_since/${encodeURIComponent(updatedSince)}`, }); }, + /** + * Lists inbound shipments expected at the warehouse. + * + * @param {object} [opts] - Request options (e.g. `params.sinceid`) + * @returns {Promise} The list of inbounds + */ + listInbounds(opts = {}) { + return this._makeRequest({ + path: "/inbounds", + ...opts, + }); + }, + /** + * Lists inbound forecast groups matching the provided filters. + * + * @param {object} [opts] - Request options (query filters under `params`) + * @returns {Promise} The list of inbound forecast groups + */ + listInboundForecastGroups(opts = {}) { + return this._makeRequest({ + path: "/inboundforecast/group", + ...opts, + }); + }, + /** + * Creates a new inbound forecast group. + * + * @param {object} opts - Request options (`data` holds the group payload) + * @returns {Promise} The created inbound forecast group + */ + createInboundForecastGroup(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/inboundforecast/group", + ...opts, + }); + }, + /** + * Retrieves a single inbound forecast group by its reference. + * + * @param {object} args - Request arguments + * @param {string} args.reference - The inbound forecast group reference + * @returns {Promise} The inbound forecast group + */ + getInboundForecastGroup({ + reference, ...opts + }) { + return this._makeRequest({ + path: `/inboundforecast/group/${encodeURIComponent(reference)}`, + ...opts, + }); + }, + /** + * Updates an existing inbound forecast group by its reference. + * + * @param {object} args - Request arguments + * @param {string} args.reference - The inbound forecast group reference + * @returns {Promise} The updated inbound forecast group + */ + updateInboundForecastGroup({ + reference, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/inboundforecast/group/${encodeURIComponent(reference)}`, + ...opts, + }); + }, + /** + * Deletes an inbound forecast group (or a single SKU within it). + * + * @param {object} args - Request arguments + * @param {string} args.reference - The inbound forecast group reference + * @returns {Promise} Empty response on success + */ + deleteInboundForecastGroup({ + reference, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/inboundforecast/group/${encodeURIComponent(reference)}`, + ...opts, + }); + }, + /** + * Retrieves a single inbound forecast by group reference and SKU. + * + * @param {object} args - Request arguments + * @param {string} args.reference - The inbound forecast group reference + * @param {string} args.sku - The product SKU + * @returns {Promise} The inbound forecast + */ + getInboundForecast({ + reference, sku, ...opts + }) { + return this._makeRequest({ + path: `/inboundforecast/group/${encodeURIComponent(reference)}/${encodeURIComponent(sku)}`, + ...opts, + }); + }, + /** + * Lists all inbound forecasts for a given product SKU. + * + * @param {object} args - Request arguments + * @param {string} args.productSku - The product SKU + * @returns {Promise} The list of inbound forecasts + */ + listInboundForecastsByProductSku({ + productSku, ...opts + }) { + return this._makeRequest({ + path: `/inboundforecast/group/byproductsku/${encodeURIComponent(productSku)}`, + ...opts, + }); + }, + /** + * Approves multiple inbound forecasts by their IDs. + * + * @param {object} opts - Request options (`data` is an array of IDs) + * @returns {Promise} Whether the approval succeeded + */ + approveInboundForecasts(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/inboundforecast/approve", + ...opts, + }); + }, + /** + * Lists inbound forecast events created after the provided event ID. + * + * @param {object} args - Request arguments + * @param {number} args.id - The event ID to fetch events after + * @returns {Promise} The list of inbound forecast events + */ + listInboundForecastEvents({ + id, ...opts + }) { + return this._makeRequest({ + path: `/inboundforecast/events/since_id/${id}`, + ...opts, + }); + }, + /** + * Creates a new order. + * + * @param {object} opts - Request options (`data` holds the order payload) + * @returns {Promise} The created order + */ + createOrder(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/order", + ...opts, + }); + }, + /** + * Updates an existing order (e.g. to change the delivery address). + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The updated order + */ + updateOrder({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/order/${encodeURIComponent(orderId)}`, + ...opts, + }); + }, + /** + * Cancels (deletes) an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} Empty response on success + */ + cancelOrder({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/order/${encodeURIComponent(orderId)}`, + ...opts, + }); + }, + /** + * Validates a delivery address. + * + * @param {object} opts - Request options (`data` holds the address payload) + * @returns {Promise} Empty response when the address is valid + */ + validateAddress(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/address", + ...opts, + }); + }, + /** + * Anonymizes (forgets) an order for GDPR erasure. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} Empty response on success + */ + forgetOrder({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/order/${encodeURIComponent(orderId)}/forget`, + ...opts, + }); + }, + /** + * Lists the colli (parcels) of an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The order colli details + */ + listOrderColli({ + orderId, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/colli`, + ...opts, + }); + }, + /** + * Adds a collo (parcel) to an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The created collo + */ + createOrderColli({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/order/${encodeURIComponent(orderId)}/colli`, + ...opts, + }); + }, + /** + * Lists the shipping labels of an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The list of shipping labels + */ + listShippingLabels({ + orderId, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/shippinglabels`, + ...opts, + }); + }, + /** + * Creates a shipping label for an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The created shipping labels + */ + createShippingLabel({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/order/${encodeURIComponent(orderId)}/shippinglabels`, + ...opts, + }); + }, + /** + * Downloads a single shipping label file for an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @param {string} args.filename - The shipping label file name + * @returns {Promise} The shipping label file contents + */ + downloadShippingLabel({ + orderId, filename, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/shippinglabels/${encodeURIComponent(filename)}`, + ...opts, + }); + }, + /** + * Lists the batches of an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The order batch details + */ + listOrderBatches({ + orderId, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/batches`, + ...opts, + }); + }, + /** + * Lists orders whose status changed since the provided date and time. + * + * @param {object} args - Request arguments + * @param {string} args.updatedSince - ISO 8601 start date and time + * @returns {Promise} The Monta response containing updated orders + */ + listUpdatedOrders({ + updatedSince, ...opts + }) { + return this._makeRequest({ + path: `/order/updated_since/${encodeURIComponent(updatedSince)}`, + ...opts, + }); + }, + /** + * Lists order events created after the provided event ID. + * + * @param {object} args - Request arguments + * @param {number} args.id - The event ID to fetch events after + * @returns {Promise} The list of order events + */ + listOrderEventsSinceId({ + id, ...opts + }) { + return this._makeRequest({ + path: `/orderevents/since_id/${id}`, + ...opts, + }); + }, + /** + * Lists the return forecasts of an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The list of return forecasts + */ + listReturnForecasts({ + orderId, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/returnforecasts`, + ...opts, + }); + }, + /** + * Lists the return labels of an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The list of return labels + */ + listReturnLabels({ + orderId, ...opts + }) { + return this._makeRequest({ + path: `/order/${encodeURIComponent(orderId)}/returnlabels`, + ...opts, + }); + }, + /** + * Creates an RMA link for an order. + * + * @param {object} args - Request arguments + * @param {string} args.orderId - The webshop order ID + * @returns {Promise} The created RMA link + */ + createRmaLink({ + orderId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/order/${encodeURIComponent(orderId)}/rmalinks`, + ...opts, + }); + }, }, }; diff --git a/components/monta/package.json b/components/monta/package.json index f61d5952f479f..fa0db2fd294cf 100644 --- a/components/monta/package.json +++ b/components/monta/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/monta", - "version": "0.3.0", + "version": "0.4.0", "description": "Pipedream Monta Components", "main": "monta.app.mjs", "keywords": [