Skip to content

Commit a25b149

Browse files
committed
fix(workday): coerce SOAP scalar strings to typed booleans/numbers
- XML parser returns leaf text as strings; `!"false"` evaluated to `false`, causing all organizations to report `isActive: false` - Add parseSoapBoolean and parseSoapNumber helpers and apply at consumer sites (Inactive, Total_Results) - Drop unused service/soapAction fields from WD_OPERATIONS map
1 parent 2710879 commit a25b149

3 files changed

Lines changed: 65 additions & 32 deletions

File tree

apps/sim/app/api/tools/workday/get-organizations/route.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
createWorkdaySoapClient,
1010
extractRefId,
1111
normalizeSoapArray,
12+
parseSoapBoolean,
13+
parseSoapNumber,
1214
type WorkdayOrganizationSoap,
1315
} from '@/tools/workday/soap'
1416

@@ -63,15 +65,18 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
6365
| undefined
6466
)
6567

66-
const organizations = orgsArray.map((o) => ({
67-
id: extractRefId(o.Organization_Reference) ?? null,
68-
descriptor: o.Organization_Descriptor ?? null,
69-
type: extractRefId(o.Organization_Data?.Organization_Type_Reference) ?? null,
70-
subtype: extractRefId(o.Organization_Data?.Organization_Subtype_Reference) ?? null,
71-
isActive: o.Organization_Data?.Inactive != null ? !o.Organization_Data.Inactive : null,
72-
}))
68+
const organizations = orgsArray.map((o) => {
69+
const inactive = parseSoapBoolean(o.Organization_Data?.Inactive)
70+
return {
71+
id: extractRefId(o.Organization_Reference) ?? null,
72+
descriptor: o.Organization_Descriptor ?? null,
73+
type: extractRefId(o.Organization_Data?.Organization_Type_Reference) ?? null,
74+
subtype: extractRefId(o.Organization_Data?.Organization_Subtype_Reference) ?? null,
75+
isActive: inactive == null ? null : !inactive,
76+
}
77+
})
7378

74-
const total = result?.Response_Results?.Total_Results ?? organizations.length
79+
const total = parseSoapNumber(result?.Response_Results?.Total_Results) ?? organizations.length
7580

7681
return NextResponse.json({
7782
success: true,

apps/sim/app/api/tools/workday/list-workers/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
createWorkdaySoapClient,
1010
extractRefId,
1111
normalizeSoapArray,
12+
parseSoapNumber,
1213
type WorkdayWorkerSoap,
1314
} from '@/tools/workday/soap'
1415

@@ -61,7 +62,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
6162
employmentData: w.Worker_Data?.Employment_Data ?? null,
6263
}))
6364

64-
const total = result?.Response_Results?.Total_Results ?? workers.length
65+
const total = parseSoapNumber(result?.Response_Results?.Total_Results) ?? workers.length
6566

6667
return NextResponse.json({
6768
success: true,

apps/sim/tools/workday/soap.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export type WorkdayServiceKey = keyof typeof WORKDAY_SERVICES
1515
export interface WorkdaySoapResult {
1616
Response_Data?: Record<string, unknown>
1717
Response_Results?: {
18-
Total_Results?: number
19-
Total_Pages?: number
20-
Page_Results?: number
21-
Page?: number
18+
Total_Results?: number | string
19+
Total_Pages?: number | string
20+
Page_Results?: number | string
21+
Page?: number | string
2222
}
2323
Event_Reference?: WorkdayReference
2424
Employee_Reference?: WorkdayReference
@@ -98,7 +98,7 @@ export interface WorkdayOrganizationSoap {
9898
export interface WorkdayOrganizationDataSoap {
9999
Organization_Type_Reference?: WorkdayReference
100100
Organization_Subtype_Reference?: WorkdayReference
101-
Inactive?: boolean
101+
Inactive?: boolean | string
102102
}
103103

104104
/**
@@ -110,24 +110,51 @@ export function normalizeSoapArray<T>(value: T | T[] | undefined): T[] {
110110
return Array.isArray(value) ? value : [value]
111111
}
112112

113-
const WD_OPERATIONS = {
114-
Get_Workers: { service: 'humanResources', soapAction: 'Get_Workers' },
115-
Get_Organizations: { service: 'humanResources', soapAction: 'Get_Organizations' },
116-
Put_Applicant: { service: 'recruiting', soapAction: 'Put_Applicant' },
117-
Hire_Employee: { service: 'staffing', soapAction: 'Hire_Employee' },
118-
Change_Job: { service: 'staffing', soapAction: 'Change_Job' },
119-
Terminate_Employee: { service: 'staffing', soapAction: 'Terminate_Employee' },
120-
Change_Personal_Information: {
121-
service: 'humanResources',
122-
soapAction: 'Change_Personal_Information',
123-
},
124-
Put_Onboarding_Plan_Assignment: {
125-
service: 'humanResources',
126-
soapAction: 'Put_Onboarding_Plan_Assignment',
127-
},
128-
} as const satisfies Record<string, { service: WorkdayServiceKey; soapAction: string }>
129-
130-
type WorkdayOperation = keyof typeof WD_OPERATIONS
113+
/**
114+
* Coerces a SOAP scalar to a boolean. The XML parser returns leaf text as strings,
115+
* so `"true"`/`"false"` must be normalized before boolean operations like negation.
116+
* Returns null when the value is null/undefined or unrecognized.
117+
*/
118+
export function parseSoapBoolean(value: unknown): boolean | null {
119+
if (value == null) return null
120+
if (typeof value === 'boolean') return value
121+
if (typeof value === 'string') {
122+
const trimmed = value.trim().toLowerCase()
123+
if (trimmed === 'true' || trimmed === '1') return true
124+
if (trimmed === 'false' || trimmed === '0') return false
125+
}
126+
return null
127+
}
128+
129+
/**
130+
* Coerces a SOAP scalar to a number. The XML parser returns leaf text as strings,
131+
* so numeric fields like `Total_Results` must be normalized before arithmetic.
132+
* Returns null when the value is null/undefined or not a finite number.
133+
*/
134+
export function parseSoapNumber(value: unknown): number | null {
135+
if (value == null) return null
136+
if (typeof value === 'number') return Number.isFinite(value) ? value : null
137+
if (typeof value === 'string') {
138+
const trimmed = value.trim()
139+
if (trimmed === '') return null
140+
const n = Number(trimmed)
141+
return Number.isFinite(n) ? n : null
142+
}
143+
return null
144+
}
145+
146+
const WD_OPERATIONS = [
147+
'Get_Workers',
148+
'Get_Organizations',
149+
'Put_Applicant',
150+
'Hire_Employee',
151+
'Change_Job',
152+
'Terminate_Employee',
153+
'Change_Personal_Information',
154+
'Put_Onboarding_Plan_Assignment',
155+
] as const
156+
157+
type WorkdayOperation = (typeof WD_OPERATIONS)[number]
131158

132159
type SoapOperationFn = (
133160
args: Record<string, unknown>

0 commit comments

Comments
 (0)