Skip to main content
Glama
create-sales-quote.ts23 kB
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; }, });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server