Add Activepieces integration for workflow automation

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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