@hecom/codearts 0.2.3 → 0.3.1
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/README.md +1 -1
- package/dist/bin/cli.js +10 -10
- package/dist/commands/bug.command.d.ts +1 -1
- package/dist/commands/bug.command.js +78 -96
- package/dist/commands/config.command.js +10 -16
- package/dist/commands/daily.command.js +155 -159
- package/dist/commands/work-hour.command.js +53 -120
- package/dist/config/holidays.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/services/business.service.d.ts +14 -19
- package/dist/services/business.service.js +28 -37
- package/dist/types/index.d.ts +13 -13
- package/dist/types/index.js +8 -17
- package/dist/utils/config-loader.d.ts +5 -5
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/console.d.ts +4 -0
- package/dist/{constant/index.js → utils/console.js} +18 -0
- package/dist/utils/csv-writer.d.ts +11 -2
- package/dist/utils/csv-writer.js +37 -5
- package/dist/utils/inquirer-theme.d.ts +13 -0
- package/dist/utils/inquirer-theme.js +29 -0
- package/dist/utils/logger.d.ts +0 -1
- package/package.json +6 -4
- package/dist/constant/index.d.ts +0 -1
|
@@ -4,47 +4,91 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.dailyCommand = dailyCommand;
|
|
7
|
+
const ora_1 = __importDefault(require("ora"));
|
|
7
8
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const holidays_1 = require("../config/holidays");
|
|
8
10
|
const business_service_1 = require("../services/business.service");
|
|
9
11
|
const config_loader_1 = require("../utils/config-loader");
|
|
12
|
+
const console_1 = require("../utils/console");
|
|
10
13
|
const csv_writer_1 = require("../utils/csv-writer");
|
|
11
14
|
const logger_1 = require("../utils/logger");
|
|
12
15
|
function isBug(workHour) {
|
|
13
|
-
return (workHour.
|
|
16
|
+
return (workHour.issueType === '缺陷' || workHour.issueType === '3' || workHour.issueType === 'Bug');
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
19
|
+
* 查询用户工时明细(不区分角色)
|
|
20
|
+
* @param businessService 业务服务实例
|
|
21
|
+
* @param projectId 项目ID
|
|
22
|
+
* @param roleIds 角色ID列表
|
|
23
|
+
* @param targetDate 目标日期
|
|
24
|
+
* @return 用户工时明细列表
|
|
17
25
|
*/
|
|
18
|
-
async function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
async function queryDailyUserStats(businessService, projectId, roleIds, targetDate) {
|
|
27
|
+
const members = await businessService.getMembersByRoleIds(projectId, roleIds);
|
|
28
|
+
const userIds = members.map((member) => member.user_id);
|
|
29
|
+
const dailyStats = await businessService.getDailyWorkHourStats(projectId, userIds, targetDate);
|
|
30
|
+
const userStats = dailyStats.userStats.map((userStat) => ({
|
|
31
|
+
date: targetDate,
|
|
32
|
+
userId: userStat.userId,
|
|
33
|
+
userName: userStat.userName,
|
|
34
|
+
roleName: members.find((m) => m.user_id === userStat.userId)?.role_name,
|
|
35
|
+
totalHours: userStat.totalHours,
|
|
36
|
+
workHours: userStat.workHours.map((workHour) => ({
|
|
37
|
+
issueId: workHour.issue_id,
|
|
38
|
+
subject: workHour.subject,
|
|
39
|
+
summary: workHour.summary,
|
|
40
|
+
issueType: workHour.issue_type,
|
|
41
|
+
nickName: workHour.nick_name,
|
|
42
|
+
workHoursTypeName: workHour.work_hours_type_name,
|
|
43
|
+
workHoursNum: workHour.work_hours_num,
|
|
44
|
+
})),
|
|
45
|
+
}));
|
|
46
|
+
// 判断是否为工作日,非工作日应计工时为0
|
|
47
|
+
const isWorkDay = (0, holidays_1.isWorkday)(new Date(targetDate), targetDate.split('-')[0]);
|
|
48
|
+
const expectedHours = isWorkDay ? members.length * 8 : 0;
|
|
49
|
+
const totalMap = [
|
|
50
|
+
['统计人数', `${members.length} 人`],
|
|
51
|
+
['应计工时', `${expectedHours} 小时`],
|
|
52
|
+
['实际工时', `${dailyStats.totalHours} 小时`],
|
|
53
|
+
];
|
|
54
|
+
return {
|
|
55
|
+
list: userStats,
|
|
56
|
+
roleNames: Array.from(new Set(userStats.map((s) => s.roleName).filter((r) => r !== undefined))),
|
|
57
|
+
title: `${targetDate} 工时统计`,
|
|
58
|
+
totalMap,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 查询日报报告数据(包括Bug汇总、迭代进度、其他工作等)
|
|
63
|
+
* @param businessService 业务服务实例
|
|
64
|
+
* @param projectId 项目ID
|
|
65
|
+
* @param targetDate 目标日期
|
|
66
|
+
* @param userIds 用户ID列表
|
|
67
|
+
* @param userStats 用户工时统计数据
|
|
68
|
+
*/
|
|
69
|
+
async function queryDailyReport(businessService, projectId, targetDate, userStats) {
|
|
70
|
+
// Bug汇总
|
|
26
71
|
const activeIssueIds = new Set();
|
|
27
|
-
|
|
72
|
+
userStats.forEach((userStat) => {
|
|
28
73
|
userStat.workHours.forEach((workHour) => {
|
|
29
|
-
activeIssueIds.add(workHour.
|
|
74
|
+
activeIssueIds.add(workHour.issueId);
|
|
30
75
|
});
|
|
31
76
|
});
|
|
32
|
-
// Bug汇总
|
|
33
77
|
const bugWorkHoursMap = new Map();
|
|
34
|
-
|
|
78
|
+
userStats.forEach((userStat) => {
|
|
35
79
|
userStat.workHours.forEach((workHour) => {
|
|
36
80
|
if (isBug(workHour)) {
|
|
37
81
|
const key = workHour.subject;
|
|
38
|
-
const hours = parseFloat(workHour.
|
|
82
|
+
const hours = parseFloat(workHour.workHoursNum) || 0;
|
|
39
83
|
if (bugWorkHoursMap.has(key)) {
|
|
40
84
|
const existing = bugWorkHoursMap.get(key);
|
|
41
|
-
existing.users.add(
|
|
85
|
+
existing.users.add(userStat.userName);
|
|
42
86
|
existing.totalHours += hours;
|
|
43
87
|
}
|
|
44
88
|
else {
|
|
45
89
|
bugWorkHoursMap.set(key, {
|
|
46
90
|
title: workHour.subject,
|
|
47
|
-
users: new Set([
|
|
91
|
+
users: new Set([userStat.userName]),
|
|
48
92
|
totalHours: hours,
|
|
49
93
|
});
|
|
50
94
|
}
|
|
@@ -61,11 +105,11 @@ async function queryDailyReportData(businessService, projectId, roleId, targetDa
|
|
|
61
105
|
};
|
|
62
106
|
// 迭代进度
|
|
63
107
|
const activeIterations = await businessService.getActiveIterationsOnDate(projectId, targetDate);
|
|
64
|
-
|
|
108
|
+
let iterationProgress = [];
|
|
65
109
|
let activeIssues = [];
|
|
66
110
|
if (activeIterations.length > 0) {
|
|
67
111
|
const activeIterationIds = activeIterations.map((iteration) => iteration.id);
|
|
68
|
-
const issues = await businessService.getWorkloadByIterationsAndUsers(projectId, activeIterationIds,
|
|
112
|
+
const issues = await businessService.getWorkloadByIterationsAndUsers(projectId, activeIterationIds, userStats.map((s) => s.userId));
|
|
69
113
|
activeIssues = issues.filter((issue) => activeIssueIds.has(issue.id));
|
|
70
114
|
if (activeIssues.length > 0) {
|
|
71
115
|
const activeIssuesByIteration = new Map();
|
|
@@ -131,42 +175,26 @@ async function queryDailyReportData(businessService, projectId, roleId, targetDa
|
|
|
131
175
|
'【移动端】【鸿蒙】调研、问题修复、打包等零散工作': '【鸿蒙】',
|
|
132
176
|
};
|
|
133
177
|
const otherWork = [];
|
|
134
|
-
|
|
178
|
+
userStats.forEach((userStat) => {
|
|
135
179
|
userStat.workHours.forEach((workHour) => {
|
|
136
180
|
if (workHour.summary &&
|
|
137
181
|
workHour.summary.trim() !== '' &&
|
|
138
182
|
!isBug(workHour) &&
|
|
139
|
-
!displayedIssueIds.has(workHour.
|
|
183
|
+
!displayedIssueIds.has(workHour.issueId)) {
|
|
140
184
|
const subject = subjectMap[workHour.subject] ?? workHour.subject;
|
|
141
185
|
otherWork.push({
|
|
142
186
|
subject,
|
|
143
187
|
summary: workHour.summary,
|
|
144
|
-
nickName:
|
|
188
|
+
nickName: userStat.userName,
|
|
145
189
|
});
|
|
146
190
|
}
|
|
147
191
|
});
|
|
148
192
|
});
|
|
149
|
-
// 用户工时统计
|
|
150
|
-
const userStats = dailyStats.userStats.map((userStat) => ({
|
|
151
|
-
userName: userStat.userName,
|
|
152
|
-
totalHours: userStat.totalHours,
|
|
153
|
-
workHours: userStat.workHours.map((workHour) => ({
|
|
154
|
-
subject: workHour.subject,
|
|
155
|
-
summary: workHour.summary,
|
|
156
|
-
issueType: workHour.issue_type,
|
|
157
|
-
workHoursTypeName: workHour.work_hours_type_name,
|
|
158
|
-
workHoursNum: workHour.work_hours_num,
|
|
159
|
-
})),
|
|
160
|
-
}));
|
|
161
193
|
return {
|
|
162
|
-
date: targetDate,
|
|
163
|
-
roleName,
|
|
164
|
-
roleId,
|
|
165
|
-
userStats,
|
|
166
194
|
bugSummary,
|
|
167
195
|
iterationProgress,
|
|
168
196
|
otherWork,
|
|
169
|
-
totalHours:
|
|
197
|
+
totalHours: userStats.reduce((sum, stat) => sum + stat.totalHours, 0),
|
|
170
198
|
};
|
|
171
199
|
}
|
|
172
200
|
function issueTypeColor(type) {
|
|
@@ -179,76 +207,44 @@ function issueTypeColor(type) {
|
|
|
179
207
|
/**
|
|
180
208
|
* 控制台输出日报(多角色统一汇总)
|
|
181
209
|
*/
|
|
182
|
-
function outputConsole(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
const targetDate = allReports[0].date;
|
|
187
|
-
logger_1.logger.info(`\n${targetDate} 工时统计 [${allReports.map((r) => r.roleName).join(', ')}]`);
|
|
188
|
-
logger_1.logger.info('='.repeat(80));
|
|
189
|
-
// 按角色显示汇总工时和人数
|
|
190
|
-
const roleSummaries = [];
|
|
191
|
-
const total = { totalHours: 0, memberCount: 0, roleName: '总计' };
|
|
192
|
-
allReports.forEach((report) => {
|
|
193
|
-
roleSummaries.push({
|
|
194
|
-
roleName: report.roleName,
|
|
195
|
-
totalHours: report.totalHours,
|
|
196
|
-
memberCount: report.userStats.length,
|
|
197
|
-
});
|
|
198
|
-
total.totalHours += report.totalHours;
|
|
199
|
-
total.memberCount += report.userStats.length;
|
|
200
|
-
});
|
|
201
|
-
roleSummaries.push(total);
|
|
202
|
-
roleSummaries.forEach((summary) => {
|
|
203
|
-
logger_1.logger.info(`[${summary.roleName}] ${summary.totalHours}小时, 人数: ${summary.memberCount}人`);
|
|
204
|
-
});
|
|
205
|
-
logger_1.logger.info('='.repeat(80));
|
|
210
|
+
function outputConsole(data) {
|
|
211
|
+
const list = data.list;
|
|
212
|
+
(0, console_1.consoleTotal)(data);
|
|
206
213
|
// 平铺所有用户的工时明细
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
? ` (${workHour.workHoursTypeName})`
|
|
219
|
-
: '';
|
|
220
|
-
logger_1.logger.info(` ${issueTypeColor(workHour.issueType)} ${workHour.subject}${summaryPart} ${workHoursTypePart} ${workHour.workHoursNum}小时`);
|
|
221
|
-
});
|
|
214
|
+
list.forEach((userStat) => {
|
|
215
|
+
logger_1.logger.info();
|
|
216
|
+
logger_1.logger.info(picocolors_1.default.red(`${userStat.userName} ${userStat.totalHours}小时`));
|
|
217
|
+
userStat.workHours
|
|
218
|
+
.sort((a, b) => a.issueType.localeCompare(b.issueType))
|
|
219
|
+
.forEach((workHour) => {
|
|
220
|
+
const summaryPart = workHour.summary && workHour.summary.trim() !== '' ? picocolors_1.default.cyan(` ${workHour.summary}`) : '';
|
|
221
|
+
const workHoursTypePart = workHour.workHoursTypeName
|
|
222
|
+
? ` (${workHour.workHoursTypeName})`
|
|
223
|
+
: '';
|
|
224
|
+
logger_1.logger.info(` ${issueTypeColor(workHour.issueType)} ${workHour.subject}${summaryPart} ${workHoursTypePart} ${workHour.workHoursNum}小时`);
|
|
222
225
|
});
|
|
223
226
|
});
|
|
224
|
-
if (!showReport) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
// 总结报告(合并所有角色的数据)
|
|
228
|
-
consoleReport(allReports, total);
|
|
229
227
|
}
|
|
230
|
-
function consoleReport(
|
|
228
|
+
function consoleReport(report) {
|
|
231
229
|
logger_1.logger.info('\n\n');
|
|
232
230
|
logger_1.logger.info(`总结报告:`);
|
|
233
231
|
logger_1.logger.info('='.repeat(80));
|
|
234
232
|
let index = 1;
|
|
235
233
|
// 合并所有 Bug
|
|
236
234
|
const allBugs = new Map();
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
});
|
|
235
|
+
report.bugSummary.items.forEach((bug) => {
|
|
236
|
+
if (allBugs.has(bug.title)) {
|
|
237
|
+
const existing = allBugs.get(bug.title);
|
|
238
|
+
bug.users.split('、').forEach((user) => existing.users.add(user));
|
|
239
|
+
existing.hours += bug.hours;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
allBugs.set(bug.title, {
|
|
243
|
+
title: bug.title,
|
|
244
|
+
users: new Set(bug.users.split('、')),
|
|
245
|
+
hours: bug.hours,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
252
248
|
});
|
|
253
249
|
if (allBugs.size > 0) {
|
|
254
250
|
logger_1.logger.info(`\n${index}.Bug跟进: ${allBugs.size}项`);
|
|
@@ -261,28 +257,26 @@ function consoleReport(allReports, total) {
|
|
|
261
257
|
}
|
|
262
258
|
// 合并所有迭代进度
|
|
263
259
|
const allIterations = new Map();
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
});
|
|
260
|
+
report.iterationProgress.forEach((iteration) => {
|
|
261
|
+
if (allIterations.has(iteration.iterationName)) {
|
|
262
|
+
const existing = allIterations.get(iteration.iterationName);
|
|
263
|
+
// 使用最高的完成率
|
|
264
|
+
existing.completionRate = Math.max(existing.completionRate, iteration.completionRate);
|
|
265
|
+
// 合并活跃问题(去重)
|
|
266
|
+
iteration.activeIssues.forEach((issue) => {
|
|
267
|
+
const exists = existing.activeIssues.some((i) => i.name === issue.name);
|
|
268
|
+
if (!exists) {
|
|
269
|
+
existing.activeIssues.push(issue);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
allIterations.set(iteration.iterationName, {
|
|
275
|
+
iterationName: iteration.iterationName,
|
|
276
|
+
completionRate: iteration.completionRate,
|
|
277
|
+
activeIssues: [...iteration.activeIssues],
|
|
278
|
+
});
|
|
279
|
+
}
|
|
286
280
|
});
|
|
287
281
|
Array.from(allIterations.values()).forEach((iteration) => {
|
|
288
282
|
logger_1.logger.info(`${index}.${iteration.iterationName} ${iteration.completionRate}%`);
|
|
@@ -292,31 +286,34 @@ function consoleReport(allReports, total) {
|
|
|
292
286
|
});
|
|
293
287
|
});
|
|
294
288
|
// 合并所有其他工作
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
});
|
|
299
|
-
if (allOtherWork.length > 0) {
|
|
300
|
-
logger_1.logger.info(`${index}.其他: ${allOtherWork.length}项`);
|
|
301
|
-
allOtherWork.forEach((work) => {
|
|
289
|
+
if (report.otherWork.length > 0) {
|
|
290
|
+
logger_1.logger.info(`${index}.其他: ${report.otherWork.length}项`);
|
|
291
|
+
report.otherWork.forEach((work) => {
|
|
302
292
|
const subjectPart = work.subject ? `${work.subject} ` : '';
|
|
303
293
|
logger_1.logger.info(` - ${subjectPart}${work.summary} ${work.nickName}`);
|
|
304
294
|
});
|
|
305
295
|
index++;
|
|
306
296
|
}
|
|
307
|
-
logger_1.logger.info(`${index}.工时: ${
|
|
297
|
+
logger_1.logger.info(`${index}.工时: ${report.totalHours}`);
|
|
308
298
|
}
|
|
309
299
|
/**
|
|
310
300
|
* CSV 文件输出
|
|
311
301
|
*/
|
|
312
302
|
function outputCsv(list, targetDate) {
|
|
313
303
|
const csvLines = [];
|
|
314
|
-
csvLines.push('
|
|
315
|
-
list.forEach((
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
304
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)(['日期', '角色', '用户', '类型', '工作项', '工作内容', '工时类型', '工时']));
|
|
305
|
+
list.forEach((userStat) => {
|
|
306
|
+
userStat.workHours.forEach((workHour) => {
|
|
307
|
+
csvLines.push((0, csv_writer_1.buildCsvRow)([
|
|
308
|
+
userStat.date,
|
|
309
|
+
userStat.roleName,
|
|
310
|
+
userStat.userName,
|
|
311
|
+
workHour.issueType,
|
|
312
|
+
workHour.subject,
|
|
313
|
+
workHour.summary,
|
|
314
|
+
workHour.workHoursTypeName,
|
|
315
|
+
workHour.workHoursNum,
|
|
316
|
+
]));
|
|
320
317
|
});
|
|
321
318
|
});
|
|
322
319
|
const filename = `daily-${targetDate}.csv`;
|
|
@@ -332,31 +329,30 @@ function outputJson(data) {
|
|
|
332
329
|
* daily 命令入口
|
|
333
330
|
*/
|
|
334
331
|
async function dailyCommand(date, cliOptions = {}) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
else if (outputFormat === 'json') {
|
|
355
|
-
outputJson(allReports);
|
|
332
|
+
const spinner = (0, ora_1.default)('正在查询数据...').start();
|
|
333
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
334
|
+
const showReport = cliOptions.report ?? false;
|
|
335
|
+
const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
336
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
337
|
+
// 查询用户工时统计
|
|
338
|
+
const data = await queryDailyUserStats(businessService, projectId, roleIds, targetDate);
|
|
339
|
+
// 查询报告数据
|
|
340
|
+
let report = null;
|
|
341
|
+
if (showReport && outputFormat === 'console') {
|
|
342
|
+
report = await queryDailyReport(businessService, projectId, targetDate, data.list);
|
|
343
|
+
}
|
|
344
|
+
spinner.stop();
|
|
345
|
+
// 控制台输出
|
|
346
|
+
if (outputFormat === 'console') {
|
|
347
|
+
outputConsole(data);
|
|
348
|
+
if (report) {
|
|
349
|
+
consoleReport(report);
|
|
356
350
|
}
|
|
357
351
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
352
|
+
else if (outputFormat === 'csv') {
|
|
353
|
+
outputCsv(data.list, targetDate);
|
|
354
|
+
}
|
|
355
|
+
else if (outputFormat === 'json') {
|
|
356
|
+
outputJson(data.list);
|
|
361
357
|
}
|
|
362
358
|
}
|