@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.
@@ -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.issue_type === '缺陷' || workHour.issue_type === '3' || workHour.issue_type === 'Bug');
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 queryDailyReportData(businessService, projectId, roleId, targetDate) {
15
- const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
16
- if (roleMembers.length === 0) {
17
- return null;
18
- }
19
- const roleName = roleMembers[0].role_name;
20
- const roleUserIds = roleMembers.map((member) => member.user_id);
21
- 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汇总
22
71
  const activeIssueIds = new Set();
23
- dailyStats.userStats.forEach((userStat) => {
72
+ userStats.forEach((userStat) => {
24
73
  userStat.workHours.forEach((workHour) => {
25
- activeIssueIds.add(workHour.issue_id);
74
+ activeIssueIds.add(workHour.issueId);
26
75
  });
27
76
  });
28
- // Bug汇总
29
77
  const bugWorkHoursMap = new Map();
30
- dailyStats.userStats.forEach((userStat) => {
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.work_hours_num) || 0;
82
+ const hours = parseFloat(workHour.workHoursNum) || 0;
35
83
  if (bugWorkHoursMap.has(key)) {
36
84
  const existing = bugWorkHoursMap.get(key);
37
- existing.users.add(workHour.nick_name);
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([workHour.nick_name]),
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
- const iterationProgress = [];
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, roleUserIds);
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
- dailyStats.userStats.forEach((userStat) => {
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.issue_id)) {
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: workHour.nick_name,
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: dailyStats.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(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));
210
+ function outputConsole(data) {
211
+ const list = data.list;
212
+ (0, console_1.consoleTotal)(data);
195
213
  // 平铺所有用户的工时明细
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
- });
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(allReports, total) {
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
- 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,
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
- 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
- });
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
- 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) => {
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}.工时: ${total.totalHours}`);
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((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
- });
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
- try {
324
- const targetDate = date || new Date().toISOString().split('T')[0];
325
- const showReport = cliOptions.report ?? false;
326
- const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
327
- const businessService = new business_service_1.BusinessService(config);
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);
333
- }
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);
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
- catch (error) {
347
- logger_1.logger.error(`执行过程中发生错误:`, error);
348
- process.exit(1);
352
+ else if (outputFormat === 'csv') {
353
+ outputCsv(data.list, targetDate);
354
+ }
355
+ else if (outputFormat === 'json') {
356
+ outputJson(data.list);
349
357
  }
350
358
  }