R47 A1-A8:完成薪资管理模块

这个提交包含在:
2026-03-29 19:05:55 +08:00
父节点 c0384b5bbf
当前提交 7ba72ca87d
共修改 25 个文件,包含 3598 行新增和 4 行删除
+14
查看文件
@@ -250,6 +250,20 @@ export type EmployeeStatsResponse = {
validHolderCount: number;
expiringSoonCount: number;
};
salarySummary: {
currentMonth: {
month: string;
totalNetAmount: number;
averageNetAmount: number;
issuedCount: number;
} | null;
previousMonth: {
month: string;
totalNetAmount: number;
averageNetAmount: number;
issuedCount: number;
} | null;
} | null;
performanceSummary: {
cycle: {
id: string;
+11
查看文件
@@ -83,4 +83,15 @@ export async function exportEmployees(params: {
return apiRequestBlob(`/api/exports/employees?${qs.toString()}`);
}
export async function exportPayslips(params: {
format: "csv" | "xlsx";
month: string;
}) {
const qs = new URLSearchParams();
qs.set("format", params.format);
qs.set("month", params.month);
return apiRequestBlob(`/api/exports/payslips?${qs.toString()}`);
}
export type { ApiBlobResponse };
+188
查看文件
@@ -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 }),
});
}