diff --git a/packages/nodes-base/credentials/Magento2Api.credentials.ts b/packages/nodes-base/credentials/Magento2Api.credentials.ts new file mode 100644 index 0000000000..640e4250c1 --- /dev/null +++ b/packages/nodes-base/credentials/Magento2Api.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class Magento2Api implements ICredentialType { + name = 'magento2Api'; + displayName = 'Magento 2 API'; + properties = [ + { + displayName: 'Host', + name: 'host', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Magento/CustomerDescription.ts b/packages/nodes-base/nodes/Magento/CustomerDescription.ts new file mode 100644 index 0000000000..c3a3af60f8 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/CustomerDescription.ts @@ -0,0 +1,313 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + getCustomerOptionalFields, + getSearchFilters, +} from './GenericFunctions'; + +export const customerOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new customer', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a customer', + }, + { + name: 'Get', + value: 'get', + description: 'Get a customer', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all customers', + }, + { + name: 'Update', + value: 'update', + description: 'Update a customer', + }, + ], + default: 'create', + description: 'The operation to perform', + }, +] as INodeProperties[]; + +export const customerFields = [ + + /* -------------------------------------------------------------------------- */ + /* customer:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + description: 'Email address of the user to create', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + description: 'First name of the user to create', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + description: 'Last name of the user to create', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + ...getCustomerOptionalFields(), + ], + }, + + /* -------------------------------------------------------------------------- */ + /* customer:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + description: 'ID of the customer to update', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Website Name/ID', + name: 'website_id', + type: 'options', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getWebsites', + }, + default: '', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + ...getCustomerOptionalFields(), + ], + }, + + /* -------------------------------------------------------------------------- */ + /* customer:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'delete', + 'get', + ], + }, + }, + }, + + /* -------------------------------------------------------------------------- */ + /* customer:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return', + }, + ...getSearchFilters( + 'customer', + 'getSystemAttributes', + 'getSystemAttributes', + ), +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Magento/GenericFunctions.ts b/packages/nodes-base/nodes/Magento/GenericFunctions.ts new file mode 100644 index 0000000000..7a17be3980 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/GenericFunctions.ts @@ -0,0 +1,1031 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodePropertyOptions, + NodeApiError, +} from 'n8n-workflow'; + +import { + Address, + Filter, + FilterGroup, + ProductAttribute, + Search, +} from './Types'; + +export async function magentoApiRequest(this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = await this.getCredentials('magento2Api') as IDataObject; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${credentials.accessToken}`, + }, + method, + body, + qs, + uri: uri || `${credentials.host}${resource}`, + json: true, + }; + + try { + options = Object.assign({}, options, option); + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.request.call(this, options); + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } +} + +export async function magentoApiRequestAllItems(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + do { + responseData = await magentoApiRequest.call(this, method, resource, body, query); + returnData.push.apply(returnData, responseData[propertyName]); + query['current_page'] = (query.current_page) ? (query.current_page as number)++ : 1; + } while ( + returnData.length < responseData.total_count + ); + + return returnData; +} + +export function getAddressesUi() { + return { + displayName: 'Addresses', + name: 'addresses', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + displayName: 'Address', + name: 'address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'Region', + name: 'region', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postcode', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'Country', + name: 'country_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCountries', + }, + required: true, + default: '', + }, + { + displayName: 'Company', + name: 'company', + type: 'string', + default: '', + }, + { + displayName: 'Fax', + name: 'fax', + type: 'string', + default: '', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'Middle Name', + name: 'middlename', + type: 'string', + default: '', + }, + { + displayName: 'Prefix', + name: 'prefix', + type: 'string', + default: '', + }, + { + displayName: 'Suffix', + name: 'suffix', + type: 'string', + default: '', + }, + { + displayName: 'Telephone', + name: 'telephone', + type: 'string', + required: true, + default: '', + }, + { + displayName: 'Default Billing', + name: 'default_billing', + type: 'boolean', + default: false, + descrition: 'Weather this address is default billing address', + }, + { + displayName: 'Default Shipping', + name: 'default_shipping', + type: 'boolean', + default: false, + descrition: 'Weather this address is default shipping address', + }, + ], + }, + ], + }; +} + +// tslint:disable-next-line: no-any +export function adjustAddresses(addresses: [{ street: string, [key: string]: string }]): Address[] { + const _addresses: Address[] = []; + for (let i = 0; i < addresses.length; i++) { + if (addresses[i]?.region === '') { + delete addresses[i].region; + } + _addresses.push({ + ...addresses[i], + street: [addresses[i].street], + }); + } + return _addresses; +} + +export function getSearchFilters(resource: string, filterableAttributeFunction: string, sortableAttributeFunction: string) { + return [ + { + displayName: 'Filter', + name: 'filterType', + type: 'options', + options: [ + { + name: 'None', + value: 'none', + }, + { + name: 'Build Manually', + value: 'manual', + }, + { + name: 'JSON', + value: 'json', + }, + ], + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + }, + }, + default: 'none', + }, + { + displayName: 'Must Match', + name: 'matchType', + type: 'options', + options: [ + { + name: 'Any filter', + value: 'anyFilter', + }, + { + name: 'All Filters', + value: 'allFilters', + }, + ], + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: 'anyFilter', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + filterType: [ + 'manual', + ], + }, + }, + default: '', + placeholder: 'Add Condition', + options: [ + { + displayName: 'Conditions', + name: 'conditions', + values: [ + ...getConditions(filterableAttributeFunction), + ], + }, + ], + }, + { + displayName: 'See Magento guide to creating filters', + name: 'jsonNotice', + type: 'notice', + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + }, + { + displayName: 'Filters (JSON)', + name: 'filterJson', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + filterType: [ + 'json', + ], + }, + }, + default: '', + description: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource: [ + resource, + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + // { + // displayName: 'Properties', + // name: 'properties', + // type: 'multiOptions', + // typeOptions: { + // loadOptionsMethod: attributeFunction, + // }, + // default: ['*'], + // description: 'Properties the response will return. By default all properties are returned', + // }, + { + displayName: 'Sort', + name: 'sort', + type: 'fixedCollection', + placeholder: 'Add Sort', + typeOptions: { + multipleValues: true, + }, + default: [], + options: [ + { + displayName: 'Sort', + name: 'sort', + values: [ + { + displayName: 'Direction', + name: 'direction', + type: 'options', + options: [ + { + name: 'Ascending', + value: 'ASC', + }, + { + name: 'Descending', + value: 'DESC', + }, + ], + default: 'ASC', + description: 'The sorting direction', + }, + { + displayName: 'Field', + name: 'field', + type: 'options', + typeOptions: { + loadOptionsMethod: sortableAttributeFunction, + }, + default: '', + description: `The sorting field`, + }, + ], + }, + ], + }, + ], + }, + ]; +} + +function getConditionTypeFields() { + return { + displayName: 'Condition Type', + name: 'condition_type', + type: 'options', + options: [ + { + name: 'Equals', + value: 'eq', + }, + { + name: 'Greater than', + value: 'gt', + }, + { + name: 'Greater than or equal', + value: 'gteq', + }, + { + name: 'In', + value: 'in', + description: 'The value can contain a comma-separated list of values', + }, + { + name: 'Like', + value: 'like', + description: 'The value can contain the SQL wildcard characters when like is specified', + }, + { + name: 'Less Than', + value: 'lt', + }, + { + name: 'Less Than or Equal', + value: 'lte', + }, + { + name: 'More or Equal', + value: 'moreq', + }, + { + name: 'Not Equal', + value: 'neq', + }, + { + name: 'Not In', + value: 'nin', + description: 'The value can contain a comma-separated list of values', + }, + { + name: 'Not Null', + value: 'notnull', + }, + { + name: 'Null', + value: 'null', + }, + ], + default: 'eq', + }; +} + +function getConditions(attributeFunction: string) { + return [ + { + displayName: 'Field', + name: 'field', + type: 'options', + typeOptions: { + loadOptionsMethod: attributeFunction, + }, + default: '', + }, + getConditionTypeFields(), + { + displayName: 'Value', + name: 'value', + type: 'string', + displayOptions: { + hide: { + condition_type: [ + 'null', + 'notnull', + ], + }, + }, + default: '', + }, + ]; +} + +export function getFilterQuery(data: { conditions?: Filter[], matchType: string, sort: [{ direction: string, field: string }] }): Search { + + if (!data.hasOwnProperty('conditions') || data.conditions?.length === 0) { + throw new Error('At least one filter has to be set'); + } + + if (data.matchType === 'anyFilter') { + return { + search_criteria: { + filter_groups: [ + { + filters: data?.conditions, + }, + ], + sort_orders: data.sort, + }, + }; + } else if (data.conditions?.length !== 0) { + return { + search_criteria: { + filter_groups: data?.conditions?.map((filter: Filter) => { + return { + filters: [filter], + }; + }) as FilterGroup[], + sort_orders: data.sort, + }, + }; + } + return { + search_criteria: {}, + }; +} + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} + +export function getCustomerOptionalFields() { + return [ + getAddressesUi(), + { + displayName: 'Amazon ID', + name: 'amazon_id', + type: 'string', + default: '', + }, + { + displayName: 'Confirmation', + name: 'confirmation', + type: 'string', + default: '', + }, + { + displayName: 'Custom Attributes', + name: 'customAttributes', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + default: '', + placeholder: 'Add Custom Attribute', + options: [ + { + displayName: 'Custom Attribute', + name: 'customAttribute', + values: [ + { + displayName: 'Attribute Code', + name: 'attribute_code', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCustomAttributes', + }, + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Date of Birth', + name: 'dob', + type: 'dateTime', + default: '', + }, + { + displayName: 'Default Billing Address ID', + name: 'default_billing', + type: 'string', + default: '', + }, + { + displayName: 'Default Shipping Address ID', + name: 'default_shipping', + type: 'string', + default: '', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'options', + options: [ + { + name: 'Male', + value: 1, + }, + { + name: 'Female', + value: 2, + }, + { + name: 'Not Specified', + value: 3, + }, + ], + default: '', + }, + { + displayName: 'Group Name/ID', + name: 'group_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + default: '', + }, + { + displayName: 'Is Subscribed', + name: 'is_subscribed', + type: 'boolean', + default: false, + }, + { + displayName: 'Middle Name', + name: 'middlename', + type: 'string', + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + }, + { + displayName: 'Prefix', + name: 'prefix', + type: 'string', + default: '', + }, + { + displayName: 'Store URL/ID', + name: 'store_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getStores', + }, + default: '', + }, + { + displayName: 'Suffix', + name: 'suffix', + type: 'string', + default: '', + }, + { + displayName: 'Vertex Customer Code', + name: 'vertex_customer_code', + type: 'string', + default: '', + }, + { + displayName: 'Vertex Customer Country', + name: 'vertex_customer_country', + type: 'string', + default: '', + }, + { + displayName: 'Website Name/ID', + name: 'website_id', + type: 'options', + displayOptions: { + show: { + '/operation': [ + 'create', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getWebsites', + }, + default: '', + }, + ]; +} + +export function getProductOptionalFields() { + return [ + { + displayName: 'Attribute Set Name/ID', + name: 'attribute_set_id', + type: 'options', + displayOptions: { + show: { + '/operation': [ + 'update', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getAttributeSets', + }, + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + displayOptions: { + show: { + '/operation': [ + 'update', + ], + }, + }, + default: '', + }, + // { + // displayName: 'Custom Attributes', + // name: 'customAttributes', + // type: 'fixedCollection', + // typeOptions: { + // multipleValues: true, + // }, + // default: '', + // placeholder: 'Add Custom Attribute', + // options: [ + // { + // displayName: 'Custom Attribute', + // name: 'customAttribute', + // values: [ + // { + // displayName: 'Attribute Code', + // name: 'attribute_code', + // type: 'options', + // typeOptions: { + // loadOptionsMethod: 'getProductAttributes', + // }, + // default: '', + // }, + // { + // displayName: 'Value', + // name: 'value', + // type: 'string', + // default: '', + // }, + // ], + // }, + // ], + // }, + // { + // displayName: 'Parent Category ID', + // name: 'category', + // type: 'options', + // typeOptions: { + // loadOptionsMethod: 'getCategories', + // }, + // default: '', + // }, + { + displayName: 'Price', + name: 'price', + type: 'number', + displayOptions: { + show: { + '/operation': [ + 'update', + ], + }, + }, + default: 0, + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Enabled', + value: 1, + }, + { + name: 'Disabled', + value: 2, + }, + ], + default: 1, + }, + { + displayName: 'Type Name/ID', + name: 'type_id', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getProductTypes', + }, + default: '', + }, + { + displayName: 'Visibility', + name: 'visibility', + type: 'options', + options: [ + { + name: 'Not Visible', + value: 1, + }, + { + name: 'Catalog', + value: 2, + }, + { + name: 'Search', + value: 3, + }, + { + name: 'Catalog & Search', + value: 4, + }, + ], + default: 4, + }, + { + displayName: 'Weight (LBS)', + name: 'weight', + type: 'number', + default: 0, + }, + ]; +} + +export function getOrderFields() { + return [ + 'adjustment_negative', + 'adjustment_positive', + 'applied_rule_ids', + 'base_adjustment_negative', + 'base_adjustment_positive', + 'base_currency_code', + 'base_discount_amount', + 'base_discount_canceled', + 'base_discount_invoiced', + 'base_discount_refunded', + 'base_grand_total', + 'base_discount_tax_compensation_amount', + 'base_discount_tax_compensation_invoiced', + 'base_discount_tax_compensation_refunded', + 'base_shipping_amount', + 'base_shipping_canceled', + 'base_shipping_discount_amount', + 'base_shipping_discount_tax_compensation_amnt', + 'base_shipping_incl_tax', + 'base_shipping_invoiced', + 'base_shipping_refunded', + 'base_shipping_tax_amount', + 'base_shipping_tax_refunded', + 'base_subtotal', + 'base_subtotal_canceled', + 'base_subtotal_incl_tax', + 'base_subtotal_invoiced', + 'base_subtotal_refunded', + 'base_tax_amount', + 'base_tax_canceled', + 'base_tax_invoiced', + 'base_tax_refunded', + 'base_total_canceled', + 'base_total_due', + 'base_total_invoiced', + 'base_total_invoiced_cost', + 'base_total_offline_refunded', + 'base_total_online_refunded', + 'base_total_paid', + 'base_total_qty_ordered', + 'base_total_refunded', + 'base_to_global_rate', + 'base_to_order_rate', + 'billing_address_id', + 'can_ship_partially', + 'can_ship_partially_item', + 'coupon_code', + 'created_at', + 'customer_dob', + 'customer_email', + 'customer_firstname', + 'customer_gender', + 'customer_group_id', + 'customer_id', + 'customer_is_guest', + 'customer_lastname', + 'customer_middlename', + 'customer_note', + 'customer_note_notify', + 'customer_prefix', + 'customer_suffix', + 'customer_taxvat', + 'discount_amount', + 'discount_canceled', + 'discount_description', + 'discount_invoiced', + 'discount_refunded', + 'edit_increment', + 'email_sent', + 'entity_id', + 'ext_customer_id', + 'ext_order_id', + 'forced_shipment_with_invoice', + 'global_currency_code', + 'grand_total', + 'discount_tax_compensation_amount', + 'discount_tax_compensation_invoiced', + 'discount_tax_compensation_refunded', + 'hold_before_state', + 'hold_before_status', + 'increment_id', + 'is_virtual', + 'order_currency_code', + 'original_increment_id', + 'payment_authorization_amount', + 'payment_auth_expiration', + 'protect_code', + 'quote_address_id', + 'quote_id', + 'relation_child_id', + 'relation_child_real_id', + 'relation_parent_id', + 'relation_parent_real_id', + 'remote_ip', + 'shipping_amount', + 'shipping_canceled', + 'shipping_description', + 'shipping_discount_amount', + 'shipping_discount_tax_compensation_amount', + 'shipping_incl_tax', + 'shipping_invoiced', + 'shipping_refunded', + 'shipping_tax_amount', + 'shipping_tax_refunded', + 'state', + 'status', + 'store_currency_code', + 'store_id', + 'store_name', + 'store_to_base_rate', + 'store_to_order_rate', + 'subtotal', + 'subtotal_canceled', + 'subtotal_incl_tax', + 'subtotal_invoiced', + 'subtotal_refunded', + 'tax_amount', + 'tax_canceled', + 'tax_invoiced', + 'tax_refunded', + 'total_canceled', + 'total_due', + 'total_invoiced', + 'total_item_count', + 'total_offline_refunded', + 'total_online_refunded', + 'total_paid', + 'total_qty_ordered', + 'total_refunded', + 'updated_at', + 'weight', + ]; +} +export const sort = (a: { name: string }, b: { name: string }) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; +}; + +// tslint:disable-next-line: no-any +export async function getProductAttributes(this: ILoadOptionsFunctions, filter?: (attribute: ProductAttribute) => any, extraValue?: { name: string, value: string }): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/productsattribute-setssetslist#operation/catalogAttributeSetRepositoryV1GetListGet + + let attributes: ProductAttribute[] = await magentoApiRequestAllItems.call(this, 'items', 'GET', `/rest/default/V1/products/attributes`, {}, { + search_criteria: 0, + }); + + attributes = attributes.filter((attribute) => + attribute.default_frontend_label !== undefined && attribute.default_frontend_label !== ''); + + if (filter) { + attributes = attributes.filter(filter); + } + + const returnData: INodePropertyOptions[] = []; + for (const attribute of attributes) { + returnData.push({ + name: attribute.default_frontend_label as string, + value: attribute.attribute_code as string, + }); + } + if (extraValue) { + returnData.unshift(extraValue); + } + return returnData.sort(sort); +} diff --git a/packages/nodes-base/nodes/Magento/InvoiceDescription.ts b/packages/nodes-base/nodes/Magento/InvoiceDescription.ts new file mode 100644 index 0000000000..a0d4fd3e8b --- /dev/null +++ b/packages/nodes-base/nodes/Magento/InvoiceDescription.ts @@ -0,0 +1,51 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const invoiceOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an invoice', + }, + ], + default: 'create', + description: 'The operation to perform', + }, +] as INodeProperties[]; + +export const invoiceFields = [ + + /* -------------------------------------------------------------------------- */ + /* invoice:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Order ID', + name: 'orderId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'invoice', + ], + operation: [ + 'create', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Magento/Magento2.node.ts b/packages/nodes-base/nodes/Magento/Magento2.node.ts new file mode 100644 index 0000000000..fee0c6b053 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/Magento2.node.ts @@ -0,0 +1,681 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, + NodeApiError, +} from 'n8n-workflow'; + +import { + adjustAddresses, + getFilterQuery, + getOrderFields, + getProductAttributes, + magentoApiRequest, + magentoApiRequestAllItems, + sort, + validateJSON, +} from './GenericFunctions'; + +import { + customerFields, + customerOperations, +} from './CustomerDescription'; + +import { + orderFields, + orderOperations, +} from './OrderDescription'; + +import { + productFields, + productOperations, +} from './ProductDescription'; + +import { + invoiceFields, + invoiceOperations, +} from './InvoiceDescription'; + +import { + CustomAttribute, + CustomerAttributeMetadata, + Filter, + NewCustomer, + NewProduct, + Search, +} from './Types'; + +import { + capitalCase, +} from 'change-case'; + +export class Magento2 implements INodeType { + description: INodeTypeDescription = { + displayName: 'Magento 2', + name: 'magento2', + icon: 'file:magento.svg', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Magento API', + defaults: { + name: 'Magento 2', + color: '#ec6737', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'magento2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Customer', + value: 'customer', + }, + { + name: 'Invoice', + value: 'invoice', + }, + { + name: 'Order', + value: 'order', + }, + { + name: 'Product', + value: 'product', + }, + ], + default: 'customer', + description: 'The resource to operate on', + }, + ...customerOperations, + ...customerFields, + ...invoiceOperations, + ...invoiceFields, + ...orderOperations, + ...orderFields, + ...productOperations, + ...productFields, + ], + }; + + methods = { + loadOptions: { + async getCountries(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/directorycountries + const countries = await magentoApiRequest.call(this, 'GET', '/rest/default/V1/directory/countries'); + const returnData: INodePropertyOptions[] = []; + for (const country of countries) { + returnData.push({ + name: country.full_name_english, + value: country.id, + }); + } + returnData.sort(sort); + return returnData; + }, + async getGroups(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/customerGroupsdefault#operation/customerGroupManagementV1GetDefaultGroupGet + const group = await magentoApiRequest.call(this, 'GET', '/rest/default/V1/customerGroups/default'); + const returnData: INodePropertyOptions[] = []; + returnData.push({ + name: group.code, + value: group.id, + }); + returnData.sort(sort); + return returnData; + }, + async getStores(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/storestoreConfigs + const stores = await magentoApiRequest.call(this, 'GET', '/rest/default/V1/store/storeConfigs'); + const returnData: INodePropertyOptions[] = []; + for (const store of stores) { + returnData.push({ + name: store.base_url, + value: store.id, + }); + } + returnData.sort(sort); + return returnData; + }, + async getWebsites(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/storewebsites + const websites = await magentoApiRequest.call(this, 'GET', '/rest/default/V1/store/websites'); + const returnData: INodePropertyOptions[] = []; + for (const website of websites) { + returnData.push({ + name: website.name, + value: website.id, + }); + } + returnData.sort(sort); + return returnData; + }, + async getCustomAttributes(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/attributeMetadatacustomer#operation/customerCustomerMetadataV1GetAllAttributesMetadataGet + const resource = this.getCurrentNodeParameter('resource') as string; + const attributes = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/attributeMetadata/${resource}`) as CustomerAttributeMetadata[]; + const returnData: INodePropertyOptions[] = []; + for (const attribute of attributes) { + if (attribute.system === false && attribute.frontend_label !== '') { + returnData.push({ + name: attribute.frontend_label as string, + value: attribute.attribute_code as string, + }); + } + } + returnData.sort(sort); + return returnData; + }, + async getSystemAttributes(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/attributeMetadatacustomer#operation/customerCustomerMetadataV1GetAllAttributesMetadataGet + const resource = this.getCurrentNodeParameter('resource') as string; + const attributes = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/attributeMetadata/${resource}`) as CustomerAttributeMetadata[]; + const returnData: INodePropertyOptions[] = []; + for (const attribute of attributes) { + if (attribute.system === true && attribute.frontend_label !== null) { + returnData.push({ + name: attribute.frontend_label as string, + value: attribute.attribute_code as string, + }); + } + } + returnData.sort(sort); + return returnData; + }, + async getProductTypes(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/productslinkstypes + const types = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/products/types`) as IDataObject[]; + const returnData: INodePropertyOptions[] = []; + for (const type of types) { + returnData.push({ + name: type.label as string, + value: type.name as string, + }); + } + returnData.sort(sort); + return returnData; + }, + async getCategories(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/categories#operation/catalogCategoryManagementV1GetTreeGet + const { items: categories } = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/categories/list`, {}, { + search_criteria: { + filter_groups: [ + { + filters: [ + { + field: 'is_active', + condition_type: 'eq', + value: 1, + }, + ], + }, + ], + }, + }) as { items: IDataObject[] }; + const returnData: INodePropertyOptions[] = []; + for (const category of categories) { + returnData.push({ + name: category.name as string, + value: category.id as string, + }); + } + returnData.sort(sort); + return returnData; + }, + async getAttributeSets(this: ILoadOptionsFunctions): Promise { + //https://magento.redoc.ly/2.3.7-admin/tag/productsattribute-setssetslist#operation/catalogAttributeSetRepositoryV1GetListGet + const { items: attributeSets } = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/products/attribute-sets/sets/list`, {}, { + search_criteria: 0, + }) as { items: IDataObject[] }; + const returnData: INodePropertyOptions[] = []; + for (const attributeSet of attributeSets) { + returnData.push({ + name: attributeSet.attribute_set_name as string, + value: attributeSet.attribute_set_id as string, + }); + } + returnData.sort(sort); + return returnData; + }, + async getFilterableCustomerAttributes(this: ILoadOptionsFunctions): Promise { + return getProductAttributes.call(this, (attribute) => attribute.is_filterable === true); + }, + async getProductAttributes(this: ILoadOptionsFunctions): Promise { + return getProductAttributes.call(this); + }, + // async getProductAttributesFields(this: ILoadOptionsFunctions): Promise { + // return getProductAttributes.call(this, undefined, { name: '*', value: '*', description: 'All properties' }); + // }, + async getFilterableProductAttributes(this: ILoadOptionsFunctions): Promise { + return getProductAttributes.call(this, (attribute) => attribute.is_searchable === '1'); + }, + async getSortableProductAttributes(this: ILoadOptionsFunctions): Promise { + return getProductAttributes.call(this, (attribute) => attribute.used_for_sort_by === true); + }, + async getOrderAttributes(this: ILoadOptionsFunctions): Promise { + return getOrderFields().map(field => ({ name: capitalCase(field), value: field })).sort(sort); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const timezone = this.getTimezone(); + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < length; i++) { + try { + if (resource === 'customer') { + if (operation === 'create') { + // https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut + const email = this.getNodeParameter('email', i) as string; + const firstname = this.getNodeParameter('firstname', i) as string; + const lastname = this.getNodeParameter('lastname', i) as string; + + const { + addresses, + customAttributes, + password, + ...rest + } = this.getNodeParameter('additionalFields', i) as { + addresses: { + address: [{ + street: string, + }] + }; + customAttributes: { + customAttribute: CustomAttribute[], + }, + password: string, + }; + + const body: NewCustomer = { + customer: { + email, + firstname, + lastname, + }, + }; + + body.customer!.addresses = adjustAddresses(addresses?.address || []); + + body.customer!.custom_attributes = customAttributes?.customAttribute || {}; + + body.customer!.extension_attributes = ['amazon_id', 'is_subscribed', 'vertex_customer_code', 'vertex_customer_country'] + // tslint:disable-next-line: no-any + .reduce((obj, value: string): any => { + if ((rest as IDataObject).hasOwnProperty(value)) { + const data = Object.assign(obj, { [value]: (rest as IDataObject)[value] }); + delete (rest as IDataObject)[value]; + return data; + } else { + return obj; + } + }, {}); + + if (password) { + body.password = password; + } + + Object.assign(body.customer, rest); + + responseData = await magentoApiRequest.call(this, 'POST', '/rest/V1/customers', body); + } + + if (operation === 'delete') { + //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut + const customerId = this.getNodeParameter('customerId', i) as string; + + responseData = await magentoApiRequest.call(this, 'DELETE', `/rest/default/V1/customers/${customerId}`); + + responseData = { success: true }; + } + + if (operation === 'get') { + //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1GetByIdGet + const customerId = this.getNodeParameter('customerId', i) as string; + + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/customers/${customerId}`); + } + + if (operation === 'getAll') { + //https://magento.redoc.ly/2.3.7-admin/tag/customerssearch + const filterType = this.getNodeParameter('filterType', i) as string; + const sort = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string, field: string }] }; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + let qs: Search = {}; + + if (filterType === 'manual') { + const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; + const matchType = this.getNodeParameter('matchType', i) as string; + qs = getFilterQuery(Object.assign(filters, { matchType }, sort)); + } else if (filterType === 'json') { + const filterJson = this.getNodeParameter('filterJson', i) as string; + if (validateJSON(filterJson) !== undefined) { + qs = JSON.parse(filterJson); + } else { + throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json' }); + } + } else { + qs = { + search_criteria: {}, + }; + + if (Object.keys(sort).length !== 0) { + qs.search_criteria = { + sort_orders: sort.sort, + }; + } + } + + if (returnAll === true) { + qs.search_criteria!.page_size = 100; + responseData = await magentoApiRequestAllItems.call(this, 'items', 'GET', `/rest/default/V1/customers/search`, {}, qs as unknown as IDataObject); + + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.search_criteria!.page_size = limit; + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/customers/search`, {}, qs as unknown as IDataObject); + responseData = responseData.items; + } + } + + if (operation === 'update') { + //https://magento.redoc.ly/2.3.7-admin/tag/customerscustomerId#operation/customerCustomerRepositoryV1SavePut + const customerId = this.getNodeParameter('customerId', i) as string; + const firstName = this.getNodeParameter('firstName', i) as string; + const lastName = this.getNodeParameter('lastName', i) as string; + const email = this.getNodeParameter('email', i) as string; + + const { + addresses, + customAttributes, + password, + ...rest + } = this.getNodeParameter('updateFields', i) as { + addresses: { + address: [{ + street: string, + }] + }; + customAttributes: { + customAttribute: CustomAttribute[], + }, + password: string, + }; + + const body: NewCustomer = { + customer: { + email, + firstname: firstName, + lastname: lastName, + id: parseInt(customerId, 10), + website_id: 0, + }, + }; + + body.customer!.addresses = adjustAddresses(addresses?.address || []); + + body.customer!.custom_attributes = customAttributes?.customAttribute || {}; + + body.customer!.extension_attributes = ['amazon_id', 'is_subscribed', 'vertex_customer_code', 'vertex_customer_country'] + // tslint:disable-next-line: no-any + .reduce((obj, value: string): any => { + if ((rest as IDataObject).hasOwnProperty(value)) { + const data = Object.assign(obj, { [value]: (rest as IDataObject)[value] }); + delete (rest as IDataObject)[value]; + return data; + } else { + return obj; + } + }, {}); + + if (password) { + body.password = password; + } + + Object.assign(body.customer, rest); + + responseData = await magentoApiRequest.call(this, 'PUT', `/rest/V1/customers/${customerId}`, body); + } + } + + if (resource === 'invoice') { + if (operation === 'create') { + ///https://magento.redoc.ly/2.3.7-admin/tag/orderorderIdinvoice + const orderId = this.getNodeParameter('orderId', i) as string; + + responseData = await magentoApiRequest.call(this, 'POST', `/rest/default/V1/order/${orderId}/invoice`); + + responseData = { success: true }; + } + } + + if (resource === 'order') { + + if (operation === 'cancel') { + //https://magento.redoc.ly/2.3.7-admin/tag/ordersidcancel + const orderId = this.getNodeParameter('orderId', i) as string; + + responseData = await magentoApiRequest.call(this, 'POST', `/rest/default/V1/orders/${orderId}/cancel`); + + responseData = { success: true }; + } + + if (operation === 'get') { + //https://magento.redoc.ly/2.3.7-admin/tag/ordersid#operation/salesOrderRepositoryV1GetGet + const orderId = this.getNodeParameter('orderId', i) as string; + + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/orders/${orderId}`); + } + + if (operation === 'ship') { + ///https://magento.redoc.ly/2.3.7-admin/tag/orderorderIdship#operation/salesShipOrderV1ExecutePost + const orderId = this.getNodeParameter('orderId', i) as string; + + responseData = await magentoApiRequest.call(this, 'POST', `/rest/default/V1/order/${orderId}/ship`); + + responseData = { success: true }; + } + + if (operation === 'getAll') { + //https://magento.redoc.ly/2.3.7-admin/tag/orders#operation/salesOrderRepositoryV1GetListGet + const filterType = this.getNodeParameter('filterType', i) as string; + const sort = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string, field: string }] }; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + let qs: Search = {}; + + if (filterType === 'manual') { + const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; + const matchType = this.getNodeParameter('matchType', i) as string; + qs = getFilterQuery(Object.assign(filters, { matchType }, sort)); + } else if (filterType === 'json') { + const filterJson = this.getNodeParameter('filterJson', i) as string; + if (validateJSON(filterJson) !== undefined) { + qs = JSON.parse(filterJson); + } else { + throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json' }); + } + } else { + qs = { + search_criteria: {}, + }; + if (Object.keys(sort).length !== 0) { + qs.search_criteria = { + sort_orders: sort.sort, + }; + } + } + + if (returnAll === true) { + qs.search_criteria!.page_size = 100; + responseData = await magentoApiRequestAllItems.call(this, 'items', 'GET', `/rest/default/V1/orders`, {}, qs as unknown as IDataObject); + + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.search_criteria!.page_size = limit; + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/orders`, {}, qs as unknown as IDataObject); + responseData = responseData.items; + } + } + } + + if (resource === 'product') { + if (operation === 'create') { + // https://magento.redoc.ly/2.3.7-admin/tag/products#operation/catalogProductRepositoryV1SavePost + const sku = this.getNodeParameter('sku', i) as string; + const name = this.getNodeParameter('name', i) as string; + const attributeSetId = this.getNodeParameter('attributeSetId', i) as string; + const price = this.getNodeParameter('price', i) as number; + + const { + customAttributes, + category, + ...rest + } = this.getNodeParameter('additionalFields', i) as { + customAttributes: { + customAttribute: CustomAttribute[], + }, + category: string, + }; + + const body: NewProduct = { + product: { + sku, + name, + attribute_set_id: parseInt(attributeSetId, 10), + price, + }, + }; + + body.product!.custom_attributes = customAttributes?.customAttribute || {}; + + Object.assign(body.product, rest); + + responseData = await magentoApiRequest.call(this, 'POST', '/rest/default/V1/products', body); + } + + if (operation === 'delete') { + //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1DeleteByIdDelete + const sku = this.getNodeParameter('sku', i) as string; + + responseData = await magentoApiRequest.call(this, 'DELETE', `/rest/default/V1/products/${sku}`); + + responseData = { success: true }; + } + + if (operation === 'get') { + //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1GetGet + const sku = this.getNodeParameter('sku', i) as string; + + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/products/${sku}`); + } + + if (operation === 'getAll') { + //https://magento.redoc.ly/2.3.7-admin/tag/customerssearch + const filterType = this.getNodeParameter('filterType', i) as string; + const sort = this.getNodeParameter('options.sort', i, {}) as { sort: [{ direction: string, field: string }] }; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + let qs: Search = {}; + + if (filterType === 'manual') { + const filters = this.getNodeParameter('filters', i) as { conditions: Filter[] }; + const matchType = this.getNodeParameter('matchType', i) as string; + qs = getFilterQuery(Object.assign(filters, { matchType }, sort)); + } else if (filterType === 'json') { + const filterJson = this.getNodeParameter('filterJson', i) as string; + if (validateJSON(filterJson) !== undefined) { + qs = JSON.parse(filterJson); + } else { + throw new NodeApiError(this.getNode(), { message: 'Filter (JSON) must be a valid json' }); + } + } else { + qs = { + search_criteria: {}, + }; + if (Object.keys(sort).length !== 0) { + qs.search_criteria = { + sort_orders: sort.sort, + }; + } + } + + if (returnAll === true) { + qs.search_criteria!.page_size = 100; + responseData = await magentoApiRequestAllItems.call(this, 'items', 'GET', `/rest/default/V1/products`, {}, qs as unknown as IDataObject); + + } else { + const limit = this.getNodeParameter('limit', 0) as number; + qs.search_criteria!.page_size = limit; + responseData = await magentoApiRequest.call(this, 'GET', `/rest/default/V1/products`, {}, qs as unknown as IDataObject); + responseData = responseData.items; + } + } + + if (operation === 'update') { + //https://magento.redoc.ly/2.3.7-admin/tag/productssku#operation/catalogProductRepositoryV1SavePut + const sku = this.getNodeParameter('sku', i) as string; + + const { + customAttributes, + ...rest + } = this.getNodeParameter('updateFields', i) as { + customAttributes: { + customAttribute: CustomAttribute[], + }, + }; + + if (!Object.keys(rest).length) { + throw new NodeApiError(this.getNode(), { message: 'At least one parameter has to be updated' }); + } + + const body: NewProduct = { + product: { + sku, + }, + }; + + body.product!.custom_attributes = customAttributes?.customAttribute || {}; + + Object.assign(body.product, rest); + + responseData = await magentoApiRequest.call(this, 'PUT', `/rest/default/V1/products/${sku}`, body); + } + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Magento/OrderDescription.ts b/packages/nodes-base/nodes/Magento/OrderDescription.ts new file mode 100644 index 0000000000..3e79b5fa8f --- /dev/null +++ b/packages/nodes-base/nodes/Magento/OrderDescription.ts @@ -0,0 +1,123 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + getSearchFilters, +} from './GenericFunctions'; + +export const orderOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'order', + ], + }, + }, + options: [ + { + name: 'Cancel', + value: 'cancel', + description: 'Cancel an order', + }, + { + name: 'Get', + value: 'get', + description: 'Get an order', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all orders', + }, + { + name: 'Ship', + value: 'ship', + description: 'Ship an order', + }, + ], + default: 'cancel', + description: 'The operation to perform', + }, +] as INodeProperties[]; + +export const orderFields = [ + + /* -------------------------------------------------------------------------- */ + /* order:cancel */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Order ID', + name: 'orderId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'order', + ], + operation: [ + 'cancel', + 'get', + 'ship', + ], + }, + }, + }, + + /* -------------------------------------------------------------------------- */ + /* order:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'order', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'order', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return', + }, + ...getSearchFilters( + 'order', + 'getOrderAttributes', + 'getOrderAttributes', + ), + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Magento/ProductDescription.ts b/packages/nodes-base/nodes/Magento/ProductDescription.ts new file mode 100644 index 0000000000..ec8fe09ef8 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/ProductDescription.ts @@ -0,0 +1,244 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +import { + getProductOptionalFields, + getSearchFilters, +} from './GenericFunctions'; + +export const productOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'product', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a product', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a product', + }, + { + name: 'Get', + value: 'get', + description: 'Get a product', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all producs', + }, + { + name: 'Update', + value: 'update', + description: 'Update a product', + }, + ], + default: 'create', + description: 'The operation to perform', + }, +] as INodeProperties[]; + +export const productFields = [ + + /* -------------------------------------------------------------------------- */ + /* product:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'SKU', + name: 'sku', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'create', + 'update', + ], + }, + }, + description: 'Stock-keeping unit of the product', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + }, + { + displayName: 'Attribute Set Name/ID', + name: 'attributeSetId', + type: 'options', + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'create', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getAttributeSets', + }, + default: '', + }, + { + displayName: 'Price', + name: 'price', + type: 'number', + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'create', + ], + }, + }, + default: 0, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + ...getProductOptionalFields(), + ], + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + ...getProductOptionalFields(), + ], + }, + + /* -------------------------------------------------------------------------- */ + /* product:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'SKU', + name: 'sku', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'delete', + 'get', + ], + }, + }, + description: 'Stock-keeping unit of the product', + }, + + /* -------------------------------------------------------------------------- */ + /* product:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'Whether all results should be returned or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'product', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 10, + }, + default: 5, + description: 'How many results to return', + }, + ...getSearchFilters( + 'product', + //'getProductAttributesFields', + 'getFilterableProductAttributes', + 'getSortableProductAttributes'), + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Magento/Types.d.ts b/packages/nodes-base/nodes/Magento/Types.d.ts new file mode 100644 index 0000000000..f5367245e6 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/Types.d.ts @@ -0,0 +1,192 @@ + +export interface NewCustomer { + customer?: Customer; + password?: string; + redirectUrl?: string; +} + +export interface Customer { + id?: number; + group_id?: number; + default_billing?: string; + default_shipping?: string; + confirmation?: string; + created_at?: string; + updated_at?: string; + created_in?: string; + dob?: string; + email?: string; + firstname?: string; + lastname?: string; + middlename?: string; + prefix?: string; + suffix?: string; + gender?: number; + store_id?: number; + taxvat?: string; + website_id?: number; + addresses?: Address[]; + disable_auto_group_change?: number; + extension_attributes?: CustomerExtensionAttributes; + custom_attributes?: CustomAttribute[]; +} + +export interface Address { + id?: number; + customer_id?: number; + region?: Region; + region_id?: number; + country_id?: string; + street?: string[]; + company?: string; + telephone?: string; + fax?: string; + postcode?: string; + city?: string; + firstname?: string; + lastname?: string; + middlename?: string; + prefix?: string; + suffix?: string; + vat_id?: string; + default_shipping?: boolean; + default_billing?: boolean; + extension_attributes?: AddressExtensionAttributes; + custom_attributes?: CustomAttribute[]; +} + +export interface CustomAttribute { + attribute_code?: string; + value?: string; +} + +export interface AddressExtensionAttributes { + amazon_id?: string; + is_subscribed?: boolean; + vertex_customer_role?: string; + vertex_customer_country?: string; +} + +export interface Region { + region_code?: string; + region?: string; + region_id?: number; + extension_attributes?: AddressExtensionAttributes; +} + +export interface CustomerExtensionAttributes { + company_attributes?: CompanyAttributes; + is_subscribed?: boolean; + amazon_id?: string; + vertex_customer_code?: string; + vertex_customer_country?: string; +} + +export interface CompanyAttributes { + customer_id?: number; + company_id?: number; + job_title?: string; + status?: number; + telephone?: string; + extension_attributes?: AddressExtensionAttributes; +} + +export interface CustomerAttributeMetadata { + frontend_input?: string; + input_filter?: string; + store_label?: string; + validation_rules?: ValidationRule[]; + multiline_count?: number; + visible?: boolean; + required?: boolean; + data_model?: string; + options?: CustomerAttributeMetadataOption[]; + frontend_class?: string; + user_defined?: boolean; + sort_order?: number; + frontend_label?: string; + note?: string; + system?: boolean; + backend_type?: string; + is_used_in_grid?: boolean; + is_visible_in_grid?: boolean; + is_filterable_in_grid?: boolean; + is_searchable_in_grid?: boolean; + attribute_code?: string; +} + +export interface CustomerAttributeMetadataOption { + label?: string; + value?: string; + options?: OptionOption[]; +} + +export interface OptionOption { +} + +export interface ValidationRule { + name?: string; + value?: string; +} + +export interface Search { + search_criteria?: SearchCriteria; + total_count?: number; +} + +export interface SearchCriteria { + filter_groups?: FilterGroup[]; + sort_orders?: SortOrder[]; + page_size?: number; + current_page?: number; +} + +export interface FilterGroup { + filters?: Filter[]; +} + +export interface Filter { + field?: string; + value?: string; + condition_type?: string; +} + +export interface SortOrder { + field?: string; + direction?: string; +} + +export interface NewProduct { + product?: Product; + saveOptions?: boolean; +} + +export interface Product { + id?: number; + sku?: string; + name?: string; + attribute_set_id?: number; + price?: number; + status?: number; + visibility?: number; + type_id?: string; + created_at?: string; + updated_at?: string; + weight?: number; + extension_attributes?: { + category_links?: [{ + category_id?: string, + }] + } + custom_attributes?: CustomAttribute[]; +} + +export interface ProductAttribute { + is_filterable_in_search: boolean, + default_frontend_label: string; + attribute_id: string; + is_filterable: boolean; + used_for_sort_by: boolean; + is_searchable: string, + attribute_code: string, +} diff --git a/packages/nodes-base/nodes/Magento/magento.svg b/packages/nodes-base/nodes/Magento/magento.svg new file mode 100644 index 0000000000..7f18f4eeb8 --- /dev/null +++ b/packages/nodes-base/nodes/Magento/magento.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 7b98b32bcb..c517fb7efe 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -153,6 +153,7 @@ "dist/credentials/LineNotifyOAuth2Api.credentials.js", "dist/credentials/LingvaNexApi.credentials.js", "dist/credentials/LinkedInOAuth2Api.credentials.js", + "dist/credentials/Magento2Api.credentials.js", "dist/credentials/MailerLiteApi.credentials.js", "dist/credentials/MailcheckApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", @@ -456,6 +457,7 @@ "dist/nodes/Line/Line.node.js", "dist/nodes/LingvaNex/LingvaNex.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", + "dist/nodes/Magento/Magento2.node.js", "dist/nodes/MailerLite/MailerLite.node.js", "dist/nodes/MailerLite/MailerLiteTrigger.node.js", "dist/nodes/Mailcheck/Mailcheck.node.js",