first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.bambooHr",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Miscellaneous"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/bamboohr/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.bamboohr/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { router } from './v1/actions/router';
|
||||
import { versionDescription } from './v1/actions/versionDescription';
|
||||
import { credentialTest, loadOptions } from './v1/methods';
|
||||
|
||||
export class BambooHr implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
...versionDescription,
|
||||
usableAsTool: true,
|
||||
};
|
||||
}
|
||||
|
||||
methods = {
|
||||
loadOptions,
|
||||
credentialTest,
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
return [await router.call(this)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"employees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fullName1": {
|
||||
"type": "string"
|
||||
},
|
||||
"fullName2": {
|
||||
"type": "string"
|
||||
},
|
||||
"hireDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"payRate": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 4
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"canUploadPhoto": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"hireDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"photoUploaded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"photoUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 5
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"canUploadPhoto": {
|
||||
"type": "integer"
|
||||
},
|
||||
"department": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"jobTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
"photoUploaded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"photoUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"pronouns": {
|
||||
"type": "null"
|
||||
},
|
||||
"supervisor": {
|
||||
"type": "string"
|
||||
},
|
||||
"workEmail": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 5
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,29 @@
|
||||
import type { AllEntities, Entity, PropertiesOf } from 'n8n-workflow';
|
||||
|
||||
type BambooHrMap = {
|
||||
employee: 'create' | 'get' | 'getAll' | 'update';
|
||||
employeeDocument: 'delete' | 'download' | 'get' | 'getAll' | 'update' | 'upload';
|
||||
file: 'delete' | 'download' | 'getAll' | 'update';
|
||||
companyReport: 'get';
|
||||
};
|
||||
|
||||
export type BambooHr = AllEntities<BambooHrMap>;
|
||||
|
||||
export type BambooHrFile = Entity<BambooHrMap, 'file'>;
|
||||
export type BambooHrEmployee = Entity<BambooHrMap, 'employee'>;
|
||||
export type BambooHrEmployeeDocument = Entity<BambooHrMap, 'employeeDocument'>;
|
||||
export type BambooHrCompanyReport = Entity<BambooHrMap, 'companyReport'>;
|
||||
|
||||
export type FileProperties = PropertiesOf<BambooHrFile>;
|
||||
export type EmployeeProperties = PropertiesOf<BambooHrEmployee>;
|
||||
export type EmployeeDocumentProperties = PropertiesOf<BambooHrEmployeeDocument>;
|
||||
export type CompanyReportProperties = PropertiesOf<BambooHrCompanyReport>;
|
||||
|
||||
export interface IAttachment {
|
||||
fields: {
|
||||
item?: object[];
|
||||
};
|
||||
actions: {
|
||||
item?: object[];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const companyReportGetDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Report ID',
|
||||
name: 'reportId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['companyReport'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'ID of the report. You can get the report number by hovering over the report name on the reports page and grabbing the ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Format',
|
||||
name: 'format',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'CSV',
|
||||
value: 'CSV',
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
value: 'JSON',
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
value: 'PDF',
|
||||
},
|
||||
{
|
||||
name: 'XLS',
|
||||
value: 'XLS',
|
||||
},
|
||||
{
|
||||
name: 'XML',
|
||||
value: 'XML',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['companyReport'],
|
||||
},
|
||||
},
|
||||
default: 'JSON',
|
||||
description: 'The output format for the report',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output In Field',
|
||||
name: 'output',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
description: 'The name of the output field to put the binary file data in',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['companyReport'],
|
||||
},
|
||||
hide: {
|
||||
format: ['JSON'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['companyReport'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Duplicate Field Filtering',
|
||||
name: 'fd',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to apply the standard duplicate field filtering or not',
|
||||
},
|
||||
{
|
||||
displayName: 'Only Current',
|
||||
name: 'onlyCurrent',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to hide future dated values from the history table fields or not',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function get(this: IExecuteFunctions, index: number) {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const items = this.getInputData();
|
||||
|
||||
//meta data
|
||||
const reportId = this.getNodeParameter('reportId', index) as string;
|
||||
const format = this.getNodeParameter('format', 0) as string;
|
||||
const fd = this.getNodeParameter('options.fd', index, true) as boolean;
|
||||
const onlyCurrent = this.getNodeParameter('options.onlyCurrent', index, true) as boolean;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `reports/${reportId}/?format=${format}&fd=${fd}&onlyCurrent=${onlyCurrent}`;
|
||||
|
||||
if (format === 'JSON') {
|
||||
const responseData = await apiRequest.call(
|
||||
this,
|
||||
requestMethod,
|
||||
endpoint,
|
||||
body,
|
||||
{},
|
||||
{ resolveWithFullResponse: true },
|
||||
);
|
||||
return this.helpers.returnJsonArray(responseData.body as IDataObject);
|
||||
}
|
||||
|
||||
const output: string = this.getNodeParameter('output', index) as string;
|
||||
|
||||
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject, {
|
||||
encoding: null,
|
||||
json: false,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
let mimeType = response.headers['content-type'] as string | undefined;
|
||||
mimeType = mimeType ? mimeType.split(';').find((value) => value.includes('/')) : undefined;
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
const fileNameRegex = /(?<=filename=").*\b/;
|
||||
const match = fileNameRegex.exec(contentDisposition as string);
|
||||
let fileName = '';
|
||||
|
||||
// file name was found
|
||||
if (match !== null) {
|
||||
fileName = match[0];
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[index].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (items[index].binary !== undefined && newItem.binary) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary, items[index].binary);
|
||||
}
|
||||
|
||||
newItem.binary = {
|
||||
[output]: await this.helpers.prepareBinaryData(
|
||||
response.body as unknown as Buffer,
|
||||
fileName,
|
||||
mimeType,
|
||||
),
|
||||
};
|
||||
|
||||
return [newItem as unknown as INodeExecutionData[]];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { companyReportGetDescription as description } from './description';
|
||||
import { get as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as get from './get';
|
||||
|
||||
export { get };
|
||||
|
||||
export const descriptions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['companyReport'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a company report',
|
||||
action: 'Get a company report',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
...get.description,
|
||||
];
|
||||
@@ -0,0 +1,75 @@
|
||||
import { createEmployeeSharedDescription } from './shareDescription';
|
||||
import type { EmployeeProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeCreateDescription: EmployeeProperties = [
|
||||
{
|
||||
displayName: 'Synced with Trax Payroll',
|
||||
name: 'synced',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description:
|
||||
'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
...(createEmployeeSharedDescription(true) as EmployeeProperties),
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
...createEmployeeSharedDescription(false),
|
||||
{
|
||||
displayName: 'Work Email',
|
||||
name: 'workEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Work Phone',
|
||||
name: 'workPhone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,104 @@
|
||||
import { capitalCase } from 'change-case';
|
||||
import moment from 'moment-timezone';
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function create(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
const endpoint = 'employees';
|
||||
|
||||
//body parameters
|
||||
body.firstName = this.getNodeParameter('firstName', index) as string;
|
||||
body.lastName = this.getNodeParameter('lastName', index) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||
const synced = this.getNodeParameter('synced', index) as boolean;
|
||||
|
||||
if (synced) {
|
||||
Object.assign(body, {
|
||||
address: this.getNodeParameter('address.value', index, {}) as IDataObject,
|
||||
});
|
||||
Object.assign(body, {
|
||||
payRate: this.getNodeParameter('payRate.value', index, {}) as IDataObject,
|
||||
});
|
||||
body.department = this.getNodeParameter('department', index) as string;
|
||||
body.dateOfBirth = this.getNodeParameter('dateOfBirth', index);
|
||||
body.division = this.getNodeParameter('division', index) as string;
|
||||
body.employeeNumber = this.getNodeParameter('employeeNumber', index) as string;
|
||||
body.exempt = this.getNodeParameter('exempt', index) as string;
|
||||
body.gender = this.getNodeParameter('gender', index) as string;
|
||||
body.hireDate = this.getNodeParameter('hireDate', index) as string;
|
||||
body.location = this.getNodeParameter('location', index) as string;
|
||||
body.maritalStatus = this.getNodeParameter('maritalStatus', index) as string;
|
||||
body.mobilePhone = this.getNodeParameter('mobilePhone', index) as string;
|
||||
body.paidPer = this.getNodeParameter('paidPer', index) as string;
|
||||
body.payType = this.getNodeParameter('payType', index) as string;
|
||||
body.preferredName = this.getNodeParameter('preferredName', index) as string;
|
||||
body.ssn = this.getNodeParameter('ssn', index) as string;
|
||||
} else {
|
||||
Object.assign(body, {
|
||||
address: this.getNodeParameter('additionalFields.address.value', index, {}) as IDataObject,
|
||||
});
|
||||
Object.assign(body, {
|
||||
payRate: this.getNodeParameter('additionalFields.payRate.value', index, {}) as IDataObject,
|
||||
});
|
||||
delete additionalFields.address;
|
||||
delete additionalFields.payRate;
|
||||
}
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
if (body.gender) {
|
||||
body.gender = capitalCase(body.gender as string);
|
||||
}
|
||||
|
||||
if (body.dateOfBirth) {
|
||||
body.dateOfBirth = moment(body.dateOfBirth as string).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (body.exempt) {
|
||||
body.exempt = capitalCase(body.exempt as string);
|
||||
}
|
||||
|
||||
if (body.hireDate) {
|
||||
body.hireDate = moment(body.hireDate as string).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (body.maritalStatus) {
|
||||
body.maritalStatus = capitalCase(body.maritalStatus as string);
|
||||
}
|
||||
|
||||
if (body.payType) {
|
||||
body.payType = capitalCase(body.payType as string);
|
||||
}
|
||||
|
||||
if (body.paidPer) {
|
||||
body.paidPer = capitalCase(body.paidPer as string);
|
||||
}
|
||||
|
||||
if (!Object.keys(body.payRate as IDataObject).length) {
|
||||
delete body.payRate;
|
||||
}
|
||||
|
||||
//response
|
||||
const responseData = await apiRequest.call(
|
||||
this,
|
||||
requestMethod,
|
||||
endpoint,
|
||||
body,
|
||||
{},
|
||||
{ resolveWithFullResponse: true },
|
||||
);
|
||||
|
||||
//obtain employeeID
|
||||
const rawEmployeeId: number = responseData.headers.location.lastIndexOf('/');
|
||||
const employeeId = responseData.headers.location.substring(rawEmployeeId + 1);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ id: employeeId });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeCreateDescription as description } from './description';
|
||||
import { create as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,322 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const createEmployeeSharedDescription = (sync = false): INodeProperties[] => {
|
||||
let elements: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
placeholder: 'Address',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'value',
|
||||
displayName: 'Address',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Line 1',
|
||||
name: 'address1',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Line 2',
|
||||
name: 'address2',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Florida',
|
||||
description: 'The full name of the state/province',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'United States',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Date of Birth',
|
||||
name: 'dateOfBirth',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Department Name or ID',
|
||||
name: 'department',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDepartments',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Division Name or ID',
|
||||
name: 'division',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDivisions',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Employee Number',
|
||||
name: 'employeeNumber',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'FLSA Overtime Status',
|
||||
name: 'exempt',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Exempt',
|
||||
value: 'exempt',
|
||||
},
|
||||
{
|
||||
name: 'Non-Exempt',
|
||||
value: 'non-exempt',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Gender',
|
||||
name: 'gender',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Female',
|
||||
value: 'female',
|
||||
},
|
||||
{
|
||||
name: 'Male',
|
||||
value: 'male',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Hire Date',
|
||||
name: 'hireDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Location Name or ID',
|
||||
name: 'location',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getEmployeeLocations',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Marital Status',
|
||||
name: 'maritalStatus',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Single',
|
||||
value: 'single',
|
||||
},
|
||||
{
|
||||
name: 'Married',
|
||||
value: 'married',
|
||||
},
|
||||
{
|
||||
name: 'Domestic Partnership',
|
||||
value: 'domesticPartnership',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mobile Phone',
|
||||
name: 'mobilePhone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Per',
|
||||
name: 'paidPer',
|
||||
type: 'options',
|
||||
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Hour',
|
||||
value: 'hour',
|
||||
},
|
||||
{
|
||||
name: 'Day',
|
||||
value: 'day',
|
||||
},
|
||||
{
|
||||
name: 'Week',
|
||||
value: 'week',
|
||||
},
|
||||
{
|
||||
name: 'Month',
|
||||
value: 'month',
|
||||
},
|
||||
{
|
||||
name: 'Quater',
|
||||
value: 'quater',
|
||||
},
|
||||
{
|
||||
name: 'Year',
|
||||
value: 'year',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Rate',
|
||||
name: 'payRate',
|
||||
placeholder: 'Add Pay Rate',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'value',
|
||||
displayName: 'Pay Rate',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '20.00',
|
||||
},
|
||||
{
|
||||
displayName: 'Currency',
|
||||
name: 'currency',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'USD',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Type',
|
||||
name: 'payType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Commission',
|
||||
value: 'commission',
|
||||
},
|
||||
{
|
||||
name: 'Contract',
|
||||
value: 'contract',
|
||||
},
|
||||
{
|
||||
name: 'Daily',
|
||||
value: 'daily',
|
||||
},
|
||||
{
|
||||
name: 'Exception Hourly',
|
||||
value: 'exceptionHourly',
|
||||
},
|
||||
{
|
||||
name: 'Hourly',
|
||||
value: 'hourly',
|
||||
},
|
||||
{
|
||||
name: 'Monthly',
|
||||
value: 'monthly',
|
||||
},
|
||||
{
|
||||
name: 'Piece Rate',
|
||||
value: 'pieceRate',
|
||||
},
|
||||
{
|
||||
name: 'Pro Rata',
|
||||
value: 'proRata',
|
||||
},
|
||||
{
|
||||
name: 'Salary',
|
||||
value: 'salary',
|
||||
},
|
||||
{
|
||||
name: 'Weekly',
|
||||
value: 'weekly',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Preferred Name',
|
||||
name: 'preferredName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Social Security Number',
|
||||
name: 'ssn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '123-45-6789',
|
||||
description: 'A standard United States Social Security number, with dashes',
|
||||
},
|
||||
];
|
||||
|
||||
if (sync) {
|
||||
elements = elements.map((element) => {
|
||||
return Object.assign(element, {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employee'],
|
||||
operation: ['create'],
|
||||
synced: [true],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
return elements;
|
||||
} else {
|
||||
elements = elements.map((element) => {
|
||||
return Object.assign(element, {
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/synced': [false],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { EmployeeProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeGetDescription: EmployeeProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field Names or IDs',
|
||||
name: 'fields',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getEmployeeFields',
|
||||
},
|
||||
default: ['all'],
|
||||
description:
|
||||
'Set of fields to get from employee data. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function get(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
|
||||
//meta data
|
||||
const id = this.getNodeParameter('employeeId', index) as string;
|
||||
|
||||
//query parameters
|
||||
let fields = this.getNodeParameter('options.fields', index, ['all']) as string[];
|
||||
|
||||
if (fields.includes('all')) {
|
||||
const { fields: allFields } = await apiRequest.call(
|
||||
this,
|
||||
requestMethod,
|
||||
'employees/directory',
|
||||
body,
|
||||
);
|
||||
fields = allFields.map((field: IDataObject) => field.id);
|
||||
}
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}?fields=${fields}`;
|
||||
|
||||
//response
|
||||
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeGetDescription as description } from './description';
|
||||
import { get as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const employeeGetAllDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employee'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employee'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function getAll(
|
||||
this: IExecuteFunctions,
|
||||
_index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endpoint = 'employees/directory';
|
||||
|
||||
//limit parameters
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false);
|
||||
const limit = this.getNodeParameter('limit', 0, 0);
|
||||
|
||||
//response
|
||||
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return limited result
|
||||
if (!returnAll && responseData.employees.length > limit) {
|
||||
return this.helpers.returnJsonArray(responseData.employees.slice(0, limit) as IDataObject[]);
|
||||
}
|
||||
|
||||
//return all result
|
||||
return this.helpers.returnJsonArray(responseData.employees as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeGetAllDescription as description } from './description';
|
||||
import { getAll as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create';
|
||||
import * as get from './get';
|
||||
import * as getAll from './getAll';
|
||||
import * as update from './update';
|
||||
|
||||
export { create, get, getAll, update };
|
||||
|
||||
export const descriptions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an employee',
|
||||
action: 'Create an employee',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get an employee',
|
||||
action: 'Get an employee',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many employees',
|
||||
action: 'Get many employees',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update an employee',
|
||||
action: 'Update an employee',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
...create.description,
|
||||
...get.description,
|
||||
...getAll.description,
|
||||
...update.description,
|
||||
];
|
||||
@@ -0,0 +1,62 @@
|
||||
import { updateEmployeeSharedDescription } from './sharedDescription';
|
||||
import type { EmployeeProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeUpdateDescription: EmployeeProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Synced with Trax Payroll',
|
||||
name: 'synced',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description:
|
||||
'Whether the employee to create was added to a pay schedule synced with Trax Payroll',
|
||||
},
|
||||
...(updateEmployeeSharedDescription(true) as EmployeeProperties),
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employee'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
...updateEmployeeSharedDescription(false),
|
||||
{
|
||||
displayName: 'Work Email',
|
||||
name: 'workEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Work Phone',
|
||||
name: 'workPhone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,103 @@
|
||||
import { capitalCase } from 'change-case';
|
||||
import moment from 'moment-timezone';
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function update(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
|
||||
//meta data
|
||||
const id = this.getNodeParameter('employeeId', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}`;
|
||||
|
||||
//body parameters
|
||||
body = this.getNodeParameter('updateFields', index);
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', index);
|
||||
const synced = this.getNodeParameter('synced', index) as boolean;
|
||||
|
||||
if (synced) {
|
||||
Object.assign(body, {
|
||||
address: this.getNodeParameter('address.value', index, {}) as IDataObject,
|
||||
});
|
||||
Object.assign(body, {
|
||||
payRate: this.getNodeParameter('payRate.value', index, {}) as IDataObject,
|
||||
});
|
||||
body.firstName = this.getNodeParameter('firstName', index) as string;
|
||||
body.lastName = this.getNodeParameter('lastName', index) as string;
|
||||
body.department = this.getNodeParameter('department', index) as string;
|
||||
body.dateOfBirth = this.getNodeParameter('dateOfBirth', index) as string;
|
||||
body.division = this.getNodeParameter('division', index) as string;
|
||||
body.employeeNumber = this.getNodeParameter('employeeNumber', index) as string;
|
||||
body.exempt = this.getNodeParameter('exempt', index) as string;
|
||||
body.gender = this.getNodeParameter('gender', index) as string;
|
||||
body.hireDate = this.getNodeParameter('hireDate', index) as string;
|
||||
body.location = this.getNodeParameter('location', index) as string;
|
||||
body.maritalStatus = this.getNodeParameter('maritalStatus', index) as string;
|
||||
body.mobilePhone = this.getNodeParameter('mobilePhone', index) as string;
|
||||
body.paidPer = this.getNodeParameter('paidPer', index) as string;
|
||||
body.payType = this.getNodeParameter('payType', index) as string;
|
||||
body.preferredName = this.getNodeParameter('preferredName', index) as string;
|
||||
body.ssn = this.getNodeParameter('ssn', index) as string;
|
||||
} else {
|
||||
if (!Object.keys(updateFields).length) {
|
||||
throw new NodeOperationError(this.getNode(), 'At least one fields must be updated');
|
||||
}
|
||||
|
||||
Object.assign(body, {
|
||||
address: this.getNodeParameter('updateFields.address.value', index, {}) as IDataObject,
|
||||
});
|
||||
Object.assign(body, {
|
||||
payRate: this.getNodeParameter('updateFields.payRate.value', index, {}) as IDataObject,
|
||||
});
|
||||
delete updateFields.address;
|
||||
delete updateFields.payRate;
|
||||
}
|
||||
|
||||
Object.assign(body, updateFields);
|
||||
|
||||
if (body.gender) {
|
||||
body.gender = capitalCase(body.gender as string);
|
||||
}
|
||||
|
||||
if (body.dateOfBirth) {
|
||||
body.dateOfBirth = moment(body.dateOfBirth as string).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (body.exempt) {
|
||||
body.exempt = capitalCase(body.exempt as string);
|
||||
}
|
||||
|
||||
if (body.hireDate) {
|
||||
body.hireDate = moment(body.hireDate as string).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (body.maritalStatus) {
|
||||
body.maritalStatus = capitalCase(body.maritalStatus as string);
|
||||
}
|
||||
|
||||
if (body.payType) {
|
||||
body.payType = capitalCase(body.payType as string);
|
||||
}
|
||||
|
||||
if (body.paidPer) {
|
||||
body.paidPer = capitalCase(body.paidPer as string);
|
||||
}
|
||||
|
||||
if (!Object.keys(body.payRate as IDataObject).length) {
|
||||
delete body.payRate;
|
||||
}
|
||||
|
||||
await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ success: true });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeUpdateDescription as description } from './description';
|
||||
import { update as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,344 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const updateEmployeeSharedDescription = (sync = false): INodeProperties[] => {
|
||||
let elements: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'addasasress',
|
||||
placeholder: 'Address',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'value',
|
||||
displayName: 'Address',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Line 1',
|
||||
name: 'address1',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Line 2',
|
||||
name: 'address2',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Florida',
|
||||
description: 'The full name of the state/province',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'United States',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Date of Birth',
|
||||
name: 'dateOfBirth',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Department Name or ID',
|
||||
name: 'department',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDepartments',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Division Name or ID',
|
||||
name: 'division',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDivisions',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Employee Number',
|
||||
name: 'employeeNumber',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
synced: [false],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
synced: [false],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'FLSA Overtime Status',
|
||||
name: 'exempt',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Exempt',
|
||||
value: 'exempt',
|
||||
},
|
||||
{
|
||||
name: 'Non-Exempt',
|
||||
value: 'non-exempt',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Gender',
|
||||
name: 'gender',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Female',
|
||||
value: 'female',
|
||||
},
|
||||
{
|
||||
name: 'Male',
|
||||
value: 'male',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Hire Date',
|
||||
name: 'hireDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Location Name or ID',
|
||||
name: 'location',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getEmployeeLocations',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Marital Status',
|
||||
name: 'maritalStatus',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Single',
|
||||
value: 'single',
|
||||
},
|
||||
{
|
||||
name: 'Married',
|
||||
value: 'married',
|
||||
},
|
||||
{
|
||||
name: 'Domestic Partnership',
|
||||
value: 'domesticPartnership',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mobile Phone',
|
||||
name: 'mobilePhone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Per',
|
||||
name: 'paidPer',
|
||||
type: 'options',
|
||||
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Hour',
|
||||
value: 'hour',
|
||||
},
|
||||
{
|
||||
name: 'Day',
|
||||
value: 'day',
|
||||
},
|
||||
{
|
||||
name: 'Week',
|
||||
value: 'week',
|
||||
},
|
||||
{
|
||||
name: 'Month',
|
||||
value: 'month',
|
||||
},
|
||||
{
|
||||
name: 'Quater',
|
||||
value: 'quater',
|
||||
},
|
||||
{
|
||||
name: 'Year',
|
||||
value: 'year',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Rate',
|
||||
name: 'payRate',
|
||||
placeholder: 'Add Pay Rate',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'value',
|
||||
displayName: 'Pay Rate',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '20.00',
|
||||
},
|
||||
{
|
||||
displayName: 'Currency',
|
||||
name: 'currency',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'USD',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Pay Type',
|
||||
name: 'payType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Commission',
|
||||
value: 'commission',
|
||||
},
|
||||
{
|
||||
name: 'Contract',
|
||||
value: 'contract',
|
||||
},
|
||||
{
|
||||
name: 'Daily',
|
||||
value: 'daily',
|
||||
},
|
||||
{
|
||||
name: 'Exception Hourly',
|
||||
value: 'exceptionHourly',
|
||||
},
|
||||
{
|
||||
name: 'Hourly',
|
||||
value: 'hourly',
|
||||
},
|
||||
{
|
||||
name: 'Monthly',
|
||||
value: 'monthly',
|
||||
},
|
||||
{
|
||||
name: 'Piece Rate',
|
||||
value: 'pieceRate',
|
||||
},
|
||||
{
|
||||
name: 'Pro Rata',
|
||||
value: 'proRata',
|
||||
},
|
||||
{
|
||||
name: 'Salary',
|
||||
value: 'salary',
|
||||
},
|
||||
{
|
||||
name: 'Weekly',
|
||||
value: 'weekly',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Preferred Name',
|
||||
name: 'preferredName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Social Security Number',
|
||||
name: 'ssn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '123-45-6789',
|
||||
description: 'A standard United States Social Security number, with dashes',
|
||||
},
|
||||
];
|
||||
|
||||
if (sync) {
|
||||
elements = elements.map((element) => {
|
||||
return Object.assign(element, {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employee'],
|
||||
operation: ['update'],
|
||||
synced: [true],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
});
|
||||
});
|
||||
return elements;
|
||||
} else {
|
||||
elements = elements.map((element) => {
|
||||
return Object.assign(element, {
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/synced': [false],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return elements;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { EmployeeDocumentProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeDocumentDelDescription: EmployeeDocumentProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the employee',
|
||||
},
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the employee file',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'DELETE';
|
||||
|
||||
//meta data
|
||||
const id: string = this.getNodeParameter('employeeId', index) as string;
|
||||
const fileId: string = this.getNodeParameter('fileId', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}/files/${fileId}`;
|
||||
|
||||
//response
|
||||
await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ success: true });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeDocumentDelDescription as description } from './description';
|
||||
import { del as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import type { EmployeeDocumentProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeDocumentDownloadDescription: EmployeeDocumentProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the employee',
|
||||
},
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the employee file',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output In Field',
|
||||
name: 'output',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
description: 'The name of the output field to put the binary file data in',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function download(this: IExecuteFunctions, index: number) {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const items = this.getInputData();
|
||||
|
||||
//meta data
|
||||
const id: string = this.getNodeParameter('employeeId', index) as string;
|
||||
const fileId: string = this.getNodeParameter('fileId', index) as string;
|
||||
const output: string = this.getNodeParameter('output', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}/files/${fileId}/`;
|
||||
|
||||
//response
|
||||
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject, {
|
||||
encoding: null,
|
||||
json: false,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
let mimeType = response.headers['content-type'] as string | undefined;
|
||||
mimeType = mimeType ? mimeType.split(';').find((value) => value.includes('/')) : undefined;
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
const fileNameRegex = /(?<=filename=").*\b/;
|
||||
const match = fileNameRegex.exec(contentDisposition as string);
|
||||
let fileName = '';
|
||||
|
||||
// file name was found
|
||||
if (match !== null) {
|
||||
fileName = match[0];
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[index].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (items[index].binary !== undefined && newItem.binary) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary, items[index].binary);
|
||||
}
|
||||
|
||||
newItem.binary = {
|
||||
[output]: await this.helpers.prepareBinaryData(
|
||||
response.body as unknown as Buffer,
|
||||
fileName,
|
||||
mimeType,
|
||||
),
|
||||
};
|
||||
|
||||
return [newItem as unknown as INodeExecutionData[]];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeDocumentDownloadDescription as description } from './description';
|
||||
import { download as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { EmployeeDocumentProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeDocumentGetAllDescription: EmployeeDocumentProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['employeeDocument'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simplifyOutput',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function getAll(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
|
||||
//meta data
|
||||
const id = this.getNodeParameter('employeeId', index) as string;
|
||||
|
||||
//limit parameters
|
||||
const simplifyOutput: boolean = this.getNodeParameter('simplifyOutput', index) as boolean;
|
||||
const returnAll: boolean = this.getNodeParameter('returnAll', 0, false);
|
||||
const limit: number = this.getNodeParameter('limit', 0, 0);
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}/files/view/`;
|
||||
|
||||
//response
|
||||
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
const onlyFilesArray = [];
|
||||
|
||||
//return only files without categories
|
||||
if (simplifyOutput) {
|
||||
for (let i = 0; i < responseData.categories.length; i++) {
|
||||
if (responseData.categories[i].hasOwnProperty('files')) {
|
||||
for (let j = 0; j < responseData.categories[i].files.length; j++) {
|
||||
onlyFilesArray.push(responseData.categories[i].files[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!returnAll && onlyFilesArray.length > limit) {
|
||||
return this.helpers.returnJsonArray(onlyFilesArray.slice(0, limit));
|
||||
} else {
|
||||
return this.helpers.returnJsonArray(onlyFilesArray);
|
||||
}
|
||||
}
|
||||
|
||||
//return limited result
|
||||
if (!returnAll && responseData.categories.length > limit) {
|
||||
return this.helpers.returnJsonArray(responseData.categories.slice(0, limit) as IDataObject[]);
|
||||
}
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray(responseData.categories as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeDocumentGetAllDescription as description } from './description';
|
||||
import { getAll as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as del from './del';
|
||||
import * as download from './download';
|
||||
import * as getAll from './getAll';
|
||||
import * as update from './update';
|
||||
import * as upload from './upload';
|
||||
|
||||
export { del, download, getAll, update, upload };
|
||||
|
||||
export const descriptions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an employee document',
|
||||
action: 'Delete an employee document',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download an employee document',
|
||||
action: 'Download an employee document',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many employee documents',
|
||||
action: 'Get many employee documents',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update an employee document',
|
||||
action: 'Update an employee document',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload an employee document',
|
||||
action: 'Upload an employee document',
|
||||
},
|
||||
],
|
||||
default: 'delete',
|
||||
},
|
||||
...del.description,
|
||||
...download.description,
|
||||
...getAll.description,
|
||||
...update.description,
|
||||
...upload.description,
|
||||
];
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { EmployeeDocumentProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeDocumentUpdateDescription: EmployeeDocumentProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Employee Document Category Name or ID',
|
||||
name: 'categoryId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getEmployeeDocumentCategories',
|
||||
loadOptionsDependsOn: ['employeeId'],
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'ID of the new category of the file. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'New name of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Share with Employee',
|
||||
name: 'shareWithEmployee',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether this file is shared or not',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function update(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
|
||||
//meta data
|
||||
const id = this.getNodeParameter('employeeId', index) as string;
|
||||
const fileId = this.getNodeParameter('fileId', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}/files/${fileId}`;
|
||||
|
||||
//body parameters
|
||||
body = this.getNodeParameter('updateFields', index);
|
||||
body.shareWithEmployee ? (body.shareWithEmployee = 'yes') : (body.shareWithEmployee = 'no');
|
||||
|
||||
//response
|
||||
await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ success: true });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeDocumentUpdateDescription as description } from './description';
|
||||
import { update as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { EmployeeDocumentProperties } from '../../Interfaces';
|
||||
|
||||
export const employeeDocumentUploadDescription: EmployeeDocumentProperties = [
|
||||
{
|
||||
displayName: 'Employee ID',
|
||||
name: 'employeeId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the employee',
|
||||
},
|
||||
{
|
||||
displayName: 'Employee Document Category ID',
|
||||
name: 'categoryId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Data Field Name',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description:
|
||||
'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['employeeDocument'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Share with Employee',
|
||||
name: 'share',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether this file is shared or not',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function upload(this: IExecuteFunctions, index: number) {
|
||||
let body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
|
||||
const id: string = this.getNodeParameter('employeeId', index) as string;
|
||||
const category = this.getNodeParameter('categoryId', index) as string;
|
||||
const options = this.getNodeParameter('options', index);
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', index);
|
||||
const { fileName, mimeType } = this.helpers.assertBinaryData(index, binaryPropertyName);
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(index, binaryPropertyName);
|
||||
|
||||
body = {
|
||||
json: false,
|
||||
formData: {
|
||||
file: {
|
||||
value: binaryDataBuffer,
|
||||
options: {
|
||||
filename: fileName,
|
||||
contentType: mimeType,
|
||||
},
|
||||
},
|
||||
fileName,
|
||||
category,
|
||||
},
|
||||
resolveWithFullResponse: true,
|
||||
};
|
||||
|
||||
if (options.hasOwnProperty('share') && body.formData) {
|
||||
Object.assign(body.formData, options.share ? { share: 'yes' } : { share: 'no' });
|
||||
}
|
||||
//endpoint
|
||||
const endpoint = `employees/${id}/files`;
|
||||
const { headers } = await apiRequest.call(this, requestMethod, endpoint, {}, {}, body);
|
||||
return this.helpers.returnJsonArray({ fileId: headers.location.split('/').pop() });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { employeeDocumentUploadDescription as description } from './description';
|
||||
import { upload as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { FileProperties } from '../../Interfaces';
|
||||
|
||||
export const fileDelDescription: FileProperties = [
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the file',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function del(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'DELETE';
|
||||
|
||||
//meta data
|
||||
const fileId: string = this.getNodeParameter('fileId', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `files/${fileId}`;
|
||||
|
||||
//response
|
||||
await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ success: true });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { fileDelDescription as description } from './description';
|
||||
import { del as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { FileProperties } from '../../Interfaces';
|
||||
|
||||
export const fileDownloadDescription: FileProperties = [
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output In Field',
|
||||
name: 'output',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
description: 'The name of the output field to put the binary file data in',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function download(this: IExecuteFunctions, index: number) {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const items = this.getInputData();
|
||||
|
||||
//meta data
|
||||
const fileId: string = this.getNodeParameter('fileId', index) as string;
|
||||
const output: string = this.getNodeParameter('output', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `files/${fileId}/`;
|
||||
|
||||
//response
|
||||
const response = await apiRequest.call(this, requestMethod, endpoint, body, {} as IDataObject, {
|
||||
encoding: null,
|
||||
json: false,
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
|
||||
let mimeType = response.headers['content-type'] as string | undefined;
|
||||
mimeType = mimeType ? mimeType.split(';').find((value) => value.includes('/')) : undefined;
|
||||
const contentDisposition = response.headers['content-disposition'];
|
||||
const fileNameRegex = /(?<=filename=").*\b/;
|
||||
const match = fileNameRegex.exec(contentDisposition as string);
|
||||
let fileName = '';
|
||||
|
||||
// file name was found
|
||||
if (match !== null) {
|
||||
fileName = match[0];
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[index].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (items[index].binary !== undefined && newItem.binary) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary, items[index].binary);
|
||||
}
|
||||
|
||||
newItem.binary = {
|
||||
[output]: await this.helpers.prepareBinaryData(
|
||||
response.body as unknown as Buffer,
|
||||
fileName,
|
||||
mimeType,
|
||||
),
|
||||
};
|
||||
|
||||
return [newItem as unknown as INodeExecutionData[]];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { fileDownloadDescription as description } from './description';
|
||||
import { download as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { FileProperties } from '../../Interfaces';
|
||||
|
||||
export const fileGetAllDescription: FileProperties = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simplifyOutput',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function getAll(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endpoint = 'files/view';
|
||||
|
||||
//limit parameters
|
||||
const simplifyOutput: boolean = this.getNodeParameter('simplifyOutput', index) as boolean;
|
||||
const returnAll: boolean = this.getNodeParameter('returnAll', 0, false);
|
||||
const limit: number = this.getNodeParameter('limit', 0, 0);
|
||||
|
||||
//response
|
||||
const responseData = await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
const onlyFilesArray = [];
|
||||
|
||||
//return only files without categories
|
||||
if (simplifyOutput) {
|
||||
for (let i = 0; i < responseData.categories.length; i++) {
|
||||
if (responseData.categories[i].hasOwnProperty('files')) {
|
||||
for (let j = 0; j < responseData.categories[i].files.length; j++) {
|
||||
onlyFilesArray.push(responseData.categories[i].files[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!returnAll && onlyFilesArray.length > limit) {
|
||||
return this.helpers.returnJsonArray(onlyFilesArray.slice(0, limit));
|
||||
} else {
|
||||
return this.helpers.returnJsonArray(onlyFilesArray);
|
||||
}
|
||||
}
|
||||
|
||||
//return limited result
|
||||
if (!returnAll && responseData.categories.length > limit) {
|
||||
return this.helpers.returnJsonArray(responseData.categories.slice(0, limit) as IDataObject[]);
|
||||
}
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray(responseData.categories as IDataObject[]);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { fileGetAllDescription as description } from './description';
|
||||
import { getAll as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as del from './del';
|
||||
import * as download from './download';
|
||||
import * as getAll from './getAll';
|
||||
import * as update from './update';
|
||||
import * as upload from './upload';
|
||||
|
||||
export { del, download, getAll, update, upload };
|
||||
|
||||
export const descriptions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a company file',
|
||||
action: 'Delete a file',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download a company file',
|
||||
action: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many company files',
|
||||
action: 'Get many files',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a company file',
|
||||
action: 'Update a file',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload a company file',
|
||||
action: 'Upload a file',
|
||||
},
|
||||
],
|
||||
default: 'delete',
|
||||
},
|
||||
...del.description,
|
||||
...download.description,
|
||||
...getAll.description,
|
||||
...update.description,
|
||||
...upload.description,
|
||||
];
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { FileProperties } from '../../Interfaces';
|
||||
|
||||
export const fileUpdateDescription: FileProperties = [
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Category Name or ID',
|
||||
name: 'categoryId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCompanyFileCategories',
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Move the file to a different category. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'New name of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Share with Employee',
|
||||
name: 'shareWithEmployee',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether this file is shared or not',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function update(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
|
||||
//meta data
|
||||
const fileId: string = this.getNodeParameter('fileId', index) as string;
|
||||
|
||||
//endpoint
|
||||
const endpoint = `files/${fileId}`;
|
||||
|
||||
//body parameters
|
||||
const shareWithEmployee = this.getNodeParameter(
|
||||
'updateFields.shareWithEmployee',
|
||||
index,
|
||||
true,
|
||||
) as boolean;
|
||||
|
||||
body.shareWithEmployee = shareWithEmployee ? 'yes' : 'no';
|
||||
|
||||
//response
|
||||
await apiRequest.call(this, requestMethod, endpoint, body);
|
||||
|
||||
//return
|
||||
return this.helpers.returnJsonArray({ success: true });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { fileUpdateDescription as description } from './description';
|
||||
import { update as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fileUploadDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Input Data Field Name',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description:
|
||||
'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||
},
|
||||
{
|
||||
displayName: 'Category Name or ID',
|
||||
name: 'categoryId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCompanyFileCategories',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Share with Employee',
|
||||
name: 'share',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether this file is shared or not',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../../../transport';
|
||||
|
||||
export async function upload(this: IExecuteFunctions, index: number) {
|
||||
let body: IDataObject = {};
|
||||
const requestMethod = 'POST';
|
||||
|
||||
const category = this.getNodeParameter('categoryId', index) as string;
|
||||
const share = this.getNodeParameter('options.share', index, true) as boolean;
|
||||
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', index);
|
||||
const { fileName, mimeType } = this.helpers.assertBinaryData(index, binaryPropertyName);
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(index, binaryPropertyName);
|
||||
|
||||
body = {
|
||||
json: false,
|
||||
formData: {
|
||||
file: {
|
||||
value: binaryDataBuffer,
|
||||
options: {
|
||||
filename: fileName,
|
||||
contentType: mimeType,
|
||||
},
|
||||
},
|
||||
fileName,
|
||||
category,
|
||||
},
|
||||
resolveWithFullResponse: true,
|
||||
};
|
||||
|
||||
if (body.formData) {
|
||||
Object.assign(body.formData, share ? { share: 'yes' } : { share: 'no' });
|
||||
}
|
||||
|
||||
//endpoint
|
||||
const endpoint = 'files';
|
||||
const { headers } = await apiRequest.call(this, requestMethod, endpoint, {}, {}, body);
|
||||
return this.helpers.returnJsonArray({ fileId: headers.location.split('/').pop() });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { fileUploadDescription as description } from './description';
|
||||
import { upload as execute } from './execute';
|
||||
|
||||
export { description, execute };
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
|
||||
import * as companyReport from './companyReport';
|
||||
import * as employee from './employee';
|
||||
import * as employeeDocument from './employeeDocument';
|
||||
import * as file from './file';
|
||||
import type { BambooHr } from './Interfaces';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
|
||||
const items = this.getInputData();
|
||||
const operationResult: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const resource = this.getNodeParameter<BambooHr>('resource', i);
|
||||
const operation = this.getNodeParameter('operation', i);
|
||||
|
||||
const bamboohr = {
|
||||
resource,
|
||||
operation,
|
||||
} as BambooHr;
|
||||
|
||||
if (bamboohr.operation === 'delete') {
|
||||
//@ts-ignore
|
||||
bamboohr.operation = 'del';
|
||||
}
|
||||
|
||||
try {
|
||||
if (bamboohr.resource === 'employee') {
|
||||
operationResult.push(...(await employee[bamboohr.operation].execute.call(this, i)));
|
||||
} else if (bamboohr.resource === 'employeeDocument') {
|
||||
//@ts-ignore
|
||||
operationResult.push(...(await employeeDocument[bamboohr.operation].execute.call(this, i)));
|
||||
} else if (bamboohr.resource === 'file') {
|
||||
//@ts-ignore
|
||||
operationResult.push(...(await file[bamboohr.operation].execute.call(this, i)));
|
||||
} else if (bamboohr.resource === 'companyReport') {
|
||||
//@ts-ignore
|
||||
operationResult.push(...(await companyReport[bamboohr.operation].execute.call(this, i)));
|
||||
}
|
||||
} catch (err) {
|
||||
if (this.continueOnFail()) {
|
||||
operationResult.push({ json: this.getInputData(i)[0].json, error: err });
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operationResult;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as companyReport from './companyReport';
|
||||
import * as employee from './employee';
|
||||
import * as employeeDocument from './employeeDocument';
|
||||
import * as file from './file';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
credentials: [
|
||||
{
|
||||
name: 'bambooHrApi',
|
||||
required: true,
|
||||
testedBy: 'bambooHrApiCredentialTest',
|
||||
},
|
||||
],
|
||||
defaults: {
|
||||
name: 'BambooHR',
|
||||
},
|
||||
description: 'Consume BambooHR API',
|
||||
displayName: 'BambooHR',
|
||||
group: ['transform'],
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
|
||||
icon: 'file:bambooHr.png',
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
name: 'bambooHr',
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Company Report',
|
||||
value: 'companyReport',
|
||||
},
|
||||
{
|
||||
name: 'Employee',
|
||||
value: 'employee',
|
||||
},
|
||||
{
|
||||
name: 'Employee Document',
|
||||
value: 'employeeDocument',
|
||||
},
|
||||
{
|
||||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
],
|
||||
default: 'employee',
|
||||
},
|
||||
...employee.descriptions,
|
||||
...employeeDocument.descriptions,
|
||||
...file.descriptions,
|
||||
...companyReport.descriptions,
|
||||
],
|
||||
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||
version: 1,
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IHttpRequestOptions,
|
||||
INodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
async function validateCredentials(
|
||||
this: ICredentialTestFunctions,
|
||||
decryptedCredentials: ICredentialDataDecryptedObject,
|
||||
): Promise<any> {
|
||||
const credentials = decryptedCredentials;
|
||||
|
||||
const { subdomain, apiKey } = credentials as {
|
||||
subdomain: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
const options: IHttpRequestOptions = {
|
||||
method: 'GET',
|
||||
auth: {
|
||||
username: apiKey,
|
||||
password: 'x',
|
||||
},
|
||||
url: `https://api.bamboohr.com/api/gateway.php/${subdomain}/v1/employees/directory`,
|
||||
};
|
||||
|
||||
return await this.helpers.request(options);
|
||||
}
|
||||
|
||||
export async function bambooHrApiCredentialTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: 'The API Key included in the request is invalid',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
} as INodeCredentialTestResult;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as loadOptions from './loadOptions';
|
||||
export * as credentialTest from './credentialTest';
|
||||
@@ -0,0 +1,187 @@
|
||||
import type { IDataObject, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from '../transport';
|
||||
|
||||
// Get all the available channels
|
||||
export async function getTimeOffTypeID(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'meta/time_off/types';
|
||||
|
||||
const response = await apiRequest.call(this, requestMethod, endPoint, body);
|
||||
const timeOffTypeIds = response.body.timeOffTypes;
|
||||
|
||||
for (const item of timeOffTypeIds) {
|
||||
returnData.push({
|
||||
name: item.name,
|
||||
value: item.id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const sort = (a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export async function getCompanyFileCategories(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'files/view/';
|
||||
|
||||
const response = await apiRequest.call(this, requestMethod, endPoint, body);
|
||||
const categories = response.categories;
|
||||
|
||||
for (const category of categories) {
|
||||
returnData.push({
|
||||
name: category.name,
|
||||
value: category.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getEmployeeDocumentCategories(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const id = this.getCurrentNodeParameter('employeeId') as string;
|
||||
|
||||
const endPoint = `employees/${id}/files/view/`;
|
||||
|
||||
const response = await apiRequest.call(this, requestMethod, endPoint, body);
|
||||
const categories = response.categories;
|
||||
|
||||
for (const category of categories) {
|
||||
returnData.push({
|
||||
name: category.name,
|
||||
value: category.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getEmployeeLocations(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'meta/lists/';
|
||||
|
||||
//do not request all data?
|
||||
const fields = (await apiRequest.call(this, requestMethod, endPoint, body, {})) as [
|
||||
{ fieldId: number; options: [{ id: number; name: string }] },
|
||||
];
|
||||
|
||||
const options = fields.filter((field) => field.fieldId === 18)[0].options;
|
||||
|
||||
for (const option of options) {
|
||||
returnData.push({
|
||||
name: option.name,
|
||||
value: option.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getDepartments(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'meta/lists/';
|
||||
|
||||
//do not request all data?
|
||||
const fields = (await apiRequest.call(this, requestMethod, endPoint, body, {})) as [
|
||||
{ fieldId: number; options: [{ id: number; name: string }] },
|
||||
];
|
||||
|
||||
const options = fields.filter((field) => field.fieldId === 4)[0].options;
|
||||
|
||||
for (const option of options) {
|
||||
returnData.push({
|
||||
name: option.name,
|
||||
value: option.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getDivisions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'meta/lists/';
|
||||
|
||||
//do not request all data?
|
||||
const fields = (await apiRequest.call(this, requestMethod, endPoint, body, {})) as [
|
||||
{ fieldId: number; options: [{ id: number; name: string }] },
|
||||
];
|
||||
|
||||
const options = fields.filter((field) => field.fieldId === 1355)[0].options;
|
||||
|
||||
for (const option of options) {
|
||||
returnData.push({
|
||||
name: option.name,
|
||||
value: option.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getEmployeeFields(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {};
|
||||
const requestMethod = 'GET';
|
||||
const endPoint = 'employees/directory';
|
||||
|
||||
const { fields } = await apiRequest.call(this, requestMethod, endPoint, body);
|
||||
|
||||
for (const field of fields) {
|
||||
returnData.push({
|
||||
name: field.name || field.id,
|
||||
value: field.id,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort(sort);
|
||||
|
||||
returnData.unshift({
|
||||
name: '[All]',
|
||||
value: 'all',
|
||||
});
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
JsonObject,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Make an API request to Mattermost
|
||||
*/
|
||||
export async function apiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||
endpoint: string,
|
||||
body: string[] | IDataObject = {},
|
||||
query: IDataObject = {},
|
||||
option: IDataObject = {},
|
||||
) {
|
||||
const credentials = await this.getCredentials('bambooHrApi');
|
||||
|
||||
//set-up credentials
|
||||
const apiKey = credentials.apiKey;
|
||||
const subdomain = credentials.subdomain;
|
||||
|
||||
//set-up uri
|
||||
const uri = `https://api.bamboohr.com/api/gateway.php/${subdomain}/v1/${endpoint}`;
|
||||
|
||||
const options: IRequestOptions = {
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
url: uri,
|
||||
auth: {
|
||||
username: apiKey as string,
|
||||
password: 'x',
|
||||
},
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (Object.keys(option).length) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (!Object.keys(query).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request(options);
|
||||
} catch (error) {
|
||||
const description = error?.response?.headers['x-bamboohr-error-messsage'] || '';
|
||||
const message = error?.message || '';
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, { message, description });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user