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:
@@ -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)"
|
||||
}
|
||||
130
activepieces-fork/packages/pieces/community/bexio/src/index.ts
Normal file
130
activepieces-fork/packages/pieces/community/bexio/src/index.ts
Normal 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,
|
||||
],
|
||||
});
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const bexioCommon = {
|
||||
baseUrl: 'https://api.bexio.com',
|
||||
api_version: '3.0',
|
||||
};
|
||||
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user