Skip to content
16 changes: 16 additions & 0 deletions packages/core/src/api/contractorCurrencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { selectContractorCurrencyCodes } from './contractorCurrencies';

describe('selectContractorCurrencyCodes', () => {
it('returns only the currency codes', () => {
const response = {
data: {
data: [
{ code: 'EUR', source: 'guaranteed' },
{ code: 'USD', source: 'guaranteed' },
],
},
} as Parameters<typeof selectContractorCurrencyCodes>[0];

expect(selectContractorCurrencyCodes(response)).toEqual(['EUR', 'USD']);
});
});
29 changes: 29 additions & 0 deletions packages/core/src/api/contractorCurrencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getV1ContractorsEmploymentsEmploymentIdContractorCurrencies } from '../client/sdk.gen';
import type { Client } from '../client/client';

export const contractorCurrenciesOptions = (
client: Client,
employmentId: string,
) => ({
queryKey: ['contractor-currencies', employmentId] as const,
queryFn: () =>
getV1ContractorsEmploymentsEmploymentIdContractorCurrencies({
client,
path: { employment_id: employmentId },
query: {
restrict_to_guaranteed_pay_out_currencies: false,
},
}),
});

type ContractorCurrenciesResponse = Awaited<
ReturnType<ReturnType<typeof contractorCurrenciesOptions>['queryFn']>
>;

export const selectContractorCurrencies = ({
data,
}: ContractorCurrenciesResponse) => data?.data;

export const selectContractorCurrencyCodes = (
response: ContractorCurrenciesResponse,
) => selectContractorCurrencies(response)?.map((currency) => currency.code);
21 changes: 21 additions & 0 deletions packages/core/src/api/employment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getV1EmploymentsEmploymentId } from '../client/sdk.gen';
import type { Client } from '../client/client';
import type { $TSFixMe } from '../types';

export const employmentOptions = (
client: Client,
employmentId: string,
queryParams?: $TSFixMe,
) => ({
queryKey: ['employment', employmentId] as const,
retry: false as const,
queryFn: () =>
getV1EmploymentsEmploymentId({
client,
headers: {
Authorization: ``,
},
path: { employment_id: employmentId },
query: queryParams,
}),
});
22 changes: 22 additions & 0 deletions packages/core/src/api/invoiceSchedules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { selectExistingInvoiceSchedule } from './invoiceSchedules';

describe('selectExistingInvoiceSchedule', () => {
it('returns the first schedule that is still editable', () => {
const response = {
data: {
data: {
contractor_invoice_schedules: [
{ id: 'done', status: 'completed' },
{ id: 'open', status: 'processing' },
{ id: 'later', status: 'pending_company_action' },
],
},
},
} as Parameters<typeof selectExistingInvoiceSchedule>[0];

expect(selectExistingInvoiceSchedule(response)).toEqual({
id: 'open',
status: 'processing',
});
});
});
132 changes: 132 additions & 0 deletions packages/core/src/api/invoiceSchedules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
getV1ContractorInvoiceSchedules,
patchV1ContractorInvoiceSchedulesId2,
postV1ContractorInvoiceSchedules,
postV1EmploymentsEmploymentIdContractorInvoicesPreview,
} from '../client/sdk.gen';
import type {
BulkContractorInvoiceScheduleCreateParams,
ContractorInvoiceScheduleCreateParams,
PreviewContractorInvoiceParams,
UpdateScheduleContractorInvoiceParams,
} from '../client/types.gen';
import type { Client } from '../client/client';
import { INVOICE_SCHEDULE_STATUS } from '../domains/invoice-schedule/constants';
import {
buildInvoicePreviewPayload,
buildInvoiceSchedulePayload,
} from '../domains/invoice-schedule/utils';
import type { FieldValues } from '../types';

export const invoiceSchedulesOptions = (
client: Client,
employmentId: string,
) => ({
queryKey: ['contractor-invoice-schedules', employmentId] as const,
queryFn: async () => {
const response = await getV1ContractorInvoiceSchedules({
client,
query: { employment_id: employmentId },
});

if (response.error || !response.data) {
throw new Error('Failed to fetch invoice schedules');
}

return response;
},
});

type InvoiceSchedulesResponse = Awaited<
ReturnType<ReturnType<typeof invoiceSchedulesOptions>['queryFn']>
>;

const EDITABLE_STATUSES: string[] = [
INVOICE_SCHEDULE_STATUS.PENDING_CONTRACTOR_ACTION,
INVOICE_SCHEDULE_STATUS.PROCESSING,
INVOICE_SCHEDULE_STATUS.PENDING_COMPANY_ACTION,
];

export const selectExistingInvoiceSchedule = (
response: InvoiceSchedulesResponse,
) =>
response.data?.data?.contractor_invoice_schedules?.find((schedule) =>
EDITABLE_STATUSES.includes(schedule.status),
);

export const createInvoiceScheduleMutation = (client: Client) => ({
mutationFn: async ({
employmentId,
values,
}: {
employmentId: string;
values: FieldValues;
}) => {
const payload: BulkContractorInvoiceScheduleCreateParams = {
contractor_invoice_schedules: [
{
employment_id: employmentId,
...buildInvoiceSchedulePayload(values),
} as ContractorInvoiceScheduleCreateParams,
],
};

return postV1ContractorInvoiceSchedules({
client,
body: payload,
});
},
});

export const updateInvoiceScheduleMutation = (client: Client) => ({
mutationFn: async ({
scheduleId,
values,
}: {
scheduleId: string;
values: FieldValues;
}) => {
const payload: UpdateScheduleContractorInvoiceParams =
buildInvoiceSchedulePayload(values);

return patchV1ContractorInvoiceSchedulesId2({
client,
path: { id: scheduleId },
body: payload,
});
},
});

export const skipInvoiceScheduleMutation = (client: Client) => ({
mutationFn: async ({ scheduleId }: { scheduleId: string }) => {
const payload: UpdateScheduleContractorInvoiceParams = {
status: 'deleted',
};

return patchV1ContractorInvoiceSchedulesId2({
client,
path: { id: scheduleId },
body: payload,
});
},
});

export const previewContractorInvoiceMutation = (client: Client) => ({
mutationFn: async ({
employmentId,
values,
}: {
employmentId: string;
values: FieldValues;
}) => {
const payload = buildInvoicePreviewPayload(
values,
) as PreviewContractorInvoiceParams;

return postV1EmploymentsEmploymentIdContractorInvoicesPreview({
client,
path: { employment_id: employmentId },
body: payload,
});
},
});
16 changes: 14 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,26 @@ export type {
JSONSchemaFormResultWithFieldsets,
UploadedFileReference,
} from './types';
export { INVOICE_SCHEDULE_STATUS } from './domains/invoice-schedule/constants';
export { buildCreateInvoiceScheduleSchema } from './domains/invoice-schedule/jsonSchema';
export {
buildInvoicePreviewPayload,
buildInvoiceSchedulePayload,
buildInvoiceScheduleFormValues,
} from './domains/invoice-schedule/utils';
export type { ContractorInvoicePreview } from './domains/invoice-schedule/types';
export { clearBase64Data, formatCurrency } from './format';
export { createClient } from './createClient';
export type { AuthResponse, CreateClientOptions } from './createClient';
export {
contractorCurrenciesOptions,
selectContractorCurrencies,
selectContractorCurrencyCodes,
} from './api/contractorCurrencies';
export { employmentOptions } from './api/employment';
export {
createInvoiceScheduleMutation,
invoiceSchedulesOptions,
previewContractorInvoiceMutation,
selectExistingInvoiceSchedule,
skipInvoiceScheduleMutation,
updateInvoiceScheduleMutation,
} from './api/invoiceSchedules';
33 changes: 13 additions & 20 deletions src/common/api/contractor-contract-details.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { useMemo } from 'react';
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import { FieldValues } from 'react-hook-form';
import { getV1CountriesCountryCodeContractorContractDetails } from '@/src/client';
import {
getV1CountriesCountryCodeContractorContractDetails,
getV1ContractorsEmploymentsEmploymentIdContractorCurrencies,
} from '@/src/client';
contractorCurrenciesOptions,
selectContractorCurrencies,
} from '@remoteoss/remote-flows-core';
import { useClient } from '@/src/context';
import { withClient } from '@/src/lib/withClient';
import { Client } from '@/src/client/client';
// oxlint-disable-next-line no-restricted-imports -- TODO: move onto useHeadlessForm, see docs/USE_HEADLESS_FORM_ROLLOUT.md
import { createHeadlessForm } from '@/src/common/createHeadlessForm';
Expand All @@ -15,31 +17,22 @@ import {
} from '@/src/flows/types';
import { $TSFixMe } from '@/src/types/remoteFlows';

export const useContractorCurrenciesOptions = withClient(
contractorCurrenciesOptions,
);

export const useContractorCurrencies = ({
employmentId,
options,
}: {
employmentId: string;
options?: { queryOptions?: { enabled?: boolean } };
}) => {
const { client } = useClient();
return useQuery({
queryKey: ['contractor-currencies', employmentId],
queryFn: async () => {
return getV1ContractorsEmploymentsEmploymentIdContractorCurrencies({
client: client as Client,
path: { employment_id: employmentId },
query: {
restrict_to_guaranteed_pay_out_currencies: false,
},
});
},
}) =>
useQuery({
...useContractorCurrenciesOptions(employmentId),
enabled: options?.queryOptions?.enabled,
select: ({ data }) => {
return data?.data;
},
select: selectContractorCurrencies,
});
};

/**
* Get the contractor onboarding details schema for a given country
Expand Down
15 changes: 2 additions & 13 deletions src/common/api/employment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
getV1EmploymentsEmploymentId,
GetV1EmploymentsEmploymentIdResponse,
getV2EmploymentsEmploymentIdBasicInformation,
GetV2EmploymentsEmploymentIdBasicInformationResponse,
Expand All @@ -9,6 +8,7 @@ import { useClient } from '@/src/context';
import { $TSFixMe } from '@/src/types/remoteFlows';
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import { Client } from '@/src/client/client';
import { employmentOptions } from '@remoteoss/remote-flows-core';

/**
* Hook to retrieve employment details for a specific employment ID.
Expand All @@ -32,18 +32,7 @@ export const useEmploymentQuery = ({
> => {
const { client } = useClient();
return useQuery({
queryKey: ['employment', employmentId],
retry: false,
queryFn: () => {
return getV1EmploymentsEmploymentId({
client: client as Client,
headers: {
Authorization: ``,
},
path: { employment_id: employmentId },
query: queryParams,
});
},
...employmentOptions(client as Client, employmentId, queryParams),
enabled: !!employmentId && enabled,
select: ({ data }) => data?.data?.employment,
});
Expand Down
Loading
Loading