verify-employment-calendar.cjs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. 'use strict';
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const path = require('node:path');
  5. const vm = require('node:vm');
  6. /** @param {string} source 页面源码 @param {string} name 方法名 @returns {string} 可执行方法 */
  7. function extract(source, name) {
  8. const match = new RegExp('export function ' + name + '\\([^]*?\\n}').exec(source);
  9. assert.ok(match, name);
  10. return match[0].replace('export ', '');
  11. }
  12. /** @param {string} file 页面文件 @param {boolean} employment 是否验证任职控制 @returns {object} 独立页面上下文 */
  13. function page(file, employment) {
  14. const source = fs.readFileSync(path.resolve(__dirname, '../src', file), 'utf8');
  15. const names = ['isWeekend', 'isDayOff', 'getHolidayType', 'isBeisenLeaveWorkday', 'loadHolidays'];
  16. if (employment) names.push('getEmploymentDateKey', 'getEmploymentBounds', 'isOutsideEmploymentDates',
  17. 'isBeforeEmploymentDate', 'assertEmploymentInputs', 'assertPersonnelSnapshot',
  18. 'getDayTotal', 'getOvertimeDayTotal', 'validateMonthlyOvertime', 'applyDefaultWorkdayHours');
  19. const scope = {APP_TYPE:'app', FORM_IDS:{holiday:'calendar'},
  20. HOLIDAY_FIELD_IDS:{year:'year',date:'date',type:'type'},
  21. VALIDATION:{maxHoursPerDay:8,maxOvertimePerMonth:36}};
  22. vm.createContext(scope);
  23. vm.runInContext(names.map(name => extract(source, name)).join('\n'), scope);
  24. const env = {...scope, state:{selectedYear:2026,selectedMonth:10,userInfo:{},
  25. billableData:{},nonBillableData:{},otherData:{},holidayMap:{1:'调休',10:'加班'}},
  26. getTexts:()=>({employmentInvalid:'invalid dates',employmentDraftOutside:'outside ',
  27. personnelCalendarChanged:'reload',overtimeExceeded:()=> 'over 36'}),
  28. utils:{toast:()=>{}},showTimecardError:()=>{},isNormalTimecardBlocked:()=>false};
  29. return env;
  30. }
  31. /** @returns {Promise<void>} 实际业务方法的日期、日历、默认值和提交前复核测试 */
  32. async function main() {
  33. const files = ['timecard-main.actions.js','timecard-repair.oyd.jsx','timecard-repair-lifecycle.oyd.jsx','timecard-change.oyd.jsx'];
  34. for (const file of files) {
  35. const employment = !file.includes('change');
  36. const env = page(file, employment);
  37. let queries = 0;
  38. env.utils.yida = {searchFormDatas:async () => { queries++; return {data:[]}; }};
  39. assert.equal(env.isDayOff(2026,10,1),true,'国内调休为休息日');
  40. assert.equal(env.isDayOff(2026,10,10),false,'国内周末补班为工作日');
  41. env.state.userInfo.isOverseasStaff = true;
  42. env._tcHolidayCalendarByYear = {2026:{20261001:'调休',20261010:'加班'}};
  43. assert.equal(env.isDayOff(2026,10,1),false);
  44. assert.equal(env.isDayOff(2026,10,10),true);
  45. assert.equal(env.isBeisenLeaveWorkday(2026,10,1),true);
  46. assert.equal(env.isBeisenLeaveWorkday(2026,10,10),false);
  47. assert.equal(env.getHolidayType(1),null);
  48. assert.equal(Object.keys(await env.loadHolidays(2026,10)).length,0);
  49. assert.equal(Object.keys(env._tcHolidayCalendarByYear).length,0);
  50. assert.equal(queries,0,'海外员工不请求国内日历');
  51. env.state.userInfo.isOverseasStaff = false;
  52. await env.loadHolidays(2026,10);
  53. assert.equal(queries,1,'切换回国内仍查询日历');
  54. if (!employment) continue;
  55. env.state.userInfo = {employmentStartDate:'2026-10-01',employmentEndDate:'2026-10-15',isOverseasStaff:true};
  56. assert.equal(env.isBeforeEmploymentDate(2026,9,30),true);
  57. assert.equal(env.isBeforeEmploymentDate(2026,10,1),false);
  58. assert.equal(env.isBeforeEmploymentDate(2026,10,15),false);
  59. assert.equal(env.isBeforeEmploymentDate(2026,10,16),true);
  60. assert.equal(env.getEmploymentDateKey(Date.parse('2026-10-14T16:00:00Z')),20261015);
  61. assert.equal(env.getEmploymentDateKey('2026-10-14T16:00:00Z'),20261015);
  62. assert.throws(()=> env.getEmploymentDateKey('2026-02-30'),/invalid/);
  63. assert.throws(()=> env.getEmploymentBounds({employmentStartDate:'2026-10-16',employmentEndDate:'2026-10-15'}),/invalid/);
  64. env.state.billableData = {0:{16:8}};
  65. assert.throws(()=> env.assertEmploymentInputs(),/20261016/,'范围外草稿不静默过滤');
  66. env.state.cellStatusMap = {billable_0_16:'approved'};
  67. env.assertEmploymentInputs();
  68. env.state.billableData = {};
  69. env.assertPersonnelSnapshot({...env.state.userInfo});
  70. assert.throws(()=> env.assertPersonnelSnapshot({...env.state.userInfo,isOverseasStaff:false}),/reload/);
  71. assert.throws(()=> env.assertPersonnelSnapshot({...env.state.userInfo,employmentEndDate:'2026-10-14'}),/reload/);
  72. const billable = {};
  73. env.applyDefaultWorkdayHours(2026,10,env.state.holidayMap,[{code:'P'}],billable,{}, {},false,{});
  74. assert.equal(billable[0][1],8,'海外节假日中的自然工作日默认8小时');
  75. assert.equal(billable[0][10],undefined,'海外周末补班不默认8小时');
  76. assert.equal(billable[0][16],undefined,'离职后不默认工时');
  77. env.state.userInfo = {isOverseasStaff:true};
  78. env.state.billableData = {0:{1:8,10:8}};
  79. assert.equal(env.validateMonthlyOvertime(),true);
  80. for (const hours of [36,36.5]) {
  81. env.state.billableData = {0:{3:hours/2,4:hours/2}};
  82. assert.equal(env.validateMonthlyOvertime(),hours===36,'保留36小时边界');
  83. }
  84. env.state.isExternalStaff = true;
  85. assert.equal(env.validateMonthlyOvertime(),true,'保留外部员工原豁免');
  86. }
  87. console.log('PASS 四入口海外自然周、国内回退、任职闭区间、上海时区、默认值、草稿保护、档案变更及36小时边界');
  88. }
  89. main().catch(error => {console.error(error);process.exitCode=1;});