// 测试场景库 —— payroll 模块、index.html 基准、重构后对照三方共用同一批数据。
//
// 为什么单独一个文件:基准快照与重构后的实测必须喂**完全相同**的输入,
// 否则对不上时分不清是「代码变了」还是「测试数据变了」。
'use strict';
const W = (h, extra) => Object.assign({
status: 'work', shift_type: 'day', hours: h,
contraband_found: 0, contraband_missed: 0, other_penalty: null, penalty_reason: ''
}, extra || {});
const FINAL = (mk, amount) => ({
[mk]: { status: 'final', finalAmount: amount, note: '', finalizedAt: '2026-10-01T09:00:00.000Z' }
});
const DRAFT = (mk, amount) => ({
[mk]: { status: 'draft', finalAmount: amount, note: '', finalizedAt: '' }
});
/** 满勤月:1–N 日全上白班 8h */
function fullMonth(days, hours) {
const rec = {};
for (let d = 1; d <= days; d++) rec[`2026-09-${String(d).padStart(2, '0')}`] = W(hours === undefined ? 8 : hours);
return rec;
}
// 冻结的「现在」:不冻结的话 payDayAPassed / isCurrentMonth / 今日高亮
// 每天跑出来都不一样,基准 JSON 根本没法比。
// 取 2026-09-20 是刻意的:已过 payDayA(22)? 没过 —— 正好覆盖「上半月待发」这一支。
const NOW = '2026-09-20T10:00:00+08:00';
/**
* 每个场景都会被喂给三方:纯模块、重构前的 index.html、重构后的 index.html。
* 覆盖目标:12 个薪酬 computed 的全部分支。
*/
const SCENARIOS = [
{
name: '基础分段·上下半月各2天无奖惩',
records: { '2026-09-03': W(8), '2026-09-14': W(8), '2026-09-20': W(10), '2026-09-28': W(10) },
year: 2026, month: 8
},
{
name: '边界·15日算上半月/16日算下半月',
records: { '2026-09-15': W(12), '2026-09-16': W(12) },
year: 2026, month: 8
},
{
name: '夜班跨天·15日夜班10h整段进上半月',
records: { '2026-09-15': W(10, { shift_type: 'night' }) },
year: 2026, month: 8
},
{
name: '满勤达标·27天×8h',
records: fullMonth(27),
year: 2026, month: 8
},
{
name: '差1天不满勤·26天×8h',
records: fullMonth(26),
year: 2026, month: 8
},
{
name: '奖惩四项归集·未定稿只记录',
records: {
'2026-09-05': W(8, { contraband_found: 3 }),
'2026-09-20': W(8, { contraband_missed: 2 }),
'2026-09-25': W(8, { other_penalty: 50, penalty_reason: '迟到' })
},
year: 2026, month: 8
},
{
name: '奖惩已定稿-60',
records: {
'2026-09-05': W(8, { contraband_found: 3 }),
'2026-09-20': W(8, { contraband_missed: 2 }),
'2026-09-25': W(8, { other_penalty: 50, penalty_reason: '迟到' })
},
year: 2026, month: 8, adjust: FINAL('2026-09', -60)
},
{
name: '草稿不生效·draft+30',
records: { '2026-09-05': W(8, { contraband_found: 3 }) },
year: 2026, month: 8, adjust: DRAFT('2026-09', 30)
},
{
name: '审核改数·累计+30只认+20',
records: { '2026-09-05': W(8, { contraband_found: 3 }) },
year: 2026, month: 8, adjust: FINAL('2026-09', 20)
},
{
name: '跨月结转·8月未定稿',
records: {
'2026-08-10': W(8),
'2026-08-18': W(10), '2026-08-22': W(10),
'2026-08-27': W(10, { contraband_found: 5, contraband_missed: 1 }),
'2026-09-02': W(8), '2026-09-09': W(8)
},
year: 2026, month: 8
},
{
name: '跨月结转·8月已定稿+30',
records: {
'2026-08-10': W(8),
'2026-08-18': W(10), '2026-08-22': W(10),
'2026-08-27': W(10, { contraband_found: 5, contraband_missed: 1 }),
'2026-09-02': W(8), '2026-09-09': W(8)
},
year: 2026, month: 8, adjust: FINAL('2026-08', 30)
},
{
name: '跨年边界·2026年1月取2025年12月',
records: {
'2025-12-20': W(10), '2025-12-28': W(10, { contraband_found: 2 }),
'2026-01-05': W(8)
},
year: 2026, month: 0
},
{
name: '跨年边界·2025-12已定稿+20',
records: {
'2025-12-20': W(10), '2025-12-28': W(10, { contraband_found: 2 }),
'2026-01-05': W(8)
},
year: 2026, month: 0, adjust: FINAL('2025-12', 20)
},
{
name: '处罚超过下半月工资·未定稿',
records: { '2026-09-20': W(6, { contraband_missed: 10, other_penalty: 200 }) },
year: 2026, month: 8
},
{
name: '处罚超过下半月工资·定稿-400触发地板0',
records: { '2026-09-20': W(6, { contraband_missed: 10, other_penalty: 200 }) },
year: 2026, month: 8, adjust: FINAL('2026-09', -400)
},
{
name: '休息请假不计工时',
records: {
'2026-09-03': W(8),
'2026-09-04': { status: 'rest' },
'2026-09-05': { status: 'leave' },
'2026-09-06': { status: 'rest' }
},
year: 2026, month: 8
},
{
name: '脏数据·其他扣款负数夹为0',
records: { '2026-09-20': W(8, { other_penalty: -500 }) },
year: 2026, month: 8
},
{
name: '月份隔离·不串月',
records: { '2026-08-05': W(12), '2026-10-05': W(12), '2026-09-05': W(8) },
year: 2026, month: 8
},
{
name: '自定义设置·时薪30/满勤20/单价2/截止10/发薪25与5',
records: (() => {
const rec = {};
for (let d = 1; d <= 20; d++) rec[`2026-09-${String(d).padStart(2, '0')}`] = W(10);
return rec;
})(),
year: 2026, month: 8,
settings: { rate: 30, fullDays: 20, fullBonusPerHour: 2, cutoff: 10, payDayA: 25, payDayB: 5 }
},
{
name: '非法设置全部回落默认',
records: { '2026-09-05': W(8) },
year: 2026, month: 8,
settings: { rate: null, fullDays: 0, fullBonusPerHour: undefined, cutoff: 99, foundBonus: -5, missedPenalty: NaN }
},
{
name: '处罚明细排序与原因',
records: {
'2026-09-25': W(8, { other_penalty: 50, penalty_reason: '迟到' }),
'2026-09-08': W(8, { contraband_missed: 2 }),
'2026-09-12': W(8, { contraband_missed: 1, other_penalty: 30 }),
'2026-09-15': W(8)
},
year: 2026, month: 8
},
{
name: '空数据不崩',
records: {},
year: 2026, month: 8
},
{
name: '越界旧值原样保留·14h与4h',
records: { '2026-09-05': W(14), '2026-09-06': W(4), '2026-09-07': W(8.5) },
year: 2026, month: 8
},
{
name: '用户实测场景·3天22h含查获1件未定稿',
records: { '2026-09-01': W(8), '2026-09-02': W(8), '2026-09-03': W(6, { contraband_found: 1 }) },
year: 2026, month: 8
},
{
name: '用户实测场景·同上已定稿+10',
records: { '2026-09-01': W(8), '2026-09-02': W(8), '2026-09-03': W(6, { contraband_found: 1 }) },
year: 2026, month: 8, adjust: FINAL('2026-09', 10)
},
{
name: '满勤+查获5件未定稿·满勤计奖惩不计',
records: Object.assign(fullMonth(27), { '2026-09-05': W(8, { contraband_found: 5 }) }),
year: 2026, month: 8
},
{
name: '满勤+查获5件已定稿+50·两项全计',
records: Object.assign(fullMonth(27), { '2026-09-05': W(8, { contraband_found: 5 }) }),
year: 2026, month: 8, adjust: FINAL('2026-09', 50)
},
{
name: '历史月份·看8月(payDayA 已过)',
records: { '2026-08-05': W(8), '2026-08-20': W(10) },
year: 2026, month: 7
},
{
name: '未来月份·看10月(payDayA 未过)',
records: { '2026-10-05': W(8), '2026-10-20': W(10) },
year: 2026, month: 9
},
{
name: '班次默认工时越界·白班2h夜班20h',
records: {},
year: 2026, month: 8,
settings: { dayHours: 2, nightHours: 20 }
}
];
module.exports = { W, FINAL, DRAFT, fullMonth, SCENARIOS, NOW };