476 lines
16 KiB
TypeScript
476 lines
16 KiB
TypeScript
import type { RequestFn } from "./utils";
|
|
|
|
export type PayrollLineKind =
|
|
| "earning"
|
|
| "adjustment_credit"
|
|
| "adjustment_debit"
|
|
| "deduction"
|
|
| "payment";
|
|
|
|
export type PayrollPeriodSummary = {
|
|
code: string;
|
|
name: string;
|
|
date_from: string;
|
|
date_to: string;
|
|
pay_date: string | null;
|
|
};
|
|
|
|
export type PayrollAcknowledgement = {
|
|
viewed_at: string | null;
|
|
acknowledged_at: string | null;
|
|
disputed_at: string | null;
|
|
};
|
|
|
|
export type PayrollStatementSummary = {
|
|
public_id: string;
|
|
period: PayrollPeriodSummary;
|
|
revision: number;
|
|
published_at: string;
|
|
currency: string;
|
|
payable: string;
|
|
acknowledgement: PayrollAcknowledgement | null;
|
|
};
|
|
|
|
export type PayrollStatementLine = {
|
|
code: string;
|
|
label: string;
|
|
kind: PayrollLineKind;
|
|
amount: string;
|
|
source_period_from: string | null;
|
|
source_period_to: string | null;
|
|
is_retro: boolean;
|
|
calculated: boolean;
|
|
};
|
|
|
|
export type PayrollStatement = PayrollStatementSummary & {
|
|
point_delta: string | null;
|
|
gross_before_adjustments: string;
|
|
adjustment_total: string;
|
|
gross_total: string;
|
|
deduction_total: string;
|
|
net_pay: string;
|
|
payment_total: string;
|
|
lines: PayrollStatementLine[];
|
|
};
|
|
|
|
export type PayrollStatementPage = {
|
|
count: number;
|
|
next: string | null;
|
|
previous: string | null;
|
|
results: PayrollStatementSummary[];
|
|
};
|
|
|
|
export type PayrollApprovalStatus = "draft" | "approved" | "voided";
|
|
export type PayrollPeriodStatus = "open" | "calculated" | "review" | "approved" | "published" | "closed";
|
|
export type PayrollRunStatus = "calculated" | "review" | "approved" | "published" | "returned" | "superseded";
|
|
|
|
export type PayrollAdminUserRef = {
|
|
id: number;
|
|
display_name: string;
|
|
};
|
|
|
|
export type PayrollAdminEmployee = PayrollAdminUserRef & {
|
|
position: string | null;
|
|
department?: string | null;
|
|
};
|
|
|
|
export type PayrollAdminPeriod = PayrollPeriodSummary & {
|
|
id: number;
|
|
currency: string;
|
|
status: PayrollPeriodStatus;
|
|
lock_version: number;
|
|
current_run_id: number | null;
|
|
};
|
|
|
|
export type PayrollAdminRun = {
|
|
id: number;
|
|
period_id: number;
|
|
revision: number;
|
|
status: PayrollRunStatus;
|
|
employee_count: number;
|
|
gross_total?: string | null;
|
|
deduction_total?: string | null;
|
|
payable_total?: string | null;
|
|
requested_by: PayrollAdminUserRef;
|
|
requested_at: string;
|
|
approved_by: PayrollAdminUserRef | null;
|
|
approved_at: string | null;
|
|
published_by: PayrollAdminUserRef | null;
|
|
published_at: string | null;
|
|
recalculation_reason: string;
|
|
self_approval_overridden: boolean;
|
|
};
|
|
|
|
export type PayrollAdminPermissions = {
|
|
manage_inputs: boolean;
|
|
approve_inputs: boolean;
|
|
override_approval: boolean;
|
|
calculate: boolean;
|
|
approve_run: boolean;
|
|
publish: boolean;
|
|
view_all: boolean;
|
|
audit: boolean;
|
|
};
|
|
|
|
export type PayrollReadinessGroup = {
|
|
ready: boolean;
|
|
total: number;
|
|
approved: number;
|
|
draft: number;
|
|
missing_employee_ids?: number[];
|
|
};
|
|
|
|
export type PayrollCalculationBlocker = {
|
|
code: string;
|
|
message: string;
|
|
employee_id?: number;
|
|
};
|
|
|
|
export type PayrollAdminWorkspace = {
|
|
permissions: PayrollAdminPermissions;
|
|
employees: PayrollAdminEmployee[];
|
|
components: PayrollComponent[];
|
|
periods: PayrollAdminPeriod[];
|
|
selected_period: PayrollAdminPeriod | null;
|
|
readiness: {
|
|
rates: PayrollReadinessGroup;
|
|
work_records: PayrollReadinessGroup;
|
|
input_lines: Pick<PayrollReadinessGroup, "approved" | "draft">;
|
|
calculation: {
|
|
ready: boolean;
|
|
blockers: PayrollCalculationBlocker[];
|
|
};
|
|
};
|
|
current_run: PayrollAdminRun | null;
|
|
runs: PayrollAdminRun[];
|
|
summary: {
|
|
employee_count: number;
|
|
gross_total: string | null;
|
|
deduction_total: string | null;
|
|
payable_total: string | null;
|
|
} | null;
|
|
pending_approvals: {
|
|
rates: number;
|
|
work_records: number;
|
|
input_lines: number;
|
|
};
|
|
};
|
|
|
|
export type PayrollComponent = {
|
|
id: number;
|
|
code: string;
|
|
name: string;
|
|
kind: PayrollLineKind;
|
|
requires_reason: boolean;
|
|
is_active: boolean;
|
|
display_order: number;
|
|
};
|
|
|
|
export type PayrollAdminPayRate = {
|
|
id: number;
|
|
employee_id?: number;
|
|
employee: PayrollAdminEmployee;
|
|
rate_code: string;
|
|
amount: string;
|
|
point_rate: string;
|
|
currency: string;
|
|
effective_from: string;
|
|
revision: number;
|
|
status: PayrollApprovalStatus;
|
|
reason: string;
|
|
lock_version: number;
|
|
created_by: PayrollAdminUserRef;
|
|
created_at: string;
|
|
approved_by: PayrollAdminUserRef | null;
|
|
approved_at: string | null;
|
|
self_approval_overridden: boolean;
|
|
};
|
|
|
|
export type PayrollAdminWorkRecord = {
|
|
id: number;
|
|
period_id: number;
|
|
employee_id?: number;
|
|
employee: PayrollAdminEmployee;
|
|
target_points: string;
|
|
actual_points: string;
|
|
expected_point_amount: string | null;
|
|
expected_gross: string | null;
|
|
expected_recalculated_gross: string | null;
|
|
expected_payable: string | null;
|
|
revision: number;
|
|
status: PayrollApprovalStatus;
|
|
source?: string;
|
|
replaces_id?: number | null;
|
|
reason: string;
|
|
lock_version: number;
|
|
created_by: PayrollAdminUserRef;
|
|
created_at: string;
|
|
approved_by: PayrollAdminUserRef | null;
|
|
approved_at: string | null;
|
|
self_approval_overridden: boolean;
|
|
};
|
|
|
|
export type PayrollAttendanceWorkMode = "missing_only" | "replace_existing";
|
|
|
|
export type PayrollAttendanceWorkAction =
|
|
| "create"
|
|
| "update"
|
|
| "revise"
|
|
| "unchanged"
|
|
| "skip"
|
|
| "blocked";
|
|
|
|
export type PayrollAttendanceWorkIssue = {
|
|
code: string;
|
|
message: string;
|
|
};
|
|
|
|
export type PayrollAttendanceWorkModeSummary = {
|
|
create: number;
|
|
update: number;
|
|
revise: number;
|
|
unchanged: number;
|
|
skip: number;
|
|
blocked: number;
|
|
changes: number;
|
|
};
|
|
|
|
export type PayrollAttendanceExistingWorkRecord = {
|
|
id: number;
|
|
revision: number;
|
|
status: PayrollApprovalStatus;
|
|
source?: string;
|
|
target_points: string;
|
|
actual_points: string;
|
|
created_by?: PayrollAdminUserRef;
|
|
};
|
|
|
|
export type PayrollAttendanceWorkPreviewItem = {
|
|
employee: PayrollAdminEmployee;
|
|
target_points: string;
|
|
actual_points: string;
|
|
expected_hours: string;
|
|
worked_hours: string;
|
|
attendance_days: number;
|
|
effective_workdays: number;
|
|
technical_issue_days: number;
|
|
warnings: PayrollAttendanceWorkIssue[];
|
|
blockers: PayrollAttendanceWorkIssue[];
|
|
existing_record: PayrollAttendanceExistingWorkRecord | null;
|
|
actions: Record<PayrollAttendanceWorkMode, PayrollAttendanceWorkAction>;
|
|
};
|
|
|
|
export type PayrollAttendanceWorkPreview = {
|
|
period_id: number;
|
|
generated_at: string;
|
|
preview_token: string;
|
|
policy: {
|
|
code: string;
|
|
label: string;
|
|
description: string;
|
|
};
|
|
summary: {
|
|
attendance_employees: number;
|
|
existing: number;
|
|
blocked: number;
|
|
modes: Record<PayrollAttendanceWorkMode, PayrollAttendanceWorkModeSummary>;
|
|
};
|
|
items: PayrollAttendanceWorkPreviewItem[];
|
|
};
|
|
|
|
export type PayrollAttendanceWorkApplyPayload = {
|
|
mode: PayrollAttendanceWorkMode;
|
|
preview_token: string;
|
|
expected_period_lock_version: number;
|
|
reason: string;
|
|
};
|
|
|
|
export type PayrollAttendanceWorkApplyResult = {
|
|
mode: PayrollAttendanceWorkMode;
|
|
summary: {
|
|
created: number;
|
|
updated: number;
|
|
revised: number;
|
|
unchanged: number;
|
|
skipped: number;
|
|
blocked: number;
|
|
};
|
|
records: PayrollAdminWorkRecord[];
|
|
};
|
|
|
|
export type PayrollAdminInputLine = {
|
|
id: number;
|
|
period_id: number;
|
|
employee: PayrollAdminEmployee;
|
|
employee_id?: number;
|
|
component: PayrollComponent;
|
|
component_id?: number;
|
|
amount: string;
|
|
relates_to_period_id: number | null;
|
|
reason: string;
|
|
status: PayrollApprovalStatus;
|
|
lock_version: number;
|
|
created_by: PayrollAdminUserRef;
|
|
created_at: string;
|
|
approved_by: PayrollAdminUserRef | null;
|
|
approved_at: string | null;
|
|
self_approval_overridden: boolean;
|
|
};
|
|
|
|
export type PayrollAdminPage<T> = {
|
|
count: number;
|
|
next: string | null;
|
|
previous: string | null;
|
|
results: T[];
|
|
};
|
|
|
|
export type PayrollAdminListPayload<T> = PayrollAdminPage<T> | T[];
|
|
|
|
export type PayrollPeriodWrite = {
|
|
code: string;
|
|
name: string;
|
|
date_from: string;
|
|
date_to: string;
|
|
pay_date: string | null;
|
|
currency: string;
|
|
};
|
|
|
|
export type PayrollPayRateWrite = {
|
|
employee_id: number;
|
|
rate_code: string;
|
|
amount: string;
|
|
point_rate: string;
|
|
currency: string;
|
|
effective_from: string;
|
|
reason: string;
|
|
};
|
|
|
|
export type PayrollWorkRecordWrite = {
|
|
period_id: number;
|
|
employee_id: number;
|
|
target_points: string;
|
|
actual_points: string;
|
|
expected_point_amount?: string | null;
|
|
expected_gross?: string | null;
|
|
expected_recalculated_gross?: string | null;
|
|
expected_payable?: string | null;
|
|
reason: string;
|
|
};
|
|
|
|
export type PayrollInputLineWrite = {
|
|
period_id: number;
|
|
employee_id: number;
|
|
component_id: number;
|
|
amount: string;
|
|
relates_to_period_id?: number | null;
|
|
reason: string;
|
|
};
|
|
|
|
type PayrollAdminListParams = {
|
|
period_id?: number;
|
|
search?: string;
|
|
status?: PayrollApprovalStatus | "";
|
|
};
|
|
|
|
const adminPrefix = "/api/v1/finance/payroll/admin";
|
|
|
|
function buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {
|
|
const search = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null && value !== "") search.set(key, String(value));
|
|
});
|
|
const query = search.toString();
|
|
return query ? `?${query}` : "";
|
|
}
|
|
|
|
function jsonOptions(method: "POST" | "PATCH", payload: object = {}): RequestInit {
|
|
return { method, body: JSON.stringify(payload) };
|
|
}
|
|
|
|
export function createFinanceApi(request: RequestFn) {
|
|
return {
|
|
getMyPayrollStatements: (): Promise<PayrollStatementPage | PayrollStatementSummary[]> =>
|
|
request("/api/v1/finance/payroll/me/statements/?page_size=100"),
|
|
getMyPayrollStatement: (publicId: string): Promise<PayrollStatement> =>
|
|
request(`/api/v1/finance/payroll/me/statements/${encodeURIComponent(publicId)}/`),
|
|
acknowledgeMyPayrollStatement: (publicId: string): Promise<PayrollAcknowledgement> =>
|
|
request(
|
|
`/api/v1/finance/payroll/me/statements/${encodeURIComponent(publicId)}/acknowledge/`,
|
|
{ method: "POST" },
|
|
),
|
|
getPayrollAdminWorkspace: (periodId?: number): Promise<PayrollAdminWorkspace> =>
|
|
request(`${adminPrefix}/workspace/${buildQuery({ period_id: periodId })}`),
|
|
getPayrollAdminPeriods: (): Promise<PayrollAdminListPayload<PayrollAdminPeriod>> =>
|
|
request(`${adminPrefix}/periods/`),
|
|
createPayrollAdminPeriod: (payload: PayrollPeriodWrite): Promise<PayrollAdminPeriod> =>
|
|
request(`${adminPrefix}/periods/`, jsonOptions("POST", payload)),
|
|
updatePayrollAdminPeriod: (
|
|
periodId: number,
|
|
payload: Partial<PayrollPeriodWrite> & { expected_lock_version: number },
|
|
): Promise<PayrollAdminPeriod> =>
|
|
request(`${adminPrefix}/periods/${periodId}/`, jsonOptions("PATCH", payload)),
|
|
getPayrollAdminPayRates: (params: PayrollAdminListParams = {}): Promise<PayrollAdminListPayload<PayrollAdminPayRate>> =>
|
|
request(`${adminPrefix}/pay-rates/${buildQuery({ ...params, page_size: 200 })}`),
|
|
createPayrollAdminPayRate: (payload: PayrollPayRateWrite): Promise<PayrollAdminPayRate> =>
|
|
request(`${adminPrefix}/pay-rates/`, jsonOptions("POST", payload)),
|
|
updatePayrollAdminPayRate: (
|
|
rateId: number,
|
|
payload: Partial<Omit<PayrollPayRateWrite, "employee_id" | "rate_code" | "currency">> & { expected_lock_version: number },
|
|
): Promise<PayrollAdminPayRate> =>
|
|
request(`${adminPrefix}/pay-rates/${rateId}/`, jsonOptions("PATCH", payload)),
|
|
approvePayrollAdminPayRate: (rateId: number, expectedLockVersion: number): Promise<PayrollAdminPayRate> =>
|
|
request(`${adminPrefix}/pay-rates/${rateId}/approve/`, jsonOptions("POST", { expected_lock_version: expectedLockVersion })),
|
|
revisePayrollAdminPayRate: (rateId: number, reason: string): Promise<PayrollAdminPayRate> =>
|
|
request(`${adminPrefix}/pay-rates/${rateId}/revise/`, jsonOptions("POST", { reason })),
|
|
getPayrollAdminWorkRecords: (params: PayrollAdminListParams = {}): Promise<PayrollAdminListPayload<PayrollAdminWorkRecord>> =>
|
|
request(`${adminPrefix}/work-records/${buildQuery({ ...params, page_size: 200 })}`),
|
|
createPayrollAdminWorkRecord: (payload: PayrollWorkRecordWrite): Promise<PayrollAdminWorkRecord> =>
|
|
request(`${adminPrefix}/work-records/`, jsonOptions("POST", payload)),
|
|
updatePayrollAdminWorkRecord: (
|
|
recordId: number,
|
|
payload: Partial<Omit<PayrollWorkRecordWrite, "period_id" | "employee_id">> & { expected_lock_version: number },
|
|
): Promise<PayrollAdminWorkRecord> =>
|
|
request(`${adminPrefix}/work-records/${recordId}/`, jsonOptions("PATCH", payload)),
|
|
approvePayrollAdminWorkRecord: (recordId: number, expectedLockVersion: number): Promise<PayrollAdminWorkRecord> =>
|
|
request(`${adminPrefix}/work-records/${recordId}/approve/`, jsonOptions("POST", { expected_lock_version: expectedLockVersion })),
|
|
revisePayrollAdminWorkRecord: (recordId: number, reason: string): Promise<PayrollAdminWorkRecord> =>
|
|
request(`${adminPrefix}/work-records/${recordId}/revise/`, jsonOptions("POST", { reason })),
|
|
getPayrollAdminAttendanceWorkPreview: (periodId: number): Promise<PayrollAttendanceWorkPreview> =>
|
|
request(`${adminPrefix}/periods/${periodId}/attendance-work-records/`),
|
|
applyPayrollAdminAttendanceWork: (
|
|
periodId: number,
|
|
payload: PayrollAttendanceWorkApplyPayload,
|
|
): Promise<PayrollAttendanceWorkApplyResult> =>
|
|
request(
|
|
`${adminPrefix}/periods/${periodId}/attendance-work-records/`,
|
|
jsonOptions("POST", payload),
|
|
),
|
|
getPayrollAdminInputLines: (params: PayrollAdminListParams = {}): Promise<PayrollAdminListPayload<PayrollAdminInputLine>> =>
|
|
request(`${adminPrefix}/input-lines/${buildQuery({ ...params, page_size: 200 })}`),
|
|
createPayrollAdminInputLine: (payload: PayrollInputLineWrite): Promise<PayrollAdminInputLine> =>
|
|
request(`${adminPrefix}/input-lines/`, jsonOptions("POST", payload)),
|
|
updatePayrollAdminInputLine: (
|
|
lineId: number,
|
|
payload: Partial<Omit<PayrollInputLineWrite, "period_id" | "employee_id" | "component_id">> & { expected_lock_version: number },
|
|
): Promise<PayrollAdminInputLine> =>
|
|
request(`${adminPrefix}/input-lines/${lineId}/`, jsonOptions("PATCH", payload)),
|
|
approvePayrollAdminInputLine: (lineId: number, expectedLockVersion: number): Promise<PayrollAdminInputLine> =>
|
|
request(`${adminPrefix}/input-lines/${lineId}/approve/`, jsonOptions("POST", { expected_lock_version: expectedLockVersion })),
|
|
calculatePayrollAdminPeriod: (
|
|
periodId: number,
|
|
payload: { idempotency_key: string; recalculation_reason: string; expected_lock_version: number },
|
|
): Promise<PayrollAdminRun> =>
|
|
request(`${adminPrefix}/periods/${periodId}/calculate/`, jsonOptions("POST", payload)),
|
|
submitPayrollAdminRunForReview: (runId: number): Promise<PayrollAdminRun> =>
|
|
request(`${adminPrefix}/runs/${runId}/submit-review/`, jsonOptions("POST")),
|
|
returnPayrollAdminRun: (runId: number, reason: string): Promise<PayrollAdminRun> =>
|
|
request(`${adminPrefix}/runs/${runId}/return/`, jsonOptions("POST", { reason })),
|
|
approvePayrollAdminRun: (runId: number): Promise<PayrollAdminRun> =>
|
|
request(`${adminPrefix}/runs/${runId}/approve/`, jsonOptions("POST")),
|
|
publishPayrollAdminRun: (runId: number): Promise<PayrollAdminRun> =>
|
|
request(`${adminPrefix}/runs/${runId}/publish/`, jsonOptions("POST")),
|
|
closePayrollAdminPeriod: (periodId: number): Promise<PayrollAdminPeriod> =>
|
|
request(`${adminPrefix}/periods/${periodId}/close/`, jsonOptions("POST")),
|
|
};
|
|
}
|