diff --git a/components/pennylane/actions/create-billing-subscription/create-billing-subscription.mjs b/components/pennylane/actions/create-billing-subscription/create-billing-subscription.mjs new file mode 100644 index 0000000000000..4835da3aa8a83 --- /dev/null +++ b/components/pennylane/actions/create-billing-subscription/create-billing-subscription.mjs @@ -0,0 +1,162 @@ +import { + DAY_OF_WEEK_OPTIONS, + MODE_OPTIONS, + PAYMENT_CONDITIONS_OPTIONS, + PAYMENT_METHOD_OPTIONS, + RECURRING_RULE_TYPE, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; +import pennylane from "../../pennylane.app.mjs"; + +export default { + key: "pennylane-create-billing-subscription", + name: "Create Billing Subscription", + description: "Creates a billing subscription for a customer. [See the documentation](https://pennylane.readme.io/reference/billing_subscriptions-post-1).", + version: "0.0.1", + type: "action", + props: { + pennylane, + currency: { + type: "string", + label: "Currency", + description: "Invoice Currency (ISO 4217). Default is EUR.", + optional: true, + }, + mode: { + type: "string", + label: "Mode", + description: "Mode in which the new invoices will be created.", + options: MODE_OPTIONS, + }, + start: { + type: "string", + label: "Start", + description: "Start date (ISO 8601)", + }, + paymentConditions: { + type: "string", + label: "Payment Conditions", + description: "Customer payment conditions", + options: PAYMENT_CONDITIONS_OPTIONS, + }, + paymentMethod: { + type: "string", + label: "Payment Method", + description: "PaymentMethod", + options: PAYMENT_METHOD_OPTIONS, + }, + recurringRuleType: { + type: "string", + label: "Recurring Rule Type", + description: "Type of the billing subscription's recurrence", + options: RECURRING_RULE_TYPE, + reloadProps: true, + }, + dayOfMonth: { + type: "integer", + label: "Day Of Month", + description: "The day of occurrences of the recurring rule", + hidden: true, + }, + dayOfWeek: { + type: "string", + label: "Day Of Week", + description: "The day of occurrences of the recurring rule", + options: DAY_OF_WEEK_OPTIONS, + hidden: true, + }, + interval: { + type: "string", + label: "Interval", + description: "The interval of occurrences of the recurring rule", + optional: true, + }, + count: { + type: "integer", + label: "Count", + description: "Number of occurrences of the recurring rule", + optional: true, + }, + specialMention: { + type: "string", + label: "Special Mention", + description: "Additional details", + optional: true, + }, + discount: { + type: "integer", + label: "Discount", + description: "Invoice discount (in percent)", + optional: true, + }, + customerId: { + propDefinition: [ + pennylane, + "customerId", + ], + }, + lineItemsSectionsAttributes: { + propDefinition: [ + pennylane, + "lineItemsSectionsAttributes", + ], + optional: true, + }, + invoiceLines: { + propDefinition: [ + pennylane, + "lineItems", + ], + label: "Invoice Lines", + }, + }, + async additionalProps(props) { + switch (this.recurringRuleType) { + case "monthly": + props.dayOfMonth.hidden = false; + props.dayOfWeek.hidden = true; + break; + case "weekly": + props.dayOfMonth.hidden = true; + props.dayOfWeek.hidden = false; + break; + case "yearly": + props.dayOfMonth.hidden = true; + props.dayOfWeek.hidden = true; + break; + } + return {}; + }, + async run({ $ }) { + const response = await this.pennylane.createBillingSubscription({ + $, + data: { + create_customer: false, + create_products: false, + billing_subscription: { + currency: this.currency, + mode: this.mode, + start: this.start, + payment_conditions: this.paymentConditions, + payment_method: this.paymentMethod, + recurring_rule: { + type: this.recurringRuleType, + day_of_month: this.dayOfMonth, + day_of_week: this.dayOfWeek, + interval: this.interval, + count: this.count, + }, + special_mention: this.specialMention, + discount: this.discount, + customer: { + source_id: this.customerId, + }, + line_items_sections_attributes: parseObject(this.lineItemsSectionsAttributes), + invoice_lines: parseObject(this.invoiceLines), + }, + }, + }); + $.export("$summary", `Created billing subscription with ID ${response.billing_subscription.id}`); + return response; + }, +}; diff --git a/components/pennylane/actions/create-customer-invoice/create-customer-invoice.mjs b/components/pennylane/actions/create-customer-invoice/create-customer-invoice.mjs new file mode 100644 index 0000000000000..1e568b8339f73 --- /dev/null +++ b/components/pennylane/actions/create-customer-invoice/create-customer-invoice.mjs @@ -0,0 +1,182 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + BANKING_PROVIDER_OPTIONS, + LANGUAGE_OPTIONS, + PROVIDER_FIELD_NAMES_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; +import pennylane from "../../pennylane.app.mjs"; + +export default { + key: "pennylane-create-customer-invoice", + name: "Create Customer Invoice", + description: "Generates a new invoice for a customer using Pennylane. [See the documentation](https://pennylane.readme.io/reference/customer_invoices-post-1)", + version: "0.0.1", + type: "action", + props: { + pennylane, + date: { + type: "string", + label: "Date", + description: "Invoice date (ISO 8601)", + }, + deadline: { + type: "string", + label: "Deadline", + description: "Invoice payment deadline (ISO 8601)", + }, + externalId: { + type: "string", + label: "External Id", + description: "An id you can use to refer to the invoice from outside of Pennylane", + optional: true, + }, + pdfInvoiceFreeText: { + type: "string", + label: "PDF Invoice Free Text", + description: "For example, the contact details of the person to contact", + optional: true, + }, + pdfInvoiceSubject: { + type: "string", + label: "PDF Invoice Subject", + description: "Invoice title", + optional: true, + }, + draft: { + type: "boolean", + label: "Draft", + description: "Do you wish to create a draft invoice (otherwise it is a finalized invoice)? Reminder, once created, a finalized invoice cannot be edited!", + }, + currency: { + type: "string", + label: "Currency", + description: "Invoice Currency (ISO 4217). Default is EUR.", + optional: true, + }, + specialMention: { + type: "string", + label: "Special Mention", + description: "Additional details", + optional: true, + }, + discount: { + type: "integer", + label: "Discount", + description: "Invoice discount (in percent)", + optional: true, + }, + language: { + type: "string", + label: "Language", + description: "invoice pdf language", + options: LANGUAGE_OPTIONS, + optional: true, + }, + bankingProvider: { + type: "string", + label: "Banking Provider", + description: "The banking provider for the transaction", + options: BANKING_PROVIDER_OPTIONS, + reloadProps: true, + }, + providerFieldName: { + type: "string", + label: "Provider Field Name", + description: "Name of the field that you want to match", + options: PROVIDER_FIELD_NAMES_OPTIONS, + hidden: true, + }, + providerFieldValue: { + type: "string", + label: "Provider Field Value", + description: "Value that you want to match", + }, + customerId: { + propDefinition: [ + pennylane, + "customerId", + ], + }, + lineItemsSectionsAttributes: { + propDefinition: [ + pennylane, + "lineItemsSectionsAttributes", + ], + optional: true, + }, + lineItems: { + propDefinition: [ + pennylane, + "lineItems", + ], + }, + categories: { + type: "string[]", + label: "Categories", + description: "A list of objects of categories", + optional: true, + }, + startDate: { + type: "string", + label: "Start Date", + description: "Start date of the imputation period (ISO 8601)", + }, + endDate: { + type: "string", + label: "End Date", + description: "End date of the imputation period (ISO 8601)", + }, + }, + async additionalProps(props) { + if (this.bankingProvider === "stripe") { + props.providerFieldName.hidden = false; + } + return {}; + }, + async run({ $ }) { + try { + const invoice = await this.pennylane.createInvoice({ + $, + data: { + create_customer: false, + create_products: false, + invoice: { + date: this.date, + deadline: this.deadline, + external_id: this.externalId, + pdf_invoice_free_text: this.pdfInvoiceFreeText, + pdf_invoice_subject: this.pdfInvoiceSubject, + draft: this.draft, + currency: this.currency, + special_mention: this.specialMention, + discount: this.discount, + language: this.language, + transactions_reference: { + banking_provider: this.bankingProvider, + provider_field_name: (this.bankingProvider === "gocardless") + ? "payment_id" + : this.providerFieldName, + provider_field_value: this.providerFieldValue, + }, + customer: { + source_id: this.customerId, + }, + line_items_sections_attributes: parseObject(this.lineItemsSectionsAttributes), + line_items: parseObject(this.lineItems), + categories: parseObject(this.categories), + imputation_dates: { + start_date: this.startDate, + end_date: this.endDate, + }, + }, + }, + }); + + $.export("$summary", `Created invoice with ID ${invoice.invoice.id}`); + return invoice; + } catch ({ response }) { + throw new ConfigurationError(response?.data?.message || response?.data?.error); + } + }, +}; diff --git a/components/pennylane/actions/create-customer/create-customer.mjs b/components/pennylane/actions/create-customer/create-customer.mjs new file mode 100644 index 0000000000000..38054afd4905c --- /dev/null +++ b/components/pennylane/actions/create-customer/create-customer.mjs @@ -0,0 +1,229 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { + CUSTOMER_GENDER_OPTIONS, + CUSTOMER_TYPE_OPTIONS, PAYMENT_CONDITIONS_OPTIONS, +} from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; +import pennylane from "../../pennylane.app.mjs"; + +export default { + key: "pennylane-create-customer", + name: "Create Customer", + description: "Creates a new customer in Pennylane. [See the documentation](https://pennylane.readme.io/reference/customers-post-1)", + version: "0.0.1", + type: "action", + props: { + pennylane, + customerType: { + type: "string", + label: "Customer Type", + description: "The type of the customer you want to create.", + options: CUSTOMER_TYPE_OPTIONS, + reloadProps: true, + }, + firstName: { + type: "string", + label: "First Name", + description: "Customer first name.", + hidden: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "Customer last name.", + hidden: true, + }, + gender: { + type: "string", + label: "Gender", + description: "Customer Gender", + options: CUSTOMER_GENDER_OPTIONS, + hidden: true, + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "The name of the company.", + hidden: true, + }, + regNo: { + type: "string", + label: "Reg No", + description: "Customer registration number (SIREN).", + hidden: true, + optional: true, + }, + address: { + type: "string", + label: "Address", + description: "Customer address (billing address).", + }, + postalCode: { + type: "string", + label: "Postal Code", + description: "Postal code (billing address).", + }, + city: { + type: "string", + label: "City", + description: "City (billing address).", + }, + country: { + type: "string", + label: "Country", + description: "Any ISO 3166 Alpha-2 country code (billing address).", + }, + recipient: { + type: "string", + label: "Recipient", + description: "Recipient displayed in the invoice.", + hidden: true, + optional: true, + }, + vatNumber: { + type: "string", + label: "VAT Number", + description: "Customer's VAT number.", + hidden: true, + optional: true, + }, + sourceId: { + type: "string", + label: "Source Id", + description: "You can use your own id when creating the customer. If not provided, Pennylane will pick one for you. Id must be unique.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "A list of customer emails.", + optional: true, + }, + billingIban: { + type: "string", + label: "Billing IBAN", + description: "The billing IBAN of the customer. This is the iban on which you wish to receive payment from this customer.", + optional: true, + }, + deliveryAddress: { + type: "string", + label: "Delivery Address", + description: "Address (shipping address).", + optional: true, + }, + deliveryPostalCode: { + type: "string", + label: "Delivery Postal Code", + description: "Postal code (shipping address).", + optional: true, + }, + deliveryCity: { + type: "string", + label: "Delivery City", + description: "City (shipping address).", + optional: true, + }, + deliveryCountry: { + type: "string", + label: "Delivery Country", + description: "Any ISO 3166 Alpha-2 country code (shipping address).", + optional: true, + }, + paymentConditions: { + type: "string", + label: "Payment Conditions", + description: "Customer payment conditions", + options: PAYMENT_CONDITIONS_OPTIONS, + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "Customer phone number.", + optional: true, + }, + reference: { + type: "string", + label: "Reference", + description: "This reference doesn't appear on the invoice.", + optional: true, + }, + notes: { + type: "string", + label: "Notes", + description: "Notes about the customer.", + optional: true, + }, + mandate: { + type: "object", + label: "Mandate", + description: "The mandate object. **Example: {\"provider\": \"gocardless\",\"source_id\": \"MD001H23WP8E7XN\"}**.", + optional: true, + }, + planItem: { + type: "object", + label: "Plan Item", + description: "The plan item object. **Example: {\"enabled\": true,\"number\": \"123\",\"label\": \"label\",\"vat_rate\": \"123\",\"country_alpha2\": \"US\",\"description\": \"description\"}**.", + optional: true, + }, + }, + async additionalProps(props) { + const typeCompany = (this.customerType === "company"); + props.name.hidden = !typeCompany; + props.regNo.hidden = !typeCompany; + props.recipient.hidden = !typeCompany; + props.vatNumber.hidden = !typeCompany; + props.firstName.hidden = typeCompany; + props.lastName.hidden = typeCompany; + props.gender.hidden = typeCompany; + return {}; + }, + async run({ $ }) { + try { + const additionalData = this.customerType === "company" + ? { + name: this.name, + reg_no: this.regNo, + vat_number: this.vatNumber, + recipient: this.recipient, + } + : { + first_name: this.firstName, + last_name: this.lastName, + gender: this.gender, + }; + + const response = await this.pennylane.createCustomer({ + $, + data: { + customer: { + customer_type: this.customerType, + address: this.address, + postal_code: this.postalCode, + city: this.city, + country_alpha2: this.country, + source_id: this.sourceId, + emails: parseObject(this.emails), + billing_iban: this.billingIban, + delivery_address: this.deliveryAddress, + delivery_postal_code: this.deliveryPostalCode, + delivery_city: this.deliveryCity, + delivery_country_alpha2: this.deliveryCountry, + payment_conditions: this.paymentConditions, + phone: this.phone, + reference: this.reference, + notes: this.notes, + mandate: parseObject(this.mandate), + plan_item: parseObject(this.planItem), + ...additionalData, + }, + }, + }); + $.export("$summary", `Successfully created customer with Id: ${response.customer.source_id}`); + return response; + } catch ({ response }) { + throw new ConfigurationError(response?.data?.message || response?.data?.error); + } + }, +}; diff --git a/components/pennylane/common/constants.mjs b/components/pennylane/common/constants.mjs new file mode 100644 index 0000000000000..3cdf83a14f219 --- /dev/null +++ b/components/pennylane/common/constants.mjs @@ -0,0 +1,75 @@ +export const CUSTOMER_TYPE_OPTIONS = [ + "company", + "individual", +]; + +export const PAYMENT_CONDITIONS_OPTIONS = [ + "upon_receipt", + "custom", + "15_days", + "30_days", + "45_days", + "60_days", +]; + +export const CUSTOMER_GENDER_OPTIONS = [ + "mister", + "madam", +]; + +export const LANGUAGE_OPTIONS = [ + "en_GB", + "fr_FR", +]; + +export const BANKING_PROVIDER_OPTIONS = [ + "gocardless", + "stripe", +]; + +export const PROVIDER_FIELD_NAMES_OPTIONS = [ + "payment_id", + "charge_id", +]; + +export const RECURRING_RULE_TYPE = [ + "monthly", + "weekly", + "yearly", +]; + +export const MODE_OPTIONS = [ + { + label: "Draft invoices will be created", + value: "awaiting_validation", + }, + { + label: "Finalized invoices will be created", + value: "finalized", + }, + { + label: "Finalized invoices will be created and autimatically sent to the client at each new occurrence", + value: "email", + }, +]; + +export const PAYMENT_METHOD_OPTIONS = [ + { + label: "Offline - The subscription is not linked to a payment method", + value: "offline", + }, + { + label: "Gocardless Direct Debit - At each new occurrence the client will be automatically debited thanks to GoCardless", + value: "gocardless_direct_debit", + }, +]; + +export const DAY_OF_WEEK_OPTIONS = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", +]; diff --git a/components/pennylane/common/utils.mjs b/components/pennylane/common/utils.mjs new file mode 100644 index 0000000000000..9050833bcda0b --- /dev/null +++ b/components/pennylane/common/utils.mjs @@ -0,0 +1,32 @@ +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 parseObject(item); + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + if (typeof obj === "object") { + for (const [ + key, + value, + ] of Object.entries(obj)) { + obj[key] = parseObject(value); + } + } + return obj; +}; diff --git a/components/pennylane/package.json b/components/pennylane/package.json index 4a288e4b4b0f1..0acf42d2418cb 100644 --- a/components/pennylane/package.json +++ b/components/pennylane/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/pennylane", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Pennylane Components", "main": "pennylane.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/pennylane/pennylane.app.mjs b/components/pennylane/pennylane.app.mjs index 91b88847f1af9..a163a81424d8b 100644 --- a/components/pennylane/pennylane.app.mjs +++ b/components/pennylane/pennylane.app.mjs @@ -1,11 +1,123 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "pennylane", - propDefinitions: {}, + propDefinitions: { + customerId: { + type: "string", + label: "Customer Id", + description: "Existing customer identifier (source_id)", + async options({ page }) { + const { customers } = await this.listCustomers({ + params: { + page: page + 1, + }, + }); + + return customers.map(({ + source_id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + lineItemsSectionsAttributes: { + type: "string[]", + label: "Line Items Sections Attributes", + description: "A list of objects of items sections to be listed on the invoice. **Example: [{\"title\": \"Title\",\"description\": \"description\",\"rank\": 1}]** [See the documentation](https://pennylane.readme.io/reference/customer_invoices-post-1) from further information.", + }, + lineItems: { + type: "string[]", + label: "Line Items", + description: "A list of objects of items to be listed on the invoice. **Example: [{\"product\": {\"source_id\": \"0e67fc3c-c632-4feb-ad34-e18ed5fbf66a\",\"unit\": \"20\",\"price\": \"10.00\",\"label\": \"Product label\",\"vat_rate\": \"FR_09\",\"currency\": \"EUR\"},\"quantity\": 2,\"label\": \"Line Item Label\"}]** [See the documentation](https://pennylane.readme.io/reference/customer_invoices-post-1) from further information.", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://app.pennylane.com/api/external/v1"; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "Content-Type": "application/json", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + createCustomer(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/customers", + ...opts, + }); + }, + createInvoice(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/customer_invoices", + ...opts, + }); + }, + listCustomers(opts = {}) { + return this._makeRequest({ + path: "/customers", + ...opts, + }); + }, + createBillingSubscription(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/billing_subscriptions", + ...opts, + }); + }, + listBillingSubscriptions(opts = {}) { + return this._makeRequest({ + path: "/billing_subscriptions", + params: opts.params, + }); + }, + listInvoices(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/customer_invoices", + ...opts, + }); + }, + async *paginate({ + fn, params = {}, fieldName, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = ++page; + const data = await fn({ + params, + ...opts, + }); + const items = data[fieldName]; + for (const d of items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = items.length; + + } while (hasMore); }, }, }; diff --git a/components/pennylane/sources/common/base.mjs b/components/pennylane/sources/common/base.mjs new file mode 100644 index 0000000000000..43611e79f381e --- /dev/null +++ b/components/pennylane/sources/common/base.mjs @@ -0,0 +1,60 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import pennylane from "../../pennylane.app.mjs"; + +export default { + props: { + pennylane, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.pennylane.paginate({ + fn: this.getFunction(), + fieldName: this.getFieldName(), + }); + + let responseArray = []; + for await (const item of response) { + if (Date.parse(item.updated_at) <= lastDate) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastDate(responseArray[0].updated_at); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.source_id || item.id, + summary: this.getSummary(item), + ts: Date.parse(item.updated_at || item.activated_at || new Date()), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/pennylane/sources/new-billing-subscription/new-billing-subscription.mjs b/components/pennylane/sources/new-billing-subscription/new-billing-subscription.mjs new file mode 100644 index 0000000000000..7b331da212156 --- /dev/null +++ b/components/pennylane/sources/new-billing-subscription/new-billing-subscription.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "pennylane-new-billing-subscription", + name: "New Billing Subscription Created", + description: "Emit new event when a billing subscription is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.pennylane.listBillingSubscriptions; + }, + getFieldName() { + return "billing_subscriptions"; + }, + getSummary(item) { + return `New Billing Subscription: ${item.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/pennylane/sources/new-billing-subscription/test-event.mjs b/components/pennylane/sources/new-billing-subscription/test-event.mjs new file mode 100644 index 0000000000000..a9dd205c14c45 --- /dev/null +++ b/components/pennylane/sources/new-billing-subscription/test-event.mjs @@ -0,0 +1,106 @@ +export default { + "id": 0, + "next_occurrence": "string", + "prev_occurrence": "string", + "stopped_at": "2023-08-30T10:08:08.146343Z", + "start": "2023-01-01", + "finish": "2023-12-31", + "status": "string", + "mode": "string", + "email_settings": {}, + "activated_at": "2023-08-30T10:08:08.146343Z", + "payment_method": "string", + "recurring_rule": { + "day_of_month": [ + 0 + ], + "week_start": 0, + "day": [ + 0 + ], + "rule_type": "weekly", + "interval": 0, + "count": 12, + "until": "string" + }, + "customer": { + "first_name": "John", + "last_name": "Doe", + "gender": "mister", + "name": "Pennylane", + "reg_no": "XXXXXXXXX", + "vat_number": "FR12345678910", + "updated_at": "2023-08-30T10:08:08.146343Z", + "source_id": "38a1f19a-256d-4692-a8fe-0a16403f59ff", + "emails": [ + "hello@example.org" + ], + "billing_iban": "FRXX XXXX XXXX XXXX XXXX XXXX XXX", + "customer_type": "company", + "address": "4 rue du faubourg saint martin", + "postal_code": "75010", + "city": "Paris", + "country_alpha2": "FR", + "recipient": "string", + "billing_address": { + "address": "string", + "postal_code": "string", + "city": "string", + "country_alpha2": "string" + }, + "delivery_address": { + "address": "105 Rue Mondenard", + "postal_code": "33100", + "city": "Bordeaux", + "country_alpha2": "FR" + }, + "payment_conditions": "upon_receipt", + "phone": "+33123232323", + "reference": "This is a custom reference", + "notes": "This is a note", + "v2_id": 1234 + }, + "invoice_template": { + "label": "Invoice label", + "currency": "EUR", + "amount": "230.32", + "currency_amount": "230.32", + "currency_amount_before_tax": "196.32", + "exchange_rate": "1.0", + "payment_condition": "15_days", + "currency_tax": "34.0", + "language": "fr_FR", + "discount": "50.1", + "discount_type": "relative", + "special_mention": "Additional details", + "updated_at": "2023-08-30T10:08:08.146343Z", + "line_items_sections_attributes": [ + { + "title": "Line items section title", + "description": "Description of the line items section", + "rank": 1 + } + ], + "line_items": [ + { + "id": 444, + "label": "Demo label", + "unit": "piece", + "quantity": 12, + "amount": "50.4", + "currency_amount": "50.4", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor", + "product_id": "9bca2815-217a-4210-bed8-73139977b9a8", + "product_v2_id": 1234, + "vat_rate": "FR_200", + "currency_price_before_tax": "30", + "currency_tax": "10", + "discount": "25", + "discount_type": "relative", + "section_rank": 1, + "v2_id": 1234 + } + ] + }, + "v2_id": 1234 +} \ No newline at end of file diff --git a/components/pennylane/sources/new-customer-invoice/new-customer-invoice.mjs b/components/pennylane/sources/new-customer-invoice/new-customer-invoice.mjs new file mode 100644 index 0000000000000..0bb509b1f85c4 --- /dev/null +++ b/components/pennylane/sources/new-customer-invoice/new-customer-invoice.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "pennylane-new-customer-invoice", + name: "New Customer Invoice Created or Imported", + description: "Emit new event when a new invoice is created or imported.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.pennylane.listInvoices; + }, + getFieldName() { + return "invoices"; + }, + getSummary(item) { + return `New Invoice: ${item.invoice_number || item.label}`; + }, + }, + sampleEmit, +}; diff --git a/components/pennylane/sources/new-customer-invoice/test-event.mjs b/components/pennylane/sources/new-customer-invoice/test-event.mjs new file mode 100644 index 0000000000000..a12654d228b09 --- /dev/null +++ b/components/pennylane/sources/new-customer-invoice/test-event.mjs @@ -0,0 +1,114 @@ +export default { + "id": "wMoOACctiA", + "label": "Invoice label", + "invoice_number": "Invoice number", + "quote_group_uuid": "b50b8fe6-48ce-4380-b9b3-52eec688fc04", + "is_draft": true, + "is_estimate": true, + "currency": "EUR", + "amount": "230.32", + "currency_amount": "230.32", + "currency_amount_before_tax": "196.32", + "exchange_rate": 1, + "date": "2023-08-30", + "deadline": "2020-09-02", + "currency_tax": "34.0", + "language": "fr_FR", + "paid": false, + "status": "upcoming", + "discount": "50.1", + "discount_type": "relative", + "public_url": "https://app.pennylane.com/...", + "file_url": "https://app.pennylane.com/.../file.pdf", + "filename": "my_file.pdf", + "remaining_amount": "20.0", + "source": "email", + "special_mention": "Additional details", + "updated_at": "2023-08-30T10:08:08.146343Z", + "imputation_dates": { + "start_date": "2020-06-30", + "end_date": "2021-06-30" + }, + "line_items_sections_attributes": [ + { + "title": "Line items section title", + "description": "Description of the line items section", + "rank": 1 + } + ], + "line_items": [ + { + "id": 444, + "label": "Demo label", + "unit": "piece", + "quantity": 12, + "amount": "50.4", + "currency_amount": "50.4", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor", + "product_id": "9bca2815-217a-4210-bed8-73139977b9a8", + "product_v2_id": 1234, + "vat_rate": "FR_200", + "currency_price_before_tax": "30", + "currency_tax": "10", + "raw_currency_unit_price": "5", + "discount": "25", + "discount_type": "relative", + "section_rank": 1, + "v2_id": 1234 + } + ], + "categories": [ + { + "source_id": "38a1f19a-256d-4692-a8fe-0a16403f59ff", + "weight": 0.8, + "label": "Alimentaire", + "direction": "cash_in", + "created_at": "2023-08-30T10:08:08.146343Z", + "updated_at": "2023-08-30T10:08:08.146343Z", + "v2_id": 1234 + } + ], + "transactions_reference": { + "banking_provider": "bank", + "provider_field_name": "label", + "provider_field_value": "invoice_number" + }, + "payments": [ + { + "label": "string", + "created_at": "2023-08-30", + "currency_amount": "string" + } + ], + "matched_transactions": [ + { + "label": "string", + "amount": "string", + "group_uuid": "string", + "date": "2023-08-30", + "fee": "string", + "currency": "string" + } + ], + "pdf_invoice_free_text": "string", + "pdf_invoice_subject": "string", + "billing_subscription": { + "id": 0, + "v2_id": 1234 + }, + "credit_notes": [ + { + "id": "BCVPZQJ17V", + "amount": "230.32", + "tax": "34.0", + "currency": "EUR", + "currency_amount": "230.32", + "currency_tax": "230.32", + "currency_price_before_tax": "196.32", + "invoice_number": "Credit note number", + "draft": false, + "v2_id": 1234 + } + ], + "v2_id": 1234 +} \ No newline at end of file diff --git a/components/pennylane/sources/new-customer/new-customer.mjs b/components/pennylane/sources/new-customer/new-customer.mjs new file mode 100644 index 0000000000000..bd3dc384edbed --- /dev/null +++ b/components/pennylane/sources/new-customer/new-customer.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "pennylane-new-customer", + name: "New Customer Created", + description: "Emit new event when a customer is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.pennylane.listCustomers; + }, + getFieldName() { + return "customers"; + }, + getSummary(item) { + return `New Customer: ${item.name}`; + }, + }, + sampleEmit, +}; diff --git a/components/pennylane/sources/new-customer/test-event.mjs b/components/pennylane/sources/new-customer/test-event.mjs new file mode 100644 index 0000000000000..89b75ac2b2e00 --- /dev/null +++ b/components/pennylane/sources/new-customer/test-event.mjs @@ -0,0 +1,38 @@ +export default { + "name": "Pennylane", + "reg_no": "XXXXXXXXX", + "vat_number": "FR12345678910", + "updated_at": "2021-06-30T07:44:37.545Z", + "source_id": "38a1f19a-256d-4692-a8fe-0a16403f59ff", + "emails": [ + "hello@example.org" + ], + "billing_iban": "FRXX XXXX XXXX XXXX XXXX XXXX XXX", + "customer_type": "company", + "recipient": "On the behalf of John", + "billing_address": { + "address": "33 rue du mail", + "postal_code": "75010", + "city": "Paris", + "country_alpha2": "FR" + }, + "delivery_address": { + "address": "33 rue du mail", + "postal_code": "75010", + "city": "Paris", + "country_alpha2": "FR" + }, + "payment_conditions": "upon_receipt", + "phone": "+33123232323", + "reference": "This is a custom reference", + "notes": "This is a note", + "plan_item": { + "number": "string", + "label": "string", + "enabled": true, + "vat_rate": "string", + "country_alpha2": "string", + "description": "string" + }, + "v2_id": 1234 +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a33aa6f1955d..bdd7501f145bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7506,7 +7506,11 @@ importers: components/pendo: {} - components/pennylane: {} + components/pennylane: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/people_data_labs: dependencies: @@ -12164,10 +12168,10 @@ importers: version: 14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra: specifier: latest - version: 3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) nextra-theme-docs: specifier: latest - version: 3.2.5(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.3.0(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -22359,16 +22363,16 @@ packages: sass: optional: true - nextra-theme-docs@3.2.5: - resolution: {integrity: sha512-eF0j1VNNS1rFjZOfYqlrXISaCU3+MhZ9hhXY+TUydRlfELrFWpGzrpW6MiL7hq4JvUR7OBtHHs8+A+8AYcETBQ==} + nextra-theme-docs@3.3.0: + resolution: {integrity: sha512-4JSbDmsbtaYa2eKHsNymWy6So4/fAAXuNPSkjgQ3S+aLRiC730mR9djdkTd1iRca4+czetzBWaqxu+HwTVSSCA==} peerDependencies: next: '>=13' - nextra: 3.2.5 + nextra: 3.3.0 react: '>=18' react-dom: '>=18' - nextra@3.2.5: - resolution: {integrity: sha512-n665DRpI/brjHXM83G5LdlbYA2nOtjaLcWJs7mZS3gkuRDmEXpJj4XJ860xrhkYZW2iYoUMu32zzhPuFByU7VA==} + nextra@3.3.0: + resolution: {integrity: sha512-//+bQW3oKrpLrrIFD5HJow310+YcNYhGIgdM4y+EjYuIXScJcgp6Q4Upsq8c2s2fQKEJjeuf+hXKusx9fvH/2w==} engines: {node: '>=18'} peerDependencies: next: '>=13' @@ -23420,6 +23424,12 @@ packages: '@types/react': '>=18' react: '>=18' + react-medium-image-zoom@5.2.12: + resolution: {integrity: sha512-BbQ9jLBFxu6z+viH5tzQzAGqHOJQoYUM7iT1KUkamWKOO6vR1pC33os7LGLrHvOcyySMw74rUdoUCXFdeglwCQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-select@5.8.3: resolution: {integrity: sha512-lVswnIq8/iTj1db7XCG74M/3fbGB6ZaluCzvwPGT5ZOjCdL/k0CLWhEK0vCBLuU5bHTEf6Gj8jtSvi+3v+tO1w==} peerDependencies: @@ -39697,7 +39707,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.2.5(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.3.0(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -39705,13 +39715,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + nextra: 3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): + nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): dependencies: '@formatjs/intl-localematcher': 0.5.8 '@headlessui/react': 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -39738,6 +39748,7 @@ snapshots: p-limit: 6.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-medium-image-zoom: 5.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rehype-katex: 7.0.1 rehype-pretty-code: 0.14.0(shiki@1.24.0) rehype-raw: 7.0.0 @@ -41175,6 +41186,11 @@ snapshots: transitivePeerDependencies: - supports-color + react-medium-image-zoom@5.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-select@5.8.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0