@hecom/codearts 0.2.1 → 0.2.3

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.
@@ -1,20 +1,24 @@
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
- exports.generateDailyReport = generateDailyReport;
4
6
  exports.dailyCommand = dailyCommand;
7
+ const picocolors_1 = __importDefault(require("picocolors"));
5
8
  const business_service_1 = require("../services/business.service");
6
9
  const config_loader_1 = require("../utils/config-loader");
10
+ const csv_writer_1 = require("../utils/csv-writer");
11
+ const logger_1 = require("../utils/logger");
7
12
  function isBug(workHour) {
8
13
  return (workHour.issue_type === '缺陷' || workHour.issue_type === '3' || workHour.issue_type === 'Bug');
9
14
  }
10
15
  /**
11
- * 生成单个角色的日报
16
+ * 查询单个角色的日报数据
12
17
  */
13
- async function generateDailyReport(businessService, projectId, roleId, targetDate) {
18
+ async function queryDailyReportData(businessService, projectId, roleId, targetDate) {
14
19
  const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
15
20
  if (roleMembers.length === 0) {
16
- console.log(`角色ID ${roleId} 未找到用户,跳过工时查询`);
17
- return;
21
+ return null;
18
22
  }
19
23
  const roleName = roleMembers[0].role_name;
20
24
  const roleUserIds = roleMembers.map((member) => member.user_id);
@@ -25,6 +29,7 @@ async function generateDailyReport(businessService, projectId, roleId, targetDat
25
29
  activeIssueIds.add(workHour.issue_id);
26
30
  });
27
31
  });
32
+ // Bug汇总
28
33
  const bugWorkHoursMap = new Map();
29
34
  dailyStats.userStats.forEach((userStat) => {
30
35
  userStat.workHours.forEach((workHour) => {
@@ -46,95 +51,86 @@ async function generateDailyReport(businessService, projectId, roleId, targetDat
46
51
  }
47
52
  });
48
53
  });
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}`);
54
+ const bugSummary = {
55
+ count: bugWorkHoursMap.size,
56
+ items: Array.from(bugWorkHoursMap.values()).map((bug) => ({
57
+ title: bug.title,
58
+ users: Array.from(bug.users).join('、'),
59
+ hours: bug.totalHours,
60
+ })),
61
+ };
62
+ // 迭代进度
63
+ const activeIterations = await businessService.getActiveIterationsOnDate(projectId, targetDate);
64
+ const iterationProgress = [];
65
+ let activeIssues = [];
66
+ if (activeIterations.length > 0) {
67
+ const activeIterationIds = activeIterations.map((iteration) => iteration.id);
68
+ const issues = await businessService.getWorkloadByIterationsAndUsers(projectId, activeIterationIds, roleUserIds);
69
+ activeIssues = issues.filter((issue) => activeIssueIds.has(issue.id));
70
+ if (activeIssues.length > 0) {
71
+ const activeIssuesByIteration = new Map();
72
+ activeIssues.forEach((issue) => {
73
+ const iterationId = issue.iteration.id;
74
+ if (!activeIssuesByIteration.has(iterationId)) {
75
+ activeIssuesByIteration.set(iterationId, []);
76
+ }
77
+ activeIssuesByIteration.get(iterationId).push(issue);
78
+ });
79
+ const issuesByIteration = new Map();
80
+ issues.forEach((issue) => {
81
+ const iterationId = issue.iteration.id;
82
+ if (!issuesByIteration.has(iterationId)) {
83
+ issuesByIteration.set(iterationId, []);
84
+ }
85
+ issuesByIteration.get(iterationId).push(issue);
86
+ });
87
+ activeIterations.forEach((iteration) => {
88
+ const iterationActiveIssues = activeIssuesByIteration.get(iteration.id) || [];
89
+ if (iterationActiveIssues.length === 0) {
90
+ return;
91
+ }
92
+ const iterationIssues = issuesByIteration.get(iteration.id) || [];
93
+ let completionRate = 0;
94
+ if (iterationIssues.length > 0) {
95
+ const iterationStats = businessService.calculateWorkProgress(iterationIssues);
96
+ completionRate = iterationStats.overallCompletionRate;
97
+ }
98
+ const activeIssuesData = iterationActiveIssues.map((issue) => {
99
+ const doneRate = issue.expected_work_hours
100
+ ? issue.actual_work_hours / issue.expected_work_hours
101
+ : 0;
102
+ const displayDoneRate = issue.done_ratio
103
+ ? issue.done_ratio
104
+ : Math.round(Math.min(doneRate * 100, 100));
105
+ return {
106
+ name: issue.name,
107
+ doneRatio: displayDoneRate,
108
+ assignedUser: issue.assigned_user.nick_name,
109
+ };
110
+ });
111
+ iterationProgress.push({
112
+ iterationName: iteration.name,
113
+ completionRate,
114
+ activeIssues: activeIssuesData,
115
+ });
77
116
  });
78
117
  }
79
- index++;
80
- }
81
- const activeIterations = await businessService.getActiveIterationsOnDate(projectId, targetDate);
82
- if (activeIterations.length === 0) {
83
- console.log('没有找到正在进行中的迭代');
84
- return;
85
118
  }
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++;
119
+ // 其他工作
120
+ const displayedIssueIds = new Set();
121
+ iterationProgress.forEach((iteration) => {
122
+ iteration.activeIssues.forEach((issue) => {
123
+ const foundIssue = activeIssues?.find((i) => i.name === issue.name);
124
+ if (foundIssue) {
125
+ displayedIssueIds.add(foundIssue.id);
116
126
  }
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
  });
127
- }
128
- const displayedIssueIds = new Set();
129
- activeIssues.forEach((issue) => {
130
- displayedIssueIds.add(issue.id);
131
128
  });
132
129
  const subjectMap = {
133
130
  '【移动端】会议、调研、环境处理等零散工作': '',
134
131
  '【移动端】【鸿蒙】调研、问题修复、打包等零散工作': '【鸿蒙】',
135
- // 可在此添加更多映射
136
132
  };
137
- const otherWorkHours = [];
133
+ const otherWork = [];
138
134
  dailyStats.userStats.forEach((userStat) => {
139
135
  userStat.workHours.forEach((workHour) => {
140
136
  if (workHour.summary &&
@@ -142,23 +138,195 @@ async function generateDailyReport(businessService, projectId, roleId, targetDat
142
138
  !isBug(workHour) &&
143
139
  !displayedIssueIds.has(workHour.issue_id)) {
144
140
  const subject = subjectMap[workHour.subject] ?? workHour.subject;
145
- otherWorkHours.push({
141
+ otherWork.push({
146
142
  subject,
147
143
  summary: workHour.summary,
148
- nick_name: workHour.nick_name,
144
+ nickName: workHour.nick_name,
145
+ });
146
+ }
147
+ });
148
+ });
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
+ return {
162
+ date: targetDate,
163
+ roleName,
164
+ roleId,
165
+ userStats,
166
+ bugSummary,
167
+ iterationProgress,
168
+ otherWork,
169
+ totalHours: dailyStats.totalHours,
170
+ };
171
+ }
172
+ function issueTypeColor(type) {
173
+ const issueTypeColorMap = {
174
+ 任务: picocolors_1.default.bgCyan,
175
+ 缺陷: picocolors_1.default.bgRed,
176
+ };
177
+ return issueTypeColorMap[type] ? issueTypeColorMap[type](type) : picocolors_1.default.bgGreen(type);
178
+ }
179
+ /**
180
+ * 控制台输出日报(多角色统一汇总)
181
+ */
182
+ function outputConsole(allReports, showReport = false) {
183
+ if (allReports.length === 0) {
184
+ return;
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));
206
+ // 平铺所有用户的工时明细
207
+ allReports.forEach((report) => {
208
+ report.userStats.forEach((userStat) => {
209
+ logger_1.logger.info();
210
+ logger_1.logger.info(picocolors_1.default.red(`${userStat.userName} ${userStat.totalHours}小时`));
211
+ userStat.workHours
212
+ .sort((a, b) => a.issueType.localeCompare(b.issueType))
213
+ .forEach((workHour) => {
214
+ const summaryPart = workHour.summary && workHour.summary.trim() !== ''
215
+ ? picocolors_1.default.cyan(` ${workHour.summary}`)
216
+ : '';
217
+ const workHoursTypePart = workHour.workHoursTypeName
218
+ ? ` (${workHour.workHoursTypeName})`
219
+ : '';
220
+ logger_1.logger.info(` ${issueTypeColor(workHour.issueType)} ${workHour.subject}${summaryPart} ${workHoursTypePart} ${workHour.workHoursNum}小时`);
221
+ });
222
+ });
223
+ });
224
+ if (!showReport) {
225
+ return;
226
+ }
227
+ // 总结报告(合并所有角色的数据)
228
+ consoleReport(allReports, total);
229
+ }
230
+ function consoleReport(allReports, total) {
231
+ logger_1.logger.info('\n\n');
232
+ logger_1.logger.info(`总结报告:`);
233
+ logger_1.logger.info('='.repeat(80));
234
+ let index = 1;
235
+ // 合并所有 Bug
236
+ const allBugs = new Map();
237
+ allReports.forEach((report) => {
238
+ report.bugSummary.items.forEach((bug) => {
239
+ if (allBugs.has(bug.title)) {
240
+ const existing = allBugs.get(bug.title);
241
+ bug.users.split('、').forEach((user) => existing.users.add(user));
242
+ existing.hours += bug.hours;
243
+ }
244
+ else {
245
+ allBugs.set(bug.title, {
246
+ title: bug.title,
247
+ users: new Set(bug.users.split('、')),
248
+ hours: bug.hours,
249
+ });
250
+ }
251
+ });
252
+ });
253
+ if (allBugs.size > 0) {
254
+ logger_1.logger.info(`\n${index}.Bug跟进: ${allBugs.size}项`);
255
+ if (allBugs.size < 6) {
256
+ Array.from(allBugs.values()).forEach((bug) => {
257
+ logger_1.logger.info(` - ${bug.title} ${Array.from(bug.users).join('、')}`);
258
+ });
259
+ }
260
+ index++;
261
+ }
262
+ // 合并所有迭代进度
263
+ const allIterations = new Map();
264
+ allReports.forEach((report) => {
265
+ report.iterationProgress.forEach((iteration) => {
266
+ if (allIterations.has(iteration.iterationName)) {
267
+ const existing = allIterations.get(iteration.iterationName);
268
+ // 使用最高的完成率
269
+ existing.completionRate = Math.max(existing.completionRate, iteration.completionRate);
270
+ // 合并活跃问题(去重)
271
+ iteration.activeIssues.forEach((issue) => {
272
+ const exists = existing.activeIssues.some((i) => i.name === issue.name);
273
+ if (!exists) {
274
+ existing.activeIssues.push(issue);
275
+ }
149
276
  });
150
277
  }
278
+ else {
279
+ allIterations.set(iteration.iterationName, {
280
+ iterationName: iteration.iterationName,
281
+ completionRate: iteration.completionRate,
282
+ activeIssues: [...iteration.activeIssues],
283
+ });
284
+ }
285
+ });
286
+ });
287
+ Array.from(allIterations.values()).forEach((iteration) => {
288
+ logger_1.logger.info(`${index}.${iteration.iterationName} ${iteration.completionRate}%`);
289
+ index++;
290
+ iteration.activeIssues.forEach((issue) => {
291
+ logger_1.logger.info(` - ${issue.name} ${issue.doneRatio}% ${issue.assignedUser}`);
151
292
  });
152
293
  });
153
- if (otherWorkHours.length > 0) {
154
- console.log(`${index}.其他: ${otherWorkHours.length}项`);
155
- otherWorkHours.forEach((work) => {
294
+ // 合并所有其他工作
295
+ const allOtherWork = [];
296
+ allReports.forEach((report) => {
297
+ allOtherWork.push(...report.otherWork);
298
+ });
299
+ if (allOtherWork.length > 0) {
300
+ logger_1.logger.info(`${index}.其他: ${allOtherWork.length}项`);
301
+ allOtherWork.forEach((work) => {
156
302
  const subjectPart = work.subject ? `${work.subject} ` : '';
157
- console.log(` - ${subjectPart}${work.summary} ${work.nick_name}`);
303
+ logger_1.logger.info(` - ${subjectPart}${work.summary} ${work.nickName}`);
158
304
  });
159
305
  index++;
160
306
  }
161
- console.log(`${index}.工时: ${dailyStats.totalHours}`);
307
+ logger_1.logger.info(`${index}.工时: ${total.totalHours}`);
308
+ }
309
+ /**
310
+ * CSV 文件输出
311
+ */
312
+ function outputCsv(list, targetDate) {
313
+ const csvLines = [];
314
+ csvLines.push('日期,角色,用户,工作项,摘要,类型,工时类型,工时');
315
+ list.forEach((data) => {
316
+ data.userStats.forEach((userStat) => {
317
+ userStat.workHours.forEach((workHour) => {
318
+ csvLines.push(`${data.date ?? ''},${data.roleName ?? ''},${userStat.userName ?? ''},"${(0, csv_writer_1.escapeCsv)(workHour.subject)}","${(0, csv_writer_1.escapeCsv)(workHour.summary)}",${workHour.issueType ?? ''},${workHour.workHoursTypeName ?? ''},${workHour.workHoursNum ?? ''}`);
319
+ });
320
+ });
321
+ });
322
+ const filename = `daily-${targetDate}.csv`;
323
+ (0, csv_writer_1.writeCsvFile)(filename, csvLines, logger_1.logger);
324
+ }
325
+ /**
326
+ * JSON 输出(直接打印到控制台,供编程调用)
327
+ */
328
+ function outputJson(data) {
329
+ logger_1.logger.json(data);
162
330
  }
163
331
  /**
164
332
  * daily 命令入口
@@ -166,19 +334,29 @@ async function generateDailyReport(businessService, projectId, roleId, targetDat
166
334
  async function dailyCommand(date, cliOptions = {}) {
167
335
  try {
168
336
  const targetDate = date || new Date().toISOString().split('T')[0];
169
- console.log(`开始统计 ${targetDate} 的日报...`);
170
- const { projectId, roleIds, config } = (0, config_loader_1.loadConfig)(cliOptions);
337
+ const showReport = cliOptions.report ?? false;
338
+ const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
171
339
  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');
340
+ const allReports = [];
341
+ for (const roleId of roleIds) {
342
+ const reportData = await queryDailyReportData(businessService, projectId, roleId, targetDate);
343
+ if (reportData) {
344
+ allReports.push(reportData);
176
345
  }
177
- await generateDailyReport(businessService, projectId, roleId, targetDate);
346
+ }
347
+ // 控制台输出
348
+ if (outputFormat === 'console') {
349
+ outputConsole(allReports, showReport);
350
+ }
351
+ else if (outputFormat === 'csv') {
352
+ outputCsv(allReports, targetDate);
353
+ }
354
+ else if (outputFormat === 'json') {
355
+ outputJson(allReports);
178
356
  }
179
357
  }
180
358
  catch (error) {
181
- console.error('执行过程中发生错误:', error);
359
+ logger_1.logger.error(`执行过程中发生错误:`, error);
182
360
  process.exit(1);
183
361
  }
184
362
  }