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