@hecom/codearts 0.2.0 → 0.2.2

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