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; 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; }; 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; }; 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 = { count: number; next: string | null; previous: string | null; results: T[]; }; export type PayrollAdminListPayload = PayrollAdminPage | 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 { 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 => request("/api/v1/finance/payroll/me/statements/?page_size=100"), getMyPayrollStatement: (publicId: string): Promise => request(`/api/v1/finance/payroll/me/statements/${encodeURIComponent(publicId)}/`), acknowledgeMyPayrollStatement: (publicId: string): Promise => request( `/api/v1/finance/payroll/me/statements/${encodeURIComponent(publicId)}/acknowledge/`, { method: "POST" }, ), getPayrollAdminWorkspace: (periodId?: number): Promise => request(`${adminPrefix}/workspace/${buildQuery({ period_id: periodId })}`), getPayrollAdminPeriods: (): Promise> => request(`${adminPrefix}/periods/`), createPayrollAdminPeriod: (payload: PayrollPeriodWrite): Promise => request(`${adminPrefix}/periods/`, jsonOptions("POST", payload)), updatePayrollAdminPeriod: ( periodId: number, payload: Partial & { expected_lock_version: number }, ): Promise => request(`${adminPrefix}/periods/${periodId}/`, jsonOptions("PATCH", payload)), getPayrollAdminPayRates: (params: PayrollAdminListParams = {}): Promise> => request(`${adminPrefix}/pay-rates/${buildQuery({ ...params, page_size: 200 })}`), createPayrollAdminPayRate: (payload: PayrollPayRateWrite): Promise => request(`${adminPrefix}/pay-rates/`, jsonOptions("POST", payload)), updatePayrollAdminPayRate: ( rateId: number, payload: Partial> & { expected_lock_version: number }, ): Promise => request(`${adminPrefix}/pay-rates/${rateId}/`, jsonOptions("PATCH", payload)), approvePayrollAdminPayRate: (rateId: number, expectedLockVersion: number): Promise => request(`${adminPrefix}/pay-rates/${rateId}/approve/`, jsonOptions("POST", { expected_lock_version: expectedLockVersion })), revisePayrollAdminPayRate: (rateId: number, reason: string): Promise => request(`${adminPrefix}/pay-rates/${rateId}/revise/`, jsonOptions("POST", { reason })), getPayrollAdminWorkRecords: (params: PayrollAdminListParams = {}): Promise> => request(`${adminPrefix}/work-records/${buildQuery({ ...params, page_size: 200 })}`), createPayrollAdminWorkRecord: (payload: PayrollWorkRecordWrite): Promise => request(`${adminPrefix}/work-records/`, jsonOptions("POST", payload)), updatePayrollAdminWorkRecord: ( recordId: number, payload: Partial> & { expected_lock_version: number }, ): Promise => request(`${adminPrefix}/work-records/${recordId}/`, jsonOptions("PATCH", payload)), approvePayrollAdminWorkRecord: (recordId: number, expectedLockVersion: number): Promise => request(`${adminPrefix}/work-records/${recordId}/approve/`, jsonOptions("POST", { expected_lock_version: expectedLockVersion })), revisePayrollAdminWorkRecord: (recordId: number, reason: string): Promise => request(`${adminPrefix}/work-records/${recordId}/revise/`, jsonOptions("POST", { reason })), getPayrollAdminAttendanceWorkPreview: (periodId: number): Promise => request(`${adminPrefix}/periods/${periodId}/attendance-work-records/`), applyPayrollAdminAttendanceWork: ( periodId: number, payload: PayrollAttendanceWorkApplyPayload, ): Promise => request( `${adminPrefix}/periods/${periodId}/attendance-work-records/`, jsonOptions("POST", payload), ), getPayrollAdminInputLines: (params: PayrollAdminListParams = {}): Promise> => request(`${adminPrefix}/input-lines/${buildQuery({ ...params, page_size: 200 })}`), createPayrollAdminInputLine: (payload: PayrollInputLineWrite): Promise => request(`${adminPrefix}/input-lines/`, jsonOptions("POST", payload)), updatePayrollAdminInputLine: ( lineId: number, payload: Partial> & { expected_lock_version: number }, ): Promise => request(`${adminPrefix}/input-lines/${lineId}/`, jsonOptions("PATCH", payload)), approvePayrollAdminInputLine: (lineId: number, expectedLockVersion: number): Promise => 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 => request(`${adminPrefix}/periods/${periodId}/calculate/`, jsonOptions("POST", payload)), submitPayrollAdminRunForReview: (runId: number): Promise => request(`${adminPrefix}/runs/${runId}/submit-review/`, jsonOptions("POST")), returnPayrollAdminRun: (runId: number, reason: string): Promise => request(`${adminPrefix}/runs/${runId}/return/`, jsonOptions("POST", { reason })), approvePayrollAdminRun: (runId: number): Promise => request(`${adminPrefix}/runs/${runId}/approve/`, jsonOptions("POST")), publishPayrollAdminRun: (runId: number): Promise => request(`${adminPrefix}/runs/${runId}/publish/`, jsonOptions("POST")), closePayrollAdminPeriod: (periodId: number): Promise => request(`${adminPrefix}/periods/${periodId}/close/`, jsonOptions("POST")), }; }