@hecom/codearts 0.2.3 → 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.
@@ -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.issue_type === '缺陷' || workHour.issue_type === '3' || workHour.issue_type === 'Bug');
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 queryDailyReportData(businessService, projectId, roleId, targetDate) {
19
- const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
20
- if (roleMembers.length === 0) {
21
- return null;
22
- }
23
- const roleName = roleMembers[0].role_name;
24
- const roleUserIds = roleMembers.map((member) => member.user_id);
25
- const dailyStats = await businessService.getDailyWorkHourStats(projectId, roleUserIds, targetDate);
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
- dailyStats.userStats.forEach((userStat) => {
72
+ userStats.forEach((userStat) => {
28
73
  userStat.workHours.forEach((workHour) => {
29
- activeIssueIds.add(workHour.issue_id);
74
+ activeIssueIds.add(workHour.issueId);
30
75
  });
31
76
  });
32
- // Bug汇总
33
77
  const bugWorkHoursMap = new Map();
34
- dailyStats.userStats.forEach((userStat) => {
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.work_hours_num) || 0;
82
+ const hours = parseFloat(workHour.workHoursNum) || 0;
39
83
  if (bugWorkHoursMap.has(key)) {
40
84
  const existing = bugWorkHoursMap.get(key);
41
- existing.users.add(workHour.nick_name);
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([workHour.nick_name]),
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
- const iterationProgress = [];
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, roleUserIds);
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
- dailyStats.userStats.forEach((userStat) => {
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.issue_id)) {
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: workHour.nick_name,
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: dailyStats.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(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));
210
+ function outputConsole(data) {
211
+ const list = data.list;
212
+ (0, console_1.consoleTotal)(data);
206
213
  // 平铺所有用户的工时明细
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
- });
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(allReports, total) {
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
- 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
- });
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
- 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
- }
276
- });
277
- }
278
- else {
279
- allIterations.set(iteration.iterationName, {
280
- iterationName: iteration.iterationName,
281
- completionRate: iteration.completionRate,
282
- activeIssues: [...iteration.activeIssues],
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
- 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) => {
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}.工时: ${total.totalHours}`);
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((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
- });
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
- try {
336
- const targetDate = date || new Date().toISOString().split('T')[0];
337
- const showReport = cliOptions.report ?? false;
338
- const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
339
- const businessService = new business_service_1.BusinessService(config);
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);
345
- }
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);
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
- catch (error) {
359
- logger_1.logger.error(`执行过程中发生错误:`, error);
360
- process.exit(1);
352
+ else if (outputFormat === 'csv') {
353
+ outputCsv(data.list, targetDate);
354
+ }
355
+ else if (outputFormat === 'json') {
356
+ outputJson(data.list);
361
357
  }
362
358
  }