From cc07a4e06a6ab0dd90cea3a60d1e99194f2b6143 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 2 Jan 2025 17:27:47 -0300 Subject: [PATCH 1/5] alegra init --- .../actions/create-contact/create-contact.mjs | 150 ++++++ .../actions/create-invoice/create-invoice.mjs | 168 +++++++ .../actions/find-contact/find-contact.mjs | 25 + components/alegra/alegra.app.mjs | 433 +++++++++++++++++- .../alegra/sources/new-client/new-client.mjs | 70 +++ .../sources/new-invoice/new-invoice.mjs | 90 ++++ .../alegra/sources/new-item/new-item.mjs | 79 ++++ 7 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 components/alegra/actions/create-contact/create-contact.mjs create mode 100644 components/alegra/actions/create-invoice/create-invoice.mjs create mode 100644 components/alegra/actions/find-contact/find-contact.mjs create mode 100644 components/alegra/sources/new-client/new-client.mjs create mode 100644 components/alegra/sources/new-invoice/new-invoice.mjs create mode 100644 components/alegra/sources/new-item/new-item.mjs diff --git a/components/alegra/actions/create-contact/create-contact.mjs b/components/alegra/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..596735ae65a40 --- /dev/null +++ b/components/alegra/actions/create-contact/create-contact.mjs @@ -0,0 +1,150 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-create-contact", + name: "Create Contact", + description: "Adds a new contact to Alegra. [See the documentation]().", + version: "0.0.{{ts}}", + type: "action", + props: { + alegra, + name: { + propDefinition: [ + "alegra", + "name", + ], + }, + identification: { + propDefinition: [ + "alegra", + "identification", + ], + optional: true, + }, + address: { + propDefinition: [ + "alegra", + "address", + ], + optional: true, + }, + city: { + propDefinition: [ + "alegra", + "city", + ], + optional: true, + }, + phonePrimary: { + propDefinition: [ + "alegra", + "phonePrimary", + ], + optional: true, + }, + phoneSecondary: { + propDefinition: [ + "alegra", + "phoneSecondary", + ], + optional: true, + }, + mobile: { + propDefinition: [ + "alegra", + "mobile", + ], + optional: true, + }, + email: { + propDefinition: [ + "alegra", + "email", + ], + optional: true, + }, + type: { + propDefinition: [ + "alegra", + "type", + ], + optional: true, + }, + status: { + propDefinition: [ + "alegra", + "status", + ], + optional: true, + }, + fax: { + propDefinition: [ + "alegra", + "fax", + ], + optional: true, + }, + debtToPay: { + propDefinition: [ + "alegra", + "debtToPay", + ], + optional: true, + }, + accountReceivable: { + propDefinition: [ + "alegra", + "accountReceivable", + ], + optional: true, + }, + internalContacts: { + propDefinition: [ + "alegra", + "internalContacts", + ], + optional: true, + }, + ignoreRepeated: { + propDefinition: [ + "alegra", + "ignoreRepeated", + ], + optional: true, + }, + statementAttached: { + propDefinition: [ + "alegra", + "statementAttached", + ], + optional: true, + }, + seller: { + propDefinition: [ + "alegra", + "sellerContact", + ], + optional: true, + }, + priceList: { + propDefinition: [ + "alegra", + "priceListContact", + ], + optional: true, + }, + term: { + propDefinition: [ + "alegra", + "termContact", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.alegra.createContact(); + $.export("$summary", `Created contact with ID ${response.id}`); + return response; + }, +}; diff --git a/components/alegra/actions/create-invoice/create-invoice.mjs b/components/alegra/actions/create-invoice/create-invoice.mjs new file mode 100644 index 0000000000000..a08fcebda7952 --- /dev/null +++ b/components/alegra/actions/create-invoice/create-invoice.mjs @@ -0,0 +1,168 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-create-invoice", + name: "Create Invoice", + description: "Creates a new invoice in Alegra. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + alegra, + items: { + propDefinition: [ + "alegra", + "items", + ], + }, + dueDate: { + propDefinition: [ + "alegra", + "dueDate", + ], + }, + date: { + propDefinition: [ + "alegra", + "date", + ], + }, + client: { + propDefinition: [ + "alegra", + "client", + ], + }, + status: { + propDefinition: [ + "alegra", + "statusInvoice", + ], + optional: true, + }, + numberTemplateId: { + propDefinition: [ + "alegra", + "numberTemplateId", + ], + optional: true, + }, + numberTemplatePrefix: { + propDefinition: [ + "alegra", + "numberTemplatePrefix", + ], + optional: true, + }, + numberTemplateNumber: { + propDefinition: [ + "alegra", + "numberTemplateNumber", + ], + optional: true, + }, + payments: { + propDefinition: [ + "alegra", + "payments", + ], + optional: true, + }, + estimate: { + propDefinition: [ + "alegra", + "estimate", + ], + optional: true, + }, + termsConditions: { + propDefinition: [ + "alegra", + "termsConditions", + ], + optional: true, + }, + annotation: { + propDefinition: [ + "alegra", + "annotation", + ], + optional: true, + }, + observations: { + propDefinition: [ + "alegra", + "observations", + ], + optional: true, + }, + seller: { + propDefinition: [ + "alegra", + "sellerInvoice", + ], + optional: true, + }, + pricelist: { + propDefinition: [ + "alegra", + "priceListInvoice", + ], + optional: true, + }, + currency: { + propDefinition: [ + "alegra", + "currency", + ], + optional: true, + }, + retentions: { + propDefinition: [ + "alegra", + "retentions", + ], + optional: true, + }, + warehouse: { + propDefinition: [ + "alegra", + "warehouse", + ], + optional: true, + }, + remissions: { + propDefinition: [ + "alegra", + "remissions", + ], + optional: true, + }, + costCenter: { + propDefinition: [ + "alegra", + "costCenter", + ], + optional: true, + }, + comments: { + propDefinition: [ + "alegra", + "comments", + ], + optional: true, + }, + periodicity: { + propDefinition: [ + "alegra", + "periodicity", + ], + optional: true, + }, + }, + async run({ $ }) { + const invoice = await this.alegra.generateInvoice(); + $.export("$summary", `Created invoice with ID ${invoice.id}`); + return invoice; + }, +}; diff --git a/components/alegra/actions/find-contact/find-contact.mjs b/components/alegra/actions/find-contact/find-contact.mjs new file mode 100644 index 0000000000000..0a1a0c52abd43 --- /dev/null +++ b/components/alegra/actions/find-contact/find-contact.mjs @@ -0,0 +1,25 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-find-contact", + name: "Find Contact", + description: "Search for an existing contact in Alegra based on email, phone number, or name. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + alegra, + query: { + propDefinition: [ + "alegra", + "query", + ], + }, + }, + async run({ $ }) { + const response = await this.alegra.searchContact(); + const contacts = response.items; + $.export("$summary", `Found ${contacts.length} contact(s) matching your query`); + return contacts; + }, +}; diff --git a/components/alegra/alegra.app.mjs b/components/alegra/alegra.app.mjs index 94de318ca6c35..a231581f12300 100644 --- a/components/alegra/alegra.app.mjs +++ b/components/alegra/alegra.app.mjs @@ -1,11 +1,440 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "alegra", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + // Create Contact + name: { + type: "string", + label: "Name", + description: "Name of the contact", + }, + identification: { + type: "string", + label: "Identification", + description: "Identification of the contact", + optional: true, + }, + address: { + type: "string", + label: "Address", + description: "Address of the contact", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "City of the contact", + optional: true, + }, + phonePrimary: { + type: "string", + label: "Primary Phone", + description: "Primary phone number of the contact", + optional: true, + }, + phoneSecondary: { + type: "string", + label: "Secondary Phone", + description: "Secondary phone number of the contact", + optional: true, + }, + mobile: { + type: "string", + label: "Mobile", + description: "Mobile phone number of the contact", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "Email of the contact", + optional: true, + }, + type: { + type: "string", + label: "Type", + description: "Type of the contact", + optional: true, + }, + status: { + type: "string", + label: "Status", + description: "Status of the contact", + optional: true, + }, + fax: { + type: "string", + label: "Fax", + description: "Fax number of the contact", + optional: true, + }, + debtToPay: { + type: "integer", + label: "Debt to Pay", + description: "Debt to pay for the contact", + optional: true, + }, + accountReceivable: { + type: "integer", + label: "Account Receivable", + description: "Account receivable associated with the contact", + optional: true, + }, + internalContacts: { + type: "string", + label: "Internal Contacts", + description: "Internal contacts related to the contact", + optional: true, + }, + ignoreRepeated: { + type: "boolean", + label: "Ignore Repeated", + description: "Ignore repeated contacts", + optional: true, + }, + statementAttached: { + type: "boolean", + label: "Statement Attached", + description: "Whether statement is attached", + optional: true, + }, + sellerContact: { + type: "string", + label: "Seller", + description: "Seller associated with the contact", + optional: true, + async options() { + const sellersResponse = await this.getSellers(); + return sellersResponse.items.map((seller) => ({ + label: seller.name, + value: seller.id, + })); + }, + }, + priceListContact: { + type: "string", + label: "Price List", + description: "Price list associated with the contact", + optional: true, + async options() { + const priceListsResponse = await this.getPriceLists(); + return priceListsResponse.items.map((priceList) => ({ + label: priceList.name, + value: priceList.id, + })); + }, + }, + termContact: { + type: "string", + label: "Term", + description: "Payment terms associated with the contact", + optional: true, + async options() { + const termsResponse = await this.getTerms(); + return termsResponse.items.map((term) => ({ + label: term.name, + value: term.id, + })); + }, + }, + + // Create Invoice + items: { + type: "string[]", + label: "Items", + description: "Array of items in JSON format", + }, + dueDate: { + type: "string", + label: "Due Date", + description: "Due date of the invoice (YYYY-MM-DD)", + }, + date: { + type: "string", + label: "Date", + description: "Date of the invoice (YYYY-MM-DD)", + }, + client: { + type: "string", + label: "Client", + description: "Client ID associated with the invoice", + }, + statusInvoice: { + type: "string", + label: "Status", + description: "Status of the invoice", + optional: true, + }, + numberTemplateId: { + type: "string", + label: "Number Template ID", + description: "Number template ID for the invoice", + optional: true, + }, + numberTemplatePrefix: { + type: "string", + label: "Number Template Prefix", + description: "Prefix for the number template", + optional: true, + }, + numberTemplateNumber: { + type: "string", + label: "Number Template Number", + description: "Number part of the number template", + optional: true, + }, + payments: { + type: "string[]", + label: "Payments", + description: "Array of payments in JSON format", + optional: true, + }, + estimate: { + type: "string", + label: "Estimate", + description: "Estimate associated with the invoice", + optional: true, + }, + termsConditions: { + type: "string", + label: "Terms & Conditions", + description: "Terms and conditions of the invoice", + optional: true, + }, + annotation: { + type: "string", + label: "Annotation", + description: "Annotation for the invoice", + optional: true, + }, + observations: { + type: "string", + label: "Observations", + description: "Observations for the invoice", + optional: true, + }, + sellerInvoice: { + type: "string", + label: "Seller", + description: "Seller associated with the invoice", + optional: true, + async options() { + const sellersResponse = await this.getSellers(); + return sellersResponse.items.map((seller) => ({ + label: seller.name, + value: seller.id, + })); + }, + }, + priceListInvoice: { + type: "string", + label: "Price List", + description: "Price list associated with the invoice", + optional: true, + async options() { + const priceListsResponse = await this.getPriceLists(); + return priceListsResponse.items.map((priceList) => ({ + label: priceList.name, + value: priceList.id, + })); + }, + }, + currency: { + type: "string", + label: "Currency", + description: "Currency for the invoice", + optional: true, + }, + retentions: { + type: "string[]", + label: "Retentions", + description: "Array of retentions in JSON format", + optional: true, + }, + warehouse: { + type: "string", + label: "Warehouse", + description: "Warehouse associated with the invoice", + optional: true, + }, + remissions: { + type: "string[]", + label: "Remissions", + description: "Array of remissions in JSON format", + optional: true, + }, + costCenter: { + type: "string", + label: "Cost Center", + description: "Cost center associated with the invoice", + optional: true, + }, + comments: { + type: "string", + label: "Comments", + description: "Comments for the invoice", + optional: true, + }, + periodicity: { + type: "string", + label: "Periodicity", + description: "Periodicity of the invoice", + optional: true, + }, + + // Search Contact + query: { + type: "string", + label: "Query", + description: "Search query for contacting (email, phone, or name)", + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.alegra.com/api/v1"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers = {}, ...otherOpts + } = opts; + return axios($, { + method, + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + "Authorization": `Basic ${Buffer.from(this.$auth.api_key + ":").toString("base64")}`, + "Content-Type": "application/json", + }, + ...otherOpts, + }); + }, + async createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + data: { + name: this.name, + identification: this.identification, + address: this.address, + city: this.city, + phonePrimary: this.phonePrimary, + phoneSecondary: this.phoneSecondary, + mobile: this.mobile, + seller: this.sellerContact, + priceList: this.priceListContact, + term: this.termContact, + email: this.email, + type: this.type, + status: this.status, + fax: this.fax, + debtToPay: this.debtToPay, + accountReceivable: this.accountReceivable, + internalContacts: this.internalContacts, + ignoreRepeated: this.ignoreRepeated, + statementAttached: this.statementAttached, + }, + ...opts, + }); + }, + async generateInvoice(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/invoices", + data: { + items: this.items.map((item) => JSON.parse(item)), + dueDate: this.dueDate, + date: this.date, + client: this.client, + status: this.statusInvoice, + numberTemplateId: this.numberTemplateId, + numberTemplatePrefix: this.numberTemplatePrefix, + numberTemplateNumber: this.numberTemplateNumber, + payments: this.payments + ? this.payments.map((payment) => JSON.parse(payment)) + : undefined, + estimate: this.estimate, + termsConditions: this.termsConditions, + annotation: this.annotation, + observations: this.observations, + seller: this.sellerInvoice, + priceList: this.priceListInvoice, + currency: this.currency, + retentions: this.retentions + ? this.retentions.map((retention) => JSON.parse(retention)) + : undefined, + warehouse: this.warehouse, + remissions: this.remissions + ? this.remissions.map((remission) => JSON.parse(remission)) + : undefined, + costCenter: this.costCenter, + comments: this.comments, + periodicity: this.periodicity, + }, + ...opts, + }); + }, + async searchContact(opts = {}) { + return this._makeRequest({ + path: "/contacts", + params: { + query: this.query, + }, + ...opts, + }); + }, + async getSellers(opts = {}) { + return this._makeRequest({ + path: "/sellers", + ...opts, + }); + }, + async getPriceLists(opts = {}) { + return this._makeRequest({ + path: "/price-lists", + ...opts, + }); + }, + async getTerms(opts = {}) { + return this._makeRequest({ + path: "/terms", + ...opts, + }); + }, + async createWebhookSubscription({ + event, url, + }) { + return this._makeRequest({ + method: "POST", + path: "/webhooks/subscriptions", + data: { + event, + url, + }, + }); + }, + async paginate(fn, ...opts) { + const results = []; + let page = 0; + let more = true; + while (more) { + const response = await fn({ + ...opts, + page, + }); + if (!response || response.length === 0) { + more = false; + } else { + results.push(...response); + page += 1; + } + } + return results; + }, }, }; diff --git a/components/alegra/sources/new-client/new-client.mjs b/components/alegra/sources/new-client/new-client.mjs new file mode 100644 index 0000000000000..de4251ed4a29c --- /dev/null +++ b/components/alegra/sources/new-client/new-client.mjs @@ -0,0 +1,70 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-new-client", + name: "New Client Created", + description: "Emit new event when a brand new client is created. [See the documentation](https://developer.alegra.com/reference/post_webhooks-subscriptions)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + alegra: { + type: "app", + app: "alegra", + }, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + }, + hooks: { + async activate() { + const subscription = await this.alegra.createWebhookSubscription({ + event: "new-client", + url: this.http.endpoint, + }); + await this.db.set("webhook_subscription_id", subscription.id); + }, + async deactivate() { + const subscriptionId = await this.db.get("webhook_subscription_id"); + if (subscriptionId) { + await this.alegra.deleteWebhook(subscriptionId); + await this.db.set("webhook_subscription_id", null); + } + }, + async deploy() { + const response = await this.alegra._makeRequest({ + path: "/contacts", + params: { + limit: 50, + sort: "createdAt", + order: "desc", + }, + }); + const contacts = response.items; + contacts.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); + for (const contact of contacts) { + this.$emit(contact, { + id: contact.id, + summary: `New client created: ${contact.name}`, + ts: new Date(contact.createdAt).getTime(), + }); + } + }, + }, + async run(event) { + const client = event.body; + const id = client.id || `${Date.now()}`; + const summary = `New client created: ${client.name || "Unnamed client"}`; + const ts = client.createdAt + ? new Date(client.createdAt).getTime() + : Date.now(); + this.$emit(client, { + id, + summary, + ts, + }); + }, +}; diff --git a/components/alegra/sources/new-invoice/new-invoice.mjs b/components/alegra/sources/new-invoice/new-invoice.mjs new file mode 100644 index 0000000000000..286623e13d907 --- /dev/null +++ b/components/alegra/sources/new-invoice/new-invoice.mjs @@ -0,0 +1,90 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-new-invoice", + name: "New Invoice", + description: "Emit a new event when a new invoice is created. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + alegra: { + type: "app", + app: "alegra", + }, + http: { + type: "$.interface.http", + customResponse: false, + }, + db: "$.service.db", + }, + hooks: { + async activate() { + try { + const webhookResponse = await this.alegra.createWebhookSubscription({ + event: "new-invoice", + url: this.http.endpoint, + }); + + if (webhookResponse.id) { + await this.db.set("webhookId", webhookResponse.id); + } else { + throw new Error("Failed to retrieve webhook subscription ID."); + } + } catch (error) { + console.error("Error during activate:", error); + throw error; + } + }, + async deactivate() { + try { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.alegra.deleteWebhookSubscription(webhookId); + await this.db.delete("webhookId"); + } + } catch (error) { + console.error("Error during deactivate:", error); + } + }, + async deploy() { + try { + const invoices = await this.alegra.paginate(this.alegra.listInvoices.bind(this.alegra), { + perPage: 50, + }); + const recentInvoices = invoices.slice(-50); + for (const invoice of recentInvoices) { + this.$emit(invoice, { + id: invoice.id, + summary: `New invoice created: #${invoice.id}`, + ts: Date.parse(invoice.date) || Date.now(), + }); + } + } catch (error) { + console.error("Error during deploy:", error); + } + }, + }, + async run(event) { + try { + const invoice = event.body; + + if (!invoice || !invoice.id) { + throw new Error("Invalid invoice data received."); + } + + this.$emit(invoice, { + id: invoice.id, + summary: `New invoice created: #${invoice.id}`, + ts: Date.parse(invoice.date) || Date.now(), + }); + } catch (error) { + console.error("Error during run:", error); + this.http.respond({ + status: 400, + body: "Bad Request", + }); + } + }, +}; diff --git a/components/alegra/sources/new-item/new-item.mjs b/components/alegra/sources/new-item/new-item.mjs new file mode 100644 index 0000000000000..2c7dcf56cc2cc --- /dev/null +++ b/components/alegra/sources/new-item/new-item.mjs @@ -0,0 +1,79 @@ +import alegra from "../../alegra.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "alegra-new-item", + name: "New Item Added", + description: "Emit new event each time a new item is added. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + alegra: { + type: "app", + app: "alegra", + }, + db: "$.service.db", + http: { + type: "$.interface.http", + }, + }, + hooks: { + async activate() { + const webhook = await this.alegra.createWebhookSubscription({ + event: "new-item", + url: this.http.endpoint, + }); + this.db.set("webhookId", webhook.subscription.id); + }, + async deactivate() { + const webhookId = await this.db.get("webhookId"); + if (webhookId) { + await this.alegra._makeRequest({ + method: "DELETE", + path: `/webhooks/subscriptions/${webhookId}`, + }); + await this.db.delete("webhookId"); + } + }, + async deploy() { + try { + const items = await this.alegra.paginate( + async (opts) => this.alegra._makeRequest(opts), + { + path: "/items", + params: { + limit: 50, + sort: "-id", + }, + }, + ); + const last50 = items.slice(-50); + for (const item of last50) { + this.$emit(item, { + id: item.id, + summary: `New item added: ${item.name}`, + ts: Date.parse(item.createdAt) || Date.now(), + }); + } + } catch (error) { + console.error("Error deploying alegra-new-item source:", error); + } + }, + }, + async run(event) { + const item = event.body; + const ts = Date.parse(item.createdAt) || Date.now(); + const id = item.id + ? String(item.id) + : String(ts); + const summary = item.name + ? `New item added: ${item.name}` + : "New item added"; + this.$emit(item, { + id, + summary, + ts, + }); + }, +}; From 90547a804ec42f67922fae5960c61bbcf641516a Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 6 Jan 2025 10:48:02 -0300 Subject: [PATCH 2/5] [Components] alegra #13261 Sources - New Client (Instant) - New Invoice (Instant) - New Item (Instant) Actions - Create Contact - Create Invoice - Find Contact --- .../actions/create-contact/create-contact.mjs | 160 +++--- .../actions/create-invoice/create-invoice.mjs | 205 ++++---- .../actions/find-contact/find-contact.mjs | 20 +- components/alegra/alegra.app.mjs | 497 ++++++------------ components/alegra/common/constants.mjs | 25 + components/alegra/common/utils.mjs | 24 + components/alegra/package.json | 18 + components/alegra/sources/common/base.mjs | 48 ++ .../new-client-instant/new-client-instant.mjs | 22 + .../sources/new-client-instant/test-event.mjs | 31 ++ .../alegra/sources/new-client/new-client.mjs | 70 --- .../new-invoice-instant.mjs | 22 + .../new-invoice-instant/test-event.mjs | 49 ++ .../sources/new-invoice/new-invoice.mjs | 90 ---- .../new-item-instant/new-item-instant.mjs | 22 + .../sources/new-item-instant/test-event.mjs | 55 ++ .../alegra/sources/new-item/new-item.mjs | 79 --- 17 files changed, 685 insertions(+), 752 deletions(-) create mode 100644 components/alegra/common/constants.mjs create mode 100644 components/alegra/common/utils.mjs create mode 100644 components/alegra/package.json create mode 100644 components/alegra/sources/common/base.mjs create mode 100644 components/alegra/sources/new-client-instant/new-client-instant.mjs create mode 100644 components/alegra/sources/new-client-instant/test-event.mjs delete mode 100644 components/alegra/sources/new-client/new-client.mjs create mode 100644 components/alegra/sources/new-invoice-instant/new-invoice-instant.mjs create mode 100644 components/alegra/sources/new-invoice-instant/test-event.mjs delete mode 100644 components/alegra/sources/new-invoice/new-invoice.mjs create mode 100644 components/alegra/sources/new-item-instant/new-item-instant.mjs create mode 100644 components/alegra/sources/new-item-instant/test-event.mjs delete mode 100644 components/alegra/sources/new-item/new-item.mjs diff --git a/components/alegra/actions/create-contact/create-contact.mjs b/components/alegra/actions/create-contact/create-contact.mjs index 596735ae65a40..7dedc816f1e1c 100644 --- a/components/alegra/actions/create-contact/create-contact.mjs +++ b/components/alegra/actions/create-contact/create-contact.mjs @@ -1,149 +1,171 @@ import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; +import { + STATUS_OPTIONS, + TYPE_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; export default { key: "alegra-create-contact", name: "Create Contact", - description: "Adds a new contact to Alegra. [See the documentation]().", - version: "0.0.{{ts}}", + description: "Adds a new contact to Alegra. [See the documentation](https://developer.alegra.com/reference/post_contacts).", + version: "0.0.1", type: "action", props: { alegra, name: { - propDefinition: [ - "alegra", - "name", - ], + type: "string", + label: "Name", + description: "Name of the contact", }, identification: { - propDefinition: [ - "alegra", - "identification", - ], + type: "string", + label: "Identification", + description: "Identification of the contact", optional: true, }, address: { - propDefinition: [ - "alegra", - "address", - ], + type: "string", + label: "Address", + description: "Address of the contact", optional: true, }, city: { - propDefinition: [ - "alegra", - "city", - ], + type: "string", + label: "City", + description: "City of the contact", optional: true, }, phonePrimary: { - propDefinition: [ - "alegra", - "phonePrimary", - ], + type: "string", + label: "Primary Phone", + description: "Primary phone number of the contact", optional: true, }, phoneSecondary: { - propDefinition: [ - "alegra", - "phoneSecondary", - ], + type: "string", + label: "Secondary Phone", + description: "Secondary phone number of the contact", optional: true, }, mobile: { - propDefinition: [ - "alegra", - "mobile", - ], + type: "string", + label: "Mobile", + description: "Mobile phone number of the contact", optional: true, }, email: { - propDefinition: [ - "alegra", - "email", - ], + type: "string", + label: "Email", + description: "Email of the contact", optional: true, }, type: { - propDefinition: [ - "alegra", - "type", - ], + type: "string", + label: "Type", + description: "Type of the contact", + options: TYPE_OPTIONS, optional: true, }, status: { - propDefinition: [ - "alegra", - "status", - ], + type: "string", + label: "Status", + description: "Status of the contact", + options: STATUS_OPTIONS, optional: true, }, fax: { - propDefinition: [ - "alegra", - "fax", - ], + type: "string", + label: "Fax", + description: "Fax number of the contact", optional: true, }, debtToPay: { propDefinition: [ - "alegra", + alegra, "debtToPay", ], optional: true, }, accountReceivable: { propDefinition: [ - "alegra", - "accountReceivable", + alegra, + "debtToPay", ], + label: "Account Receivable", + description: "Id of the account receivable associated with the contact", optional: true, }, internalContacts: { - propDefinition: [ - "alegra", - "internalContacts", - ], + type: "string[]", + label: "Internal Contacts", + description: "A list of objects of internal contacts related to the contact. **Example: [ { name: \"John Doe\", email: \"john@email.com\"}]**. [See the documentation](https://developer.alegra.com/reference/post_contacts) for further information.", optional: true, }, ignoreRepeated: { - propDefinition: [ - "alegra", - "ignoreRepeated", - ], + type: "boolean", + label: "Ignore Repeated", + description: "Ignore repeated contacts", optional: true, }, statementAttached: { - propDefinition: [ - "alegra", - "statementAttached", - ], + type: "boolean", + label: "Statement Attached", + description: "Indicates whether to include a statement for the contact", optional: true, }, seller: { propDefinition: [ - "alegra", - "sellerContact", + alegra, + "seller", ], optional: true, }, priceList: { propDefinition: [ - "alegra", - "priceListContact", + alegra, + "priceList", ], optional: true, }, term: { propDefinition: [ - "alegra", - "termContact", + alegra, + "term", ], optional: true, }, }, async run({ $ }) { - const response = await this.alegra.createContact(); + const { + alegra, + city, + address, + debtToPay, + accountReceivable, + internalContacts, + statementAttached, + ...data + } = this; + + const response = await alegra.createContact({ + $, + data: { + ...data, + address: { + city, + address, + }, + accounting: { + debtToPay, + accountReceivable, + }, + internalContacts: parseObject(internalContacts), + statementAttached: statementAttached + ? "yes" + : "no", + }, + }); $.export("$summary", `Created contact with ID ${response.id}`); return response; }, diff --git a/components/alegra/actions/create-invoice/create-invoice.mjs b/components/alegra/actions/create-invoice/create-invoice.mjs index a08fcebda7952..563d691876428 100644 --- a/components/alegra/actions/create-invoice/create-invoice.mjs +++ b/components/alegra/actions/create-invoice/create-invoice.mjs @@ -1,168 +1,193 @@ +import { ConfigurationError } from "@pipedream/platform"; import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; +import { + INVOICE_STATUS_OPTIONS, PERIODICITY_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; export default { key: "alegra-create-invoice", name: "Create Invoice", description: "Creates a new invoice in Alegra. [See the documentation]()", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { alegra, - items: { - propDefinition: [ - "alegra", - "items", - ], - }, - dueDate: { - propDefinition: [ - "alegra", - "dueDate", - ], - }, - date: { - propDefinition: [ - "alegra", - "date", - ], - }, - client: { - propDefinition: [ - "alegra", - "client", - ], - }, status: { - propDefinition: [ - "alegra", - "statusInvoice", - ], + type: "string", + label: "Status", + description: "Status of the invoice. If this attribute is not sent and no associated payments are sent, the invoice is created in \"draft\". If payments are sent to the invoice, the invoice is created in \"open\".", + options: INVOICE_STATUS_OPTIONS, optional: true, }, numberTemplateId: { - propDefinition: [ - "alegra", - "numberTemplateId", - ], + type: "string", + label: "Number Template ID", + description: "Number template ID for the invoice. You can use this to automatically numbering.", optional: true, }, numberTemplatePrefix: { - propDefinition: [ - "alegra", - "numberTemplatePrefix", - ], + type: "string", + label: "Number Template Prefix", + description: "Number template prefix for the invoice. Send in case the numbering is manual. (Optional)", optional: true, }, numberTemplateNumber: { - propDefinition: [ - "alegra", - "numberTemplateNumber", - ], + type: "string", + label: "Number Template Number", + description: "Number template number for the invoice. Send in case the numbering is manual. (Required)", optional: true, }, + items: { + type: "string[]", + label: "Items", + description: "Array of item objects (products/services) associated with the invoice. **Example: [{\"id\": \"123\", \"name\": \"Name\", \"price\": \"12.00\", \"quantity\": \"2\"}]**. [See the documentation](https://developer.alegra.com/reference/post_invoices) for further information.", + }, payments: { - propDefinition: [ - "alegra", - "payments", - ], + type: "string[]", + label: "Payments", + description: "Array of objects indicating the payments made to the invoice. **Example: [{\"date\": \"YYYY-MM-DD\", \"account\": \"123123\", \"amount\": \"10,00\"}]**. [See the documentation](https://developer.alegra.com/reference/post_invoices) for further information.", optional: true, }, estimate: { - propDefinition: [ - "alegra", - "estimate", - ], + type: "string", + label: "Estimate", + description: "Specifies the identifier of the quote you want to associate with the sales invoice, in this way, the quote is invoiced and the items specified in the items parameter are associated, not those in the quote.", optional: true, }, termsConditions: { - propDefinition: [ - "alegra", - "termsConditions", - ], + type: "string", + label: "Terms and Conditions", + description: "Terms and conditions of the invoice. Maximum allowed length: 500.", optional: true, }, annotation: { - propDefinition: [ - "alegra", - "annotation", - ], + type: "string", + label: "Annotation", + description: "Invoice notes, visible in the PDF or printed document. Maximum allowed length: 500.", optional: true, }, + dueDate: { + type: "string", + label: "Due Date", + description: "Invoice due date. Format yyyy-MM-dd.", + }, + date: { + type: "string", + label: "Date", + description: "Invoice date. Format yyyy-MM-dd.", + }, observations: { + type: "string", + label: "Observations", + description: "Invoice observations (not visible in the PDF or printed document). Maximum allowed length: 500.", + optional: true, + }, + client: { propDefinition: [ - "alegra", - "observations", + alegra, + "client", ], - optional: true, }, seller: { propDefinition: [ - "alegra", - "sellerInvoice", + alegra, + "seller", ], + description: "Seller associated with the invoice.", optional: true, }, pricelist: { propDefinition: [ - "alegra", - "priceListInvoice", + alegra, + "priceList", ], + description: "Price list associated with the invoice", optional: true, }, currency: { - propDefinition: [ - "alegra", - "currency", - ], + type: "object", + label: "Currency", + description: "Object that includes the information of the currency and exchange rate associated with the invoice. It should only be included if the company has the multi-currency functionality active and has configured the selected currency. It must include the currency code (three letters according to ISO) and the exchange rate.", optional: true, }, retentions: { - propDefinition: [ - "alegra", - "retentions", - ], + type: "string[]", + label: "Retentions", + description: "Array of retention objects indicating the retentions of the sales invoice. **Example: [{\"id\": \"123123\", \"amount\": 10}]**. [See the documentation](https://developer.alegra.com/reference/post_invoices) for further information.", optional: true, }, warehouse: { propDefinition: [ - "alegra", + alegra, "warehouse", ], optional: true, }, remissions: { - propDefinition: [ - "alegra", - "remissions", - ], + type: "string[]", + label: "Remissions", + description: "Array of identifiers of the remissions to be invoiced, you can associate one or more remissions by simply indicating the id of each one in an array. The client of the remissions and the sales invoice must be the same. Only open remissions can be invoiced. In this way, the items of each remission will be invoiced, and you can also specify other items with the items parameter. **Example: [{\"id\": 123, \"items\": [{\"id\": 123}], }]**. [See the documentation](https://developer.alegra.com/reference/post_invoices) for further information.", optional: true, }, costCenter: { propDefinition: [ - "alegra", + alegra, "costCenter", ], optional: true, }, comments: { - propDefinition: [ - "alegra", - "comments", - ], + type: "string[]", + label: "Comments", + description: "Array of strings with each of the comments to be associated. Comments can be updated even if the sales invoice cannot be edited.", optional: true, }, periodicity: { - propDefinition: [ - "alegra", - "periodicity", - ], + type: "string", + label: "Periodicity", + description: "Indicates the periodicity of the payments of the invoice installments. If you want to issue the invoice, the payment method is on credit this attribute becomes mandatory.", + options: PERIODICITY_OPTIONS, optional: true, }, }, async run({ $ }) { - const invoice = await this.alegra.generateInvoice(); - $.export("$summary", `Created invoice with ID ${invoice.id}`); - return invoice; + try { + const invoice = await this.alegra.generateInvoice({ + $, + data: { + status: this.status, + numberTemplate: { + id: this.numberTemplateId, + prefix: this.numberTemplatePrefix, + number: this.numberTemplateNumber, + }, + items: parseObject(this.items), + payments: parseObject(this.payments), + estimate: this.estimate, + termsConditions: this.termsConditions, + annotation: this.annotation, + dueDate: this.dueDate, + date: this.date, + observations: this.observations, + client: { + id: this.client, + }, + seller: this.seller, + pricelist: this.pricelist, + currency: parseObject(this.currency), + retentions: parseObject(this.retentions), + warehouse: this.warehouse, + remissions: parseObject(this.remissions), + costCenter: this.costCenter, + comments: parseObject(this.comments), + periodicity: this.periodicity, + }, + }); + $.export("$summary", `Created invoice with ID ${invoice.id}`); + return invoice; + } catch (e) { + throw new ConfigurationError(e.response.data.message); + } }, }; diff --git a/components/alegra/actions/find-contact/find-contact.mjs b/components/alegra/actions/find-contact/find-contact.mjs index 0a1a0c52abd43..ec0e2dee1bf75 100644 --- a/components/alegra/actions/find-contact/find-contact.mjs +++ b/components/alegra/actions/find-contact/find-contact.mjs @@ -1,25 +1,29 @@ import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "alegra-find-contact", name: "Find Contact", - description: "Search for an existing contact in Alegra based on email, phone number, or name. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Search for an existing contact in Alegra based on name or identification. [See the documentation](https://developer.alegra.com/reference/listcontacts-1)", + version: "0.0.1", type: "action", props: { alegra, query: { propDefinition: [ - "alegra", + alegra, "query", ], }, }, async run({ $ }) { - const response = await this.alegra.searchContact(); - const contacts = response.items; - $.export("$summary", `Found ${contacts.length} contact(s) matching your query`); - return contacts; + const response = await this.alegra.searchContact({ + $, + params: { + query: this.query, + }, + }); + + $.export("$summary", `Found ${response.length} contact(s) matching your query`); + return response; }, }; diff --git a/components/alegra/alegra.app.mjs b/components/alegra/alegra.app.mjs index a231581f12300..77631f7fb376c 100644 --- a/components/alegra/alegra.app.mjs +++ b/components/alegra/alegra.app.mjs @@ -1,440 +1,245 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", app: "alegra", - version: "0.0.{{ts}}", propDefinitions: { - // Create Contact - name: { - type: "string", - label: "Name", - description: "Name of the contact", - }, - identification: { - type: "string", - label: "Identification", - description: "Identification of the contact", - optional: true, - }, - address: { - type: "string", - label: "Address", - description: "Address of the contact", - optional: true, - }, - city: { - type: "string", - label: "City", - description: "City of the contact", - optional: true, - }, - phonePrimary: { - type: "string", - label: "Primary Phone", - description: "Primary phone number of the contact", - optional: true, - }, - phoneSecondary: { - type: "string", - label: "Secondary Phone", - description: "Secondary phone number of the contact", - optional: true, - }, - mobile: { - type: "string", - label: "Mobile", - description: "Mobile phone number of the contact", - optional: true, - }, - email: { - type: "string", - label: "Email", - description: "Email of the contact", - optional: true, - }, - type: { - type: "string", - label: "Type", - description: "Type of the contact", - optional: true, - }, - status: { - type: "string", - label: "Status", - description: "Status of the contact", - optional: true, - }, - fax: { - type: "string", - label: "Fax", - description: "Fax number of the contact", - optional: true, - }, - debtToPay: { - type: "integer", - label: "Debt to Pay", - description: "Debt to pay for the contact", - optional: true, - }, - accountReceivable: { - type: "integer", - label: "Account Receivable", - description: "Account receivable associated with the contact", - optional: true, - }, - internalContacts: { - type: "string", - label: "Internal Contacts", - description: "Internal contacts related to the contact", - optional: true, - }, - ignoreRepeated: { - type: "boolean", - label: "Ignore Repeated", - description: "Ignore repeated contacts", - optional: true, - }, - statementAttached: { - type: "boolean", - label: "Statement Attached", - description: "Whether statement is attached", - optional: true, - }, - sellerContact: { + seller: { type: "string", label: "Seller", description: "Seller associated with the contact", - optional: true, - async options() { - const sellersResponse = await this.getSellers(); - return sellersResponse.items.map((seller) => ({ - label: seller.name, - value: seller.id, + async options({ page }) { + const data = await this.getSellers({ + params: { + start: LIMIT * page, + limit: LIMIT, + status: "active", + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - priceListContact: { + priceList: { type: "string", label: "Price List", description: "Price list associated with the contact", - optional: true, - async options() { - const priceListsResponse = await this.getPriceLists(); - return priceListsResponse.items.map((priceList) => ({ - label: priceList.name, - value: priceList.id, + async options({ page }) { + const data = await this.getPriceLists({ + params: { + start: LIMIT * page, + limit: LIMIT, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - termContact: { + term: { type: "string", label: "Term", description: "Payment terms associated with the contact", - optional: true, - async options() { - const termsResponse = await this.getTerms(); - return termsResponse.items.map((term) => ({ - label: term.name, - value: term.id, + async options({ page }) { + const data = await this.getTerms({ + params: { + start: LIMIT * page, + limit: LIMIT, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - - // Create Invoice - items: { - type: "string[]", - label: "Items", - description: "Array of items in JSON format", - }, - dueDate: { - type: "string", - label: "Due Date", - description: "Due date of the invoice (YYYY-MM-DD)", + debtToPay: { + type: "integer", + label: "Debt to Pay", + description: "The Id of the debt to pay for the contact", + async options({ page }) { + const data = await this.getAccountingAccounts({ + params: { + start: LIMIT * page, + limit: LIMIT, + }, + }); + return data.map(({ + id, name: label, + }) => ({ + label, + value: parseInt(id), + })); + }, }, - date: { + query: { type: "string", - label: "Date", - description: "Date of the invoice (YYYY-MM-DD)", + label: "Query", + description: "Search query for contacting (email, phone, or name)", }, client: { - type: "string", + type: "integer", label: "Client", - description: "Client ID associated with the invoice", - }, - statusInvoice: { - type: "string", - label: "Status", - description: "Status of the invoice", - optional: true, - }, - numberTemplateId: { - type: "string", - label: "Number Template ID", - description: "Number template ID for the invoice", - optional: true, - }, - numberTemplatePrefix: { - type: "string", - label: "Number Template Prefix", - description: "Prefix for the number template", - optional: true, - }, - numberTemplateNumber: { - type: "string", - label: "Number Template Number", - description: "Number part of the number template", - optional: true, - }, - payments: { - type: "string[]", - label: "Payments", - description: "Array of payments in JSON format", - optional: true, - }, - estimate: { - type: "string", - label: "Estimate", - description: "Estimate associated with the invoice", - optional: true, - }, - termsConditions: { - type: "string", - label: "Terms & Conditions", - description: "Terms and conditions of the invoice", - optional: true, - }, - annotation: { - type: "string", - label: "Annotation", - description: "Annotation for the invoice", - optional: true, - }, - observations: { - type: "string", - label: "Observations", - description: "Observations for the invoice", - optional: true, - }, - sellerInvoice: { - type: "string", - label: "Seller", - description: "Seller associated with the invoice", - optional: true, - async options() { - const sellersResponse = await this.getSellers(); - return sellersResponse.items.map((seller) => ({ - label: seller.name, - value: seller.id, - })); - }, - }, - priceListInvoice: { - type: "string", - label: "Price List", - description: "Price list associated with the invoice", - optional: true, - async options() { - const priceListsResponse = await this.getPriceLists(); - return priceListsResponse.items.map((priceList) => ({ - label: priceList.name, - value: priceList.id, + description: "Client associated with the invoice", + async options({ page }) { + const data = await this.searchContact({ + params: { + start: LIMIT * page, + limit: LIMIT, + type: "client", + }, + }); + + return data.map(({ + id, name: label, + }) => ({ + label, + value: parseInt(id), })); }, }, - currency: { - type: "string", - label: "Currency", - description: "Currency for the invoice", - optional: true, - }, - retentions: { - type: "string[]", - label: "Retentions", - description: "Array of retentions in JSON format", - optional: true, - }, warehouse: { type: "string", label: "Warehouse", description: "Warehouse associated with the invoice", - optional: true, - }, - remissions: { - type: "string[]", - label: "Remissions", - description: "Array of remissions in JSON format", - optional: true, + async options({ page }) { + const data = await this.getWarehouses({ + params: { + start: LIMIT * page, + limit: LIMIT, + }, + }); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, costCenter: { type: "string", label: "Cost Center", description: "Cost center associated with the invoice", - optional: true, - }, - comments: { - type: "string", - label: "Comments", - description: "Comments for the invoice", - optional: true, - }, - periodicity: { - type: "string", - label: "Periodicity", - description: "Periodicity of the invoice", - optional: true, - }, + async options({ page }) { + const data = await this.getCostCenters({ + params: { + start: LIMIT * page, + limit: LIMIT, + }, + }); - // Search Contact - query: { - type: "string", - label: "Query", - description: "Search query for contacting (email, phone, or name)", + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { return "https://api.alegra.com/api/v1"; }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers = {}, ...otherOpts - } = opts; + _auth() { + return { + username: this.$auth.user_email, + password: this.$auth.access_token, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - method, - url: `${this._baseUrl()}${path}`, + url: this._baseUrl() + path, + auth: this._auth(), headers: { - ...headers, - "Authorization": `Basic ${Buffer.from(this.$auth.api_key + ":").toString("base64")}`, + "Accept": "application/json", "Content-Type": "application/json", }, - ...otherOpts, + ...opts, }); }, - async createContact(opts = {}) { + createContact(opts = {}) { return this._makeRequest({ method: "POST", path: "/contacts", - data: { - name: this.name, - identification: this.identification, - address: this.address, - city: this.city, - phonePrimary: this.phonePrimary, - phoneSecondary: this.phoneSecondary, - mobile: this.mobile, - seller: this.sellerContact, - priceList: this.priceListContact, - term: this.termContact, - email: this.email, - type: this.type, - status: this.status, - fax: this.fax, - debtToPay: this.debtToPay, - accountReceivable: this.accountReceivable, - internalContacts: this.internalContacts, - ignoreRepeated: this.ignoreRepeated, - statementAttached: this.statementAttached, - }, ...opts, }); }, - async generateInvoice(opts = {}) { + generateInvoice(opts = {}) { return this._makeRequest({ method: "POST", path: "/invoices", - data: { - items: this.items.map((item) => JSON.parse(item)), - dueDate: this.dueDate, - date: this.date, - client: this.client, - status: this.statusInvoice, - numberTemplateId: this.numberTemplateId, - numberTemplatePrefix: this.numberTemplatePrefix, - numberTemplateNumber: this.numberTemplateNumber, - payments: this.payments - ? this.payments.map((payment) => JSON.parse(payment)) - : undefined, - estimate: this.estimate, - termsConditions: this.termsConditions, - annotation: this.annotation, - observations: this.observations, - seller: this.sellerInvoice, - priceList: this.priceListInvoice, - currency: this.currency, - retentions: this.retentions - ? this.retentions.map((retention) => JSON.parse(retention)) - : undefined, - warehouse: this.warehouse, - remissions: this.remissions - ? this.remissions.map((remission) => JSON.parse(remission)) - : undefined, - costCenter: this.costCenter, - comments: this.comments, - periodicity: this.periodicity, - }, ...opts, }); }, - async searchContact(opts = {}) { + searchContact(opts = {}) { return this._makeRequest({ path: "/contacts", - params: { - query: this.query, - }, ...opts, }); }, - async getSellers(opts = {}) { + getSellers(opts = {}) { return this._makeRequest({ path: "/sellers", ...opts, }); }, - async getPriceLists(opts = {}) { + getPriceLists(opts = {}) { return this._makeRequest({ path: "/price-lists", ...opts, }); }, - async getTerms(opts = {}) { + getTerms(opts = {}) { return this._makeRequest({ path: "/terms", ...opts, }); }, - async createWebhookSubscription({ - event, url, - }) { + getAccountingAccounts(opts = {}) { + return this._makeRequest({ + path: "/categories", + ...opts, + }); + }, + getWarehouses(opts = {}) { + return this._makeRequest({ + path: "/warehouses", + ...opts, + }); + }, + getCostCenters(opts = {}) { + return this._makeRequest({ + path: "/cost-centers", + ...opts, + }); + }, + createWebhook(opts = {}) { return this._makeRequest({ method: "POST", path: "/webhooks/subscriptions", - data: { - event, - url, - }, + ...opts, }); }, - async paginate(fn, ...opts) { - const results = []; - let page = 0; - let more = true; - while (more) { - const response = await fn({ - ...opts, - page, - }); - if (!response || response.length === 0) { - more = false; - } else { - results.push(...response); - page += 1; - } - } - return results; + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/subscriptions/${webhookId}`, + }); }, }, }; diff --git a/components/alegra/common/constants.mjs b/components/alegra/common/constants.mjs new file mode 100644 index 0000000000000..fd6248d1442d4 --- /dev/null +++ b/components/alegra/common/constants.mjs @@ -0,0 +1,25 @@ +export const LIMIT = 30; + +export const TYPE_OPTIONS = [ + "client", + "provider", +]; + +export const STATUS_OPTIONS = [ + "active", + "inactive", +]; + +export const INVOICE_STATUS_OPTIONS = [ + "draft", + "open", +]; + +export const PERIODICITY_OPTIONS = [ + "BIWEEKLY", + "MONTHLY", + "BIMONTHLY", + "QUARTERLY", + "SEMIANNUALLY", + "MANUAL", +]; diff --git a/components/alegra/common/utils.mjs b/components/alegra/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/alegra/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/alegra/package.json b/components/alegra/package.json new file mode 100644 index 0000000000000..b60563d76c3bf --- /dev/null +++ b/components/alegra/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/alegra", + "version": "0.1.0", + "description": "Pipedream Alegra Components", + "main": "alegra.app.mjs", + "keywords": [ + "pipedream", + "alegra" + ], + "homepage": "https://pipedream.com/apps/alegra", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/alegra/sources/common/base.mjs b/components/alegra/sources/common/base.mjs new file mode 100644 index 0000000000000..694f1d364c895 --- /dev/null +++ b/components/alegra/sources/common/base.mjs @@ -0,0 +1,48 @@ +import alegra from "../../alegra.app.mjs"; + +export default { + props: { + alegra, + http: "$.interface.http", + db: "$.service.db", + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + const { subscription } = await this.alegra.createWebhook({ + data: { + url: this.http.endpoint.split("https://")[1], + event: this.getEventType(), + }, + }); + this._setHookId(subscription.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.alegra.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + if (!body.message) return; + + const ts = Date.parse(new Date()); + const message = body.message; + const model = message.client || message.item || message.invoice; + + this.$emit(body, { + id: model.id, + summary: this.getSummary(message), + ts: ts, + }); + }, +}; diff --git a/components/alegra/sources/new-client-instant/new-client-instant.mjs b/components/alegra/sources/new-client-instant/new-client-instant.mjs new file mode 100644 index 0000000000000..80430f82ad44a --- /dev/null +++ b/components/alegra/sources/new-client-instant/new-client-instant.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "alegra-new-client-instant", + name: "New Client Created (Instant)", + description: "Emit new event when a new client is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return "new-client"; + }, + getSummary({ client }) { + return `New client created: ${client.email} - (${client.email})`; + }, + }, + sampleEmit, +}; diff --git a/components/alegra/sources/new-client-instant/test-event.mjs b/components/alegra/sources/new-client-instant/test-event.mjs new file mode 100644 index 0000000000000..00230042d7306 --- /dev/null +++ b/components/alegra/sources/new-client-instant/test-event.mjs @@ -0,0 +1,31 @@ +export default { + "subject": "new-client", + "message": { + "client": { + "id": "774", + "name": { + "firstName": "Primer Nombre", + "secondName": "Segundo Nombre", + "lastName": "Primer Apellido", + "secondLastName": "Segundo Apellido" + }, + "phonePrimary": "+432432234", + "phoneSecondary": "+534234523", + "mobile": "+443242323123", + "email": "uncorreo@correo.com", + "type": [ + "client", + "provider" + ], + "fax": "unFax", + "identification": "3211233", + "address": { + "zipCode": "050013", + "department": "Antioquia", + "country": "Colombia", + "address": "Una Dirección", + "city": "Abriaquí" + } + } + } +} diff --git a/components/alegra/sources/new-client/new-client.mjs b/components/alegra/sources/new-client/new-client.mjs deleted file mode 100644 index de4251ed4a29c..0000000000000 --- a/components/alegra/sources/new-client/new-client.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "alegra-new-client", - name: "New Client Created", - description: "Emit new event when a brand new client is created. [See the documentation](https://developer.alegra.com/reference/post_webhooks-subscriptions)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - alegra: { - type: "app", - app: "alegra", - }, - http: { - type: "$.interface.http", - customResponse: false, - }, - db: "$.service.db", - }, - hooks: { - async activate() { - const subscription = await this.alegra.createWebhookSubscription({ - event: "new-client", - url: this.http.endpoint, - }); - await this.db.set("webhook_subscription_id", subscription.id); - }, - async deactivate() { - const subscriptionId = await this.db.get("webhook_subscription_id"); - if (subscriptionId) { - await this.alegra.deleteWebhook(subscriptionId); - await this.db.set("webhook_subscription_id", null); - } - }, - async deploy() { - const response = await this.alegra._makeRequest({ - path: "/contacts", - params: { - limit: 50, - sort: "createdAt", - order: "desc", - }, - }); - const contacts = response.items; - contacts.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); - for (const contact of contacts) { - this.$emit(contact, { - id: contact.id, - summary: `New client created: ${contact.name}`, - ts: new Date(contact.createdAt).getTime(), - }); - } - }, - }, - async run(event) { - const client = event.body; - const id = client.id || `${Date.now()}`; - const summary = `New client created: ${client.name || "Unnamed client"}`; - const ts = client.createdAt - ? new Date(client.createdAt).getTime() - : Date.now(); - this.$emit(client, { - id, - summary, - ts, - }); - }, -}; diff --git a/components/alegra/sources/new-invoice-instant/new-invoice-instant.mjs b/components/alegra/sources/new-invoice-instant/new-invoice-instant.mjs new file mode 100644 index 0000000000000..0dd0fac77c14f --- /dev/null +++ b/components/alegra/sources/new-invoice-instant/new-invoice-instant.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "alegra-new-invoice-instant", + name: "New Invoice Created (Instant)", + description: "Emit new event when a new invoice is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return "new-invoice"; + }, + getSummary({ invoice }) { + return `New invoice created: ${invoice.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/alegra/sources/new-invoice-instant/test-event.mjs b/components/alegra/sources/new-invoice-instant/test-event.mjs new file mode 100644 index 0000000000000..144db0d0b0f84 --- /dev/null +++ b/components/alegra/sources/new-invoice-instant/test-event.mjs @@ -0,0 +1,49 @@ +export default { + "subject": "new-invoice", + "message": { + "invoice": { + "id": "123", + "date": "2024-06-18", + "dueDate": "2024-06-18", + "observations": "Notas de facturas de capacitación", + "anotation": "Notas de facturas de capacitación", + "status": "open", + "client": { + "id": "123" + }, + "numberTemplate": { + "id": "123" + }, + "seller": { + "id": "123" + }, + "total": 1190, + "totalPaid": 0, + "balance": 1190, + "decimalPrecision": "0", + "estimate": { + "id": "123" + }, + "costCenter": { + "id": "123" + }, + "items": [ + { + "name": "cédula digital / Option 2", + "description": "Descripción del Item", + "reference": "123321", + "price": 1000, + "quantity": 1, + "tax": [ + { + "id": "123" + } + ], + "remission": { + "id": "123" + } + } + ] + } + } +} diff --git a/components/alegra/sources/new-invoice/new-invoice.mjs b/components/alegra/sources/new-invoice/new-invoice.mjs deleted file mode 100644 index 286623e13d907..0000000000000 --- a/components/alegra/sources/new-invoice/new-invoice.mjs +++ /dev/null @@ -1,90 +0,0 @@ -import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "alegra-new-invoice", - name: "New Invoice", - description: "Emit a new event when a new invoice is created. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - alegra: { - type: "app", - app: "alegra", - }, - http: { - type: "$.interface.http", - customResponse: false, - }, - db: "$.service.db", - }, - hooks: { - async activate() { - try { - const webhookResponse = await this.alegra.createWebhookSubscription({ - event: "new-invoice", - url: this.http.endpoint, - }); - - if (webhookResponse.id) { - await this.db.set("webhookId", webhookResponse.id); - } else { - throw new Error("Failed to retrieve webhook subscription ID."); - } - } catch (error) { - console.error("Error during activate:", error); - throw error; - } - }, - async deactivate() { - try { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.alegra.deleteWebhookSubscription(webhookId); - await this.db.delete("webhookId"); - } - } catch (error) { - console.error("Error during deactivate:", error); - } - }, - async deploy() { - try { - const invoices = await this.alegra.paginate(this.alegra.listInvoices.bind(this.alegra), { - perPage: 50, - }); - const recentInvoices = invoices.slice(-50); - for (const invoice of recentInvoices) { - this.$emit(invoice, { - id: invoice.id, - summary: `New invoice created: #${invoice.id}`, - ts: Date.parse(invoice.date) || Date.now(), - }); - } - } catch (error) { - console.error("Error during deploy:", error); - } - }, - }, - async run(event) { - try { - const invoice = event.body; - - if (!invoice || !invoice.id) { - throw new Error("Invalid invoice data received."); - } - - this.$emit(invoice, { - id: invoice.id, - summary: `New invoice created: #${invoice.id}`, - ts: Date.parse(invoice.date) || Date.now(), - }); - } catch (error) { - console.error("Error during run:", error); - this.http.respond({ - status: 400, - body: "Bad Request", - }); - } - }, -}; diff --git a/components/alegra/sources/new-item-instant/new-item-instant.mjs b/components/alegra/sources/new-item-instant/new-item-instant.mjs new file mode 100644 index 0000000000000..6e3fdc2d89f7a --- /dev/null +++ b/components/alegra/sources/new-item-instant/new-item-instant.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "alegra-new-item-instant", + name: "New Item Added (Instant)", + description: "Emit new event each time a new item is added.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return "new-item"; + }, + getSummary({ item }) { + return `New item added: ${item.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/alegra/sources/new-item-instant/test-event.mjs b/components/alegra/sources/new-item-instant/test-event.mjs new file mode 100644 index 0000000000000..45fdcfacf010d --- /dev/null +++ b/components/alegra/sources/new-item-instant/test-event.mjs @@ -0,0 +1,55 @@ +export default { + "subject": "new-item", + "message": { + "item": { + "id": "865", + "name": "Un Ítem / S", + "description": "Descripción del Item", + "reference": "423424134213", + "itemCategory": { + "id": "19" + }, + "price": [ + { + "id": "123", + "price": 1 + }, + { + "id": "124", + "price": 2 + } + ], + "inventory": { + "unit": "unit", + "availableQuantity": 1, + "unitCost": 1, + "initialQuantity": 1, + "warehouses": [ + { + "id": "10" + } + ] + }, + "category": { + "id": "234" + }, + "tax": [], + "status": "active", + "customFields": [ + { + "id": "6" + }, + { + "id": "5" + }, + { + "id": "1" + } + ], + "type": "variant", + "variantAttributes": null, + "itemVariants": null, + "subitems": null + } + } +} diff --git a/components/alegra/sources/new-item/new-item.mjs b/components/alegra/sources/new-item/new-item.mjs deleted file mode 100644 index 2c7dcf56cc2cc..0000000000000 --- a/components/alegra/sources/new-item/new-item.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import alegra from "../../alegra.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "alegra-new-item", - name: "New Item Added", - description: "Emit new event each time a new item is added. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - alegra: { - type: "app", - app: "alegra", - }, - db: "$.service.db", - http: { - type: "$.interface.http", - }, - }, - hooks: { - async activate() { - const webhook = await this.alegra.createWebhookSubscription({ - event: "new-item", - url: this.http.endpoint, - }); - this.db.set("webhookId", webhook.subscription.id); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.alegra._makeRequest({ - method: "DELETE", - path: `/webhooks/subscriptions/${webhookId}`, - }); - await this.db.delete("webhookId"); - } - }, - async deploy() { - try { - const items = await this.alegra.paginate( - async (opts) => this.alegra._makeRequest(opts), - { - path: "/items", - params: { - limit: 50, - sort: "-id", - }, - }, - ); - const last50 = items.slice(-50); - for (const item of last50) { - this.$emit(item, { - id: item.id, - summary: `New item added: ${item.name}`, - ts: Date.parse(item.createdAt) || Date.now(), - }); - } - } catch (error) { - console.error("Error deploying alegra-new-item source:", error); - } - }, - }, - async run(event) { - const item = event.body; - const ts = Date.parse(item.createdAt) || Date.now(); - const id = item.id - ? String(item.id) - : String(ts); - const summary = item.name - ? `New item added: ${item.name}` - : "New item added"; - this.$emit(item, { - id, - summary, - ts, - }); - }, -}; From 871aa8dff913c07bc6f20ba8280820d7b55d1972 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 6 Jan 2025 10:58:12 -0300 Subject: [PATCH 3/5] pnpm update --- pnpm-lock.yaml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8618b6ed1f0b..08f37c232a151 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -517,6 +517,12 @@ importers: specifier: ^3.0.3 version: 3.0.3 + components/alegra: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 + components/alerty: dependencies: '@pipedream/platform': @@ -1180,8 +1186,7 @@ importers: specifier: ^0.0.4 version: 0.0.4 - components/bitdefender_gravityzone: - specifiers: {} + components/bitdefender_gravityzone: {} components/bitport: {} @@ -8434,8 +8439,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/recruiterflow: - specifiers: {} + components/recruiterflow: {} components/recruitis: dependencies: @@ -12489,7 +12493,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2) + version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2) typescript: specifier: ^5.6 version: 5.7.2 @@ -43251,7 +43255,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -43262,15 +43266,15 @@ snapshots: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.7.2 + typescript: 5.6.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 8.0.0-alpha.13 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) - ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -43281,7 +43285,7 @@ snapshots: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.6.3 + typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 8.0.0-alpha.13 From ce8519a7b93015b8926b0791574d37131ef630cb Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 6 Jan 2025 10:59:05 -0300 Subject: [PATCH 4/5] pnpm update --- pnpm-lock.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08f37c232a151..12f3856f7431b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12493,7 +12493,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2) typescript: specifier: ^5.6 version: 5.7.2 @@ -43255,7 +43255,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -43266,15 +43266,15 @@ snapshots: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.6.3 + typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 8.0.0-alpha.13 + '@babel/core': 7.26.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) + babel-jest: 29.7.0(@babel/core@7.26.0) - ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -43285,7 +43285,7 @@ snapshots: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.7.2 + typescript: 5.6.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 8.0.0-alpha.13 From d4113b7dbea52808aff80b1e045134b9538d9ea4 Mon Sep 17 00:00:00 2001 From: Leo Vu Date: Tue, 7 Jan 2025 10:21:21 +0700 Subject: [PATCH 5/5] Add create invoice api doc url --- components/alegra/actions/create-invoice/create-invoice.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/alegra/actions/create-invoice/create-invoice.mjs b/components/alegra/actions/create-invoice/create-invoice.mjs index 563d691876428..909d4294b1f9b 100644 --- a/components/alegra/actions/create-invoice/create-invoice.mjs +++ b/components/alegra/actions/create-invoice/create-invoice.mjs @@ -8,7 +8,7 @@ import { parseObject } from "../../common/utils.mjs"; export default { key: "alegra-create-invoice", name: "Create Invoice", - description: "Creates a new invoice in Alegra. [See the documentation]()", + description: "Creates a new invoice in Alegra. [See the documentation](https://developer.alegra.com/reference/post_invoices)", version: "0.0.1", type: "action", props: {