Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,414 @@
{
"Swiss business software for accounting, invoicing, and project management": "Swiss business software for accounting, invoicing, and project management",
"Create Manual Entry": "Create Manual Entry",
"Create Company": "Create Company",
"Create Person": "Create Person",
"Update Person": "Update Person",
"Update Company": "Update Company",
"Find Account": "Find Account",
"Find Company": "Find Company",
"Find Person": "Find Person",
"Find Product": "Find Product",
"Find Country": "Find Country",
"Search Invoice": "Search Invoice",
"Search Order": "Search Order",
"Search Quote": "Search Quote",
"Create File": "Create File",
"Create Sales Invoice": "Create Sales Invoice",
"Export Invoice to PDF": "Export Invoice to PDF",
"Send Sales Invoice": "Send Sales Invoice",
"Create Product": "Create Product",
"Update Product": "Update Product",
"Create Sales Order": "Create Sales Order",
"Create Project": "Create Project",
"Create Sales Quote": "Create Sales Quote",
"Create Time Tracking": "Create Time Tracking",
"Custom API Call": "Custom API Call",
"Create a manual accounting entry (single, compound, or group)": "Create a manual accounting entry (single, compound, or group)",
"Create a new company contact": "Create a new company contact",
"Create a new person contact": "Create a new person contact",
"Update an existing person contact": "Update an existing person contact",
"Update an existing company contact": "Update an existing company contact",
"Search for accounts using various criteria": "Search for accounts using various criteria",
"Search for company contacts using various criteria": "Search for company contacts using various criteria",
"Search for person contacts using various criteria": "Search for person contacts using various criteria",
"Search for products by name or code": "Search for products by name or code",
"Search for countries using various criteria": "Search for countries using various criteria",
"Search for invoices using various criteria": "Search for invoices using various criteria",
"Search for orders using various criteria": "Search for orders using various criteria",
"Search for quotes using various criteria": "Search for quotes using various criteria",
"Upload a new file to Bexio": "Upload a new file to Bexio",
"Create a new product-based sales invoice": "Create a new product-based sales invoice",
"Export an existing sales invoice as a PDF document": "Export an existing sales invoice as a PDF document",
"Send a sales invoice to an email address": "Send a sales invoice to an email address",
"Create a new product or service": "Create a new product or service",
"Update an existing product or service": "Update an existing product or service",
"Create a new product-based sales order": "Create a new product-based sales order",
"Create a new project": "Create a new project",
"Create a new product-based sales quote": "Create a new product-based sales quote",
"Create a new timesheet entry": "Create a new timesheet entry",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Entry Type": "Entry Type",
"Booking Date": "Booking Date",
"Reference Number": "Reference Number",
"Entry Details": "Entry Details",
"Company Name": "Company Name",
"Company Addition": "Company Addition",
"Contact Number": "Contact Number",
"Salutation": "Salutation",
"Title": "Title",
"Street Name": "Street Name",
"House Number": "House Number",
"Address Addition": "Address Addition",
"Postcode": "Postcode",
"City": "City",
"Country": "Country",
"Email": "Email",
"Secondary Email": "Secondary Email",
"Phone": "Phone",
"Secondary Phone": "Secondary Phone",
"Mobile Phone": "Mobile Phone",
"Fax": "Fax",
"Website": "Website",
"Skype Name": "Skype Name",
"Remarks": "Remarks",
"Language": "Language",
"Contact Groups": "Contact Groups",
"Contact Sectors": "Contact Sectors",
"User": "User",
"Owner": "Owner",
"Last Name": "Last Name",
"First Name": "First Name",
"Salutation Form": "Salutation Form",
"Birthday": "Birthday",
"Person": "Person",
"Website URL": "Website URL",
"Company": "Company",
"Search Criteria": "Search Criteria",
"Limit": "Limit",
"Offset": "Offset",
"Order By": "Order By",
"Show Archived": "Show Archived",
"Order Direction": "Order Direction",
"File": "File",
"Document Number": "Document Number",
"Contact": "Contact",
"Contact Sub": "Contact Sub",
"Project": "Project",
"Bank Account": "Bank Account",
"Currency": "Currency",
"Payment Type": "Payment Type",
"Header": "Header",
"Footer": "Footer",
"Tax Type": "Tax Type",
"Tax is Net": "Tax is Net",
"Show Position Taxes": "Show Position Taxes",
"Valid From": "Valid From",
"Valid To": "Valid To",
"Manual Contact Address": "Manual Contact Address",
"Reference": "Reference",
"API Reference": "API Reference",
"Document Template": "Document Template",
"Invoice Positions": "Invoice Positions",
"Invoice": "Invoice",
"Letterhead": "Letterhead",
"Recipient Email": "Recipient Email",
"Subject": "Subject",
"Message": "Message",
"Mark as Open": "Mark as Open",
"Attach PDF": "Attach PDF",
"Product Type": "Product Type",
"Internal Code": "Internal Code",
"Internal Name": "Internal Name",
"Internal Description": "Internal Description",
"Supplier Code": "Supplier Code",
"Supplier Name": "Supplier Name",
"Supplier Description": "Supplier Description",
"Purchase Price": "Purchase Price",
"Sale Price": "Sale Price",
"Purchase Total": "Purchase Total",
"Sale Total": "Sale Total",
"Income Tax": "Income Tax",
"Expense Tax": "Expense Tax",
"Unit": "Unit",
"Is Stock Item": "Is Stock Item",
"Stock Location": "Stock Location",
"Stock Area": "Stock Area",
"Stock Number": "Stock Number",
"Minimum Stock": "Minimum Stock",
"Width": "Width",
"Height": "Height",
"Weight": "Weight",
"Volume": "Volume",
"Delivery Price": "Delivery Price",
"Article Group": "Article Group",
"Account": "Account",
"Expense Account": "Expense Account",
"Product": "Product",
"Delivery Address Type": "Delivery Address Type",
"Manual Delivery Address": "Manual Delivery Address",
"Order Positions": "Order Positions",
"Project Name": "Project Name",
"Start Date": "Start Date",
"End Date": "End Date",
"Comment": "Comment",
"Project Status": "Project Status",
"Project Type": "Project Type",
"Invoice Type": "Invoice Type",
"Invoice Type Amount": "Invoice Type Amount",
"Budget Type": "Budget Type",
"Budget Type Amount": "Budget Type Amount",
"Valid Until": "Valid Until",
"Viewed By Client At": "Viewed By Client At",
"Terms of Payment Template ID": "Terms of Payment Template ID",
"Quote Positions": "Quote Positions",
"Status": "Status",
"Client Service": "Client Service",
"Description": "Description",
"Allowable Bill": "Allowable Bill",
"Charge": "Charge",
"Sub Contact": "Sub Contact",
"Package ID": "Package ID",
"Milestone ID": "Milestone ID",
"Estimated Time": "Estimated Time",
"Tracking Type": "Tracking Type",
"Tracking Details": "Tracking Details",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"Choose the type of manual entry": "Choose the type of manual entry",
"Date of the booking (YYYY-MM-DD)": "Date of the booking (YYYY-MM-DD)",
"Reference number for the booking": "Reference number for the booking",
"Configure the entry details based on selected type": "Configure the entry details based on selected type",
"The name of the company": "The name of the company",
"Additional company name (e.g., \"Ltd.\", \"Inc.\")": "Additional company name (e.g., \"Ltd.\", \"Inc.\")",
"Contact number (leave empty to auto-assign)": "Contact number (leave empty to auto-assign)",
"Salutation for the company": "Salutation for the company",
"Title for the company": "Title for the company",
"Street name": "Street name",
"House number": "House number",
"Additional address information (e.g., \"Building C\")": "Additional address information (e.g., \"Building C\")",
"Postal code": "Postal code",
"Primary email address": "Primary email address",
"Secondary email address": "Secondary email address",
"Fixed phone number": "Fixed phone number",
"Secondary fixed phone number": "Secondary fixed phone number",
"Mobile phone number": "Mobile phone number",
"Fax number": "Fax number",
"Company website URL": "Company website URL",
"Skype username": "Skype username",
"Additional notes about the company": "Additional notes about the company",
"Preferred language": "Preferred language",
"Select contact groups": "Select contact groups",
"Select contact sectors": "Select contact sectors",
"User assigned to this contact": "User assigned to this contact",
"Owner of this contact": "Owner of this contact",
"The last name of the person": "The last name of the person",
"The first name of the person": "The first name of the person",
"Salutation for the person": "Salutation for the person",
"Salutation form (optional)": "Salutation form (optional)",
"Title for the person (e.g., Dr., Prof.)": "Title for the person (e.g., Dr., Prof.)",
"Birthday date (YYYY-MM-DD)": "Birthday date (YYYY-MM-DD)",
"Personal website URL": "Personal website URL",
"Additional notes about the person": "Additional notes about the person",
"Select the person to update": "Select the person to update",
"Contact number (leave empty to keep current or auto-assign)": "Contact number (leave empty to keep current or auto-assign)",
"Additional address information": "Additional address information",
"Select the company to update": "Select the company to update",
"Search criteria for finding accounts": "Search criteria for finding accounts",
"Maximum number of results to return (max 2000)": "Maximum number of results to return (max 2000)",
"Number of results to skip": "Number of results to skip",
"Search criteria for finding companies": "Search criteria for finding companies",
"Field to order results by": "Field to order results by",
"Show archived contacts only": "Show archived contacts only",
"Search criteria for finding persons": "Search criteria for finding persons",
"Field to sort the results by": "Field to sort the results by",
"Sort order": "Sort order",
"Include archived contacts in the search results": "Include archived contacts in the search results",
"Search criteria for finding products": "Search criteria for finding products",
"Search criteria for finding countries": "Search criteria for finding countries",
"Search criteria for finding invoices": "Search criteria for finding invoices",
"Search criteria for finding orders": "Search criteria for finding orders",
"Search criteria for finding quotes": "Search criteria for finding quotes",
"The file to upload": "The file to upload",
"Invoice number (required if automatic numbering is disabled)": "Invoice number (required if automatic numbering is disabled)",
"Invoice title": "Invoice title",
"The contact for this invoice": "The contact for this invoice",
"Contact sub-address (optional)": "Contact sub-address (optional)",
"User assigned to this invoice": "User assigned to this invoice",
"Project associated with this invoice": "Project associated with this invoice",
"Language for the invoice": "Language for the invoice",
"Bank account for payment": "Bank account for payment",
"Payment type for this invoice": "Payment type for this invoice",
"Header text for the invoice": "Header text for the invoice",
"Footer text for the invoice": "Footer text for the invoice",
"How taxes are handled": "How taxes are handled",
"If taxes are included, set to true to add taxes to total, false to include in total": "If taxes are included, set to true to add taxes to total, false to include in total",
"Show taxes for each position": "Show taxes for each position",
"Invoice valid from date (YYYY-MM-DD)": "Invoice valid from date (YYYY-MM-DD)",
"Invoice valid to date (YYYY-MM-DD)": "Invoice valid to date (YYYY-MM-DD)",
"Override contact address (leave empty to use contact address)": "Override contact address (leave empty to use contact address)",
"Reference number": "Reference number",
"Reference for API use (can only be edited via API)": "Reference for API use (can only be edited via API)",
"Document template for the invoice": "Document template for the invoice",
"Configure invoice line items": "Configure invoice line items",
"Select the invoice to export": "Select the invoice to export",
"Whether to include letterhead in the PDF": "Whether to include letterhead in the PDF",
"Select the invoice to send": "Select the invoice to send",
"Email address to send the invoice to": "Email address to send the invoice to",
"Email subject line": "Email subject line",
"Email message body. Must include \"[Network Link]\" placeholder": "Email message body. Must include \"[Network Link]\" placeholder",
"Mark the invoice as open": "Mark the invoice as open",
"Attach PDF directly to the email": "Attach PDF directly to the email",
"User associated with this product": "User associated with this product",
"Type of product": "Type of product",
"Internal product code": "Internal product code",
"Internal product name": "Internal product name",
"Internal product description": "Internal product description",
"Contact associated with this product": "Contact associated with this product",
"Supplier product code": "Supplier product code",
"Supplier product name": "Supplier product name",
"Supplier product description": "Supplier product description",
"Purchase price": "Purchase price",
"Sale price": "Sale price",
"Total purchase amount": "Total purchase amount",
"Total sale amount": "Total sale amount",
"Tax for income/sales": "Tax for income/sales",
"Tax for expenses/purchases": "Tax for expenses/purchases",
"Unit of measurement": "Unit of measurement",
"Whether this is a stock item (requires stock_edit scope)": "Whether this is a stock item (requires stock_edit scope)",
"Stock location": "Stock location",
"Stock area/place": "Stock area/place",
"Current stock quantity": "Current stock quantity",
"Minimum stock quantity": "Minimum stock quantity",
"Product width": "Product width",
"Product height": "Product height",
"Product weight": "Product weight",
"Product volume": "Product volume",
"Additional remarks": "Additional remarks",
"Delivery price": "Delivery price",
"Product group/category": "Product group/category",
"Account for this product": "Account for this product",
"Expense account for this product": "Expense account for this product",
"Select the product to update": "Select the product to update",
"Current stock quantity (can only be set if no bookings exist)": "Current stock quantity (can only be set if no bookings exist)",
"Order number (required if automatic numbering is disabled)": "Order number (required if automatic numbering is disabled)",
"Order title": "Order title",
"The contact for this order": "The contact for this order",
"User responsible for this order": "User responsible for this order",
"Project associated with this order": "Project associated with this order",
"Language for the order": "Language for the order",
"Currency for this order": "Currency for this order",
"Payment type for this order": "Payment type for this order",
"Header text for the order": "Header text for the order",
"Footer text for the order": "Footer text for the order",
"Order valid from date (YYYY-MM-DD)": "Order valid from date (YYYY-MM-DD)",
"Type of delivery address": "Type of delivery address",
"Custom delivery address (only used if delivery address type is set to custom)": "Custom delivery address (only used if delivery address type is set to custom)",
"Document template for the order": "Document template for the order",
"Configure order line items": "Configure order line items",
"Project number (required if automatic numbering is disabled)": "Project number (required if automatic numbering is disabled)",
"Name of the project": "Name of the project",
"Project start date": "Project start date",
"Project end date": "Project end date",
"Project comment/description": "Project comment/description",
"Status of the project": "Status of the project",
"Type of project": "Type of project",
"Contact associated with this project": "Contact associated with this project",
"Type of invoicing for this project": "Type of invoicing for this project",
"Amount for invoice type (only for hourly rate project or fix price)": "Amount for invoice type (only for hourly rate project or fix price)",
"Type of budget for this project": "Type of budget for this project",
"Amount for budget type (only for total budget costs or total budget hours)": "Amount for budget type (only for total budget costs or total budget hours)",
"User responsible for this project": "User responsible for this project",
"Quote number (required if automatic numbering is disabled)": "Quote number (required if automatic numbering is disabled)",
"Quote title": "Quote title",
"The contact for this quote": "The contact for this quote",
"User responsible for this quote": "User responsible for this quote",
"Project associated with this quote": "Project associated with this quote",
"Language for the quote": "Language for the quote",
"Currency for this quote": "Currency for this quote",
"Payment type for this quote": "Payment type for this quote",
"Header text for the quote": "Header text for the quote",
"Footer text for the quote": "Footer text for the quote",
"Quote valid from date (YYYY-MM-DD)": "Quote valid from date (YYYY-MM-DD)",
"Quote valid until date (YYYY-MM-DD)": "Quote valid until date (YYYY-MM-DD)",
"Date when the quote was viewed by the client": "Date when the quote was viewed by the client",
"Terms of payment template ID": "Terms of payment template ID",
"Document template for the quote": "Document template for the quote",
"Configure quote line items": "Configure quote line items",
"User for this timesheet entry": "User for this timesheet entry",
"Timesheet status": "Timesheet status",
"Business activity/client service": "Business activity/client service",
"Description of the work performed": "Description of the work performed",
"Whether this time is billable": "Whether this time is billable",
"Charge amount": "Charge amount",
"Contact associated with this timesheet": "Contact associated with this timesheet",
"Sub contact (optional)": "Sub contact (optional)",
"Project associated with this timesheet": "Project associated with this timesheet",
"Project package ID": "Project package ID",
"Project milestone ID": "Project milestone ID",
"Estimated time (format: HH:MM)": "Estimated time (format: HH:MM)",
"Type of time tracking": "Type of time tracking",
"Time tracking details": "Time tracking details",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"Single Entry - Simple one-line booking": "Single Entry - Simple one-line booking",
"Compound Entry - Total distributed across multiple accounts": "Compound Entry - Total distributed across multiple accounts",
"Group Entry - Multiple separate bookings with same reference": "Group Entry - Multiple separate bookings with same reference",
"ID": "ID",
"Updated At": "Updated At",
"Ascending": "Ascending",
"Descending": "Descending",
"Country Name": "Country Name",
"Country Code": "Country Code",
"Total": "Total",
"Total Net": "Total Net",
"Total Gross": "Total Gross",
"Including taxes": "Including taxes",
"Excluding taxes": "Excluding taxes",
"Exempt from taxes": "Exempt from taxes",
"With Letterhead": "With Letterhead",
"Without Letterhead": "Without Letterhead",
"Physical Product": "Physical Product",
"Service": "Service",
"Use Invoice Address": "Use Invoice Address",
"Use Custom Address": "Use Custom Address",
"Hourly rate for client services": "Hourly rate for client services",
"Hourly rate for employee": "Hourly rate for employee",
"Hourly rate for project": "Hourly rate for project",
"Fix price for project": "Fix price for project",
"Total budget costs": "Total budget costs",
"Total budget hours": "Total budget hours",
"Budget for each client services": "Budget for each client services",
"Budget for each employee": "Budget for each employee",
"Duration": "Duration",
"Range": "Range",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New/Updated Company": "New/Updated Company",
"New/Updated Person": "New/Updated Person",
"New Product": "New Product",
"New Project": "New Project",
"New Sales-Invoice": "New Sales-Invoice",
"New Order": "New Order",
"New Quotes": "New Quotes",
"Triggers when a company is added or updated": "Triggers when a company is added or updated",
"Triggers when a person is added or updated": "Triggers when a person is added or updated",
"Triggers when a new product is created": "Triggers when a new product is created",
"Triggers when a Project is created or updated with the chosen status": "Triggers when a Project is created or updated with the chosen status",
"Triggers when a Sales-Invoice is created or updated with the chosen status": "Triggers when a Sales-Invoice is created or updated with the chosen status",
"Triggers when an Order is created or updated with the chosen status": "Triggers when an Order is created or updated with the chosen status",
"Triggers when a quote is created or updated with the chosen status": "Triggers when a quote is created or updated with the chosen status",
"Filter projects by status (leave empty to trigger for all statuses)": "Filter projects by status (leave empty to trigger for all statuses)",
"Filter invoices by status (leave empty to trigger for all statuses)": "Filter invoices by status (leave empty to trigger for all statuses)",
"Filter orders by status (leave empty to trigger for all statuses)": "Filter orders by status (leave empty to trigger for all statuses)",
"Filter quotes by status (leave empty to trigger for all statuses)": "Filter quotes by status (leave empty to trigger for all statuses)"
}

View File

@@ -0,0 +1,130 @@
import { createCustomApiCallAction } from '@activepieces/pieces-common';
import {
createPiece,
PieceAuth,
} from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { BexioClient } from './lib/common/client';
import { createManualEntryAction } from './lib/actions/create-manual-entry';
import { createCompanyAction } from './lib/actions/create-company';
import { createPersonAction } from './lib/actions/create-person';
import { updatePersonAction } from './lib/actions/update-person';
import { createFileAction } from './lib/actions/create-file';
import { createSalesInvoiceAction } from './lib/actions/create-sales-invoice';
import { exportInvoicePdfAction } from './lib/actions/export-invoice-pdf';
import { sendSalesInvoiceAction } from './lib/actions/send-sales-invoice';
import { createProductAction } from './lib/actions/create-product';
import { updateProductAction } from './lib/actions/update-product';
import { createSalesOrderAction } from './lib/actions/create-sales-order';
import { createProjectAction } from './lib/actions/create-project';
import { createSalesQuoteAction } from './lib/actions/create-sales-quote';
import { createTimeTrackingAction } from './lib/actions/create-time-tracking';
import { updateCompanyAction } from './lib/actions/update-company';
import { findAccountAction } from './lib/actions/find-account';
import { findCompanyAction } from './lib/actions/find-company';
import { findPersonAction } from './lib/actions/find-person';
import { findProductAction } from './lib/actions/find-product';
import { findCountryAction } from './lib/actions/find-country';
import { searchInvoiceAction } from './lib/actions/search-invoice';
import { searchOrderAction } from './lib/actions/search-order';
import { searchQuoteAction } from './lib/actions/search-quote';
import { newUpdatedCompanyTrigger } from './lib/triggers/new-updated-company';
import { newSalesInvoiceTrigger } from './lib/triggers/new-sales-invoice';
import { newOrderTrigger } from './lib/triggers/new-order';
import { newUpdatedPersonTrigger } from './lib/triggers/new-updated-person';
import { newProductTrigger } from './lib/triggers/new-product';
import { newProjectTrigger } from './lib/triggers/new-project';
import { newQuoteTrigger } from './lib/triggers/new-quote';
export const bexioAuth = PieceAuth.OAuth2({
authUrl: 'https://auth.bexio.com/realms/bexio/protocol/openid-connect/auth',
tokenUrl: 'https://auth.bexio.com/realms/bexio/protocol/openid-connect/token',
required: true,
scope: [
'openid',
'profile',
'email',
'offline_access',
'contact_show',
'contact_edit',
'article_show',
'article_edit',
'kb_invoice_show',
'kb_invoice_edit',
'kb_offer_show',
'kb_offer_edit',
'kb_order_show',
'kb_order_edit',
'project_show',
'project_edit',
'task_show',
'monitoring_edit',
'accounting',
'file',
],
validate: async ({ auth }) => {
try {
const client = new BexioClient(auth);
await client.get('/users/me');
return {
valid: true,
};
} catch (error) {
return {
valid: false,
error: 'Authentication failed. Please check your connection and try again.',
};
}
},
});
export const bexio = createPiece({
displayName: 'Bexio',
description: 'Swiss business software for accounting, invoicing, and project management',
auth: bexioAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/bexio.png',
authors: ["onyedikachi-david"],
categories: [PieceCategory.BUSINESS_INTELLIGENCE],
actions: [
createManualEntryAction,
createCompanyAction,
createPersonAction,
updatePersonAction,
updateCompanyAction,
findAccountAction,
findCompanyAction,
findPersonAction,
findProductAction,
findCountryAction,
searchInvoiceAction,
searchOrderAction,
searchQuoteAction,
createFileAction,
createSalesInvoiceAction,
exportInvoicePdfAction,
sendSalesInvoiceAction,
createProductAction,
updateProductAction,
createSalesOrderAction,
createProjectAction,
createSalesQuoteAction,
createTimeTrackingAction,
createCustomApiCallAction({
baseUrl: () => 'https://api.bexio.com',
auth: bexioAuth,
authMapping: async (auth) => ({
Authorization: `Bearer ${(auth).access_token}`,
}),
}),
],
triggers: [
newUpdatedCompanyTrigger,
newUpdatedPersonTrigger,
newProductTrigger,
newProjectTrigger,
newSalesInvoiceTrigger,
newOrderTrigger,
newQuoteTrigger,
],
});

View File

@@ -0,0 +1,462 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createCompanyAction = createAction({
auth: bexioAuth,
name: 'create_company',
displayName: 'Create Company',
description: 'Create a new company contact',
props: {
name_1: Property.ShortText({
displayName: 'Company Name',
description: 'The name of the company',
required: true,
}),
name_2: Property.ShortText({
displayName: 'Company Addition',
description: 'Additional company name (e.g., "Ltd.", "Inc.")',
required: false,
}),
nr: Property.ShortText({
displayName: 'Contact Number',
description: 'Contact number (leave empty to auto-assign)',
required: false,
}),
salutation_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Salutation',
description: 'Salutation for the company',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const salutations = await client.get<Array<{ id: number; name: string }>>('/2.0/salutation');
return {
disabled: false,
options: salutations.map((sal) => ({
label: sal.name,
value: sal.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load salutations',
options: [],
};
}
},
}),
title_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Title',
description: 'Title for the company',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const titles = await client.get<Array<{ id: number; name: string }>>('/2.0/title');
return {
disabled: false,
options: titles.map((title) => ({
label: title.name,
value: title.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load titles',
options: [],
};
}
},
}),
street_name: Property.ShortText({
displayName: 'Street Name',
description: 'Street name',
required: false,
}),
house_number: Property.ShortText({
displayName: 'House Number',
description: 'House number',
required: false,
}),
address_addition: Property.ShortText({
displayName: 'Address Addition',
description: 'Additional address information (e.g., "Building C")',
required: false,
}),
postcode: Property.ShortText({
displayName: 'Postcode',
description: 'Postal code',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
description: 'City',
required: false,
}),
country_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Country',
description: 'Country',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const countries = await client.get<Array<{ id: number; name: string; name_short: string; iso3166_alpha2: string }>>('/2.0/country');
return {
disabled: false,
options: countries.map((country) => ({
label: country.name,
value: country.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load countries',
options: [],
};
}
},
}),
mail: Property.ShortText({
displayName: 'Email',
description: 'Primary email address',
required: false,
}),
mail_second: Property.ShortText({
displayName: 'Secondary Email',
description: 'Secondary email address',
required: false,
}),
phone_fixed: Property.ShortText({
displayName: 'Phone',
description: 'Fixed phone number',
required: false,
}),
phone_fixed_second: Property.ShortText({
displayName: 'Secondary Phone',
description: 'Secondary fixed phone number',
required: false,
}),
phone_mobile: Property.ShortText({
displayName: 'Mobile Phone',
description: 'Mobile phone number',
required: false,
}),
fax: Property.ShortText({
displayName: 'Fax',
description: 'Fax number',
required: false,
}),
url: Property.ShortText({
displayName: 'Website',
description: 'Company website URL',
required: false,
}),
skype_name: Property.ShortText({
displayName: 'Skype Name',
description: 'Skype username',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional notes about the company',
required: false,
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Preferred language',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
contact_group_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Groups',
description: 'Select contact groups',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_group');
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact groups',
options: [],
};
}
},
}),
contact_branch_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Sectors',
description: 'Select contact sectors',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const branches = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_branch');
return {
disabled: false,
options: branches.map((branch) => ({
label: branch.name,
value: branch.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact sectors',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User assigned to this contact',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
owner_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Owner',
description: 'Owner of this contact',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
contact_type_id: 1, // 1 = company
name_1: props['name_1'],
user_id: props['user_id'],
owner_id: props['owner_id'],
};
// Add optional fields only if they have values
if (props['name_2']) {
requestBody['name_2'] = props['name_2'];
}
if (props['nr'] !== undefined && props['nr'] !== null && props['nr'] !== '') {
requestBody['nr'] = props['nr'];
} else {
requestBody['nr'] = null; // Auto-assign
}
if (props['salutation_id']) {
requestBody['salutation_id'] = props['salutation_id'];
}
if (props['title_id']) {
requestBody['title_id'] = props['title_id'];
}
if (props['street_name']) {
requestBody['street_name'] = props['street_name'];
}
if (props['house_number']) {
requestBody['house_number'] = props['house_number'];
}
if (props['address_addition']) {
requestBody['address_addition'] = props['address_addition'];
}
if (props['postcode']) {
requestBody['postcode'] = props['postcode'];
}
if (props['city']) {
requestBody['city'] = props['city'];
}
if (props['country_id']) {
requestBody['country_id'] = props['country_id'];
}
if (props['mail']) {
requestBody['mail'] = props['mail'];
}
if (props['mail_second']) {
requestBody['mail_second'] = props['mail_second'];
}
if (props['phone_fixed']) {
requestBody['phone_fixed'] = props['phone_fixed'];
}
if (props['phone_fixed_second']) {
requestBody['phone_fixed_second'] = props['phone_fixed_second'];
}
if (props['phone_mobile']) {
requestBody['phone_mobile'] = props['phone_mobile'];
}
if (props['fax']) {
requestBody['fax'] = props['fax'];
}
if (props['url']) {
requestBody['url'] = props['url'];
}
if (props['skype_name']) {
requestBody['skype_name'] = props['skype_name'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['language_id']) {
requestBody['language_id'] = props['language_id'];
}
if (props['contact_group_ids'] && Array.isArray(props['contact_group_ids']) && props['contact_group_ids'].length > 0) {
requestBody['contact_group_ids'] = props['contact_group_ids'].join(',');
}
if (props['contact_branch_ids'] && Array.isArray(props['contact_branch_ids']) && props['contact_branch_ids'].length > 0) {
requestBody['contact_branch_ids'] = props['contact_branch_ids'].join(',');
}
const response = await client.post('/2.0/contact', requestBody);
return response;
},
});

View File

@@ -0,0 +1,60 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { HttpMethod, httpClient, AuthenticationType } from '@activepieces/pieces-common';
import { bexioCommon } from '../common/index';
import FormData from 'form-data';
export const createFileAction = createAction({
auth: bexioAuth,
name: 'create_file',
displayName: 'Create File',
description: 'Upload a new file to Bexio',
props: {
file: Property.File({
displayName: 'File',
description: 'The file to upload',
required: true,
}),
},
async run(context) {
const file = context.propsValue['file'];
// Create FormData for multipart/form-data upload
const formData = new FormData();
// Use Buffer directly - form-data package handles it correctly
formData.append('file', file.data, file.filename);
// Use httpClient directly for multipart/form-data since BexioClient uses JSON
const response = await httpClient.sendRequest<Array<{
id: number;
uuid: string;
name: string;
size_in_bytes: number;
extension: string;
mime_type: string;
uploader_email: string | null;
user_id: number;
is_archived: boolean;
source_id: number;
source_type: string | null;
is_referenced: boolean;
created_at: string;
}>>({
url: `${bexioCommon.baseUrl}/3.0/files`,
method: HttpMethod.POST,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.access_token,
},
headers: {
Accept: 'application/json',
// Don't set Content-Type manually - formData.getHeaders() will set it with boundary for multipart/form-data
...formData.getHeaders(),
},
body: formData,
});
return response.body;
},
});

View File

@@ -0,0 +1,443 @@
import { createAction, Property, DynamicPropsValue, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { bexioCommonProps } from '../common/props';
import { BexioManualEntry, BexioManualEntryResponse } from '../common/types';
export const createManualEntryAction = createAction({
auth: bexioAuth,
name: 'create_manual_entry',
displayName: 'Create Manual Entry',
description: 'Create a manual accounting entry (single, compound, or group)',
props: {
type: Property.StaticDropdown({
displayName: 'Entry Type',
description: 'Choose the type of manual entry',
required: true,
defaultValue: 'manual_single_entry',
options: {
disabled: false,
options: [
{
label: 'Single Entry - Simple one-line booking',
value: 'manual_single_entry',
},
{
label: 'Compound Entry - Total distributed across multiple accounts',
value: 'manual_compound_entry',
},
{
label: 'Group Entry - Multiple separate bookings with same reference',
value: 'manual_group_entry',
},
],
},
}),
date: Property.ShortText({
displayName: 'Booking Date',
description: 'Date of the booking (YYYY-MM-DD)',
required: true,
}),
reference_nr: Property.ShortText({
displayName: 'Reference Number',
description: 'Reference number for the booking',
required: false,
}),
entryFields: Property.DynamicProperties({
auth: bexioAuth,
displayName: 'Entry Details',
description: 'Configure the entry details based on selected type',
required: true,
refreshers: ['type', 'auth'],
props: async ({ type, auth }, ctx) => {
const entryType = (type as unknown as string) || 'manual_single_entry';
let accounts: Array<{ id: number; account_no: string; name: string }> = [];
let taxes: Array<{ id: number; name: string; percentage: string }> = [];
let currencies: Array<{ id: number; name: string }> = [];
if (auth) {
try {
const client = new BexioClient(auth);
accounts = await client.get<Array<{ id: number; account_no: string; name: string }>>('/accounts');
taxes = await client.get<Array<{ id: number; name: string; percentage: string }>>('/taxes');
currencies = await client.get<Array<{ id: number; name: string }>>('/3.0/currencies');
} catch (error) {
console.warn('Failed to fetch accounts/taxes/currencies:', error);
}
}
const accountOptions = accounts.map((acc) => ({
label: `${acc.account_no} - ${acc.name}`,
value: acc.id,
}));
const taxOptions = taxes.map((tax) => ({
label: `${tax.name} (${tax.percentage}%)`,
value: tax.id,
}));
const currencyOptions = currencies.map((curr) => ({
label: curr.name,
value: curr.id,
}));
if (entryType === 'manual_single_entry') {
return {
debit_account: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Debit Account',
description: 'The account to debit',
required: true,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Debit Account ID',
description: 'The account ID to debit',
required: true,
}),
credit_account: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Credit Account',
description: 'The account to credit',
required: true,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Credit Account ID',
description: 'The account ID to credit',
required: true,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'The transaction amount',
required: true,
}),
description: Property.ShortText({
displayName: 'Description',
description: 'Description of the transaction',
required: false,
}),
tax_id: taxOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax',
description: 'Tax rate (optional)',
required: false,
options: {
disabled: false,
options: taxOptions,
},
})
: Property.Number({
displayName: 'Tax ID',
description: 'Tax ID (optional)',
required: false,
}),
tax_account: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax Account',
description: 'Account for tax calculation',
required: false,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Tax Account ID',
description: 'Account ID for tax calculation',
required: false,
}),
currency_id: currencyOptions.length > 0
? Property.StaticDropdown({
displayName: 'Currency',
description: 'Currency (optional)',
required: false,
options: {
disabled: false,
options: currencyOptions,
},
})
: Property.Number({
displayName: 'Currency ID',
description: 'Currency ID (optional)',
required: false,
}),
currency_factor: Property.Number({
displayName: 'Currency Factor',
description: 'Exchange rate factor (defaults to 1)',
required: false,
}),
helper_info: Property.MarkDown({
value: '',
}),
entries: Property.Array({
displayName: 'Entries',
description: 'Not used for single entries',
required: false,
properties: {},
}),
};
} else {
const isCompound = entryType === 'manual_compound_entry';
return {
debit_account: Property.Number({
displayName: 'Debit Account',
description: 'Not used for compound/group entries',
required: false,
}),
credit_account: Property.Number({
displayName: 'Credit Account',
description: 'Not used for compound/group entries',
required: false,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'Not used for compound/group entries',
required: false,
}),
description: Property.ShortText({
displayName: 'Description',
description: 'Not used for compound/group entries',
required: false,
}),
tax_id: Property.Number({
displayName: 'Tax ID',
description: 'Not used for compound/group entries',
required: false,
}),
tax_account: Property.Number({
displayName: 'Tax Account',
description: 'Not used for compound/group entries',
required: false,
}),
currency_id: Property.Number({
displayName: 'Currency ID',
description: 'Not used for compound/group entries',
required: false,
}),
currency_factor: Property.Number({
displayName: 'Currency Factor',
description: 'Not used for compound/group entries',
required: false,
}),
helper_info: Property.MarkDown({
value: isCompound
? 'For compound entries, each entry should have EITHER debit_account_id OR credit_account_id (not both).'
: 'For group entries, each entry must have BOTH debit_account_id and credit_account_id.',
}),
entries: Property.Array({
displayName: 'Entries',
description: isCompound
? 'Multiple entries to distribute the total. Each entry should have either debit_account_id OR credit_account_id (not both).'
: 'Multiple complete bookings. Each entry must have both debit_account_id and credit_account_id.',
required: true,
properties: {
debit_account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Debit Account',
description: isCompound
? 'Account to debit (leave empty if this is a credit entry)'
: 'Account to debit',
required: !isCompound,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Debit Account ID',
description: isCompound
? 'Account ID to debit (leave 0 if this is a credit entry)'
: 'Account ID to debit',
required: !isCompound,
}),
credit_account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Credit Account',
description: isCompound
? 'Account to credit (leave empty if this is a debit entry)'
: 'Account to credit',
required: !isCompound,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Credit Account ID',
description: isCompound
? 'Account ID to credit (leave 0 if this is a debit entry)'
: 'Account ID to credit',
required: !isCompound,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'The transaction amount',
required: true,
}),
description: Property.ShortText({
displayName: 'Description',
description: 'Description for this entry',
required: false,
}),
tax_id: taxOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax',
description: 'Tax rate (optional)',
required: false,
options: {
disabled: false,
options: taxOptions,
},
})
: Property.Number({
displayName: 'Tax ID',
description: 'Tax ID (optional)',
required: false,
}),
tax_account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax Account',
description: 'Account for tax calculation (optional)',
required: false,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Tax Account ID',
description: 'Account ID for tax calculation (optional)',
required: false,
}),
currency_id: currencyOptions.length > 0
? Property.StaticDropdown({
displayName: 'Currency',
description: 'Currency (optional)',
required: false,
options: {
disabled: false,
options: currencyOptions,
},
})
: Property.Number({
displayName: 'Currency ID',
description: 'Currency ID (optional)',
required: false,
}),
currency_factor: Property.Number({
displayName: 'Currency Factor',
description: 'Exchange rate factor (defaults to 1)',
required: false,
}),
},
}),
};
}
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const entryType = context.propsValue.type as 'manual_single_entry' | 'manual_compound_entry' | 'manual_group_entry';
const entryFields = context.propsValue.entryFields as DynamicPropsValue;
let entries: BexioManualEntry[] = [];
if (entryType === 'manual_single_entry') {
const entry: BexioManualEntry = {
debit_account_id: entryFields['debit_account'] as number,
credit_account_id: entryFields['credit_account'] as number,
amount: entryFields['amount'] as number,
};
if (entryFields['description']) {
entry.description = entryFields['description'] as string;
}
if (entryFields['tax_id']) {
entry.tax_id = entryFields['tax_id'] as number;
}
if (entryFields['tax_account']) {
entry.tax_account_id = entryFields['tax_account'] as number;
}
if (entryFields['currency_id']) {
entry.currency_id = entryFields['currency_id'] as number;
}
if (entryFields['currency_factor']) {
entry.currency_factor = entryFields['currency_factor'] as number;
}
entries = [entry];
} else {
const entriesArray = entryFields['entries'] as Array<Record<string, unknown>> | undefined;
if (!entriesArray || entriesArray.length === 0) {
throw new Error('At least one entry is required for compound or group entries');
}
entries = entriesArray.map((entry) => {
const bexioEntry: BexioManualEntry = {
amount: entry['amount'] as number,
};
const debitAccountId = entry['debit_account_id'] as number | undefined;
if (debitAccountId && debitAccountId !== 0) {
bexioEntry.debit_account_id = debitAccountId;
}
const creditAccountId = entry['credit_account_id'] as number | undefined;
if (creditAccountId && creditAccountId !== 0) {
bexioEntry.credit_account_id = creditAccountId;
}
if (entry['description']) {
bexioEntry.description = entry['description'] as string;
}
const taxId = entry['tax_id'] as number | undefined;
if (taxId && taxId !== 0) {
bexioEntry.tax_id = taxId;
}
const taxAccountId = entry['tax_account_id'] as number | undefined;
if (taxAccountId && taxAccountId !== 0) {
bexioEntry.tax_account_id = taxAccountId;
}
const currencyId = entry['currency_id'] as number | undefined;
if (currencyId && currencyId !== 0) {
bexioEntry.currency_id = currencyId;
}
if (entry['currency_factor']) {
bexioEntry.currency_factor = entry['currency_factor'] as number;
}
return bexioEntry;
});
}
const requestBody = {
type: entryType,
date: context.propsValue.date,
reference_nr: context.propsValue.reference_nr,
entries,
};
const response = await client.post<BexioManualEntryResponse>(
'/accounts/manual_entries',
requestBody
);
return response;
},
});

View File

@@ -0,0 +1,479 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createPersonAction = createAction({
auth: bexioAuth,
name: 'create_person',
displayName: 'Create Person',
description: 'Create a new person contact',
props: {
name_1: Property.ShortText({
displayName: 'Last Name',
description: 'The last name of the person',
required: true,
}),
name_2: Property.ShortText({
displayName: 'First Name',
description: 'The first name of the person',
required: false,
}),
nr: Property.ShortText({
displayName: 'Contact Number',
description: 'Contact number (leave empty to auto-assign)',
required: false,
}),
salutation_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Salutation',
description: 'Salutation for the person',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const salutations = await client.get<Array<{ id: number; name: string }>>('/2.0/salutation');
return {
disabled: false,
options: salutations.map((sal) => ({
label: sal.name,
value: sal.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load salutations',
options: [],
};
}
},
}),
salutation_form: Property.Number({
displayName: 'Salutation Form',
description: 'Salutation form (optional)',
required: false,
}),
title_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Title',
description: 'Title for the person (e.g., Dr., Prof.)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const titles = await client.get<Array<{ id: number; name: string }>>('/2.0/title');
return {
disabled: false,
options: titles.map((title) => ({
label: title.name,
value: title.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load titles',
options: [],
};
}
},
}),
birthday: Property.ShortText({
displayName: 'Birthday',
description: 'Birthday date (YYYY-MM-DD)',
required: false,
}),
street_name: Property.ShortText({
displayName: 'Street Name',
description: 'Street name',
required: false,
}),
house_number: Property.ShortText({
displayName: 'House Number',
description: 'House number',
required: false,
}),
address_addition: Property.ShortText({
displayName: 'Address Addition',
description: 'Additional address information (e.g., "Building C")',
required: false,
}),
postcode: Property.ShortText({
displayName: 'Postcode',
description: 'Postal code',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
description: 'City',
required: false,
}),
country_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Country',
description: 'Country',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const countries = await client.get<Array<{ id: number; name: string; name_short: string; iso3166_alpha2: string }>>('/2.0/country');
return {
disabled: false,
options: countries.map((country) => ({
label: `${country.name} (${country.name_short})`,
value: country.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load countries',
options: [],
};
}
},
}),
mail: Property.ShortText({
displayName: 'Email',
description: 'Primary email address',
required: false,
}),
mail_second: Property.ShortText({
displayName: 'Secondary Email',
description: 'Secondary email address',
required: false,
}),
phone_fixed: Property.ShortText({
displayName: 'Phone',
description: 'Fixed phone number',
required: false,
}),
phone_fixed_second: Property.ShortText({
displayName: 'Secondary Phone',
description: 'Secondary fixed phone number',
required: false,
}),
phone_mobile: Property.ShortText({
displayName: 'Mobile Phone',
description: 'Mobile phone number',
required: false,
}),
fax: Property.ShortText({
displayName: 'Fax',
description: 'Fax number',
required: false,
}),
url: Property.ShortText({
displayName: 'Website',
description: 'Personal website URL',
required: false,
}),
skype_name: Property.ShortText({
displayName: 'Skype Name',
description: 'Skype username',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional notes about the person',
required: false,
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Preferred language',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
contact_group_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Groups',
description: 'Select contact groups',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_group');
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact groups',
options: [],
};
}
},
}),
contact_branch_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Sectors',
description: 'Select contact sectors',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const branches = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_branch');
return {
disabled: false,
options: branches.map((branch) => ({
label: branch.name,
value: branch.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact sectors',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User assigned to this contact',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
owner_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Owner',
description: 'Owner of this contact',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
contact_type_id: 2, // 2 = person
name_1: props['name_1'],
user_id: props['user_id'],
owner_id: props['owner_id'],
};
// Add optional fields only if they have values
if (props['name_2']) {
requestBody['name_2'] = props['name_2'];
}
if (props['nr'] !== undefined && props['nr'] !== null && props['nr'] !== '') {
requestBody['nr'] = props['nr'];
} else {
requestBody['nr'] = null; // Auto-assign
}
if (props['salutation_id']) {
requestBody['salutation_id'] = props['salutation_id'];
}
if (props['salutation_form']) {
requestBody['salutation_form'] = props['salutation_form'];
}
if (props['title_id']) {
requestBody['title_id'] = props['title_id'];
}
if (props['birthday']) {
requestBody['birthday'] = props['birthday'];
}
if (props['street_name']) {
requestBody['street_name'] = props['street_name'];
}
if (props['house_number']) {
requestBody['house_number'] = props['house_number'];
}
if (props['address_addition']) {
requestBody['address_addition'] = props['address_addition'];
}
if (props['postcode']) {
requestBody['postcode'] = props['postcode'];
}
if (props['city']) {
requestBody['city'] = props['city'];
}
if (props['country_id']) {
requestBody['country_id'] = props['country_id'];
}
if (props['mail']) {
requestBody['mail'] = props['mail'];
}
if (props['mail_second']) {
requestBody['mail_second'] = props['mail_second'];
}
if (props['phone_fixed']) {
requestBody['phone_fixed'] = props['phone_fixed'];
}
if (props['phone_fixed_second']) {
requestBody['phone_fixed_second'] = props['phone_fixed_second'];
}
if (props['phone_mobile']) {
requestBody['phone_mobile'] = props['phone_mobile'];
}
if (props['fax']) {
requestBody['fax'] = props['fax'];
}
if (props['url']) {
requestBody['url'] = props['url'];
}
if (props['skype_name']) {
requestBody['skype_name'] = props['skype_name'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['language_id']) {
requestBody['language_id'] = props['language_id'];
}
if (props['contact_group_ids'] && Array.isArray(props['contact_group_ids']) && props['contact_group_ids'].length > 0) {
requestBody['contact_group_ids'] = props['contact_group_ids'].join(',');
}
if (props['contact_branch_ids'] && Array.isArray(props['contact_branch_ids']) && props['contact_branch_ids'].length > 0) {
requestBody['contact_branch_ids'] = props['contact_branch_ids'].join(',');
}
const response = await client.post('/2.0/contact', requestBody);
return response;
},
});

View File

@@ -0,0 +1,561 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { bexioCommonProps } from '../common/props';
export const createProductAction = createAction({
auth: bexioAuth,
name: 'create_product',
displayName: 'Create Product',
description: 'Create a new product or service',
props: {
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User associated with this product',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
article_type_id: Property.StaticDropdown({
displayName: 'Product Type',
description: 'Type of product',
required: true,
defaultValue: 1,
options: {
disabled: false,
options: [
{ label: 'Physical Product', value: 1 },
{ label: 'Service', value: 2 },
],
},
}),
intern_code: Property.ShortText({
displayName: 'Internal Code',
description: 'Internal product code',
required: true,
}),
intern_name: Property.ShortText({
displayName: 'Internal Name',
description: 'Internal product name',
required: true,
}),
intern_description: Property.LongText({
displayName: 'Internal Description',
description: 'Internal product description',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'Contact associated with this product',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
deliverer_code: Property.ShortText({
displayName: 'Supplier Code',
description: 'Supplier product code',
required: false,
}),
deliverer_name: Property.ShortText({
displayName: 'Supplier Name',
description: 'Supplier product name',
required: false,
}),
deliverer_description: Property.LongText({
displayName: 'Supplier Description',
description: 'Supplier product description',
required: false,
}),
purchase_price: Property.ShortText({
displayName: 'Purchase Price',
description: 'Purchase price',
required: false,
}),
sale_price: Property.ShortText({
displayName: 'Sale Price',
description: 'Sale price',
required: false,
}),
purchase_total: Property.Number({
displayName: 'Purchase Total',
description: 'Total purchase amount',
required: false,
}),
sale_total: Property.Number({
displayName: 'Sale Total',
description: 'Total sale amount',
required: false,
}),
currency_id: bexioCommonProps.currency({
displayName: 'Currency',
required: false,
}),
tax_income_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Income Tax',
description: 'Tax for income/sales',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const taxes = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
type?: string;
}>>('/3.0/taxes');
return {
disabled: false,
options: taxes
.filter((tax) => tax.type === 'sales_tax' || !tax.type)
.map((tax) => ({
label: tax.display_name || tax.name,
value: tax.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load taxes',
options: [],
};
}
},
}),
tax_expense_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Expense Tax',
description: 'Tax for expenses/purchases',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const taxes = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
type?: string;
}>>('/3.0/taxes');
return {
disabled: false,
options: taxes
.filter((tax) => tax.type === 'pre_tax' || !tax.type)
.map((tax) => ({
label: tax.display_name || tax.name,
value: tax.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load taxes',
options: [],
};
}
},
}),
unit_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Unit',
description: 'Unit of measurement',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const units = await client.get<Array<{ id: number; name: string }>>('/2.0/unit');
return {
disabled: false,
options: units.map((unit) => ({
label: unit.name,
value: unit.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load units',
options: [],
};
}
},
}),
is_stock: Property.Checkbox({
displayName: 'Is Stock Item',
description: 'Whether this is a stock item (requires stock_edit scope)',
required: false,
defaultValue: false,
}),
stock_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Stock Location',
description: 'Stock location',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
// TODO: Need to confirm endpoint - assuming /2.0/stock or /2.0/stock_location
const stocks = await client.get<Array<{ id: number; name: string }>>('/2.0/stock').catch(() => []);
return {
disabled: false,
options: stocks.map((stock) => ({
label: stock.name,
value: stock.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load stock locations',
options: [],
};
}
},
}),
stock_place_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Stock Area',
description: 'Stock area/place',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
// TODO: Need to confirm endpoint - assuming /2.0/stock_place or /2.0/stock_area
const stockPlaces = await client.get<Array<{ id: number; name: string }>>('/2.0/stock_place').catch(() => []);
return {
disabled: false,
options: stockPlaces.map((place) => ({
label: place.name,
value: place.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load stock areas',
options: [],
};
}
},
}),
stock_nr: Property.Number({
displayName: 'Stock Number',
description: 'Current stock quantity',
required: true,
defaultValue: 0,
}),
stock_min_nr: Property.Number({
displayName: 'Minimum Stock',
description: 'Minimum stock quantity',
required: true,
defaultValue: 0,
}),
width: Property.Number({
displayName: 'Width',
description: 'Product width',
required: false,
}),
height: Property.Number({
displayName: 'Height',
description: 'Product height',
required: false,
}),
weight: Property.Number({
displayName: 'Weight',
description: 'Product weight',
required: false,
}),
volume: Property.Number({
displayName: 'Volume',
description: 'Product volume',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional remarks',
required: false,
}),
delivery_price: Property.Number({
displayName: 'Delivery Price',
description: 'Delivery price',
required: false,
}),
article_group_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Article Group',
description: 'Product group/category',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
// TODO: Need to confirm endpoint - assuming /2.0/article_group
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/article_group').catch(() => []);
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load article groups',
options: [],
};
}
},
}),
account_id: bexioCommonProps.account({
displayName: 'Account',
description: 'Account for this product',
required: false,
}),
expense_account_id: bexioCommonProps.account({
displayName: 'Expense Account',
description: 'Expense account for this product',
required: false,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const props = propsValue;
const requestBody: Record<string, unknown> = {
user_id: props['user_id'],
article_type_id: props['article_type_id'],
intern_code: props['intern_code'],
intern_name: props['intern_name'],
stock_nr: props['stock_nr'] || 0,
stock_min_nr: props['stock_min_nr'] || 0,
};
if (props['intern_description']) {
requestBody['intern_description'] = props['intern_description'];
}
if (props['contact_id']) {
requestBody['contact_id'] = props['contact_id'];
}
if (props['deliverer_code']) {
requestBody['deliverer_code'] = props['deliverer_code'];
}
if (props['deliverer_name']) {
requestBody['deliverer_name'] = props['deliverer_name'];
}
if (props['deliverer_description']) {
requestBody['deliverer_description'] = props['deliverer_description'];
}
if (props['purchase_price']) {
requestBody['purchase_price'] = props['purchase_price'];
}
if (props['sale_price']) {
requestBody['sale_price'] = props['sale_price'];
}
if (props['purchase_total']) {
requestBody['purchase_total'] = props['purchase_total'];
}
if (props['sale_total']) {
requestBody['sale_total'] = props['sale_total'];
}
if (props['currency_id']) {
requestBody['currency_id'] = props['currency_id'];
}
if (props['tax_income_id']) {
requestBody['tax_income_id'] = props['tax_income_id'];
}
if (props['tax_expense_id']) {
requestBody['tax_expense_id'] = props['tax_expense_id'];
}
if (props['unit_id']) {
requestBody['unit_id'] = props['unit_id'];
}
if (props['is_stock'] !== undefined) {
requestBody['is_stock'] = props['is_stock'];
}
if (props['stock_id']) {
requestBody['stock_id'] = props['stock_id'];
}
if (props['stock_place_id']) {
requestBody['stock_place_id'] = props['stock_place_id'];
}
if (props['width']) {
requestBody['width'] = props['width'];
}
if (props['height']) {
requestBody['height'] = props['height'];
}
if (props['weight']) {
requestBody['weight'] = props['weight'];
}
if (props['volume']) {
requestBody['volume'] = props['volume'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['delivery_price']) {
requestBody['delivery_price'] = props['delivery_price'];
}
if (props['article_group_id']) {
requestBody['article_group_id'] = props['article_group_id'];
}
if (props['account_id']) {
requestBody['account_id'] = props['account_id'];
}
if (props['expense_account_id']) {
requestBody['expense_account_id'] = props['expense_account_id'];
}
const response = await client.post<{
id: number;
user_id: number;
article_type_id: number;
intern_code: string;
intern_name: string;
stock_nr: number;
stock_min_nr: number;
}>('/2.0/article', requestBody);
return response;
},
});

View File

@@ -0,0 +1,342 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createProjectAction = createAction({
auth: bexioAuth,
name: 'create_project',
displayName: 'Create Project',
description: 'Create a new project',
props: {
document_nr: Property.ShortText({
displayName: 'Document Number',
description: 'Project number (required if automatic numbering is disabled)',
required: false,
}),
name: Property.ShortText({
displayName: 'Project Name',
description: 'Name of the project',
required: true,
}),
start_date: Property.DateTime({
displayName: 'Start Date',
description: 'Project start date',
required: false,
}),
end_date: Property.DateTime({
displayName: 'End Date',
description: 'Project end date',
required: false,
}),
comment: Property.LongText({
displayName: 'Comment',
description: 'Project comment/description',
required: false,
}),
pr_state_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project Status',
description: 'Status of the project',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const states = await client.get<Array<{ id: number; name: string }>>('/2.0/pr_project_state');
return {
disabled: false,
options: states.map((state) => ({
label: state.name,
value: state.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load project statuses',
options: [],
};
}
},
}),
pr_project_type_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project Type',
description: 'Type of project',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const types = await client.get<Array<{ id: number; name: string }>>('/2.0/pr_project_type');
return {
disabled: false,
options: types.map((type) => ({
label: type.name,
value: type.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load project types',
options: [],
};
}
},
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'Contact associated with this project',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
contact_sub_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact Sub',
description: 'Contact sub-address (optional)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
pr_invoice_type_id: Property.StaticDropdown({
displayName: 'Invoice Type',
description: 'Type of invoicing for this project',
required: false,
options: {
disabled: false,
options: [
{ label: 'Hourly rate for client services', value: 1 },
{ label: 'Hourly rate for employee', value: 2 },
{ label: 'Hourly rate for project', value: 3 },
{ label: 'Fix price for project', value: 4 },
],
},
}),
pr_invoice_type_amount: Property.ShortText({
displayName: 'Invoice Type Amount',
description: 'Amount for invoice type (only for hourly rate project or fix price)',
required: false,
}),
pr_budget_type_id: Property.StaticDropdown({
displayName: 'Budget Type',
description: 'Type of budget for this project',
required: false,
options: {
disabled: false,
options: [
{ label: 'Total budget costs', value: 1 },
{ label: 'Total budget hours', value: 2 },
{ label: 'Budget for each client services', value: 3 },
{ label: 'Budget for each employee', value: 4 },
],
},
}),
pr_budget_type_amount: Property.ShortText({
displayName: 'Budget Type Amount',
description: 'Amount for budget type (only for total budget costs or total budget hours)',
required: false,
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User responsible for this project',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const props = propsValue;
const requestBody: Record<string, unknown> = {
name: props['name'],
pr_state_id: props['pr_state_id'],
pr_project_type_id: props['pr_project_type_id'],
contact_id: props['contact_id'],
user_id: props['user_id'],
};
if (props['document_nr']) {
requestBody['document_nr'] = props['document_nr'];
}
if (props['start_date']) {
// Convert DateTime to ISO string format expected by API
const startDate = props['start_date'] as string;
requestBody['start_date'] = startDate;
}
if (props['end_date']) {
// Convert DateTime to ISO string format expected by API
const endDate = props['end_date'] as string;
requestBody['end_date'] = endDate;
}
if (props['comment']) {
requestBody['comment'] = props['comment'];
}
if (props['contact_sub_id']) {
requestBody['contact_sub_id'] = props['contact_sub_id'];
}
if (props['pr_invoice_type_id']) {
requestBody['pr_invoice_type_id'] = props['pr_invoice_type_id'];
}
if (props['pr_invoice_type_amount']) {
requestBody['pr_invoice_type_amount'] = props['pr_invoice_type_amount'];
}
if (props['pr_budget_type_id']) {
requestBody['pr_budget_type_id'] = props['pr_budget_type_id'];
}
if (props['pr_budget_type_amount']) {
requestBody['pr_budget_type_amount'] = props['pr_budget_type_amount'];
}
const response = await client.post<{
id: number;
uuid: string;
nr: string;
name: string;
pr_state_id: number;
pr_project_type_id: number;
contact_id: number;
user_id: number;
}>('/2.0/pr_project', requestBody);
return response;
},
});

View File

@@ -0,0 +1,654 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { bexioCommonProps } from '../common/props';
export const createSalesInvoiceAction = createAction({
auth: bexioAuth,
name: 'create_sales_invoice',
displayName: 'Create Sales Invoice',
description: 'Create a new product-based sales invoice',
props: {
document_nr: Property.ShortText({
displayName: 'Document Number',
description: 'Invoice number (required if automatic numbering is disabled)',
required: false,
}),
title: Property.ShortText({
displayName: 'Title',
description: 'Invoice title',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'The contact for this invoice',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
contact_sub_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact Sub',
description: 'Contact sub-address (optional)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User assigned to this invoice',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
pr_project_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project',
description: 'Project associated with this invoice',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const projects = await client.get<Array<{
id: number;
name: string;
nr?: string;
}>>('/2.0/pr_project');
return {
disabled: false,
options: projects.map((project) => ({
label: project.nr ? `${project.name} (#${project.nr})` : project.name,
value: project.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load projects',
options: [],
};
}
},
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Language for the invoice',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
bank_account_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Bank Account',
description: 'Bank account for payment',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const bankAccounts = await client.get<Array<{
id: number;
name: string;
iban_nr?: string;
bank_account_nr?: string;
}>>('/3.0/banking/accounts');
return {
disabled: false,
options: bankAccounts.map((account) => {
const iban = account.iban_nr || account.bank_account_nr;
return {
label: iban ? `${account.name} (${iban})` : account.name,
value: account.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load bank accounts',
options: [],
};
}
},
}),
currency_id: bexioCommonProps.currency({
displayName: 'Currency',
required: true,
}),
payment_type_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Payment Type',
description: 'Payment type for this invoice',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const paymentTypes = await client.get<Array<{ id: number; name: string }>>('/2.0/payment_type');
return {
disabled: false,
options: paymentTypes.map((type) => ({
label: type.name,
value: type.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load payment types',
options: [],
};
}
},
}),
header: Property.LongText({
displayName: 'Header',
description: 'Header text for the invoice',
required: false,
}),
footer: Property.LongText({
displayName: 'Footer',
description: 'Footer text for the invoice',
required: false,
}),
mwst_type: Property.StaticDropdown({
displayName: 'Tax Type',
description: 'How taxes are handled',
required: true,
defaultValue: 0,
options: {
disabled: false,
options: [
{ label: 'Including taxes', value: 0 },
{ label: 'Excluding taxes', value: 1 },
{ label: 'Exempt from taxes', value: 2 },
],
},
}),
mwst_is_net: Property.Checkbox({
displayName: 'Tax is Net',
description: 'If taxes are included, set to true to add taxes to total, false to include in total',
required: false,
defaultValue: false,
}),
show_position_taxes: Property.Checkbox({
displayName: 'Show Position Taxes',
description: 'Show taxes for each position',
required: false,
defaultValue: false,
}),
is_valid_from: Property.ShortText({
displayName: 'Valid From',
description: 'Invoice valid from date (YYYY-MM-DD)',
required: false,
}),
is_valid_to: Property.ShortText({
displayName: 'Valid To',
description: 'Invoice valid to date (YYYY-MM-DD)',
required: false,
}),
contact_address_manual: Property.LongText({
displayName: 'Manual Contact Address',
description: 'Override contact address (leave empty to use contact address)',
required: false,
}),
reference: Property.ShortText({
displayName: 'Reference',
description: 'Reference number',
required: false,
}),
api_reference: Property.ShortText({
displayName: 'API Reference',
description: 'Reference for API use (can only be edited via API)',
required: false,
}),
template_slug: Property.Dropdown({
auth: bexioAuth,
displayName: 'Document Template',
description: 'Document template for the invoice',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const templates = await client.get<Array<{
template_slug: string;
name: string;
is_default: boolean;
default_for_document_types: string[];
}>>('/3.0/document_templates');
return {
disabled: false,
options: templates.map((template) => ({
label: template.is_default ? `${template.name} (Default)` : template.name,
value: template.template_slug,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load templates',
options: [],
};
}
},
}),
positionFields: Property.DynamicProperties({
auth: bexioAuth,
displayName: 'Invoice Positions',
description: 'Configure invoice line items',
required: true,
refreshers: ['auth'],
props: async ({ auth }) => {
let units: Array<{ id: number; name: string }> = [];
let accounts: Array<{ id: number; account_no: string; name: string }> = [];
let taxes: Array<{ id: number; name: string; percentage: string }> = [];
if (auth) {
try {
const client = new BexioClient(auth);
units = await client.get<Array<{ id: number; name: string }>>('/2.0/unit').catch(() => []);
accounts = await client.get<Array<{ id: number; account_no: string; name: string }>>('/accounts').catch(() => []);
const taxesResponse = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
}>>('/3.0/taxes?types=sales_tax&scope=active').catch(() => []);
taxes = taxesResponse.map((tax) => ({
id: tax.id,
name: tax.display_name || tax.name,
percentage: tax.value.toString(),
}));
} catch (error) {
// Ignore error, use empty array as fallback
}
}
const unitOptions = units.map((unit) => ({ label: unit.name, value: unit.id }));
const accountOptions = accounts.map((acc) => ({ label: `${acc.account_no} - ${acc.name}`, value: acc.id }));
const taxOptions = taxes.map((tax) => ({ label: `${tax.name} (${tax.percentage}%)`, value: tax.id }));
return {
positions: Property.Array({
displayName: 'Positions',
description: 'Invoice line items',
required: true,
properties: {
type: Property.StaticDropdown({
displayName: 'Position Type',
description: 'Type of invoice position',
required: true,
defaultValue: 'KbPositionCustom',
options: {
disabled: false,
options: [
{ label: 'Custom Position', value: 'KbPositionCustom' },
{ label: 'Article Position', value: 'KbPositionArticle' },
{ label: 'Text Position', value: 'KbPositionText' },
{ label: 'Subtotal Position', value: 'KbPositionSubtotal' },
{ label: 'Page Break Position', value: 'KbPositionPagebreak' },
{ label: 'Discount Position', value: 'KbPositionDiscount' },
],
},
}),
amount: Property.ShortText({
displayName: 'Amount',
description: 'Quantity/amount',
required: true,
}),
unit_id: unitOptions.length > 0
? Property.StaticDropdown({
displayName: 'Unit',
description: 'Unit of measurement',
required: false,
options: {
disabled: false,
options: unitOptions,
},
})
: Property.Number({
displayName: 'Unit ID',
description: 'Unit ID (use List Units action to find IDs)',
required: false,
}),
account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Account',
description: 'Account for this position',
required: false,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Account ID',
description: 'Account ID (use List Accounts action to find IDs)',
required: false,
}),
tax_id: taxOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax',
description: 'Tax rate (only active sales taxes)',
required: false,
options: {
disabled: false,
options: taxOptions,
},
})
: Property.Number({
displayName: 'Tax ID',
description: 'Tax ID (use List Taxes action to find IDs)',
required: false,
}),
text: Property.LongText({
displayName: 'Description',
description: 'Position description/text',
required: false,
}),
unit_price: Property.ShortText({
displayName: 'Unit Price',
description: 'Price per unit (max 6 decimals)',
required: false,
}),
discount_in_percent: Property.ShortText({
displayName: 'Discount (%)',
description: 'Discount percentage (max 6 decimals)',
required: false,
}),
parent_id: Property.Number({
displayName: 'Parent ID',
description: 'Parent position ID (for grouped positions)',
required: false,
}),
},
}),
};
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const props = context.propsValue;
const requestBody: Record<string, unknown> = {};
if (props['user_id']) {
requestBody['user_id'] = props['user_id'];
}
if (props['language_id']) {
requestBody['language_id'] = props['language_id'];
}
if (props['bank_account_id']) {
requestBody['bank_account_id'] = props['bank_account_id'];
}
if (props['currency_id']) {
requestBody['currency_id'] = props['currency_id'];
}
if (props['payment_type_id']) {
requestBody['payment_type_id'] = props['payment_type_id'];
}
if (props['document_nr']) {
requestBody['document_nr'] = props['document_nr'];
}
if (props['title'] !== undefined) {
requestBody['title'] = props['title'] || null;
}
if (props['contact_id']) {
requestBody['contact_id'] = props['contact_id'];
}
if (props['contact_sub_id']) {
requestBody['contact_sub_id'] = props['contact_sub_id'];
}
if (props['pr_project_id']) {
requestBody['pr_project_id'] = props['pr_project_id'];
}
if (props['header']) {
requestBody['header'] = props['header'];
}
if (props['footer']) {
requestBody['footer'] = props['footer'];
}
if (props['mwst_type'] !== undefined) {
requestBody['mwst_type'] = props['mwst_type'];
}
if (props['mwst_is_net'] !== undefined) {
requestBody['mwst_is_net'] = props['mwst_is_net'];
}
if (props['show_position_taxes'] !== undefined) {
requestBody['show_position_taxes'] = props['show_position_taxes'];
}
if (props['is_valid_from']) {
requestBody['is_valid_from'] = props['is_valid_from'];
}
if (props['is_valid_to']) {
requestBody['is_valid_to'] = props['is_valid_to'];
}
if (props['contact_address_manual']) {
requestBody['contact_address_manual'] = props['contact_address_manual'];
}
if (props['reference']) {
requestBody['reference'] = props['reference'];
}
if (props['api_reference']) {
requestBody['api_reference'] = props['api_reference'];
}
if (props['template_slug']) {
requestBody['template_slug'] = props['template_slug'];
}
const positionFields = props['positionFields'] as Record<string, unknown>;
if (positionFields && positionFields['positions'] && Array.isArray(positionFields['positions'])) {
requestBody['positions'] = (positionFields['positions'] as Array<Record<string, unknown>>).map((position) => {
const pos: Record<string, unknown> = {
type: position['type'] || 'KbPositionCustom',
};
if (position['amount']) {
pos['amount'] = position['amount'];
}
if (position['unit_id']) {
pos['unit_id'] = position['unit_id'];
}
if (position['account_id']) {
pos['account_id'] = position['account_id'];
}
if (position['tax_id']) {
pos['tax_id'] = position['tax_id'];
}
if (position['text']) {
pos['text'] = position['text'];
}
if (position['unit_price']) {
pos['unit_price'] = position['unit_price'];
}
if (position['discount_in_percent']) {
pos['discount_in_percent'] = position['discount_in_percent'];
}
if (position['parent_id']) {
pos['parent_id'] = position['parent_id'];
}
return pos;
});
}
const response = await client.post('/2.0/kb_invoice', requestBody);
return response;
},
});

View File

@@ -0,0 +1,687 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createSalesOrderAction = createAction({
auth: bexioAuth,
name: 'create_sales_order',
displayName: 'Create Sales Order',
description: 'Create a new product-based sales order',
props: {
document_nr: Property.ShortText({
displayName: 'Document Number',
description: 'Order number (required if automatic numbering is disabled)',
required: false,
}),
title: Property.ShortText({
displayName: 'Title',
description: 'Order title',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'The contact for this order',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
contact_sub_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact Sub',
description: 'Contact sub-address (optional)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User responsible for this order',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
pr_project_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project',
description: 'Project associated with this order',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const projects = await client.get<Array<{
id: number;
name: string;
nr?: string;
}>>('/2.0/pr_project');
return {
disabled: false,
options: projects.map((project) => ({
label: project.nr ? `${project.name} (#${project.nr})` : project.name,
value: project.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load projects',
options: [],
};
}
},
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Language for the order',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
bank_account_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Bank Account',
description: 'Bank account for payment',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const bankAccounts = await client.get<Array<{
id: number;
name: string;
iban_nr?: string;
bank_account_nr?: string;
}>>('/3.0/banking/accounts');
return {
disabled: false,
options: bankAccounts.map((account) => {
const iban = account.iban_nr || account.bank_account_nr;
return {
label: iban ? `${account.name} (${iban})` : account.name,
value: account.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load bank accounts',
options: [],
};
}
},
}),
currency_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Currency',
description: 'Currency for this order',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const currencies = await client.get<Array<{ id: number; name: string }>>('/currencies');
return {
disabled: false,
options: currencies.map((currency) => ({
label: currency.name,
value: currency.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load currencies',
options: [],
};
}
},
}),
payment_type_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Payment Type',
description: 'Payment type for this order',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const paymentTypes = await client.get<Array<{ id: number; name: string }>>('/2.0/payment_type');
return {
disabled: false,
options: paymentTypes.map((type) => ({
label: type.name,
value: type.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load payment types',
options: [],
};
}
},
}),
header: Property.LongText({
displayName: 'Header',
description: 'Header text for the order',
required: false,
}),
footer: Property.LongText({
displayName: 'Footer',
description: 'Footer text for the order',
required: false,
}),
mwst_type: Property.StaticDropdown({
displayName: 'Tax Type',
description: 'How taxes are handled',
required: true,
defaultValue: 0,
options: {
disabled: false,
options: [
{ label: 'Including taxes', value: 0 },
{ label: 'Excluding taxes', value: 1 },
{ label: 'Exempt from taxes', value: 2 },
],
},
}),
mwst_is_net: Property.Checkbox({
displayName: 'Tax is Net',
description: 'If taxes are included, set to true to add taxes to total, false to include in total',
required: false,
defaultValue: false,
}),
show_position_taxes: Property.Checkbox({
displayName: 'Show Position Taxes',
description: 'Show taxes for each position',
required: false,
defaultValue: false,
}),
is_valid_from: Property.ShortText({
displayName: 'Valid From',
description: 'Order valid from date (YYYY-MM-DD)',
required: true,
}),
contact_address_manual: Property.LongText({
displayName: 'Manual Contact Address',
description: 'Override contact address (leave empty to use contact address)',
required: false,
}),
delivery_address_type: Property.StaticDropdown({
displayName: 'Delivery Address Type',
description: 'Type of delivery address',
required: false,
defaultValue: 0,
options: {
disabled: false,
options: [
{ label: 'Use Invoice Address', value: 0 },
{ label: 'Use Custom Address', value: 1 },
],
},
}),
delivery_address_manual_text: Property.LongText({
displayName: 'Manual Delivery Address',
description: 'Custom delivery address (only used if delivery address type is set to custom)',
required: false,
}),
api_reference: Property.ShortText({
displayName: 'API Reference',
description: 'Reference for API use (can only be edited via API)',
required: false,
}),
template_slug: Property.Dropdown({
auth: bexioAuth,
displayName: 'Document Template',
description: 'Document template for the order',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const templates = await client.get<Array<{
template_slug: string;
name: string;
is_default: boolean;
default_for_document_types: string[];
}>>('/3.0/document_templates');
return {
disabled: false,
options: templates.map((template) => ({
label: template.is_default ? `${template.name} (Default)` : template.name,
value: template.template_slug,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load templates',
options: [],
};
}
},
}),
positionFields: Property.DynamicProperties({
auth: bexioAuth,
displayName: 'Order Positions',
description: 'Configure order line items',
required: true,
refreshers: ['auth'],
props: async ({ auth }) => {
let units: Array<{ id: number; name: string }> = [];
let accounts: Array<{ id: number; account_no: string; name: string }> = [];
let taxes: Array<{ id: number; name: string; percentage: string }> = [];
if (auth) {
try {
const client = new BexioClient(auth);
units = await client.get<Array<{ id: number; name: string }>>('/2.0/unit').catch(() => []);
accounts = await client.get<Array<{ id: number; account_no: string; name: string }>>('/accounts').catch(() => []);
const taxesResponse = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
}>>('/3.0/taxes?types=sales_tax&scope=active').catch(() => []);
taxes = taxesResponse.map((tax) => ({
id: tax.id,
name: tax.display_name || tax.name,
percentage: tax.value.toString(),
}));
} catch (error) {
// Ignore error, use empty array as fallback
}
}
const unitOptions = units.map((unit) => ({ label: unit.name, value: unit.id }));
const accountOptions = accounts.map((acc) => ({ label: `${acc.account_no} - ${acc.name}`, value: acc.id }));
const taxOptions = taxes.map((tax) => ({ label: `${tax.name} (${tax.percentage}%)`, value: tax.id }));
return {
positions: Property.Array({
displayName: 'Positions',
description: 'Order line items',
required: true,
properties: {
type: Property.StaticDropdown({
displayName: 'Position Type',
description: 'Type of order position',
required: true,
defaultValue: 'KbPositionCustom',
options: {
disabled: false,
options: [
{ label: 'Custom Position', value: 'KbPositionCustom' },
{ label: 'Article Position', value: 'KbPositionArticle' },
{ label: 'Text Position', value: 'KbPositionText' },
{ label: 'Subtotal Position', value: 'KbPositionSubtotal' },
{ label: 'Page Break Position', value: 'KbPositionPagebreak' },
{ label: 'Discount Position', value: 'KbPositionDiscount' },
],
},
}),
amount: Property.ShortText({
displayName: 'Amount',
description: 'Quantity/amount',
required: true,
}),
unit_id: unitOptions.length > 0
? Property.StaticDropdown({
displayName: 'Unit',
description: 'Unit of measurement',
required: false,
options: {
disabled: false,
options: unitOptions,
},
})
: Property.Number({
displayName: 'Unit ID',
description: 'Unit ID (use List Units action to find IDs)',
required: false,
}),
account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Account',
description: 'Account for this position',
required: false,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Account ID',
description: 'Account ID (use List Accounts action to find IDs)',
required: false,
}),
tax_id: taxOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax',
description: 'Tax rate (only active sales taxes)',
required: false,
options: {
disabled: false,
options: taxOptions,
},
})
: Property.Number({
displayName: 'Tax ID',
description: 'Tax ID (use List Taxes action to find IDs)',
required: false,
}),
text: Property.LongText({
displayName: 'Description',
description: 'Position description/text',
required: false,
}),
unit_price: Property.ShortText({
displayName: 'Unit Price',
description: 'Price per unit (max 6 decimals)',
required: false,
}),
discount_in_percent: Property.ShortText({
displayName: 'Discount (%)',
description: 'Discount percentage (max 6 decimals)',
required: false,
}),
parent_id: Property.Number({
displayName: 'Parent ID',
description: 'Parent position ID (for grouped positions)',
required: false,
}),
},
}),
};
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
contact_id: props['contact_id'],
user_id: props['user_id'],
language_id: props['language_id'],
bank_account_id: props['bank_account_id'],
currency_id: props['currency_id'],
payment_type_id: props['payment_type_id'],
mwst_type: props['mwst_type'],
is_valid_from: props['is_valid_from'],
};
if (props['document_nr']) {
requestBody['document_nr'] = props['document_nr'];
}
if (props['title'] !== undefined) {
requestBody['title'] = props['title'] || null;
}
if (props['contact_sub_id']) {
requestBody['contact_sub_id'] = props['contact_sub_id'];
}
if (props['pr_project_id']) {
requestBody['pr_project_id'] = props['pr_project_id'];
}
if (props['header']) {
requestBody['header'] = props['header'];
}
if (props['footer']) {
requestBody['footer'] = props['footer'];
}
if (props['mwst_is_net'] !== undefined) {
requestBody['mwst_is_net'] = props['mwst_is_net'];
}
if (props['show_position_taxes'] !== undefined) {
requestBody['show_position_taxes'] = props['show_position_taxes'];
}
if (props['contact_address_manual']) {
requestBody['contact_address_manual'] = props['contact_address_manual'];
}
if (props['delivery_address_type'] !== undefined) {
requestBody['delivery_address_type'] = props['delivery_address_type'];
}
if (props['delivery_address_manual_text']) {
requestBody['delivery_address_manual'] = props['delivery_address_manual_text'];
}
if (props['api_reference']) {
requestBody['api_reference'] = props['api_reference'];
}
if (props['template_slug']) {
requestBody['template_slug'] = props['template_slug'];
}
const positionFields = props['positionFields'] as Record<string, unknown>;
if (positionFields && positionFields['positions'] && Array.isArray(positionFields['positions'])) {
requestBody['positions'] = (positionFields['positions'] as Array<Record<string, unknown>>).map((position) => {
const pos: Record<string, unknown> = {
type: position['type'] || 'KbPositionCustom',
};
if (position['amount']) {
pos['amount'] = position['amount'];
}
if (position['unit_id']) {
pos['unit_id'] = position['unit_id'];
}
if (position['account_id']) {
pos['account_id'] = position['account_id'];
}
if (position['tax_id']) {
pos['tax_id'] = position['tax_id'];
}
if (position['text']) {
pos['text'] = position['text'];
}
if (position['unit_price']) {
pos['unit_price'] = position['unit_price'];
}
if (position['discount_in_percent']) {
pos['discount_in_percent'] = position['discount_in_percent'];
}
if (position['parent_id']) {
pos['parent_id'] = position['parent_id'];
}
return pos;
});
}
const response = await client.post('/2.0/kb_order', requestBody);
return response;
},
});

View File

@@ -0,0 +1,711 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createSalesQuoteAction = createAction({
auth: bexioAuth,
name: 'create_sales_quote',
displayName: 'Create Sales Quote',
description: 'Create a new product-based sales quote',
props: {
document_nr: Property.ShortText({
displayName: 'Document Number',
description: 'Quote number (required if automatic numbering is disabled)',
required: false,
}),
title: Property.ShortText({
displayName: 'Title',
description: 'Quote title',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'The contact for this quote',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
contact_sub_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact Sub',
description: 'Contact sub-address (optional)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User responsible for this quote',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
pr_project_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project',
description: 'Project associated with this quote',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const projects = await client.get<Array<{
id: number;
name: string;
nr?: string;
}>>('/2.0/pr_project');
return {
disabled: false,
options: projects.map((project) => ({
label: project.nr ? `${project.name} (#${project.nr})` : project.name,
value: project.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load projects',
options: [],
};
}
},
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Language for the quote',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
bank_account_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Bank Account',
description: 'Bank account for payment',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const bankAccounts = await client.get<Array<{
id: number;
name: string;
iban_nr?: string;
bank_account_nr?: string;
}>>('/3.0/banking/accounts');
return {
disabled: false,
options: bankAccounts.map((account) => {
const iban = account.iban_nr || account.bank_account_nr;
return {
label: iban ? `${account.name} (${iban})` : account.name,
value: account.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load bank accounts',
options: [],
};
}
},
}),
currency_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Currency',
description: 'Currency for this quote',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const currencies = await client.get<Array<{ id: number; name: string }>>('/currencies');
return {
disabled: false,
options: currencies.map((currency) => ({
label: currency.name,
value: currency.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load currencies',
options: [],
};
}
},
}),
payment_type_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Payment Type',
description: 'Payment type for this quote',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const paymentTypes = await client.get<Array<{ id: number; name: string }>>('/2.0/payment_type');
return {
disabled: false,
options: paymentTypes.map((type) => ({
label: type.name,
value: type.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load payment types',
options: [],
};
}
},
}),
header: Property.LongText({
displayName: 'Header',
description: 'Header text for the quote',
required: false,
}),
footer: Property.LongText({
displayName: 'Footer',
description: 'Footer text for the quote',
required: false,
}),
mwst_type: Property.StaticDropdown({
displayName: 'Tax Type',
description: 'How taxes are handled',
required: true,
defaultValue: 0,
options: {
disabled: false,
options: [
{ label: 'Including taxes', value: 0 },
{ label: 'Excluding taxes', value: 1 },
{ label: 'Exempt from taxes', value: 2 },
],
},
}),
mwst_is_net: Property.Checkbox({
displayName: 'Tax is Net',
description: 'If taxes are included, set to true to add taxes to total, false to include in total',
required: false,
defaultValue: false,
}),
show_position_taxes: Property.Checkbox({
displayName: 'Show Position Taxes',
description: 'Show taxes for each position',
required: false,
defaultValue: false,
}),
is_valid_from: Property.ShortText({
displayName: 'Valid From',
description: 'Quote valid from date (YYYY-MM-DD)',
required: true,
}),
is_valid_until: Property.ShortText({
displayName: 'Valid Until',
description: 'Quote valid until date (YYYY-MM-DD)',
required: true,
}),
contact_address_manual: Property.LongText({
displayName: 'Manual Contact Address',
description: 'Override contact address (leave empty to use contact address)',
required: false,
}),
delivery_address_type: Property.StaticDropdown({
displayName: 'Delivery Address Type',
description: 'Type of delivery address',
required: false,
defaultValue: 0,
options: {
disabled: false,
options: [
{ label: 'Use Invoice Address', value: 0 },
{ label: 'Use Custom Address', value: 1 },
],
},
}),
delivery_address_manual_text: Property.LongText({
displayName: 'Manual Delivery Address',
description: 'Custom delivery address (only used if delivery address type is set to custom)',
required: false,
}),
api_reference: Property.ShortText({
displayName: 'API Reference',
description: 'Reference for API use (can only be edited via API)',
required: false,
}),
viewed_by_client_at: Property.DateTime({
displayName: 'Viewed By Client At',
description: 'Date when the quote was viewed by the client',
required: false,
}),
kb_terms_of_payment_template_id: Property.Number({
displayName: 'Terms of Payment Template ID',
description: 'Terms of payment template ID',
required: false,
}),
template_slug: Property.Dropdown({
auth: bexioAuth,
displayName: 'Document Template',
description: 'Document template for the quote',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const templates = await client.get<Array<{
template_slug: string;
name: string;
is_default: boolean;
default_for_document_types: string[];
}>>('/3.0/document_templates');
return {
disabled: false,
options: templates.map((template) => ({
label: template.is_default ? `${template.name} (Default)` : template.name,
value: template.template_slug,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load templates',
options: [],
};
}
},
}),
positionFields: Property.DynamicProperties({
auth: bexioAuth,
displayName: 'Quote Positions',
description: 'Configure quote line items',
required: true,
refreshers: ['auth'],
props: async ({ auth }) => {
let units: Array<{ id: number; name: string }> = [];
let accounts: Array<{ id: number; account_no: string; name: string }> = [];
let taxes: Array<{ id: number; name: string; percentage: string }> = [];
if (auth) {
try {
const client = new BexioClient(auth);
units = await client.get<Array<{ id: number; name: string }>>('/2.0/unit').catch(() => []);
accounts = await client.get<Array<{ id: number; account_no: string; name: string }>>('/accounts').catch(() => []);
const taxesResponse = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
}>>('/3.0/taxes?types=sales_tax&scope=active').catch(() => []);
taxes = taxesResponse.map((tax) => ({
id: tax.id,
name: tax.display_name || tax.name,
percentage: tax.value.toString(),
}));
} catch (error) {
// Ignore error, use empty array as fallback
}
}
const unitOptions = units.map((unit) => ({ label: unit.name, value: unit.id }));
const accountOptions = accounts.map((acc) => ({ label: `${acc.account_no} - ${acc.name}`, value: acc.id }));
const taxOptions = taxes.map((tax) => ({ label: `${tax.name} (${tax.percentage}%)`, value: tax.id }));
return {
positions: Property.Array({
displayName: 'Positions',
description: 'Quote line items',
required: true,
properties: {
type: Property.StaticDropdown({
displayName: 'Position Type',
description: 'Type of quote position',
required: true,
defaultValue: 'KbPositionCustom',
options: {
disabled: false,
options: [
{ label: 'Custom Position', value: 'KbPositionCustom' },
{ label: 'Article Position', value: 'KbPositionArticle' },
{ label: 'Text Position', value: 'KbPositionText' },
{ label: 'Subtotal Position', value: 'KbPositionSubtotal' },
{ label: 'Page Break Position', value: 'KbPositionPagebreak' },
{ label: 'Discount Position', value: 'KbPositionDiscount' },
],
},
}),
amount: Property.ShortText({
displayName: 'Amount',
description: 'Quantity/amount',
required: true,
}),
unit_id: unitOptions.length > 0
? Property.StaticDropdown({
displayName: 'Unit',
description: 'Unit of measurement',
required: false,
options: {
disabled: false,
options: unitOptions,
},
})
: Property.Number({
displayName: 'Unit ID',
description: 'Unit ID (use List Units action to find IDs)',
required: false,
}),
account_id: accountOptions.length > 0
? Property.StaticDropdown({
displayName: 'Account',
description: 'Account for this position',
required: false,
options: {
disabled: false,
options: accountOptions,
},
})
: Property.Number({
displayName: 'Account ID',
description: 'Account ID (use List Accounts action to find IDs)',
required: false,
}),
tax_id: taxOptions.length > 0
? Property.StaticDropdown({
displayName: 'Tax',
description: 'Tax rate (only active sales taxes)',
required: false,
options: {
disabled: false,
options: taxOptions,
},
})
: Property.Number({
displayName: 'Tax ID',
description: 'Tax ID (use List Taxes action to find IDs)',
required: false,
}),
text: Property.LongText({
displayName: 'Description',
description: 'Position description/text',
required: false,
}),
unit_price: Property.ShortText({
displayName: 'Unit Price',
description: 'Price per unit (max 6 decimals)',
required: false,
}),
discount_in_percent: Property.ShortText({
displayName: 'Discount (%)',
description: 'Discount percentage (max 6 decimals)',
required: false,
}),
parent_id: Property.Number({
displayName: 'Parent ID',
description: 'Parent position ID (for grouped positions)',
required: false,
}),
},
}),
};
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
user_id: props['user_id'],
language_id: props['language_id'],
bank_account_id: props['bank_account_id'],
currency_id: props['currency_id'],
payment_type_id: props['payment_type_id'],
mwst_type: props['mwst_type'],
is_valid_from: props['is_valid_from'],
is_valid_until: props['is_valid_until'],
};
if (props['document_nr']) {
requestBody['document_nr'] = props['document_nr'];
}
if (props['title'] !== undefined) {
requestBody['title'] = props['title'] || null;
}
if (props['contact_id']) {
requestBody['contact_id'] = props['contact_id'];
}
if (props['contact_sub_id']) {
requestBody['contact_sub_id'] = props['contact_sub_id'];
}
if (props['pr_project_id']) {
requestBody['pr_project_id'] = props['pr_project_id'];
}
if (props['header']) {
requestBody['header'] = props['header'];
}
if (props['footer']) {
requestBody['footer'] = props['footer'];
}
if (props['mwst_is_net'] !== undefined) {
requestBody['mwst_is_net'] = props['mwst_is_net'];
}
if (props['show_position_taxes'] !== undefined) {
requestBody['show_position_taxes'] = props['show_position_taxes'];
}
if (props['contact_address_manual']) {
requestBody['contact_address_manual'] = props['contact_address_manual'];
}
if (props['delivery_address_type'] !== undefined) {
requestBody['delivery_address_type'] = props['delivery_address_type'];
}
if (props['delivery_address_manual_text']) {
requestBody['delivery_address_manual'] = props['delivery_address_manual_text'];
}
if (props['api_reference']) {
requestBody['api_reference'] = props['api_reference'];
}
if (props['viewed_by_client_at']) {
requestBody['viewed_by_client_at'] = props['viewed_by_client_at'];
}
if (props['kb_terms_of_payment_template_id']) {
requestBody['kb_terms_of_payment_template_id'] = props['kb_terms_of_payment_template_id'];
}
if (props['template_slug']) {
requestBody['template_slug'] = props['template_slug'];
}
const positionFields = props['positionFields'] as Record<string, unknown>;
if (positionFields && positionFields['positions'] && Array.isArray(positionFields['positions'])) {
requestBody['positions'] = (positionFields['positions'] as Array<Record<string, unknown>>).map((position) => {
const pos: Record<string, unknown> = {
type: position['type'] || 'KbPositionCustom',
};
if (position['amount']) {
pos['amount'] = position['amount'];
}
if (position['unit_id']) {
pos['unit_id'] = position['unit_id'];
}
if (position['account_id']) {
pos['account_id'] = position['account_id'];
}
if (position['tax_id']) {
pos['tax_id'] = position['tax_id'];
}
if (position['text']) {
pos['text'] = position['text'];
}
if (position['unit_price']) {
pos['unit_price'] = position['unit_price'];
}
if (position['discount_in_percent']) {
pos['discount_in_percent'] = position['discount_in_percent'];
}
if (position['parent_id']) {
pos['parent_id'] = position['parent_id'];
}
return pos;
});
}
const response = await client.post('/2.0/kb_offer', requestBody);
return response;
},
});

View File

@@ -0,0 +1,416 @@
import { createAction, Property, OAuth2PropertyValue, DynamicPropsValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const createTimeTrackingAction = createAction({
auth: bexioAuth,
name: 'create_time_tracking',
displayName: 'Create Time Tracking',
description: 'Create a new timesheet entry',
props: {
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User for this timesheet entry',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
status_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Status',
description: 'Timesheet status',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
// TODO: Need to confirm endpoint - assuming /2.0/timesheet_status
const statuses = await client.get<Array<{ id: number; name: string }>>('/2.0/timesheet_status').catch(() => []);
return {
disabled: false,
options: statuses.map((status) => ({
label: status.name,
value: status.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load timesheet statuses',
options: [],
};
}
},
}),
client_service_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Client Service',
description: 'Business activity/client service',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
// TODO: Need to confirm endpoint - assuming /2.0/client_service
const services = await client.get<Array<{ id: number; name: string }>>('/2.0/client_service').catch(() => []);
return {
disabled: false,
options: services.map((service) => ({
label: service.name,
value: service.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load client services',
options: [],
};
}
},
}),
text: Property.LongText({
displayName: 'Description',
description: 'Description of the work performed',
required: false,
}),
allowable_bill: Property.Checkbox({
displayName: 'Allowable Bill',
description: 'Whether this time is billable',
required: true,
defaultValue: true,
}),
charge: Property.ShortText({
displayName: 'Charge',
description: 'Charge amount',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'Contact associated with this timesheet',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
sub_contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Sub Contact',
description: 'Sub contact (optional)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
pr_project_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Project',
description: 'Project associated with this timesheet',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const projects = await client.get<Array<{
id: number;
name: string;
nr?: string;
}>>('/2.0/pr_project');
return {
disabled: false,
options: projects.map((project) => ({
label: project.nr ? `${project.name} (#${project.nr})` : project.name,
value: project.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load projects',
options: [],
};
}
},
}),
pr_package_id: Property.Number({
displayName: 'Package ID',
description: 'Project package ID',
required: false,
}),
pr_milestone_id: Property.Number({
displayName: 'Milestone ID',
description: 'Project milestone ID',
required: false,
}),
estimated_time: Property.ShortText({
displayName: 'Estimated Time',
description: 'Estimated time (format: HH:MM)',
required: false,
}),
tracking_type: Property.StaticDropdown({
displayName: 'Tracking Type',
description: 'Type of time tracking',
required: true,
defaultValue: 'duration',
options: {
disabled: false,
options: [
{ label: 'Duration', value: 'duration' },
{ label: 'Range', value: 'range' },
],
},
}),
tracking_details: Property.DynamicProperties({
auth: bexioAuth,
displayName: 'Tracking Details',
description: 'Time tracking details',
required: true,
refreshers: ['tracking_type'],
props: async ({ tracking_type }) => {
const type = (tracking_type as unknown as string) || 'duration';
const dynamicProps: Record<string, any> = {};
if (type === 'duration') {
dynamicProps['date'] = Property.ShortText({
displayName: 'Date',
description: 'Date of the timesheet entry (YYYY-MM-DD)',
required: true,
});
dynamicProps['duration'] = Property.ShortText({
displayName: 'Duration',
description: 'Duration of time tracked (format: HH:MM)',
required: true,
});
} else {
// Range type
dynamicProps['from'] = Property.DateTime({
displayName: 'From',
description: 'Start date and time',
required: true,
});
dynamicProps['to'] = Property.DateTime({
displayName: 'To',
description: 'End date and time',
required: true,
});
}
return dynamicProps;
},
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const props = propsValue;
const requestBody: Record<string, unknown> = {
user_id: props['user_id'],
client_service_id: props['client_service_id'],
allowable_bill: props['allowable_bill'],
};
if (props['status_id']) {
requestBody['status_id'] = props['status_id'];
}
if (props['text']) {
requestBody['text'] = props['text'];
}
if (props['charge']) {
requestBody['charge'] = props['charge'];
}
if (props['contact_id']) {
requestBody['contact_id'] = props['contact_id'];
}
if (props['sub_contact_id']) {
requestBody['sub_contact_id'] = props['sub_contact_id'];
}
if (props['pr_project_id']) {
requestBody['pr_project_id'] = props['pr_project_id'];
}
if (props['pr_package_id']) {
requestBody['pr_package_id'] = props['pr_package_id'];
}
if (props['pr_milestone_id']) {
requestBody['pr_milestone_id'] = props['pr_milestone_id'];
}
if (props['estimated_time']) {
requestBody['estimated_time'] = props['estimated_time'];
}
// Build tracking object
const trackingType = props['tracking_type'] as string;
const trackingDetails = props['tracking_details'] as DynamicPropsValue;
const tracking: Record<string, unknown> = {
type: trackingType,
};
if (trackingType === 'duration') {
tracking['date'] = trackingDetails['date'];
tracking['duration'] = trackingDetails['duration'];
} else {
// Range type
tracking['from'] = trackingDetails['from'];
tracking['to'] = trackingDetails['to'];
}
requestBody['tracking'] = tracking;
const response = await client.post<{
id: number;
user_id: number;
client_service_id: number;
tracking: {
type: string;
date?: string;
duration?: string;
from?: string;
to?: string;
};
}>('/2.0/timesheet', requestBody);
return response;
},
});

View File

@@ -0,0 +1,102 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const exportInvoicePdfAction = createAction({
auth: bexioAuth,
name: 'export_invoice_pdf',
displayName: 'Export Invoice to PDF',
description: 'Export an existing sales invoice as a PDF document',
props: {
invoice_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Invoice',
description: 'Select the invoice to export',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const invoices = await client.get<Array<{
id: number;
document_nr: string;
title?: string | null;
contact_id?: number | null;
total?: string;
kb_item_status_id?: number;
}>>('/2.0/kb_invoice');
return {
disabled: false,
options: invoices.map((invoice) => {
const label = invoice.title
? `${invoice.document_nr} - ${invoice.title}`
: invoice.document_nr;
return {
label,
value: invoice.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load invoices',
options: [],
};
}
},
}),
logopaper: Property.StaticDropdown({
displayName: 'Letterhead',
description: 'Whether to include letterhead in the PDF',
required: false,
defaultValue: 1,
options: {
disabled: false,
options: [
{ label: 'With Letterhead', value: 1 },
{ label: 'Without Letterhead', value: 0 },
],
},
}),
},
async run({ auth, propsValue, files }) {
const client = new BexioClient(auth);
const invoiceId = propsValue['invoice_id'] as number;
const logopaper = propsValue['logopaper'] as number | undefined;
const queryParams: Record<string, string> = {};
if (logopaper !== undefined) {
queryParams['logopaper'] = logopaper.toString();
}
const response = await client.get<{
name: string;
size: number;
mime: string;
content: string;
}>(`/2.0/kb_invoice/${invoiceId}/pdf`, queryParams);
const fileUrl = await files.write({
fileName: response.name || `invoice_${invoiceId}.pdf`,
data: Buffer.from(response.content, 'base64'),
});
return {
file: fileUrl,
fileName: response.name,
size: response.size,
mimeType: response.mime,
};
},
});

View File

@@ -0,0 +1,112 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const findAccountAction = createAction({
auth: bexioAuth,
name: 'find_account',
displayName: 'Find Account',
description: 'Search for accounts using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding accounts',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'Account Number', value: 'account_no' },
{ label: 'Account Name', value: 'name' },
{ label: 'Account Type', value: 'account_type' },
{ label: 'Account Group ID', value: 'fibu_account_group_id' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
],
},
}),
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
uuid: string;
account_no: string;
name: string;
account_type: number;
tax_id?: number;
fibu_account_group_id?: number;
is_active: boolean;
is_locked: boolean;
}>>('/2.0/accounts/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,191 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const findCompanyAction = createAction({
auth: bexioAuth,
name: 'find_company',
displayName: 'Find Company',
description: 'Search for company contacts using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding companies',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Company Name', value: 'name_1' },
{ label: 'Company Addition', value: 'name_2' },
{ label: 'Contact Number', value: 'nr' },
{ label: 'Address', value: 'address' },
{ label: 'Email', value: 'mail' },
{ label: 'Secondary Email', value: 'mail_second' },
{ label: 'Postcode', value: 'postcode' },
{ label: 'City', value: 'city' },
{ label: 'Country ID', value: 'country_id' },
{ label: 'Contact Group IDs', value: 'contact_group_ids' },
{ label: 'Contact Type ID', value: 'contact_type_id' },
{ label: 'Updated At', value: 'updated_at' },
{ label: 'User ID', value: 'user_id' },
{ label: 'Phone', value: 'phone_fixed' },
{ label: 'Mobile Phone', value: 'phone_mobile' },
{ label: 'Fax', value: 'fax' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Contact Number', value: 'nr' },
{ label: 'Company Name', value: 'name_1' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
show_archived: Property.Checkbox({
displayName: 'Show Archived',
description: 'Show archived contacts only',
required: false,
defaultValue: false,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const showArchived = propsValue['show_archived'] as boolean | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
// Add contact_type_id filter to only return companies if not already specified
const hasContactTypeFilter = searchCriteria.some(
(criteria) => (criteria['field'] as string) === 'contact_type_id'
);
if (!hasContactTypeFilter) {
searchBody.push({
field: 'contact_type_id',
value: '1',
criteria: '=',
});
}
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
if (showArchived !== undefined) {
queryParams['show_archived'] = showArchived.toString();
}
const response = await client.post<Array<{
id: number;
nr: string | null;
contact_type_id: number;
name_1: string;
name_2: string | null;
salutation_id: number | null;
salutation_form: number | null;
title_id: number | null;
birthday: string | null;
address: string | null;
street_name: string | null;
house_number: string | null;
address_addition: string | null;
postcode: string | null;
city: string | null;
country_id: number | null;
mail: string | null;
mail_second: string | null;
phone_fixed: string | null;
phone_fixed_second: string | null;
phone_mobile: string | null;
fax: string | null;
url: string | null;
skype_name: string | null;
remarks: string | null;
language_id: number | null;
is_lead: boolean;
contact_group_ids: string | null;
contact_branch_ids: string | null;
user_id: number;
owner_id: number;
updated_at: string;
}>>('/2.0/contact/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,125 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const findCountryAction = createAction({
auth: bexioAuth,
name: 'find_country',
displayName: 'Find Country',
description: 'Search for countries using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding countries',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'Country Name', value: 'name' },
{ label: 'Country Code (Short)', value: 'name_short' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Country Name', value: 'name' },
{ label: 'Country Code', value: 'name_short' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
name: string;
name_short: string;
iso3166_alpha2: string;
}>>('/2.0/country/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,206 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const findPersonAction = createAction({
auth: bexioAuth,
name: 'find_person',
displayName: 'Find Person',
description: 'Search for person contacts using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding persons',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'First Name (name_1)', value: 'name_1' },
{ label: 'Last Name (name_2)', value: 'name_2' },
{ label: 'Contact Number', value: 'nr' },
{ label: 'Address', value: 'address' },
{ label: 'Email', value: 'mail' },
{ label: 'Secondary Email', value: 'mail_second' },
{ label: 'Postcode', value: 'postcode' },
{ label: 'City', value: 'city' },
{ label: 'Country ID', value: 'country_id' },
{ label: 'Contact Group IDs', value: 'contact_group_ids' },
{ label: 'Contact Type ID', value: 'contact_type_id' },
{ label: 'Updated At', value: 'updated_at' },
{ label: 'User ID', value: 'user_id' },
{ label: 'Fixed Phone', value: 'phone_fixed' },
{ label: 'Mobile Phone', value: 'phone_mobile' },
{ label: 'Fax', value: 'fax' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array of values)', value: 'in' },
{ label: 'Not In (array of values)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to sort the results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Contact Number', value: 'nr' },
{ label: 'First Name', value: 'name_1' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
order_direction: Property.StaticDropdown({
displayName: 'Order Direction',
description: 'Sort order',
required: false,
defaultValue: 'asc',
options: {
disabled: false,
options: [
{ label: 'Ascending', value: 'asc' },
{ label: 'Descending', value: 'desc' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
show_archived: Property.Checkbox({
displayName: 'Show Archived',
description: 'Include archived contacts in the search results',
required: false,
defaultValue: false,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const orderDirection = propsValue['order_direction'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const showArchived = propsValue['show_archived'] as boolean | undefined;
const finalSearchBody = [...searchCriteria];
// Ensure contact_type_id is 2 (person) unless explicitly overridden by user
const contactTypeCriteriaIndex = finalSearchBody.findIndex(
(criteria) => criteria['field'] === 'contact_type_id'
);
if (contactTypeCriteriaIndex === -1) {
finalSearchBody.push({
field: 'contact_type_id',
value: '2',
criteria: '=',
});
} else if (finalSearchBody[contactTypeCriteriaIndex]['value'] !== '2') {
// If user provided a different contact_type_id, ensure it's 2 for "Find Person"
finalSearchBody[contactTypeCriteriaIndex]['value'] = '2';
finalSearchBody[contactTypeCriteriaIndex]['criteria'] = '=';
}
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = `${orderBy}${orderDirection === 'desc' ? '_desc' : ''}`;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
if (showArchived !== undefined) {
queryParams['show_archived'] = showArchived.toString();
}
const response = await client.post<Array<{
id: number;
nr?: string | null;
contact_type_id: number;
name_1: string;
name_2?: string | null;
salutation_id?: number | null;
salutation_form?: string | null;
title_id?: number | null;
birthday?: string | null;
address?: string | null;
street_name?: string | null;
house_number?: string | null;
address_addition?: string | null;
postcode?: string | null;
city?: string | null;
country_id?: number | null;
mail?: string | null;
mail_second?: string | null;
phone_fixed?: string | null;
phone_fixed_second?: string | null;
phone_mobile?: string | null;
fax?: string | null;
url?: string | null;
skype_name?: string | null;
remarks?: string | null;
language_id?: number | null;
is_lead?: boolean;
contact_group_ids?: string | null;
contact_branch_ids?: string | null;
user_id?: number;
owner_id?: number;
updated_at?: string;
}>>('/2.0/contact/search', finalSearchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,159 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const findProductAction = createAction({
auth: bexioAuth,
name: 'find_product',
displayName: 'Find Product',
description: 'Search for products by name or code',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding products',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'Internal Name', value: 'intern_name' },
{ label: 'Internal Code', value: 'intern_code' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Internal Name', value: 'intern_name' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
user_id: number;
article_type_id: number;
contact_id: number | null;
deliverer_code: string | null;
deliverer_name: string | null;
deliverer_description: string | null;
intern_code: string;
intern_name: string;
intern_description: string | null;
purchase_price: string | null;
sale_price: string | null;
purchase_total: number | null;
sale_total: number | null;
currency_id: number | null;
tax_income_id: number | null;
tax_id: number | null;
tax_expense_id: number | null;
unit_id: number | null;
is_stock: boolean;
stock_id: number | null;
stock_place_id: number | null;
stock_nr: number;
stock_min_nr: number;
stock_reserved_nr: number;
stock_available_nr: number;
stock_picked_nr: number;
stock_disposed_nr: number;
stock_ordered_nr: number;
width: number | null;
height: number | null;
weight: number | null;
volume: number | null;
html_text: string | null;
remarks: string | null;
delivery_price: number | null;
article_group_id: number | null;
account_id: number | null;
expense_account_id: number | null;
}>>('/2.0/article/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,155 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const searchInvoiceAction = createAction({
auth: bexioAuth,
name: 'search_invoice',
displayName: 'Search Invoice',
description: 'Search for invoices using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding invoices',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Status ID', value: 'kb_item_status_id' },
{ label: 'Document Number', value: 'document_nr' },
{ label: 'Title', value: 'title' },
{ label: 'API Reference', value: 'api_reference' },
{ label: 'Contact ID', value: 'contact_id' },
{ label: 'Sub Contact ID', value: 'contact_sub_id' },
{ label: 'User ID', value: 'user_id' },
{ label: 'Currency ID', value: 'currency_id' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total', value: 'total' },
{ label: 'Valid From', value: 'is_valid_from' },
{ label: 'Valid To', value: 'is_valid_to' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Total', value: 'total' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
is_valid_to: string | null;
updated_at: string;
}>>('/2.0/kb_invoice/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,153 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const searchOrderAction = createAction({
auth: bexioAuth,
name: 'search_order',
displayName: 'Search Order',
description: 'Search for orders using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding orders',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Status ID', value: 'kb_item_status_id' },
{ label: 'Document Number', value: 'document_nr' },
{ label: 'Title', value: 'title' },
{ label: 'Contact ID', value: 'contact_id' },
{ label: 'Sub Contact ID', value: 'contact_sub_id' },
{ label: 'User ID', value: 'user_id' },
{ label: 'Currency ID', value: 'currency_id' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total', value: 'total' },
{ label: 'Valid From', value: 'is_valid_from' },
{ label: 'Valid To', value: 'is_valid_to' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Total', value: 'total' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
updated_at: string;
}>>('/2.0/kb_order/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,155 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const searchQuoteAction = createAction({
auth: bexioAuth,
name: 'search_quote',
displayName: 'Search Quote',
description: 'Search for quotes using various criteria',
props: {
search_criteria: Property.Array({
displayName: 'Search Criteria',
description: 'Search criteria for finding quotes',
required: true,
properties: {
field: Property.StaticDropdown({
displayName: 'Search Field',
description: 'Field to search in',
required: true,
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Status ID', value: 'kb_item_status_id' },
{ label: 'Document Number', value: 'document_nr' },
{ label: 'Title', value: 'title' },
{ label: 'Contact ID', value: 'contact_id' },
{ label: 'Sub Contact ID', value: 'contact_sub_id' },
{ label: 'User ID', value: 'user_id' },
{ label: 'Currency ID', value: 'currency_id' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total', value: 'total' },
{ label: 'Valid From', value: 'is_valid_from' },
{ label: 'Valid To', value: 'is_valid_to' },
{ label: 'Valid Until', value: 'is_valid_until' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
value: Property.ShortText({
displayName: 'Search Value',
description: 'Value to search for',
required: true,
}),
criteria: Property.StaticDropdown({
displayName: 'Criteria',
description: 'Search criteria type',
required: false,
defaultValue: 'like',
options: {
disabled: false,
options: [
{ label: 'Contains (like)', value: 'like' },
{ label: 'Exact match (=)', value: '=' },
{ label: 'Exact match (equal)', value: 'equal' },
{ label: 'Not equal (!=)', value: '!=' },
{ label: 'Not equal (not_equal)', value: 'not_equal' },
{ label: 'Greater than (>)', value: '>' },
{ label: 'Greater than (greater_than)', value: 'greater_than' },
{ label: 'Greater or equal (>=)', value: '>=' },
{ label: 'Greater or equal (greater_equal)', value: 'greater_equal' },
{ label: 'Less than (<)', value: '<' },
{ label: 'Less than (less_than)', value: 'less_than' },
{ label: 'Less or equal (<=)', value: '<=' },
{ label: 'Less or equal (less_equal)', value: 'less_equal' },
{ label: 'Does not contain (not_like)', value: 'not_like' },
{ label: 'Is NULL', value: 'is_null' },
{ label: 'Is not NULL', value: 'not_null' },
{ label: 'In (array)', value: 'in' },
{ label: 'Not in (array)', value: 'not_in' },
],
},
}),
},
}),
order_by: Property.StaticDropdown({
displayName: 'Order By',
description: 'Field to order results by',
required: false,
defaultValue: 'id',
options: {
disabled: false,
options: [
{ label: 'ID', value: 'id' },
{ label: 'Total', value: 'total' },
{ label: 'Total Net', value: 'total_net' },
{ label: 'Total Gross', value: 'total_gross' },
{ label: 'Updated At', value: 'updated_at' },
],
},
}),
limit: Property.Number({
displayName: 'Limit',
description: 'Maximum number of results to return (max 2000)',
required: false,
defaultValue: 500,
}),
offset: Property.Number({
displayName: 'Offset',
description: 'Number of results to skip',
required: false,
defaultValue: 0,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const searchCriteria = propsValue['search_criteria'] as Array<Record<string, unknown>>;
const orderBy = propsValue['order_by'] as string | undefined;
const limit = propsValue['limit'] as number | undefined;
const offset = propsValue['offset'] as number | undefined;
const searchBody = searchCriteria.map((criteria) => ({
field: criteria['field'] as string,
value: criteria['value'] as string,
criteria: (criteria['criteria'] as string) || 'like',
}));
const queryParams: Record<string, string> = {};
if (orderBy) {
queryParams['order_by'] = orderBy;
}
if (limit !== undefined) {
queryParams['limit'] = limit.toString();
}
if (offset !== undefined) {
queryParams['offset'] = offset.toString();
}
const response = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
is_valid_until: string | null;
updated_at: string;
}>>('/2.0/kb_offer/search', searchBody, queryParams);
return response;
},
});

View File

@@ -0,0 +1,116 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const sendSalesInvoiceAction = createAction({
auth: bexioAuth,
name: 'send_sales_invoice',
displayName: 'Send Sales Invoice',
description: 'Send a sales invoice to an email address',
props: {
invoice_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Invoice',
description: 'Select the invoice to send',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const invoices = await client.get<Array<{
id: number;
document_nr: string;
title?: string | null;
contact_id?: number | null;
total?: string;
kb_item_status_id?: number;
}>>('/2.0/kb_invoice');
return {
disabled: false,
options: invoices.map((invoice) => {
const label = invoice.title
? `${invoice.document_nr} - ${invoice.title}`
: invoice.document_nr;
return {
label,
value: invoice.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load invoices',
options: [],
};
}
},
}),
recipient_email: Property.ShortText({
displayName: 'Recipient Email',
description: 'Email address to send the invoice to',
required: true,
}),
subject: Property.ShortText({
displayName: 'Subject',
description: 'Email subject line',
required: true,
}),
message: Property.LongText({
displayName: 'Message',
description: 'Email message body. Must include "[Network Link]" placeholder',
required: true,
}),
mark_as_open: Property.Checkbox({
displayName: 'Mark as Open',
description: 'Mark the invoice as open',
required: false,
defaultValue: false,
}),
attach_pdf: Property.Checkbox({
displayName: 'Attach PDF',
description: 'Attach PDF directly to the email',
required: false,
defaultValue: false,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const invoiceId = propsValue['invoice_id'] as number;
const recipientEmail = propsValue['recipient_email'] as string;
const subject = propsValue['subject'] as string;
const message = propsValue['message'] as string;
const markAsOpen = propsValue['mark_as_open'] as boolean | undefined;
const attachPdf = propsValue['attach_pdf'] as boolean | undefined;
const requestBody: Record<string, unknown> = {
recipient_email: recipientEmail,
subject,
message,
};
if (markAsOpen !== undefined) {
requestBody['mark_as_open'] = markAsOpen;
}
if (attachPdf !== undefined) {
requestBody['attach_pdf'] = attachPdf;
}
const response = await client.post<{
success: boolean;
}>(`/2.0/kb_invoice/${invoiceId}/send`, requestBody);
return response;
},
});

View File

@@ -0,0 +1,516 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const updateCompanyAction = createAction({
auth: bexioAuth,
name: 'update_company',
displayName: 'Update Company',
description: 'Update an existing company contact',
props: {
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Company',
description: 'Select the company to update',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
const companies = contacts.filter((contact) => contact.contact_type_id === 1);
return {
disabled: false,
options: companies.map((company) => {
const label = company.nr ? `${company.name_1} (#${company.nr})` : company.name_1;
return {
label,
value: company.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load companies',
options: [],
};
}
},
}),
name_1: Property.ShortText({
displayName: 'Company Name',
description: 'The name of the company',
required: false,
}),
name_2: Property.ShortText({
displayName: 'Company Addition',
description: 'Additional company name (e.g., "Ltd.", "Inc.")',
required: false,
}),
nr: Property.ShortText({
displayName: 'Contact Number',
description: 'Contact number (leave empty to keep current or auto-assign)',
required: false,
}),
salutation_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Salutation',
description: 'Salutation for the company',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const salutations = await client.get<Array<{ id: number; name: string }>>('/2.0/salutation');
return {
disabled: false,
options: salutations.map((sal) => ({
label: sal.name,
value: sal.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load salutations',
options: [],
};
}
},
}),
title_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Title',
description: 'Title for the company',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const titles = await client.get<Array<{ id: number; name: string }>>('/2.0/title');
return {
disabled: false,
options: titles.map((title) => ({
label: title.name,
value: title.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load titles',
options: [],
};
}
},
}),
street_name: Property.ShortText({
displayName: 'Street Name',
description: 'Street name',
required: false,
}),
house_number: Property.ShortText({
displayName: 'House Number',
description: 'House number',
required: false,
}),
address_addition: Property.ShortText({
displayName: 'Address Addition',
description: 'Additional address information',
required: false,
}),
postcode: Property.ShortText({
displayName: 'Postcode',
description: 'Postal code',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
description: 'City',
required: false,
}),
country_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Country',
description: 'Country',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const countries = await client.get<Array<{ id: number; name: string; name_short: string; iso3166_alpha2: string }>>('/2.0/country');
return {
disabled: false,
options: countries.map((country) => ({
label: `${country.name} (${country.name_short})`,
value: country.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load countries',
options: [],
};
}
},
}),
mail: Property.ShortText({
displayName: 'Email',
description: 'Primary email address',
required: false,
}),
mail_second: Property.ShortText({
displayName: 'Secondary Email',
description: 'Secondary email address',
required: false,
}),
phone_fixed: Property.ShortText({
displayName: 'Phone',
description: 'Fixed phone number',
required: false,
}),
phone_fixed_second: Property.ShortText({
displayName: 'Secondary Phone',
description: 'Secondary fixed phone number',
required: false,
}),
phone_mobile: Property.ShortText({
displayName: 'Mobile Phone',
description: 'Mobile phone number',
required: false,
}),
fax: Property.ShortText({
displayName: 'Fax',
description: 'Fax number',
required: false,
}),
url: Property.ShortText({
displayName: 'Website URL',
description: 'Company website URL',
required: false,
}),
skype_name: Property.ShortText({
displayName: 'Skype Name',
description: 'Skype username',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional notes about the company',
required: false,
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Preferred language',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
contact_group_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Groups',
description: 'Select contact groups',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_group');
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact groups',
options: [],
};
}
},
}),
contact_branch_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Sectors',
description: 'Select contact sectors',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const branches = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_branch');
return {
disabled: false,
options: branches.map((branch) => ({
label: branch.name,
value: branch.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact sectors',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User assigned to this contact',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
owner_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Owner',
description: 'Owner of this contact',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const contactId = context.propsValue['contact_id'] as number;
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
contact_type_id: 1, // 1 = company
};
if (props['name_1']) {
requestBody['name_1'] = props['name_1'];
}
if (props['name_2'] !== undefined) {
requestBody['name_2'] = props['name_2'] || null;
}
if (props['nr'] !== undefined && props['nr'] !== null && props['nr'] !== '') {
requestBody['nr'] = props['nr'];
} else if (props['nr'] === '') {
requestBody['nr'] = null;
}
if (props['salutation_id']) {
requestBody['salutation_id'] = props['salutation_id'];
}
if (props['title_id']) {
requestBody['title_id'] = props['title_id'];
}
if (props['street_name']) {
requestBody['street_name'] = props['street_name'];
}
if (props['house_number']) {
requestBody['house_number'] = props['house_number'];
}
if (props['address_addition']) {
requestBody['address_addition'] = props['address_addition'];
}
if (props['postcode']) {
requestBody['postcode'] = props['postcode'];
}
if (props['city']) {
requestBody['city'] = props['city'];
}
if (props['country_id']) {
requestBody['country_id'] = props['country_id'];
}
if (props['mail']) {
requestBody['mail'] = props['mail'];
}
if (props['mail_second']) {
requestBody['mail_second'] = props['mail_second'];
}
if (props['phone_fixed']) {
requestBody['phone_fixed'] = props['phone_fixed'];
}
if (props['phone_fixed_second']) {
requestBody['phone_fixed_second'] = props['phone_fixed_second'];
}
if (props['phone_mobile']) {
requestBody['phone_mobile'] = props['phone_mobile'];
}
if (props['fax']) {
requestBody['fax'] = props['fax'];
}
if (props['url']) {
requestBody['url'] = props['url'];
}
if (props['skype_name']) {
requestBody['skype_name'] = props['skype_name'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['language_id']) {
requestBody['language_id'] = props['language_id'];
}
if (props['contact_group_ids'] && Array.isArray(props['contact_group_ids']) && props['contact_group_ids'].length > 0) {
requestBody['contact_group_ids'] = props['contact_group_ids'].join(',');
}
if (props['contact_branch_ids'] && Array.isArray(props['contact_branch_ids']) && props['contact_branch_ids'].length > 0) {
requestBody['contact_branch_ids'] = props['contact_branch_ids'].join(',');
}
if (props['user_id']) {
requestBody['user_id'] = props['user_id'];
}
if (props['owner_id']) {
requestBody['owner_id'] = props['owner_id'];
}
// Docs specify POST for updates (not PATCH)
const response = await client.post(`/2.0/contact/${contactId}`, requestBody);
return response;
},
});

View File

@@ -0,0 +1,534 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
export const updatePersonAction = createAction({
auth: bexioAuth,
name: 'update_person',
displayName: 'Update Person',
description: 'Update an existing person contact',
props: {
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Person',
description: 'Select the person to update',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
const persons = contacts.filter((contact) => contact.contact_type_id === 2);
return {
disabled: false,
options: persons.map((person) => {
const name = person.name_2
? `${person.name_2} ${person.name_1}`
: person.name_1;
const label = person.nr ? `${name} (#${person.nr})` : name;
return {
label,
value: person.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load persons',
options: [],
};
}
},
}),
name_1: Property.ShortText({
displayName: 'Last Name',
description: 'The last name of the person',
required: false,
}),
name_2: Property.ShortText({
displayName: 'First Name',
description: 'The first name of the person',
required: false,
}),
nr: Property.ShortText({
displayName: 'Contact Number',
description: 'Contact number (leave empty to keep current or auto-assign)',
required: false,
}),
salutation_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Salutation',
description: 'Salutation for the person',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const salutations = await client.get<Array<{ id: number; name: string }>>('/2.0/salutation');
return {
disabled: false,
options: salutations.map((sal) => ({
label: sal.name,
value: sal.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load salutations',
options: [],
};
}
},
}),
salutation_form: Property.Number({
displayName: 'Salutation Form',
description: 'Salutation form (optional)',
required: false,
}),
title_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Title',
description: 'Title for the person (e.g., Dr., Prof.)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const titles = await client.get<Array<{ id: number; name: string }>>('/2.0/title');
return {
disabled: false,
options: titles.map((title) => ({
label: title.name,
value: title.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load titles',
options: [],
};
}
},
}),
birthday: Property.ShortText({
displayName: 'Birthday',
description: 'Birthday date (YYYY-MM-DD)',
required: false,
}),
street_name: Property.ShortText({
displayName: 'Street Name',
description: 'Street name',
required: false,
}),
house_number: Property.ShortText({
displayName: 'House Number',
description: 'House number',
required: false,
}),
address_addition: Property.ShortText({
displayName: 'Address Addition',
description: 'Additional address information',
required: false,
}),
postcode: Property.ShortText({
displayName: 'Postcode',
description: 'Postal code',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
description: 'City',
required: false,
}),
country_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Country',
description: 'Country',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const countries = await client.get<Array<{ id: number; name: string; name_short: string; iso3166_alpha2: string }>>('/2.0/country');
return {
disabled: false,
options: countries.map((country) => ({
label: `${country.name} (${country.name_short})`,
value: country.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load countries',
options: [],
};
}
},
}),
mail: Property.ShortText({
displayName: 'Email',
description: 'Primary email address',
required: false,
}),
mail_second: Property.ShortText({
displayName: 'Secondary Email',
description: 'Secondary email address',
required: false,
}),
phone_fixed: Property.ShortText({
displayName: 'Phone',
description: 'Fixed phone number',
required: false,
}),
phone_fixed_second: Property.ShortText({
displayName: 'Secondary Phone',
description: 'Secondary fixed phone number',
required: false,
}),
phone_mobile: Property.ShortText({
displayName: 'Mobile Phone',
description: 'Mobile phone number',
required: false,
}),
fax: Property.ShortText({
displayName: 'Fax',
description: 'Fax number',
required: false,
}),
url: Property.ShortText({
displayName: 'Website URL',
description: 'Personal website URL',
required: false,
}),
skype_name: Property.ShortText({
displayName: 'Skype Name',
description: 'Skype username',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional notes about the person',
required: false,
}),
language_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Language',
description: 'Preferred language',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const languages = await client.get<Array<{ id: number; name: string }>>('/2.0/language');
return {
disabled: false,
options: languages.map((lang) => ({
label: lang.name,
value: lang.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load languages',
options: [],
};
}
},
}),
contact_group_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Groups',
description: 'Select contact groups',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_group');
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact groups',
options: [],
};
}
},
}),
contact_branch_ids: Property.MultiSelectDropdown({
auth: bexioAuth,
displayName: 'Contact Sectors',
description: 'Select contact sectors',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const branches = await client.get<Array<{ id: number; name: string }>>('/2.0/contact_branch');
return {
disabled: false,
options: branches.map((branch) => ({
label: branch.name,
value: branch.id.toString(),
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contact sectors',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User assigned to this contact',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
owner_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Owner',
description: 'Owner of this contact',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{ id: number; firstname: string | null; lastname: string | null; email: string }>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => ({
label: `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email,
value: user.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
},
async run(context) {
const client = new BexioClient(context.auth);
const contactId = context.propsValue['contact_id'] as number;
const props = context.propsValue;
const requestBody: Record<string, unknown> = {
contact_type_id: 2, // 2 = person
};
if (props['name_1']) {
requestBody['name_1'] = props['name_1'];
}
if (props['name_2'] !== undefined) {
requestBody['name_2'] = props['name_2'] || null;
}
if (props['nr'] !== undefined && props['nr'] !== null && props['nr'] !== '') {
requestBody['nr'] = props['nr'];
} else if (props['nr'] === '') {
requestBody['nr'] = null;
}
if (props['salutation_id']) {
requestBody['salutation_id'] = props['salutation_id'];
}
if (props['salutation_form']) {
requestBody['salutation_form'] = props['salutation_form'];
}
if (props['title_id']) {
requestBody['title_id'] = props['title_id'];
}
if (props['birthday']) {
requestBody['birthday'] = props['birthday'];
}
if (props['street_name']) {
requestBody['street_name'] = props['street_name'];
}
if (props['house_number']) {
requestBody['house_number'] = props['house_number'];
}
if (props['address_addition']) {
requestBody['address_addition'] = props['address_addition'];
}
if (props['postcode']) {
requestBody['postcode'] = props['postcode'];
}
if (props['city']) {
requestBody['city'] = props['city'];
}
if (props['country_id']) {
requestBody['country_id'] = props['country_id'];
}
if (props['mail']) {
requestBody['mail'] = props['mail'];
}
if (props['mail_second']) {
requestBody['mail_second'] = props['mail_second'];
}
if (props['phone_fixed']) {
requestBody['phone_fixed'] = props['phone_fixed'];
}
if (props['phone_fixed_second']) {
requestBody['phone_fixed_second'] = props['phone_fixed_second'];
}
if (props['phone_mobile']) {
requestBody['phone_mobile'] = props['phone_mobile'];
}
if (props['fax']) {
requestBody['fax'] = props['fax'];
}
if (props['url']) {
requestBody['url'] = props['url'];
}
if (props['skype_name']) {
requestBody['skype_name'] = props['skype_name'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['language_id']) {
requestBody['language_id'] = props['language_id'];
}
if (props['contact_group_ids'] && Array.isArray(props['contact_group_ids']) && props['contact_group_ids'].length > 0) {
requestBody['contact_group_ids'] = props['contact_group_ids'].join(',');
}
if (props['contact_branch_ids'] && Array.isArray(props['contact_branch_ids']) && props['contact_branch_ids'].length > 0) {
requestBody['contact_branch_ids'] = props['contact_branch_ids'].join(',');
}
if (props['user_id']) {
requestBody['user_id'] = props['user_id'];
}
if (props['owner_id']) {
requestBody['owner_id'] = props['owner_id'];
}
const response = await client.patch(`/2.0/contact/${contactId}`, requestBody);
return response;
},
});

View File

@@ -0,0 +1,592 @@
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { bexioCommonProps } from '../common/props';
export const updateProductAction = createAction({
auth: bexioAuth,
name: 'update_product',
displayName: 'Update Product',
description: 'Update an existing product or service',
props: {
article_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Product',
description: 'Select the product to update',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const articles = await client.get<Array<{
id: number;
intern_code?: string;
intern_name: string;
article_type_id?: number;
}>>('/2.0/article');
return {
disabled: false,
options: articles.map((article) => {
const label = article.intern_code
? `${article.intern_name} (${article.intern_code})`
: article.intern_name;
return {
label,
value: article.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load products',
options: [],
};
}
},
}),
user_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'User',
description: 'User associated with this product',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const users = await client.get<Array<{
id: number;
firstname?: string | null;
lastname?: string | null;
email: string;
}>>('/3.0/users');
return {
disabled: false,
options: users.map((user) => {
const name = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.email;
return {
label: name,
value: user.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load users',
options: [],
};
}
},
}),
intern_code: Property.ShortText({
displayName: 'Internal Code',
description: 'Internal product code',
required: false,
}),
intern_name: Property.ShortText({
displayName: 'Internal Name',
description: 'Internal product name',
required: false,
}),
intern_description: Property.LongText({
displayName: 'Internal Description',
description: 'Internal product description',
required: false,
}),
contact_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Contact',
description: 'Contact associated with this product',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const contacts = await client.get<Array<{
id: number;
contact_type_id: number;
name_1: string;
name_2?: string | null;
nr?: string | null;
}>>('/2.0/contact');
return {
disabled: false,
options: contacts.map((contact) => {
const name = contact.name_2
? `${contact.name_2} ${contact.name_1}`
: contact.name_1;
const label = contact.nr ? `${name} (#${contact.nr})` : name;
return {
label,
value: contact.id,
};
}),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load contacts',
options: [],
};
}
},
}),
deliverer_code: Property.ShortText({
displayName: 'Supplier Code',
description: 'Supplier product code',
required: false,
}),
deliverer_name: Property.ShortText({
displayName: 'Supplier Name',
description: 'Supplier product name',
required: false,
}),
deliverer_description: Property.LongText({
displayName: 'Supplier Description',
description: 'Supplier product description',
required: false,
}),
purchase_price: Property.ShortText({
displayName: 'Purchase Price',
description: 'Purchase price',
required: false,
}),
sale_price: Property.ShortText({
displayName: 'Sale Price',
description: 'Sale price',
required: false,
}),
purchase_total: Property.Number({
displayName: 'Purchase Total',
description: 'Total purchase amount',
required: false,
}),
sale_total: Property.Number({
displayName: 'Sale Total',
description: 'Total sale amount',
required: false,
}),
currency_id: bexioCommonProps.currency({
displayName: 'Currency',
required: false,
}),
tax_income_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Income Tax',
description: 'Tax for income/sales',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const taxes = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
type?: string;
}>>('/3.0/taxes');
return {
disabled: false,
options: taxes
.filter((tax) => tax.type === 'sales_tax' || !tax.type)
.map((tax) => ({
label: tax.display_name || tax.name,
value: tax.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load taxes',
options: [],
};
}
},
}),
tax_expense_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Expense Tax',
description: 'Tax for expenses/purchases',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const taxes = await client.get<Array<{
id: number;
name: string;
value: number;
display_name?: string;
type?: string;
}>>('/3.0/taxes');
return {
disabled: false,
options: taxes
.filter((tax) => tax.type === 'pre_tax' || !tax.type)
.map((tax) => ({
label: tax.display_name || tax.name,
value: tax.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load taxes',
options: [],
};
}
},
}),
unit_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Unit',
description: 'Unit of measurement',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const units = await client.get<Array<{ id: number; name: string }>>('/2.0/unit');
return {
disabled: false,
options: units.map((unit) => ({
label: unit.name,
value: unit.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load units',
options: [],
};
}
},
}),
is_stock: Property.Checkbox({
displayName: 'Is Stock Item',
description: 'Whether this is a stock item (requires stock_edit scope)',
required: false,
}),
stock_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Stock Location',
description: 'Stock location',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const stocks = await client.get<Array<{ id: number; name: string }>>('/2.0/stock').catch(() => []);
return {
disabled: false,
options: stocks.map((stock) => ({
label: stock.name,
value: stock.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load stock locations',
options: [],
};
}
},
}),
stock_place_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Stock Area',
description: 'Stock area/place',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const stockPlaces = await client.get<Array<{ id: number; name: string }>>('/2.0/stock_place').catch(() => []);
return {
disabled: false,
options: stockPlaces.map((place) => ({
label: place.name,
value: place.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load stock areas',
options: [],
};
}
},
}),
stock_nr: Property.Number({
displayName: 'Stock Number',
description: 'Current stock quantity (can only be set if no bookings exist)',
required: false,
}),
stock_min_nr: Property.Number({
displayName: 'Minimum Stock',
description: 'Minimum stock quantity',
required: false,
}),
width: Property.Number({
displayName: 'Width',
description: 'Product width',
required: false,
}),
height: Property.Number({
displayName: 'Height',
description: 'Product height',
required: false,
}),
weight: Property.Number({
displayName: 'Weight',
description: 'Product weight',
required: false,
}),
volume: Property.Number({
displayName: 'Volume',
description: 'Product volume',
required: false,
}),
remarks: Property.LongText({
displayName: 'Remarks',
description: 'Additional remarks',
required: false,
}),
delivery_price: Property.Number({
displayName: 'Delivery Price',
description: 'Delivery price',
required: false,
}),
article_group_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Article Group',
description: 'Product group/category',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const groups = await client.get<Array<{ id: number; name: string }>>('/2.0/article_group').catch(() => []);
return {
disabled: false,
options: groups.map((group) => ({
label: group.name,
value: group.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load article groups',
options: [],
};
}
},
}),
account_id: bexioCommonProps.account({
displayName: 'Account',
description: 'Account for this product',
required: false,
}),
expense_account_id: bexioCommonProps.account({
displayName: 'Expense Account',
description: 'Expense account for this product',
required: false,
}),
},
async run({ auth, propsValue }) {
const client = new BexioClient(auth);
const props = propsValue;
const articleId = props['article_id'] as number;
const requestBody: Record<string, unknown> = {};
if (props['user_id']) {
requestBody['user_id'] = props['user_id'];
}
if (props['intern_code']) {
requestBody['intern_code'] = props['intern_code'];
}
if (props['intern_name']) {
requestBody['intern_name'] = props['intern_name'];
}
if (props['intern_description']) {
requestBody['intern_description'] = props['intern_description'];
}
if (props['contact_id']) {
requestBody['contact_id'] = props['contact_id'];
}
if (props['deliverer_code']) {
requestBody['deliverer_code'] = props['deliverer_code'];
}
if (props['deliverer_name']) {
requestBody['deliverer_name'] = props['deliverer_name'];
}
if (props['deliverer_description']) {
requestBody['deliverer_description'] = props['deliverer_description'];
}
if (props['purchase_price']) {
requestBody['purchase_price'] = props['purchase_price'];
}
if (props['sale_price']) {
requestBody['sale_price'] = props['sale_price'];
}
if (props['purchase_total']) {
requestBody['purchase_total'] = props['purchase_total'];
}
if (props['sale_total']) {
requestBody['sale_total'] = props['sale_total'];
}
if (props['currency_id']) {
requestBody['currency_id'] = props['currency_id'];
}
if (props['tax_income_id']) {
requestBody['tax_income_id'] = props['tax_income_id'];
}
if (props['tax_expense_id']) {
requestBody['tax_expense_id'] = props['tax_expense_id'];
}
if (props['unit_id']) {
requestBody['unit_id'] = props['unit_id'];
}
if (props['is_stock'] !== undefined) {
requestBody['is_stock'] = props['is_stock'];
}
if (props['stock_id']) {
requestBody['stock_id'] = props['stock_id'];
}
if (props['stock_place_id']) {
requestBody['stock_place_id'] = props['stock_place_id'];
}
if (props['stock_nr'] !== undefined) {
requestBody['stock_nr'] = props['stock_nr'];
}
if (props['stock_min_nr'] !== undefined) {
requestBody['stock_min_nr'] = props['stock_min_nr'];
}
if (props['width']) {
requestBody['width'] = props['width'];
}
if (props['height']) {
requestBody['height'] = props['height'];
}
if (props['weight']) {
requestBody['weight'] = props['weight'];
}
if (props['volume']) {
requestBody['volume'] = props['volume'];
}
if (props['remarks']) {
requestBody['remarks'] = props['remarks'];
}
if (props['delivery_price']) {
requestBody['delivery_price'] = props['delivery_price'];
}
if (props['article_group_id']) {
requestBody['article_group_id'] = props['article_group_id'];
}
if (props['account_id']) {
requestBody['account_id'] = props['account_id'];
}
if (props['expense_account_id']) {
requestBody['expense_account_id'] = props['expense_account_id'];
}
const response = await client.post<{
id: number;
intern_code: string;
intern_name: string;
}>(`/2.0/article/${articleId}`, requestBody);
return response;
},
});

View File

@@ -0,0 +1,65 @@
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
import { bexioCommon } from './index';
export class BexioClient {
constructor(private auth: {access_token: string}) {}
async makeRequest<T>(
method: HttpMethod,
endpoint: string,
data?: unknown,
queryParams?: Record<string, string>
): Promise<T> {
// If endpoint already includes version (starts with /2.0/ or /3.0/), use it directly
// Otherwise, prepend the default API version
const url = endpoint.startsWith('/2.0/') || endpoint.startsWith('/3.0/')
? `${bexioCommon.baseUrl}${endpoint}`
: `${bexioCommon.baseUrl}/${bexioCommon.api_version}${endpoint}`;
const request: HttpRequest = {
method,
url,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: this.auth.access_token,
},
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
if (data) {
request.body = data;
}
if (queryParams) {
request.queryParams = queryParams;
}
const response = await httpClient.sendRequest<T>(request);
return response.body;
}
async get<T>(endpoint: string, queryParams?: Record<string, string>): Promise<T> {
return this.makeRequest<T>(HttpMethod.GET, endpoint, undefined, queryParams);
}
async post<T>(endpoint: string, data: unknown, queryParams?: Record<string, string>): Promise<T> {
return this.makeRequest<T>(HttpMethod.POST, endpoint, data, queryParams);
}
async patch<T>(endpoint: string, data: unknown): Promise<T> {
return this.makeRequest<T>(HttpMethod.PATCH, endpoint, data);
}
async delete<T>(endpoint: string): Promise<T> {
return this.makeRequest<T>(HttpMethod.DELETE, endpoint);
}
}

View File

@@ -0,0 +1,5 @@
export const bexioCommon = {
baseUrl: 'https://api.bexio.com',
api_version: '3.0',
};

View File

@@ -0,0 +1,127 @@
import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import { BexioClient } from './client';
import { BexioAccount, BexioTax, BexioCurrency } from './types';
import { bexioAuth } from '../..';
export const bexioCommonProps = {
account: (options: {
displayName: string;
description?: string;
required: boolean;
}) => {
return Property.Dropdown({
auth: bexioAuth,
displayName: options.displayName,
description: options.description,
required: options.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const accounts = await client.get<BexioAccount[]>('/accounts');
return {
disabled: false,
options: accounts.map((account) => ({
label: `${account.account_no} - ${account.name}`,
value: account.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load accounts',
options: [],
};
}
},
});
},
tax: Property.Dropdown({
auth: bexioAuth,
displayName: 'Tax',
description: 'Select a tax rate',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const taxes = await client.get<BexioTax[]>('/taxes');
return {
disabled: false,
options: taxes.map((tax) => ({
label: `${tax.name} (${tax.percentage}%)`,
value: tax.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load taxes',
options: [],
};
}
},
}),
currency: (options: {
displayName: string;
description?: string;
required: boolean;
}) => {
return Property.Dropdown({
auth: bexioAuth,
displayName: options.displayName,
description: options.description,
required: options.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const currencies = await client.get<BexioCurrency[]>('/3.0/currencies');
return {
disabled: false,
options: currencies.map((currency) => ({
label: currency.name,
value: currency.id,
})),
};
} catch (error) {
return {
disabled: true,
placeholder: 'Failed to load currencies',
options: [],
};
}
},
});
},
};

View File

@@ -0,0 +1,99 @@
// Common types for Bexio API
export interface BexioContact {
id: number;
nr: string;
contact_type_id: number;
name_1: string;
name_2?: string;
salutation_id?: number;
salutation_form?: number;
title_id?: number;
birthday?: string;
address?: string;
postcode?: string;
city?: string;
country_id?: number;
mail?: string;
mail_second?: string;
phone_fixed?: string;
phone_fixed_second?: string;
phone_mobile?: string;
fax?: string;
url?: string;
skype_name?: string;
remarks?: string;
language_id?: number;
contact_group_ids?: string;
contact_branch_ids?: string;
user_id?: number;
owner_id?: number;
}
export interface BexioCompany {
id: number;
name: string;
address?: string;
address_nr?: string;
postcode?: string;
city?: string;
country_id?: number;
legal_form?: string;
}
export interface BexioAccount {
id: number;
account_no: string;
name: string;
account_type: number;
}
export interface BexioTax {
id: number;
name: string;
percentage: string;
}
export interface BexioCurrency {
id: number;
name: string;
}
export interface BexioManualEntry {
debit_account_id?: number;
credit_account_id?: number;
tax_id?: number;
tax_account_id?: number;
description?: string;
amount?: number;
currency_id?: number;
currency_factor?: number;
}
export interface BexioManualEntryResponse {
id: number;
type: 'manual_single_entry' | 'manual_compound_entry' | 'manual_group_entry';
date: string;
reference_nr?: string;
created_by_user_id?: number;
edited_by_user_id?: number;
entries: BexioManualEntry[];
is_locked?: boolean;
locked_info?: string;
}
export interface BexioError {
error_code: number;
message: string;
}
export interface BexioListResponse<T> {
data: T[];
paging?: {
page: number;
page_size: number;
page_count: number;
item_count: number;
};
}

View File

@@ -0,0 +1,181 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
import dayjs from 'dayjs';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
{ status_id?: number }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS, propsValue }) => {
const client = new BexioClient(auth);
const isTest = lastFetchEpochMS === 0;
const lastFetchDate = isTest
? dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss')
: dayjs(lastFetchEpochMS).format('YYYY-MM-DD HH:mm:ss');
const searchBody: Array<{ field: string; value: string; criteria: string }> = [
{
field: 'updated_at',
value: lastFetchDate,
criteria: '>=',
},
];
if (propsValue.status_id !== undefined && propsValue.status_id !== null) {
searchBody.push({
field: 'kb_item_status_id',
value: propsValue.status_id.toString(),
criteria: '=',
});
}
const queryParams: Record<string, string> = {
order_by: 'updated_at_desc',
limit: isTest ? '5' : '500',
};
const orders = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
updated_at: string;
}>>('/2.0/kb_order/search', searchBody, queryParams);
return orders.map((order) => {
const updatedAt = order.updated_at || new Date().toISOString();
const epochMilliSeconds = dayjs(updatedAt).valueOf();
return {
epochMilliSeconds,
data: order,
};
});
},
};
export const newOrderTrigger = createTrigger({
auth: bexioAuth,
name: 'new_order',
displayName: 'New Order',
description: 'Triggers when an Order is created or updated with the chosen status',
type: TriggerStrategy.POLLING,
props: {
status_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Status',
description: 'Filter orders by status (leave empty to trigger for all statuses)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const statuses = await client.get<Array<{
id: number;
name: string;
}>>('/2.0/kb_order_status').catch(() => []);
if (statuses.length === 0) {
return {
disabled: false,
placeholder: 'Status filter not available - will trigger for all statuses',
options: [],
};
}
return {
disabled: false,
options: statuses.map((status) => ({
label: status.name,
value: status.id,
})),
};
} catch (error) {
return {
disabled: false,
placeholder: 'Enter status ID manually or leave empty for all statuses',
options: [],
};
}
},
}),
},
sampleData: {
id: 4,
document_nr: 'AU-00001',
title: null,
contact_id: 14,
contact_sub_id: null,
user_id: 1,
project_id: null,
language_id: 1,
bank_account_id: 1,
currency_id: 1,
payment_type_id: 1,
total_gross: '17.800000',
total_net: '17.800000',
total_taxes: '1.3706',
total: '19.150000',
kb_item_status_id: 5,
is_valid_from: '2019-06-24',
updated_at: '2019-04-08 13:17:32',
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,151 @@
import { createTrigger, TriggerStrategy, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ auth, lastItemId }) => {
const client = new BexioClient(auth);
// Use GET endpoint to fetch all articles, then sort by ID descending
// This avoids the issue with empty search arrays
const queryParams: Record<string, string> = {
limit: '500',
};
const products = await client.get<Array<{
id: number;
user_id: number;
article_type_id: number;
contact_id: number | null;
deliverer_code: string | null;
deliverer_name: string | null;
deliverer_description: string | null;
intern_code: string;
intern_name: string;
intern_description: string | null;
purchase_price: string | null;
sale_price: string | null;
purchase_total: number | null;
sale_total: number | null;
currency_id: number | null;
tax_income_id: number | null;
tax_id: number | null;
tax_expense_id: number | null;
unit_id: number | null;
is_stock: boolean;
stock_id: number | null;
stock_place_id: number | null;
stock_nr: number;
stock_min_nr: number;
stock_reserved_nr: number;
stock_available_nr: number;
stock_picked_nr: number;
stock_disposed_nr: number;
stock_ordered_nr: number;
width: number | null;
height: number | null;
weight: number | null;
volume: number | null;
html_text: string | null;
remarks: string | null;
delivery_price: number | null;
article_group_id: number | null;
account_id: number | null;
expense_account_id: number | null;
}>>('/2.0/article', queryParams);
// Sort by ID descending to get newest first (since GET doesn't support order_by)
const sortedProducts = products.sort((a, b) => b.id - a.id);
return sortedProducts.map((product) => ({
id: product.id,
data: product,
}));
},
};
export const newProductTrigger = createTrigger({
auth: bexioAuth,
name: 'new_product',
displayName: 'New Product',
description: 'Triggers when a new product is created',
type: TriggerStrategy.POLLING,
props: {},
sampleData: {
id: 4,
user_id: 1,
article_type_id: 1,
contact_id: 14,
deliverer_code: null,
deliverer_name: null,
deliverer_description: null,
intern_code: 'wh-2019',
intern_name: 'Webhosting',
intern_description: null,
purchase_price: null,
sale_price: null,
purchase_total: null,
sale_total: null,
currency_id: null,
tax_income_id: null,
tax_id: null,
tax_expense_id: null,
unit_id: null,
is_stock: false,
stock_id: null,
stock_place_id: null,
stock_nr: 0,
stock_min_nr: 0,
stock_reserved_nr: 0,
stock_available_nr: 0,
stock_picked_nr: 0,
stock_disposed_nr: 0,
stock_ordered_nr: 0,
width: null,
height: null,
weight: null,
volume: null,
html_text: null,
remarks: null,
delivery_price: null,
article_group_id: null,
account_id: null,
expense_account_id: null,
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,162 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
{ status_id?: number }
> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ auth, lastItemId, propsValue }) => {
const client = new BexioClient(auth);
const searchBody: Array<{ field: string; value: string; criteria: string }> = [
{
field: 'name',
value: '',
criteria: 'not_null',
},
];
if (propsValue.status_id !== undefined && propsValue.status_id !== null) {
searchBody.push({
field: 'pr_state_id',
value: propsValue.status_id.toString(),
criteria: '=',
});
}
const queryParams: Record<string, string> = {
order_by: 'id_desc',
limit: '500',
};
const projects = await client.post<Array<{
id: number;
uuid: string;
nr: string;
name: string;
start_date: string | null;
end_date: string | null;
comment: string | null;
pr_state_id: number;
pr_project_type_id: number;
contact_id: number | null;
contact_sub_id: number | null;
pr_invoice_type_id: number | null;
pr_invoice_type_amount: string | null;
pr_budget_type_id: number | null;
pr_budget_type_amount: string | null;
user_id: number;
}>>('/2.0/pr_project/search', searchBody, queryParams);
return projects.map((project) => ({
id: project.id,
data: project,
}));
},
};
export const newProjectTrigger = createTrigger({
auth: bexioAuth,
name: 'new_project',
displayName: 'New Project',
description: 'Triggers when a Project is created or updated with the chosen status',
type: TriggerStrategy.POLLING,
props: {
status_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Status',
description: 'Filter projects by status (leave empty to trigger for all statuses)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const statuses = await client.get<Array<{ id: number; name: string }>>('/2.0/pr_project_state').catch(() => []);
if (statuses.length === 0) {
return {
disabled: false,
placeholder: 'Status filter not available - will trigger for all statuses',
options: [],
};
}
return {
disabled: false,
options: statuses.map((status) => ({
label: status.name,
value: status.id,
})),
};
} catch (error) {
return {
disabled: false,
placeholder: 'Enter status ID manually or leave empty for all statuses',
options: [],
};
}
},
}),
},
sampleData: {
id: 2,
uuid: '046b6c7f-0b8a-43b9-b35d-6489e6daee91',
nr: '000002',
name: 'Villa Kunterbunt',
start_date: '2019-07-12 00:00:00',
end_date: null,
comment: '',
pr_state_id: 2,
pr_project_type_id: 2,
contact_id: 2,
contact_sub_id: null,
pr_invoice_type_id: 3,
pr_invoice_type_amount: '230.00',
pr_budget_type_id: 1,
pr_budget_type_amount: '200.00',
user_id: 1,
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,183 @@
import { createTrigger, TriggerStrategy, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
import dayjs from 'dayjs';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
{ status_id?: number }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS, propsValue }) => {
const client = new BexioClient(auth);
const isTest = lastFetchEpochMS === 0;
const lastFetchDate = isTest
? dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss')
: dayjs(lastFetchEpochMS).format('YYYY-MM-DD HH:mm:ss');
const searchBody: Array<{ field: string; value: string; criteria: string }> = [
{
field: 'updated_at',
value: lastFetchDate,
criteria: '>=',
},
];
if (propsValue.status_id !== undefined && propsValue.status_id !== null) {
searchBody.push({
field: 'kb_item_status_id',
value: propsValue.status_id.toString(),
criteria: '=',
});
}
const queryParams: Record<string, string> = {
order_by: 'updated_at_desc',
limit: isTest ? '5' : '500',
};
const quotes = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
is_valid_until: string | null;
updated_at: string;
}>>('/2.0/kb_offer/search', searchBody, queryParams);
return quotes.map((quote) => {
const updatedAt = quote.updated_at || new Date().toISOString();
const epochMilliSeconds = dayjs(updatedAt).valueOf();
return {
epochMilliSeconds,
data: quote,
};
});
},
};
export const newQuoteTrigger = createTrigger({
auth: bexioAuth,
name: 'new_quote',
displayName: 'New Quotes',
description: 'Triggers when a quote is created or updated with the chosen status',
type: TriggerStrategy.POLLING,
props: {
status_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Status',
description: 'Filter quotes by status (leave empty to trigger for all statuses)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const statuses = await client.get<Array<{
id: number;
name: string;
}>>('/2.0/kb_offer_status').catch(() => []);
if (statuses.length === 0) {
return {
disabled: false,
placeholder: 'Status filter not available - will trigger for all statuses',
options: [],
};
}
return {
disabled: false,
options: statuses.map((status) => ({
label: status.name,
value: status.id,
})),
};
} catch (error) {
return {
disabled: false,
placeholder: 'Enter status ID manually or leave empty for all statuses',
options: [],
};
}
},
}),
},
sampleData: {
id: 4,
document_nr: 'AN-00001',
title: null,
contact_id: 14,
contact_sub_id: null,
user_id: 1,
project_id: null,
language_id: 1,
bank_account_id: 1,
currency_id: 1,
payment_type_id: 1,
total_gross: '17.800000',
total_net: '17.800000',
total_taxes: '1.3706',
total: '19.150000',
kb_item_status_id: 3,
is_valid_from: '2019-06-24',
is_valid_until: '2019-07-24',
updated_at: '2019-04-08 13:17:32',
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,183 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
import dayjs from 'dayjs';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
{ status_id?: number }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS, propsValue }) => {
const client = new BexioClient(auth);
const isTest = lastFetchEpochMS === 0;
const lastFetchDate = isTest
? dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss')
: dayjs(lastFetchEpochMS).format('YYYY-MM-DD HH:mm:ss');
const searchBody: Array<{ field: string; value: string; criteria: string }> = [
{
field: 'updated_at',
value: lastFetchDate,
criteria: '>=',
},
];
if (propsValue.status_id !== undefined && propsValue.status_id !== null) {
searchBody.push({
field: 'kb_item_status_id',
value: propsValue.status_id.toString(),
criteria: '=',
});
}
const queryParams: Record<string, string> = {
order_by: 'updated_at_desc',
limit: isTest ? '5' : '500',
};
const invoices = await client.post<Array<{
id: number;
document_nr: string;
title: string | null;
contact_id: number | null;
contact_sub_id: number | null;
user_id: number;
project_id: number | null;
language_id: number;
bank_account_id: number;
currency_id: number;
payment_type_id: number;
total_gross: string;
total_net: string;
total_taxes: string;
total: string;
kb_item_status_id: number;
is_valid_from: string;
is_valid_to: string | null;
updated_at: string;
}>>('/2.0/kb_invoice/search', searchBody, queryParams);
return invoices.map((invoice) => {
const updatedAt = invoice.updated_at || new Date().toISOString();
const epochMilliSeconds = dayjs(updatedAt).valueOf();
return {
epochMilliSeconds,
data: invoice,
};
});
},
};
export const newSalesInvoiceTrigger = createTrigger({
auth: bexioAuth,
name: 'new_sales_invoice',
displayName: 'New Sales-Invoice',
description: 'Triggers when a Sales-Invoice is created or updated with the chosen status',
type: TriggerStrategy.POLLING,
props: {
status_id: Property.Dropdown({
auth: bexioAuth,
displayName: 'Status',
description: 'Filter invoices by status (leave empty to trigger for all statuses)',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Bexio account first',
options: [],
};
}
try {
const client = new BexioClient(auth);
const statuses = await client.get<Array<{
id: number;
name: string;
}>>('/2.0/kb_invoice_status').catch(() => []);
if (statuses.length === 0) {
return {
disabled: false,
placeholder: 'Status filter not available - will trigger for all statuses',
options: [],
};
}
return {
disabled: false,
options: statuses.map((status) => ({
label: status.name,
value: status.id,
})),
};
} catch (error) {
return {
disabled: false,
placeholder: 'Enter status ID manually or leave empty for all statuses',
options: [],
};
}
},
}),
},
sampleData: {
id: 4,
document_nr: 'RE-00001',
title: null,
contact_id: 14,
contact_sub_id: null,
user_id: 1,
project_id: null,
language_id: 1,
bank_account_id: 1,
currency_id: 1,
payment_type_id: 1,
total_gross: '17.800000',
total_net: '17.800000',
total_taxes: '1.3706',
total: '19.150000',
kb_item_status_id: 3,
is_valid_from: '2019-06-24',
is_valid_to: '2019-07-24',
updated_at: '2019-04-08 13:17:32',
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,158 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import dayjs from 'dayjs';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
const client = new BexioClient(auth);
const isTest = lastFetchEpochMS === 0;
const lastFetchDate = isTest
? dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss')
: dayjs(lastFetchEpochMS).format('YYYY-MM-DD HH:mm:ss');
const searchBody = [
{
field: 'contact_type_id',
value: '1', // 1 = company
criteria: '=',
},
{
field: 'updated_at',
value: lastFetchDate,
criteria: '>=',
},
];
const queryParams: Record<string, string> = {
order_by: 'updated_at_desc',
limit: isTest ? '5' : '500', // Limit to 5 for test, 500 for normal polling
};
const companies = await client.post<Array<{
id: number;
nr?: string | null;
contact_type_id: number;
name_1: string;
name_2?: string | null;
salutation_id?: number | null;
salutation_form?: string | null;
title_id?: number | null;
birthday?: string | null;
address?: string | null;
street_name?: string | null;
house_number?: string | null;
address_addition?: string | null;
postcode?: string | null;
city?: string | null;
country_id?: number | null;
mail?: string | null;
mail_second?: string | null;
phone_fixed?: string | null;
phone_fixed_second?: string | null;
phone_mobile?: string | null;
fax?: string | null;
url?: string | null;
skype_name?: string | null;
remarks?: string | null;
language_id?: number | null;
is_lead?: boolean;
contact_group_ids?: string | null;
contact_branch_ids?: string | null;
user_id?: number;
owner_id?: number;
updated_at?: string;
}>>('/2.0/contact/search', searchBody, queryParams);
return companies.map((company) => {
const updatedAt = company.updated_at || new Date().toISOString();
const epochMilliSeconds = dayjs(updatedAt).valueOf();
return {
epochMilliSeconds,
data: company,
};
});
},
};
export const newUpdatedCompanyTrigger = createTrigger({
auth: bexioAuth,
name: 'new_updated_company',
displayName: 'New/Updated Company',
description: 'Triggers when a company is added or updated',
type: TriggerStrategy.POLLING,
props: {},
sampleData: {
id: 4,
nr: null,
contact_type_id: 1,
name_1: 'Example Company',
name_2: null,
salutation_id: 2,
salutation_form: null,
title_id: null,
birthday: null,
address: 'Smith Street 22',
street_name: 'Smith Street',
house_number: '77',
address_addition: 'Building C',
postcode: '8004',
city: 'Zurich',
country_id: 1,
mail: 'contact@example.org',
mail_second: '',
phone_fixed: '',
phone_fixed_second: '',
phone_mobile: '',
fax: '',
url: '',
skype_name: '',
remarks: '',
language_id: null,
is_lead: false,
contact_group_ids: '1,2',
contact_branch_ids: null,
user_id: 1,
owner_id: 1,
updated_at: '2019-04-08 13:17:32',
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});

View File

@@ -0,0 +1,158 @@
import { createTrigger, TriggerStrategy, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { bexioAuth } from '../../index';
import { BexioClient } from '../common/client';
import dayjs from 'dayjs';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof bexioAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
const client = new BexioClient(auth);
const isTest = lastFetchEpochMS === 0;
const lastFetchDate = isTest
? dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss')
: dayjs(lastFetchEpochMS).format('YYYY-MM-DD HH:mm:ss');
const searchBody = [
{
field: 'contact_type_id',
value: '2', // 2 = person
criteria: '=',
},
{
field: 'updated_at',
value: lastFetchDate,
criteria: '>=',
},
];
const queryParams: Record<string, string> = {
order_by: 'updated_at_desc',
limit: isTest ? '5' : '500',
};
const persons = await client.post<Array<{
id: number;
nr?: string | null;
contact_type_id: number;
name_1: string;
name_2?: string | null;
salutation_id?: number | null;
salutation_form?: string | null;
title_id?: number | null;
birthday?: string | null;
address?: string | null;
street_name?: string | null;
house_number?: string | null;
address_addition?: string | null;
postcode?: string | null;
city?: string | null;
country_id?: number | null;
mail?: string | null;
mail_second?: string | null;
phone_fixed?: string | null;
phone_fixed_second?: string | null;
phone_mobile?: string | null;
fax?: string | null;
url?: string | null;
skype_name?: string | null;
remarks?: string | null;
language_id?: number | null;
is_lead?: boolean;
contact_group_ids?: string | null;
contact_branch_ids?: string | null;
user_id?: number;
owner_id?: number;
updated_at?: string;
}>>('/2.0/contact/search', searchBody, queryParams);
return persons.map((person) => {
const updatedAt = person.updated_at || new Date().toISOString();
const epochMilliSeconds = dayjs(updatedAt).valueOf();
return {
epochMilliSeconds,
data: person,
};
});
},
};
export const newUpdatedPersonTrigger = createTrigger({
auth: bexioAuth,
name: 'new_updated_person',
displayName: 'New/Updated Person',
description: 'Triggers when a person is added or updated',
type: TriggerStrategy.POLLING,
props: {},
sampleData: {
id: 4,
nr: null,
contact_type_id: 2,
name_1: 'John',
name_2: 'Doe',
salutation_id: 1,
salutation_form: null,
title_id: null,
birthday: '1990-01-01',
address: 'Smith Street 22',
street_name: 'Smith Street',
house_number: '77',
address_addition: 'Building C',
postcode: '8004',
city: 'Zurich',
country_id: 1,
mail: 'john.doe@example.org',
mail_second: '',
phone_fixed: '',
phone_fixed_second: '',
phone_mobile: '',
fax: '',
url: '',
skype_name: '',
remarks: '',
language_id: null,
is_lead: false,
contact_group_ids: '1,2',
contact_branch_ids: null,
user_id: 1,
owner_id: 1,
updated_at: '2019-04-08 13:17:32',
},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});