|
@@ -5,30 +5,52 @@ import request from '@/utils/request';
|
|
|
* 后端分组:人力资源-员工自助明细服务(/hr/ess/**),全部按当前登录人鉴权,
|
|
* 后端分组:人力资源-员工自助明细服务(/hr/ess/**),全部按当前登录人鉴权,
|
|
|
* 接口不接收 userId / 员工主键(后端以登录态复核归属)。
|
|
* 接口不接收 userId / 员工主键(后端以登录态复核归属)。
|
|
|
*
|
|
*
|
|
|
- * 接口契约(2026-10-27 实抓 swagger,与文档页一致,勿凭旧文档改):
|
|
|
|
|
- * 1. GET /hr/ess/payslips —— 查询我的工资条列表
|
|
|
|
|
- * 无任何 query / body 参数,返回 List<员工自助工资条>(非分页)。
|
|
|
|
|
- * ★ 契约原文:「列表不返回工资金额和银行卡;详情需单独通过可信二次认证」。
|
|
|
|
|
- * 2. GET /hr/ess/payslips/{id}?verificationGrantId= —— 查询我的工资条详情
|
|
|
|
|
- * verificationGrantId(可信二次认证授权主键)**必填**,服务端同时复核归属。
|
|
|
|
|
- * 返回明文金额:grossAmount / deductionAmount / netAmount / maskedBankCardNo。
|
|
|
|
|
- * 3. POST /hr/ess/payslips/{id}/appeals —— 提交我的工资条异议
|
|
|
|
|
- * body(三项全部必填):appealReason(≤500) + requestId(客户端请求号,≤128) + verificationGrantId。
|
|
|
|
|
|
|
+ * 接口契约(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=
|
|
* 4. POST /hr/ess/payslips/{id}/confirm?requestId=&verificationGrantId=
|
|
|
- * 两个参数都在 **query** 上(不是 body),同一 requestId 幂等重放。
|
|
|
|
|
|
|
+ * 参数在 query 上(不是 body),requestId 必填并幂等重放;grantId 选填。
|
|
|
|
|
+ * 5. POST /hr/ess/payslips/{id}/appeals —— 提交异议
|
|
|
|
|
+ * body:appealReason(≤500) + requestId(≤128) 必填,verificationGrantId 选填。
|
|
|
*
|
|
*
|
|
|
- * ★★ 后端缺口(前端绝不兜底):「可信二次认证授权」只有消费接口、
|
|
|
|
|
- * 没有发放/申请接口 —— 全平台 swagger(18 个分组)均未提供获取
|
|
|
|
|
- * verificationGrantId 的途径。在补上之前,详情 / 确认 / 申诉都无法发起,
|
|
|
|
|
- * 前端只能如实提示,不造值、不跳过(见 resolveVerificationGrantId)。
|
|
|
|
|
|
|
+ * ★ 状态文案:后端枚举 PayrollPayslipStatusEnum 统一在 payslipStatusLabel 字段下发中文,
|
|
|
|
|
+ * 前端不再维护状态字典、也不再请求 sys_dict;下面的内置文案只作为旧后端兜底。
|
|
|
|
|
+ * ★ 安全口径:二次认证授权降级为「有则校验」后,服务端对未携带授权的详情访问
|
|
|
|
|
+ * 单独记一条 PAYSLIP_VIEW_NO_SECOND_FACTOR 审计;前端不伪造、不猜授权值。
|
|
|
*/
|
|
*/
|
|
|
import {
|
|
import {
|
|
|
PAYSLIP_STATUS_OPTIONS,
|
|
PAYSLIP_STATUS_OPTIONS,
|
|
|
- PAYSLIP_STATUS_LABELS,
|
|
|
|
|
createIdempotentKey,
|
|
createIdempotentKey,
|
|
|
isIdempotentKeyExpired
|
|
isIdempotentKeyExpired
|
|
|
} from './salarySlip';
|
|
} 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 };
|
|
export { createIdempotentKey, isIdempotentKeyExpired };
|
|
|
|
|
|
|
|
function unwrap(res) {
|
|
function unwrap(res) {
|
|
@@ -50,27 +72,26 @@ function toId(value) {
|
|
|
/**
|
|
/**
|
|
|
* 获取「可信二次认证授权主键」(verificationGrantId)。
|
|
* 获取「可信二次认证授权主键」(verificationGrantId)。
|
|
|
*
|
|
*
|
|
|
- * ★ 后端未提供发放接口 —— 这里不猜、不造值、不用别的字段顶替,
|
|
|
|
|
- * 直接抛出明确的缺口提示,由页面原样展示给用户。
|
|
|
|
|
- * 后端补上接口后,只改这一个函数即可(详情 / 确认 / 申诉共用)。
|
|
|
|
|
|
|
+ * ★ 2026-10-30 契约降级:grantId 改为选填,后端不再要求必须先取得授权,
|
|
|
|
|
+ * 因此这里直接返回 undefined(请求层会省略该参数),不再抛错阻断用户。
|
|
|
|
|
+ * 若后续补上发放接口,只改这一个函数即可(详情 / 确认 / 申诉共用)。
|
|
|
*
|
|
*
|
|
|
- * @param {string} _scene 业务场景(仅供后端补接口时区分用途,如 detail / confirm / appeal)
|
|
|
|
|
- * @returns {Promise<number>} verificationGrantId(int64)
|
|
|
|
|
|
|
+ * @param {string} _scene 业务场景(detail / confirm / appeal,预留给授权发放接口区分用途)
|
|
|
|
|
+ * @returns {Promise<number|undefined>} verificationGrantId(int64),未取得授权时为 undefined
|
|
|
*/
|
|
*/
|
|
|
export async function resolveVerificationGrantId(_scene = '') {
|
|
export async function resolveVerificationGrantId(_scene = '') {
|
|
|
- throw new Error(
|
|
|
|
|
- '获取「可信二次认证授权」的接口后端尚未提供(verificationGrantId 无发放接口),无法继续该操作,请后端补充'
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return undefined;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* ---------------- 适配器 ---------------- */
|
|
/* ---------------- 适配器 ---------------- */
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 员工自助工资条行适配。
|
|
* 员工自助工资条行适配。
|
|
|
- * 契约字段:payslipId / payrollMonth / payslipStatus / grossAmount / deductionAmount /
|
|
|
|
|
- * netAmount / maskedBankCardNo / publishTime / firstViewTime / appealReason
|
|
|
|
|
- * ★ 状态枚举与薪资核算工资条同一套(PUBLISHED/VIEWED/CONFIRMED/APPEALED/CORRECTED/CLOSED),
|
|
|
|
|
- * 后端未在文档中单独给枚举表 —— 未识别取值原样显示,不猜测语义。
|
|
|
|
|
|
|
+ * 契约字段:payslipId / payrollMonth / payslipStatus / payslipStatusLabel / confirmTime /
|
|
|
|
|
+ * grossAmount / deductionAmount / netAmount / maskedBankCardNo /
|
|
|
|
|
+ * publishTime / firstViewTime / appealReason
|
|
|
|
|
+ * ★ 状态中文文案由后端枚举 PayrollPayslipStatusEnum 在 payslipStatusLabel 下发,
|
|
|
|
|
+ * 前端只在旧后端缺该字段时用兜底表,不猜测语义。
|
|
|
* ★ 金额字段列表接口不返回(null)→ 原样保留 undefined,页面显示「—」并引导去详情,不做兜底。
|
|
* ★ 金额字段列表接口不返回(null)→ 原样保留 undefined,页面显示「—」并引导去详情,不做兜底。
|
|
|
*/
|
|
*/
|
|
|
function adaptMyPayslipRow(raw = {}) {
|
|
function adaptMyPayslipRow(raw = {}) {
|
|
@@ -82,13 +103,14 @@ function adaptMyPayslipRow(raw = {}) {
|
|
|
payslipId: toId(raw.payslipId),
|
|
payslipId: toId(raw.payslipId),
|
|
|
payrollMonth: raw.payrollMonth || '',
|
|
payrollMonth: raw.payrollMonth || '',
|
|
|
payslipStatus: status,
|
|
payslipStatus: status,
|
|
|
- payslipStatusLabel: PAYSLIP_STATUS_LABELS[status] || status || '—',
|
|
|
|
|
|
|
+ payslipStatusLabel: payslipStatusLabel(raw, status),
|
|
|
grossAmount: raw.grossAmount,
|
|
grossAmount: raw.grossAmount,
|
|
|
deductionAmount: raw.deductionAmount,
|
|
deductionAmount: raw.deductionAmount,
|
|
|
netAmount: raw.netAmount,
|
|
netAmount: raw.netAmount,
|
|
|
maskedBankCardNo: raw.maskedBankCardNo || '',
|
|
maskedBankCardNo: raw.maskedBankCardNo || '',
|
|
|
publishTime: raw.publishTime || '',
|
|
publishTime: raw.publishTime || '',
|
|
|
firstViewTime: raw.firstViewTime || '',
|
|
firstViewTime: raw.firstViewTime || '',
|
|
|
|
|
+ confirmTime: raw.confirmTime || '',
|
|
|
appealReason: raw.appealReason || ''
|
|
appealReason: raw.appealReason || ''
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -104,8 +126,27 @@ export function hasAmount(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(无参数,非分页,后端按登录人返回全量)
|
|
* GET /hr/ess/payslips(无参数,非分页,后端按登录人返回全量)
|
|
|
|
|
+ * ★ 老调用方仍可用;需要分页/确认时间/仅当前有效版本时改用 getMyPayslipPage。
|
|
|
*/
|
|
*/
|
|
|
export async function getMyPayslipList() {
|
|
export async function getMyPayslipList() {
|
|
|
const res = await request.get('/hr/ess/payslips');
|
|
const res = await request.get('/hr/ess/payslips');
|
|
@@ -119,17 +160,12 @@ export async function getMyPayslipList() {
|
|
|
* 查询我的工资条详情(明文金额)
|
|
* 查询我的工资条详情(明文金额)
|
|
|
* GET /hr/ess/payslips/{id}?verificationGrantId=
|
|
* GET /hr/ess/payslips/{id}?verificationGrantId=
|
|
|
* @param {number|string} payslipId 工资条主键
|
|
* @param {number|string} payslipId 工资条主键
|
|
|
- * @param {number|string} verificationGrantId 可信二次认证授权主键(必填,调用方先 resolveVerificationGrantId 取得)
|
|
|
|
|
|
|
+ * @param {number|string} [verificationGrantId] 二次认证授权主键(选填;有则带上,后端会校验有效性)
|
|
|
*/
|
|
*/
|
|
|
export async function getMyPayslipDetail(payslipId, verificationGrantId) {
|
|
export async function getMyPayslipDetail(payslipId, verificationGrantId) {
|
|
|
const grantId = toId(verificationGrantId);
|
|
const grantId = toId(verificationGrantId);
|
|
|
- if (grantId == null) {
|
|
|
|
|
- throw new Error(
|
|
|
|
|
- '缺少可信二次认证授权主键(verificationGrantId),无法查询工资条详情'
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
const res = await request.get(`/hr/ess/payslips/${toId(payslipId)}`, {
|
|
const res = await request.get(`/hr/ess/payslips/${toId(payslipId)}`, {
|
|
|
- params: { verificationGrantId: grantId }
|
|
|
|
|
|
|
+ params: grantId == null ? {} : { verificationGrantId: grantId }
|
|
|
});
|
|
});
|
|
|
return adaptMyPayslipRow(unwrap(res) || {});
|
|
return adaptMyPayslipRow(unwrap(res) || {});
|
|
|
}
|
|
}
|
|
@@ -148,13 +184,11 @@ export async function confirmMyPayslip(payslipId, payload = {}) {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
const grantId = toId(payload.verificationGrantId);
|
|
const grantId = toId(payload.verificationGrantId);
|
|
|
- if (grantId == null) {
|
|
|
|
|
- throw new Error('缺少可信二次认证授权主键(verificationGrantId)');
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const params = grantId == null ? { requestId } : { requestId, verificationGrantId: grantId };
|
|
|
const res = await request.post(
|
|
const res = await request.post(
|
|
|
`/hr/ess/payslips/${toId(payslipId)}/confirm`,
|
|
`/hr/ess/payslips/${toId(payslipId)}/confirm`,
|
|
|
null,
|
|
null,
|
|
|
- { params: { requestId, verificationGrantId: grantId } }
|
|
|
|
|
|
|
+ { params }
|
|
|
);
|
|
);
|
|
|
return unwrap(res);
|
|
return unwrap(res);
|
|
|
}
|
|
}
|
|
@@ -162,7 +196,7 @@ export async function confirmMyPayslip(payslipId, payload = {}) {
|
|
|
/**
|
|
/**
|
|
|
* 提交我的工资条异议
|
|
* 提交我的工资条异议
|
|
|
* POST /hr/ess/payslips/{id}/appeals
|
|
* POST /hr/ess/payslips/{id}/appeals
|
|
|
- * body:{ appealReason(≤500,必填), requestId(≤128,必填), verificationGrantId(必填) }
|
|
|
|
|
|
|
+ * body:{ appealReason(≤500,必填), requestId(≤128,必填), verificationGrantId(选填) }
|
|
|
*/
|
|
*/
|
|
|
export async function submitMyPayslipAppeal(payslipId, payload = {}) {
|
|
export async function submitMyPayslipAppeal(payslipId, payload = {}) {
|
|
|
const appealReason = String(payload.appealReason || '').trim();
|
|
const appealReason = String(payload.appealReason || '').trim();
|
|
@@ -176,13 +210,8 @@ export async function submitMyPayslipAppeal(payslipId, payload = {}) {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
const grantId = toId(payload.verificationGrantId);
|
|
const grantId = toId(payload.verificationGrantId);
|
|
|
- if (grantId == null) {
|
|
|
|
|
- throw new Error('缺少可信二次认证授权主键(verificationGrantId)');
|
|
|
|
|
- }
|
|
|
|
|
- const res = await request.post(`/hr/ess/payslips/${toId(payslipId)}/appeals`, {
|
|
|
|
|
- appealReason,
|
|
|
|
|
- requestId,
|
|
|
|
|
- verificationGrantId: grantId
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ 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);
|
|
return unwrap(res);
|
|
|
}
|
|
}
|