diff --git a/example/src/ContractorOnboarding.tsx b/example/src/ContractorOnboarding.tsx index e340fc1c8..8eecbaf3d 100644 --- a/example/src/ContractorOnboarding.tsx +++ b/example/src/ContractorOnboarding.tsx @@ -31,6 +31,7 @@ import { import Flag from 'react-flagpack'; import React, { useState } from 'react'; import { RemoteFlows } from './RemoteFlows'; +import { getUrlParam } from './utils/urlState'; import { AlertError } from './AlertError'; import { ReviewContractorOnboardingStep } from './ReviewContractorOnboardingStep'; import { PricingPlanCard } from './components/PricingPlanCard'; @@ -821,7 +822,9 @@ export const ContractorOnboardingWithProps = ({ export const ContractorOnboardingForm = () => { const [formData, setFormData] = useState({ employmentId: - import.meta.env.VITE_CONTRACTOR_MANAGEMENT_EMPLOYMENT_ID || '', // use your own employment ID + getUrlParam('employmentId') || + import.meta.env.VITE_CONTRACTOR_MANAGEMENT_EMPLOYMENT_ID || + '', // use your own employment ID externalId: '', partnerExternalId: '', }); diff --git a/scripts/seed-onboarding.ts b/scripts/seed-onboarding.ts index ac7aed7b1..ab59549a0 100644 --- a/scripts/seed-onboarding.ts +++ b/scripts/seed-onboarding.ts @@ -31,6 +31,17 @@ * npm run seed:onboarding -- --country=DEU * npm run seed:onboarding -- --country=ESP --basic-info-version=4 * + * Pass --type=contractor to instead create a contractor employment from the + * contractor_basic_information schema and stop there, mirroring the + * basic_information step of src/flows/ContractorOnboarding/hooks.tsx. + * ir35 (GBR) and nationality_status (SAU/KWT/OMN/QAT/BHR) aren't in that + * schema - ContractorOnboarding's jsfModify adds them client-side and the + * hook writes them to the contractor contract document after creating the + * employment - so this does the same with fixed answers that avoid the IR35 + * SDS upload and the non-national warning. + * + * npm run seed:onboarding -- --country=GBR --type=contractor --env=sandbox + * * By default this proxies through a locally running `example` dev server * (BASE_URL, `example/.env`'s VITE_REMOTE_GATEWAY decides which gateway that * is - easy to lose track of). @@ -89,7 +100,13 @@ const stringArg = (key: string): string | undefined => { return typeof value === 'string' ? value : undefined; }; const COUNTRY = (stringArg('country') || 'DEU').toUpperCase(); -const BASIC_INFO_VERSION = Number(stringArg('basic-info-version') || 4); +const TYPE = stringArg('type') || 'employee'; +if (TYPE !== 'employee' && TYPE !== 'contractor') { + throw new Error(`--type must be employee or contractor (got ${TYPE}).`); +} +const BASIC_INFO_VERSION = Number( + stringArg('basic-info-version') || (TYPE === 'contractor' ? 1 : 4), +); if ('env' in args && stringArg('env') === undefined) { throw new Error( '--env requires a value, e.g. --env=sandbox (got a bare --env flag).', @@ -206,7 +223,86 @@ async function findSafeStartDate(): Promise { return pickSafeDate(holidayDates); } -async function main() { +const CONTRACTOR_NATIONALITY_COUNTRIES = ['SAU', 'KWT', 'OMN', 'QAT', 'BHR']; + +function contractorContractDocumentSeed(): Record | undefined { + if (COUNTRY === 'GBR') return { ir_35: 'exempt' }; + if (CONTRACTOR_NATIONALITY_COUNTRIES.includes(COUNTRY)) { + return { nationality: 'national' }; + } + return undefined; +} + +async function seedContractor() { + console.log(`Fetching contractor_basic_information schema for ${COUNTRY}...`); + const basicInfoSchema = await fetchSchema( + 'contractor_basic_information', + BASIC_INFO_VERSION, + ); + const startDate = await findSafeStartDate(); + const { values: basicInformation, skipped } = fillSchema(basicInfoSchema, { + provisional_start_date: startDate, + }); + console.log( + 'basic_information payload:', + JSON.stringify(basicInformation, null, 2), + ); + if (skipped.length) { + console.log('Skipped (unfillable) fields:', skipped.join(', ')); + } + + console.log('\nCreating contractor employment...'); + const created = await api<{ data?: { employment?: { id?: string } } }>( + 'POST', + '/v1/employments', + { + query: { json_schema_version: BASIC_INFO_VERSION }, + body: { + basic_information: basicInformation, + type: 'contractor', + country_code: COUNTRY, + }, + }, + ); + const employmentId = created?.data?.employment?.id; + if (!employmentId) { + throw new Error( + `Could not find employment id in response: ${JSON.stringify(created)}`, + ); + } + console.log(`Employment created: ${employmentId}`); + + const contractDocument = contractorContractDocumentSeed(); + if (contractDocument) { + console.log( + `\nSetting ${Object.keys(contractDocument).join(', ')} on the contract document...`, + ); + await api( + 'POST', + `/v1/contractors/employments/${employmentId}/contract-documents`, + { body: { contract_document: contractDocument } }, + ); + } + + console.log( + `\nDone. Contractor employment ${employmentId} for ${COUNTRY} has basic_information filled in.`, + ); + const appUrl = process.env.VITE_APP_URL || (ENV ? undefined : BASE_URL); + if (appUrl) { + console.log( + `\nOpen this link (Employment ID is prefilled via ?employmentId=):\n\n` + + ` ${appUrl}/?demo=contract-onboarding&employmentId=${employmentId}\n`, + ); + } else { + console.log( + `\nOpen your app's contractor onboarding demo and enter this Employment ID ` + + `(?employmentId=${employmentId} also works as a query param).\n` + + `Tip: set VITE_APP_URL= in .env.${ENV} to get a ready-to-click link next time.`, + ); + } +} + +async function seedEmployee() { console.log(`Fetching employment_basic_information schema for ${COUNTRY}...`); const basicInfoSchema = await fetchSchema( 'employment_basic_information', @@ -311,6 +407,10 @@ async function main() { } } +function main() { + return TYPE === 'contractor' ? seedContractor() : seedEmployee(); +} + main().catch((err: unknown) => { console.error('\nFailed:', err instanceof Error ? err.message : err); if (err instanceof ApiError && err.body) {