| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import request from '@/utils/request';
- /**
- * 员工自助 · 我的工资条
- *
- * 后端分组:人力资源-员工自助明细服务(/hr/ess/**),全部按当前登录人鉴权,
- * 接口不接收 userId / 员工主键(后端以登录态复核归属)。
- *
- * 接口契约(2026-10-30 后端改造后):
- * 1. POST /hr/ess/payslips/page —— 分页查询我的工资条(**新接入优先使用**)
- * body 可空:{ pageNum, size };size 超过 100 由服务端收敛;返回 Pagination。
- * 只返回当前有效版本(更正后旧版本不再出现),行内带 confirmTime。
- * 2. GET /hr/ess/payslips —— 兼容保留的全量列表(老调用方仍在用),结构不变。
- * 3. GET /hr/ess/payslips/{id}?verificationGrantId= —— 查询我的工资条详情
- * ★ verificationGrantId 改为**选填**:不传直接返回本人明文金额;
- * 传了必须是本人、本工资条、未过期的有效授权,否则仍被拒绝。
- * 4. POST /hr/ess/payslips/{id}/confirm?requestId=&verificationGrantId=
- * 参数在 query 上(不是 body),requestId 必填并幂等重放;grantId 选填。
- * 5. POST /hr/ess/payslips/{id}/appeals —— 提交异议
- * body:appealReason(≤500) + requestId(≤128) 必填,verificationGrantId 选填。
- *
- * ★ 状态文案:后端枚举 PayrollPayslipStatusEnum 统一在 payslipStatusLabel 字段下发中文,
- * 前端不再维护状态字典、也不再请求 sys_dict;下面的内置文案只作为旧后端兜底。
- * ★ 安全口径:二次认证授权降级为「有则校验」后,服务端对未携带授权的详情访问
- * 单独记一条 PAYSLIP_VIEW_NO_SECOND_FACTOR 审计;前端不伪造、不猜授权值。
- */
- import {
- PAYSLIP_STATUS_OPTIONS,
- createIdempotentKey,
- isIdempotentKeyExpired
- } from './salarySlip';
- /** 旧后端兜底文案:与 PayrollConstants 的 7 个码值一致,正常不再使用 */
- const FALLBACK_STATUS_LABELS = {
- WAIT_PUBLISH: '待发布',
- PUBLISHED: '已发布',
- VIEWED: '已查看',
- CONFIRMED: '已确认',
- APPEALED: '申诉中',
- CORRECTED: '已更正',
- CLOSED: '已关闭'
- };
- /** 状态码 → 文案:优先后端下发的 payslipStatusLabel,其次兜底表,最后原样显示 */
- function payslipStatusLabel(raw = {}, status = '') {
- const code = String(status || '').toUpperCase();
- return (
- String(raw.payslipStatusLabel || '').trim() ||
- FALLBACK_STATUS_LABELS[code] ||
- code ||
- '—'
- );
- }
- export { createIdempotentKey, isIdempotentKeyExpired };
- function unwrap(res) {
- if (res.data.code == 0) {
- return res.data.data;
- }
- return Promise.reject(new Error(res.data.message || '接口调用失败'));
- }
- function toId(value) {
- if (value === undefined || value === null || value === '') return undefined;
- const text = String(value).trim();
- if (!/^-?\d+$/.test(text)) return value;
- return Number.isSafeInteger(Number(text)) ? Number(text) : text;
- }
- /* ---------------- 二次认证授权(后端缺口,唯一取数口) ---------------- */
- /**
- * 获取「可信二次认证授权主键」(verificationGrantId)。
- *
- * ★ 2026-10-30 契约降级:grantId 改为选填,后端不再要求必须先取得授权,
- * 因此这里直接返回 undefined(请求层会省略该参数),不再抛错阻断用户。
- * 若后续补上发放接口,只改这一个函数即可(详情 / 确认 / 申诉共用)。
- *
- * @param {string} _scene 业务场景(detail / confirm / appeal,预留给授权发放接口区分用途)
- * @returns {Promise<number|undefined>} verificationGrantId(int64),未取得授权时为 undefined
- */
- export async function resolveVerificationGrantId(_scene = '') {
- return undefined;
- }
- /* ---------------- 适配器 ---------------- */
- /**
- * 员工自助工资条行适配。
- * 契约字段:payslipId / payrollMonth / payslipStatus / payslipStatusLabel / confirmTime /
- * grossAmount / deductionAmount / netAmount / maskedBankCardNo /
- * publishTime / firstViewTime / appealReason
- * ★ 状态中文文案由后端枚举 PayrollPayslipStatusEnum 在 payslipStatusLabel 下发,
- * 前端只在旧后端缺该字段时用兜底表,不猜测语义。
- * ★ 金额字段列表接口不返回(null)→ 原样保留 undefined,页面显示「—」并引导去详情,不做兜底。
- */
- function adaptMyPayslipRow(raw = {}) {
- const status = String(raw.payslipStatus || '').toUpperCase();
- return {
- ...raw,
- /* 列表行主键叫 payslipId(不是 id),统一透出 id 便于表格 row-key */
- id: raw.payslipId,
- payslipId: toId(raw.payslipId),
- payrollMonth: raw.payrollMonth || '',
- payslipStatus: status,
- payslipStatusLabel: payslipStatusLabel(raw, status),
- grossAmount: raw.grossAmount,
- deductionAmount: raw.deductionAmount,
- netAmount: raw.netAmount,
- maskedBankCardNo: raw.maskedBankCardNo || '',
- publishTime: raw.publishTime || '',
- firstViewTime: raw.firstViewTime || '',
- confirmTime: raw.confirmTime || '',
- appealReason: raw.appealReason || ''
- };
- }
- /** 状态筛选项(与薪资核算工资条共用同一套枚举文案) */
- export const MY_PAYSLIP_STATUS_OPTIONS = PAYSLIP_STATUS_OPTIONS;
- /** 金额是否真实返回(0 是合法金额,不能用 falsy 判断) */
- export function hasAmount(value) {
- return typeof value === 'number' && !Number.isNaN(value);
- }
- /* ---------------- 接口 ---------------- */
- /**
- * 分页查询我的工资条(新接入优先使用)
- * POST /hr/ess/payslips/page
- * @param {object} params { pageNum, size }
- * @returns {Promise<{list: Array, count: number, pageNum: number, size: number}>}
- */
- export async function getMyPayslipPage(params = {}) {
- const pageNum = Number(params.pageNum) > 0 ? Number(params.pageNum) : 1;
- const size = Number(params.size) > 0 ? Math.min(Number(params.size), 100) : 20;
- const res = await request.post('/hr/ess/payslips/page', { pageNum, size });
- const data = unwrap(res) || {};
- const list = (Array.isArray(data.list) ? data.list : []).map(
- adaptMyPayslipRow
- );
- const count = Number(data.count != null ? data.count : list.length) || 0;
- return { list, count, pageNum: data.pageNum || pageNum, size: data.size || size };
- }
- /**
- * 查询我的工资条列表(兼容保留)
- * GET /hr/ess/payslips(无参数,非分页,后端按登录人返回全量)
- * ★ 老调用方仍可用;需要分页/确认时间/仅当前有效版本时改用 getMyPayslipPage。
- */
- export async function getMyPayslipList() {
- const res = await request.get('/hr/ess/payslips');
- const list = (Array.isArray(unwrap(res)) ? unwrap(res) : []).map(
- adaptMyPayslipRow
- );
- return { list, count: list.length };
- }
- /**
- * 查询我的工资条详情(明文金额)
- * GET /hr/ess/payslips/{id}?verificationGrantId=
- * @param {number|string} payslipId 工资条主键
- * @param {number|string} [verificationGrantId] 二次认证授权主键(选填;有则带上,后端会校验有效性)
- */
- export async function getMyPayslipDetail(payslipId, verificationGrantId) {
- const grantId = toId(verificationGrantId);
- const res = await request.get(`/hr/ess/payslips/${toId(payslipId)}`, {
- params: grantId == null ? {} : { verificationGrantId: grantId }
- });
- return adaptMyPayslipRow(unwrap(res) || {});
- }
- /**
- * 确认我的工资条
- * POST /hr/ess/payslips/{id}/confirm?requestId=&verificationGrantId=
- * ★ 参数在 query 上;同一 requestId 幂等重放 —— requestId 必须由调用方生成一次并复用
- * (createIdempotentKey + isIdempotentKeyExpired,10 分钟内重试复用同一个)。
- */
- export async function confirmMyPayslip(payslipId, payload = {}) {
- const requestId = String(payload.requestId || '').trim();
- if (!requestId) {
- throw new Error(
- '缺少客户端请求号(requestId)。请用 createIdempotentKey() 生成一次并在重试时复用'
- );
- }
- const grantId = toId(payload.verificationGrantId);
- const params = grantId == null ? { requestId } : { requestId, verificationGrantId: grantId };
- const res = await request.post(
- `/hr/ess/payslips/${toId(payslipId)}/confirm`,
- null,
- { params }
- );
- return unwrap(res);
- }
- /**
- * 提交我的工资条异议
- * POST /hr/ess/payslips/{id}/appeals
- * body:{ appealReason(≤500,必填), requestId(≤128,必填), verificationGrantId(选填) }
- */
- export async function submitMyPayslipAppeal(payslipId, payload = {}) {
- const appealReason = String(payload.appealReason || '').trim();
- if (!appealReason) {
- throw new Error('请填写异议说明');
- }
- const requestId = String(payload.requestId || '').trim();
- if (!requestId) {
- throw new Error(
- '缺少客户端请求号(requestId)。请用 createIdempotentKey() 生成一次并在重试时复用'
- );
- }
- const grantId = toId(payload.verificationGrantId);
- const body = { appealReason, requestId };
- if (grantId != null) body.verificationGrantId = grantId;
- const res = await request.post(`/hr/ess/payslips/${toId(payslipId)}/appeals`, body);
- return unwrap(res);
- }
|