R47 A1-A8:完成薪资管理模块
这个提交包含在:
@@ -0,0 +1,188 @@
|
||||
import { apiRequestData } from "./http";
|
||||
|
||||
export type SalaryItemType = "earning" | "deduction";
|
||||
export type PayslipStatus = "draft" | "issued";
|
||||
|
||||
export type SalaryItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
itemType: SalaryItemType;
|
||||
sortOrder: number;
|
||||
enabled: boolean;
|
||||
createdAt: string | null;
|
||||
createdBy: string | null;
|
||||
updatedAt: string | null;
|
||||
updatedBy: string | null;
|
||||
};
|
||||
|
||||
export type EmployeeSalaryProfileItem = {
|
||||
salaryItemId: string;
|
||||
name: string;
|
||||
itemType: SalaryItemType;
|
||||
sortOrder: number;
|
||||
monthlyAmount: number;
|
||||
};
|
||||
|
||||
export type EmployeeSalaryProfileSnapshot = {
|
||||
effectiveDate: string;
|
||||
createdAt: string | null;
|
||||
createdBy: string | null;
|
||||
updatedAt: string | null;
|
||||
updatedBy: string | null;
|
||||
items: EmployeeSalaryProfileItem[];
|
||||
};
|
||||
|
||||
export type PayslipItem = {
|
||||
salaryItemId: string;
|
||||
name: string;
|
||||
itemType: SalaryItemType;
|
||||
sortOrder: number;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type Payslip = {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
payMonth: string | null;
|
||||
status: PayslipStatus;
|
||||
grossAmount: number;
|
||||
deductionAmount: number;
|
||||
netAmount: number;
|
||||
issuedAt: string | null;
|
||||
createdAt: string | null;
|
||||
createdBy: string | null;
|
||||
updatedAt: string | null;
|
||||
updatedBy: string | null;
|
||||
employee: {
|
||||
id: string;
|
||||
employeeNo: string;
|
||||
name: string;
|
||||
title: string;
|
||||
department: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | null;
|
||||
} | null;
|
||||
items: PayslipItem[];
|
||||
};
|
||||
|
||||
export type PayrollMonthSummary = {
|
||||
month: string;
|
||||
totalNetAmount: number;
|
||||
averageNetAmount: number;
|
||||
issuedCount: number;
|
||||
};
|
||||
|
||||
export async function fetchSalaryItems() {
|
||||
return apiRequestData<{ items: SalaryItem[] }>("/api/payroll/items");
|
||||
}
|
||||
|
||||
export async function createSalaryItem(payload: {
|
||||
name: string;
|
||||
itemType: SalaryItemType;
|
||||
sortOrder: number;
|
||||
enabled?: boolean;
|
||||
}) {
|
||||
return apiRequestData<SalaryItem>("/api/payroll/items", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchSalaryItem(
|
||||
itemId: string,
|
||||
payload: Partial<{
|
||||
name: string;
|
||||
itemType: SalaryItemType;
|
||||
sortOrder: number;
|
||||
enabled: boolean;
|
||||
}>,
|
||||
) {
|
||||
return apiRequestData<SalaryItem>(`/api/payroll/items/${itemId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchEmployeeSalaryProfile(employeeId: string) {
|
||||
return apiRequestData<{ employeeId: string; snapshots: EmployeeSalaryProfileSnapshot[] }>(
|
||||
`/api/payroll/employees/${employeeId}/profile`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function upsertEmployeeSalaryProfile(
|
||||
employeeId: string,
|
||||
payload: {
|
||||
effectiveDate: string;
|
||||
items: Array<{
|
||||
salaryItemId: string;
|
||||
monthlyAmount: number;
|
||||
}>;
|
||||
},
|
||||
) {
|
||||
return apiRequestData<{ employeeId: string; snapshots: EmployeeSalaryProfileSnapshot[] }>(
|
||||
`/api/payroll/employees/${employeeId}/profile`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchEmployeePayslips(employeeId: string) {
|
||||
return apiRequestData<{ employeeId: string; items: Payslip[] }>(`/api/payroll/employees/${employeeId}/payslips`);
|
||||
}
|
||||
|
||||
export async function fetchPayslipsByMonth(month: string) {
|
||||
return apiRequestData<{
|
||||
month: string;
|
||||
salaryItems: SalaryItem[];
|
||||
items: Payslip[];
|
||||
summary: {
|
||||
total: number;
|
||||
draft: number;
|
||||
issued: number;
|
||||
};
|
||||
}>(`/api/payroll/payslips?month=${encodeURIComponent(month)}`);
|
||||
}
|
||||
|
||||
export async function generatePayslips(month: string) {
|
||||
return apiRequestData<{
|
||||
month: string;
|
||||
summary: {
|
||||
total: number;
|
||||
generated: number;
|
||||
};
|
||||
}>("/api/payroll/payslips/generate", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ month }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchPayslip(
|
||||
payslipId: string,
|
||||
payload: {
|
||||
items: Array<{
|
||||
salaryItemId: string;
|
||||
amount: number;
|
||||
}>;
|
||||
},
|
||||
) {
|
||||
return apiRequestData<Payslip>(`/api/payroll/payslips/${payslipId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function issuePayslips(month: string) {
|
||||
return apiRequestData<{
|
||||
month: string;
|
||||
summary: {
|
||||
total: number;
|
||||
issued: number;
|
||||
};
|
||||
}>("/api/payroll/payslips/issue", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ month }),
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户