Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { CreateEntryParams } from '../common/types';
|
||||
|
||||
export const addNoteToEntityAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'add_note_to_entity',
|
||||
displayName: 'Add Note to Entity',
|
||||
description:
|
||||
'Add a comment/note to an entity (e.g., contact, opportunity, project).',
|
||||
props: {
|
||||
content: Property.LongText({
|
||||
displayName: 'Note Content',
|
||||
description: 'The body of the note.',
|
||||
required: true,
|
||||
}),
|
||||
entityType: Property.StaticDropdown({
|
||||
displayName: 'Entity Type',
|
||||
description: 'The type of entity to add the note to.',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Party (Contact)', value: 'party' },
|
||||
{ label: 'Opportunity', value: 'opportunity' },
|
||||
{ label: 'Project', value: 'project' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
entityId: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Entity',
|
||||
required: true,
|
||||
refreshers: ['entityType'],
|
||||
props: async ({ auth, entityType }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
const entityTypeStr = entityType as unknown as string;
|
||||
if (!auth || !entityTypeStr) return fields;
|
||||
|
||||
if (entityTypeStr === 'party') {
|
||||
const contacts = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
''
|
||||
);
|
||||
fields['partyId'] = Property.StaticDropdown({
|
||||
displayName: 'Party',
|
||||
required: true,
|
||||
options:{
|
||||
options: contacts.map((contact) => ({
|
||||
label:
|
||||
contact.type === 'person'
|
||||
? `${contact.firstName} ${contact.lastName}`
|
||||
: contact.name || `Unnamed ${contact.type}`,
|
||||
value: contact.id,
|
||||
})),
|
||||
}})
|
||||
} else if (entityTypeStr === 'opportunity') {
|
||||
const opportunities =
|
||||
await capsuleCrmClient.searchOpportunities(
|
||||
auth
|
||||
);
|
||||
fields['opportunityId'] = Property.StaticDropdown({
|
||||
displayName: 'Opportunity',
|
||||
required: true,
|
||||
options: {
|
||||
options: opportunities.map((opportunity) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
})),
|
||||
}})
|
||||
} else if (entityTypeStr === 'project') {
|
||||
const projects = await capsuleCrmClient.searchProjects(
|
||||
auth
|
||||
);
|
||||
fields['projectId'] = Property.StaticDropdown({
|
||||
displayName: 'Project',
|
||||
required: true,
|
||||
options:{
|
||||
options: projects.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.id,
|
||||
})),
|
||||
}})
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
activityTypeId: Property.Dropdown({
|
||||
displayName: 'Activity Type',
|
||||
description: 'The activity type for this entry. Defaults to "Note".',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const activityTypes = await capsuleCrmClient.listActivityTypes(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: activityTypes.map((activityType) => ({
|
||||
label: activityType.name,
|
||||
value: activityType.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
|
||||
const entryData: CreateEntryParams = {
|
||||
type: 'note',
|
||||
content: propsValue.content,
|
||||
};
|
||||
|
||||
if (propsValue.activityTypeId) {
|
||||
entryData.activityType = { id: propsValue.activityTypeId };
|
||||
}
|
||||
|
||||
const entityId = propsValue.entityId as DynamicPropsValue;
|
||||
if (entityId) {
|
||||
if (entityId['partyId']) {
|
||||
entryData.party = { id: entityId['partyId'] as number };
|
||||
} else if (entityId['opportunityId']) {
|
||||
entryData.opportunity = { id: entityId['opportunityId'] as number };
|
||||
} else if (entityId['projectId']) {
|
||||
entryData.kase = { id: entityId['projectId'] as number };
|
||||
}
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.createEntry(auth, entryData);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,378 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
OAuth2PropertyValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { CreatePartyParams } from '../common/types';
|
||||
|
||||
export const createContactAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'create_contact',
|
||||
displayName: 'Create Contact',
|
||||
description: 'Create a new Person or Organisation in Capsule CRM.',
|
||||
props: {
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Contact Type',
|
||||
description: 'The type of contact to create.',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Person', value: 'person' },
|
||||
{ label: 'Organisation', value: 'organisation' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
contactFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Details',
|
||||
required: true,
|
||||
refreshers: ['type'],
|
||||
props: async ({ auth, type }) => {
|
||||
const contactType = type as unknown as string;
|
||||
const fields: DynamicPropsValue = {};
|
||||
|
||||
if (contactType === 'person') {
|
||||
// Fetch organisations data ONCE at the outer level
|
||||
let organisationOptions: { label: string; value: number }[] = [];
|
||||
|
||||
if (auth) {
|
||||
try {
|
||||
const organisations = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
''
|
||||
);
|
||||
|
||||
organisationOptions = organisations
|
||||
.filter((party) => party.type === 'organisation')
|
||||
.map((org) => ({
|
||||
label: org.name || 'Unnamed Organisation',
|
||||
value: org.id,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to load organisations:', error);
|
||||
}
|
||||
}
|
||||
|
||||
fields['firstName'] = Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
required: true,
|
||||
});
|
||||
fields['lastName'] = Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
required: true,
|
||||
});
|
||||
fields['title'] = Property.ShortText({
|
||||
displayName: 'Title',
|
||||
required: false,
|
||||
});
|
||||
fields['jobTitle'] = Property.ShortText({
|
||||
displayName: 'Job Title',
|
||||
required: false,
|
||||
});
|
||||
fields['organisationId'] = Property.StaticDropdown({
|
||||
displayName: 'Organisation',
|
||||
required: false,
|
||||
options: {
|
||||
options: organisationOptions,
|
||||
},
|
||||
});
|
||||
} else if (contactType === 'organisation') {
|
||||
fields['organisationName'] = Property.ShortText({
|
||||
displayName: 'Organisation Name',
|
||||
required: true,
|
||||
});
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
about: Property.LongText({
|
||||
displayName: 'About',
|
||||
required: false,
|
||||
}),
|
||||
ownerId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Owner',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [] };
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: users.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
teamId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Team',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [] };
|
||||
const teams = await capsuleCrmClient.listTeams(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: teams.map((team) => ({
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
tags: Property.MultiSelectDropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Tags',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [] };
|
||||
const tags = await capsuleCrmClient.listTags(auth);
|
||||
return {
|
||||
options: tags.map((tag) => ({
|
||||
label: tag.name,
|
||||
value: tag.name,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
customFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Custom Fields',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
props: async ({ auth }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth) return fields;
|
||||
const customFields = await capsuleCrmClient.listCustomFields(
|
||||
auth
|
||||
);
|
||||
for (const field of customFields) {
|
||||
switch (field.type) {
|
||||
case 'list':
|
||||
fields[field.id] = Property.StaticDropdown({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
options: {
|
||||
options:
|
||||
field.options?.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
})) || [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'boolean':
|
||||
fields[field.id] = Property.Checkbox({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
case 'date':
|
||||
fields[field.id] = Property.DateTime({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
fields[field.id] = Property.ShortText({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
emailAddresses: Property.Array({
|
||||
displayName: 'Email Addresses',
|
||||
required: false,
|
||||
properties: {
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
address: Property.ShortText({
|
||||
displayName: 'Address',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
phoneNumbers: Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
{ label: 'Mobile', value: 'Mobile' },
|
||||
{ label: 'Fax', value: 'Fax' },
|
||||
{ label: 'Direct', value: 'Direct' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
addresses: Property.Array({
|
||||
displayName: 'Addresses',
|
||||
required: false,
|
||||
properties: {
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Postal', value: 'Postal' },
|
||||
{ label: 'Office', value: 'Office' },
|
||||
{ label: 'Billing', value: 'Billing' },
|
||||
{ label: 'Shipping', value: 'Shipping' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
}),
|
||||
city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
required: false,
|
||||
}),
|
||||
state: Property.ShortText({
|
||||
displayName: 'State',
|
||||
required: false,
|
||||
}),
|
||||
country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
}),
|
||||
zip: Property.ShortText({
|
||||
displayName: 'Zip',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
websites: Property.Array({
|
||||
displayName: 'Websites',
|
||||
required: false,
|
||||
properties: {
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
service: Property.StaticDropdown({
|
||||
displayName: 'Service',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'URL', value: 'URL' },
|
||||
{ label: 'Skype', value: 'SKYPE' },
|
||||
{ label: 'Twitter', value: 'TWITTER' },
|
||||
{ label: 'LinkedIn', value: 'LINKED_IN' },
|
||||
{ label: 'Facebook', value: 'FACEBOOK' },
|
||||
{ label: 'Xing', value: 'XING' },
|
||||
{ label: 'Feed', value: 'FEED' },
|
||||
{ label: 'Google+', value: 'GOOGLE_PLUS' },
|
||||
{ label: 'Flickr', value: 'FLICKR' },
|
||||
{ label: 'GitHub', value: 'GITHUB' },
|
||||
{ label: 'YouTube', value: 'YOUTUBE' },
|
||||
{ label: 'Instagram', value: 'INSTAGRAM' },
|
||||
{ label: 'Pinterest', value: 'PINTEREST' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
address: Property.ShortText({
|
||||
displayName: 'Address',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
|
||||
const type = propsValue.type as 'person' | 'organisation';
|
||||
const contactFields = propsValue.contactFields as DynamicPropsValue;
|
||||
const contactData: Partial<CreatePartyParams> = {
|
||||
type: type,
|
||||
title: contactFields['title'] as string | undefined,
|
||||
jobTitle: contactFields['jobTitle'] as string | undefined,
|
||||
about: propsValue.about,
|
||||
organisationId: contactFields['organisationId'] as number | undefined,
|
||||
ownerId: propsValue.ownerId,
|
||||
teamId: propsValue.teamId,
|
||||
tags: propsValue.tags,
|
||||
emailAddresses: propsValue.emailAddresses as {
|
||||
type?: string;
|
||||
address: string;
|
||||
}[],
|
||||
phoneNumbers: propsValue.phoneNumbers as {
|
||||
type?: string;
|
||||
number: string;
|
||||
}[],
|
||||
addresses: propsValue.addresses as {
|
||||
type?: string;
|
||||
street?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
zip?: string;
|
||||
}[],
|
||||
websites: propsValue.websites as {
|
||||
type?: string;
|
||||
service: string;
|
||||
address: string;
|
||||
}[],
|
||||
};
|
||||
|
||||
if (type === 'person') {
|
||||
contactData.firstName = contactFields['firstName'] as string;
|
||||
contactData.lastName = contactFields['lastName'] as string;
|
||||
} else if (type === 'organisation') {
|
||||
contactData.name = contactFields['organisationName'] as string;
|
||||
}
|
||||
|
||||
const customFields = propsValue.customFields as DynamicPropsValue;
|
||||
if (customFields) {
|
||||
contactData.fields = Object.entries(customFields)
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.map(([id, value]) => ({
|
||||
definition: { id: Number(id) },
|
||||
value: value,
|
||||
}));
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.createContact(
|
||||
auth,
|
||||
contactData as CreatePartyParams
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import {
|
||||
CreateOpportunityParams,
|
||||
OpportunityCustomField,
|
||||
OpportunityTag,
|
||||
} from '../common/types';
|
||||
|
||||
export const createOpportunityAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'create_opportunity',
|
||||
displayName: 'Create Opportunity',
|
||||
description: 'Create a new Opportunity in Capsule CRM.',
|
||||
props: {
|
||||
partyId: Property.Dropdown({
|
||||
displayName: 'Party',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const contacts = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
''
|
||||
);
|
||||
return {
|
||||
options: contacts.map((contact) => ({
|
||||
label:
|
||||
contact.type === 'person'
|
||||
? `${contact.firstName} ${contact.lastName}`
|
||||
: contact.name || `Unnamed ${contact.type}`,
|
||||
value: contact.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'A short description of the opportunity.',
|
||||
required: true,
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description: 'More details about the opportunity.',
|
||||
required: false,
|
||||
}),
|
||||
milestoneId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Milestone',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const milestones = await capsuleCrmClient.listMilestones(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: milestones.map((milestone) => ({
|
||||
label: milestone.name,
|
||||
value: milestone.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
currency: Property.ShortText({
|
||||
displayName: 'Currency',
|
||||
description: 'The currency for the opportunity value (e.g., USD, GBP).',
|
||||
required: false,
|
||||
}),
|
||||
amount: Property.Number({
|
||||
displayName: 'Amount',
|
||||
description: 'The numerical value of the opportunity.',
|
||||
required: false,
|
||||
}),
|
||||
expectedCloseOn: Property.DateTime({
|
||||
displayName: 'Expected Close Date',
|
||||
description: 'The expected closing date for the opportunity.',
|
||||
required: false,
|
||||
}),
|
||||
probability: Property.Number({
|
||||
displayName: 'Probability',
|
||||
description: 'The probability of winning the opportunity.',
|
||||
required: false,
|
||||
}),
|
||||
durationBasis: Property.StaticDropdown({
|
||||
displayName: 'Duration Basis',
|
||||
required: false,
|
||||
description: 'The basis of the duration of the opportunity.',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Fixed', value: 'FIXED' },
|
||||
{ label: 'Hour', value: 'HOUR' },
|
||||
{ label: 'Day', value: 'DAY' },
|
||||
{ label: 'Week', value: 'WEEK' },
|
||||
{ label: 'Month', value: 'MONTH' },
|
||||
{ label: 'Quarter', value: 'QUARTER' },
|
||||
{ label: 'Year', value: 'YEAR' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
duration: Property.Number({
|
||||
displayName: 'Duration',
|
||||
required: false,
|
||||
description: 'The duration of the opportunity.',
|
||||
}),
|
||||
ownerId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Owner',
|
||||
required: false,
|
||||
description: 'The user the opportunity is assigned to.',
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: users.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
teamId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Team',
|
||||
required: false,
|
||||
description: 'The team the opportunity is assigned to.',
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const teams = await capsuleCrmClient.listTeams(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: teams.map((team) => ({
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
tags: Property.MultiSelectDropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Tags',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const tags = await capsuleCrmClient.listTags(auth);
|
||||
return {
|
||||
options: tags.map((tag) => ({
|
||||
label: tag.name,
|
||||
value: tag.name,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
customFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Custom Fields',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
props: async ({ auth }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const customFields = await capsuleCrmClient.listCustomFields(
|
||||
auth
|
||||
);
|
||||
for (const field of customFields) {
|
||||
switch (field.type) {
|
||||
case 'list':
|
||||
fields[field.id] = Property.StaticDropdown({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
options: {
|
||||
options:
|
||||
field.options?.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
})) || [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'boolean':
|
||||
fields[field.id] = Property.Checkbox({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
case 'date':
|
||||
fields[field.id] = Property.DateTime({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
fields[field.id] = Property.ShortText({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
|
||||
const opportunityData: CreateOpportunityParams = {
|
||||
party: { id: propsValue.partyId },
|
||||
name: propsValue.name,
|
||||
milestone: { id: propsValue.milestoneId },
|
||||
description: propsValue.description,
|
||||
expectedCloseOn: propsValue.expectedCloseOn,
|
||||
probability: propsValue.probability,
|
||||
durationBasis: propsValue.durationBasis,
|
||||
duration: propsValue.duration,
|
||||
};
|
||||
|
||||
if (propsValue.ownerId) {
|
||||
opportunityData.owner = { id: propsValue.ownerId };
|
||||
}
|
||||
if (propsValue.teamId) {
|
||||
opportunityData.team = { id: propsValue.teamId };
|
||||
}
|
||||
|
||||
if (propsValue.currency && propsValue.amount) {
|
||||
opportunityData.value = {
|
||||
currency: propsValue.currency,
|
||||
amount: propsValue.amount,
|
||||
};
|
||||
}
|
||||
|
||||
if (propsValue.tags) {
|
||||
opportunityData.tags = propsValue.tags.map(
|
||||
(tag) => ({ name: tag } as OpportunityTag)
|
||||
);
|
||||
}
|
||||
|
||||
const customFields = propsValue.customFields as DynamicPropsValue;
|
||||
if (customFields) {
|
||||
opportunityData.fields = Object.entries(customFields)
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.map(
|
||||
([id, value]) =>
|
||||
({
|
||||
definition: { id: Number(id) },
|
||||
value: value,
|
||||
} as OpportunityCustomField)
|
||||
);
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.createOpportunity(auth, opportunityData);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,288 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import {
|
||||
CreateProjectParams,
|
||||
OpportunityCustomField,
|
||||
OpportunityTag,
|
||||
} from '../common/types';
|
||||
|
||||
export const createProjectAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'create_project',
|
||||
displayName: 'Create Project',
|
||||
description: 'Create a new Project in Capsule CRM.',
|
||||
props: {
|
||||
partyId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Party',
|
||||
description: 'The main contact for this project.',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const contacts = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
''
|
||||
);
|
||||
return {
|
||||
options: contacts.map((contact) => ({
|
||||
label:
|
||||
contact.type === 'person'
|
||||
? `${contact.firstName} ${contact.lastName}`
|
||||
: contact.name || `Unnamed ${contact.type}`,
|
||||
value: contact.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of this project.',
|
||||
required: true,
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description: 'The description of this project.',
|
||||
required: false,
|
||||
}),
|
||||
opportunityId: Property.Dropdown({
|
||||
displayName: 'Opportunity',
|
||||
description:
|
||||
'An optional link to the opportunity that this project was created to support.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const opportunities = await capsuleCrmClient.searchOpportunities(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: opportunities.map((opportunity) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
stageId: Property.Dropdown({
|
||||
displayName: 'Stage',
|
||||
description: 'The stage that this project is on.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const stages = await capsuleCrmClient.listStages(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: stages.map((stage) => ({
|
||||
label: stage.name,
|
||||
value: stage.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
status: Property.StaticDropdown({
|
||||
displayName: 'Status',
|
||||
description: 'The status of the project.',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Open', value: 'OPEN' },
|
||||
{ label: 'Closed', value: 'CLOSED' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
expectedCloseOn: Property.DateTime({
|
||||
displayName: 'Expected Close Date',
|
||||
description: 'The expected close date of this project.',
|
||||
required: false,
|
||||
}),
|
||||
ownerId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Owner',
|
||||
description: 'The user this project is assigned to.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: users.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
teamId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Team',
|
||||
description: 'The team this project is assigned to.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const teams = await capsuleCrmClient.listTeams(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: teams.map((team) => ({
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
tags: Property.MultiSelectDropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Tags',
|
||||
description: 'An array of tags that are added to this project.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const tags = await capsuleCrmClient.listTags(auth);
|
||||
return {
|
||||
options: tags.map((tag) => ({
|
||||
label: tag.name,
|
||||
value: tag.name,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
customFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Custom Fields',
|
||||
description: 'An array of custom fields that are defined for this project.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
props: async ({ auth }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth) return fields;
|
||||
const customFields = await capsuleCrmClient.listCustomFields(
|
||||
auth
|
||||
);
|
||||
for (const field of customFields) {
|
||||
switch (field.type) {
|
||||
case 'list':
|
||||
fields[field.id] = Property.StaticDropdown({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
options: {
|
||||
options:
|
||||
field.options?.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
})) || [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'boolean':
|
||||
fields[field.id] = Property.Checkbox({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
case 'date':
|
||||
fields[field.id] = Property.DateTime({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
fields[field.id] = Property.ShortText({
|
||||
displayName: field.name,
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
|
||||
const projectData: CreateProjectParams = {
|
||||
party: { id: propsValue.partyId },
|
||||
name: propsValue.name,
|
||||
description: propsValue.description,
|
||||
status: propsValue.status as 'OPEN' | 'CLOSED' | undefined,
|
||||
expectedCloseOn: propsValue.expectedCloseOn,
|
||||
};
|
||||
|
||||
if (propsValue.opportunityId) {
|
||||
projectData.opportunity = { id: propsValue.opportunityId };
|
||||
}
|
||||
if (propsValue.stageId) {
|
||||
projectData.stage = { id: propsValue.stageId };
|
||||
}
|
||||
if (propsValue.ownerId) {
|
||||
projectData.owner = { id: propsValue.ownerId };
|
||||
}
|
||||
if (propsValue.teamId) {
|
||||
projectData.team = { id: propsValue.teamId };
|
||||
}
|
||||
|
||||
if (propsValue.tags) {
|
||||
projectData.tags = propsValue.tags.map(
|
||||
(tag) => ({ name: tag } as OpportunityTag)
|
||||
);
|
||||
}
|
||||
|
||||
const customFields = propsValue.customFields as DynamicPropsValue;
|
||||
if (customFields) {
|
||||
projectData.fields = Object.entries(customFields)
|
||||
.filter(([, value]) => value !== undefined && value !== null)
|
||||
.map(
|
||||
([id, value]) =>
|
||||
({
|
||||
definition: { id: Number(id) },
|
||||
value: value,
|
||||
} as OpportunityCustomField)
|
||||
);
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.createProject(auth, projectData);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { CreateTaskParams } from '../common/types';
|
||||
|
||||
export const createTaskAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'create_task',
|
||||
displayName: 'Create Task',
|
||||
description: 'Create a new Task in Capsule CRM.',
|
||||
props: {
|
||||
description: Property.ShortText({
|
||||
displayName: 'Description',
|
||||
description: 'A short description of the task.',
|
||||
required: true,
|
||||
}),
|
||||
dueOn: Property.DateTime({
|
||||
displayName: 'Due Date',
|
||||
description: 'The date when this task is due.',
|
||||
required: true,
|
||||
}),
|
||||
detail: Property.LongText({
|
||||
displayName: 'Details',
|
||||
description: 'More details about the task.',
|
||||
required: false,
|
||||
}),
|
||||
dueTime: Property.ShortText({
|
||||
displayName: 'Due Time',
|
||||
description:
|
||||
"The time when this task is due (e.g., 18:00:00). Note: The time is in the user's timezone.",
|
||||
required: false,
|
||||
}),
|
||||
linkTo: Property.StaticDropdown({
|
||||
displayName: 'Link To',
|
||||
description:
|
||||
'The entity this task is linked to. Only one can be selected.',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Party (Contact)', value: 'party' },
|
||||
{ label: 'Opportunity', value: 'opportunity' },
|
||||
{ label: 'Project', value: 'project' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
linkedEntityId: Property.DynamicProperties({
|
||||
displayName: 'Linked Entity',
|
||||
required: false,
|
||||
refreshers: ['linkTo'],
|
||||
auth: capsuleCrmAuth,
|
||||
props: async ({ auth, linkTo }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
const linkToType = linkTo as unknown as string;
|
||||
if (!auth || !linkToType) return fields;
|
||||
|
||||
if (linkToType === 'party') {
|
||||
const contacts = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
''
|
||||
);
|
||||
const contactOptions = contacts.map((contact) => ({
|
||||
label:
|
||||
contact.type === 'person'
|
||||
? `${contact.firstName} ${contact.lastName}`
|
||||
: contact.name || `Unnamed ${contact.type}`,
|
||||
value: contact.id,
|
||||
}));
|
||||
fields['partyId'] = Property.StaticDropdown({
|
||||
displayName: 'Party',
|
||||
required: true,
|
||||
options: {
|
||||
options: contactOptions,
|
||||
},
|
||||
});
|
||||
} else if (linkToType === 'opportunity') {
|
||||
const opportunities = await capsuleCrmClient.searchOpportunities(
|
||||
auth
|
||||
);
|
||||
const opportunityOptions = opportunities.map((opportunity) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
}));
|
||||
fields['opportunityId'] = Property.StaticDropdown({
|
||||
displayName: 'Opportunity',
|
||||
required: true,
|
||||
options: {
|
||||
options: opportunityOptions,
|
||||
},
|
||||
});
|
||||
} else if (linkToType === 'project') {
|
||||
const projects = await capsuleCrmClient.searchProjects(
|
||||
auth
|
||||
);
|
||||
const projectOptions = projects.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.id,
|
||||
}));
|
||||
fields['projectId'] = Property.StaticDropdown({
|
||||
displayName: 'Project',
|
||||
required: true,
|
||||
options: {
|
||||
options: projectOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
categoryId: Property.Dropdown({
|
||||
displayName: 'Category',
|
||||
description: 'The category of this task.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const categories = await capsuleCrmClient.listCategories(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: categories.map((category) => ({
|
||||
label: category.name,
|
||||
value: category.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
ownerId: Property.Dropdown({
|
||||
displayName: 'Owner',
|
||||
description: 'The user this task is assigned to.',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth)
|
||||
return {
|
||||
options: [],
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your Capsule CRM account first',
|
||||
};
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: users.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
|
||||
const taskData: CreateTaskParams = {
|
||||
description: propsValue.description,
|
||||
dueOn: propsValue.dueOn,
|
||||
detail: propsValue.detail,
|
||||
dueTime: propsValue.dueTime,
|
||||
};
|
||||
|
||||
if (propsValue.categoryId) {
|
||||
taskData.category = { id: propsValue.categoryId };
|
||||
}
|
||||
if (propsValue.ownerId) {
|
||||
taskData.owner = { id: propsValue.ownerId };
|
||||
}
|
||||
|
||||
const linkedEntity = propsValue.linkedEntityId as DynamicPropsValue;
|
||||
if (linkedEntity) {
|
||||
if (linkedEntity['partyId']) {
|
||||
taskData.party = { id: linkedEntity['partyId'] as number };
|
||||
} else if (linkedEntity['opportunityId']) {
|
||||
taskData.opportunity = { id: linkedEntity['opportunityId'] as number };
|
||||
} else if (linkedEntity['projectId']) {
|
||||
taskData.kase = { id: linkedEntity['projectId'] as number };
|
||||
}
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.createTask(auth, taskData);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
|
||||
export const findContactAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'find_contact',
|
||||
displayName: 'Find Contact',
|
||||
description: 'Find a Person by search criteria.',
|
||||
props: {
|
||||
term: Property.ShortText({
|
||||
displayName: 'Search Term',
|
||||
description: 'The value to search for (e.g., a name or email).',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
const parties = await capsuleCrmClient.findContact(auth, propsValue.term);
|
||||
return parties.filter((party) => party.type === 'person');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { Filter } from '../common/types';
|
||||
|
||||
export const findOpportunityAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'find_opportunity',
|
||||
displayName: 'Find Opportunity',
|
||||
description: 'Find an Opportunity by search criteria.',
|
||||
props: {
|
||||
filter: Property.Json({
|
||||
displayName: 'Filter',
|
||||
description:
|
||||
'The structured filter query. See the [documentation](https://capsulecrm.com/developer/api-v2/filters/) for examples.',
|
||||
required: true,
|
||||
defaultValue: {
|
||||
conditions: [
|
||||
{
|
||||
field: 'name',
|
||||
operator: 'is',
|
||||
value: 'example',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
return await capsuleCrmClient.filterOpportunities(
|
||||
auth,
|
||||
propsValue.filter as unknown as Filter
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { Filter } from '../common/types';
|
||||
|
||||
export const findProjectAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'find_project',
|
||||
displayName: 'Find Project',
|
||||
description: 'Find a Project by search criteria.',
|
||||
props: {
|
||||
filter: Property.Json({
|
||||
displayName: 'Filter',
|
||||
description:
|
||||
'The structured filter query. See the [documentation](https://capsulecrm.com/developer/api-v2/filters/) for examples.',
|
||||
required: true,
|
||||
defaultValue: {
|
||||
conditions: [
|
||||
{
|
||||
field: 'name',
|
||||
operator: 'is',
|
||||
value: 'example',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
return await capsuleCrmClient.filterProjects(
|
||||
auth,
|
||||
propsValue.filter as unknown as Filter
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,368 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
OAuth2PropertyValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import { capsuleCrmProps } from '../common/props';
|
||||
|
||||
export const updateContactAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'update_contact',
|
||||
displayName: 'Update Contact',
|
||||
description: 'Update fields on an existing Person or Organisation.',
|
||||
props: {
|
||||
contact_id: capsuleCrmProps.contact_id(),
|
||||
contactFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Details',
|
||||
required: true,
|
||||
refreshers: ['contact_id'],
|
||||
props: async ({ auth, contact_id }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !contact_id) return fields;
|
||||
|
||||
const contact = await capsuleCrmClient.getContact(
|
||||
auth,
|
||||
contact_id as unknown as number
|
||||
);
|
||||
|
||||
if (contact?.type === 'person') {
|
||||
fields['firstName'] = Property.ShortText({
|
||||
displayName: 'First Name',
|
||||
description: "Update the person's first name.",
|
||||
required: false,
|
||||
});
|
||||
fields['lastName'] = Property.ShortText({
|
||||
displayName: 'Last Name',
|
||||
description: "Update the person's last name.",
|
||||
required: false,
|
||||
});
|
||||
fields['title'] = Property.ShortText({
|
||||
displayName: 'Title',
|
||||
description: "Update the person's job title.",
|
||||
required: false,
|
||||
});
|
||||
} else if (contact?.type === 'organisation') {
|
||||
fields['organisationName'] = Property.ShortText({
|
||||
displayName: 'Organisation Name',
|
||||
description: "Update the organisation's name.",
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
ownerId: capsuleCrmProps.owner_id(false),
|
||||
teamId: capsuleCrmProps.team_id(false),
|
||||
about: Property.LongText({
|
||||
displayName: 'About',
|
||||
description: 'Update the biography or description for the contact.',
|
||||
required: false,
|
||||
}),
|
||||
addresses: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Addresses',
|
||||
required: false,
|
||||
refreshers: ['contact_id'],
|
||||
props: async ({ auth, contact_id }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !contact_id) return fields;
|
||||
|
||||
const contact = await capsuleCrmClient.getContact(
|
||||
auth,
|
||||
contact_id as unknown as number
|
||||
);
|
||||
const addressOptions =
|
||||
contact?.addresses?.map((address) => ({
|
||||
label: `${address.street}, ${address.city}`,
|
||||
value: address.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['addresses'] = Property.Array({
|
||||
displayName: 'Addresses',
|
||||
required: false,
|
||||
properties: {
|
||||
id: Property.StaticDropdown({
|
||||
displayName: 'Address',
|
||||
required: false,
|
||||
options: {
|
||||
options: addressOptions,
|
||||
},
|
||||
}),
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Postal', value: 'Postal' },
|
||||
{ label: 'Office', value: 'Office' },
|
||||
{ label: 'Billing', value: 'Billing' },
|
||||
{ label: 'Shipping', value: 'Shipping' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
street: Property.ShortText({
|
||||
displayName: 'Street',
|
||||
required: false,
|
||||
}),
|
||||
city: Property.ShortText({
|
||||
displayName: 'City',
|
||||
required: false,
|
||||
}),
|
||||
state: Property.ShortText({
|
||||
displayName: 'State',
|
||||
required: false,
|
||||
}),
|
||||
country: Property.ShortText({
|
||||
displayName: 'Country',
|
||||
required: false,
|
||||
}),
|
||||
zip: Property.ShortText({
|
||||
displayName: 'Zip',
|
||||
required: false,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the address.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
websites: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Websites',
|
||||
required: false,
|
||||
refreshers: ['contact_id'],
|
||||
props: async ({ auth, contact_id }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !contact_id) return fields;
|
||||
|
||||
const contact = await capsuleCrmClient.getContact(
|
||||
auth,
|
||||
contact_id as unknown as number
|
||||
);
|
||||
const websiteOptions =
|
||||
contact?.websites?.map((website) => ({
|
||||
label: website.address,
|
||||
value: website.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['websites'] = Property.Array({
|
||||
displayName: 'Websites',
|
||||
required: false,
|
||||
properties: {
|
||||
id: Property.StaticDropdown({
|
||||
displayName: 'Website',
|
||||
required: false,
|
||||
options: {
|
||||
options: websiteOptions,
|
||||
},
|
||||
}),
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
service: Property.StaticDropdown({
|
||||
displayName: 'Service',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'URL', value: 'URL' },
|
||||
{ label: 'Skype', value: 'SKYPE' },
|
||||
{ label: 'Twitter', value: 'TWITTER' },
|
||||
{ label: 'LinkedIn', value: 'LINKED_IN' },
|
||||
{ label: 'Facebook', value: 'FACEBOOK' },
|
||||
{ label: 'Xing', value: 'XING' },
|
||||
{ label: 'Feed', value: 'FEED' },
|
||||
{ label: 'Google+', value: 'GOOGLE_PLUS' },
|
||||
{ label: 'Flickr', value: 'FLICKR' },
|
||||
{ label: 'GitHub', value: 'GITHUB' },
|
||||
{ label: 'YouTube', value: 'YOUTUBE' },
|
||||
{ label: 'Instagram', value: 'INSTAGRAM' },
|
||||
{ label: 'Pinterest', value: 'PINTEREST' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
address: Property.ShortText({
|
||||
displayName: 'Address',
|
||||
required: false,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the website.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
emailAddresses: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Email Addresses',
|
||||
required: false,
|
||||
refreshers: ['contact_id'],
|
||||
props: async ({ auth, contact_id }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !contact_id) return fields;
|
||||
|
||||
const contact = await capsuleCrmClient.getContact(
|
||||
auth,
|
||||
contact_id as unknown as number
|
||||
);
|
||||
const emailOptions =
|
||||
contact?.emailAddresses?.map((email) => ({
|
||||
label: email.address,
|
||||
value: email.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['emailAddresses'] = Property.Array({
|
||||
displayName: 'Email Addresses',
|
||||
required: false,
|
||||
properties: {
|
||||
id: Property.StaticDropdown({
|
||||
displayName: 'Email Address',
|
||||
required: false,
|
||||
options: {
|
||||
options: emailOptions,
|
||||
},
|
||||
}),
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
address: Property.ShortText({
|
||||
displayName: 'Address',
|
||||
required: false,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the email.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
phoneNumbers: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
refreshers: ['contact_id'],
|
||||
props: async ({ auth, contact_id }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !contact_id) return fields;
|
||||
|
||||
const contact = await capsuleCrmClient.getContact(
|
||||
auth,
|
||||
contact_id as unknown as number
|
||||
);
|
||||
const phoneOptions =
|
||||
contact?.phoneNumbers?.map((phone) => ({
|
||||
label: phone.number,
|
||||
value: phone.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['phoneNumbers'] = Property.Array({
|
||||
displayName: 'Phone Numbers',
|
||||
required: false,
|
||||
properties: {
|
||||
id: Property.StaticDropdown({
|
||||
displayName: 'Phone Number',
|
||||
required: false,
|
||||
options: {
|
||||
options: phoneOptions,
|
||||
},
|
||||
}),
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Home', value: 'Home' },
|
||||
{ label: 'Work', value: 'Work' },
|
||||
{ label: 'Mobile', value: 'Mobile' },
|
||||
{ label: 'Fax', value: 'Fax' },
|
||||
{ label: 'Direct', value: 'Direct' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
number: Property.ShortText({
|
||||
displayName: 'Number',
|
||||
required: false,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the phone number.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
const contactId = propsValue.contact_id as number;
|
||||
const contactFields = propsValue.contactFields as DynamicPropsValue;
|
||||
|
||||
return await capsuleCrmClient.updateContact(auth, contactId, {
|
||||
firstName: contactFields['firstName'] as string | undefined,
|
||||
lastName: contactFields['lastName'] as string | undefined,
|
||||
name: contactFields['organisationName'] as string | undefined,
|
||||
title: contactFields['title'] as string | undefined,
|
||||
about: propsValue.about,
|
||||
ownerId: propsValue.ownerId,
|
||||
teamId: propsValue.teamId,
|
||||
addresses: (
|
||||
(propsValue.addresses as DynamicPropsValue)?.['addresses'] as any[]
|
||||
)?.map((address) => ({
|
||||
...address,
|
||||
_delete: address.delete,
|
||||
})),
|
||||
websites: (
|
||||
(propsValue.websites as DynamicPropsValue)?.['websites'] as any[]
|
||||
)?.map((website) => ({
|
||||
...website,
|
||||
_delete: website.delete,
|
||||
})),
|
||||
emailAddresses: (
|
||||
(propsValue.emailAddresses as DynamicPropsValue)?.[
|
||||
'emailAddresses'
|
||||
] as any[]
|
||||
)?.map((email) => ({
|
||||
...email,
|
||||
_delete: email.delete,
|
||||
})),
|
||||
phoneNumbers: (
|
||||
(propsValue.phoneNumbers as DynamicPropsValue)?.[
|
||||
'phoneNumbers'
|
||||
] as any[]
|
||||
)?.map((phone) => ({
|
||||
...phone,
|
||||
_delete: phone.delete,
|
||||
})),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,286 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
DynamicPropsValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
|
||||
import { capsuleCrmClient } from '../common/client';
|
||||
import {
|
||||
UpdateOpportunityParams,
|
||||
OpportunityCustomField,
|
||||
OpportunityTag,
|
||||
} from '../common/types';
|
||||
|
||||
export const updateOpportunityAction = createAction({
|
||||
auth: capsuleCrmAuth,
|
||||
name: 'update_opportunity',
|
||||
displayName: 'Update Opportunity',
|
||||
description: 'Update an existing Opportunity in Capsule CRM.',
|
||||
props: {
|
||||
opportunityId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Opportunity',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const opportunities = await capsuleCrmClient.searchOpportunities(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: opportunities.map((opportunity) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
required: false,
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
required: false,
|
||||
}),
|
||||
milestoneId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Milestone',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const milestones = await capsuleCrmClient.listMilestones(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: milestones.map((milestone) => ({
|
||||
label: milestone.name,
|
||||
value: milestone.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
currency: Property.ShortText({
|
||||
displayName: 'Currency',
|
||||
required: false,
|
||||
}),
|
||||
amount: Property.Number({
|
||||
displayName: 'Amount',
|
||||
required: false,
|
||||
}),
|
||||
expectedCloseOn: Property.DateTime({
|
||||
displayName: 'Expected Close Date',
|
||||
required: false,
|
||||
}),
|
||||
probability: Property.Number({
|
||||
displayName: 'Probability',
|
||||
required: false,
|
||||
}),
|
||||
durationBasis: Property.StaticDropdown({
|
||||
displayName: 'Duration Basis',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Fixed', value: 'FIXED' },
|
||||
{ label: 'Hour', value: 'HOUR' },
|
||||
{ label: 'Day', value: 'DAY' },
|
||||
{ label: 'Week', value: 'WEEK' },
|
||||
{ label: 'Month', value: 'MONTH' },
|
||||
{ label: 'Quarter', value: 'QUARTER' },
|
||||
{ label: 'Year', value: 'YEAR' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
duration: Property.Number({
|
||||
displayName: 'Duration',
|
||||
required: false,
|
||||
}),
|
||||
ownerId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Owner',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: users.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
teamId: Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Team',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
|
||||
const teams = await capsuleCrmClient.listTeams(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
options: teams.map((team) => ({
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
tags: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Tags',
|
||||
required: false,
|
||||
refreshers: ['opportunityId'],
|
||||
props: async ({ auth, opportunityId }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !opportunityId) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first or select an opportunity" };
|
||||
|
||||
const opportunity = await capsuleCrmClient.getOpportunity(
|
||||
auth,
|
||||
opportunityId as unknown as number
|
||||
);
|
||||
const tagOptions =
|
||||
opportunity.tags?.map((tag) => ({
|
||||
label: tag.name,
|
||||
value: tag.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['tags'] = Property.Array({
|
||||
displayName: 'Tags',
|
||||
required: false,
|
||||
properties: {
|
||||
id: Property.StaticDropdown({
|
||||
displayName: 'Tag',
|
||||
required: false,
|
||||
options: {
|
||||
options: tagOptions,
|
||||
},
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'New Tag Name',
|
||||
description: 'Enter a name to create a new tag.',
|
||||
required: false,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the tag.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
customFields: Property.DynamicProperties({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Custom Fields',
|
||||
required: false,
|
||||
refreshers: ['opportunityId'],
|
||||
props: async ({ auth, opportunityId }) => {
|
||||
const fields: DynamicPropsValue = {};
|
||||
if (!auth || !opportunityId) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first or select an opportunity" };
|
||||
|
||||
await capsuleCrmClient.getOpportunity(
|
||||
auth,
|
||||
opportunityId as unknown as number
|
||||
);
|
||||
const allCustomFields = await capsuleCrmClient.listCustomFields(
|
||||
auth
|
||||
);
|
||||
|
||||
const customFieldOptions =
|
||||
allCustomFields?.map((field) => ({
|
||||
label: field.name,
|
||||
value: field.id,
|
||||
})) ?? [];
|
||||
|
||||
fields['customFields'] = Property.Array({
|
||||
displayName: 'Custom Fields',
|
||||
required: false,
|
||||
properties: {
|
||||
definitionId: Property.StaticDropdown({
|
||||
displayName: 'Field',
|
||||
required: true,
|
||||
options: {
|
||||
options: customFieldOptions,
|
||||
},
|
||||
}),
|
||||
value: Property.ShortText({
|
||||
displayName: 'Value',
|
||||
required: true,
|
||||
}),
|
||||
delete: Property.Checkbox({
|
||||
displayName: 'Delete',
|
||||
description: 'Check this to delete the custom field value.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { auth, propsValue } = context;
|
||||
const { opportunityId } = propsValue;
|
||||
|
||||
const opportunityData: UpdateOpportunityParams = {
|
||||
name: propsValue.name,
|
||||
description: propsValue.description,
|
||||
expectedCloseOn: propsValue.expectedCloseOn,
|
||||
probability: propsValue.probability,
|
||||
durationBasis: propsValue.durationBasis,
|
||||
duration: propsValue.duration,
|
||||
};
|
||||
|
||||
if (propsValue.ownerId) {
|
||||
opportunityData.owner = { id: propsValue.ownerId };
|
||||
}
|
||||
if (propsValue.teamId) {
|
||||
opportunityData.team = { id: propsValue.teamId };
|
||||
}
|
||||
if (propsValue.milestoneId) {
|
||||
opportunityData.milestone = { id: propsValue.milestoneId };
|
||||
}
|
||||
if (propsValue.currency && propsValue.amount) {
|
||||
opportunityData.value = {
|
||||
currency: propsValue.currency,
|
||||
amount: propsValue.amount,
|
||||
};
|
||||
}
|
||||
|
||||
const tags = (propsValue.tags as { tags: OpportunityTag[] })?.tags;
|
||||
if (tags) {
|
||||
opportunityData.tags = tags.map((tag) => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
_delete: tag._delete,
|
||||
}));
|
||||
}
|
||||
|
||||
const customFields = (
|
||||
propsValue.customFields as {
|
||||
customFields: { definitionId: number; value: unknown; delete: boolean }[];
|
||||
}
|
||||
)?.customFields;
|
||||
if (customFields) {
|
||||
opportunityData.fields = customFields.map((field) => ({
|
||||
definition: { id: field.definitionId },
|
||||
value: field.value,
|
||||
_delete: field.delete,
|
||||
}));
|
||||
}
|
||||
|
||||
return await capsuleCrmClient.updateOpportunity(
|
||||
auth,
|
||||
opportunityId,
|
||||
opportunityData
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
AuthenticationType,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
export const capsuleCrmAuth = PieceAuth.OAuth2({
|
||||
description: `
|
||||
To authenticate with Capsule CRM:
|
||||
1. Go to your Capsule CRM user settings.
|
||||
2. Navigate to "My Preferences" > "API Authentication Tokens".
|
||||
3. Register a new application to get a Client ID and Client Secret.
|
||||
4. Add https://cloud.activepieces.com/redirect to the authorized redirect URIs.
|
||||
5. Use the OAuth2 flow below.`,
|
||||
authUrl: 'https://api.capsulecrm.com/oauth/authorise',
|
||||
tokenUrl: 'https://api.capsulecrm.com/oauth/token',
|
||||
required: true,
|
||||
scope: ['read', 'write'],
|
||||
validate: async ({ auth }) => {
|
||||
try {
|
||||
await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: 'https://api.capsulecrm.com/api/v2/site',
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
});
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid credentials',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export type CapsuleCrmAuthType = OAuth2PropertyValue;
|
||||
@@ -0,0 +1,778 @@
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
CreateOpportunityParams,
|
||||
CreatePartyParams,
|
||||
CreateProjectParams,
|
||||
Milestone,
|
||||
Opportunity,
|
||||
Party,
|
||||
Project,
|
||||
UpdateOpportunityParams,
|
||||
UpdatePartyParams,
|
||||
Case,
|
||||
CreateTaskParams,
|
||||
Task,
|
||||
User,
|
||||
Team,
|
||||
Tag,
|
||||
CustomField,
|
||||
CreateNoteParams,
|
||||
Note,
|
||||
FindProjectParams,
|
||||
FindOpportunityParams,
|
||||
Webhook,
|
||||
Stage,
|
||||
Category,
|
||||
ActivityType,
|
||||
CreateEntryParams,
|
||||
Entry,
|
||||
Filter,
|
||||
CreateRestHookParams,
|
||||
RestHook,
|
||||
} from './types';
|
||||
import { CapsuleCrmAuthType } from './auth';
|
||||
|
||||
export const capsuleCrmClient = {
|
||||
async createContact(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreatePartyParams
|
||||
): Promise<Party> {
|
||||
const partyData: { [key: string]: unknown } = {
|
||||
type: params.type,
|
||||
};
|
||||
|
||||
if (params.type === 'person') {
|
||||
partyData['firstName'] = params.firstName;
|
||||
partyData['lastName'] = params.lastName;
|
||||
} else {
|
||||
partyData['name'] = params.name;
|
||||
}
|
||||
|
||||
if (params.title) partyData['title'] = params.title;
|
||||
if (params.jobTitle) partyData['jobTitle'] = params.jobTitle;
|
||||
if (params.about) partyData['about'] = params.about;
|
||||
|
||||
if (params.organisationId) {
|
||||
partyData['organisation'] = { id: params.organisationId };
|
||||
}
|
||||
|
||||
if (params.ownerId) {
|
||||
partyData['owner'] = { id: params.ownerId };
|
||||
}
|
||||
if (params.teamId) {
|
||||
partyData['team'] = { id: params.teamId };
|
||||
}
|
||||
if (params.tags) {
|
||||
partyData['tags'] = params.tags.map((tag) => ({ name: tag }));
|
||||
}
|
||||
if (params.fields) {
|
||||
partyData['fields'] = params.fields;
|
||||
}
|
||||
|
||||
if (params.emailAddresses) {
|
||||
partyData['emailAddresses'] = params.emailAddresses;
|
||||
}
|
||||
if (params.phoneNumbers) {
|
||||
partyData['phoneNumbers'] = params.phoneNumbers;
|
||||
}
|
||||
if (params.addresses) {
|
||||
partyData['addresses'] = params.addresses;
|
||||
}
|
||||
if (params.websites) {
|
||||
partyData['websites'] = params.websites;
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: 'https://api.capsulecrm.com/api/v2/parties',
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
party: partyData,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<Party>(request);
|
||||
return response.body;
|
||||
},
|
||||
|
||||
async searchContacts(
|
||||
auth: CapsuleCrmAuthType,
|
||||
searchTerm: string
|
||||
): Promise<Party[]> {
|
||||
const isSearch = searchTerm && searchTerm.length > 0;
|
||||
const url = isSearch
|
||||
? `https://api.capsulecrm.com/api/v2/parties/search`
|
||||
: `https://api.capsulecrm.com/api/v2/parties`;
|
||||
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
if (isSearch) {
|
||||
queryParams['q'] = searchTerm;
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: queryParams,
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ parties: Party[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.parties;
|
||||
},
|
||||
|
||||
async searchOrganisations(
|
||||
auth: CapsuleCrmAuthType,
|
||||
searchTerm?: string
|
||||
): Promise<Party[]> {
|
||||
try {
|
||||
const url = searchTerm
|
||||
? `https://api.capsulecrm.com/api/v2/parties/search`
|
||||
: `https://api.capsulecrm.com/api/v2/parties`;
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
perPage: '100',
|
||||
};
|
||||
|
||||
if (searchTerm) {
|
||||
queryParams['q'] = searchTerm;
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: queryParams,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ parties: Party[] }>(
|
||||
request
|
||||
);
|
||||
|
||||
if (!response.body || !response.body.parties) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Temporarily return all parties to debug
|
||||
console.log('Parties response:', response.body.parties);
|
||||
const organisations = response.body.parties.filter(
|
||||
(party) => party.type === 'organisation'
|
||||
);
|
||||
console.log('Filtered organisations:', organisations);
|
||||
return organisations;
|
||||
} catch (error) {
|
||||
console.error('searchOrganisations error:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async listTeams(auth: CapsuleCrmAuthType): Promise<Team[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/teams`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ teams: Team[] }>(request);
|
||||
return response.body.teams;
|
||||
},
|
||||
|
||||
async listTags(auth: CapsuleCrmAuthType): Promise<Tag[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/parties/tags`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ tags: Tag[] }>(request);
|
||||
return response.body.tags;
|
||||
},
|
||||
|
||||
async listCustomFields(auth: CapsuleCrmAuthType): Promise<CustomField[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/parties/fields/definitions`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
definitions: CustomField[];
|
||||
}>(request);
|
||||
return response.body.definitions;
|
||||
},
|
||||
|
||||
async updateContact(
|
||||
auth: CapsuleCrmAuthType,
|
||||
partyId: number,
|
||||
params: UpdatePartyParams
|
||||
): Promise<Party> {
|
||||
const partyData: { [key: string]: unknown } = {};
|
||||
if (params.firstName) partyData['firstName'] = params.firstName;
|
||||
if (params.lastName) partyData['lastName'] = params.lastName;
|
||||
if (params.name) partyData['name'] = params.name;
|
||||
if (params.title) partyData['title'] = params.title;
|
||||
if (params.about) partyData['about'] = params.about;
|
||||
if (params.ownerId) partyData['owner'] = { id: params.ownerId };
|
||||
if (params.teamId) partyData['team'] = { id: params.teamId };
|
||||
if (params.addresses) partyData['addresses'] = params.addresses;
|
||||
if (params.websites) partyData['websites'] = params.websites;
|
||||
if (params.emailAddresses)
|
||||
partyData['emailAddresses'] = params.emailAddresses;
|
||||
if (params.phoneNumbers) partyData['phoneNumbers'] = params.phoneNumbers;
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.PUT,
|
||||
url: `https://api.capsulecrm.com/api/v2/parties/${partyId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
party: partyData,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<Party>(request);
|
||||
return response.body;
|
||||
},
|
||||
|
||||
async listMilestones(auth: CapsuleCrmAuthType): Promise<Milestone[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/milestones`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ milestones: Milestone[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.milestones;
|
||||
},
|
||||
|
||||
async searchOpportunities(
|
||||
auth: CapsuleCrmAuthType
|
||||
): Promise<Opportunity[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
opportunities: Opportunity[];
|
||||
}>(request);
|
||||
return response.body.opportunities;
|
||||
},
|
||||
|
||||
async getOpportunity(
|
||||
auth: CapsuleCrmAuthType,
|
||||
opportunityId: number
|
||||
): Promise<Opportunity> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities/${opportunityId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
embed: 'tags,fields',
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
|
||||
request
|
||||
);
|
||||
return response.body.opportunity;
|
||||
},
|
||||
|
||||
async createOpportunity(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateOpportunityParams
|
||||
): Promise<Opportunity> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
opportunity: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
|
||||
request
|
||||
);
|
||||
return response.body.opportunity;
|
||||
},
|
||||
|
||||
async listOpportunities(auth: CapsuleCrmAuthType): Promise<Opportunity[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
opportunities: Opportunity[];
|
||||
}>(request);
|
||||
return response.body.opportunities;
|
||||
},
|
||||
|
||||
async updateOpportunity(
|
||||
auth: CapsuleCrmAuthType,
|
||||
opportunityId: number,
|
||||
params: UpdateOpportunityParams
|
||||
): Promise<Opportunity> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.PUT,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities/${opportunityId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
opportunity: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
|
||||
request
|
||||
);
|
||||
return response.body.opportunity;
|
||||
},
|
||||
|
||||
async listCategories(auth: CapsuleCrmAuthType): Promise<Category[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/categories`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ categories: Category[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.categories;
|
||||
},
|
||||
|
||||
async searchProjects(auth: CapsuleCrmAuthType): Promise<Project[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kases: Project[] }>(request);
|
||||
return response.body.kases;
|
||||
},
|
||||
|
||||
async subscribeRestHook(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateRestHookParams
|
||||
): Promise<RestHook> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/resthooks`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
restHook: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ restHook: RestHook }>(
|
||||
request
|
||||
);
|
||||
return response.body.restHook;
|
||||
},
|
||||
|
||||
async unsubscribeRestHook(
|
||||
auth: CapsuleCrmAuthType,
|
||||
hookId: number
|
||||
): Promise<void> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.DELETE,
|
||||
url: `https://api.capsulecrm.com/api/v2/resthooks/${hookId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
await httpClient.sendRequest(request);
|
||||
},
|
||||
|
||||
async listActivityTypes(auth: CapsuleCrmAuthType): Promise<ActivityType[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/activitytypes`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
activityTypes: ActivityType[];
|
||||
}>(request);
|
||||
return response.body.activityTypes;
|
||||
},
|
||||
|
||||
async findContact(
|
||||
auth: CapsuleCrmAuthType,
|
||||
term: string
|
||||
): Promise<Party[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/parties/search`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
q: term,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ parties: Party[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.parties;
|
||||
},
|
||||
|
||||
async filterOpportunities(
|
||||
auth: CapsuleCrmAuthType,
|
||||
filter: Filter
|
||||
): Promise<Opportunity[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities/filters/results`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
filter: filter,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
opportunities: Opportunity[];
|
||||
}>(request);
|
||||
return response.body.opportunities;
|
||||
},
|
||||
|
||||
async filterProjects(
|
||||
auth: CapsuleCrmAuthType,
|
||||
filter: Filter
|
||||
): Promise<Project[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases/filters/results`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
filter: filter,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kases: Project[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.kases;
|
||||
},
|
||||
|
||||
async listStages(auth: CapsuleCrmAuthType): Promise<Stage[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/stages`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ stages: Stage[] }>(request);
|
||||
return response.body.stages;
|
||||
},
|
||||
|
||||
async createProject(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateProjectParams
|
||||
): Promise<Project> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
kase: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kase: Project }>(request);
|
||||
return response.body.kase;
|
||||
},
|
||||
|
||||
async listProjects(auth: CapsuleCrmAuthType): Promise<Project[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kases: Project[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.kases;
|
||||
},
|
||||
|
||||
async listCases(auth: CapsuleCrmAuthType): Promise<Case[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kases: Case[] }>(request);
|
||||
return response.body.kases;
|
||||
},
|
||||
|
||||
async listUsers(auth: CapsuleCrmAuthType): Promise<User[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/users`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ users: User[] }>(request);
|
||||
return response.body.users;
|
||||
},
|
||||
|
||||
async createEntry(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateEntryParams
|
||||
): Promise<Entry> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/entries`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
entry: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ entry: Entry }>(request);
|
||||
return response.body.entry;
|
||||
},
|
||||
|
||||
async createTask(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateTaskParams
|
||||
): Promise<Task> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/tasks`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
task: params,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ task: Task }>(request);
|
||||
return response.body.task;
|
||||
},
|
||||
|
||||
async createNote(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: CreateNoteParams
|
||||
): Promise<Note> {
|
||||
const entryData: { [key: string]: unknown } = {
|
||||
type: 'Note',
|
||||
content: params.content,
|
||||
};
|
||||
|
||||
if (params.partyId) entryData['party'] = { id: params.partyId };
|
||||
if (params.opportunityId)
|
||||
entryData['opportunity'] = { id: params.opportunityId };
|
||||
if (params.caseId) entryData['case'] = { id: params.caseId };
|
||||
if (params.projectId) entryData['project'] = { id: params.projectId };
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: 'https://api.capsulecrm.com/api/v2/entries',
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
entry: entryData,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<Note>(request);
|
||||
return response.body;
|
||||
},
|
||||
|
||||
async getContact(
|
||||
auth: CapsuleCrmAuthType,
|
||||
contactId: number
|
||||
): Promise<Party | null> {
|
||||
try {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/parties/${contactId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
embed: 'addresses,phoneNumbers,websites,emailAddresses',
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ party: Party }>(request);
|
||||
return response.body.party;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async findProject(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: FindProjectParams
|
||||
): Promise<Project[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
q: params.searchTerm,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kases: Project[] }>(
|
||||
request
|
||||
);
|
||||
return response.body.kases;
|
||||
},
|
||||
|
||||
async getProject(
|
||||
auth: CapsuleCrmAuthType,
|
||||
projectId: number
|
||||
): Promise<Project | null> {
|
||||
try {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/kases/${projectId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ kase: Project }>(request);
|
||||
return response.body.kase;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async findOpportunity(
|
||||
auth: CapsuleCrmAuthType,
|
||||
params: FindOpportunityParams
|
||||
): Promise<Opportunity[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `https://api.capsulecrm.com/api/v2/opportunities/search`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
q: params.searchTerm,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{
|
||||
opportunities: Opportunity[];
|
||||
}>(request);
|
||||
return response.body.opportunities;
|
||||
},
|
||||
|
||||
async subscribeWebhook(
|
||||
auth: CapsuleCrmAuthType,
|
||||
url: string,
|
||||
event: string
|
||||
): Promise<Webhook> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `https://api.capsulecrm.com/api/v2/webhooks`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
body: {
|
||||
webhook: {
|
||||
url: url,
|
||||
event: event,
|
||||
},
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<{ webhook: Webhook }>(
|
||||
request
|
||||
);
|
||||
return response.body.webhook;
|
||||
},
|
||||
|
||||
async unsubscribeWebhook(
|
||||
auth: CapsuleCrmAuthType,
|
||||
webhookId: number
|
||||
): Promise<void> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.DELETE,
|
||||
url: `https://api.capsulecrm.com/api/v2/webhooks/${webhookId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
await httpClient.sendRequest(request);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth, CapsuleCrmAuthType } from './auth';
|
||||
import { capsuleCrmClient } from './client';
|
||||
|
||||
export const capsuleCrmProps = {
|
||||
contact_id: (required = true) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Contact',
|
||||
description: 'The contact (Person or Organisation) to select.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async (props) => {
|
||||
const { auth } = props;
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
const searchTerm = (props['searchValue'] as string) ?? '';
|
||||
|
||||
const contacts = await capsuleCrmClient.searchContacts(
|
||||
auth,
|
||||
searchTerm
|
||||
);
|
||||
|
||||
const options = contacts.map((contact) => {
|
||||
const label =
|
||||
contact.type === 'person'
|
||||
? `${contact.firstName} ${contact.lastName}`
|
||||
: contact.name;
|
||||
return {
|
||||
label: label || 'Unnamed Contact',
|
||||
value: contact.id,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: options,
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
milestone_id: (required = true) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Milestone',
|
||||
description: 'The milestone to assign the opportunity to.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const milestones = await capsuleCrmClient.listMilestones(
|
||||
auth
|
||||
);
|
||||
const options = milestones.map((milestone) => {
|
||||
return {
|
||||
label: milestone.name,
|
||||
value: milestone.id,
|
||||
};
|
||||
});
|
||||
return {
|
||||
disabled: false,
|
||||
options: options,
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
opportunity_id: (required = true) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Opportunity',
|
||||
description: 'The opportunity to associate with this item.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const opportunities = await capsuleCrmClient.listOpportunities(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: opportunities.map((opportunity) => ({
|
||||
label: opportunity.name,
|
||||
value: opportunity.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
project_id: (required = false) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Project',
|
||||
description: 'The project to associate this item with.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const projects = await capsuleCrmClient.listProjects(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: projects.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
case_id: (required = false) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Case',
|
||||
description: 'The case to associate this item with.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const cases = await capsuleCrmClient.listCases(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: cases.map((kase) => ({ label: kase.name, value: kase.id })),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
owner_id: (required = false) =>
|
||||
Property.Dropdown({
|
||||
auth: capsuleCrmAuth,
|
||||
displayName: 'Owner',
|
||||
description: 'The user to assign the task to.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const users = await capsuleCrmClient.listUsers(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: users.map((user) => ({
|
||||
label: user.username,
|
||||
value: user.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
team_id: (required = false) =>
|
||||
Property.Dropdown({
|
||||
displayName: 'Team',
|
||||
description: 'The team to assign the contact to.',
|
||||
required: required,
|
||||
refreshers: [],
|
||||
auth: capsuleCrmAuth,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const teams = await capsuleCrmClient.listTeams(
|
||||
auth
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: teams.map((team) => ({
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
})),
|
||||
};
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
createTrigger,
|
||||
TriggerStrategy,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { capsuleCrmAuth } from './auth';
|
||||
import { capsuleCrmClient } from './client';
|
||||
|
||||
type TriggerParams = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
event: string;
|
||||
sampleData: unknown;
|
||||
};
|
||||
|
||||
export const capsuleCrmCreateTrigger = ({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
event,
|
||||
sampleData,
|
||||
}: TriggerParams) => {
|
||||
return createTrigger({
|
||||
auth: capsuleCrmAuth,
|
||||
name: name,
|
||||
displayName: displayName,
|
||||
description: description,
|
||||
props: {},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: sampleData,
|
||||
async onEnable(context) {
|
||||
const hook = await capsuleCrmClient.subscribeRestHook(context.auth, {
|
||||
targetUrl: context.webhookUrl,
|
||||
event: event,
|
||||
description: `Activepieces - ${displayName}`,
|
||||
});
|
||||
await context.store.put(`capsule_crm_trigger_${name}`, {
|
||||
hookId: hook.id,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const storedData = await context.store.get<{ hookId: number }>(
|
||||
`capsule_crm_trigger_${name}`
|
||||
);
|
||||
if (storedData) {
|
||||
await capsuleCrmClient.unsubscribeRestHook(context.auth, storedData.hookId);
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const body = context.payload.body as { payload: unknown[] };
|
||||
return body.payload;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,337 @@
|
||||
export interface Party {
|
||||
id: number;
|
||||
type: 'person' | 'organisation';
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
name?: string;
|
||||
addresses?: {
|
||||
id: number;
|
||||
street: string;
|
||||
city: string;
|
||||
}[];
|
||||
websites?: {
|
||||
id: number;
|
||||
address: string;
|
||||
}[];
|
||||
emailAddresses?: {
|
||||
id: number;
|
||||
address: string;
|
||||
}[];
|
||||
phoneNumbers?: {
|
||||
id: number;
|
||||
number: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface CreatePartyParams {
|
||||
type: 'person' | 'organisation';
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
name?: string;
|
||||
emailAddresses?: {
|
||||
type?: string;
|
||||
address: string;
|
||||
}[];
|
||||
phoneNumbers?: {
|
||||
type?: string;
|
||||
number: string;
|
||||
}[];
|
||||
title?: string;
|
||||
jobTitle?: string;
|
||||
about?: string;
|
||||
organisationId?: number;
|
||||
ownerId?: number;
|
||||
teamId?: number;
|
||||
tags?: string[];
|
||||
fields?: { definition: { id: number }; value: unknown }[];
|
||||
addresses?: {
|
||||
type?: string;
|
||||
street?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
zip?: string;
|
||||
}[];
|
||||
websites?: {
|
||||
type?: string;
|
||||
service: string;
|
||||
address: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface UpdatePartyParams {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
about?: string;
|
||||
ownerId?: number;
|
||||
teamId?: number;
|
||||
addresses?: {
|
||||
id?: number;
|
||||
type?: string;
|
||||
street?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
zip?: string;
|
||||
_delete?: boolean;
|
||||
}[];
|
||||
websites?: {
|
||||
id?: number;
|
||||
type?: string;
|
||||
service?: string;
|
||||
address?: string;
|
||||
_delete?: boolean;
|
||||
}[];
|
||||
emailAddresses?: {
|
||||
id?: number;
|
||||
type?: string;
|
||||
address?: string;
|
||||
_delete?: boolean;
|
||||
}[];
|
||||
phoneNumbers?: {
|
||||
id?: number;
|
||||
type?: string;
|
||||
number?: string;
|
||||
_delete?: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Milestone {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
probability: number;
|
||||
complete: boolean;
|
||||
daysUntilStale: number;
|
||||
}
|
||||
|
||||
export interface OpportunityValue {
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface OpportunityCustomField {
|
||||
id?: number;
|
||||
definition: {
|
||||
id: number;
|
||||
};
|
||||
value: unknown;
|
||||
_delete?: boolean;
|
||||
}
|
||||
|
||||
export interface OpportunityTag {
|
||||
id?: number;
|
||||
name: string;
|
||||
_delete?: boolean;
|
||||
}
|
||||
|
||||
export interface Opportunity extends CreateOpportunityParams {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tags?: OpportunityTag[];
|
||||
fields?: OpportunityCustomField[];
|
||||
}
|
||||
|
||||
export interface Stage {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CreateProjectParams {
|
||||
party: { id: number };
|
||||
name: string;
|
||||
description?: string;
|
||||
owner?: { id: number };
|
||||
team?: { id: number };
|
||||
status?: 'OPEN' | 'CLOSED';
|
||||
opportunity?: { id: number };
|
||||
stage?: { id: number };
|
||||
expectedCloseOn?: string;
|
||||
tags?: OpportunityTag[];
|
||||
fields?: OpportunityCustomField[];
|
||||
}
|
||||
|
||||
export interface Project extends CreateProjectParams {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
closedOn?: string;
|
||||
}
|
||||
|
||||
export interface CreateOpportunityParams {
|
||||
name: string;
|
||||
party: {
|
||||
id: number;
|
||||
};
|
||||
milestone: {
|
||||
id: number;
|
||||
};
|
||||
description?: string;
|
||||
owner?: { id: number };
|
||||
team?: { id: number };
|
||||
value?: OpportunityValue;
|
||||
expectedCloseOn?: string;
|
||||
probability?: number;
|
||||
durationBasis?: string;
|
||||
duration?: number;
|
||||
tags?: OpportunityTag[];
|
||||
fields?: OpportunityCustomField[];
|
||||
}
|
||||
|
||||
export interface UpdateOpportunityParams {
|
||||
name?: string;
|
||||
description?: string;
|
||||
owner?: { id: number };
|
||||
team?: { id: number };
|
||||
milestone?: { id: number };
|
||||
value?: OpportunityValue;
|
||||
expectedCloseOn?: string;
|
||||
probability?: number;
|
||||
durationBasis?: string;
|
||||
duration?: number;
|
||||
tags?: OpportunityTag[];
|
||||
fields?: OpportunityCustomField[];
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Case {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Team {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number;
|
||||
name: string;
|
||||
dataTag: boolean;
|
||||
}
|
||||
|
||||
export interface CustomField {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: number;
|
||||
type: 'Task';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface CreateTaskParams {
|
||||
description: string;
|
||||
dueOn: string;
|
||||
detail?: string;
|
||||
category?: { id: number };
|
||||
owner?: { id: number };
|
||||
party?: { id: number };
|
||||
opportunity?: { id: number };
|
||||
kase?: { id: number };
|
||||
dueTime?: string;
|
||||
}
|
||||
|
||||
export interface Task extends CreateTaskParams {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
status: 'OPEN' | 'COMPLETED' | 'PENDING';
|
||||
}
|
||||
|
||||
export interface ActivityType {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface CreateEntryParams {
|
||||
type: 'note';
|
||||
content: string;
|
||||
activityType?: { id: number };
|
||||
party?: { id: number };
|
||||
opportunity?: { id: number };
|
||||
kase?: { id: number };
|
||||
}
|
||||
|
||||
export interface Entry extends CreateEntryParams {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
id: number;
|
||||
type: 'Note';
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface CreateNoteParams {
|
||||
content: string;
|
||||
partyId?: number;
|
||||
opportunityId?: number;
|
||||
caseId?: number;
|
||||
projectId?: number;
|
||||
}
|
||||
|
||||
export interface FindContactParams {
|
||||
email?: string;
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
||||
export interface FindProjectParams {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export interface FindOpportunityParams {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
id: number;
|
||||
url: string;
|
||||
event: string;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
colour: string;
|
||||
}
|
||||
|
||||
export interface FilterCondition {
|
||||
field: string;
|
||||
operator: string;
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
conditions: FilterCondition[];
|
||||
}
|
||||
|
||||
export interface RestHook {
|
||||
id: number;
|
||||
event: string;
|
||||
targetUrl: string;
|
||||
}
|
||||
|
||||
export interface CreateRestHookParams {
|
||||
event: string;
|
||||
targetUrl: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { capsuleCrmCreateTrigger } from '../common/trigger';
|
||||
|
||||
export const newCaseTrigger = capsuleCrmCreateTrigger({
|
||||
name: 'new_case',
|
||||
displayName: 'New Case',
|
||||
description: 'Fires when a new case (project) is created in Capsule CRM.',
|
||||
event: 'kase/created',
|
||||
sampleData: {
|
||||
event: 'kase/created',
|
||||
payload: [
|
||||
{
|
||||
id: 12,
|
||||
party: {
|
||||
id: 892,
|
||||
type: 'organisation',
|
||||
name: 'Zestia',
|
||||
pictureURL:
|
||||
'https://capsulecrm.com/theme/default/images/org_avatar_70.png',
|
||||
},
|
||||
owner: {
|
||||
id: 61,
|
||||
username: 'ted',
|
||||
name: 'Ted Danson',
|
||||
},
|
||||
status: 'OPEN',
|
||||
stage: {
|
||||
name: 'Project Brief',
|
||||
id: 149,
|
||||
},
|
||||
createdAt: '2015-12-07T16:54:27Z',
|
||||
updatedAt: '2015-12-07T16:54:27Z',
|
||||
expectedCloseOn: '2015-12-09',
|
||||
description: 'Scope and design web site shopping cart',
|
||||
name: 'Consulting',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { capsuleCrmCreateTrigger } from '../common/trigger';
|
||||
|
||||
export const newOpportunityTrigger = capsuleCrmCreateTrigger({
|
||||
name: 'new_opportunity',
|
||||
displayName: 'New Opportunity',
|
||||
description: 'Fires when a new opportunity is created.',
|
||||
event: 'opportunity/created',
|
||||
sampleData: {
|
||||
event: 'opportunity/created',
|
||||
payload: [
|
||||
{
|
||||
id: 83948362,
|
||||
updatedAt: '2015-10-29T12:55:12Z',
|
||||
description: 'Scope and design web site shopping cart',
|
||||
owner: {
|
||||
id: 6,
|
||||
username: 'scottspacey',
|
||||
name: 'Scott Spacey',
|
||||
},
|
||||
party: {
|
||||
id: 581,
|
||||
pictureURL:
|
||||
'https://capsulecrm.com/theme/default/images/person_avatar_70.png',
|
||||
type: 'organisation',
|
||||
name: 'Capsule',
|
||||
},
|
||||
lostReason: null,
|
||||
milestone: {
|
||||
id: 14,
|
||||
name: 'Bid',
|
||||
},
|
||||
value: {
|
||||
amount: 500,
|
||||
currency: 'GBP',
|
||||
},
|
||||
expectedCloseOn: '2015-10-31',
|
||||
probability: 50,
|
||||
durationBasis: 'FIXED',
|
||||
duration: null,
|
||||
closedOn: null,
|
||||
createdAt: '2015-10-29T12:55:12Z',
|
||||
name: 'Consulting',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { capsuleCrmCreateTrigger } from '../common/trigger';
|
||||
|
||||
export const newProjectTrigger = capsuleCrmCreateTrigger({
|
||||
name: 'new_project',
|
||||
displayName: 'New Project',
|
||||
description: 'Fires when a project is created.',
|
||||
event: 'kase/created',
|
||||
sampleData: {
|
||||
event: 'kase/created',
|
||||
payload: [
|
||||
{
|
||||
id: 12,
|
||||
party: {
|
||||
id: 892,
|
||||
type: 'organisation',
|
||||
name: 'Zestia',
|
||||
pictureURL:
|
||||
'https://capsulecrm.com/theme/default/images/org_avatar_70.png',
|
||||
},
|
||||
owner: {
|
||||
id: 61,
|
||||
username: 'ted',
|
||||
name: 'Ted Danson',
|
||||
},
|
||||
status: 'OPEN',
|
||||
stage: {
|
||||
name: 'Project Brief',
|
||||
id: 149,
|
||||
},
|
||||
createdAt: '2015-12-07T16:54:27Z',
|
||||
updatedAt: '2015-12-07T16:54:27Z',
|
||||
expectedCloseOn: '2015-12-09',
|
||||
description: 'Scope and design web site shopping cart',
|
||||
name: 'Consulting',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { capsuleCrmCreateTrigger } from '../common/trigger';
|
||||
|
||||
export const newTaskTrigger = capsuleCrmCreateTrigger({
|
||||
name: 'new_task',
|
||||
displayName: 'New Task',
|
||||
description: 'Fires when a new task is created.',
|
||||
event: 'task/created',
|
||||
sampleData: {
|
||||
event: 'task/created',
|
||||
payload: [
|
||||
{
|
||||
id: 530,
|
||||
description: 'Email product details',
|
||||
dueTime: '18:00:00',
|
||||
status: 'OPEN',
|
||||
party: {
|
||||
id: 11587,
|
||||
type: 'person',
|
||||
firstName: 'Scott',
|
||||
lastName: 'Spacey',
|
||||
pictureURL:
|
||||
'https://capsulecrm.com/theme/default/images/person_avatar_70.png',
|
||||
},
|
||||
owner: {
|
||||
id: 1,
|
||||
username: 'john',
|
||||
name: 'John Spacey',
|
||||
},
|
||||
createdAt: '2015-12-21T13:51:38Z',
|
||||
updatedAt: '2015-12-21T13:51:38Z',
|
||||
dueOn: '2014-05-20',
|
||||
hasTrack: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user