Skip to content

Commit

Permalink
HCM Embed and FileFeed (#29)
Browse files Browse the repository at this point in the history
* HCM Embed and FileFeed

* Use new sync file feed route

* Fix number rounding

* Use webhook instead of v2

* Coderabbit fix and comment about filefeed
  • Loading branch information
gaelyn authored May 24, 2024
1 parent 2ad87ab commit c5a16ae
Show file tree
Hide file tree
Showing 9 changed files with 659 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import { CUSTOMERS_SHEET_NAME } from '@/workflows/fieldServices/blueprints/custo
import { hcmProjectSpaceConfigure } from './workflows/hcm/actions/hcmProjectSpaceConfigure';
import { hcmPrefillData } from '@/shared/eventHandlers/hcmPrefillData';
import { HcmShowApiService } from '@/shared/hcm-show-api-service';
import { hcmEmbeddedSpaceConfigure } from '@/workflows/hcm/actions/hcmEmbeddedSpaceConfigure';
import { hcmFileFeedSpaceConfigure } from '@/workflows/hcm/actions/hcmFileFeedSpaceConfigure';
import { BENEFITS_SHEET_NAME } from '@/workflows/hcm/blueprints/benefits';

function configureSharedUses({
listener,
Expand Down Expand Up @@ -141,5 +144,36 @@ export default function (listener: FlatfileListener) {
configureSharedUses({ listener, apiService: HcmShowApiService });
});

listener.namespace('space:hcmembedded', (listener) => {
listener.use(hcmEmbeddedSpaceConfigure);
listener.use(hcmPrefillData());
configureSharedUses({ listener, apiService: HcmShowApiService });
});

// TODO: filefeed hook could be abstratced into a use function
listener.namespace('space:hcmfilefeed', (listener) => {
listener.use(hcmFileFeedSpaceConfigure);
configureSharedUses({ listener, apiService: HcmShowApiService });

listener.use(
filefeedAutomap({
apiService: HcmShowApiService,
matchFilename: /^benefits.*$/i,
defaultTargetSheet: BENEFITS_SHEET_NAME,
})
);

listener.on('**', (event) => {
// Send certain filefeed events to hcm.show
if (
event.topic.includes('records:') ||
(event.topic === 'job:completed' &&
event?.payload?.status === 'complete')
) {
HcmShowApiService.sendFilefeedEvent(event);
}
});
});

// Add more namespace configurations as needed)
}
21 changes: 21 additions & 0 deletions src/shared/externalContraints/externalConstraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,25 @@ export const externalConstraints = {
}
},
},
number: {
validator: (value, key, { config, record }) => {
if (value) {
const numericValue = Number(value);
if (Number.isNaN(numericValue)) {
record.addError(
key,
`The field "${key}" should be a number. Please enter a valid numeric value, using a dot (.) as a decimal separator.`
);
} else {
const roundedValue = Number.parseFloat(value).toFixed(
config.decimalPlaces
);
if (value !== roundedValue) {
record.set(key, roundedValue);
record.addInfo(key, `${key} has been rounded to 2 decimal places.`);
}
}
}
},
},
};
30 changes: 30 additions & 0 deletions src/shared/hcm-show-api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,34 @@ export class HcmShowApiService {

return employees;
};

static sendFilefeedEvent = async (event: FlatfileEvent) => {
console.log('Sending filefeed event to hcm.show.');

const { spaceId } = event.context;
const topic = event.payload.job || event.topic;

if (!topic) {
return;
}

const apiBaseUrl = await event.secrets('HCM_API_BASE_URL');
const url = `${apiBaseUrl}/api/v1/sync-file-feed`;

let response;

try {
response = await axios.post(
url,
{ spaceId, topic },
{ headers: await this.headers(event) }
);

if (response.status !== 200) {
throw new Error(`response status was ${response.status}`);
}
} catch (error) {
console.error(`Failed to send filefeed event: ${error.message}`);
}
};
}
54 changes: 54 additions & 0 deletions src/workflows/hcm/actions/hcmEmbeddedSpaceConfigure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { configureSpace } from '@flatfile/plugin-space-configure';
import * as hcmBlueprints from '@/workflows/hcm/blueprints/_index';
import { FlatfileListener } from '@flatfile/listener';
import { embeddedSpaceTheme } from '@/workflows/themes/embedded-space-theme';
import { embeddedSpaceDocument } from '@/workflows/plm/documents/embedded-space-document';
import api from '@flatfile/api';
import { HCM_WORKBOOK_NAME } from '@/shared/constants';

export function hcmEmbeddedSpaceConfigure(listener: FlatfileListener) {
listener.use(
configureSpace(
{
documents: [embeddedSpaceDocument],
workbooks: [
{
name: HCM_WORKBOOK_NAME,
namespace: 'hcmImport',
sheets: [hcmBlueprints.benefits],
actions: [
{
operation: 'submitAction',
mode: 'foreground',
constraints: [{ type: 'hasData' }],
label: 'Push records to HCM.show',
primary: true,
},
],
},
],
},
async (event) => {
const { spaceId } = event.context;
const documents = await api.documents.list(spaceId);

// Get the first documentId
const documentId =
documents.data.length > 0 ? documents.data[0]['id'] : null;

// Update the space adding theme and setting the documentId as the default page
await api.spaces.update(spaceId, {
metadata: {
sidebarConfig: {
showSidebar: true,
defaultPage: {
documentId,
},
},
theme: embeddedSpaceTheme,
},
});
}
)
);
}
57 changes: 57 additions & 0 deletions src/workflows/hcm/actions/hcmFileFeedSpaceConfigure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { configureSpace } from '@flatfile/plugin-space-configure';
import * as hcmBlueprints from '../blueprints/_index';
import { FlatfileListener } from '@flatfile/listener';
import { modifySheet } from '../../../shared/helpers/modifySheet';
import { fileFeedSpaceTheme } from '@/workflows/themes/file-feed-space-theme';
import { fileFeedSpaceDocument } from '@/workflows/hcm/documents/file-feed-space-document';
import api from '@flatfile/api';
import { HCM_WORKBOOK_NAME } from '@/shared/constants';

const modifiedBenefits = modifySheet(hcmBlueprints.benefits);

export function hcmFileFeedSpaceConfigure(listener: FlatfileListener) {
listener.use(
configureSpace(
{
documents: [fileFeedSpaceDocument],
workbooks: [
{
name: HCM_WORKBOOK_NAME,
namespace: 'hcmImport',
sheets: [modifiedBenefits],
actions: [
{
operation: 'submitAction',
mode: 'foreground',
constraints: [{ type: 'hasData' }],
label: 'Submit Data',
primary: true,
},
],
},
],
},
async (event) => {
const { spaceId } = event.context;
const documents = await api.documents.list(spaceId);

// Get the first documentId
const documentId =
documents.data.length > 0 ? documents.data[0]['id'] : null;

// Update the space adding theme and setting the documentId as the default page
await api.spaces.update(spaceId, {
metadata: {
sidebarConfig: {
showSidebar: true,
defaultPage: {
documentId,
},
},
theme: fileFeedSpaceTheme,
},
});
}
)
);
}
1 change: 1 addition & 0 deletions src/workflows/hcm/blueprints/_index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './benefits';
export * from './departments';
export * from './employees';
export * from './jobs';
117 changes: 117 additions & 0 deletions src/workflows/hcm/blueprints/benefits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { SheetConfig } from '@flatfile/api/api';

export const BENEFITS_SHEET_NAME = 'Benefit Elections';

export const benefits: SheetConfig = {
name: BENEFITS_SHEET_NAME,
slug: 'benefit-elections-sheet',
readonly: false,
fields: [
//Validate against exisitng Employess in DB. If not found, throw an error in Flatfile. Open question around whether this could be a ReferenceField with a lookup to the Employee table. What should happen if an Emplpyee is not found? Should we create a new Employee record in Flatfile or should that occur in HCM.Show?

{
key: 'employeeId',
label: 'Employee ID',
description: 'Employee ID for existing Employee in HCM.Show.',
type: 'string',
constraints: [{ type: 'required' }],
},

// Validate against exisitng benefit plans in DB. If not found, throw an error in Flatfile. Open question around whether this could be a ReferenceField with a lookup to the Benefit Plan table. What should happen if a Benefit Plan is not found? Should we create a new Benefit Plan record in Flatfile or should that occur in HCM.Show?

{
key: 'benefitPlan',
label: 'Benefit Plan',
description: 'Benefit Plan for existing Benefit Plan in HCM.Show.',
type: 'string',
constraints: [{ type: 'required' }],
},

//Required checkbox → “required: true” validation

{
key: 'currentlyEnrolled',
label: 'Currently Enrolled',
description: 'Is the employee currently enrolled in this benefit plan?',
type: 'boolean',
constraints: [
{ type: 'required' },
{ type: 'external', validator: 'boolean' },
],
},

//Date fields have a date format selection → updated target date format for SmartDateField

{
key: 'coverageStartDate',
label: 'Coverage Start Date',
description:
'Date coverage begins for this benefit plan. Must be formatted as yyyy-MM-dd',
type: 'date',
constraints: [
{ type: 'required' },
{
type: 'external',
validator: 'date',
config: { format: 'yyyy-MM-dd' },
},
],
},

//Round to two decimal places → validation / compute on Number fields for decimal places (trim and validate)

{
key: 'employerContribution',
label: 'Employer Contribution',
type: 'string',
description:
'Employer contribution for this benefit plan per plan frequency.',
constraints: [
{ type: 'required' },
{ type: 'external', validator: 'number', config: { decimalPlaces: 2 } },
],
},

{
key: 'benefitCoverageType',
label: 'Benefit Coverage Type',
type: 'enum',
description:
'Indicates the type of insurance, retirement savings, or other benefits that are provided by an employer to an employee.',
constraints: [{ type: 'required' }],
config: {
options: [
{
value: 'Insurance_Coverage_Type_Insurance',
label: 'Insurance',
},

{
value: 'Health_Care_Coverage_Type_Medical',
label: 'Medical',
},
{
value: 'Health_Care_Coverage_Type_Dental',
label: 'Dental',
},
{
value: 'Retirement_Savings_Coverage_Type_Retirement',
label: 'Retirement',
},
{
value: 'Additional_Benefits_Coverage_Type_Other',
label: 'Other',
},
],
},
},
],
actions: [
{
operation: 'dedupe-benefit-elections',
mode: 'background',
label: 'Dedupe benefit elections',
description: 'Remove duplicate benefit elections',
},
],
};
Loading

0 comments on commit c5a16ae

Please sign in to comment.