@hecom/codearts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/README.md +228 -0
- package/bin/codearts +2 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +137 -0
- package/dist/commands/config.command.d.ts +5 -0
- package/dist/commands/config.command.js +109 -0
- package/dist/commands/daily.command.d.ts +10 -0
- package/dist/commands/daily.command.js +184 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/work-hour.command.d.ts +5 -0
- package/dist/commands/work-hour.command.js +153 -0
- package/dist/config/holidays.d.ts +60 -0
- package/dist/config/holidays.js +177 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/services/api.service.d.ts +98 -0
- package/dist/services/api.service.js +405 -0
- package/dist/services/business.service.d.ts +69 -0
- package/dist/services/business.service.js +330 -0
- package/dist/types/index.d.ts +522 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/config-loader.d.ts +22 -0
- package/dist/utils/config-loader.js +56 -0
- package/dist/utils/global-config.d.ts +24 -0
- package/dist/utils/global-config.js +153 -0
- package/package.json +70 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateDailyReport = generateDailyReport;
|
|
4
|
+
exports.dailyCommand = dailyCommand;
|
|
5
|
+
const business_service_1 = require("../services/business.service");
|
|
6
|
+
const config_loader_1 = require("../utils/config-loader");
|
|
7
|
+
function isBug(workHour) {
|
|
8
|
+
return (workHour.issue_type === '缺陷' || workHour.issue_type === '3' || workHour.issue_type === 'Bug');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 生成单个角色的日报
|
|
12
|
+
*/
|
|
13
|
+
async function generateDailyReport(businessService, projectId, roleId, targetDate) {
|
|
14
|
+
const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
|
|
15
|
+
if (roleMembers.length === 0) {
|
|
16
|
+
console.log(`角色ID ${roleId} 未找到用户,跳过工时查询`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const roleName = roleMembers[0].role_name;
|
|
20
|
+
const roleUserIds = roleMembers.map((member) => member.user_id);
|
|
21
|
+
const dailyStats = await businessService.getDailyWorkHourStats(projectId, roleUserIds, targetDate);
|
|
22
|
+
const activeIssueIds = new Set();
|
|
23
|
+
dailyStats.userStats.forEach((userStat) => {
|
|
24
|
+
userStat.workHours.forEach((workHour) => {
|
|
25
|
+
activeIssueIds.add(workHour.issue_id);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const bugWorkHoursMap = new Map();
|
|
29
|
+
dailyStats.userStats.forEach((userStat) => {
|
|
30
|
+
userStat.workHours.forEach((workHour) => {
|
|
31
|
+
if (isBug(workHour)) {
|
|
32
|
+
const key = workHour.subject;
|
|
33
|
+
const hours = parseFloat(workHour.work_hours_num) || 0;
|
|
34
|
+
if (bugWorkHoursMap.has(key)) {
|
|
35
|
+
const existing = bugWorkHoursMap.get(key);
|
|
36
|
+
existing.users.add(workHour.nick_name);
|
|
37
|
+
existing.totalHours += hours;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
bugWorkHoursMap.set(key, {
|
|
41
|
+
title: workHour.subject,
|
|
42
|
+
users: new Set([workHour.nick_name]),
|
|
43
|
+
totalHours: hours,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const bugWorkHours = Array.from(bugWorkHoursMap.values()).map((bug) => ({
|
|
50
|
+
title: bug.title,
|
|
51
|
+
nick_name: Array.from(bug.users).join('、'),
|
|
52
|
+
work_hours: bug.totalHours.toString(),
|
|
53
|
+
}));
|
|
54
|
+
console.log(`\n${dailyStats.date} 日报 [${roleName}]:`);
|
|
55
|
+
console.log('='.repeat(50));
|
|
56
|
+
dailyStats.userStats.forEach((userStat) => {
|
|
57
|
+
console.log(`\n\x1b[31m${userStat.userName} 工时: ${userStat.totalHours}小时\x1b[0m`);
|
|
58
|
+
userStat.workHours.forEach((workHour) => {
|
|
59
|
+
const summaryPart = workHour.summary && workHour.summary.trim() !== ''
|
|
60
|
+
? ` \x1b[36m${workHour.summary}\x1b[0m`
|
|
61
|
+
: '';
|
|
62
|
+
const workHoursTypePart = workHour.work_hours_type_name
|
|
63
|
+
? ` (${workHour.work_hours_type_name})`
|
|
64
|
+
: '';
|
|
65
|
+
console.log(` - ${workHour.subject}${summaryPart} (${workHour.issue_type})${workHoursTypePart} ${workHour.work_hours_num}小时`);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
console.log('\n\n');
|
|
69
|
+
console.log(`总结报告 [${roleName}]:`);
|
|
70
|
+
console.log('='.repeat(50));
|
|
71
|
+
let index = 1;
|
|
72
|
+
if (bugWorkHours.length > 0) {
|
|
73
|
+
console.log(`\n${index}.Bug跟进: ${bugWorkHours.length}项`);
|
|
74
|
+
if (bugWorkHours.length < 6) {
|
|
75
|
+
bugWorkHours.forEach((bug) => {
|
|
76
|
+
console.log(` - ${bug.title} ${bug.nick_name}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
index++;
|
|
80
|
+
}
|
|
81
|
+
const activeIterations = await businessService.getActiveIterationsOnDate(projectId, targetDate);
|
|
82
|
+
if (activeIterations.length === 0) {
|
|
83
|
+
console.log('没有找到正在进行中的迭代');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const activeIterationIds = activeIterations.map((iteration) => iteration.id);
|
|
87
|
+
const issues = await businessService.getWorkloadByIterationsAndUsers(projectId, activeIterationIds, roleUserIds);
|
|
88
|
+
const activeIssues = issues.filter((issue) => activeIssueIds.has(issue.id));
|
|
89
|
+
if (activeIssues.length !== 0) {
|
|
90
|
+
const activeIssuesByIteration = new Map();
|
|
91
|
+
activeIssues.forEach((issue) => {
|
|
92
|
+
const iterationId = issue.iteration.id;
|
|
93
|
+
if (!activeIssuesByIteration.has(iterationId)) {
|
|
94
|
+
activeIssuesByIteration.set(iterationId, []);
|
|
95
|
+
}
|
|
96
|
+
activeIssuesByIteration.get(iterationId).push(issue);
|
|
97
|
+
});
|
|
98
|
+
const issuesByIteration = new Map();
|
|
99
|
+
issues.forEach((issue) => {
|
|
100
|
+
const iterationId = issue.iteration.id;
|
|
101
|
+
if (!issuesByIteration.has(iterationId)) {
|
|
102
|
+
issuesByIteration.set(iterationId, []);
|
|
103
|
+
}
|
|
104
|
+
issuesByIteration.get(iterationId).push(issue);
|
|
105
|
+
});
|
|
106
|
+
activeIterations.forEach((iteration) => {
|
|
107
|
+
const iterationActiveIssues = activeIssuesByIteration.get(iteration.id) || [];
|
|
108
|
+
if (iterationActiveIssues.length === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const iterationIssues = issuesByIteration.get(iteration.id) || [];
|
|
112
|
+
if (iterationIssues.length > 0) {
|
|
113
|
+
const iterationStats = businessService.calculateWorkProgress(iterationIssues);
|
|
114
|
+
console.log(`${index}.${iteration.name} ${iterationStats.overallCompletionRate}%`);
|
|
115
|
+
index++;
|
|
116
|
+
}
|
|
117
|
+
iterationActiveIssues.forEach((issue) => {
|
|
118
|
+
const doneRate = issue.expected_work_hours
|
|
119
|
+
? issue.actual_work_hours / issue.expected_work_hours
|
|
120
|
+
: 0;
|
|
121
|
+
const displayDoneRate = issue.done_ratio
|
|
122
|
+
? issue.done_ratio
|
|
123
|
+
: Math.round(Math.min(doneRate * 100, 100));
|
|
124
|
+
console.log(` - ${issue.name} ${displayDoneRate}% ${issue.assigned_user.nick_name}`);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const displayedIssueIds = new Set();
|
|
129
|
+
activeIssues.forEach((issue) => {
|
|
130
|
+
displayedIssueIds.add(issue.id);
|
|
131
|
+
});
|
|
132
|
+
const subjectMap = {
|
|
133
|
+
'【移动端】会议、调研、环境处理等零散工作': '',
|
|
134
|
+
'【移动端】【鸿蒙】调研、问题修复、打包等零散工作': '【鸿蒙】',
|
|
135
|
+
// 可在此添加更多映射
|
|
136
|
+
};
|
|
137
|
+
const otherWorkHours = [];
|
|
138
|
+
dailyStats.userStats.forEach((userStat) => {
|
|
139
|
+
userStat.workHours.forEach((workHour) => {
|
|
140
|
+
if (workHour.summary &&
|
|
141
|
+
workHour.summary.trim() !== '' &&
|
|
142
|
+
!isBug(workHour) &&
|
|
143
|
+
!displayedIssueIds.has(workHour.issue_id)) {
|
|
144
|
+
const subject = subjectMap[workHour.subject] ?? workHour.subject;
|
|
145
|
+
otherWorkHours.push({
|
|
146
|
+
subject,
|
|
147
|
+
summary: workHour.summary,
|
|
148
|
+
nick_name: workHour.nick_name,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
if (otherWorkHours.length > 0) {
|
|
154
|
+
console.log(`${index}.其他: ${otherWorkHours.length}项`);
|
|
155
|
+
otherWorkHours.forEach((work) => {
|
|
156
|
+
const subjectPart = work.subject ? `${work.subject} ` : '';
|
|
157
|
+
console.log(` - ${subjectPart}${work.summary} ${work.nick_name}`);
|
|
158
|
+
});
|
|
159
|
+
index++;
|
|
160
|
+
}
|
|
161
|
+
console.log(`${index}.工时: ${dailyStats.totalHours}`);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* daily 命令入口
|
|
165
|
+
*/
|
|
166
|
+
async function dailyCommand(date, cliOptions = {}) {
|
|
167
|
+
try {
|
|
168
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
169
|
+
console.log(`开始统计 ${targetDate} 的日报...`);
|
|
170
|
+
const { projectId, roleIds, config } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
171
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
172
|
+
for (let i = 0; i < roleIds.length; i++) {
|
|
173
|
+
const roleId = roleIds[i];
|
|
174
|
+
if (i > 0) {
|
|
175
|
+
console.log('\n\n' + '='.repeat(80) + '\n');
|
|
176
|
+
}
|
|
177
|
+
await generateDailyReport(businessService, projectId, roleId, targetDate);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
console.error('执行过程中发生错误:', error);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configCommand = exports.workHourCommand = exports.dailyCommand = void 0;
|
|
4
|
+
var daily_command_1 = require("./daily.command");
|
|
5
|
+
Object.defineProperty(exports, "dailyCommand", { enumerable: true, get: function () { return daily_command_1.dailyCommand; } });
|
|
6
|
+
var work_hour_command_1 = require("./work-hour.command");
|
|
7
|
+
Object.defineProperty(exports, "workHourCommand", { enumerable: true, get: function () { return work_hour_command_1.workHourCommand; } });
|
|
8
|
+
var config_command_1 = require("./config.command");
|
|
9
|
+
Object.defineProperty(exports, "configCommand", { enumerable: true, get: function () { return config_command_1.configCommand; } });
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workHourCommand = workHourCommand;
|
|
4
|
+
const holidays_1 = require("../config/holidays");
|
|
5
|
+
const business_service_1 = require("../services/business.service");
|
|
6
|
+
const config_loader_1 = require("../utils/config-loader");
|
|
7
|
+
/**
|
|
8
|
+
* work-hour 命令入口
|
|
9
|
+
*/
|
|
10
|
+
async function workHourCommand(year, cliOptions = {}) {
|
|
11
|
+
try {
|
|
12
|
+
const targetYear = year || new Date().getFullYear().toString();
|
|
13
|
+
console.log(`开始获取 ${targetYear} 年工时统计...`);
|
|
14
|
+
const { projectId, roleIds, config } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
15
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
16
|
+
// 收集所有角色的成员和工时数据
|
|
17
|
+
const allMembers = [];
|
|
18
|
+
const allUserStats = [];
|
|
19
|
+
const allTypes = new Set();
|
|
20
|
+
for (const roleId of roleIds) {
|
|
21
|
+
const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
|
|
22
|
+
if (roleMembers.length === 0) {
|
|
23
|
+
console.log(`角色ID ${roleId} 未找到用户,跳过`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const roleName = roleMembers[0].role_name;
|
|
27
|
+
allMembers.push(...roleMembers);
|
|
28
|
+
const roleUserIds = roleMembers.map((member) => member.user_id);
|
|
29
|
+
const stats = await businessService.getAllWorkHourStats(projectId, roleUserIds, `${targetYear}-01-01`, `${targetYear}-12-31`);
|
|
30
|
+
// 为每个用户添加角色信息
|
|
31
|
+
stats.userStats.forEach((userStat) => {
|
|
32
|
+
allUserStats.push({
|
|
33
|
+
...userStat,
|
|
34
|
+
roleName,
|
|
35
|
+
roleId,
|
|
36
|
+
});
|
|
37
|
+
// 收集所有领域类型
|
|
38
|
+
userStat.domainStats.forEach((domainStat) => {
|
|
39
|
+
allTypes.add(domainStat.type);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (allMembers.length === 0) {
|
|
44
|
+
console.log('未找到任何角色的用户');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// 按角色ID排序用户
|
|
48
|
+
allUserStats.sort((a, b) => {
|
|
49
|
+
if (a.roleId !== b.roleId) {
|
|
50
|
+
return a.roleId - b.roleId;
|
|
51
|
+
}
|
|
52
|
+
return a.userName.localeCompare(b.userName);
|
|
53
|
+
});
|
|
54
|
+
const expectedWorkdays = (0, holidays_1.calculateExpectedWorkdays)(targetYear);
|
|
55
|
+
const expectedHoursPerPerson = expectedWorkdays * 8;
|
|
56
|
+
const totalExpectedHours = expectedHoursPerPerson * allMembers.length;
|
|
57
|
+
// 计算总工时
|
|
58
|
+
const totalHours = allUserStats.reduce((sum, userStat) => sum + userStat.totalHours, 0);
|
|
59
|
+
const totalEntries = allUserStats.reduce((sum, userStat) => sum + userStat.domainStats.reduce((s, d) => s + d.workHours.length, 0), 0);
|
|
60
|
+
console.log(`\n${targetYear}年工时统计报告`);
|
|
61
|
+
console.log('='.repeat(80));
|
|
62
|
+
console.log(`统计期间: ${targetYear}-01-01 至 ${targetYear}-12-31`);
|
|
63
|
+
console.log(`统计角色: ${roleIds.length} 个角色`);
|
|
64
|
+
console.log(`统计人数: ${allMembers.length} 人`);
|
|
65
|
+
console.log(`应计工作日: ${expectedWorkdays} 天`);
|
|
66
|
+
console.log(`应计工时: ${totalExpectedHours} 小时 (${expectedHoursPerPerson} 小时/人)`);
|
|
67
|
+
console.log(`实际工时: ${totalHours} 小时`);
|
|
68
|
+
console.log(`工时完成率: ${((totalHours / totalExpectedHours) * 100).toFixed(2)}%`);
|
|
69
|
+
console.log(`工时条目: ${totalEntries} 条`);
|
|
70
|
+
console.log('='.repeat(80));
|
|
71
|
+
// 构建表格数据:人作为行,type作为列
|
|
72
|
+
const tableData = {};
|
|
73
|
+
const typeTotals = {};
|
|
74
|
+
// 初始化总计
|
|
75
|
+
allTypes.forEach((type) => {
|
|
76
|
+
typeTotals[type] = 0;
|
|
77
|
+
});
|
|
78
|
+
// 按角色分组并填充数据
|
|
79
|
+
let currentRoleId = null;
|
|
80
|
+
let currentRoleName = '';
|
|
81
|
+
const roleSubtotals = {};
|
|
82
|
+
allUserStats.forEach((userStat, index) => {
|
|
83
|
+
// 检测角色变化,插入小计行
|
|
84
|
+
if (currentRoleId !== null && currentRoleId !== userStat.roleId) {
|
|
85
|
+
const subtotalRow = {};
|
|
86
|
+
subtotalRow['角色'] = `${currentRoleName} 小计`;
|
|
87
|
+
let subtotal = 0;
|
|
88
|
+
allTypes.forEach((type) => {
|
|
89
|
+
subtotalRow[type] = roleSubtotals[type] || 0;
|
|
90
|
+
subtotal += roleSubtotals[type] || 0;
|
|
91
|
+
});
|
|
92
|
+
subtotalRow['合计'] = subtotal;
|
|
93
|
+
tableData[`─ ${currentRoleName} 小计`] = subtotalRow;
|
|
94
|
+
// 重置小计
|
|
95
|
+
Object.keys(roleSubtotals).forEach((key) => {
|
|
96
|
+
roleSubtotals[key] = 0;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 更新当前角色
|
|
100
|
+
currentRoleId = userStat.roleId;
|
|
101
|
+
currentRoleName = userStat.roleName;
|
|
102
|
+
// 填充用户数据
|
|
103
|
+
const row = {};
|
|
104
|
+
row['角色'] = userStat.roleName;
|
|
105
|
+
// 初始化所有类型为0
|
|
106
|
+
allTypes.forEach((type) => {
|
|
107
|
+
row[type] = 0;
|
|
108
|
+
});
|
|
109
|
+
// 填充实际工时
|
|
110
|
+
let userTotal = 0;
|
|
111
|
+
userStat.domainStats.forEach((domainStat) => {
|
|
112
|
+
row[domainStat.type] = domainStat.totalHours;
|
|
113
|
+
userTotal += domainStat.totalHours;
|
|
114
|
+
typeTotals[domainStat.type] += domainStat.totalHours;
|
|
115
|
+
// 累加到角色小计
|
|
116
|
+
if (!roleSubtotals[domainStat.type]) {
|
|
117
|
+
roleSubtotals[domainStat.type] = 0;
|
|
118
|
+
}
|
|
119
|
+
roleSubtotals[domainStat.type] += domainStat.totalHours;
|
|
120
|
+
});
|
|
121
|
+
row['合计'] = userTotal;
|
|
122
|
+
tableData[userStat.userName] = row;
|
|
123
|
+
// 如果是最后一个用户,添加最后一个角色的小计
|
|
124
|
+
if (index === allUserStats.length - 1) {
|
|
125
|
+
const subtotalRow = {};
|
|
126
|
+
subtotalRow['角色'] = `${currentRoleName} 小计`;
|
|
127
|
+
let subtotal = 0;
|
|
128
|
+
allTypes.forEach((type) => {
|
|
129
|
+
subtotalRow[type] = roleSubtotals[type] || 0;
|
|
130
|
+
subtotal += roleSubtotals[type] || 0;
|
|
131
|
+
});
|
|
132
|
+
subtotalRow['合计'] = subtotal;
|
|
133
|
+
tableData[`─ ${currentRoleName} 小计`] = subtotalRow;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// 添加总计行
|
|
137
|
+
const totalRow = {};
|
|
138
|
+
totalRow['角色'] = '━ 总计';
|
|
139
|
+
let grandTotal = 0;
|
|
140
|
+
allTypes.forEach((type) => {
|
|
141
|
+
totalRow[type] = typeTotals[type];
|
|
142
|
+
grandTotal += typeTotals[type];
|
|
143
|
+
});
|
|
144
|
+
totalRow['合计'] = grandTotal;
|
|
145
|
+
tableData['━━ 总计'] = totalRow;
|
|
146
|
+
console.log('\n工时统计表:');
|
|
147
|
+
console.table(tableData);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('执行过程中发生错误:', error);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中国法定节假日配置
|
|
3
|
+
*
|
|
4
|
+
* 配置说明:
|
|
5
|
+
* - festivals: 按节日类型组织的假期配置
|
|
6
|
+
* - adjustedWorkdays: 按月份组织的调休工作日
|
|
7
|
+
* - bigSmallWeekWorkdays: 按月份组织的大小周制度工作日
|
|
8
|
+
*
|
|
9
|
+
* 数据来源:国务院办公厅每年发布的节假日安排通知
|
|
10
|
+
*/
|
|
11
|
+
/** 法定节假日枚举 */
|
|
12
|
+
declare enum FestivalType {
|
|
13
|
+
NewYear = "\u5143\u65E6",
|
|
14
|
+
SpringFestival = "\u6625\u8282",
|
|
15
|
+
TombSweepingDay = "\u6E05\u660E\u8282",
|
|
16
|
+
LaborDay = "\u52B3\u52A8\u8282",
|
|
17
|
+
DragonBoatFestival = "\u7AEF\u5348\u8282",
|
|
18
|
+
MidAutumnFestival = "\u4E2D\u79CB\u8282",
|
|
19
|
+
NationalDay = "\u56FD\u5E86\u8282"
|
|
20
|
+
}
|
|
21
|
+
/** 节日配置 */
|
|
22
|
+
interface FestivalConfig {
|
|
23
|
+
type: FestivalType;
|
|
24
|
+
dates: string[];
|
|
25
|
+
adjustedDays?: string[];
|
|
26
|
+
}
|
|
27
|
+
/** 年度假期配置 */
|
|
28
|
+
interface HolidayConfig {
|
|
29
|
+
festivals: Record<FestivalType, FestivalConfig>;
|
|
30
|
+
bigWeekWorkdays: [
|
|
31
|
+
string[],
|
|
32
|
+
string[],
|
|
33
|
+
string[],
|
|
34
|
+
string[],
|
|
35
|
+
string[],
|
|
36
|
+
string[],
|
|
37
|
+
string[],
|
|
38
|
+
string[],
|
|
39
|
+
string[],
|
|
40
|
+
string[],
|
|
41
|
+
string[],
|
|
42
|
+
string[]
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
export declare const HOLIDAYS: Record<string, HolidayConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* 格式化日期为 YYYY-MM-DD
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatDate(date: Date): string;
|
|
50
|
+
/**
|
|
51
|
+
* 判断是否为工作日
|
|
52
|
+
*/
|
|
53
|
+
export declare function isWorkday(date: Date, year: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 计算工作日
|
|
56
|
+
* @param year 年份
|
|
57
|
+
* @returns 应计工作日天数
|
|
58
|
+
*/
|
|
59
|
+
export declare function calculateExpectedWorkdays(year: string): number;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 中国法定节假日配置
|
|
4
|
+
*
|
|
5
|
+
* 配置说明:
|
|
6
|
+
* - festivals: 按节日类型组织的假期配置
|
|
7
|
+
* - adjustedWorkdays: 按月份组织的调休工作日
|
|
8
|
+
* - bigSmallWeekWorkdays: 按月份组织的大小周制度工作日
|
|
9
|
+
*
|
|
10
|
+
* 数据来源:国务院办公厅每年发布的节假日安排通知
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.HOLIDAYS = void 0;
|
|
14
|
+
exports.formatDate = formatDate;
|
|
15
|
+
exports.isWorkday = isWorkday;
|
|
16
|
+
exports.calculateExpectedWorkdays = calculateExpectedWorkdays;
|
|
17
|
+
/** 法定节假日枚举 */
|
|
18
|
+
var FestivalType;
|
|
19
|
+
(function (FestivalType) {
|
|
20
|
+
FestivalType["NewYear"] = "\u5143\u65E6";
|
|
21
|
+
FestivalType["SpringFestival"] = "\u6625\u8282";
|
|
22
|
+
FestivalType["TombSweepingDay"] = "\u6E05\u660E\u8282";
|
|
23
|
+
FestivalType["LaborDay"] = "\u52B3\u52A8\u8282";
|
|
24
|
+
FestivalType["DragonBoatFestival"] = "\u7AEF\u5348\u8282";
|
|
25
|
+
FestivalType["MidAutumnFestival"] = "\u4E2D\u79CB\u8282";
|
|
26
|
+
FestivalType["NationalDay"] = "\u56FD\u5E86\u8282";
|
|
27
|
+
})(FestivalType || (FestivalType = {}));
|
|
28
|
+
exports.HOLIDAYS = {
|
|
29
|
+
'2026': {
|
|
30
|
+
festivals: {
|
|
31
|
+
[FestivalType.NewYear]: {
|
|
32
|
+
type: FestivalType.NewYear,
|
|
33
|
+
dates: ['2026-01-01', '2026-01-02', '2026-01-03'],
|
|
34
|
+
adjustedDays: ['2026-01-04'], // 1月4日(周日)上班
|
|
35
|
+
},
|
|
36
|
+
[FestivalType.SpringFestival]: {
|
|
37
|
+
type: FestivalType.SpringFestival,
|
|
38
|
+
dates: [
|
|
39
|
+
'2026-02-15',
|
|
40
|
+
'2026-02-16',
|
|
41
|
+
'2026-02-17',
|
|
42
|
+
'2026-02-18',
|
|
43
|
+
'2026-02-19',
|
|
44
|
+
'2026-02-20',
|
|
45
|
+
'2026-02-21',
|
|
46
|
+
'2026-02-22',
|
|
47
|
+
'2026-02-23',
|
|
48
|
+
],
|
|
49
|
+
adjustedDays: [
|
|
50
|
+
'2026-02-14', // 2月14日(周六)上班
|
|
51
|
+
'2026-02-28', // 2月28日(周六)上班
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
[FestivalType.TombSweepingDay]: {
|
|
55
|
+
type: FestivalType.TombSweepingDay,
|
|
56
|
+
dates: ['2026-04-04', '2026-04-05', '2026-04-06'],
|
|
57
|
+
},
|
|
58
|
+
[FestivalType.LaborDay]: {
|
|
59
|
+
type: FestivalType.LaborDay,
|
|
60
|
+
dates: ['2026-05-01', '2026-05-02', '2026-05-03', '2026-05-04', '2026-05-05'],
|
|
61
|
+
adjustedDays: ['2026-05-09'], // 5月9日(周六)上班
|
|
62
|
+
},
|
|
63
|
+
[FestivalType.DragonBoatFestival]: {
|
|
64
|
+
type: FestivalType.DragonBoatFestival,
|
|
65
|
+
dates: ['2026-06-22', '2026-06-23', '2026-06-24'],
|
|
66
|
+
},
|
|
67
|
+
[FestivalType.MidAutumnFestival]: {
|
|
68
|
+
type: FestivalType.MidAutumnFestival,
|
|
69
|
+
dates: ['2026-09-29', '2026-09-30', '2026-10-01'],
|
|
70
|
+
},
|
|
71
|
+
[FestivalType.NationalDay]: {
|
|
72
|
+
type: FestivalType.NationalDay,
|
|
73
|
+
dates: [
|
|
74
|
+
'2026-10-01',
|
|
75
|
+
'2026-10-02',
|
|
76
|
+
'2026-10-03',
|
|
77
|
+
'2026-10-04',
|
|
78
|
+
'2026-10-05',
|
|
79
|
+
'2026-10-06',
|
|
80
|
+
'2026-10-07',
|
|
81
|
+
],
|
|
82
|
+
adjustedDays: [
|
|
83
|
+
'2026-10-08', // 10月8日(周日)上班
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
bigWeekWorkdays: [
|
|
88
|
+
['2026-01-17', '2026-01-31'],
|
|
89
|
+
['2026-02-07', '2026-02-21'],
|
|
90
|
+
['2026-03-07', '2026-03-21'],
|
|
91
|
+
['2026-04-11', '2026-04-25'],
|
|
92
|
+
['2026-05-16', '2026-05-30'], // ?
|
|
93
|
+
['2026-06-13', '2026-06-27'],
|
|
94
|
+
['2026-07-11', '2026-07-25'],
|
|
95
|
+
['2026-08-08', '2026-08-22'],
|
|
96
|
+
['2026-09-05', '2026-09-19'],
|
|
97
|
+
['2026-10-17', '2026-10-31'],
|
|
98
|
+
['2026-11-14', '2026-11-28'],
|
|
99
|
+
['2026-12-12', '2026-12-26'],
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 判断是否为周末(周六或周日)
|
|
105
|
+
*/
|
|
106
|
+
function isWeekend(date) {
|
|
107
|
+
const day = date.getDay();
|
|
108
|
+
return day === 0 || day === 6;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 格式化日期为 YYYY-MM-DD
|
|
112
|
+
*/
|
|
113
|
+
function formatDate(date) {
|
|
114
|
+
const year = date.getFullYear();
|
|
115
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
116
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
117
|
+
return `${year}-${month}-${day}`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 判断是否为工作日
|
|
121
|
+
*/
|
|
122
|
+
function isWorkday(date, year) {
|
|
123
|
+
const dateStr = formatDate(date);
|
|
124
|
+
const yearConfig = exports.HOLIDAYS[year];
|
|
125
|
+
if (!yearConfig) {
|
|
126
|
+
return !isWeekend(date);
|
|
127
|
+
}
|
|
128
|
+
// 检查是否在法定节假日中
|
|
129
|
+
for (const festival of Object.values(yearConfig.festivals)) {
|
|
130
|
+
if (festival.dates.includes(dateStr)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
// 检查是否是该节日的调休工作日
|
|
134
|
+
if (festival.adjustedDays?.includes(dateStr)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// 检查是否在大小周工作日中
|
|
139
|
+
for (const workdays of yearConfig.bigWeekWorkdays) {
|
|
140
|
+
if (workdays.includes(dateStr)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return !isWeekend(date);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 计算工作日
|
|
148
|
+
* @param year 年份
|
|
149
|
+
* @returns 应计工作日天数
|
|
150
|
+
*/
|
|
151
|
+
function calculateExpectedWorkdays(year) {
|
|
152
|
+
const yearNum = parseInt(year);
|
|
153
|
+
const currentYear = new Date().getFullYear();
|
|
154
|
+
const currentDate = new Date();
|
|
155
|
+
let endDate;
|
|
156
|
+
if (yearNum < currentYear) {
|
|
157
|
+
// 历史年份,计算全年
|
|
158
|
+
endDate = new Date(yearNum, 11, 31);
|
|
159
|
+
}
|
|
160
|
+
else if (yearNum === currentYear) {
|
|
161
|
+
// 当前年份,计算到今天
|
|
162
|
+
endDate = currentDate;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// 未来年份,返回0
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
const startDate = new Date(yearNum, 0, 1);
|
|
169
|
+
let workdays = 0;
|
|
170
|
+
// 遍历每一天
|
|
171
|
+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
|
172
|
+
if (isWorkday(d, year)) {
|
|
173
|
+
workdays++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return workdays;
|
|
177
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.BusinessService = exports.ApiService = void 0;
|
|
18
|
+
// 导出API服务和类型定义
|
|
19
|
+
var api_service_1 = require("./services/api.service");
|
|
20
|
+
Object.defineProperty(exports, "ApiService", { enumerable: true, get: function () { return api_service_1.ApiService; } });
|
|
21
|
+
var business_service_1 = require("./services/business.service");
|
|
22
|
+
Object.defineProperty(exports, "BusinessService", { enumerable: true, get: function () { return business_service_1.BusinessService; } });
|
|
23
|
+
__exportStar(require("./types"), exports);
|