mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(workday): block + tools (#3663)
* checkpoint workday block * add icon svg * fix workday to use soap api * fix SOAP API * address comments * fix * more type fixes * address more comments * fix files * fix file editor useEffect * fix build issue * fix typing * fix test
This commit is contained in:
committed by
GitHub
parent
12908c14be
commit
bc111a6d5c
@@ -102,7 +102,7 @@ async function handleLocalFile(filename: string, userId: string): Promise<NextRe
|
||||
throw new FileNotFoundError(`File not found: ${filename}`)
|
||||
}
|
||||
|
||||
const filePath = findLocalFile(filename)
|
||||
const filePath = await findLocalFile(filename)
|
||||
|
||||
if (!filePath) {
|
||||
throw new FileNotFoundError(`File not found: ${filename}`)
|
||||
@@ -228,7 +228,7 @@ async function handleCloudProxyPublic(
|
||||
|
||||
async function handleLocalFilePublic(filename: string): Promise<NextResponse> {
|
||||
try {
|
||||
const filePath = findLocalFile(filename)
|
||||
const filePath = await findLocalFile(filename)
|
||||
|
||||
if (!filePath) {
|
||||
throw new FileNotFoundError(`File not found: ${filename}`)
|
||||
|
||||
@@ -75,7 +75,7 @@ export async function POST(request: NextRequest) {
|
||||
const uploadResults = []
|
||||
|
||||
for (const file of files) {
|
||||
const originalName = file.name || 'untitled'
|
||||
const originalName = file.name || 'untitled.md'
|
||||
|
||||
if (!validateFileExtension(originalName)) {
|
||||
const extension = originalName.split('.').pop()?.toLowerCase() || 'unknown'
|
||||
|
||||
@@ -331,7 +331,7 @@ describe('extractFilename', () => {
|
||||
|
||||
describe('findLocalFile - Path Traversal Security Tests', () => {
|
||||
describe('path traversal attack prevention', () => {
|
||||
it.concurrent('should reject classic path traversal attacks', () => {
|
||||
it.concurrent('should reject classic path traversal attacks', async () => {
|
||||
const maliciousInputs = [
|
||||
'../../../etc/passwd',
|
||||
'..\\..\\..\\windows\\system32\\config\\sam',
|
||||
@@ -340,35 +340,35 @@ describe('findLocalFile - Path Traversal Security Tests', () => {
|
||||
'..\\config.ini',
|
||||
]
|
||||
|
||||
maliciousInputs.forEach((input) => {
|
||||
const result = findLocalFile(input)
|
||||
for (const input of maliciousInputs) {
|
||||
const result = await findLocalFile(input)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it.concurrent('should reject encoded path traversal attempts', () => {
|
||||
it.concurrent('should reject encoded path traversal attempts', async () => {
|
||||
const encodedInputs = [
|
||||
'%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64', // ../../../etc/passwd
|
||||
'..%2f..%2fetc%2fpasswd',
|
||||
'..%5c..%5cconfig.ini',
|
||||
]
|
||||
|
||||
encodedInputs.forEach((input) => {
|
||||
const result = findLocalFile(input)
|
||||
for (const input of encodedInputs) {
|
||||
const result = await findLocalFile(input)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it.concurrent('should reject mixed path separators', () => {
|
||||
it.concurrent('should reject mixed path separators', async () => {
|
||||
const mixedInputs = ['../..\\config.txt', '..\\../secret.ini', '/..\\..\\system32']
|
||||
|
||||
mixedInputs.forEach((input) => {
|
||||
const result = findLocalFile(input)
|
||||
for (const input of mixedInputs) {
|
||||
const result = await findLocalFile(input)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it.concurrent('should reject filenames with dangerous characters', () => {
|
||||
it.concurrent('should reject filenames with dangerous characters', async () => {
|
||||
const dangerousInputs = [
|
||||
'file:with:colons.txt',
|
||||
'file|with|pipes.txt',
|
||||
@@ -376,43 +376,45 @@ describe('findLocalFile - Path Traversal Security Tests', () => {
|
||||
'file*with*asterisks.txt',
|
||||
]
|
||||
|
||||
dangerousInputs.forEach((input) => {
|
||||
const result = findLocalFile(input)
|
||||
for (const input of dangerousInputs) {
|
||||
const result = await findLocalFile(input)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it.concurrent('should reject null and empty inputs', () => {
|
||||
expect(findLocalFile('')).toBeNull()
|
||||
expect(findLocalFile(' ')).toBeNull()
|
||||
expect(findLocalFile('\t\n')).toBeNull()
|
||||
it.concurrent('should reject null and empty inputs', async () => {
|
||||
expect(await findLocalFile('')).toBeNull()
|
||||
expect(await findLocalFile(' ')).toBeNull()
|
||||
expect(await findLocalFile('\t\n')).toBeNull()
|
||||
})
|
||||
|
||||
it.concurrent('should reject filenames that become empty after sanitization', () => {
|
||||
it.concurrent('should reject filenames that become empty after sanitization', async () => {
|
||||
const emptyAfterSanitization = ['../..', '..\\..\\', '////', '....', '..']
|
||||
|
||||
emptyAfterSanitization.forEach((input) => {
|
||||
const result = findLocalFile(input)
|
||||
for (const input of emptyAfterSanitization) {
|
||||
const result = await findLocalFile(input)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('security validation passes for legitimate files', () => {
|
||||
it.concurrent('should accept properly formatted filenames without throwing errors', () => {
|
||||
const legitimateInputs = [
|
||||
'document.pdf',
|
||||
'image.png',
|
||||
'data.csv',
|
||||
'report-2024.doc',
|
||||
'file_with_underscores.txt',
|
||||
'file-with-dashes.json',
|
||||
]
|
||||
it.concurrent(
|
||||
'should accept properly formatted filenames without throwing errors',
|
||||
async () => {
|
||||
const legitimateInputs = [
|
||||
'document.pdf',
|
||||
'image.png',
|
||||
'data.csv',
|
||||
'report-2024.doc',
|
||||
'file_with_underscores.txt',
|
||||
'file-with-dashes.json',
|
||||
]
|
||||
|
||||
legitimateInputs.forEach((input) => {
|
||||
// Should not throw security errors for legitimate filenames
|
||||
expect(() => findLocalFile(input)).not.toThrow()
|
||||
})
|
||||
})
|
||||
for (const input of legitimateInputs) {
|
||||
await expect(findLocalFile(input)).resolves.toBeDefined()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { existsSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { UPLOAD_DIR } from '@/lib/uploads/config'
|
||||
import { sanitizeFileKey } from '@/lib/uploads/utils/file-utils'
|
||||
|
||||
const logger = createLogger('FilesUtils')
|
||||
@@ -123,76 +120,29 @@ export function extractFilename(path: string): string {
|
||||
return filename
|
||||
}
|
||||
|
||||
function sanitizeFilename(filename: string): string {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw new Error('Invalid filename provided')
|
||||
}
|
||||
|
||||
if (!filename.includes('/')) {
|
||||
throw new Error('File key must include a context prefix (e.g., kb/, workspace/, execution/)')
|
||||
}
|
||||
|
||||
const segments = filename.split('/')
|
||||
|
||||
const sanitizedSegments = segments.map((segment) => {
|
||||
if (segment === '..' || segment === '.') {
|
||||
throw new Error('Path traversal detected')
|
||||
}
|
||||
|
||||
const sanitized = segment.replace(/\.\./g, '').replace(/[\\]/g, '').replace(/^\./g, '').trim()
|
||||
|
||||
if (!sanitized) {
|
||||
throw new Error('Invalid or empty path segment after sanitization')
|
||||
}
|
||||
|
||||
if (
|
||||
sanitized.includes(':') ||
|
||||
sanitized.includes('|') ||
|
||||
sanitized.includes('?') ||
|
||||
sanitized.includes('*') ||
|
||||
sanitized.includes('\x00') ||
|
||||
/[\x00-\x1F\x7F]/.test(sanitized)
|
||||
) {
|
||||
throw new Error('Path segment contains invalid characters')
|
||||
}
|
||||
|
||||
return sanitized
|
||||
})
|
||||
|
||||
return sanitizedSegments.join(path.sep)
|
||||
}
|
||||
|
||||
export function findLocalFile(filename: string): string | null {
|
||||
export async function findLocalFile(filename: string): Promise<string | null> {
|
||||
try {
|
||||
const sanitizedFilename = sanitizeFileKey(filename)
|
||||
|
||||
// Reject if sanitized filename is empty or only contains path separators/dots
|
||||
if (!sanitizedFilename || !sanitizedFilename.trim() || /^[/\\.\s]+$/.test(sanitizedFilename)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const possiblePaths = [
|
||||
path.join(UPLOAD_DIR, sanitizedFilename),
|
||||
path.join(process.cwd(), 'uploads', sanitizedFilename),
|
||||
]
|
||||
const { existsSync } = await import('fs')
|
||||
const path = await import('path')
|
||||
const { UPLOAD_DIR_SERVER } = await import('@/lib/uploads/core/setup.server')
|
||||
|
||||
for (const filePath of possiblePaths) {
|
||||
const resolvedPath = path.resolve(filePath)
|
||||
const allowedDirs = [path.resolve(UPLOAD_DIR), path.resolve(process.cwd(), 'uploads')]
|
||||
const resolvedPath = path.join(UPLOAD_DIR_SERVER, sanitizedFilename)
|
||||
|
||||
// Must be within allowed directory but NOT the directory itself
|
||||
const isWithinAllowedDir = allowedDirs.some(
|
||||
(allowedDir) =>
|
||||
resolvedPath.startsWith(allowedDir + path.sep) && resolvedPath !== allowedDir
|
||||
)
|
||||
if (
|
||||
!resolvedPath.startsWith(UPLOAD_DIR_SERVER + path.sep) ||
|
||||
resolvedPath === UPLOAD_DIR_SERVER
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isWithinAllowedDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (existsSync(resolvedPath)) {
|
||||
return resolvedPath
|
||||
}
|
||||
if (existsSync(resolvedPath)) {
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
67
apps/sim/app/api/tools/workday/assign-onboarding/route.ts
Normal file
67
apps/sim/app/api/tools/workday/assign-onboarding/route.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayAssignOnboardingAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
onboardingPlanId: z.string().min(1),
|
||||
actionEventId: z.string().min(1),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Put_Onboarding_Plan_AssignmentAsync({
|
||||
Onboarding_Plan_Assignment_Data: {
|
||||
Onboarding_Plan_Reference: wdRef('Onboarding_Plan_ID', data.onboardingPlanId),
|
||||
Person_Reference: wdRef('WID', data.workerId),
|
||||
Action_Event_Reference: wdRef('Background_Check_ID', data.actionEventId),
|
||||
Assignment_Effective_Moment: new Date().toISOString(),
|
||||
Active: true,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
assignmentId: extractRefId(result?.Onboarding_Plan_Assignment_Reference),
|
||||
workerId: data.workerId,
|
||||
planId: data.onboardingPlanId,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday assign onboarding failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
94
apps/sim/app/api/tools/workday/change-job/route.ts
Normal file
94
apps/sim/app/api/tools/workday/change-job/route.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayChangeJobAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
effectiveDate: z.string().min(1),
|
||||
newPositionId: z.string().optional(),
|
||||
newJobProfileId: z.string().optional(),
|
||||
newLocationId: z.string().optional(),
|
||||
newSupervisoryOrgId: z.string().optional(),
|
||||
reason: z.string().min(1, 'Reason is required for job changes'),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const changeJobDetailData: Record<string, unknown> = {
|
||||
Reason_Reference: wdRef('Change_Job_Subcategory_ID', data.reason),
|
||||
}
|
||||
if (data.newPositionId) {
|
||||
changeJobDetailData.Position_Reference = wdRef('Position_ID', data.newPositionId)
|
||||
}
|
||||
if (data.newJobProfileId) {
|
||||
changeJobDetailData.Job_Profile_Reference = wdRef('Job_Profile_ID', data.newJobProfileId)
|
||||
}
|
||||
if (data.newLocationId) {
|
||||
changeJobDetailData.Location_Reference = wdRef('Location_ID', data.newLocationId)
|
||||
}
|
||||
if (data.newSupervisoryOrgId) {
|
||||
changeJobDetailData.Supervisory_Organization_Reference = wdRef(
|
||||
'Supervisory_Organization_ID',
|
||||
data.newSupervisoryOrgId
|
||||
)
|
||||
}
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'staffing',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Change_JobAsync({
|
||||
Business_Process_Parameters: {
|
||||
Auto_Complete: true,
|
||||
Run_Now: true,
|
||||
},
|
||||
Change_Job_Data: {
|
||||
Worker_Reference: wdRef('Employee_ID', data.workerId),
|
||||
Effective_Date: data.effectiveDate,
|
||||
Change_Job_Detail_Data: changeJobDetailData,
|
||||
},
|
||||
})
|
||||
|
||||
const eventRef = result?.Event_Reference
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
eventId: extractRefId(eventRef),
|
||||
workerId: data.workerId,
|
||||
effectiveDate: data.effectiveDate,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday change job failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
134
apps/sim/app/api/tools/workday/create-prehire/route.ts
Normal file
134
apps/sim/app/api/tools/workday/create-prehire/route.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayCreatePrehireAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
legalName: z.string().min(1),
|
||||
email: z.string().optional(),
|
||||
phoneNumber: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
countryCode: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
if (!data.email && !data.phoneNumber && !data.address) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'At least one contact method (email, phone, or address) is required',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const parts = data.legalName.trim().split(/\s+/)
|
||||
const firstName = parts[0] ?? ''
|
||||
const lastName = parts.length > 1 ? parts.slice(1).join(' ') : ''
|
||||
|
||||
if (!lastName) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Legal name must include both a first name and last name' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'staffing',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const contactData: Record<string, unknown> = {}
|
||||
if (data.email) {
|
||||
contactData.Email_Address_Data = [
|
||||
{
|
||||
Email_Address: data.email,
|
||||
Usage_Data: {
|
||||
Type_Data: { Type_Reference: wdRef('Communication_Usage_Type_ID', 'WORK') },
|
||||
Public: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
if (data.phoneNumber) {
|
||||
contactData.Phone_Data = [
|
||||
{
|
||||
Phone_Number: data.phoneNumber,
|
||||
Phone_Device_Type_Reference: wdRef('Phone_Device_Type_ID', 'Landline'),
|
||||
Usage_Data: {
|
||||
Type_Data: { Type_Reference: wdRef('Communication_Usage_Type_ID', 'WORK') },
|
||||
Public: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
if (data.address) {
|
||||
contactData.Address_Data = [
|
||||
{
|
||||
Formatted_Address: data.address,
|
||||
Usage_Data: {
|
||||
Type_Data: { Type_Reference: wdRef('Communication_Usage_Type_ID', 'WORK') },
|
||||
Public: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const [result] = await client.Put_ApplicantAsync({
|
||||
Applicant_Data: {
|
||||
Personal_Data: {
|
||||
Name_Data: {
|
||||
Legal_Name_Data: {
|
||||
Name_Detail_Data: {
|
||||
Country_Reference: wdRef('ISO_3166-1_Alpha-2_Code', data.countryCode ?? 'US'),
|
||||
First_Name: firstName,
|
||||
Last_Name: lastName,
|
||||
},
|
||||
},
|
||||
},
|
||||
Contact_Information_Data: contactData,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const applicantRef = result?.Applicant_Reference
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
preHireId: extractRefId(applicantRef),
|
||||
descriptor: applicantRef?.attributes?.Descriptor ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday create prehire failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
101
apps/sim/app/api/tools/workday/get-compensation/route.ts
Normal file
101
apps/sim/app/api/tools/workday/get-compensation/route.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import {
|
||||
createWorkdaySoapClient,
|
||||
extractRefId,
|
||||
normalizeSoapArray,
|
||||
type WorkdayCompensationDataSoap,
|
||||
type WorkdayCompensationPlanSoap,
|
||||
type WorkdayWorkerSoap,
|
||||
} from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayGetCompensationAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Get_WorkersAsync({
|
||||
Request_References: {
|
||||
Worker_Reference: {
|
||||
ID: { attributes: { 'wd:type': 'Employee_ID' }, $value: data.workerId },
|
||||
},
|
||||
},
|
||||
Response_Group: {
|
||||
Include_Reference: true,
|
||||
Include_Compensation: true,
|
||||
},
|
||||
})
|
||||
|
||||
const worker =
|
||||
normalizeSoapArray(
|
||||
result?.Response_Data?.Worker as WorkdayWorkerSoap | WorkdayWorkerSoap[] | undefined
|
||||
)[0] ?? null
|
||||
const compensationData = worker?.Worker_Data?.Compensation_Data
|
||||
|
||||
const mapPlan = (p: WorkdayCompensationPlanSoap) => ({
|
||||
id: extractRefId(p.Compensation_Plan_Reference) ?? null,
|
||||
planName: p.Compensation_Plan_Reference?.attributes?.Descriptor ?? null,
|
||||
amount: p.Amount ?? p.Per_Unit_Amount ?? p.Individual_Target_Amount ?? null,
|
||||
currency: extractRefId(p.Currency_Reference) ?? null,
|
||||
frequency: extractRefId(p.Frequency_Reference) ?? null,
|
||||
})
|
||||
|
||||
const planTypeKeys: (keyof WorkdayCompensationDataSoap)[] = [
|
||||
'Employee_Base_Pay_Plan_Assignment_Data',
|
||||
'Employee_Salary_Unit_Plan_Assignment_Data',
|
||||
'Employee_Bonus_Plan_Assignment_Data',
|
||||
'Employee_Allowance_Plan_Assignment_Data',
|
||||
'Employee_Commission_Plan_Assignment_Data',
|
||||
'Employee_Stock_Plan_Assignment_Data',
|
||||
'Employee_Period_Salary_Plan_Assignment_Data',
|
||||
]
|
||||
|
||||
const compensationPlans: ReturnType<typeof mapPlan>[] = []
|
||||
for (const key of planTypeKeys) {
|
||||
for (const plan of normalizeSoapArray(compensationData?.[key])) {
|
||||
compensationPlans.push(mapPlan(plan))
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { compensationPlans },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday get compensation failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
94
apps/sim/app/api/tools/workday/get-organizations/route.ts
Normal file
94
apps/sim/app/api/tools/workday/get-organizations/route.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import {
|
||||
createWorkdaySoapClient,
|
||||
extractRefId,
|
||||
normalizeSoapArray,
|
||||
type WorkdayOrganizationSoap,
|
||||
} from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayGetOrganizationsAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
type: z.string().optional(),
|
||||
limit: z.number().optional(),
|
||||
offset: z.number().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const limit = data.limit ?? 20
|
||||
const offset = data.offset ?? 0
|
||||
const page = offset > 0 ? Math.floor(offset / limit) + 1 : 1
|
||||
|
||||
const [result] = await client.Get_OrganizationsAsync({
|
||||
Response_Filter: { Page: page, Count: limit },
|
||||
Request_Criteria: data.type
|
||||
? {
|
||||
Organization_Type_Reference: {
|
||||
ID: {
|
||||
attributes: { 'wd:type': 'Organization_Type_ID' },
|
||||
$value: data.type,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
Response_Group: { Include_Hierarchy_Data: true },
|
||||
})
|
||||
|
||||
const orgsArray = normalizeSoapArray(
|
||||
result?.Response_Data?.Organization as
|
||||
| WorkdayOrganizationSoap
|
||||
| WorkdayOrganizationSoap[]
|
||||
| undefined
|
||||
)
|
||||
|
||||
const organizations = orgsArray.map((o) => ({
|
||||
id: extractRefId(o.Organization_Reference) ?? null,
|
||||
descriptor: o.Organization_Descriptor ?? null,
|
||||
type: extractRefId(o.Organization_Data?.Organization_Type_Reference) ?? null,
|
||||
subtype: extractRefId(o.Organization_Data?.Organization_Subtype_Reference) ?? null,
|
||||
isActive: o.Organization_Data?.Inactive != null ? !o.Organization_Data.Inactive : null,
|
||||
}))
|
||||
|
||||
const total = result?.Response_Results?.Total_Results ?? organizations.length
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { organizations, total },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday get organizations failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
87
apps/sim/app/api/tools/workday/get-worker/route.ts
Normal file
87
apps/sim/app/api/tools/workday/get-worker/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import {
|
||||
createWorkdaySoapClient,
|
||||
extractRefId,
|
||||
normalizeSoapArray,
|
||||
type WorkdayWorkerSoap,
|
||||
} from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayGetWorkerAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Get_WorkersAsync({
|
||||
Request_References: {
|
||||
Worker_Reference: {
|
||||
ID: { attributes: { 'wd:type': 'Employee_ID' }, $value: data.workerId },
|
||||
},
|
||||
},
|
||||
Response_Group: {
|
||||
Include_Reference: true,
|
||||
Include_Personal_Information: true,
|
||||
Include_Employment_Information: true,
|
||||
Include_Compensation: true,
|
||||
Include_Organizations: true,
|
||||
},
|
||||
})
|
||||
|
||||
const worker =
|
||||
normalizeSoapArray(
|
||||
result?.Response_Data?.Worker as WorkdayWorkerSoap | WorkdayWorkerSoap[] | undefined
|
||||
)[0] ?? null
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
worker: worker
|
||||
? {
|
||||
id: extractRefId(worker.Worker_Reference) ?? null,
|
||||
descriptor: worker.Worker_Descriptor ?? null,
|
||||
personalData: worker.Worker_Data?.Personal_Data ?? null,
|
||||
employmentData: worker.Worker_Data?.Employment_Data ?? null,
|
||||
compensationData: worker.Worker_Data?.Compensation_Data ?? null,
|
||||
organizationData: worker.Worker_Data?.Organization_Data ?? null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday get worker failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
78
apps/sim/app/api/tools/workday/hire/route.ts
Normal file
78
apps/sim/app/api/tools/workday/hire/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayHireAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
preHireId: z.string().min(1),
|
||||
positionId: z.string().min(1),
|
||||
hireDate: z.string().min(1),
|
||||
employeeType: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'staffing',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Hire_EmployeeAsync({
|
||||
Business_Process_Parameters: {
|
||||
Auto_Complete: true,
|
||||
Run_Now: true,
|
||||
},
|
||||
Hire_Employee_Data: {
|
||||
Applicant_Reference: wdRef('Applicant_ID', data.preHireId),
|
||||
Position_Reference: wdRef('Position_ID', data.positionId),
|
||||
Hire_Date: data.hireDate,
|
||||
Hire_Employee_Event_Data: {
|
||||
Employee_Type_Reference: wdRef('Employee_Type_ID', data.employeeType ?? 'Regular'),
|
||||
First_Day_of_Work: data.hireDate,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const employeeRef = result?.Employee_Reference
|
||||
const eventRef = result?.Event_Reference
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
workerId: extractRefId(employeeRef),
|
||||
employeeId: extractRefId(employeeRef),
|
||||
eventId: extractRefId(eventRef),
|
||||
hireDate: data.hireDate,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday hire employee failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
83
apps/sim/app/api/tools/workday/list-workers/route.ts
Normal file
83
apps/sim/app/api/tools/workday/list-workers/route.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import {
|
||||
createWorkdaySoapClient,
|
||||
extractRefId,
|
||||
normalizeSoapArray,
|
||||
type WorkdayWorkerSoap,
|
||||
} from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayListWorkersAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
limit: z.number().optional(),
|
||||
offset: z.number().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const limit = data.limit ?? 20
|
||||
const offset = data.offset ?? 0
|
||||
const page = offset > 0 ? Math.floor(offset / limit) + 1 : 1
|
||||
|
||||
const [result] = await client.Get_WorkersAsync({
|
||||
Response_Filter: { Page: page, Count: limit },
|
||||
Response_Group: {
|
||||
Include_Reference: true,
|
||||
Include_Personal_Information: true,
|
||||
Include_Employment_Information: true,
|
||||
},
|
||||
})
|
||||
|
||||
const workersArray = normalizeSoapArray(
|
||||
result?.Response_Data?.Worker as WorkdayWorkerSoap | WorkdayWorkerSoap[] | undefined
|
||||
)
|
||||
|
||||
const workers = workersArray.map((w) => ({
|
||||
id: extractRefId(w.Worker_Reference) ?? null,
|
||||
descriptor: w.Worker_Descriptor ?? null,
|
||||
personalData: w.Worker_Data?.Personal_Data ?? null,
|
||||
employmentData: w.Worker_Data?.Employment_Data ?? null,
|
||||
}))
|
||||
|
||||
const total = result?.Response_Results?.Total_Results ?? workers.length
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { workers, total },
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday list workers failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
77
apps/sim/app/api/tools/workday/terminate/route.ts
Normal file
77
apps/sim/app/api/tools/workday/terminate/route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayTerminateAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
terminationDate: z.string().min(1),
|
||||
reason: z.string().min(1),
|
||||
notificationDate: z.string().optional(),
|
||||
lastDayOfWork: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'staffing',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Terminate_EmployeeAsync({
|
||||
Business_Process_Parameters: {
|
||||
Auto_Complete: true,
|
||||
Run_Now: true,
|
||||
},
|
||||
Terminate_Employee_Data: {
|
||||
Employee_Reference: wdRef('Employee_ID', data.workerId),
|
||||
Termination_Date: data.terminationDate,
|
||||
Terminate_Event_Data: {
|
||||
Primary_Reason_Reference: wdRef('Termination_Subcategory_ID', data.reason),
|
||||
Last_Day_of_Work: data.lastDayOfWork ?? data.terminationDate,
|
||||
Notification_Date: data.notificationDate ?? data.terminationDate,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const eventRef = result?.Event_Reference
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
eventId: extractRefId(eventRef),
|
||||
workerId: data.workerId,
|
||||
terminationDate: data.terminationDate,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday terminate employee failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
66
apps/sim/app/api/tools/workday/update-worker/route.ts
Normal file
66
apps/sim/app/api/tools/workday/update-worker/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createWorkdaySoapClient, extractRefId, wdRef } from '@/tools/workday/soap'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('WorkdayUpdateWorkerAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
tenantUrl: z.string().min(1),
|
||||
tenant: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
workerId: z.string().min(1),
|
||||
fields: z.record(z.unknown()),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const data = RequestSchema.parse(body)
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
data.tenant,
|
||||
'humanResources',
|
||||
data.username,
|
||||
data.password
|
||||
)
|
||||
|
||||
const [result] = await client.Change_Personal_InformationAsync({
|
||||
Business_Process_Parameters: {
|
||||
Auto_Complete: true,
|
||||
Run_Now: true,
|
||||
},
|
||||
Change_Personal_Information_Data: {
|
||||
Person_Reference: wdRef('Employee_ID', data.workerId),
|
||||
Personal_Information_Data: data.fields,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
eventId: extractRefId(result?.Personal_Information_Change_Event_Reference),
|
||||
workerId: data.workerId,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Workday update worker failed`, { error })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
|
||||
}
|
||||
|
||||
const fileName = rawFile.name || 'untitled'
|
||||
const fileName = rawFile.name || 'untitled.md'
|
||||
|
||||
const maxSize = 100 * 1024 * 1024
|
||||
if (rawFile.size > maxSize) {
|
||||
|
||||
@@ -151,6 +151,8 @@ export function Files() {
|
||||
}
|
||||
|
||||
const justCreatedFileIdRef = useRef<string | null>(null)
|
||||
const filesRef = useRef(files)
|
||||
filesRef.current = files
|
||||
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [uploadProgress, setUploadProgress] = useState({ completed: 0, total: 0 })
|
||||
@@ -483,11 +485,11 @@ export function Files() {
|
||||
if (isJustCreated) {
|
||||
setPreviewMode('editor')
|
||||
} else {
|
||||
const file = selectedFileId ? files.find((f) => f.id === selectedFileId) : null
|
||||
const file = selectedFileId ? filesRef.current.find((f) => f.id === selectedFileId) : null
|
||||
const canPreview = file ? isPreviewable(file) : false
|
||||
setPreviewMode(canPreview ? 'preview' : 'editor')
|
||||
}
|
||||
}, [selectedFileId, files])
|
||||
}, [selectedFileId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedFile) return
|
||||
|
||||
440
apps/sim/blocks/blocks/workday.ts
Normal file
440
apps/sim/blocks/blocks/workday.ts
Normal file
@@ -0,0 +1,440 @@
|
||||
import { WorkdayIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
|
||||
export const WorkdayBlock: BlockConfig = {
|
||||
type: 'workday',
|
||||
name: 'Workday',
|
||||
description: 'Manage workers, hiring, onboarding, and HR operations in Workday',
|
||||
longDescription:
|
||||
'Integrate Workday HRIS into your workflow. Create pre-hires, hire employees, manage worker profiles, assign onboarding plans, handle job changes, retrieve compensation data, and process terminations.',
|
||||
docsLink: 'https://docs.sim.ai/tools/workday',
|
||||
category: 'tools',
|
||||
bgColor: '#F5F0EB',
|
||||
icon: WorkdayIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Worker', id: 'get_worker' },
|
||||
{ label: 'List Workers', id: 'list_workers' },
|
||||
{ label: 'Create Pre-Hire', id: 'create_prehire' },
|
||||
{ label: 'Hire Employee', id: 'hire_employee' },
|
||||
{ label: 'Update Worker', id: 'update_worker' },
|
||||
{ label: 'Assign Onboarding Plan', id: 'assign_onboarding' },
|
||||
{ label: 'Get Organizations', id: 'get_organizations' },
|
||||
{ label: 'Change Job', id: 'change_job' },
|
||||
{ label: 'Get Compensation', id: 'get_compensation' },
|
||||
{ label: 'Terminate Worker', id: 'terminate_worker' },
|
||||
],
|
||||
value: () => 'get_worker',
|
||||
},
|
||||
{
|
||||
id: 'tenantUrl',
|
||||
title: 'Tenant URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'https://wd2-impl-services1.workday.com',
|
||||
required: true,
|
||||
description: 'Your Workday instance URL (e.g., https://wd2-impl-services1.workday.com)',
|
||||
},
|
||||
{
|
||||
id: 'tenant',
|
||||
title: 'Tenant Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'mycompany',
|
||||
required: true,
|
||||
description: 'Workday tenant identifier',
|
||||
},
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISU username',
|
||||
required: true,
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
{
|
||||
id: 'password',
|
||||
title: 'Password',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISU password',
|
||||
password: true,
|
||||
required: true,
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
|
||||
// Get Worker
|
||||
{
|
||||
id: 'workerId',
|
||||
title: 'Worker ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., 3aa5550b7fe348b98d7b5741afc65534',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_worker',
|
||||
'update_worker',
|
||||
'assign_onboarding',
|
||||
'change_job',
|
||||
'get_compensation',
|
||||
'terminate_worker',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_worker',
|
||||
'update_worker',
|
||||
'assign_onboarding',
|
||||
'change_job',
|
||||
'get_compensation',
|
||||
'terminate_worker',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// List Workers
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '20',
|
||||
condition: { field: 'operation', value: ['list_workers', 'get_organizations'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: ['list_workers', 'get_organizations'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// Create Pre-Hire
|
||||
{
|
||||
id: 'legalName',
|
||||
title: 'Legal Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., Jane Doe',
|
||||
condition: { field: 'operation', value: 'create_prehire' },
|
||||
required: { field: 'operation', value: 'create_prehire' },
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
title: 'Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'jane.doe@company.com',
|
||||
condition: { field: 'operation', value: 'create_prehire' },
|
||||
},
|
||||
{
|
||||
id: 'phoneNumber',
|
||||
title: 'Phone Number',
|
||||
type: 'short-input',
|
||||
placeholder: '+1-555-0100',
|
||||
condition: { field: 'operation', value: 'create_prehire' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'address',
|
||||
title: 'Address',
|
||||
type: 'short-input',
|
||||
placeholder: '123 Main St, City, State',
|
||||
condition: { field: 'operation', value: 'create_prehire' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countryCode',
|
||||
title: 'Country Code',
|
||||
type: 'short-input',
|
||||
placeholder: 'US',
|
||||
condition: { field: 'operation', value: 'create_prehire' },
|
||||
mode: 'advanced',
|
||||
description: 'ISO 3166-1 Alpha-2 country code (defaults to US)',
|
||||
},
|
||||
|
||||
// Hire Employee
|
||||
{
|
||||
id: 'preHireId',
|
||||
title: 'Pre-Hire ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Pre-hire record ID',
|
||||
condition: { field: 'operation', value: 'hire_employee' },
|
||||
required: { field: 'operation', value: 'hire_employee' },
|
||||
},
|
||||
{
|
||||
id: 'positionId',
|
||||
title: 'Position ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Position to assign',
|
||||
condition: { field: 'operation', value: ['hire_employee', 'change_job'] },
|
||||
required: { field: 'operation', value: ['hire_employee'] },
|
||||
},
|
||||
{
|
||||
id: 'hireDate',
|
||||
title: 'Hire Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: 'hire_employee' },
|
||||
required: { field: 'operation', value: 'hire_employee' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: 'Generate an ISO 8601 date (YYYY-MM-DD). Return ONLY the date string.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'jobProfileId',
|
||||
title: 'Job Profile ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Job profile ID',
|
||||
condition: { field: 'operation', value: 'change_job' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'locationId',
|
||||
title: 'Location ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Work location ID',
|
||||
condition: { field: 'operation', value: 'change_job' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'supervisoryOrgId',
|
||||
title: 'Supervisory Organization ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Target supervisory organization ID',
|
||||
condition: { field: 'operation', value: 'change_job' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'employeeType',
|
||||
title: 'Employee Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Regular', id: 'Regular' },
|
||||
{ label: 'Temporary', id: 'Temporary' },
|
||||
{ label: 'Contractor', id: 'Contractor' },
|
||||
],
|
||||
value: () => 'Regular',
|
||||
condition: { field: 'operation', value: 'hire_employee' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// Update Worker
|
||||
{
|
||||
id: 'fields',
|
||||
title: 'Fields (JSON)',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder:
|
||||
'{\n "businessTitle": "Senior Engineer",\n "primaryWorkEmail": "new@company.com"\n}',
|
||||
condition: { field: 'operation', value: 'update_worker' },
|
||||
required: { field: 'operation', value: 'update_worker' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `Generate a Workday worker update payload as JSON.
|
||||
|
||||
### COMMON FIELDS
|
||||
- businessTitle: Job title string
|
||||
- primaryWorkEmail: Work email address
|
||||
- primaryWorkPhone: Work phone number
|
||||
- managerReference: Manager worker ID
|
||||
|
||||
### RULES
|
||||
- Output ONLY valid JSON starting with { and ending with }
|
||||
- Include only fields that need updating
|
||||
|
||||
### EXAMPLE
|
||||
User: "Update title to Senior Engineer"
|
||||
Output: {"businessTitle": "Senior Engineer"}`,
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
|
||||
// Assign Onboarding
|
||||
{
|
||||
id: 'onboardingPlanId',
|
||||
title: 'Onboarding Plan ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Plan ID to assign',
|
||||
condition: { field: 'operation', value: 'assign_onboarding' },
|
||||
required: { field: 'operation', value: 'assign_onboarding' },
|
||||
},
|
||||
{
|
||||
id: 'actionEventId',
|
||||
title: 'Action Event ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Hiring event ID that enables onboarding',
|
||||
condition: { field: 'operation', value: 'assign_onboarding' },
|
||||
required: { field: 'operation', value: 'assign_onboarding' },
|
||||
},
|
||||
|
||||
// Get Organizations
|
||||
{
|
||||
id: 'orgType',
|
||||
title: 'Organization Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All Types', id: '' },
|
||||
{ label: 'Supervisory', id: 'Supervisory' },
|
||||
{ label: 'Cost Center', id: 'Cost_Center' },
|
||||
{ label: 'Company', id: 'Company' },
|
||||
{ label: 'Region', id: 'Region' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'get_organizations' },
|
||||
},
|
||||
|
||||
// Change Job
|
||||
{
|
||||
id: 'effectiveDate',
|
||||
title: 'Effective Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: 'change_job' },
|
||||
required: { field: 'operation', value: 'change_job' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: 'Generate an ISO 8601 date (YYYY-MM-DD). Return ONLY the date string.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'reason',
|
||||
title: 'Reason',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., Promotion, Transfer',
|
||||
condition: { field: 'operation', value: ['change_job', 'terminate_worker'] },
|
||||
required: { field: 'operation', value: ['change_job', 'terminate_worker'] },
|
||||
},
|
||||
|
||||
// Terminate Worker
|
||||
{
|
||||
id: 'terminationDate',
|
||||
title: 'Termination Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: 'terminate_worker' },
|
||||
required: { field: 'operation', value: 'terminate_worker' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: 'Generate an ISO 8601 date (YYYY-MM-DD). Return ONLY the date string.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'notificationDate',
|
||||
title: 'Notification Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD',
|
||||
condition: { field: 'operation', value: 'terminate_worker' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'lastDayOfWork',
|
||||
title: 'Last Day of Work',
|
||||
type: 'short-input',
|
||||
placeholder: 'YYYY-MM-DD (defaults to termination date)',
|
||||
condition: { field: 'operation', value: 'terminate_worker' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'workday_get_worker',
|
||||
'workday_list_workers',
|
||||
'workday_create_prehire',
|
||||
'workday_hire_employee',
|
||||
'workday_update_worker',
|
||||
'workday_assign_onboarding',
|
||||
'workday_get_organizations',
|
||||
'workday_change_job',
|
||||
'workday_get_compensation',
|
||||
'workday_terminate_worker',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `workday_${params.operation}`,
|
||||
params: (params) => {
|
||||
const { operation, orgType, fields, jobProfileId, locationId, supervisoryOrgId, ...rest } =
|
||||
params
|
||||
|
||||
if (rest.limit != null && rest.limit !== '') rest.limit = Number(rest.limit)
|
||||
if (rest.offset != null && rest.offset !== '') rest.offset = Number(rest.offset)
|
||||
|
||||
if (orgType) rest.type = orgType
|
||||
|
||||
if (operation === 'change_job') {
|
||||
if (rest.positionId) {
|
||||
rest.newPositionId = rest.positionId
|
||||
rest.positionId = undefined
|
||||
}
|
||||
if (jobProfileId) rest.newJobProfileId = jobProfileId
|
||||
if (locationId) rest.newLocationId = locationId
|
||||
if (supervisoryOrgId) rest.newSupervisoryOrgId = supervisoryOrgId
|
||||
}
|
||||
|
||||
if (fields && operation === 'update_worker') {
|
||||
try {
|
||||
const parsedFields = typeof fields === 'string' ? JSON.parse(fields) : fields
|
||||
return { ...rest, fields: parsedFields }
|
||||
} catch {
|
||||
throw new Error('Invalid JSON in Fields block')
|
||||
}
|
||||
}
|
||||
|
||||
return rest
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Workday operation to perform' },
|
||||
tenantUrl: { type: 'string', description: 'Workday instance URL' },
|
||||
tenant: { type: 'string', description: 'Workday tenant name' },
|
||||
username: { type: 'string', description: 'ISU username' },
|
||||
password: { type: 'string', description: 'ISU password' },
|
||||
workerId: { type: 'string', description: 'Worker ID' },
|
||||
limit: { type: 'number', description: 'Result limit' },
|
||||
offset: { type: 'number', description: 'Pagination offset' },
|
||||
legalName: { type: 'string', description: 'Legal name for pre-hire' },
|
||||
email: { type: 'string', description: 'Email address' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
address: { type: 'string', description: 'Address' },
|
||||
countryCode: { type: 'string', description: 'ISO 3166-1 Alpha-2 country code' },
|
||||
preHireId: { type: 'string', description: 'Pre-hire record ID' },
|
||||
positionId: { type: 'string', description: 'Position ID' },
|
||||
hireDate: { type: 'string', description: 'Hire date (YYYY-MM-DD)' },
|
||||
jobProfileId: { type: 'string', description: 'Job profile ID' },
|
||||
locationId: { type: 'string', description: 'Location ID' },
|
||||
supervisoryOrgId: { type: 'string', description: 'Target supervisory organization ID' },
|
||||
employeeType: { type: 'string', description: 'Employee type' },
|
||||
fields: { type: 'json', description: 'Fields to update' },
|
||||
onboardingPlanId: { type: 'string', description: 'Onboarding plan ID' },
|
||||
actionEventId: { type: 'string', description: 'Action event ID for onboarding' },
|
||||
orgType: { type: 'string', description: 'Organization type filter' },
|
||||
effectiveDate: { type: 'string', description: 'Effective date (YYYY-MM-DD)' },
|
||||
reason: { type: 'string', description: 'Reason for change or termination' },
|
||||
terminationDate: { type: 'string', description: 'Termination date (YYYY-MM-DD)' },
|
||||
notificationDate: { type: 'string', description: 'Notification date' },
|
||||
lastDayOfWork: { type: 'string', description: 'Last day of work' },
|
||||
},
|
||||
outputs: {
|
||||
worker: { type: 'json', description: 'Worker profile data' },
|
||||
workers: { type: 'json', description: 'Array of worker profiles' },
|
||||
total: { type: 'number', description: 'Total count of results' },
|
||||
preHireId: { type: 'string', description: 'Created pre-hire ID' },
|
||||
descriptor: { type: 'string', description: 'Display name of pre-hire' },
|
||||
workerId: { type: 'string', description: 'Worker ID' },
|
||||
employeeId: { type: 'string', description: 'Employee ID' },
|
||||
hireDate: { type: 'string', description: 'Hire date' },
|
||||
assignmentId: { type: 'string', description: 'Onboarding assignment ID' },
|
||||
planId: { type: 'string', description: 'Onboarding plan ID' },
|
||||
organizations: { type: 'json', description: 'Array of organizations' },
|
||||
eventId: { type: 'string', description: 'Event ID for staffing changes' },
|
||||
effectiveDate: { type: 'string', description: 'Effective date of change' },
|
||||
compensationPlans: { type: 'json', description: 'Compensation plan details' },
|
||||
terminationDate: { type: 'string', description: 'Termination date' },
|
||||
},
|
||||
}
|
||||
@@ -189,6 +189,7 @@ import { WebhookRequestBlock } from '@/blocks/blocks/webhook_request'
|
||||
import { WhatsAppBlock } from '@/blocks/blocks/whatsapp'
|
||||
import { WikipediaBlock } from '@/blocks/blocks/wikipedia'
|
||||
import { WordPressBlock } from '@/blocks/blocks/wordpress'
|
||||
import { WorkdayBlock } from '@/blocks/blocks/workday'
|
||||
import { WorkflowBlock } from '@/blocks/blocks/workflow'
|
||||
import { WorkflowInputBlock } from '@/blocks/blocks/workflow_input'
|
||||
import { XBlock } from '@/blocks/blocks/x'
|
||||
@@ -410,6 +411,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
whatsapp: WhatsAppBlock,
|
||||
wikipedia: WikipediaBlock,
|
||||
wordpress: WordPressBlock,
|
||||
workday: WorkdayBlock,
|
||||
workflow: WorkflowBlock,
|
||||
workflow_input: WorkflowInputBlock,
|
||||
x: XBlock,
|
||||
|
||||
@@ -124,6 +124,34 @@ export function NoteIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkdayIcon(props: SVGProps<SVGSVGElement>) {
|
||||
const id = useId()
|
||||
const clipId = `workday_clip_${id}`
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 64 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<g clipPath={`url(#${clipId})`} transform='matrix(0.53333333,0,0,0.53333333,-124.63685,-16)'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='m 251.21,88.7755 h 8.224 c 1.166,0 2.178,0.7836 2.444,1.8924 l 11.057,44.6751 c 0.152,0.002 12.182,-44.6393 12.182,-44.6393 0.306,-1.1361 1.36,-1.9282 2.566,-1.9282 h 12.74 c 1.144,0 2.144,0.7515 2.435,1.8296 l 12.118,44.9289 c 0.448,-0.282 11.147,-44.8661 11.147,-44.8661 0.267,-1.1088 1.279,-1.8924 2.444,-1.8924 h 8.219 c 1.649,0 2.854,1.5192 2.437,3.0742 l -15.08,56.3173 c -0.286,1.072 -1.272,1.823 -2.406,1.833 l -12.438,-0.019 c -1.142,-0.002 -2.137,-0.744 -2.429,-1.819 -2.126,-7.805 -12.605,-47.277 -12.605,-47.277 0,0 -11.008,39.471 -13.133,47.277 -0.293,1.075 -1.288,1.817 -2.429,1.819 L 266.264,150 c -1.133,-0.01 -2.119,-0.761 -2.406,-1.833 L 248.777,91.8438 c -0.416,-1.5524 0.786,-3.0683 2.433,-3.0683 z'
|
||||
fill='#005cb9'
|
||||
/>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='m 333.324,72.2449 c 0.531,0 1.071,-0.0723 1.608,-0.2234 3.18,-0.8968 5.039,-4.2303 4.153,-7.446 -0.129,-0.4673 -0.265,-0.9327 -0.408,-1.3936 C 332.529,43.3349 314.569,30 293.987,30 c -20.557,0 -38.51,13.3133 -44.673,33.1281 -0.136,0.4355 -0.267,0.8782 -0.391,1.3232 -0.902,3.2119 0.943,6.5541 4.12,7.4645 3.173,0.9112 6.48,-0.9547 7.381,-4.1666 0.094,-0.3322 0.19,-0.6616 0.292,-0.9892 4.591,-14.7582 17.961,-24.6707 33.271,-24.6707 15.329,0 28.704,9.9284 33.281,24.7063 0.105,0.3397 0.206,0.682 0.301,1.0263 0.737,2.6726 3.139,4.423 5.755,4.423 z'
|
||||
fill='#f38b00'
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<path d='M 354,30 H 234 v 120 h 120 z' fill='#ffffff' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkflowIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -159,6 +159,7 @@
|
||||
"resend": "^4.1.2",
|
||||
"rss-parser": "3.13.0",
|
||||
"sharp": "0.34.3",
|
||||
"soap": "1.8.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "4.8.1",
|
||||
"ssh2": "^1.17.0",
|
||||
|
||||
@@ -2280,6 +2280,18 @@ import {
|
||||
wordpressUpdatePostTool,
|
||||
wordpressUploadMediaTool,
|
||||
} from '@/tools/wordpress'
|
||||
import {
|
||||
workdayAssignOnboardingTool,
|
||||
workdayChangeJobTool,
|
||||
workdayCreatePrehireTool,
|
||||
workdayGetCompensationTool,
|
||||
workdayGetOrganizationsTool,
|
||||
workdayGetWorkerTool,
|
||||
workdayHireEmployeeTool,
|
||||
workdayListWorkersTool,
|
||||
workdayTerminateWorkerTool,
|
||||
workdayUpdateWorkerTool,
|
||||
} from '@/tools/workday'
|
||||
import { workflowExecutorTool } from '@/tools/workflow'
|
||||
import {
|
||||
xCreateBookmarkTool,
|
||||
@@ -4120,6 +4132,16 @@ export const tools: Record<string, ToolConfig> = {
|
||||
wordpress_list_users: wordpressListUsersTool,
|
||||
wordpress_get_user: wordpressGetUserTool,
|
||||
wordpress_search_content: wordpressSearchContentTool,
|
||||
workday_get_worker: workdayGetWorkerTool,
|
||||
workday_list_workers: workdayListWorkersTool,
|
||||
workday_create_prehire: workdayCreatePrehireTool,
|
||||
workday_hire_employee: workdayHireEmployeeTool,
|
||||
workday_update_worker: workdayUpdateWorkerTool,
|
||||
workday_assign_onboarding: workdayAssignOnboardingTool,
|
||||
workday_get_organizations: workdayGetOrganizationsTool,
|
||||
workday_change_job: workdayChangeJobTool,
|
||||
workday_get_compensation: workdayGetCompensationTool,
|
||||
workday_terminate_worker: workdayTerminateWorkerTool,
|
||||
google_ads_list_customers: googleAdsListCustomersTool,
|
||||
google_ads_search: googleAdsSearchTool,
|
||||
google_ads_list_campaigns: googleAdsListCampaignsTool,
|
||||
|
||||
93
apps/sim/tools/workday/assign_onboarding.ts
Normal file
93
apps/sim/tools/workday/assign_onboarding.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
WorkdayAssignOnboardingParams,
|
||||
WorkdayAssignOnboardingResponse,
|
||||
} from '@/tools/workday/types'
|
||||
|
||||
export const assignOnboardingTool: ToolConfig<
|
||||
WorkdayAssignOnboardingParams,
|
||||
WorkdayAssignOnboardingResponse
|
||||
> = {
|
||||
id: 'workday_assign_onboarding',
|
||||
name: 'Assign Workday Onboarding Plan',
|
||||
description:
|
||||
'Create or update an onboarding plan assignment for a worker. Sets up onboarding stages and manages the assignment lifecycle.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID to assign the onboarding plan to',
|
||||
},
|
||||
onboardingPlanId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Onboarding plan ID to assign',
|
||||
},
|
||||
actionEventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Action event ID that enables the onboarding plan (e.g., the hiring event ID)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/assign-onboarding',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
assignmentId: {
|
||||
type: 'string',
|
||||
description: 'Onboarding plan assignment ID',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
description: 'Worker ID the plan was assigned to',
|
||||
},
|
||||
planId: {
|
||||
type: 'string',
|
||||
description: 'Onboarding plan ID that was assigned',
|
||||
},
|
||||
},
|
||||
}
|
||||
111
apps/sim/tools/workday/change_job.ts
Normal file
111
apps/sim/tools/workday/change_job.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WorkdayChangeJobParams, WorkdayChangeJobResponse } from '@/tools/workday/types'
|
||||
|
||||
export const changeJobTool: ToolConfig<WorkdayChangeJobParams, WorkdayChangeJobResponse> = {
|
||||
id: 'workday_change_job',
|
||||
name: 'Change Workday Job',
|
||||
description:
|
||||
'Perform a job change for a worker including transfers, promotions, demotions, and lateral moves.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID for the job change',
|
||||
},
|
||||
effectiveDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Effective date for the job change in ISO 8601 format (e.g., 2025-06-01)',
|
||||
},
|
||||
newPositionId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New position ID (for transfers)',
|
||||
},
|
||||
newJobProfileId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New job profile ID (for role changes)',
|
||||
},
|
||||
newLocationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New work location ID (for relocations)',
|
||||
},
|
||||
newSupervisoryOrgId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Target supervisory organization ID (for org transfers)',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reason for the job change (e.g., Promotion, Transfer, Reorganization)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/change-job',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
eventId: {
|
||||
type: 'string',
|
||||
description: 'Job change event ID',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
description: 'Worker ID the job change was applied to',
|
||||
},
|
||||
effectiveDate: {
|
||||
type: 'string',
|
||||
description: 'Effective date of the job change',
|
||||
},
|
||||
},
|
||||
}
|
||||
101
apps/sim/tools/workday/create_prehire.ts
Normal file
101
apps/sim/tools/workday/create_prehire.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
WorkdayCreatePrehireParams,
|
||||
WorkdayCreatePrehireResponse,
|
||||
} from '@/tools/workday/types'
|
||||
|
||||
export const createPrehireTool: ToolConfig<
|
||||
WorkdayCreatePrehireParams,
|
||||
WorkdayCreatePrehireResponse
|
||||
> = {
|
||||
id: 'workday_create_prehire',
|
||||
name: 'Create Workday Pre-Hire',
|
||||
description:
|
||||
'Create a new pre-hire (applicant) record in Workday. This is typically the first step before hiring an employee.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
legalName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full legal name of the pre-hire (e.g., "Jane Doe")',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address of the pre-hire',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone number of the pre-hire',
|
||||
},
|
||||
address: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Address of the pre-hire',
|
||||
},
|
||||
countryCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 3166-1 Alpha-2 country code (defaults to US)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/create-prehire',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
preHireId: {
|
||||
type: 'string',
|
||||
description: 'ID of the created pre-hire record',
|
||||
},
|
||||
descriptor: {
|
||||
type: 'string',
|
||||
description: 'Display name of the pre-hire',
|
||||
},
|
||||
},
|
||||
}
|
||||
83
apps/sim/tools/workday/get_compensation.ts
Normal file
83
apps/sim/tools/workday/get_compensation.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
WorkdayGetCompensationParams,
|
||||
WorkdayGetCompensationResponse,
|
||||
} from '@/tools/workday/types'
|
||||
|
||||
export const getCompensationTool: ToolConfig<
|
||||
WorkdayGetCompensationParams,
|
||||
WorkdayGetCompensationResponse
|
||||
> = {
|
||||
id: 'workday_get_compensation',
|
||||
name: 'Get Workday Compensation',
|
||||
description: 'Retrieve compensation plan details for a specific worker.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID to retrieve compensation data for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/get-compensation',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
compensationPlans: {
|
||||
type: 'array',
|
||||
description: 'Array of compensation plan details',
|
||||
items: {
|
||||
type: 'json',
|
||||
description: 'Compensation plan with amount, currency, and frequency',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Compensation plan ID' },
|
||||
planName: { type: 'string', description: 'Name of the compensation plan' },
|
||||
amount: { type: 'number', description: 'Compensation amount' },
|
||||
currency: { type: 'string', description: 'Currency code' },
|
||||
frequency: { type: 'string', description: 'Pay frequency' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
88
apps/sim/tools/workday/get_organizations.ts
Normal file
88
apps/sim/tools/workday/get_organizations.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
WorkdayGetOrganizationsParams,
|
||||
WorkdayGetOrganizationsResponse,
|
||||
} from '@/tools/workday/types'
|
||||
|
||||
export const getOrganizationsTool: ToolConfig<
|
||||
WorkdayGetOrganizationsParams,
|
||||
WorkdayGetOrganizationsResponse
|
||||
> = {
|
||||
id: 'workday_get_organizations',
|
||||
name: 'Get Workday Organizations',
|
||||
description: 'Retrieve organizations, departments, and cost centers from Workday.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization type filter (e.g., Supervisory, Cost_Center, Company, Region)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of organizations to return (default: 20)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of records to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/get-organizations',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organizations: {
|
||||
type: 'array',
|
||||
description: 'Array of organization records',
|
||||
},
|
||||
total: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching organizations',
|
||||
},
|
||||
},
|
||||
}
|
||||
67
apps/sim/tools/workday/get_worker.ts
Normal file
67
apps/sim/tools/workday/get_worker.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WorkdayGetWorkerParams, WorkdayGetWorkerResponse } from '@/tools/workday/types'
|
||||
|
||||
export const getWorkerTool: ToolConfig<WorkdayGetWorkerParams, WorkdayGetWorkerResponse> = {
|
||||
id: 'workday_get_worker',
|
||||
name: 'Get Workday Worker',
|
||||
description:
|
||||
'Retrieve a specific worker profile including personal, employment, and organization data.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID to retrieve (e.g., 3aa5550b7fe348b98d7b5741afc65534)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/get-worker',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
worker: {
|
||||
type: 'json',
|
||||
description: 'Worker profile with personal, employment, and organization data',
|
||||
},
|
||||
},
|
||||
}
|
||||
98
apps/sim/tools/workday/hire_employee.ts
Normal file
98
apps/sim/tools/workday/hire_employee.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WorkdayHireEmployeeParams, WorkdayHireEmployeeResponse } from '@/tools/workday/types'
|
||||
|
||||
export const hireEmployeeTool: ToolConfig<WorkdayHireEmployeeParams, WorkdayHireEmployeeResponse> =
|
||||
{
|
||||
id: 'workday_hire_employee',
|
||||
name: 'Hire Workday Employee',
|
||||
description:
|
||||
'Hire a pre-hire into an employee position. Converts an applicant into an active employee record with position, start date, and manager assignment.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
preHireId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pre-hire (applicant) ID to convert into an employee',
|
||||
},
|
||||
positionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Position ID to assign the new hire to',
|
||||
},
|
||||
hireDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Hire date in ISO 8601 format (e.g., 2025-06-01)',
|
||||
},
|
||||
employeeType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Employee type (e.g., Regular, Temporary, Contractor)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/hire',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
workerId: {
|
||||
type: 'string',
|
||||
description: 'Worker ID of the newly hired employee',
|
||||
},
|
||||
employeeId: {
|
||||
type: 'string',
|
||||
description: 'Employee ID assigned to the new hire',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
description: 'Event ID of the hire business process',
|
||||
},
|
||||
hireDate: {
|
||||
type: 'string',
|
||||
description: 'Effective hire date',
|
||||
},
|
||||
},
|
||||
}
|
||||
25
apps/sim/tools/workday/index.ts
Normal file
25
apps/sim/tools/workday/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { assignOnboardingTool } from '@/tools/workday/assign_onboarding'
|
||||
import { changeJobTool } from '@/tools/workday/change_job'
|
||||
import { createPrehireTool } from '@/tools/workday/create_prehire'
|
||||
import { getCompensationTool } from '@/tools/workday/get_compensation'
|
||||
import { getOrganizationsTool } from '@/tools/workday/get_organizations'
|
||||
import { getWorkerTool } from '@/tools/workday/get_worker'
|
||||
import { hireEmployeeTool } from '@/tools/workday/hire_employee'
|
||||
import { listWorkersTool } from '@/tools/workday/list_workers'
|
||||
import { terminateWorkerTool } from '@/tools/workday/terminate_worker'
|
||||
import { updateWorkerTool } from '@/tools/workday/update_worker'
|
||||
|
||||
export {
|
||||
assignOnboardingTool as workdayAssignOnboardingTool,
|
||||
changeJobTool as workdayChangeJobTool,
|
||||
createPrehireTool as workdayCreatePrehireTool,
|
||||
getCompensationTool as workdayGetCompensationTool,
|
||||
getOrganizationsTool as workdayGetOrganizationsTool,
|
||||
getWorkerTool as workdayGetWorkerTool,
|
||||
hireEmployeeTool as workdayHireEmployeeTool,
|
||||
listWorkersTool as workdayListWorkersTool,
|
||||
terminateWorkerTool as workdayTerminateWorkerTool,
|
||||
updateWorkerTool as workdayUpdateWorkerTool,
|
||||
}
|
||||
|
||||
export * from './types'
|
||||
76
apps/sim/tools/workday/list_workers.ts
Normal file
76
apps/sim/tools/workday/list_workers.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WorkdayListWorkersParams, WorkdayListWorkersResponse } from '@/tools/workday/types'
|
||||
|
||||
export const listWorkersTool: ToolConfig<WorkdayListWorkersParams, WorkdayListWorkersResponse> = {
|
||||
id: 'workday_list_workers',
|
||||
name: 'List Workday Workers',
|
||||
description: 'List or search workers with optional filtering and pagination.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of workers to return (default: 20)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of records to skip for pagination',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/list-workers',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
workers: {
|
||||
type: 'array',
|
||||
description: 'Array of worker profiles',
|
||||
},
|
||||
total: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching workers',
|
||||
},
|
||||
},
|
||||
}
|
||||
188
apps/sim/tools/workday/soap.ts
Normal file
188
apps/sim/tools/workday/soap.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import * as soap from 'soap'
|
||||
|
||||
const logger = createLogger('WorkdaySoapClient')
|
||||
|
||||
const WORKDAY_SERVICES = {
|
||||
staffing: { name: 'Staffing', version: 'v45.1' },
|
||||
humanResources: { name: 'Human_Resources', version: 'v45.2' },
|
||||
compensation: { name: 'Compensation', version: 'v45.0' },
|
||||
recruiting: { name: 'Recruiting', version: 'v45.0' },
|
||||
} as const
|
||||
|
||||
export type WorkdayServiceKey = keyof typeof WORKDAY_SERVICES
|
||||
|
||||
export interface WorkdaySoapResult {
|
||||
Response_Data?: Record<string, unknown>
|
||||
Response_Results?: {
|
||||
Total_Results?: number
|
||||
Total_Pages?: number
|
||||
Page_Results?: number
|
||||
Page?: number
|
||||
}
|
||||
Event_Reference?: WorkdayReference
|
||||
Employee_Reference?: WorkdayReference
|
||||
Position_Reference?: WorkdayReference
|
||||
Applicant_Reference?: WorkdayReference & { attributes?: { Descriptor?: string } }
|
||||
Onboarding_Plan_Assignment_Reference?: WorkdayReference
|
||||
Personal_Information_Change_Event_Reference?: WorkdayReference
|
||||
Exceptions_Response_Data?: unknown
|
||||
}
|
||||
|
||||
export interface WorkdayReference {
|
||||
ID?: WorkdayIdEntry[] | WorkdayIdEntry
|
||||
attributes?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface WorkdayIdEntry {
|
||||
$value?: string
|
||||
_?: string
|
||||
attributes?: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw SOAP response shape for a single Worker returned by Get_Workers.
|
||||
* Fields are optional since the Response_Group controls what gets included.
|
||||
*/
|
||||
export interface WorkdayWorkerSoap {
|
||||
Worker_Reference?: WorkdayReference
|
||||
Worker_Descriptor?: string
|
||||
Worker_Data?: WorkdayWorkerDataSoap
|
||||
}
|
||||
|
||||
export interface WorkdayWorkerDataSoap {
|
||||
Personal_Data?: Record<string, unknown>
|
||||
Employment_Data?: Record<string, unknown>
|
||||
Compensation_Data?: WorkdayCompensationDataSoap
|
||||
Organization_Data?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface WorkdayCompensationDataSoap {
|
||||
Employee_Base_Pay_Plan_Assignment_Data?:
|
||||
| WorkdayCompensationPlanSoap
|
||||
| WorkdayCompensationPlanSoap[]
|
||||
Employee_Salary_Unit_Plan_Assignment_Data?:
|
||||
| WorkdayCompensationPlanSoap
|
||||
| WorkdayCompensationPlanSoap[]
|
||||
Employee_Bonus_Plan_Assignment_Data?: WorkdayCompensationPlanSoap | WorkdayCompensationPlanSoap[]
|
||||
Employee_Allowance_Plan_Assignment_Data?:
|
||||
| WorkdayCompensationPlanSoap
|
||||
| WorkdayCompensationPlanSoap[]
|
||||
Employee_Commission_Plan_Assignment_Data?:
|
||||
| WorkdayCompensationPlanSoap
|
||||
| WorkdayCompensationPlanSoap[]
|
||||
Employee_Stock_Plan_Assignment_Data?: WorkdayCompensationPlanSoap | WorkdayCompensationPlanSoap[]
|
||||
Employee_Period_Salary_Plan_Assignment_Data?:
|
||||
| WorkdayCompensationPlanSoap
|
||||
| WorkdayCompensationPlanSoap[]
|
||||
}
|
||||
|
||||
export interface WorkdayCompensationPlanSoap {
|
||||
Compensation_Plan_Reference?: WorkdayReference
|
||||
Amount?: number
|
||||
Per_Unit_Amount?: number
|
||||
Individual_Target_Amount?: number
|
||||
Currency_Reference?: WorkdayReference
|
||||
Frequency_Reference?: WorkdayReference
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw SOAP response shape for a single Organization returned by Get_Organizations.
|
||||
*/
|
||||
export interface WorkdayOrganizationSoap {
|
||||
Organization_Reference?: WorkdayReference
|
||||
Organization_Descriptor?: string
|
||||
Organization_Data?: WorkdayOrganizationDataSoap
|
||||
}
|
||||
|
||||
export interface WorkdayOrganizationDataSoap {
|
||||
Organization_Type_Reference?: WorkdayReference
|
||||
Organization_Subtype_Reference?: WorkdayReference
|
||||
Inactive?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a SOAP response field that may be a single object, an array, or undefined
|
||||
* into a consistently typed array.
|
||||
*/
|
||||
export function normalizeSoapArray<T>(value: T | T[] | undefined): T[] {
|
||||
if (!value) return []
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
type SoapOperationFn = (
|
||||
args: Record<string, unknown>
|
||||
) => Promise<[WorkdaySoapResult, string, Record<string, unknown>, string]>
|
||||
|
||||
export interface WorkdayClient extends soap.Client {
|
||||
Get_WorkersAsync: SoapOperationFn
|
||||
Get_OrganizationsAsync: SoapOperationFn
|
||||
Put_ApplicantAsync: SoapOperationFn
|
||||
Hire_EmployeeAsync: SoapOperationFn
|
||||
Change_JobAsync: SoapOperationFn
|
||||
Terminate_EmployeeAsync: SoapOperationFn
|
||||
Change_Personal_InformationAsync: SoapOperationFn
|
||||
Put_Onboarding_Plan_AssignmentAsync: SoapOperationFn
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the WSDL URL for a Workday SOAP service.
|
||||
* Pattern: {tenantUrl}/ccx/service/{tenant}/{serviceName}/{version}?wsdl
|
||||
*/
|
||||
export function buildWsdlUrl(
|
||||
tenantUrl: string,
|
||||
tenant: string,
|
||||
service: WorkdayServiceKey
|
||||
): string {
|
||||
const svc = WORKDAY_SERVICES[service]
|
||||
const baseUrl = tenantUrl.replace(/\/$/, '')
|
||||
return `${baseUrl}/ccx/service/${tenant}/${svc.name}/${svc.version}?wsdl`
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a typed SOAP client for a Workday service.
|
||||
* Uses the `soap` npm package to parse the WSDL and auto-marshall JSON to XML.
|
||||
*/
|
||||
export async function createWorkdaySoapClient(
|
||||
tenantUrl: string,
|
||||
tenant: string,
|
||||
service: WorkdayServiceKey,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<WorkdayClient> {
|
||||
const wsdlUrl = buildWsdlUrl(tenantUrl, tenant, service)
|
||||
logger.info('Creating Workday SOAP client', { service, wsdlUrl })
|
||||
|
||||
const client = await soap.createClientAsync(wsdlUrl)
|
||||
client.setSecurity(new soap.BasicAuthSecurity(username, password))
|
||||
return client as WorkdayClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Workday object reference in the format the SOAP API expects.
|
||||
* Generates: { ID: { attributes: { type: idType }, $value: idValue } }
|
||||
*/
|
||||
export function wdRef(idType: string, idValue: string): { ID: WorkdayIdEntry } {
|
||||
return {
|
||||
ID: {
|
||||
attributes: { 'wd:type': idType },
|
||||
$value: idValue,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a reference ID from a SOAP response object.
|
||||
* Handles the nested ID structure that Workday returns.
|
||||
*/
|
||||
export function extractRefId(ref: WorkdayReference | undefined): string | null {
|
||||
if (!ref) return null
|
||||
const id = ref.ID
|
||||
if (Array.isArray(id)) {
|
||||
return id[0]?.$value ?? id[0]?._ ?? null
|
||||
}
|
||||
if (id && typeof id === 'object') {
|
||||
return id.$value ?? id._ ?? null
|
||||
}
|
||||
return null
|
||||
}
|
||||
105
apps/sim/tools/workday/terminate_worker.ts
Normal file
105
apps/sim/tools/workday/terminate_worker.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type {
|
||||
WorkdayTerminateWorkerParams,
|
||||
WorkdayTerminateWorkerResponse,
|
||||
} from '@/tools/workday/types'
|
||||
|
||||
export const terminateWorkerTool: ToolConfig<
|
||||
WorkdayTerminateWorkerParams,
|
||||
WorkdayTerminateWorkerResponse
|
||||
> = {
|
||||
id: 'workday_terminate_worker',
|
||||
name: 'Terminate Workday Worker',
|
||||
description:
|
||||
'Initiate a worker termination in Workday. Triggers the Terminate Employee business process.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID to terminate',
|
||||
},
|
||||
terminationDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Termination date in ISO 8601 format (e.g., 2025-06-01)',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Termination reason (e.g., Resignation, End_of_Contract, Retirement)',
|
||||
},
|
||||
notificationDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Date the termination was communicated in ISO 8601 format',
|
||||
},
|
||||
lastDayOfWork: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last day of work in ISO 8601 format (defaults to termination date)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/terminate',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
eventId: {
|
||||
type: 'string',
|
||||
description: 'Termination event ID',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
description: 'Worker ID that was terminated',
|
||||
},
|
||||
terminationDate: {
|
||||
type: 'string',
|
||||
description: 'Effective termination date',
|
||||
},
|
||||
},
|
||||
}
|
||||
183
apps/sim/tools/workday/types.ts
Normal file
183
apps/sim/tools/workday/types.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface WorkdayBaseParams {
|
||||
tenantUrl: string
|
||||
tenant: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface WorkdayWorker {
|
||||
id: string
|
||||
descriptor: string
|
||||
primaryWorkEmail?: string
|
||||
primaryWorkPhone?: string
|
||||
businessTitle?: string
|
||||
supervisoryOrganization?: string
|
||||
hireDate?: string
|
||||
workerType?: string
|
||||
isActive?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export interface WorkdayOrganization {
|
||||
id: string
|
||||
descriptor: string
|
||||
type?: string
|
||||
subtype?: string
|
||||
isActive?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
/** Get Worker */
|
||||
export interface WorkdayGetWorkerParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
}
|
||||
|
||||
export interface WorkdayGetWorkerResponse extends ToolResponse {
|
||||
output: {
|
||||
worker: WorkdayWorker
|
||||
}
|
||||
}
|
||||
|
||||
/** List Workers */
|
||||
export interface WorkdayListWorkersParams extends WorkdayBaseParams {
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface WorkdayListWorkersResponse extends ToolResponse {
|
||||
output: {
|
||||
workers: WorkdayWorker[]
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
/** Create Pre-Hire */
|
||||
export interface WorkdayCreatePrehireParams extends WorkdayBaseParams {
|
||||
legalName: string
|
||||
email?: string
|
||||
phoneNumber?: string
|
||||
address?: string
|
||||
countryCode?: string
|
||||
}
|
||||
|
||||
export interface WorkdayCreatePrehireResponse extends ToolResponse {
|
||||
output: {
|
||||
preHireId: string
|
||||
descriptor: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Hire Employee */
|
||||
export interface WorkdayHireEmployeeParams extends WorkdayBaseParams {
|
||||
preHireId: string
|
||||
positionId: string
|
||||
hireDate: string
|
||||
employeeType?: string
|
||||
}
|
||||
|
||||
export interface WorkdayHireEmployeeResponse extends ToolResponse {
|
||||
output: {
|
||||
workerId: string
|
||||
employeeId: string
|
||||
eventId: string
|
||||
hireDate: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Update Worker */
|
||||
export interface WorkdayUpdateWorkerParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
fields: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface WorkdayUpdateWorkerResponse extends ToolResponse {
|
||||
output: {
|
||||
eventId: string
|
||||
workerId: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Assign Onboarding Plan */
|
||||
export interface WorkdayAssignOnboardingParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
onboardingPlanId: string
|
||||
actionEventId: string
|
||||
}
|
||||
|
||||
export interface WorkdayAssignOnboardingResponse extends ToolResponse {
|
||||
output: {
|
||||
assignmentId: string
|
||||
workerId: string
|
||||
planId: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Organizations */
|
||||
export interface WorkdayGetOrganizationsParams extends WorkdayBaseParams {
|
||||
type?: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface WorkdayGetOrganizationsResponse extends ToolResponse {
|
||||
output: {
|
||||
organizations: WorkdayOrganization[]
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
/** Change Job */
|
||||
export interface WorkdayChangeJobParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
effectiveDate: string
|
||||
newPositionId?: string
|
||||
newJobProfileId?: string
|
||||
newLocationId?: string
|
||||
newSupervisoryOrgId?: string
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface WorkdayChangeJobResponse extends ToolResponse {
|
||||
output: {
|
||||
eventId: string
|
||||
workerId: string
|
||||
effectiveDate: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Compensation */
|
||||
export interface WorkdayGetCompensationParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
}
|
||||
|
||||
export interface WorkdayGetCompensationResponse extends ToolResponse {
|
||||
output: {
|
||||
compensationPlans: Array<{
|
||||
id: string
|
||||
planName: string
|
||||
amount: number
|
||||
currency: string
|
||||
frequency: string
|
||||
[key: string]: unknown
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
/** Terminate Worker */
|
||||
export interface WorkdayTerminateWorkerParams extends WorkdayBaseParams {
|
||||
workerId: string
|
||||
terminationDate: string
|
||||
reason: string
|
||||
notificationDate?: string
|
||||
lastDayOfWork?: string
|
||||
}
|
||||
|
||||
export interface WorkdayTerminateWorkerResponse extends ToolResponse {
|
||||
output: {
|
||||
eventId: string
|
||||
workerId: string
|
||||
terminationDate: string
|
||||
}
|
||||
}
|
||||
78
apps/sim/tools/workday/update_worker.ts
Normal file
78
apps/sim/tools/workday/update_worker.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WorkdayUpdateWorkerParams, WorkdayUpdateWorkerResponse } from '@/tools/workday/types'
|
||||
|
||||
export const updateWorkerTool: ToolConfig<WorkdayUpdateWorkerParams, WorkdayUpdateWorkerResponse> =
|
||||
{
|
||||
id: 'workday_update_worker',
|
||||
name: 'Update Workday Worker',
|
||||
description: 'Update fields on an existing worker record in Workday.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
tenantUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday instance URL (e.g., https://wd5-impl-services1.workday.com)',
|
||||
},
|
||||
tenant: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Workday tenant name',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Integration System User password',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Worker ID to update',
|
||||
},
|
||||
fields: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields to update as JSON (e.g., {"businessTitle": "Senior Engineer", "primaryWorkEmail": "new@company.com"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/workday/update-worker',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => params,
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error ?? 'Workday API request failed')
|
||||
}
|
||||
return data
|
||||
},
|
||||
|
||||
outputs: {
|
||||
eventId: {
|
||||
type: 'string',
|
||||
description: 'Event ID of the change personal information business process',
|
||||
},
|
||||
workerId: {
|
||||
type: 'string',
|
||||
description: 'Worker ID that was updated',
|
||||
},
|
||||
},
|
||||
}
|
||||
34
bun.lock
34
bun.lock
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "simstudio",
|
||||
@@ -185,6 +184,7 @@
|
||||
"resend": "^4.1.2",
|
||||
"rss-parser": "3.13.0",
|
||||
"sharp": "0.34.3",
|
||||
"soap": "1.8.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "4.8.1",
|
||||
"ssh2": "^1.17.0",
|
||||
@@ -952,6 +952,8 @@
|
||||
|
||||
"@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
||||
|
||||
"@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="],
|
||||
|
||||
"@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="],
|
||||
|
||||
"@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="],
|
||||
@@ -1690,6 +1692,8 @@
|
||||
|
||||
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
|
||||
|
||||
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
|
||||
|
||||
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
|
||||
|
||||
"asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="],
|
||||
@@ -1712,7 +1716,9 @@
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
|
||||
"axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="],
|
||||
|
||||
"axios-ntlm": ["axios-ntlm@1.4.6", "", { "dependencies": { "axios": "^1.12.2", "des.js": "^1.1.0", "dev-null": "^0.1.1", "js-md4": "^0.3.2" } }, "sha512-4nR5cbVEBfPMTFkd77FEDpDuaR205JKibmrkaQyNwGcCx0szWNpRZaL0jZyMx4+mVY2PXHjRHuJafv9Oipl0Kg=="],
|
||||
|
||||
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
|
||||
|
||||
@@ -2020,6 +2026,8 @@
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
|
||||
@@ -2028,10 +2036,14 @@
|
||||
|
||||
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
||||
|
||||
"dev-null": ["dev-null@0.1.1", "", {}, "sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1464554", "", {}, "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw=="],
|
||||
|
||||
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
|
||||
|
||||
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
|
||||
|
||||
"dingbat-to-unicode": ["dingbat-to-unicode@1.0.1", "", {}, "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="],
|
||||
@@ -2252,6 +2264,8 @@
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="],
|
||||
@@ -2500,6 +2514,8 @@
|
||||
|
||||
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
|
||||
|
||||
"js-md4": ["js-md4@0.3.2", "", {}, "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA=="],
|
||||
|
||||
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
|
||||
|
||||
"js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="],
|
||||
@@ -2786,6 +2802,8 @@
|
||||
|
||||
"minimal-polyfills": ["minimal-polyfills@2.2.3", "", {}, "sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw=="],
|
||||
|
||||
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
|
||||
|
||||
"minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
@@ -3242,7 +3260,7 @@
|
||||
|
||||
"satori": ["satori@0.12.2", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.16", "css-to-react-native": "^3.0.0", "emoji-regex": "^10.2.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-wasm-web": "^0.3.3" } }, "sha512-3C/laIeE6UUe9A+iQ0A48ywPVCCMKCNSTU5Os101Vhgsjd3AAxGNjyq0uAA8kulMPK5n0csn8JlxPN9riXEjLA=="],
|
||||
|
||||
"sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
|
||||
|
||||
@@ -3318,6 +3336,8 @@
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"soap": ["soap@1.8.0", "", { "dependencies": { "axios": "^1.13.6", "axios-ntlm": "^1.4.6", "debug": "^4.4.3", "follow-redirects": "^1.15.11", "formidable": "^3.5.4", "sax": "^1.5.0", "whatwg-mimetype": "4.0.0", "xml-crypto": "^6.1.2" } }, "sha512-WRIzZm4M13a9j1t8yMdZZtbbkxNatXAhvtO8UXc/LvdfZ/Op1MqZS6qsAbILLsLTk3oLM/PRw0XOG0U53dAZzg=="],
|
||||
|
||||
"socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="],
|
||||
|
||||
"socket.io-adapter": ["socket.io-adapter@2.5.6", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.18.3" } }, "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ=="],
|
||||
@@ -3914,6 +3934,8 @@
|
||||
|
||||
"@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
|
||||
|
||||
"@paralleldrive/cuid2/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
||||
"@puppeteer/browsers/tar-fs": ["tar-fs@3.1.1", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg=="],
|
||||
|
||||
"@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
@@ -4326,6 +4348,8 @@
|
||||
|
||||
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||
|
||||
"twilio/axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
|
||||
|
||||
"twilio/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"twilio/xmlbuilder": ["xmlbuilder@13.0.2", "", {}, "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ=="],
|
||||
@@ -4344,6 +4368,10 @@
|
||||
|
||||
"xml-crypto/xpath": ["xpath@0.0.33", "", {}, "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA=="],
|
||||
|
||||
"xml-js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
|
||||
|
||||
"xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
|
||||
|
||||
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
Reference in New Issue
Block a user