commit b12a84e3889b02a9b5952d88e4fcc613cebbdc55 Author: Administrator <907333918@qq.com> Date: Sat Apr 11 11:51:54 2026 +0800 first commit diff --git a/.kiro/specs/employee-performance-system/design.md b/.kiro/specs/employee-performance-system/design.md new file mode 100644 index 0000000..5b02ab5 --- /dev/null +++ b/.kiro/specs/employee-performance-system/design.md @@ -0,0 +1,729 @@ +# 设计文档 - 员工月度绩效考核系统 + +## 概述 + +员工月度绩效考核系统是一个基于 Web 的全栈应用,采用前后端分离架构。系统集成 FastGPT AI 评分能力,实现员工绩效填报、AI 自动评分、管理层审核、数据统计分析的完整闭环。 + +### 技术栈选择 + +**后端:** +- Node.js + TypeScript +- Express.js 框架 +- MySQL 数据库 +- JWT 身份认证 +- Axios(FastGPT API 调用) + +**前端:** +- React + TypeScript +- Ant Design UI 组件库 +- React Router(路由管理) +- Axios(HTTP 请求) +- ECharts(数据可视化) + +### 核心设计原则 + +1. **角色权限分离**: 严格按照员工、管理层、总经理三种角色进行权限控制 +2. **数据安全**: 敏感数据加密存储,操作日志完整记录 +3. **流程自动化**: 绩效提交自动触发 AI 评分,审核完成自动归档 +4. **可扩展性**: 模块化设计,便于后续功能扩展 + +## 架构设计 + +### 系统架构图 + +```mermaid +graph TB + subgraph "前端层" + A[员工端页面] + B[管理层端页面] + C[总经理端页面] + end + + subgraph "API 网关层" + D[Express API Server] + E[JWT 认证中间件] + F[权限验证中间件] + end + + subgraph "业务逻辑层" + G[用户服务] + H[绩效服务] + I[AI 评分服务] + J[统计分析服务] + end + + subgraph "数据访问层" + K[用户 DAO] + L[绩效 DAO] + M[AI 结果 DAO] + end + + subgraph "外部服务" + N[FastGPT API] + end + + subgraph "数据存储层" + O[(MySQL 数据库)] + end + + A --> D + B --> D + C --> D + D --> E + E --> F + F --> G + F --> H + F --> J + H --> I + I --> N + G --> K + H --> L + I --> M + K --> O + L --> O + M --> O +``` + +### 数据流设计 + +**绩效提交流程:** +``` +员工填报 → 前端验证 → API 提交 → 保存数据库 → 触发 AI 评分 → 调用 FastGPT → 解析结果 → 存储 AI 结果 → 返回成功 +``` + +**绩效审核流程:** +``` +管理层查看 → 加载绩效+AI结果 → 调整评分 → 填写意见 → 提交审核 → 计算等级奖惩 → 归档数据 → 通知员工 +``` + +## 组件与接口 + +### 后端核心模块 + +#### 1. 用户认证模块 (AuthService) + +**职责**: 处理用户登录、令牌生成与验证 + +**接口:** +```typescript +interface AuthService { + // 用户登录 + login(username: string, password: string, role: string): Promise; + + // 验证令牌 + verifyToken(token: string): Promise; + + // 刷新令牌 + refreshToken(token: string): Promise; +} + +interface LoginResult { + token: string; + userInfo: UserInfo; +} + +interface UserInfo { + userId: number; + name: string; + role: 'employee' | 'manager' | 'generalManager'; + department: string; + position: string; +} +``` + +#### 2. 绩效管理模块 (PerformanceService) + +**职责**: 处理绩效填报、查询、审核等核心业务逻辑 + +**接口:** +```typescript +interface PerformanceService { + // 提交绩效(暂存或提交) + submitPerformance(data: PerformanceSubmitDTO): Promise; + + // 查询员工个人绩效 + getEmployeePerformance(userId: number, month?: string, page?: PageInfo): Promise; + + // 查询管理层下属绩效 + getManagerSubordinates(managerId: number, filters: PerformanceFilter, page: PageInfo): Promise; + + // 审核绩效 + reviewPerformance(perfId: number, reviewData: ReviewDTO): Promise; + + // 驳回绩效 + rejectPerformance(perfId: number, reason: string): Promise; + + // 申请修改绩效 + requestModification(perfId: number, reason: string): Promise; + + // 批准修改申请 + approveModification(perfId: number): Promise; +} + +interface PerformanceSubmitDTO { + userId: number; + month: string; + status: 'draft' | 'submit'; + selfScore: number; + attendance: AttendanceData; + workSummary: string; + performanceItems: PerformanceItemDTO[]; +} + +interface PerformanceItemDTO { + itemName: string; + weight: number; + userContent: string; + selfScore: number; + evidence?: string; +} + +interface AttendanceData { + leave: number; + late: number; + absent: number; + lackCard: number; + remark?: string; +} + +interface ReviewDTO { + perfId: number; + managerScore: number; + reviewOpinion: string; + itemScores: ItemScoreDTO[]; +} + +interface ItemScoreDTO { + itemName: string; + managerScore: number; + scoreExplanation: string; +} +``` + +#### 3. AI 评分模块 (AIEvaluationService) + +**职责**: 调用 FastGPT API 进行自动评分和反馈生成 + +**接口:** +```typescript +interface AIEvaluationService { + // 执行 AI 评分 + evaluatePerformance(perfId: number): Promise; + + // 构建 AI 请求 Prompt + buildPrompt(performance: PerformanceRecord): string; + + // 解析 AI 响应 + parseAIResponse(response: string): AIScoreData; + + // 重试机制 + retryEvaluation(perfId: number, maxRetries: number): Promise; +} + +interface AIResult { + aiId: number; + perfId: number; + aiScoreDetail: AIScoreItem[]; + aiTotalScore: number; + aiProblems: string[]; + aiSuggestions: string[]; + createTime: Date; +} + +interface AIScoreItem { + itemName: string; + weight: number; + aiScore: number; + scoreExplanation: string; +} +``` + +#### 4. 统计分析模块 (StatisticsService) + +**职责**: 提供多维度数据统计和报表生成 + +**接口:** +```typescript +interface StatisticsService { + // 获取团队统计 + getTeamStatistics(managerId: number, month: string): Promise; + + // 获取全公司统计 + getCompanyStatistics(month: string): Promise; + + // 多维度统计 + getMultiDimensionStats(filters: StatFilter): Promise; + + // 导出 Excel + exportToExcel(filters: ExportFilter): Promise; +} + +interface TeamStats { + averageScore: number; + excellentCount: number; + qualifiedCount: number; + needMotivationCount: number; + totalCount: number; +} + +interface CompanyStats { + departmentStats: DepartmentStat[]; + positionStats: PositionStat[]; + levelDistribution: LevelDistribution; +} +``` + +### 前端核心组件 + +#### 1. 员工端组件 + +```typescript +// 绩效填报组件 +interface PerformanceFormComponent { + props: { + month: string; + userInfo: UserInfo; + }; + state: { + formData: PerformanceFormData; + isDraft: boolean; + }; + methods: { + handleItemChange(index: number, field: string, value: any): void; + handleSave(): Promise; + handleSubmit(): Promise; + uploadEvidence(file: File): Promise; + }; +} + +// 个人绩效查看组件 +interface PerformanceHistoryComponent { + props: { + userId: number; + }; + state: { + performanceList: PerformanceRecord[]; + selectedMonth: string; + pagination: PaginationState; + }; + methods: { + loadPerformanceList(): Promise; + viewDetail(perfId: number): void; + filterByMonth(month: string): void; + }; +} +``` + +#### 2. 管理层端组件 + +```typescript +// 下属绩效列表组件 +interface SubordinateListComponent { + props: { + managerId: number; + }; + state: { + subordinateList: PerformanceRecord[]; + filters: PerformanceFilter; + pagination: PaginationState; + }; + methods: { + loadSubordinateList(): Promise; + applyFilters(filters: PerformanceFilter): void; + viewDetail(perfId: number): void; + }; +} + +// 绩效审核组件 +interface PerformanceReviewComponent { + props: { + perfId: number; + }; + state: { + performanceData: PerformanceRecord; + aiResult: AIResult; + reviewForm: ReviewFormData; + }; + methods: { + loadPerformanceDetail(): Promise; + adjustScore(itemName: string, score: number): void; + submitReview(): Promise; + rejectPerformance(reason: string): Promise; + }; +} +``` + +#### 3. 总经理端组件 + +```typescript +// 全局统计组件 +interface CompanyStatisticsComponent { + props: { + month: string; + }; + state: { + companyStats: CompanyStats; + chartData: ChartData; + selectedDimension: string; + }; + methods: { + loadStatistics(): Promise; + switchDimension(dimension: string): void; + exportData(): Promise; + }; +} +``` + +## 数据模型 + +### 数据库表设计 + +#### 1. 用户表 (user) + +```sql +CREATE TABLE user ( + user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', + username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名(工号)', + password VARCHAR(255) NOT NULL COMMENT '密码(加密存储)', + name VARCHAR(50) NOT NULL COMMENT '姓名', + role ENUM('employee', 'manager', 'generalManager') NOT NULL COMMENT '角色', + department VARCHAR(50) NOT NULL COMMENT '部门', + position VARCHAR(50) NOT NULL COMMENT '岗位', + manager_id INT COMMENT '直属管理层ID', + status ENUM('active', 'inactive') DEFAULT 'active' COMMENT '状态', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_role (role), + INDEX idx_manager (manager_id), + FOREIGN KEY (manager_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +``` + +#### 2. 绩效主表 (performance_month) + +```sql +CREATE TABLE performance_month ( + perf_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '绩效记录ID', + user_id INT NOT NULL COMMENT '员工ID', + month VARCHAR(7) NOT NULL COMMENT '考核月份(YYYY-MM)', + status ENUM('draft', 'submitted', 'under_review', 'completed', 'rejected') NOT NULL COMMENT '状态', + self_score DECIMAL(5,2) COMMENT '员工自评总分', + ai_score DECIMAL(5,2) COMMENT 'AI评分总分', + manager_score DECIMAL(5,2) COMMENT '管理层审核总分', + total_score DECIMAL(5,2) COMMENT '最终总分', + level ENUM('excellent', 'qualified', 'need_motivation', 'unqualified') COMMENT '绩效等级', + reward_punish VARCHAR(255) COMMENT '奖惩说明', + work_summary TEXT COMMENT '工作汇总', + submit_time TIMESTAMP COMMENT '提交时间', + review_time TIMESTAMP COMMENT '审核时间', + review_opinion TEXT COMMENT '审核意见', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_user_month (user_id, month), + INDEX idx_status (status), + INDEX idx_month (month), + FOREIGN KEY (user_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='绩效主表'; +``` + +#### 3. 绩效项明细表 (perf_item) + +```sql +CREATE TABLE perf_item ( + item_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '绩效项ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + item_name VARCHAR(100) NOT NULL COMMENT '考核项名称', + item_category ENUM('business', 'comprehensive') NOT NULL COMMENT '考核项类别', + weight INT NOT NULL COMMENT '权重(分数)', + user_content TEXT COMMENT '员工填写内容', + self_score DECIMAL(5,2) COMMENT '员工自评分', + ai_score DECIMAL(5,2) COMMENT 'AI评分', + ai_explanation TEXT COMMENT 'AI评分说明', + manager_score DECIMAL(5,2) COMMENT '管理层评分', + manager_explanation TEXT COMMENT '管理层评分说明', + evidence_url VARCHAR(500) COMMENT '佐证材料URL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='绩效项明细表'; +``` + +#### 4. 考勤表 (attendance) + +```sql +CREATE TABLE attendance ( + attendance_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + leave_days INT DEFAULT 0 COMMENT '事假天数', + late_times INT DEFAULT 0 COMMENT '迟到次数', + absent_days INT DEFAULT 0 COMMENT '旷工天数', + lack_card_times INT DEFAULT 0 COMMENT '缺卡次数', + attendance_score DECIMAL(5,2) COMMENT '考勤得分', + remark TEXT COMMENT '备注', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤表'; +``` + +#### 5. AI 结果表 (ai_result) + +```sql +CREATE TABLE ai_result ( + ai_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'AI结果ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + ai_score_json TEXT NOT NULL COMMENT 'AI评分详情(JSON格式)', + ai_total_score DECIMAL(5,2) NOT NULL COMMENT 'AI总分', + problems TEXT COMMENT 'AI总结的问题(JSON数组)', + suggestions TEXT COMMENT 'AI改进建议(JSON数组)', + api_response TEXT COMMENT 'FastGPT原始响应', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间', + UNIQUE KEY uk_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI结果表'; +``` + +#### 6. 操作日志表 (operation_log) + +```sql +CREATE TABLE operation_log ( + log_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID', + user_id INT NOT NULL COMMENT '操作人ID', + operation_type VARCHAR(50) NOT NULL COMMENT '操作类型', + target_type VARCHAR(50) COMMENT '目标类型', + target_id INT COMMENT '目标ID', + operation_detail TEXT COMMENT '操作详情', + ip_address VARCHAR(50) COMMENT 'IP地址', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_user (user_id), + INDEX idx_created (created_at), + FOREIGN KEY (user_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'; +``` + +### 数据关系图 + +```mermaid +erDiagram + USER ||--o{ PERFORMANCE_MONTH : "has" + USER ||--o{ USER : "manages" + PERFORMANCE_MONTH ||--|{ PERF_ITEM : "contains" + PERFORMANCE_MONTH ||--|| ATTENDANCE : "has" + PERFORMANCE_MONTH ||--|| AI_RESULT : "has" + USER ||--o{ OPERATION_LOG : "performs" + + USER { + int user_id PK + string username + string password + string name + enum role + string department + string position + int manager_id FK + } + + PERFORMANCE_MONTH { + int perf_id PK + int user_id FK + string month + enum status + decimal self_score + decimal ai_score + decimal manager_score + decimal total_score + enum level + } + + PERF_ITEM { + int item_id PK + int perf_id FK + string item_name + enum item_category + int weight + text user_content + decimal self_score + decimal ai_score + decimal manager_score + } + + ATTENDANCE { + int attendance_id PK + int perf_id FK + int leave_days + int late_times + int absent_days + int lack_card_times + decimal attendance_score + } + + AI_RESULT { + int ai_id PK + int perf_id FK + text ai_score_json + decimal ai_total_score + text problems + text suggestions + } +``` + +## 正确性属性 + +*正确性属性是系统应该在所有有效执行中保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。* + +### 属性 1: 认证正确性 + +*对于任意* 用户凭据(用户名、密码、角色),认证结果应与凭据有效性严格一致——有效凭据必须返回 token,无效凭据必须被拒绝,不存在中间状态。 + +**Validates: Requirements 1.1, 1.2** + +--- + +### 属性 2: 权限隔离不变量 + +*对于任意* 员工用户,使用其 token 查询其他员工的绩效数据时,系统应始终返回 403 权限不足错误,不泄露任何数据。 + +**Validates: Requirements 1.5** + +--- + +### 属性 3: 草稿暂存往返一致性 + +*对于任意* 绩效填报数据,暂存后再读取,返回的数据应与暂存时提交的数据完全一致(往返属性)。 + +**Validates: Requirements 2.5** + +--- + +### 属性 4: 提交幂等性 + +*对于任意* 已提交状态的绩效记录,再次尝试提交同一用户同一月份的绩效时,系统应拒绝并返回错误,绩效记录状态保持不变。 + +**Validates: Requirements 2.6** + +--- + +### 属性 5: 绩效等级与奖惩计算正确性 + +*对于任意* 最终总分(0-100),系统计算出的绩效等级和奖惩金额应严格符合以下规则: +- 分数 >= 90 → 优秀,奖励 +- 80 <= 分数 <= 89 → 合格,扣 100 元 +- 70 <= 分数 <= 79 → 合格,扣 200 元 +- 60 <= 分数 <= 69 → 需激励,扣 300 元 +- 分数 < 60 → 不合格,扣 600 元 + +**Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.5** + +--- + +### 属性 6: 连续低分预警正确性 + +*对于任意* 员工,若其连续 N 个月(N >= 2)的最终总分均低于 60 分,系统应正确标记对应的预警状态(N=2 书面警告,N>=3 劝退处理)。 + +**Validates: Requirements 5.6, 5.7** + +--- + +### 属性 7: 绩效记录查询完整性 + +*对于任意* 已提交或已完成的绩效记录,员工查询个人历史绩效时,该记录必须出现在结果列表中,且内容与提交时一致(插入/查询往返属性)。 + +**Validates: Requirements 6.1, 6.2** + +--- + +### 属性 8: 考勤分数计算正确性 + +*对于任意* 考勤数据(事假天数、迟到次数、缺卡次数),系统计算的考勤分数应满足: +- 基础分 10 分 +- 每天事假扣 5 分 +- 每次迟到/缺卡扣 2 分 +- 最终分数不低于 0 分(下限保护) + +**Validates: Requirements 11.1, 11.2, 11.3, 11.5** + +--- + +### 属性 9: AI 响应 JSON 解析往返一致性 + +*对于任意* 符合规范的 AI 评分 JSON 字符串,系统解析后再序列化,应得到语义等价的对象(解析往返属性)。 + +**Validates: Requirements 3.6, 13.3** + +--- + +### 属性 10: AI 输出格式约束 + +*对于任意* AI 评分结果,`ai_problems` 和 `ai_suggestions` 数组的长度应在 3 到 5 之间(含边界值)。 + +**Validates: Requirements 3.3, 3.4** + +--- + +## 错误处理 + +### 错误码规范 + +| 状态码 | 含义 | 场景 | +|--------|------|------| +| 200 | 成功 | 正常响应 | +| 400 | 参数错误 | 请求参数缺失或格式错误 | +| 401 | 未登录/Token 失效 | Token 过期或未携带 | +| 403 | 权限不足 | 越权访问他人数据 | +| 500 | 服务器异常 | 内部错误 | + +### 关键错误场景处理 + +**AI 调用失败:** +- 超时(>10s):记录错误日志,绩效状态标记为 `ai_failed`,管理员可手动触发重试 +- 返回格式异常:记录原始响应,尝试降级解析,失败则通知管理员 + +**数据库操作失败:** +- 使用事务保证绩效提交的原子性(绩效主表 + 绩效项 + 考勤数据同时写入) +- 失败时回滚并返回 500 错误 + +**并发提交冲突:** +- 利用 `UNIQUE KEY uk_user_month (user_id, month)` 数据库约束防止重复提交 +- 捕获唯一键冲突异常,返回友好提示 + +## 测试策略 + +### 双重测试方法 + +系统采用单元测试和属性测试相结合的方式,两者互补: + +- **单元测试**: 验证具体示例、边界条件和错误场景 +- **属性测试**: 验证跨所有输入的通用属性 + +### 属性测试配置 + +- 使用 **fast-check**(TypeScript 属性测试库) +- 每个属性测试最少运行 **100 次迭代** +- 每个测试用注释标注对应的设计属性编号 +- 标注格式: `// Feature: employee-performance-system, Property N: <属性描述>` + +### 属性测试覆盖 + +| 属性编号 | 测试内容 | 测试类型 | +|----------|----------|----------| +| 属性 1 | 认证凭据有效性 | property | +| 属性 2 | 权限隔离 | property | +| 属性 3 | 草稿暂存往返 | property | +| 属性 4 | 提交幂等性 | property | +| 属性 5 | 等级奖惩计算 | property | +| 属性 6 | 连续低分预警 | property | +| 属性 7 | 绩效记录查询 | property | +| 属性 8 | 考勤分数计算 | property | +| 属性 9 | AI JSON 解析往返 | property | +| 属性 10 | AI 输出格式约束 | property | + +### 单元测试覆盖 + +- 用户登录接口(具体示例:正确凭据、错误密码、不存在用户) +- 绩效提交接口(具体示例:暂存、提交、重复提交) +- 审核流程(具体示例:通过、驳回、修改申请) +- Excel 导出格式验证 +- 分页查询边界条件(第一页、最后一页、空结果) + diff --git a/.kiro/specs/employee-performance-system/requirements.md b/.kiro/specs/employee-performance-system/requirements.md new file mode 100644 index 0000000..ec902d1 --- /dev/null +++ b/.kiro/specs/employee-performance-system/requirements.md @@ -0,0 +1,188 @@ +# 需求文档 - 员工月度绩效考核系统 + +## 简介 + +优一科技员工月度绩效考核系统是一个用于规范化、自动化管理员工月度绩效考核的系统。系统覆盖员工、管理层、总经理三类角色,以月度为周期进行绩效考核,集成 AI 自动评分功能,实现绩效填报、审核、统计分析的全流程管理。 + +## 术语表 + +- **System**: 员工月度绩效考核系统 +- **Employee**: 员工角色,负责填报个人绩效 +- **Manager**: 管理层角色,负责审核下属员工绩效 +- **General_Manager**: 总经理角色,负责全公司绩效查看和统计分析 +- **Performance_Record**: 绩效记录,包含员工某月的完整绩效数据 +- **AI_Evaluator**: AI 评分模块,基于 FastGPT 实现自动打分和建议生成 +- **Business_Quality**: 业务素质考评,占总分 70% +- **Comprehensive_Quality**: 综合素质考评,占总分 30% +- **Performance_Cycle**: 绩效考核周期,为自然月 +- **Submission_Period**: 提交期,每月 1-5 日 +- **Review_Period**: 审核期,每月 6-10 日 + +## 需求 + +### 需求 1: 用户认证与权限管理 + +**用户故事:** 作为系统用户,我希望能够安全登录系统并根据我的角色访问相应的功能,以便保护数据安全和隐私。 + +#### 验收标准 + +1. WHEN 用户提供有效的用户名、密码和角色信息 THEN THE System SHALL 验证凭据并返回有效的访问令牌 +2. WHEN 用户提供无效的登录凭据 THEN THE System SHALL 拒绝访问并返回明确的错误信息 +3. WHEN 用户访问需要权限的资源 THEN THE System SHALL 验证用户角色并仅允许授权访问 +4. WHEN 访问令牌过期(24小时后)THEN THE System SHALL 要求用户重新登录 +5. THE System SHALL 确保员工仅能访问个人数据,管理层仅能访问下属数据,总经理可访问全量数据 + +### 需求 2: 员工绩效填报 + +**用户故事:** 作为员工,我希望能够在每月 1-5 日填写上月绩效内容,以便完成月度绩效考核。 + +#### 验收标准 + +1. WHEN 员工在提交期(每月 1-5 日)访问填报页面 THEN THE System SHALL 显示当前考核月份和员工基础信息 +2. WHEN 员工填写绩效内容 THEN THE System SHALL 要求填写 17 项考核指标(业务素质 9 项 + 综合素质 8 项)的完成情况描述和自评分数 +3. WHEN 员工填写考勤数据 THEN THE System SHALL 记录事假、病假、迟到、旷工、缺卡的具体次数及说明 +4. WHEN 员工上传佐证材料 THEN THE System SHALL 支持上传文件(截图、文档、代码片段等)作为可选附件 +5. WHEN 员工选择暂存 THEN THE System SHALL 保存草稿并允许后续继续编辑 +6. WHEN 员工选择提交 THEN THE System SHALL 锁定表单并触发 AI 评分流程 +7. WHEN 员工提交后需要修改 THEN THE System SHALL 要求员工向管理层申请退回修改 + +### 需求 3: AI 自动评分与反馈 + +**用户故事:** 作为系统,我需要在员工提交绩效后自动调用 AI 进行评分和反馈生成,以便提供客观的初步评估。 + +#### 验收标准 + +1. WHEN 员工提交绩效记录 THEN THE System SHALL 自动调用 FastGPT API 进行 AI 评分 +2. WHEN AI 评分完成 THEN THE System SHALL 存储每个考核项的 AI 评分、评分说明、总分(业务素质 70% + 综合素质 30%) +3. WHEN AI 分析完成 THEN THE System SHALL 生成 3-5 条核心问题总结 +4. WHEN AI 分析完成 THEN THE System SHALL 生成 3-5 条具体可落地的改进建议 +5. WHEN AI 调用失败或超时(>10秒)THEN THE System SHALL 记录错误并通知管理员 +6. THE System SHALL 确保 AI 返回的数据格式为标准 JSON 并可被系统正确解析 + +### 需求 4: 管理层绩效审核 + +**用户故事:** 作为管理层,我希望能够在每月 6-10 日审核下属员工的绩效,以便完成最终评分和反馈。 + +#### 验收标准 + +1. WHEN 管理层在审核期(每月 6-10 日)访问审核页面 THEN THE System SHALL 显示所有下属员工的绩效提交列表 +2. WHEN 管理层查看员工绩效 THEN THE System SHALL 显示员工填报内容、自评分数、AI 评分及 AI 反馈 +3. WHEN 管理层调整评分 THEN THE System SHALL 允许修改每个考核项的分数并要求填写调整原因 +4. WHEN 管理层填写审核意见 THEN THE System SHALL 记录详细的审核意见说明 +5. WHEN 管理层审核通过 THEN THE System SHALL 计算最终总分、确定绩效等级和奖惩说明,并归档绩效记录 +6. WHEN 管理层驳回绩效 THEN THE System SHALL 要求填写驳回原因并通知员工修改 +7. WHEN 管理层审核完成 THEN THE System SHALL 锁定绩效记录不可随意修改 + +### 需求 5: 绩效等级与奖惩计算 + +**用户故事:** 作为系统,我需要根据最终总分自动计算绩效等级和奖惩金额,以便明确员工的考核结果。 + +#### 验收标准 + +1. WHEN 最终总分 >= 90 分 THEN THE System SHALL 标记为"优秀"等级并说明按公司规定给予奖励 +2. WHEN 最终总分在 80-89 分 THEN THE System SHALL 标记为"合格"等级并说明扣除当月绩效工资 100 元 +3. WHEN 最终总分在 70-79 分 THEN THE System SHALL 标记为"合格"等级并说明扣除当月绩效工资 200 元 +4. WHEN 最终总分在 60-69 分 THEN THE System SHALL 标记为"需激励"等级并说明扣除当月绩效工资 300 元 +5. WHEN 最终总分 < 60 分 THEN THE System SHALL 标记为"不合格"等级并说明扣除当月绩效工资 600 元 +6. WHEN 员工连续 2 个月考核分数低于 60 分 THEN THE System SHALL 标记需要书面警告 +7. WHEN 员工连续 3 个月考核分数低于 60 分 THEN THE System SHALL 标记需要劝退处理 + +### 需求 6: 个人绩效查看 + +**用户故事:** 作为员工,我希望能够查看我的历史绩效记录和 AI 反馈,以便了解自己的工作表现和改进方向。 + +#### 验收标准 + +1. WHEN 员工访问个人绩效页面 THEN THE System SHALL 显示所有历史月份的绩效记录列表 +2. WHEN 员工查看某月绩效详情 THEN THE System SHALL 显示填报内容、自评分数、AI 评分、管理层评分、最终总分和等级 +3. WHEN 员工查看 AI 反馈 THEN THE System SHALL 显示 AI 总结的问题和改进建议 +4. WHEN 员工查看奖惩说明 THEN THE System SHALL 显示当月绩效对应的奖惩情况 +5. THE System SHALL 支持按月份筛选和分页查看历史绩效 + +### 需求 7: 管理层数据查看与导出 + +**用户故事:** 作为管理层,我希望能够查看和导出下属员工的绩效数据,以便进行团队管理和统计分析。 + +#### 验收标准 + +1. WHEN 管理层访问团队绩效页面 THEN THE System SHALL 显示下属员工的绩效列表 +2. WHEN 管理层筛选数据 THEN THE System SHALL 支持按考核月份、部门、员工姓名、绩效状态进行筛选 +3. WHEN 管理层导出数据 THEN THE System SHALL 生成 Excel 格式的绩效表 +4. WHEN 管理层查看团队统计 THEN THE System SHALL 显示团队绩效平均分 +5. WHEN 管理层查看团队统计 THEN THE System SHALL 显示优秀、合格、需激励员工的人数及占比 +6. THE System SHALL 支持导出单个员工的历史绩效或所有下属的当月/指定时间段绩效 + +### 需求 8: 总经理全局管理 + +**用户故事:** 作为总经理,我希望能够查看全公司的绩效数据并进行多维度统计分析,以便掌握公司整体绩效水平。 + +#### 验收标准 + +1. WHEN 总经理访问全局绩效页面 THEN THE System SHALL 显示各部门、各岗位的月度绩效整体情况 +2. WHEN 总经理进行统计分析 THEN THE System SHALL 支持按考核月份、部门、岗位、绩效等级等维度进行统计 +3. WHEN 总经理查看统计报表 THEN THE System SHALL 生成可视化图表(柱状图、饼图等) +4. WHEN 总经理导出数据 THEN THE System SHALL 支持导出全公司所有员工的所有月份绩效数据 +5. WHEN 总经理配置考核规则 THEN THE System SHALL 允许修改考核指标权重、评分标准、奖惩金额 +6. WHEN 考核规则修改后 THEN THE System SHALL 自动应用于后续考核周期 + +### 需求 9: 数据持久化与历史追溯 + +**用户故事:** 作为系统管理员,我需要确保所有绩效数据被安全存储并可追溯,以便满足审计和合规要求。 + +#### 验收标准 + +1. WHEN 绩效数据生成或修改 THEN THE System SHALL 将数据持久化存储到 MySQL 数据库 +2. WHEN 用户查询历史数据 THEN THE System SHALL 能够检索任意历史月份的绩效记录 +3. WHEN 绩效数据被修改 THEN THE System SHALL 记录修改人、修改时间、修改内容 +4. WHEN 管理层审核完成 THEN THE System SHALL 将绩效数据归档并标记为不可随意修改 +5. THE System SHALL 对员工个人信息、绩效奖惩等敏感数据进行加密存储 +6. THE System SHALL 支持数据备份与恢复功能 + +### 需求 10: 系统性能与稳定性 + +**用户故事:** 作为系统用户,我希望系统运行稳定、响应迅速,以便高效完成绩效考核工作。 + +#### 验收标准 + +1. WHEN 用户访问页面 THEN THE System SHALL 在 2 秒内完成页面加载 +2. WHEN 员工提交绩效 THEN THE System SHALL 确保提交成功率 >= 99% +3. WHEN AI 评分被触发 THEN THE System SHALL 在 10 秒内返回评分结果 +4. WHEN 管理层审核绩效 THEN THE System SHALL 确保审核操作成功率 >= 99% +5. THE System SHALL 支持主流 PC 浏览器(Chrome、Edge、Firefox) +6. WHEN 系统出现异常 THEN THE System SHALL 记录错误日志并提供友好的错误提示 + +### 需求 11: 考勤数据处理 + +**用户故事:** 作为系统,我需要根据员工填写的考勤数据自动计算考勤分数,以便准确评估综合素质。 + +#### 验收标准 + +1. WHEN 员工全勤(无事假、迟到、缺卡、旷工)THEN THE System SHALL 给予考勤满分 10 分 +2. WHEN 员工有事假记录 THEN THE System SHALL 每天扣除 5 分 +3. WHEN 员工有迟到或缺卡记录 THEN THE System SHALL 每次扣除 2 分 +4. WHEN 员工有旷工记录 THEN THE System SHALL 按公司制度另行处理 +5. THE System SHALL 确保考勤分数不低于 0 分 + +### 需求 12: 绩效修改申请流程 + +**用户故事:** 作为员工,当我提交绩效后发现需要修改时,我希望能够向管理层申请退回修改,以便更正错误信息。 + +#### 验收标准 + +1. WHEN 员工提交绩效后需要修改 THEN THE System SHALL 提供"申请修改"功能 +2. WHEN 员工发起修改申请 THEN THE System SHALL 通知所属管理层 +3. WHEN 管理层同意修改申请 THEN THE System SHALL 解锁绩效表单并允许员工重新编辑 +4. WHEN 管理层拒绝修改申请 THEN THE System SHALL 通知员工并说明拒绝原因 +5. WHEN 员工修改完成后 THEN THE System SHALL 要求重新提交并触发新的 AI 评分 + +### 需求 13: FastGPT 集成 + +**用户故事:** 作为系统开发者,我需要正确集成 FastGPT API,以便实现 AI 自动评分功能。 + +#### 验收标准 + +1. WHEN 系统调用 FastGPT API THEN THE System SHALL 使用正确的 API-Key 和模型配置 +2. WHEN 构建 AI 请求 THEN THE System SHALL 包含员工岗位、考核月份、填报内容、考核规则等完整参数 +3. WHEN 接收 AI 响应 THEN THE System SHALL 验证返回的 JSON 格式是否符合预期结构 +4. WHEN AI 返回无效数据 THEN THE System SHALL 记录错误并提供降级处理方案 +5. THE System SHALL 确保 AI 调用的安全性,防止敏感信息泄露 diff --git a/.kiro/specs/employee-performance-system/tasks.md b/.kiro/specs/employee-performance-system/tasks.md new file mode 100644 index 0000000..63d95e8 --- /dev/null +++ b/.kiro/specs/employee-performance-system/tasks.md @@ -0,0 +1,192 @@ +# 实施计划 - 员工月度绩效考核系统 + +## 概述 + +按照前后端分离架构,分阶段实施:先搭建后端基础设施和核心业务逻辑,再实现前端各角色页面,最后集成 AI 评分和统计分析功能。 + +## 任务 + +- [x] 1. 项目初始化与基础设施搭建 + - 初始化后端 Node.js + TypeScript + Express 项目结构 + - 初始化前端 React + TypeScript + Ant Design 项目结构 + - 配置 MySQL 数据库连接(使用 mysql2 驱动) + - 创建数据库初始化脚本(6张表的 DDL) + - 配置环境变量(.env:DB连接、JWT密钥、FastGPT API Key) + - _Requirements: 9.1, 10.5_ + +- [x] 2. 用户认证模块 + - [x] 2.1 实现用户登录接口 `/api/user/login` + - 查询用户表验证用户名、密码(bcrypt 比对)和角色 + - 生成 JWT token(有效期 24 小时),返回 userInfo + - _Requirements: 1.1, 1.2_ + - [x] 2.2 实现 JWT 认证中间件 + - 验证请求头中的 token,解析用户信息注入 req.user + - token 过期或无效时返回 401 + - _Requirements: 1.4_ + - [x] 2.3 实现角色权限中间件 + - 根据路由配置允许的角色列表,验证当前用户角色 + - 权限不足时返回 403 + - _Requirements: 1.3, 1.5_ + - [x] 2.4 编写认证模块属性测试 + + - **属性 1: 认证正确性** — 对任意凭据,认证结果与凭据有效性一致 + - **属性 2: 权限隔离不变量** — 员工无法访问他人数据 + - **Validates: Requirements 1.1, 1.2, 1.5** + +- [x] 3. 数据库访问层(DAO) + - [x] 3.1 实现 UserDAO + - `findByUsername(username)`: 按用户名查询 + - `findSubordinates(managerId)`: 查询下属列表 + - _Requirements: 1.1, 4.1_ + - [x] 3.2 实现 PerformanceDAO + - `upsert(data)`: 创建或更新绩效记录(支持暂存/提交) + - `findByUserAndMonth(userId, month)`: 查询指定月份绩效 + - `findByManagerId(managerId, filters, page)`: 查询下属绩效列表 + - `updateStatus(perfId, status)`: 更新绩效状态 + - `updateReview(perfId, reviewData)`: 更新审核结果 + - _Requirements: 2.5, 2.6, 4.1, 4.5, 9.1_ + - [x] 3.3 实现 AIResultDAO + - `save(aiResult)`: 保存 AI 评分结果 + - `findByPerfId(perfId)`: 查询绩效对应的 AI 结果 + - _Requirements: 3.2, 3.3_ + +- [x] 4. 考勤分数计算与绩效等级模块 + - [x] 4.1 实现考勤分数计算函数 `calculateAttendanceScore` + - 基础分 10 分,事假每天扣 5 分,迟到/缺卡每次扣 2 分,下限 0 分 + - _Requirements: 11.1, 11.2, 11.3, 11.5_ + - [x] 4.2 实现绩效等级与奖惩计算函数 `calculateLevelAndReward` + - 根据总分返回等级(excellent/qualified/need_motivation/unqualified)和奖惩说明 + - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_ + - [x] 4.3 实现连续低分预警检测函数 `checkConsecutiveLowScore` + - 查询员工最近 N 个月记录,检测连续低于 60 分的情况 + - _Requirements: 5.6, 5.7_ + - [x] 4.4 编写计算模块属性测试 + + - **属性 5: 绩效等级与奖惩计算正确性** — 对任意分数,等级和奖惩符合规则 + - **属性 6: 连续低分预警正确性** — 连续低分正确触发预警 + - **属性 8: 考勤分数计算正确性** — 对任意考勤数据,分数计算正确且不低于 0 + - **Validates: Requirements 5.1-5.7, 11.1-11.5** + +- [x] 5. 检查点 — 确保所有测试通过,如有问题请告知 + +- [x] 6. 员工端绩效填报接口 + - [x] 6.1 实现绩效提交接口 `/api/performance/submit` + - 支持 draft(暂存)和 submit(提交)两种状态 + - 使用数据库事务同时写入 performance_month、perf_item、attendance 三张表 + - 提交时触发异步 AI 评分(不阻塞响应) + - _Requirements: 2.1, 2.2, 2.3, 2.5, 2.6_ + - [x] 6.2 实现个人绩效查询接口 `/api/performance/employee/get` + - 支持按月份筛选和分页 + - 返回绩效记录含 AI 结果、考勤、审核意见 + - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_ + - [x] 6.3 实现修改申请接口 `/api/performance/request-modification` + - 员工发起申请,记录申请原因,通知管理层 + - _Requirements: 12.1, 12.2_ + - [x] 6.4 编写员工端接口属性测试 + + - **属性 3: 草稿暂存往返一致性** — 暂存后读取数据一致 + - **属性 4: 提交幂等性** — 同一用户同月不可重复提交 + - **属性 7: 绩效记录查询完整性** — 提交后查询必能找到 + - **Validates: Requirements 2.5, 2.6, 6.1** + +- [x] 7. AI 评分模块 + - [x] 7.1 实现 FastGPT 调用服务 `AIEvaluationService` + - 构建包含岗位、月份、填报内容、考核规则的 Prompt + - 调用 FastGPT API(超时 10s) + - _Requirements: 13.1, 13.2_ + - [x] 7.2 实现 AI 响应解析函数 `parseAIResponse` + - 解析 JSON 格式响应,提取 ai_score_detail、ai_total_score、ai_problems、ai_suggestions + - 验证格式合法性,异常时记录原始响应并抛出错误 + - _Requirements: 3.2, 3.3, 3.4, 13.3, 13.4_ + - [x] 7.3 实现重试机制(最多 3 次)和错误降级处理 + - _Requirements: 3.5_ + - [x] 7.4 编写 AI 模块属性测试 + + - **属性 9: AI 响应 JSON 解析往返一致性** — 解析后序列化得到等价对象 + - **属性 10: AI 输出格式约束** — problems 和 suggestions 数组长度在 3-5 之间 + - **Validates: Requirements 3.3, 3.4, 3.6** + +- [x] 8. 管理层审核接口 + - [x] 8.1 实现下属绩效列表接口 `/api/performance/manager/list` + - 支持按月份、部门、员工姓名、状态筛选和分页 + - _Requirements: 4.1, 7.1, 7.2_ + - [x] 8.2 实现绩效审核接口 `/api/performance/manager/review` + - 更新各考核项管理层评分和说明 + - 计算最终总分、等级、奖惩,归档绩效记录(状态改为 completed) + - _Requirements: 4.3, 4.4, 4.5, 4.7_ + - [x] 8.3 实现绩效驳回接口 `/api/performance/manager/reject` + - 记录驳回原因,状态改为 rejected + - _Requirements: 4.6_ + - [x] 8.4 实现修改申请审批接口 `/api/performance/manager/approve-modification` + - 同意时解锁绩效记录,拒绝时通知员工 + - _Requirements: 12.3, 12.4_ + +- [x] 9. 检查点 — 确保所有测试通过,如有问题请告知 + +- [x] 10. 统计分析与导出接口 + - [x] 10.1 实现团队统计接口 `/api/statistics/team` + - 统计下属团队平均分、优秀/合格/需激励人数及占比 + - _Requirements: 7.4, 7.5_ + - [x] 10.2 实现全公司统计接口 `/api/statistics/company` + - 按部门、岗位、绩效等级多维度统计 + - _Requirements: 8.1, 8.2_ + - [x] 10.3 实现 Excel 导出接口 `/api/performance/export` + - 使用 exceljs 生成 Excel 文件,支持单员工历史和团队批量导出 + - _Requirements: 7.3, 7.6, 8.4_ + +- [x] 11. 总经理端接口 + - [x] 11.1 实现考核规则配置接口 `/api/config/rules` + - 支持修改考核指标权重、评分标准、奖惩金额 + - 修改后记录操作日志,应用于后续考核周期 + - _Requirements: 8.5, 8.6_ + +- [x] 12. 前端基础框架搭建 + - [x] 12.1 配置 React Router,定义三类角色的路由结构 + - 员工路由:`/employee/*`,管理层路由:`/manager/*`,总经理路由:`/gm/*` + - _Requirements: 1.3_ + - [x] 12.2 实现登录页面和 AuthContext + - 登录表单(用户名、密码、角色选择),登录成功后存储 token 跳转对应角色首页 + - _Requirements: 1.1, 1.2_ + - [x] 12.3 实现 Axios 请求拦截器 + - 自动携带 token,处理 401/403 响应(跳转登录页) + - _Requirements: 1.4_ + +- [x] 13. 员工端前端页面 + - [x] 13.1 实现绩效填报页面 `PerformanceForm` + - 自动带出考核月份、员工基础信息 + - 17 项考核指标逐项填写(完成情况描述 + 自评分 + 可选佐证上传) + - 考勤数据填写区域、工作汇总文本区域、暂存和提交按钮 + - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6_ + - [x] 13.2 实现个人绩效历史页面 `PerformanceHistory` + - 绩效记录列表(含状态、总分、等级),月份筛选和分页 + - 点击查看详情(含 AI 评分、AI 问题/建议、审核意见、奖惩说明) + - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_ + +- [x] 14. 管理层端前端页面 + - [x] 14.1 实现下属绩效列表页面 `SubordinateList` + - 列表展示员工绩效状态,筛选条件(月份、部门、姓名、状态) + - _Requirements: 4.1, 7.1, 7.2_ + - [x] 14.2 实现绩效审核页面 `PerformanceReview` + - 展示员工填报内容、自评分、AI 评分及 AI 反馈 + - 可调整各考核项分数并填写说明,提交审核通过或驳回 + - _Requirements: 4.2, 4.3, 4.4, 4.5, 4.6_ + - [x] 14.3 实现团队统计页面 `TeamStatistics` + - 显示团队平均分、各等级人数及占比,Excel 导出按钮 + - _Requirements: 7.3, 7.4, 7.5_ + +- [x] 15. 总经理端前端页面 + - [x] 15.1 实现全公司绩效总览页面 `CompanyOverview` + - 各部门、各岗位绩效整体情况,ECharts 柱状图和饼图,多维度筛选 + - _Requirements: 8.1, 8.2, 8.3_ + - [x] 15.2 实现全量数据导出和考核规则配置页面 + - 全量 Excel 导出,考核指标权重和奖惩金额配置表单 + - _Requirements: 8.4, 8.5, 8.6_ + +- [x] 16. 最终检查点 — 确保所有测试通过,前后端联调完成,如有问题请告知 + +## 备注 + +- 标有 `*` 的任务为可选测试任务,可跳过以加快 MVP 交付 +- 每个任务引用了具体的需求条目,便于追溯 +- 检查点任务确保每个阶段的增量验证 +- 属性测试使用 fast-check 库,每个属性最少运行 100 次迭代 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..4929316 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,18 @@ +# 数据库配置 +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=your_password_here +DB_NAME=employee_performance + +# JWT 配置 +JWT_SECRET=your_jwt_secret_here_change_in_production + +# FastGPT API 配置 +FASTGPT_API_KEY=your_fastgpt_api_key_here +FASTGPT_API_URL=https://api.fastgpt.in/api/v1/chat/completions +FASTGPT_MODEL=gpt-4 + +# 服务器配置 +PORT=3001 +NODE_ENV=development diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..1841c96 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +*.log +coverage/ diff --git a/backend/backfill-ai.ts b/backend/backfill-ai.ts new file mode 100644 index 0000000..7abf196 --- /dev/null +++ b/backend/backfill-ai.ts @@ -0,0 +1,21 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [results] = await pool.query('SELECT ai_id, perf_id, ai_score_json FROM ai_result'); + for (const row of results) { + const items = JSON.parse(row.ai_score_json); + for (const item of items) { + await pool.query( + 'UPDATE perf_item SET ai_score = ?, ai_explanation = ? WHERE perf_id = ? AND item_name = ?', + [item.aiScore, item.scoreExplanation, row.perf_id, item.itemName] + ); + } + console.log(`回写 perfId=${row.perf_id},共 ${items.length} 项`); + } + await pool.end(); + console.log('完成'); +} + +run().catch(console.error); diff --git a/backend/backfill-self-score.ts b/backend/backfill-self-score.ts new file mode 100644 index 0000000..d61c74a --- /dev/null +++ b/backend/backfill-self-score.ts @@ -0,0 +1,38 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + // 找出所有 self_score 为空但有 perf_item 的记录 + const [perfs] = await pool.query( + `SELECT perf_id FROM performance_month WHERE self_score IS NULL OR self_score = 0` + ); + + for (const perf of perfs) { + const [items] = await pool.query( + 'SELECT weight, self_score FROM perf_item WHERE perf_id = ? AND self_score IS NOT NULL', + [perf.perf_id] + ); + if (items.length === 0) continue; + + let weightedSum = 0; + let totalWeight = 0; + for (const item of items) { + weightedSum += Number(item.self_score) * Number(item.weight); + totalWeight += Number(item.weight); + } + if (totalWeight === 0) continue; + + const selfScore = parseFloat((weightedSum / totalWeight).toFixed(2)); + await pool.query( + 'UPDATE performance_month SET self_score = ? WHERE perf_id = ?', + [selfScore, perf.perf_id] + ); + console.log(`perfId=${perf.perf_id} 自评总分=${selfScore}`); + } + + await pool.end(); + console.log('完成'); +} + +run().catch(console.error); diff --git a/backend/check-ai-data.ts b/backend/check-ai-data.ts new file mode 100644 index 0000000..58e458d --- /dev/null +++ b/backend/check-ai-data.ts @@ -0,0 +1,20 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [rows] = await pool.query( + 'SELECT perf_id, ai_total_score, problems, suggestions FROM ai_result ORDER BY ai_id DESC LIMIT 2' + ); + for (const row of rows) { + console.log('=== perfId:', row.perf_id, '==='); + console.log('ai_total_score:', row.ai_total_score); + console.log('problems raw:', row.problems); + console.log('suggestions raw:', row.suggestions); + console.log('problems parsed:', JSON.parse(row.problems || '[]')); + console.log('suggestions parsed:', JSON.parse(row.suggestions || '[]')); + } + await pool.end(); +} + +run().catch(console.error); diff --git a/backend/check-ai-raw.ts b/backend/check-ai-raw.ts new file mode 100644 index 0000000..8836579 --- /dev/null +++ b/backend/check-ai-raw.ts @@ -0,0 +1,23 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; +import * as AIResultDAO from './src/dao/AIResultDAO'; + +async function run() { + // 直接查原始数据 + const [rows] = await pool.query('SELECT * FROM ai_result WHERE perf_id = 9'); + console.log('原始数据库行:', rows[0]); + console.log('problems 类型:', typeof rows[0]?.problems); + console.log('suggestions 类型:', typeof rows[0]?.suggestions); + + // 通过 DAO 查 + const result = await AIResultDAO.findByPerfId(9); + console.log('\nDAO 返回:'); + console.log(' aiTotalScore:', result?.aiTotalScore, typeof result?.aiTotalScore); + console.log(' aiProblems:', result?.aiProblems); + console.log(' aiSuggestions:', result?.aiSuggestions); + + await pool.end(); +} + +run().catch(console.error); diff --git a/backend/check-ai-results.ts b/backend/check-ai-results.ts new file mode 100644 index 0000000..541f2c1 --- /dev/null +++ b/backend/check-ai-results.ts @@ -0,0 +1,52 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function checkAIResults() { + try { + const conn = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + console.log('=== AI评分结果 ==='); + const [aiRows] = await conn.query(` + SELECT ai_id, perf_id, ai_total_score, + LEFT(problems, 100) as problems_preview, + LEFT(suggestions, 100) as suggestions_preview, + create_time + FROM ai_result + `); + console.table(aiRows); + + console.log('\n=== 绩效记录状态 ==='); + const [perfRows] = await conn.query(` + SELECT perf_id, user_id, month, status, ai_score, submit_time + FROM performance_month + ORDER BY perf_id DESC + `); + console.table(perfRows); + + // 检查是否有AI评分但没有更新到performance_month表 + console.log('\n=== 检查数据一致性 ==='); + const [inconsistent] = await conn.query(` + SELECT pm.perf_id, pm.month, pm.ai_score as perf_ai_score, ar.ai_total_score as ai_result_score + FROM performance_month pm + LEFT JOIN ai_result ar ON pm.perf_id = ar.perf_id + WHERE pm.status = 'submitted' + `); + console.table(inconsistent); + + await conn.end(); + process.exit(0); + } catch (error: any) { + console.error('错误:', error.message); + process.exit(1); + } +} + +checkAIResults(); diff --git a/backend/check-api-response.ts b/backend/check-api-response.ts new file mode 100644 index 0000000..d63a24d --- /dev/null +++ b/backend/check-api-response.ts @@ -0,0 +1,43 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; +import axios from 'axios'; +import * as jwt from 'jsonwebtoken'; + +async function run() { + // 找最新有 ai_result 的记录和对应 user_id + const [rows] = await pool.query( + `SELECT pm.perf_id, pm.user_id, pm.month, ar.ai_total_score, ar.problems, ar.suggestions + FROM performance_month pm + JOIN ai_result ar ON pm.perf_id = ar.perf_id + ORDER BY ar.ai_id DESC LIMIT 1` + ); + const rec = rows[0]; + console.log('测试记录:', rec.perf_id, 'userId:', rec.user_id, 'month:', rec.month); + + // 获取该用户信息 + const [users] = await pool.query('SELECT username, role, name FROM user WHERE user_id = ?', [rec.user_id]); + const user = users[0]; + console.log('用户:', user); + + const token = jwt.sign( + { userId: rec.user_id, username: user.username, role: user.role, name: user.name }, + process.env.JWT_SECRET!, + { expiresIn: '1h' } + ); + + const { data } = await axios.get('http://localhost:3001/api/performance/employee/get', { + params: { perfId: rec.perf_id }, + headers: { Authorization: `Bearer ${token}` }, + }); + + const aiResult = data.data?.aiResult; + console.log('\naiResult:'); + console.log(' aiTotalScore:', aiResult?.aiTotalScore, typeof aiResult?.aiTotalScore); + console.log(' aiProblems:', aiResult?.aiProblems); + console.log(' aiSuggestions:', aiResult?.aiSuggestions); + + await pool.end(); +} + +run().catch(console.error); diff --git a/backend/check-completed.ts b/backend/check-completed.ts new file mode 100644 index 0000000..33a1c5b --- /dev/null +++ b/backend/check-completed.ts @@ -0,0 +1,16 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [rows] = await pool.query( + `SELECT pm.perf_id, pm.user_id, pm.month, pm.status, pm.total_score, pm.level, pm.manager_score, + u.manager_id + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + ORDER BY pm.perf_id` + ); + console.table(rows); + await pool.end(); +} +run().catch(console.error); diff --git a/backend/check-config.ts b/backend/check-config.ts new file mode 100644 index 0000000..2645862 --- /dev/null +++ b/backend/check-config.ts @@ -0,0 +1,50 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [rows] = await pool.query('SELECT COUNT(*) as cnt FROM performance_rules'); + console.log('performance_rules 记录数:', rows[0].cnt); + + if (rows[0].cnt === 0) { + console.log('表为空,插入默认规则...'); + const defaults = [ + ['business_completion_weight', '15', '工作完成度权重'], + ['business_quality_weight', '10', '工作质量权重'], + ['business_efficiency_weight', '10', '工作效率权重'], + ['business_skill_weight', '10', '专业技能权重'], + ['business_innovation_weight', '5', '创新能力权重'], + ['business_problem_solving_weight', '5', '问题解决权重'], + ['business_customer_satisfaction_weight', '5', '客户满意度权重'], + ['business_teamwork_weight', '5', '团队协作权重'], + ['business_goal_achievement_weight', '5', '目标达成权重'], + ['comprehensive_responsibility_weight', '5', '责任心权重'], + ['comprehensive_initiative_weight', '4', '主动性权重'], + ['comprehensive_learning_weight', '4', '学习能力权重'], + ['comprehensive_communication_weight', '4', '沟通能力权重'], + ['comprehensive_execution_weight', '4', '执行力权重'], + ['comprehensive_discipline_weight', '4', '纪律性权重'], + ['comprehensive_team_spirit_weight', '5', '团队精神权重'], + ['comprehensive_attendance_weight', '10', '考勤权重'], + ['reward_excellent', '500', '优秀奖励金额'], + ['punish_qualified_high', '0', '合格(80-89)扣款'], + ['punish_qualified_low', '100', '合格(70-79)扣款'], + ['punish_need_motivation', '200', '需激励扣款'], + ['punish_unqualified', '500', '不合格扣款'], + ['attendance_leave_deduct', '5', '事假每天扣分'], + ['attendance_late_deduct', '2', '迟到每次扣分'], + ['attendance_lack_card_deduct', '2', '缺卡每次扣分'], + ['attendance_base_score', '10', '考勤基础分'], + ]; + for (const [key, value, desc] of defaults) { + await pool.query( + 'INSERT INTO performance_rules (rule_key, rule_value, description) VALUES (?, ?, ?)', + [key, value, desc] + ); + } + console.log('默认规则插入完成'); + } + + await pool.end(); +} +run().catch(console.error); diff --git a/backend/check-db.ts b/backend/check-db.ts new file mode 100644 index 0000000..e8e6220 --- /dev/null +++ b/backend/check-db.ts @@ -0,0 +1,31 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function checkDatabase() { + try { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + console.log('✓ 已连接到数据库\n'); + + // 查看所有表 + const [tables] = await connection.query('SHOW TABLES'); + console.log('数据库中的表:'); + console.log(tables); + + await connection.end(); + process.exit(0); + } catch (error: any) { + console.error('❌ 错误:', error.message); + process.exit(1); + } +} + +checkDatabase(); diff --git a/backend/check-manager-relation.ts b/backend/check-manager-relation.ts new file mode 100644 index 0000000..1b2522c --- /dev/null +++ b/backend/check-manager-relation.ts @@ -0,0 +1,19 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [users] = await pool.query( + 'SELECT user_id, username, name, role, manager_id FROM user ORDER BY role, user_id' + ); + console.table(users); + + const [perfs] = await pool.query( + 'SELECT pm.perf_id, pm.user_id, u.name, u.manager_id, pm.month, pm.status FROM performance_month pm JOIN user u ON pm.user_id = u.user_id' + ); + console.log('\n绩效记录与管理层关联:'); + console.table(perfs); + + await pool.end(); +} +run().catch(console.error); diff --git a/backend/check-records.ts b/backend/check-records.ts new file mode 100644 index 0000000..2425974 --- /dev/null +++ b/backend/check-records.ts @@ -0,0 +1,48 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function checkRecords() { + try { + const conn = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + console.log('=== 所有绩效记录 ==='); + const [rows] = await conn.query(` + SELECT perf_id, user_id, month, status, self_score, ai_score, submit_time, created_at + FROM performance_month + ORDER BY created_at DESC + `); + console.table(rows); + + console.log('\n=== 员工用户信息 ==='); + const [users] = await conn.query(` + SELECT user_id, username, name, role, department, position + FROM user + WHERE role = 'employee' + `); + console.table(users); + + console.log('\n=== 绩效项数量统计 ==='); + const [itemCounts] = await conn.query(` + SELECT perf_id, COUNT(*) as item_count + FROM perf_item + GROUP BY perf_id + `); + console.table(itemCounts); + + await conn.end(); + process.exit(0); + } catch (error: any) { + console.error('错误:', error.message); + process.exit(1); + } +} + +checkRecords(); diff --git a/backend/clear-test-data.ts b/backend/clear-test-data.ts new file mode 100644 index 0000000..e3ead92 --- /dev/null +++ b/backend/clear-test-data.ts @@ -0,0 +1,49 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + console.log('开始清理测试数据...'); + + // 1. 清理 AI 结果 + const [ai] = await pool.query('DELETE FROM ai_result'); + console.log(`删除 ai_result: ${ai.affectedRows} 条`); + + // 2. 清理操作日志 + const [logs] = await pool.query('DELETE FROM operation_log'); + console.log(`删除 operation_log: ${logs.affectedRows} 条`); + + // 3. 清理考勤数据 + const [att] = await pool.query('DELETE FROM attendance'); + console.log(`删除 attendance: ${att.affectedRows} 条`); + + // 4. 清理绩效项明细 + const [items] = await pool.query('DELETE FROM perf_item'); + console.log(`删除 perf_item: ${items.affectedRows} 条`); + + // 5. 清理绩效主表 + const [perf] = await pool.query('DELETE FROM performance_month'); + console.log(`删除 performance_month: ${perf.affectedRows} 条`); + + // 6. 清理测试员工账号(保留管理层和总经理账号) + const [users] = await pool.query("DELETE FROM user WHERE role = 'employee'"); + console.log(`删除测试员工账号: ${users.affectedRows} 条`); + + // 重置自增ID + await pool.query('ALTER TABLE ai_result AUTO_INCREMENT = 1'); + await pool.query('ALTER TABLE operation_log AUTO_INCREMENT = 1'); + await pool.query('ALTER TABLE attendance AUTO_INCREMENT = 1'); + await pool.query('ALTER TABLE perf_item AUTO_INCREMENT = 1'); + await pool.query('ALTER TABLE performance_month AUTO_INCREMENT = 1'); + await pool.query('ALTER TABLE user AUTO_INCREMENT = 1'); + + // 查看剩余账号 + const [remaining] = await pool.query('SELECT user_id, username, name, role FROM user ORDER BY role'); + console.log('\n剩余账号:'); + console.table(remaining); + + await pool.end(); + console.log('\n清理完成!'); +} + +run().catch(console.error); diff --git a/backend/clear-wyy.ts b/backend/clear-wyy.ts new file mode 100644 index 0000000..57bc1b2 --- /dev/null +++ b/backend/clear-wyy.ts @@ -0,0 +1,38 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + // 查找 wyy 用户 + const [users] = await pool.query("SELECT * FROM user WHERE username LIKE '%wyy%' OR name LIKE '%wyy%'"); + console.log('找到用户:', users); + + if (users.length === 0) { + console.log('未找到 wyy 用户,查看所有员工账号:'); + const [all] = await pool.query("SELECT user_id, username, name, role FROM user WHERE role = 'employee'"); + console.table(all); + await pool.end(); + return; + } + + for (const user of users) { + const userId = user.user_id; + + // 找到该用户的绩效记录 + const [perfs] = await pool.query('SELECT perf_id FROM performance_month WHERE user_id = ?', [userId]); + for (const perf of perfs) { + await pool.query('DELETE FROM ai_result WHERE perf_id = ?', [perf.perf_id]); + await pool.query('DELETE FROM attendance WHERE perf_id = ?', [perf.perf_id]); + await pool.query('DELETE FROM perf_item WHERE perf_id = ?', [perf.perf_id]); + } + await pool.query('DELETE FROM performance_month WHERE user_id = ?', [userId]); + await pool.query('DELETE FROM operation_log WHERE user_id = ?', [userId]); + await pool.query('DELETE FROM user WHERE user_id = ?', [userId]); + console.log(`已删除用户 ${user.username}(${user.name}) 及其所有数据`); + } + + await pool.end(); + console.log('完成'); +} + +run().catch(console.error); diff --git a/backend/create-db-user.sql b/backend/create-db-user.sql new file mode 100644 index 0000000..f86ae7d --- /dev/null +++ b/backend/create-db-user.sql @@ -0,0 +1,14 @@ +-- 创建新的数据库用户(如果你能以 root 身份登录 MySQL) +-- 在 MySQL 命令行中执行此脚本 + +-- 创建用户(密码设置为 123456) +CREATE USER IF NOT EXISTS 'emp_user'@'localhost' IDENTIFIED BY '123456'; + +-- 授予权限 +GRANT ALL PRIVILEGES ON employee_performance.* TO 'emp_user'@'localhost'; + +-- 刷新权限 +FLUSH PRIVILEGES; + +-- 显示用户 +SELECT User, Host FROM mysql.user WHERE User = 'emp_user'; diff --git a/backend/init-config-rules.ts b/backend/init-config-rules.ts new file mode 100644 index 0000000..11bc9f9 --- /dev/null +++ b/backend/init-config-rules.ts @@ -0,0 +1,48 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +const defaults: [string, string, string][] = [ + ['business_completion_weight', '15', '工作完成度权重'], + ['business_quality_weight', '10', '工作质量权重'], + ['business_efficiency_weight', '10', '工作效率权重'], + ['business_skill_weight', '10', '专业技能权重'], + ['business_innovation_weight', '5', '创新能力权重'], + ['business_problem_solving_weight', '5', '问题解决权重'], + ['business_customer_satisfaction_weight', '5', '客户满意度权重'], + ['business_teamwork_weight', '5', '团队协作权重'], + ['business_goal_achievement_weight', '5', '目标达成权重'], + ['comprehensive_responsibility_weight', '5', '责任心权重'], + ['comprehensive_initiative_weight', '4', '主动性权重'], + ['comprehensive_learning_weight', '4', '学习能力权重'], + ['comprehensive_communication_weight', '4', '沟通能力权重'], + ['comprehensive_execution_weight', '4', '执行力权重'], + ['comprehensive_discipline_weight', '4', '纪律性权重'], + ['comprehensive_team_spirit_weight', '5', '团队精神权重'], + ['comprehensive_attendance_weight', '10', '考勤权重'], + ['reward_excellent', '500', '优秀奖励金额'], + ['punish_qualified_high', '0', '合格(80-89)扣款'], + ['punish_qualified_low', '100', '合格(70-79)扣款'], + ['punish_need_motivation', '200', '需激励扣款'], + ['punish_unqualified', '500', '不合格扣款'], + ['attendance_leave_deduct', '5', '事假每天扣分'], + ['attendance_late_deduct', '2', '迟到每次扣分'], + ['attendance_lack_card_deduct', '2', '缺卡每次扣分'], + ['attendance_base_score', '10', '考勤基础分'], +]; + +async function run() { + for (const [key, value, desc] of defaults) { + await pool.query( + `INSERT INTO performance_rules (rule_key, rule_value, description) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE description = VALUES(description)`, + [key, value, desc] + ); + } + const [rows] = await pool.query('SELECT COUNT(*) as cnt FROM performance_rules'); + console.log('规则总数:', rows[0].cnt); + await pool.end(); + console.log('完成'); +} +run().catch(console.error); diff --git a/backend/init-db-v2.ts b/backend/init-db-v2.ts new file mode 100644 index 0000000..46ab485 --- /dev/null +++ b/backend/init-db-v2.ts @@ -0,0 +1,74 @@ +import mysql from 'mysql2/promise'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function initDatabase() { + try { + console.log('开始初始化数据库...\n'); + + // 连接到 MySQL + const connection = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + multipleStatements: true, // 允许执行多条语句 + }); + + console.log('✓ 已连接到 MySQL 服务器'); + + // 创建数据库 + await connection.query(`CREATE DATABASE IF NOT EXISTS ${process.env.DB_NAME || 'employee_performance'} DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci`); + console.log('✓ 数据库已创建'); + + // 切换到目标数据库 + await connection.query(`USE ${process.env.DB_NAME || 'employee_performance'}`); + + // 读取并执行初始化脚本 + const initSql = fs.readFileSync(path.join(__dirname, 'src/db/init.sql'), 'utf-8'); + + // 移除 CREATE DATABASE 和 USE 语句 + const cleanedSql = initSql + .split('\n') + .filter(line => !line.trim().startsWith('CREATE DATABASE') && !line.trim().startsWith('USE ')) + .join('\n'); + + await connection.query(cleanedSql); + console.log('✓ 表结构已创建'); + + // 读取并执行种子数据脚本 + const seedSql = fs.readFileSync(path.join(__dirname, 'src/db/seed.sql'), 'utf-8'); + const cleanedSeedSql = seedSql + .split('\n') + .filter(line => !line.trim().startsWith('USE ')) + .join('\n'); + + await connection.query(cleanedSeedSql); + console.log('✓ 测试数据已插入'); + + // 验证表是否创建成功 + const [tables] = await connection.query('SHOW TABLES'); + console.log('\n创建的表:', tables); + + await connection.end(); + + console.log('\n✅ 数据库初始化完成!'); + console.log('\n测试账号信息:'); + console.log('总经理: gm001 / 123456'); + console.log('技术部经理: mgr001 / 123456'); + console.log('销售部经理: mgr002 / 123456'); + console.log('技术部员工: emp001, emp002, emp003 / 123456'); + console.log('销售部员工: emp004, emp005 / 123456'); + + process.exit(0); + } catch (error: any) { + console.error('\n❌ 数据库初始化失败:', error.message); + console.error(error); + process.exit(1); + } +} + +initDatabase(); diff --git a/backend/init-db.ts b/backend/init-db.ts new file mode 100644 index 0000000..ca52cd0 --- /dev/null +++ b/backend/init-db.ts @@ -0,0 +1,74 @@ +import mysql from 'mysql2/promise'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function initDatabase() { + try { + console.log('开始初始化数据库...\n'); + + // 先连接到 MySQL(不指定数据库) + const connection = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + }); + + console.log('✓ 已连接到 MySQL 服务器'); + + // 创建数据库 + await connection.query(`CREATE DATABASE IF NOT EXISTS ${process.env.DB_NAME || 'employee_performance'} DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci`); + console.log('✓ 数据库已创建'); + + // 切换到目标数据库 + await connection.query(`USE ${process.env.DB_NAME || 'employee_performance'}`); + + // 读取并执行初始化脚本 + const initSql = fs.readFileSync(path.join(__dirname, 'src/db/init.sql'), 'utf-8'); + const statements = initSql + .split(';') + .map(s => s.trim()) + .filter(s => s.length > 0 && !s.startsWith('--') && !s.startsWith('CREATE DATABASE') && !s.startsWith('USE')); + + for (const statement of statements) { + if (statement.trim()) { + await connection.query(statement); + } + } + console.log('✓ 表结构已创建'); + + // 读取并执行种子数据脚本 + const seedSql = fs.readFileSync(path.join(__dirname, 'src/db/seed.sql'), 'utf-8'); + const seedStatements = seedSql + .split(';') + .map(s => s.trim()) + .filter(s => s.length > 0 && !s.startsWith('--') && !s.startsWith('USE')); + + for (const statement of seedStatements) { + if (statement.trim()) { + await connection.query(statement); + } + } + console.log('✓ 测试数据已插入'); + + await connection.end(); + + console.log('\n✅ 数据库初始化完成!'); + console.log('\n测试账号信息:'); + console.log('总经理: gm001 / 123456'); + console.log('技术部经理: mgr001 / 123456'); + console.log('销售部经理: mgr002 / 123456'); + console.log('技术部员工: emp001, emp002, emp003 / 123456'); + console.log('销售部员工: emp004, emp005 / 123456'); + + process.exit(0); + } catch (error: any) { + console.error('\n❌ 数据库初始化失败:', error.message); + process.exit(1); + } +} + +initDatabase(); diff --git a/backend/insert-mock-ai-result.ts b/backend/insert-mock-ai-result.ts new file mode 100644 index 0000000..351ba5a --- /dev/null +++ b/backend/insert-mock-ai-result.ts @@ -0,0 +1,95 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function insertMockAIResult() { + try { + const conn = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + // 为每个已提交的绩效记录创建模拟AI评分 + const [perfRecords] = await conn.query(` + SELECT perf_id FROM performance_month WHERE status = 'submitted' + `); + + for (const record of perfRecords as any[]) { + const perfId = record.perf_id; + + // 模拟AI评分详情 + const aiScoreDetail = [ + { itemName: '工作目标完成情况', weight: 15, aiScore: 85, scoreExplanation: '工作目标基本完成,但部分细节需要改进' }, + { itemName: '工作质量', weight: 10, aiScore: 88, scoreExplanation: '工作质量良好,符合标准' }, + { itemName: '工作效率', weight: 10, aiScore: 82, scoreExplanation: '效率有待提升' }, + { itemName: '业务能力', weight: 10, aiScore: 86, scoreExplanation: '业务能力扎实' }, + { itemName: '创新能力', weight: 5, aiScore: 80, scoreExplanation: '有一定创新意识' }, + { itemName: '问题解决能力', weight: 5, aiScore: 84, scoreExplanation: '能够独立解决问题' }, + { itemName: '项目推进能力', weight: 5, aiScore: 83, scoreExplanation: '项目推进较为顺利' }, + { itemName: '客户服务', weight: 5, aiScore: 87, scoreExplanation: '客户反馈良好' }, + { itemName: '成本控制', weight: 5, aiScore: 85, scoreExplanation: '成本控制意识较强' }, + { itemName: '团队协作', weight: 5, aiScore: 89, scoreExplanation: '团队协作能力突出' }, + { itemName: '沟通能力', weight: 5, aiScore: 86, scoreExplanation: '沟通顺畅' }, + { itemName: '学习成长', weight: 5, aiScore: 88, scoreExplanation: '学习态度积极' }, + { itemName: '责任心', weight: 5, aiScore: 90, scoreExplanation: '责任心强' }, + { itemName: '执行力', weight: 5, aiScore: 87, scoreExplanation: '执行力较好' }, + { itemName: '职业素养', weight: 5, aiScore: 89, scoreExplanation: '职业素养良好' }, + { itemName: '工作态度', weight: 5, aiScore: 91, scoreExplanation: '工作态度端正' }, + ]; + + const aiTotalScore = 85.5; + + const problems = [ + '工作效率有待提升,部分任务完成时间较长', + '创新能力需要加强,建议多尝试新方法', + '项目文档记录不够完善,需要改进' + ]; + + const suggestions = [ + '建议制定更详细的工作计划,提高时间管理能力', + '多参加技术分享和培训,拓展知识面', + '加强项目文档编写,形成良好的工作习惯', + '主动承担更多挑战性任务,提升综合能力' + ]; + + // 插入AI评分结果 + await conn.query(` + INSERT INTO ai_result (perf_id, ai_score_json, ai_total_score, problems, suggestions, api_response) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + ai_score_json = VALUES(ai_score_json), + ai_total_score = VALUES(ai_total_score), + problems = VALUES(problems), + suggestions = VALUES(suggestions) + `, [ + perfId, + JSON.stringify(aiScoreDetail), + aiTotalScore, + JSON.stringify(problems), + JSON.stringify(suggestions), + '模拟AI评分结果' + ]); + + // 更新performance_month表的ai_score + await conn.query(` + UPDATE performance_month SET ai_score = ? WHERE perf_id = ? + `, [aiTotalScore, perfId]); + + console.log(`✓ 已为绩效记录 ${perfId} 创建模拟AI评分`); + } + + console.log('\n✅ 模拟AI评分数据插入完成!'); + + await conn.end(); + process.exit(0); + } catch (error: any) { + console.error('错误:', error.message); + process.exit(1); + } +} + +insertMockAIResult(); diff --git a/backend/jest.config.js b/backend/jest.config.js new file mode 100644 index 0000000..d0a0b03 --- /dev/null +++ b/backend/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/*.test.ts'], + collectCoverageFrom: ['src/**/*.ts', '!src/index.ts'], +}; diff --git a/backend/list-accounts.ts b/backend/list-accounts.ts new file mode 100644 index 0000000..7916f5b --- /dev/null +++ b/backend/list-accounts.ts @@ -0,0 +1,12 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; + +async function run() { + const [rows] = await pool.query( + "SELECT username, name, role, department, position FROM user WHERE role IN ('manager','generalManager') ORDER BY role" + ); + console.table(rows); + await pool.end(); +} +run().catch(console.error); diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..f96bc74 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,6469 @@ +{ + "name": "employee-performance-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "employee-performance-backend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.6.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "exceljs": "^4.4.0", + "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.6.5" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.10.0", + "fast-check": "^3.15.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmmirror.com/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmmirror.com/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmmirror.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmmirror.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.16", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001786", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz", + "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmmirror.com/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.332", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", + "integrity": "sha512-7OOtytmh/rINMLwaFTbcMVvYXO3AUm029X0LcyfYk0B557RlPkdpTpnH9+htMlfu5dKwOmT0+Zs2Aw+lnn6TeQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmmirror.com/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.20.0", + "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.20.0.tgz", + "integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-jest": { + "version": "29.4.9", + "resolved": "https://registry.npmmirror.com/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmmirror.com/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..3c9d13a --- /dev/null +++ b/backend/package.json @@ -0,0 +1,39 @@ +{ + "name": "employee-performance-backend", + "version": "1.0.0", + "description": "员工月度绩效考核系统后端", + "main": "dist/index.js", + "scripts": { + "dev": "ts-node-dev --respawn --transpile-only src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "test": "jest --runInBand", + "test:watch": "jest --watch", + "db:seed": "ts-node src/db/seed.ts", + "db:test": "ts-node test-db.ts", + "db:init": "ts-node init-db.ts" + }, + "dependencies": { + "axios": "^1.6.0", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "exceljs": "^4.4.0", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.6.5" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.10.0", + "fast-check": "^3.15.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.2" + } +} diff --git a/backend/src/config/database.ts b/backend/src/config/database.ts new file mode 100644 index 0000000..18ba2dd --- /dev/null +++ b/backend/src/config/database.ts @@ -0,0 +1,18 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const pool = mysql.createPool({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, + timezone: '+08:00', +}); + +export default pool; diff --git a/backend/src/config/jwt.ts b/backend/src/config/jwt.ts new file mode 100644 index 0000000..e640f25 --- /dev/null +++ b/backend/src/config/jwt.ts @@ -0,0 +1,2 @@ +export const JWT_SECRET = process.env.JWT_SECRET || 'change_this_secret_in_production'; +export const JWT_EXPIRES_IN = '24h'; diff --git a/backend/src/dao/AIResultDAO.ts b/backend/src/dao/AIResultDAO.ts new file mode 100644 index 0000000..b36f8ec --- /dev/null +++ b/backend/src/dao/AIResultDAO.ts @@ -0,0 +1,93 @@ +import pool from '../config/database'; +import { ResultSetHeader } from 'mysql2'; + +export interface AIScoreItem { + itemName: string; + weight: number; + aiScore: number; + scoreExplanation: string; +} + +export interface AIResultRow { + ai_id: number; + perf_id: number; + ai_score_json: string; + ai_total_score: number; + problems: string | null; + suggestions: string | null; + api_response: string | null; + create_time: Date; +} + +export interface SaveAIResultData { + perfId: number; + aiScoreDetail: AIScoreItem[]; + aiTotalScore: number; + aiProblems: string[]; + aiSuggestions: string[]; + apiResponse?: string; +} + +export interface AIResult { + aiId: number; + perfId: number; + aiScoreDetail: AIScoreItem[]; + aiTotalScore: number; + aiProblems: string[]; + aiSuggestions: string[]; + createTime: Date; +} + +/** Save AI evaluation result. Replaces any existing result for the same perf_id. */ +export async function save(data: SaveAIResultData): Promise { + const [result] = await pool.query( + `INSERT INTO ai_result (perf_id, ai_score_json, ai_total_score, problems, suggestions, api_response) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + ai_score_json = VALUES(ai_score_json), + ai_total_score = VALUES(ai_total_score), + problems = VALUES(problems), + suggestions = VALUES(suggestions), + api_response = VALUES(api_response)`, + [ + data.perfId, + JSON.stringify(data.aiScoreDetail), + data.aiTotalScore, + JSON.stringify(data.aiProblems), + JSON.stringify(data.aiSuggestions), + data.apiResponse ?? null, + ] + ); + + if (result.insertId !== 0) { + return result.insertId; + } + + // On UPDATE, fetch the existing ai_id + const [rows] = await pool.query( + 'SELECT ai_id FROM ai_result WHERE perf_id = ? LIMIT 1', + [data.perfId] + ); + return rows[0].ai_id; +} + +/** Retrieve the AI result for a given performance record. */ +export async function findByPerfId(perfId: number): Promise { + const [rows] = await pool.query( + 'SELECT * FROM ai_result WHERE perf_id = ? LIMIT 1', + [perfId] + ); + + if (rows.length === 0) return null; + + const row: AIResultRow = rows[0]; + return { + aiId: row.ai_id, + perfId: row.perf_id, + aiScoreDetail: JSON.parse(row.ai_score_json) as AIScoreItem[], + aiTotalScore: Number(row.ai_total_score), + aiProblems: row.problems ? (JSON.parse(row.problems) as string[]) : [], + aiSuggestions: row.suggestions ? (JSON.parse(row.suggestions) as string[]) : [], + createTime: row.create_time, + }; +} diff --git a/backend/src/dao/ConfigDAO.ts b/backend/src/dao/ConfigDAO.ts new file mode 100644 index 0000000..bb217e3 --- /dev/null +++ b/backend/src/dao/ConfigDAO.ts @@ -0,0 +1,53 @@ +import pool from '../config/database'; + +export interface RuleRow { + rule_id: number; + rule_key: string; + rule_value: string; // JSON string + description: string | null; + effective_cycle: string | null; + updated_by: number | null; + created_at: Date; + updated_at: Date; +} + +/** Fetch all rules */ +export async function findAllRules(): Promise { + const [rows] = await pool.query( + 'SELECT rule_id, rule_key, rule_value, description, effective_cycle, updated_by, created_at, updated_at FROM performance_rules ORDER BY rule_key' + ); + return rows as RuleRow[]; +} + +/** Fetch a single rule by key */ +export async function findRuleByKey(ruleKey: string): Promise { + const [rows] = await pool.query( + 'SELECT rule_id, rule_key, rule_value, description, effective_cycle, updated_by, created_at, updated_at FROM performance_rules WHERE rule_key = ? LIMIT 1', + [ruleKey] + ); + return rows.length > 0 ? (rows[0] as RuleRow) : null; +} + +export interface UpsertRuleData { + ruleKey: string; + ruleValue: unknown; // will be JSON-serialised + description?: string; + effectiveCycle?: string; + updatedBy: number; +} + +/** Insert or update a rule (upsert by rule_key) */ +export async function upsertRule(data: UpsertRuleData): Promise { + const valueJson = JSON.stringify(data.ruleValue); + await pool.query( + `INSERT INTO performance_rules (rule_key, rule_value, description, effective_cycle, updated_by) + VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + rule_value = VALUES(rule_value), + description = VALUES(description), + effective_cycle = VALUES(effective_cycle), + updated_by = VALUES(updated_by), + updated_at = CURRENT_TIMESTAMP`, + [data.ruleKey, valueJson, data.description ?? null, data.effectiveCycle ?? null, data.updatedBy] + ); +} diff --git a/backend/src/dao/PerformanceDAO.ts b/backend/src/dao/PerformanceDAO.ts new file mode 100644 index 0000000..b139333 --- /dev/null +++ b/backend/src/dao/PerformanceDAO.ts @@ -0,0 +1,319 @@ +import pool from '../config/database'; +import { ResultSetHeader } from 'mysql2'; + +export type PerformanceStatus = 'draft' | 'submitted' | 'under_review' | 'completed' | 'rejected'; +export type PerformanceLevel = 'excellent' | 'qualified' | 'need_motivation' | 'unqualified'; + +export interface PerformanceRow { + perf_id: number; + user_id: number; + month: string; + status: PerformanceStatus; + self_score: number | null; + ai_score: number | null; + manager_score: number | null; + total_score: number | null; + level: PerformanceLevel | null; + reward_punish: string | null; + work_summary: string | null; + submit_time: Date | null; + review_time: Date | null; + review_opinion: string | null; + created_at: Date; + updated_at: Date; +} + +export interface PerfItemRow { + item_id?: number; + perf_id: number; + item_name: string; + item_category: 'business' | 'comprehensive'; + weight: number; + user_content: string | null; + self_score: number | null; + ai_score: number | null; + ai_explanation: string | null; + manager_score: number | null; + manager_explanation: string | null; + evidence_url: string | null; +} + +export interface AttendanceRow { + attendance_id?: number; + perf_id: number; + leave_days: number; + late_times: number; + absent_days: number; + lack_card_times: number; + attendance_score: number | null; + remark: string | null; +} + +export interface UpsertPerformanceData { + userId: number; + month: string; + status: PerformanceStatus; + selfScore?: number; + workSummary?: string; + items?: Omit[]; + attendance?: Omit; +} + +export interface PerformanceFilter { + month?: string; + department?: string; + employeeName?: string; + status?: PerformanceStatus; +} + +export interface PageInfo { + page: number; + pageSize: number; +} + +export interface ReviewData { + managerScore: number; + reviewOpinion: string; + totalScore: number; + level: PerformanceLevel; + rewardPunish: string; + itemScores: { itemName: string; managerScore: number; managerExplanation: string }[]; +} + +export interface PerformanceListResult { + total: number; + records: PerformanceRow[]; +} + +/** + * Create or update a performance record along with its items and attendance. + * Uses INSERT ... ON DUPLICATE KEY UPDATE to handle the unique constraint on (user_id, month). + */ +export async function upsert(data: UpsertPerformanceData): Promise { + const conn = await pool.getConnection(); + try { + await conn.beginTransaction(); + + // Upsert performance_month + const submitTime = data.status === 'submitted' ? new Date() : null; + const [result] = await conn.query( + `INSERT INTO performance_month (user_id, month, status, self_score, work_summary, submit_time) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + status = VALUES(status), + self_score = VALUES(self_score), + work_summary = VALUES(work_summary), + submit_time = COALESCE(VALUES(submit_time), submit_time)`, + [data.userId, data.month, data.status, data.selfScore ?? null, data.workSummary ?? null, submitTime] + ); + + // Resolve perf_id (insertId is 0 on UPDATE, so fetch it) + let perfId: number = result.insertId; + if (perfId === 0) { + const [rows] = await conn.query( + 'SELECT perf_id FROM performance_month WHERE user_id = ? AND month = ?', + [data.userId, data.month] + ); + perfId = rows[0].perf_id; + } + + // Upsert perf_items + if (data.items && data.items.length > 0) { + // Delete existing items and re-insert for simplicity + await conn.query('DELETE FROM perf_item WHERE perf_id = ?', [perfId]); + for (const item of data.items) { + await conn.query( + `INSERT INTO perf_item (perf_id, item_name, item_category, weight, user_content, self_score, evidence_url) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [perfId, item.item_name, item.item_category, item.weight, item.user_content ?? null, item.self_score ?? null, item.evidence_url ?? null] + ); + } + } + + // Upsert attendance + if (data.attendance) { + const att = data.attendance; + await conn.query( + `INSERT INTO attendance (perf_id, leave_days, late_times, absent_days, lack_card_times, attendance_score, remark) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + leave_days = VALUES(leave_days), + late_times = VALUES(late_times), + absent_days = VALUES(absent_days), + lack_card_times = VALUES(lack_card_times), + attendance_score = VALUES(attendance_score), + remark = VALUES(remark)`, + [perfId, att.leave_days, att.late_times, att.absent_days, att.lack_card_times, att.attendance_score ?? null, att.remark ?? null] + ); + } + + await conn.commit(); + return perfId; + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } +} + +/** Query a performance record for a specific user and month, including items and attendance. */ +export async function findByUserAndMonth(userId: number, month: string): Promise { + const [rows] = await pool.query( + 'SELECT * FROM performance_month WHERE user_id = ? AND month = ? LIMIT 1', + [userId, month] + ); + return rows.length > 0 ? (rows[0] as PerformanceRow) : null; +} + +/** Query subordinates' performance records with optional filters and pagination. */ +export async function findByManagerId( + managerId: number, + filters: PerformanceFilter, + page: PageInfo +): Promise { + const conditions: string[] = ['u.manager_id = ?']; + const params: any[] = [managerId]; + + if (filters.month) { + conditions.push('pm.month = ?'); + params.push(filters.month); + } + if (filters.department) { + conditions.push('u.department = ?'); + params.push(filters.department); + } + if (filters.employeeName) { + conditions.push('u.name LIKE ?'); + params.push(`%${filters.employeeName}%`); + } + if (filters.status) { + conditions.push('pm.status = ?'); + params.push(filters.status); + } + + const where = conditions.join(' AND '); + const offset = (page.page - 1) * page.pageSize; + + const [countRows] = await pool.query( + `SELECT COUNT(*) AS total + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + WHERE ${where}`, + params + ); + const total: number = countRows[0].total; + + const [rows] = await pool.query( + `SELECT pm.* + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + WHERE ${where} + ORDER BY pm.month DESC, pm.created_at DESC + LIMIT ? OFFSET ?`, + [...params, page.pageSize, offset] + ); + + return { total, records: rows as PerformanceRow[] }; +} + +/** Update only the status of a performance record. */ +export async function updateStatus(perfId: number, status: PerformanceStatus): Promise { + await pool.query( + 'UPDATE performance_month SET status = ? WHERE perf_id = ?', + [status, perfId] + ); +} + +/** Query performance records for a specific employee with optional month filter and pagination. */ +export async function findByUserId( + userId: number, + month: string | undefined, + page: PageInfo +): Promise { + const conditions: string[] = ['pm.user_id = ?']; + const params: any[] = [userId]; + + if (month) { + conditions.push('pm.month = ?'); + params.push(month); + } + + const where = conditions.join(' AND '); + const offset = (page.page - 1) * page.pageSize; + + const [countRows] = await pool.query( + `SELECT COUNT(*) AS total FROM performance_month pm WHERE ${where}`, + params + ); + const total: number = countRows[0].total; + + const [rows] = await pool.query( + `SELECT pm.* FROM performance_month pm WHERE ${where} + ORDER BY pm.month DESC, pm.created_at DESC + LIMIT ? OFFSET ?`, + [...params, page.pageSize, offset] + ); + + return { total, records: rows as PerformanceRow[] }; +} + +/** Query full detail of a performance record including items and attendance. */ +export async function findDetailByPerfId(perfId: number): Promise<{ + performance: PerformanceRow; + items: PerfItemRow[]; + attendance: AttendanceRow | null; +} | null> { + const [perfRows] = await pool.query( + 'SELECT * FROM performance_month WHERE perf_id = ? LIMIT 1', + [perfId] + ); + if (perfRows.length === 0) return null; + + const [itemRows] = await pool.query( + 'SELECT * FROM perf_item WHERE perf_id = ?', + [perfId] + ); + + const [attRows] = await pool.query( + 'SELECT * FROM attendance WHERE perf_id = ? LIMIT 1', + [perfId] + ); + + return { + performance: perfRows[0] as PerformanceRow, + items: itemRows as PerfItemRow[], + attendance: attRows.length > 0 ? (attRows[0] as AttendanceRow) : null, + }; +} + +/** Update manager review results and archive the performance record. */ +export async function updateReview(perfId: number, reviewData: ReviewData): Promise { + const conn = await pool.getConnection(); + try { + await conn.beginTransaction(); + + await conn.query( + `UPDATE performance_month + SET manager_score = ?, review_opinion = ?, total_score = ?, level = ?, + reward_punish = ?, status = 'completed', review_time = NOW() + WHERE perf_id = ?`, + [reviewData.managerScore, reviewData.reviewOpinion, reviewData.totalScore, reviewData.level, reviewData.rewardPunish, perfId] + ); + + for (const item of reviewData.itemScores) { + await conn.query( + `UPDATE perf_item SET manager_score = ?, manager_explanation = ? + WHERE perf_id = ? AND item_name = ?`, + [item.managerScore, item.managerExplanation, perfId, item.itemName] + ); + } + + await conn.commit(); + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } +} diff --git a/backend/src/dao/UserDAO.ts b/backend/src/dao/UserDAO.ts new file mode 100644 index 0000000..e056709 --- /dev/null +++ b/backend/src/dao/UserDAO.ts @@ -0,0 +1,30 @@ +import pool from '../config/database'; +import { UserRole } from '../types'; + +export interface UserRow { + user_id: number; + username: string; + password: string; + name: string; + role: UserRole; + department: string; + position: string; + manager_id: number | null; + status: 'active' | 'inactive'; +} + +export async function findByUsername(username: string): Promise { + const [rows] = await pool.query( + 'SELECT user_id, username, password, name, role, department, position, manager_id, status FROM user WHERE username = ? LIMIT 1', + [username] + ); + return rows.length > 0 ? (rows[0] as UserRow) : null; +} + +export async function findSubordinates(managerId: number): Promise { + const [rows] = await pool.query( + 'SELECT user_id, username, name, role, department, position, manager_id, status FROM user WHERE manager_id = ? AND status = ?', + [managerId, 'active'] + ); + return rows as UserRow[]; +} diff --git a/backend/src/db/init.sql b/backend/src/db/init.sql new file mode 100644 index 0000000..0c59648 --- /dev/null +++ b/backend/src/db/init.sql @@ -0,0 +1,127 @@ +-- 员工月度绩效考核系统数据库初始化脚本 + +CREATE DATABASE IF NOT EXISTS employee_performance + DEFAULT CHARACTER SET utf8mb4 + DEFAULT COLLATE utf8mb4_unicode_ci; + +USE employee_performance; + +-- 1. 用户表 +CREATE TABLE IF NOT EXISTS user ( + user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', + username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名(工号)', + password VARCHAR(255) NOT NULL COMMENT '密码(bcrypt加密)', + name VARCHAR(50) NOT NULL COMMENT '姓名', + role ENUM('employee', 'manager', 'generalManager') NOT NULL COMMENT '角色', + department VARCHAR(50) NOT NULL COMMENT '部门', + position VARCHAR(50) NOT NULL COMMENT '岗位', + manager_id INT NULL COMMENT '直属管理层ID', + status ENUM('active', 'inactive') DEFAULT 'active' COMMENT '状态', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_role (role), + INDEX idx_manager (manager_id), + FOREIGN KEY (manager_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +-- 2. 绩效主表 +CREATE TABLE IF NOT EXISTS performance_month ( + perf_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '绩效记录ID', + user_id INT NOT NULL COMMENT '员工ID', + month VARCHAR(7) NOT NULL COMMENT '考核月份(YYYY-MM)', + status ENUM('draft', 'submitted', 'under_review', 'completed', 'rejected') NOT NULL DEFAULT 'draft' COMMENT '状态', + self_score DECIMAL(5,2) NULL COMMENT '员工自评总分', + ai_score DECIMAL(5,2) NULL COMMENT 'AI评分总分', + manager_score DECIMAL(5,2) NULL COMMENT '管理层审核总分', + total_score DECIMAL(5,2) NULL COMMENT '最终总分', + level ENUM('excellent', 'qualified', 'need_motivation', 'unqualified') NULL COMMENT '绩效等级', + reward_punish VARCHAR(255) NULL COMMENT '奖惩说明', + work_summary TEXT NULL COMMENT '工作汇总', + submit_time TIMESTAMP NULL COMMENT '提交时间', + review_time TIMESTAMP NULL COMMENT '审核时间', + review_opinion TEXT NULL COMMENT '审核意见', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_user_month (user_id, month), + INDEX idx_status (status), + INDEX idx_month (month), + FOREIGN KEY (user_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='绩效主表'; + +-- 3. 绩效项明细表 +CREATE TABLE IF NOT EXISTS perf_item ( + item_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '绩效项ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + item_name VARCHAR(100) NOT NULL COMMENT '考核项名称', + item_category ENUM('business', 'comprehensive') NOT NULL COMMENT '考核项类别', + weight INT NOT NULL COMMENT '权重(分数)', + user_content TEXT NULL COMMENT '员工填写内容', + self_score DECIMAL(5,2) NULL COMMENT '员工自评分', + ai_score DECIMAL(5,2) NULL COMMENT 'AI评分', + ai_explanation TEXT NULL COMMENT 'AI评分说明', + manager_score DECIMAL(5,2) NULL COMMENT '管理层评分', + manager_explanation TEXT NULL COMMENT '管理层评分说明', + evidence_url VARCHAR(500) NULL COMMENT '佐证材料URL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='绩效项明细表'; + +-- 4. 考勤表 +CREATE TABLE IF NOT EXISTS attendance ( + attendance_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + leave_days INT DEFAULT 0 COMMENT '事假天数', + late_times INT DEFAULT 0 COMMENT '迟到次数', + absent_days INT DEFAULT 0 COMMENT '旷工天数', + lack_card_times INT DEFAULT 0 COMMENT '缺卡次数', + attendance_score DECIMAL(5,2) NULL COMMENT '考勤得分', + remark TEXT NULL COMMENT '备注', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤表'; + +-- 5. AI 结果表 +CREATE TABLE IF NOT EXISTS ai_result ( + ai_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'AI结果ID', + perf_id INT NOT NULL COMMENT '绩效记录ID', + ai_score_json TEXT NOT NULL COMMENT 'AI评分详情(JSON格式)', + ai_total_score DECIMAL(5,2) NOT NULL COMMENT 'AI总分', + problems TEXT NULL COMMENT 'AI总结的问题(JSON数组)', + suggestions TEXT NULL COMMENT 'AI改进建议(JSON数组)', + api_response TEXT NULL COMMENT 'FastGPT原始响应', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间', + UNIQUE KEY uk_perf (perf_id), + FOREIGN KEY (perf_id) REFERENCES performance_month(perf_id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI结果表'; + +-- 6. 考核规则配置表 +CREATE TABLE IF NOT EXISTS performance_rules ( + rule_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '规则ID', + rule_key VARCHAR(100) NOT NULL UNIQUE COMMENT '规则键名', + rule_value TEXT NOT NULL COMMENT '规则值(JSON格式)', + description VARCHAR(255) NULL COMMENT '规则描述', + effective_cycle VARCHAR(7) NULL COMMENT '生效周期(YYYY-MM,NULL表示立即生效)', + updated_by INT NULL COMMENT '最后修改人ID', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (updated_by) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考核规则配置表'; + +-- 7. 操作日志表 +CREATE TABLE IF NOT EXISTS operation_log ( + log_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID', + user_id INT NOT NULL COMMENT '操作人ID', + operation_type VARCHAR(50) NOT NULL COMMENT '操作类型', + target_type VARCHAR(50) NULL COMMENT '目标类型', + target_id INT NULL COMMENT '目标ID', + operation_detail TEXT NULL COMMENT '操作详情', + ip_address VARCHAR(50) NULL COMMENT 'IP地址', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_user (user_id), + INDEX idx_created (created_at), + FOREIGN KEY (user_id) REFERENCES user(user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'; diff --git a/backend/src/db/seed.sql b/backend/src/db/seed.sql new file mode 100644 index 0000000..b14686e --- /dev/null +++ b/backend/src/db/seed.sql @@ -0,0 +1,43 @@ +-- 测试数据插入脚本 +USE employee_performance; + +-- 插入测试用户(密码都是 123456,已用 bcrypt 加密) +-- bcrypt hash for '123456': $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy + +-- 1. 总经理 +INSERT INTO user (username, password, name, role, department, position, manager_id, status) +VALUES ('gm001', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '张总', 'generalManager', '管理层', '总经理', NULL, 'active') +ON DUPLICATE KEY UPDATE username=username; + +-- 2. 部门经理(技术部) +INSERT INTO user (username, password, name, role, department, position, manager_id, status) +VALUES ('mgr001', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '李经理', 'manager', '技术部', '技术经理', 1, 'active') +ON DUPLICATE KEY UPDATE username=username; + +-- 3. 部门经理(销售部) +INSERT INTO user (username, password, name, role, department, position, manager_id, status) +VALUES ('mgr002', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '王经理', 'manager', '销售部', '销售经理', 1, 'active') +ON DUPLICATE KEY UPDATE username=username; + +-- 4. 员工(技术部) +INSERT INTO user (username, password, name, role, department, position, manager_id, status) +VALUES +('emp001', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '张三', 'employee', '技术部', '前端工程师', 2, 'active'), +('emp002', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '李四', 'employee', '技术部', '后端工程师', 2, 'active'), +('emp003', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '王五', 'employee', '技术部', '测试工程师', 2, 'active') +ON DUPLICATE KEY UPDATE username=username; + +-- 5. 员工(销售部) +INSERT INTO user (username, password, name, role, department, position, manager_id, status) +VALUES +('emp004', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '赵六', 'employee', '销售部', '销售专员', 3, 'active'), +('emp005', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '孙七', 'employee', '销售部', '销售专员', 3, 'active') +ON DUPLICATE KEY UPDATE username=username; + +-- 插入默认考核规则配置 +INSERT INTO performance_rules (rule_key, rule_value, description) +VALUES +('item_weights', '{"business": 70, "comprehensive": 30}', '考核项权重配置'), +('reward_punish', '{"excellent": "按公司规定给予奖励", "qualified_80_89": "扣除当月绩效工资100元", "qualified_70_79": "扣除当月绩效工资200元", "need_motivation": "扣除当月绩效工资300元", "unqualified": "扣除当月绩效工资600元"}', '奖惩金额配置') +ON DUPLICATE KEY UPDATE rule_key=rule_key; + diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts new file mode 100644 index 0000000..74b098a --- /dev/null +++ b/backend/src/db/seed.ts @@ -0,0 +1,40 @@ +import fs from 'fs'; +import path from 'path'; +import pool from '../config/database'; + +async function runSeed() { + try { + console.log('开始执行数据库种子数据插入...'); + + const sqlFile = path.join(__dirname, 'seed.sql'); + const sql = fs.readFileSync(sqlFile, 'utf-8'); + + // 分割SQL语句(按分号分割,但要注意处理注释) + const statements = sql + .split(';') + .map(s => s.trim()) + .filter(s => s.length > 0 && !s.startsWith('--')); + + for (const statement of statements) { + if (statement.trim()) { + await pool.query(statement); + console.log('✓ 执行成功:', statement.substring(0, 50) + '...'); + } + } + + console.log('\n✅ 数据库种子数据插入完成!'); + console.log('\n测试账号信息:'); + console.log('总经理: gm001 / 123456'); + console.log('技术部经理: mgr001 / 123456'); + console.log('销售部经理: mgr002 / 123456'); + console.log('技术部员工: emp001, emp002, emp003 / 123456'); + console.log('销售部员工: emp004, emp005 / 123456'); + + process.exit(0); + } catch (error) { + console.error('❌ 数据库种子数据插入失败:', error); + process.exit(1); + } +} + +runSeed(); diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..399b1d3 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,32 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import express from 'express'; +import cors from 'cors'; +import authRouter from './routes/auth'; +import performanceRouter from './routes/performance'; +import statisticsRouter from './routes/statistics'; +import configRouter from './routes/config'; +import employeeRouter from './routes/employee'; + +const app = express(); +const PORT = process.env.PORT || 3001; + +app.use(cors()); +app.use(express.json()); + +app.get('/health', (_req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +app.use('/api/user', authRouter); +app.use('/api/performance', performanceRouter); +app.use('/api/statistics', statisticsRouter); +app.use('/api/config', configRouter); +app.use('/api/employee', employeeRouter); + +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +export default app; diff --git a/backend/src/middlewares/__tests__/authenticate.test.ts b/backend/src/middlewares/__tests__/authenticate.test.ts new file mode 100644 index 0000000..48e1db9 --- /dev/null +++ b/backend/src/middlewares/__tests__/authenticate.test.ts @@ -0,0 +1,77 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { authenticate } from '../authenticate'; +import { JWT_SECRET } from '../../config/jwt'; +import { UserInfo } from '../../types'; + +const mockUser: UserInfo = { + userId: 1, + name: '张三', + role: 'employee', + department: '研发部', + position: '工程师', +}; + +function makeReq(authHeader?: string): Partial { + return { headers: authHeader ? { authorization: authHeader } : {} } as any; +} + +function makeRes(): { res: Partial; getStatus: () => number; getBody: () => any } { + let statusCode = 200; + let body: any = null; + const res: Partial = { + status(code: number) { statusCode = code; return this as Response; }, + json(data: any) { body = data; return this as Response; }, + }; + return { res, getStatus: () => statusCode, getBody: () => body }; +} + +describe('authenticate middleware', () => { + it('valid token sets req.user and calls next', () => { + const token = jwt.sign(mockUser, JWT_SECRET, { expiresIn: '1h' }); + const req = makeReq(`Bearer ${token}`) as Request; + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + + authenticate(req, res as Response, next); + + expect(next).toHaveBeenCalled(); + expect(req.user?.userId).toBe(1); + expect(getStatus()).toBe(200); + }); + + it('missing token returns 401', () => { + const req = makeReq() as Request; + const { res, getStatus, getBody } = makeRes(); + const next = jest.fn() as NextFunction; + + authenticate(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(401); + expect(getBody().code).toBe(401); + }); + + it('invalid token returns 401', () => { + const req = makeReq('Bearer invalidtoken') as Request; + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + + authenticate(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(401); + }); + + it('expired token returns 401', () => { + const token = jwt.sign(mockUser, JWT_SECRET, { expiresIn: '-1s' }); + const req = makeReq(`Bearer ${token}`) as Request; + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + + authenticate(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(401); + }); +}); diff --git a/backend/src/middlewares/__tests__/authorize.property.test.ts b/backend/src/middlewares/__tests__/authorize.property.test.ts new file mode 100644 index 0000000..fa1bcfe --- /dev/null +++ b/backend/src/middlewares/__tests__/authorize.property.test.ts @@ -0,0 +1,158 @@ +import * as fc from 'fast-check'; +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { authenticate } from '../authenticate'; +import { authorize } from '../authorize'; +import { JWT_SECRET } from '../../config/jwt'; +import { UserInfo, UserRole } from '../../types'; + +// Feature: employee-performance-system, Property 2: 权限隔离不变量 +// For any employee token, accessing another employee's data always returns 403. + +const ROLES: UserRole[] = ['employee', 'manager', 'generalManager']; + +function makeRes(): { res: Partial; getStatus: () => number; getBody: () => any } { + let statusCode = 200; + let body: any = null; + const res: Partial = { + status(code: number) { statusCode = code; return this as Response; }, + json(data: any) { body = data; return this as Response; }, + }; + return { res, getStatus: () => statusCode, getBody: () => body }; +} + +function makeUserInfo(overrides: Partial = {}): UserInfo { + return { + userId: 1, + name: '测试用户', + role: 'employee', + department: '研发部', + position: '工程师', + ...overrides, + }; +} + +describe('Property 2: 权限隔离不变量', () => { + it('employee token is always rejected when accessing manager-only routes', () => { + fc.assert( + fc.property( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + name: fc.string({ minLength: 1, maxLength: 20 }), + department: fc.string({ minLength: 1, maxLength: 20 }), + position: fc.string({ minLength: 1, maxLength: 20 }), + }), + ({ userId, name, department, position }) => { + const employeeInfo = makeUserInfo({ userId, name, department, position, role: 'employee' }); + const token = jwt.sign(employeeInfo, JWT_SECRET, { expiresIn: '1h' }); + + const req = { headers: { authorization: `Bearer ${token}` } } as Request; + const { res: authRes } = makeRes(); + const next = jest.fn() as NextFunction; + + // First pass through authenticate + authenticate(req, authRes as Response, next); + expect(next).toHaveBeenCalled(); + expect(req.user?.role).toBe('employee'); + + // Then attempt to access manager-only route + const { res: authzRes, getStatus, getBody } = makeRes(); + const authzNext = jest.fn() as NextFunction; + authorize('manager', 'generalManager')(req, authzRes as Response, authzNext); + + expect(authzNext).not.toHaveBeenCalled(); + expect(getStatus()).toBe(403); + expect(getBody().code).toBe(403); + } + ), + { numRuns: 100 } + ); + }); + + it('employee token is always rejected when accessing generalManager-only routes', () => { + fc.assert( + fc.property( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + }), + ({ userId }) => { + const employeeInfo = makeUserInfo({ userId, role: 'employee' }); + const token = jwt.sign(employeeInfo, JWT_SECRET, { expiresIn: '1h' }); + + const req = { headers: { authorization: `Bearer ${token}` } } as Request; + const { res: authRes } = makeRes(); + const authNext = jest.fn() as NextFunction; + authenticate(req, authRes as Response, authNext); + + const { res, getStatus, getBody } = makeRes(); + const next = jest.fn() as NextFunction; + authorize('generalManager')(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(403); + expect(getBody().code).toBe(403); + } + ), + { numRuns: 100 } + ); + }); + + it('a role is always allowed access to its own permitted routes and denied from others', () => { + fc.assert( + fc.property( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + role: fc.constantFrom(...ROLES), + targetRole: fc.constantFrom(...ROLES), + }).filter(({ role, targetRole }) => role !== targetRole), + ({ userId, role, targetRole }) => { + const userInfo = makeUserInfo({ userId, role }); + const token = jwt.sign(userInfo, JWT_SECRET, { expiresIn: '1h' }); + + const req = { headers: { authorization: `Bearer ${token}` } } as Request; + const { res: authRes } = makeRes(); + const authNext = jest.fn() as NextFunction; + authenticate(req, authRes as Response, authNext); + + // Accessing a route that only allows targetRole (which differs from user's role) + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + authorize(targetRole)(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(403); + } + ), + { numRuns: 100 } + ); + }); + + it('any role is always allowed when its role is in the permitted list', () => { + fc.assert( + fc.property( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + role: fc.constantFrom(...ROLES), + }), + ({ userId, role }) => { + const userInfo = makeUserInfo({ userId, role }); + const token = jwt.sign(userInfo, JWT_SECRET, { expiresIn: '1h' }); + + const req = { headers: { authorization: `Bearer ${token}` } } as Request; + const { res: authRes } = makeRes(); + const authNext = jest.fn() as NextFunction; + authenticate(req, authRes as Response, authNext); + + // Route allows all roles + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + authorize(...ROLES)(req, res as Response, next); + + expect(next).toHaveBeenCalled(); + expect(getStatus()).toBe(200); // unchanged — no error set + } + ), + { numRuns: 100 } + ); + }); +}); diff --git a/backend/src/middlewares/__tests__/authorize.test.ts b/backend/src/middlewares/__tests__/authorize.test.ts new file mode 100644 index 0000000..1d06a06 --- /dev/null +++ b/backend/src/middlewares/__tests__/authorize.test.ts @@ -0,0 +1,65 @@ +import { Request, Response, NextFunction } from 'express'; +import { authorize } from '../authorize'; +import { UserInfo } from '../../types'; + +function makeReq(user?: UserInfo): Partial { + return { user } as any; +} + +function makeRes(): { res: Partial; getStatus: () => number; getBody: () => any } { + let statusCode = 200; + let body: any = null; + const res: Partial = { + status(code: number) { statusCode = code; return this as Response; }, + json(data: any) { body = data; return this as Response; }, + }; + return { res, getStatus: () => statusCode, getBody: () => body }; +} + +const employee: UserInfo = { userId: 1, name: '张三', role: 'employee', department: '研发部', position: '工程师' }; +const manager: UserInfo = { userId: 2, name: '李四', role: 'manager', department: '研发部', position: '经理' }; + +describe('authorize middleware', () => { + it('allowed role calls next', () => { + const req = makeReq(employee) as Request; + const { res } = makeRes(); + const next = jest.fn() as NextFunction; + + authorize('employee')(req, res as Response, next); + + expect(next).toHaveBeenCalled(); + }); + + it('disallowed role returns 403', () => { + const req = makeReq(employee) as Request; + const { res, getStatus, getBody } = makeRes(); + const next = jest.fn() as NextFunction; + + authorize('manager')(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(403); + expect(getBody().code).toBe(403); + }); + + it('multiple allowed roles — matching role calls next', () => { + const req = makeReq(manager) as Request; + const { res } = makeRes(); + const next = jest.fn() as NextFunction; + + authorize('manager', 'generalManager')(req, res as Response, next); + + expect(next).toHaveBeenCalled(); + }); + + it('no user on request returns 401', () => { + const req = makeReq(undefined) as Request; + const { res, getStatus } = makeRes(); + const next = jest.fn() as NextFunction; + + authorize('employee')(req, res as Response, next); + + expect(next).not.toHaveBeenCalled(); + expect(getStatus()).toBe(401); + }); +}); diff --git a/backend/src/middlewares/authenticate.ts b/backend/src/middlewares/authenticate.ts new file mode 100644 index 0000000..1133a55 --- /dev/null +++ b/backend/src/middlewares/authenticate.ts @@ -0,0 +1,24 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { JWT_SECRET } from '../config/jwt'; +import { UserInfo } from '../types'; + +export function authenticate(req: Request, res: Response, next: NextFunction): void { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.startsWith('Bearer ') + ? authHeader.slice(7) + : null; + + if (!token) { + res.status(401).json({ code: 401, message: '未提供访问令牌' }); + return; + } + + try { + const decoded = jwt.verify(token, JWT_SECRET) as UserInfo; + req.user = decoded; + next(); + } catch { + res.status(401).json({ code: 401, message: '访问令牌无效或已过期' }); + } +} diff --git a/backend/src/middlewares/authorize.ts b/backend/src/middlewares/authorize.ts new file mode 100644 index 0000000..0dac23e --- /dev/null +++ b/backend/src/middlewares/authorize.ts @@ -0,0 +1,20 @@ +import { Request, Response, NextFunction } from 'express'; +import { UserRole } from '../types'; + +export function authorize(...allowedRoles: UserRole[]) { + return (req: Request, res: Response, next: NextFunction): void => { + const user = req.user; + + if (!user) { + res.status(401).json({ code: 401, message: '未认证' }); + return; + } + + if (!allowedRoles.includes(user.role)) { + res.status(403).json({ code: 403, message: '权限不足' }); + return; + } + + next(); + }; +} diff --git a/backend/src/routes/__tests__/performance.property.test.ts b/backend/src/routes/__tests__/performance.property.test.ts new file mode 100644 index 0000000..9430734 --- /dev/null +++ b/backend/src/routes/__tests__/performance.property.test.ts @@ -0,0 +1,368 @@ +import * as fc from 'fast-check'; +import * as PerformanceDAO from '../../dao/PerformanceDAO'; +import * as AIResultDAO from '../../dao/AIResultDAO'; +import { UpsertPerformanceData, PerformanceRow, PerformanceStatus } from '../../dao/PerformanceDAO'; + +jest.mock('../../dao/PerformanceDAO'); +jest.mock('../../dao/AIResultDAO'); + +const mockUpsert = PerformanceDAO.upsert as jest.MockedFunction; +const mockFindByUserAndMonth = PerformanceDAO.findByUserAndMonth as jest.MockedFunction; +const mockFindByUserId = PerformanceDAO.findByUserId as jest.MockedFunction; +const mockFindDetailByPerfId = PerformanceDAO.findDetailByPerfId as jest.MockedFunction; +const mockFindAIByPerfId = AIResultDAO.findByPerfId as jest.MockedFunction; + +afterEach(() => jest.clearAllMocks()); + +// ───────────────────────────────────────────────────────────────────────────── +// Arbitraries +// ───────────────────────────────────────────────────────────────────────────── + +const monthArb = fc.tuple( + fc.integer({ min: 2020, max: 2030 }), + fc.integer({ min: 1, max: 12 }) +).map(([y, m]) => `${y}-${String(m).padStart(2, '0')}`); + +const perfItemArb = fc.record({ + item_name: fc.string({ minLength: 1, maxLength: 30 }), + item_category: fc.constantFrom<'business' | 'comprehensive'>('business', 'comprehensive'), + weight: fc.integer({ min: 1, max: 20 }), + user_content: fc.string({ minLength: 1, maxLength: 200 }), + self_score: fc.integer({ min: 0, max: 100 }), + ai_score: fc.constant(null), + ai_explanation: fc.constant(null), + manager_score: fc.constant(null), + manager_explanation: fc.constant(null), + evidence_url: fc.constant(null), +}); + +const attendanceArb = fc.record({ + leave_days: fc.integer({ min: 0, max: 5 }), + late_times: fc.integer({ min: 0, max: 5 }), + absent_days: fc.integer({ min: 0, max: 3 }), + lack_card_times: fc.integer({ min: 0, max: 5 }), + attendance_score: fc.constant(null), + remark: fc.constant(null), +}); + +const draftDataArb = fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + selfScore: fc.integer({ min: 0, max: 100 }), + workSummary: fc.string({ minLength: 1, maxLength: 500 }), + items: fc.array(perfItemArb, { minLength: 1, maxLength: 5 }), + attendance: attendanceArb, +}); + +function makePerformanceRow(overrides: Partial & { user_id: number; month: string; perf_id: number }): PerformanceRow { + return { + perf_id: overrides.perf_id, + user_id: overrides.user_id, + month: overrides.month, + status: overrides.status ?? 'draft', + self_score: overrides.self_score ?? null, + ai_score: null, + manager_score: null, + total_score: null, + level: null, + reward_punish: null, + work_summary: overrides.work_summary ?? null, + submit_time: null, + review_time: null, + review_opinion: null, + created_at: new Date(), + updated_at: new Date(), + }; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 3: 草稿暂存往返一致性 +// For any performance draft data, saving as draft then reading back should +// return data consistent with what was saved. +// Validates: Requirements 2.5 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 3: 草稿暂存往返一致性', () => { + it('draft saved then read back returns consistent userId, month, status, selfScore, and workSummary', async () => { + await fc.assert( + fc.asyncProperty( + draftDataArb, + async ({ userId, month, selfScore, workSummary, items, attendance }) => { + const perfId = Math.floor(Math.random() * 9000) + 1; + + // Simulate upsert returning a perfId + mockUpsert.mockResolvedValue(perfId); + + // Simulate reading back the saved draft + const savedRow = makePerformanceRow({ + perf_id: perfId, + user_id: userId, + month, + status: 'draft', + self_score: selfScore, + work_summary: workSummary, + }); + mockFindByUserAndMonth.mockResolvedValue(savedRow); + + // Step 1: Save draft + const upsertData: UpsertPerformanceData = { + userId, + month, + status: 'draft', + selfScore, + workSummary, + items, + attendance, + }; + const returnedPerfId = await PerformanceDAO.upsert(upsertData); + + // Step 2: Read back + const readBack = await PerformanceDAO.findByUserAndMonth(userId, month); + + // Round-trip consistency checks + expect(readBack).not.toBeNull(); + expect(readBack!.user_id).toBe(userId); + expect(readBack!.month).toBe(month); + expect(readBack!.status).toBe('draft'); + expect(readBack!.self_score).toBe(selfScore); + expect(readBack!.work_summary).toBe(workSummary); + expect(readBack!.perf_id).toBe(returnedPerfId); + } + ), + { numRuns: 100 } + ); + }); + + it('draft status is preserved (not auto-promoted to submitted)', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + }), + async ({ userId, month }) => { + const perfId = 42; + mockUpsert.mockResolvedValue(perfId); + mockFindByUserAndMonth.mockResolvedValue( + makePerformanceRow({ perf_id: perfId, user_id: userId, month, status: 'draft' }) + ); + + await PerformanceDAO.upsert({ userId, month, status: 'draft' }); + const readBack = await PerformanceDAO.findByUserAndMonth(userId, month); + + expect(readBack!.status).toBe('draft'); + } + ), + { numRuns: 100 } + ); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 4: 提交幂等性 +// For any already-submitted performance record, attempting to submit again for +// the same user and month must be rejected — the record status stays unchanged. +// Validates: Requirements 2.6 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 4: 提交幂等性', () => { + const alreadySubmittedStatuses: PerformanceStatus[] = ['submitted', 'under_review', 'completed']; + + it('re-submitting an already-submitted record is rejected for all terminal statuses', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + existingStatus: fc.constantFrom(...alreadySubmittedStatuses), + }), + async ({ userId, month, existingStatus }) => { + const perfId = 99; + + // Existing record is already in a submitted/terminal state + mockFindByUserAndMonth.mockResolvedValue( + makePerformanceRow({ perf_id: perfId, user_id: userId, month, status: existingStatus }) + ); + + // The route checks findByUserAndMonth before calling upsert + const existing = await PerformanceDAO.findByUserAndMonth(userId, month); + + // Idempotency guard: if already submitted/under_review/completed, reject + const shouldReject = + existing !== null && + (existing.status === 'submitted' || + existing.status === 'under_review' || + existing.status === 'completed'); + + expect(shouldReject).toBe(true); + + // upsert must NOT have been called (the route returns 400 before calling upsert) + expect(mockUpsert).not.toHaveBeenCalled(); + + // The existing record status must remain unchanged + expect(existing!.status).toBe(existingStatus); + } + ), + { numRuns: 100 } + ); + }); + + it('submitting when no prior record exists is always allowed', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + }), + async ({ userId, month }) => { + // No existing record + mockFindByUserAndMonth.mockResolvedValue(null); + mockUpsert.mockResolvedValue(1); + + const existing = await PerformanceDAO.findByUserAndMonth(userId, month); + + // No existing record → submission is allowed + const shouldReject = + existing !== null && + (existing.status === 'submitted' || + existing.status === 'under_review' || + existing.status === 'completed'); + + expect(shouldReject).toBe(false); + } + ), + { numRuns: 100 } + ); + }); + + it('a draft record does not block a new submission', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + }), + async ({ userId, month }) => { + // Existing record is only a draft + mockFindByUserAndMonth.mockResolvedValue( + makePerformanceRow({ perf_id: 1, user_id: userId, month, status: 'draft' }) + ); + + const existing = await PerformanceDAO.findByUserAndMonth(userId, month); + + const shouldReject = + existing !== null && + (existing.status === 'submitted' || + existing.status === 'under_review' || + existing.status === 'completed'); + + // Draft should NOT block submission + expect(shouldReject).toBe(false); + } + ), + { numRuns: 100 } + ); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 7: 绩效记录查询完整性 +// For any submitted or completed performance record, querying the employee's +// history must include that record (insert/query round-trip). +// Validates: Requirements 6.1 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 7: 绩效记录查询完整性', () => { + const queryableStatuses: PerformanceStatus[] = ['submitted', 'completed', 'under_review']; + + it('a submitted record always appears in the employee query result', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + status: fc.constantFrom(...queryableStatuses), + selfScore: fc.integer({ min: 0, max: 100 }), + }), + async ({ userId, month, status, selfScore }) => { + const perfId = Math.floor(Math.random() * 9000) + 1; + + // Simulate the record existing after submission + const submittedRow = makePerformanceRow({ + perf_id: perfId, + user_id: userId, + month, + status, + self_score: selfScore, + }); + + mockFindByUserId.mockResolvedValue({ total: 1, records: [submittedRow] }); + + // Query the employee's performance list + const result = await PerformanceDAO.findByUserId(userId, undefined, { page: 1, pageSize: 10 }); + + // The submitted record must appear in the results + expect(result.total).toBeGreaterThanOrEqual(1); + const found = result.records.find((r) => r.perf_id === perfId); + expect(found).toBeDefined(); + expect(found!.user_id).toBe(userId); + expect(found!.month).toBe(month); + expect(found!.status).toBe(status); + expect(found!.self_score).toBe(selfScore); + } + ), + { numRuns: 100 } + ); + }); + + it('querying by month filter returns only records matching that month', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + month: monthArb, + }), + async ({ userId, month }) => { + const perfId = Math.floor(Math.random() * 9000) + 1; + const row = makePerformanceRow({ perf_id: perfId, user_id: userId, month, status: 'submitted' }); + + mockFindByUserId.mockResolvedValue({ total: 1, records: [row] }); + + const result = await PerformanceDAO.findByUserId(userId, month, { page: 1, pageSize: 10 }); + + // All returned records must match the queried month + for (const rec of result.records) { + expect(rec.month).toBe(month); + } + } + ), + { numRuns: 100 } + ); + }); + + it('total count is consistent with the number of returned records', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + userId: fc.integer({ min: 1, max: 9999 }), + recordCount: fc.integer({ min: 0, max: 10 }), + }), + async ({ userId, recordCount }) => { + const records = Array.from({ length: recordCount }, (_, i) => + makePerformanceRow({ + perf_id: i + 1, + user_id: userId, + month: `2024-${String((i % 12) + 1).padStart(2, '0')}`, + status: 'submitted', + }) + ); + + mockFindByUserId.mockResolvedValue({ total: recordCount, records }); + + const result = await PerformanceDAO.findByUserId(userId, undefined, { page: 1, pageSize: 20 }); + + expect(result.total).toBe(recordCount); + expect(result.records.length).toBe(recordCount); + } + ), + { numRuns: 100 } + ); + }); +}); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..b19bf7a --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,27 @@ +import { Router, Request, Response } from 'express'; +import { login } from '../services/AuthService'; + +const router = Router(); + +// POST /api/user/login +router.post('/login', async (req: Request, res: Response) => { + console.log('收到登录请求:', req.body); + const { username, password, role } = req.body; + + if (!username || !password || !role) { + console.log('参数验证失败'); + return res.status(400).json({ code: 400, message: '用户名、密码和角色不能为空' }); + } + + try { + console.log('调用登录服务...'); + const result = await login(username, password, role); + console.log('登录成功:', result.userInfo); + return res.json({ code: 200, message: '登录成功', data: result }); + } catch (err: any) { + console.error('登录失败:', err.message); + return res.status(401).json({ code: 401, message: err.message || '登录失败' }); + } +}); + +export default router; diff --git a/backend/src/routes/config.ts b/backend/src/routes/config.ts new file mode 100644 index 0000000..275e9e0 --- /dev/null +++ b/backend/src/routes/config.ts @@ -0,0 +1,125 @@ +import { Router, Request, Response } from 'express'; +import { authenticate } from '../middlewares/authenticate'; +import { authorize } from '../middlewares/authorize'; +import { getAllRules, getRuleByKey, updateRule, updateRules } from '../services/ConfigService'; + +const router = Router(); + +router.use(authenticate); + +// ─── GET /api/config/rules ──────────────────────────────────────────────────── +// Returns all current performance evaluation rules. +// Requirements: 8.5 +router.get( + '/rules', + authorize('generalManager'), + async (_req: Request, res: Response) => { + try { + const rules = await getAllRules(); + return res.json({ code: 200, message: '查询成功', data: rules }); + } catch (err) { + console.error('[config/rules GET]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── GET /api/config/rules/:ruleKey ────────────────────────────────────────── +// Returns a single rule by its key. +// Requirements: 8.5 +router.get( + '/rules/:ruleKey', + authorize('generalManager'), + async (req: Request, res: Response) => { + const { ruleKey } = req.params; + try { + const rule = await getRuleByKey(ruleKey); + if (!rule) { + return res.status(404).json({ code: 404, message: '规则不存在' }); + } + return res.json({ code: 200, message: '查询成功', data: rule }); + } catch (err) { + console.error('[config/rules/:ruleKey GET]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── PUT /api/config/rules ──────────────────────────────────────────────────── +// Batch update multiple rules. Logs each change and applies to subsequent cycles. +// Body: { rules: Array<{ ruleKey, ruleValue, description?, effectiveCycle? }> } +// Requirements: 8.5, 8.6 +router.put( + '/rules', + authorize('generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { rules } = req.body as { rules?: unknown[] }; + + if (!Array.isArray(rules) || rules.length === 0) { + return res.status(400).json({ code: 400, message: 'rules 数组不能为空' }); + } + + // Validate each rule entry + for (const item of rules) { + const r = item as Record; + if (!r.ruleKey || typeof r.ruleKey !== 'string') { + return res.status(400).json({ code: 400, message: '每条规则必须包含有效的 ruleKey' }); + } + if (r.ruleValue === undefined) { + return res.status(400).json({ code: 400, message: `规则 ${r.ruleKey} 缺少 ruleValue` }); + } + } + + try { + const updated = await updateRules( + (rules as Array>).map((r) => ({ + ruleKey: r.ruleKey as string, + ruleValue: r.ruleValue, + description: r.description as string | undefined, + effectiveCycle: r.effectiveCycle as string | undefined, + })), + user.userId + ); + return res.json({ code: 200, message: '规则更新成功', data: updated }); + } catch (err) { + console.error('[config/rules PUT]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── PUT /api/config/rules/:ruleKey ────────────────────────────────────────── +// Update a single rule by key. Logs the change and applies to subsequent cycles. +// Body: { ruleValue, description?, effectiveCycle? } +// Requirements: 8.5, 8.6 +router.put( + '/rules/:ruleKey', + authorize('generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { ruleKey } = req.params; + const { ruleValue, description, effectiveCycle } = req.body as { + ruleValue?: unknown; + description?: string; + effectiveCycle?: string; + }; + + if (ruleValue === undefined) { + return res.status(400).json({ code: 400, message: 'ruleValue 不能为空' }); + } + + try { + const updated = await updateRule( + { ruleKey, ruleValue, description, effectiveCycle }, + user.userId + ); + return res.json({ code: 200, message: '规则更新成功', data: updated }); + } catch (err) { + console.error('[config/rules/:ruleKey PUT]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +export default router; diff --git a/backend/src/routes/employee.ts b/backend/src/routes/employee.ts new file mode 100644 index 0000000..eb02d02 --- /dev/null +++ b/backend/src/routes/employee.ts @@ -0,0 +1,129 @@ +import { Router, Request, Response } from 'express'; +import { authenticate } from '../middlewares/authenticate'; +import { authorize } from '../middlewares/authorize'; +import pool from '../config/database'; +import bcrypt from 'bcryptjs'; + +const router = Router(); +router.use(authenticate); + +// GET /api/employee/list — 获取下属员工列表(管理层) +router.get('/list', authorize('manager', 'generalManager'), async (req: Request, res: Response) => { + const user = req.user!; + try { + let rows: any[]; + if (user.role === 'generalManager') { + [rows] = await pool.query( + `SELECT user_id, username, name, department, position, status, created_at + FROM user WHERE role = 'employee' ORDER BY department, name` + ); + } else { + [rows] = await pool.query( + `SELECT user_id, username, name, department, position, status, created_at + FROM user WHERE role = 'employee' AND manager_id = ? ORDER BY name`, + [user.userId] + ); + } + return res.json({ code: 200, message: '查询成功', data: rows }); + } catch (err) { + console.error('[employee/list]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } +}); + +// POST /api/employee/create — 新建员工账号 +router.post('/create', authorize('manager', 'generalManager'), async (req: Request, res: Response) => { + const user = req.user!; + const { username, password, name, department, position } = req.body; + + if (!username || !password || !name || !department || !position) { + return res.status(400).json({ code: 400, message: '用户名、密码、姓名、部门、岗位均为必填' }); + } + + try { + // 检查用户名是否已存在 + const [existing] = await pool.query('SELECT user_id FROM user WHERE username = ?', [username]); + if (existing.length > 0) { + return res.status(400).json({ code: 400, message: '用户名已存在' }); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const managerId = user.role === 'manager' ? user.userId : null; + + const [result] = await pool.query( + `INSERT INTO user (username, password, name, role, department, position, manager_id, status) + VALUES (?, ?, ?, 'employee', ?, ?, ?, 'active')`, + [username, hashedPassword, name, department, position, managerId] + ); + + return res.json({ code: 200, message: '员工账号创建成功', data: { userId: result.insertId } }); + } catch (err) { + console.error('[employee/create]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } +}); + +// DELETE /api/employee/:userId — 删除员工账号 +router.delete('/:userId', authorize('manager', 'generalManager'), async (req: Request, res: Response) => { + const user = req.user!; + const targetId = parseInt(req.params.userId, 10); + + try { + const [rows] = await pool.query( + 'SELECT user_id, manager_id, role FROM user WHERE user_id = ?', [targetId] + ); + if (rows.length === 0) { + return res.status(404).json({ code: 404, message: '员工不存在' }); + } + if (rows[0].role !== 'employee') { + return res.status(400).json({ code: 400, message: '只能删除员工账号' }); + } + // 管理层只能删除自己的下属 + if (user.role === 'manager' && rows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + + await pool.query('DELETE FROM user WHERE user_id = ?', [targetId]); + return res.json({ code: 200, message: '员工账号已删除' }); + } catch (err: any) { + if (err.code === 'ER_ROW_IS_REFERENCED_2') { + return res.status(400).json({ code: 400, message: '该员工有绩效记录,无法直接删除,请先归档相关数据' }); + } + console.error('[employee/delete]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } +}); + +// PATCH /api/employee/:userId/status — 禁用/启用员工账号 +router.patch('/:userId/status', authorize('manager', 'generalManager'), async (req: Request, res: Response) => { + const user = req.user!; + const targetId = parseInt(req.params.userId, 10); + const { status } = req.body; // 'active' | 'inactive' + + if (status !== 'active' && status !== 'inactive') { + return res.status(400).json({ code: 400, message: 'status 必须为 active 或 inactive' }); + } + + try { + const [rows] = await pool.query( + 'SELECT user_id, manager_id, role FROM user WHERE user_id = ?', [targetId] + ); + if (rows.length === 0) { + return res.status(404).json({ code: 404, message: '员工不存在' }); + } + if (rows[0].role !== 'employee') { + return res.status(400).json({ code: 400, message: '只能操作员工账号' }); + } + if (user.role === 'manager' && rows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + + await pool.query('UPDATE user SET status = ? WHERE user_id = ?', [status, targetId]); + return res.json({ code: 200, message: status === 'inactive' ? '员工账号已禁用' : '员工账号已启用' }); + } catch (err) { + console.error('[employee/status]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } +}); + +export default router; diff --git a/backend/src/routes/performance.ts b/backend/src/routes/performance.ts new file mode 100644 index 0000000..784769d --- /dev/null +++ b/backend/src/routes/performance.ts @@ -0,0 +1,801 @@ +import { Router, Request, Response } from 'express'; +import { authenticate } from '../middlewares/authenticate'; +import { authorize } from '../middlewares/authorize'; +import * as PerformanceDAO from '../dao/PerformanceDAO'; +import { calculateAttendanceScore, calculateLevelAndReward } from '../services/CalculationService'; +import { exportPerformanceExcel } from '../services/ExportService'; + +const router = Router(); + +// All performance routes require authentication +router.use(authenticate); + +// ─── 6.1 POST /api/performance/submit ──────────────────────────────────────── +// Supports draft (暂存) and submit (提交) states. +// Writes performance_month, perf_item, attendance in a transaction. +// On submit, triggers async AI evaluation (non-blocking). +router.post( + '/submit', + authorize('employee'), + async (req: Request, res: Response) => { + const user = req.user!; + const { month, status, selfScore, workSummary, items, performanceItems, attendance } = req.body; + + if (!month || !status) { + return res.status(400).json({ code: 400, message: '月份和状态不能为空' }); + } + if (status !== 'draft' && status !== 'submitted') { + return res.status(400).json({ code: 400, message: '状态值无效,必须为 draft 或 submitted' }); + } + + // If submitting, check for existing submitted/completed record (idempotency guard) + if (status === 'submitted') { + const existing = await PerformanceDAO.findByUserAndMonth(user.userId, month); + if (existing && (existing.status === 'submitted' || existing.status === 'completed' || existing.status === 'under_review')) { + return res.status(400).json({ code: 400, message: '该月份绩效已提交,不可重复提交' }); + } + } + + // Calculate attendance score if attendance data provided + let attendanceData: PerformanceDAO.AttendanceRow | undefined; + if (attendance) { + const score = calculateAttendanceScore({ + leaveDays: attendance.leave ?? attendance.leave_days ?? 0, + lateTimes: attendance.late ?? attendance.late_times ?? 0, + lackCardTimes: attendance.lackCard ?? attendance.lack_card_times ?? 0, + }); + attendanceData = { + perf_id: 0, // will be set by DAO + leave_days: attendance.leave ?? attendance.leave_days ?? 0, + late_times: attendance.late ?? attendance.late_times ?? 0, + absent_days: attendance.absent ?? attendance.absent_days ?? 0, + lack_card_times: attendance.lackCard ?? attendance.lack_card_times ?? 0, + attendance_score: score, + remark: attendance.remark ?? null, + }; + } + + // Convert performanceItems to items format if needed + const itemsData = performanceItems || items; + const formattedItems = itemsData?.map((item: any) => ({ + item_name: item.itemName || item.item_name, + item_category: item.itemCategory || item.item_category || 'business', + weight: item.weight, + user_content: item.userContent || item.user_content, + self_score: item.selfScore || item.self_score, + evidence_url: item.evidence || item.evidence_url, + })); + + // 计算自评总分:各项 self_score * weight 加权平均 + let calculatedSelfScore: number | undefined; + if (formattedItems && formattedItems.length > 0) { + let weightedSum = 0; + let totalWeight = 0; + for (const item of formattedItems) { + if (item.self_score != null && item.weight != null) { + weightedSum += Number(item.self_score) * Number(item.weight); + totalWeight += Number(item.weight); + } + } + if (totalWeight > 0) { + calculatedSelfScore = parseFloat((weightedSum / totalWeight).toFixed(2)); + } + } + + try { + const perfId = await PerformanceDAO.upsert({ + userId: user.userId, + month, + status, + selfScore: calculatedSelfScore, + workSummary, + items: formattedItems, + attendance: attendanceData, + }); + + // Trigger async AI evaluation on submit (non-blocking) + if (status === 'submitted') { + triggerAIEvaluation(perfId).catch((err) => { + console.error(`[AI] Evaluation failed for perfId=${perfId}:`, err); + }); + } + + return res.json({ code: 200, message: status === 'draft' ? '暂存成功' : '提交成功', data: { perfId } }); + } catch (err: any) { + // Duplicate key = already submitted + if (err.code === 'ER_DUP_ENTRY') { + return res.status(400).json({ code: 400, message: '该月份绩效已存在' }); + } + console.error('[submit]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 6.2 GET /api/performance/employee/get ─────────────────────────────────── +// Returns the authenticated employee's performance records. +// Supports month filter and pagination. Includes AI result, attendance, review opinion. +router.get( + '/employee/get', + authorize('employee'), + async (req: Request, res: Response) => { + const user = req.user!; + const { month, page = '1', pageSize = '10', perfId } = req.query as Record; + + // 如果提供了 perfId,返回单条记录详情 + if (perfId) { + try { + const { findDetailByPerfId } = await import('../dao/PerformanceDAO'); + const { findByPerfId: findAIByPerfId } = await import('../dao/AIResultDAO'); + + const detail = await findDetailByPerfId(parseInt(perfId, 10)); + if (!detail || detail.performance.user_id !== user.userId) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + const aiResult = await findAIByPerfId(parseInt(perfId, 10)); + const rec = detail.performance; + + // 转换为驼峰格式,确保数值字段为 number 类型 + const result = { + perfId: rec.perf_id, + userId: rec.user_id, + month: rec.month, + status: rec.status, + selfScore: rec.self_score != null ? Number(rec.self_score) : undefined, + aiScore: rec.ai_score != null ? Number(rec.ai_score) : undefined, + managerScore: rec.manager_score != null ? Number(rec.manager_score) : undefined, + totalScore: rec.total_score != null ? Number(rec.total_score) : undefined, + level: rec.level, + rewardPunish: rec.reward_punish, + workSummary: rec.work_summary, + submitTime: rec.submit_time, + reviewTime: rec.review_time, + reviewOpinion: rec.review_opinion, + performanceItems: detail.items?.map(item => ({ + itemId: item.item_id, + perfId: item.perf_id, + itemName: item.item_name, + itemCategory: item.item_category, + weight: Number(item.weight), + userContent: item.user_content, + selfScore: item.self_score != null ? Number(item.self_score) : undefined, + aiScore: item.ai_score != null ? Number(item.ai_score) : undefined, + aiExplanation: item.ai_explanation, + managerScore: item.manager_score != null ? Number(item.manager_score) : undefined, + managerExplanation: item.manager_explanation, + evidenceUrl: item.evidence_url, + })) ?? [], + attendance: detail.attendance ? { + leave: Number(detail.attendance.leave_days), + late: Number(detail.attendance.late_times), + absent: Number(detail.attendance.absent_days), + lackCard: Number(detail.attendance.lack_card_times), + attendanceScore: detail.attendance.attendance_score != null ? Number(detail.attendance.attendance_score) : undefined, + remark: detail.attendance.remark, + } : null, + aiResult: aiResult ? { + aiId: aiResult.aiId, + perfId: aiResult.perfId, + aiScoreDetail: aiResult.aiScoreDetail, + aiTotalScore: aiResult.aiTotalScore, + aiProblems: aiResult.aiProblems, + aiSuggestions: aiResult.aiSuggestions, + createTime: aiResult.createTime, + } : null, + }; + + return res.json({ + code: 200, + message: '查询成功', + data: result, + }); + } catch (err) { + console.error('[employee/get detail]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } + + // 否则返回列表 + const pageInfo: PerformanceDAO.PageInfo = { + page: Math.max(1, parseInt(page, 10) || 1), + pageSize: Math.min(100, Math.max(1, parseInt(pageSize, 10) || 10)), + }; + + try { + const result = await PerformanceDAO.findByUserId(user.userId, month, pageInfo); + + // Enrich each record with attendance and AI result + const { findDetailByPerfId } = await import('../dao/PerformanceDAO'); + const { findByPerfId: findAIByPerfId } = await import('../dao/AIResultDAO'); + + const enriched = await Promise.all( + result.records.map(async (rec) => { + const detail = await findDetailByPerfId(rec.perf_id); + const aiResult = await findAIByPerfId(rec.perf_id); + + // 转换为驼峰格式,确保数值字段为 number 类型 + return { + perfId: rec.perf_id, + userId: rec.user_id, + month: rec.month, + status: rec.status, + selfScore: rec.self_score != null ? Number(rec.self_score) : undefined, + aiScore: rec.ai_score != null ? Number(rec.ai_score) : undefined, + managerScore: rec.manager_score != null ? Number(rec.manager_score) : undefined, + totalScore: rec.total_score != null ? Number(rec.total_score) : undefined, + level: rec.level, + rewardPunish: rec.reward_punish, + workSummary: rec.work_summary, + submitTime: rec.submit_time, + reviewTime: rec.review_time, + reviewOpinion: rec.review_opinion, + performanceItems: detail?.items?.map(item => ({ + itemId: item.item_id, + perfId: item.perf_id, + itemName: item.item_name, + itemCategory: item.item_category, + weight: Number(item.weight), + userContent: item.user_content, + selfScore: item.self_score != null ? Number(item.self_score) : undefined, + aiScore: item.ai_score != null ? Number(item.ai_score) : undefined, + aiExplanation: item.ai_explanation, + managerScore: item.manager_score != null ? Number(item.manager_score) : undefined, + managerExplanation: item.manager_explanation, + evidenceUrl: item.evidence_url, + })) ?? [], + attendance: detail?.attendance ? { + leave: Number(detail.attendance.leave_days), + late: Number(detail.attendance.late_times), + absent: Number(detail.attendance.absent_days), + lackCard: Number(detail.attendance.lack_card_times), + attendanceScore: detail.attendance.attendance_score != null ? Number(detail.attendance.attendance_score) : undefined, + remark: detail.attendance.remark, + } : null, + aiResult: aiResult ? { + aiId: aiResult.aiId, + perfId: aiResult.perfId, + aiScoreDetail: aiResult.aiScoreDetail, + aiTotalScore: aiResult.aiTotalScore, + aiProblems: aiResult.aiProblems, + aiSuggestions: aiResult.aiSuggestions, + createTime: aiResult.createTime, + } : null, + }; + }) + ); + + return res.json({ + code: 200, + message: '查询成功', + data: { total: result.total, records: enriched }, + }); + } catch (err) { + console.error('[employee/get]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 6.3 POST /api/performance/request-modification ────────────────────────── +// Employee requests modification of a submitted performance record. +// Records the reason and notifies the manager (logged for now). +router.post( + '/request-modification', + authorize('employee'), + async (req: Request, res: Response) => { + const user = req.user!; + const { perfId, reason } = req.body; + + if (!perfId || !reason) { + return res.status(400).json({ code: 400, message: '绩效ID和申请原因不能为空' }); + } + + try { + const detail = await PerformanceDAO.findDetailByPerfId(Number(perfId)); + if (!detail) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + // Verify ownership + if (detail.performance.user_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足' }); + } + + // Only submitted records can request modification + if (detail.performance.status !== 'submitted' && detail.performance.status !== 'under_review') { + return res.status(400).json({ code: 400, message: '当前状态不允许申请修改' }); + } + + // Record the modification request in operation_log + const pool = (await import('../config/database')).default; + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, target_id, operation_detail) + VALUES (?, 'request_modification', 'performance', ?, ?)`, + [user.userId, perfId, JSON.stringify({ reason })] + ); + + // Notify manager: in a real system this would send a notification; + // here we log it as a manager-targeted operation log entry. + console.log(`[修改申请] 员工 ${user.name}(${user.userId}) 申请修改绩效 ${perfId},原因:${reason}`); + + return res.json({ code: 200, message: '修改申请已提交,等待管理层审批' }); + } catch (err) { + console.error('[request-modification]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 8.1 GET /api/performance/manager/list ─────────────────────────────────── +// Returns subordinates' performance list with optional filters and pagination. +// Supports filtering by month, department, employee name, and status. +// Requirements: 4.1, 7.1, 7.2 +router.get( + '/manager/list', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { + month, + department, + employeeName, + status, + page = '1', + pageSize = '10', + } = req.query as Record; + + const pageInfo: PerformanceDAO.PageInfo = { + page: Math.max(1, parseInt(page, 10) || 1), + pageSize: Math.min(100, Math.max(1, parseInt(pageSize, 10) || 10)), + }; + + const filters: PerformanceDAO.PerformanceFilter = { + month: month || undefined, + department: department || undefined, + employeeName: employeeName || undefined, + status: (status as PerformanceDAO.PerformanceStatus) || undefined, + }; + + try { + const result = await PerformanceDAO.findByManagerId(user.userId, filters, pageInfo); + + // Enrich each record with employee info + const pool = (await import('../config/database')).default; + const enriched = await Promise.all( + result.records.map(async (rec) => { + const [userRows] = await pool.query( + 'SELECT name, department, position FROM user WHERE user_id = ? LIMIT 1', + [rec.user_id] + ); + return { + perfId: rec.perf_id, + userId: rec.user_id, + month: rec.month, + status: rec.status, + selfScore: rec.self_score != null ? Number(rec.self_score) : null, + aiScore: rec.ai_score != null ? Number(rec.ai_score) : null, + managerScore: rec.manager_score != null ? Number(rec.manager_score) : null, + totalScore: rec.total_score != null ? Number(rec.total_score) : null, + level: rec.level, + submitTime: rec.submit_time, + reviewTime: rec.review_time, + // 员工信息 + userName: userRows[0]?.name ?? null, + userDepartment: userRows[0]?.department ?? null, + userPosition: userRows[0]?.position ?? null, + }; + }) + ); + + return res.json({ + code: 200, + message: '查询成功', + data: { total: result.total, records: enriched }, + }); + } catch (err) { + console.error('[manager/list]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── GET /api/performance/manager/detail/:perfId ───────────────────────────── +// Returns full detail of a subordinate's performance record for manager review. +router.get( + '/manager/detail/:perfId', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const perfId = parseInt(req.params.perfId, 10); + + try { + const detail = await PerformanceDAO.findDetailByPerfId(perfId); + if (!detail) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + // Verify subordinate relationship (generalManager can view all) + if (user.role !== 'generalManager') { + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT manager_id, name, department, position FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + if (!empRows[0] || empRows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足' }); + } + } + + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT name, department, position FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + + const { findByPerfId: findAIByPerfId } = await import('../dao/AIResultDAO'); + const aiResult = await findAIByPerfId(perfId); + const rec = detail.performance; + + const result = { + perfId: rec.perf_id, + userId: rec.user_id, + month: rec.month, + status: rec.status, + selfScore: rec.self_score != null ? Number(rec.self_score) : null, + aiScore: rec.ai_score != null ? Number(rec.ai_score) : null, + managerScore: rec.manager_score != null ? Number(rec.manager_score) : null, + totalScore: rec.total_score != null ? Number(rec.total_score) : null, + level: rec.level, + rewardPunish: rec.reward_punish, + workSummary: rec.work_summary, + submitTime: rec.submit_time, + reviewTime: rec.review_time, + reviewOpinion: rec.review_opinion, + // 员工信息 + userName: empRows[0]?.name ?? null, + userDepartment: empRows[0]?.department ?? null, + userPosition: empRows[0]?.position ?? null, + performanceItems: detail.items?.map(item => ({ + itemId: item.item_id, + itemName: item.item_name, + itemCategory: item.item_category, + weight: Number(item.weight), + userContent: item.user_content, + selfScore: item.self_score != null ? Number(item.self_score) : null, + aiScore: item.ai_score != null ? Number(item.ai_score) : null, + aiExplanation: item.ai_explanation, + managerScore: item.manager_score != null ? Number(item.manager_score) : null, + managerExplanation: item.manager_explanation, + evidenceUrl: item.evidence_url, + })) ?? [], + attendance: detail.attendance ? { + leave: Number(detail.attendance.leave_days), + late: Number(detail.attendance.late_times), + absent: Number(detail.attendance.absent_days), + lackCard: Number(detail.attendance.lack_card_times), + attendanceScore: detail.attendance.attendance_score != null ? Number(detail.attendance.attendance_score) : null, + remark: detail.attendance.remark, + } : null, + aiResult: aiResult ? { + aiTotalScore: aiResult.aiTotalScore, + aiProblems: aiResult.aiProblems, + aiSuggestions: aiResult.aiSuggestions, + } : null, + }; + + return res.json({ code: 200, message: '查询成功', data: result }); + } catch (err) { + console.error('[manager/detail]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 8.2 POST /api/performance/manager/review ──────────────────────────────── +// Manager reviews a performance record: updates item scores, calculates final +// total score, level, reward/punishment, and archives the record (status → completed). +// Requirements: 4.3, 4.4, 4.5, 4.7 +router.post( + '/manager/review', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { perfId, reviewOpinion, itemScores } = req.body; + + if (!perfId || !reviewOpinion || !Array.isArray(itemScores)) { + return res.status(400).json({ code: 400, message: '绩效ID、审核意见和考核项评分不能为空' }); + } + + try { + const detail = await PerformanceDAO.findDetailByPerfId(Number(perfId)); + if (!detail) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + // Verify the performance belongs to a subordinate of this manager + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT manager_id FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + if (!empRows[0] || empRows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + + // Only submitted/under_review records can be reviewed + if ( + detail.performance.status !== 'submitted' && + detail.performance.status !== 'under_review' + ) { + return res.status(400).json({ code: 400, message: '当前状态不允许审核' }); + } + + // Build a map of item scores provided by manager + const scoreMap = new Map(); + for (const s of itemScores) { + if (typeof s.itemName !== 'string' || typeof s.managerScore !== 'number') { + return res.status(400).json({ code: 400, message: '考核项评分格式无效' }); + } + scoreMap.set(s.itemName, { + managerScore: s.managerScore, + managerExplanation: s.managerExplanation ?? '', + }); + } + + // Calculate manager total score: weighted average of item manager scores + let weightedSum = 0; + let totalWeight = 0; + for (const item of detail.items) { + const scored = scoreMap.get(item.item_name); + const score = scored ? scored.managerScore : (item.ai_score ?? item.self_score ?? 0); + weightedSum += score * item.weight; + totalWeight += item.weight; + } + const managerScore = totalWeight > 0 ? weightedSum / totalWeight : 0; + + // Get attendance score + const attendanceScore = detail.attendance?.attendance_score ?? 0; + + // Final total score = manager score (already weighted across items) + attendance contribution + // The items already include attendance-category items; use managerScore directly as total + const totalScore = Math.min(100, Math.max(0, parseFloat(managerScore.toFixed(2)))); + + const { level, rewardPunish } = calculateLevelAndReward(totalScore); + + const reviewData: PerformanceDAO.ReviewData = { + managerScore: parseFloat(managerScore.toFixed(2)), + reviewOpinion, + totalScore, + level, + rewardPunish, + itemScores: detail.items.map((item) => { + const scored = scoreMap.get(item.item_name); + return { + itemName: item.item_name, + managerScore: scored ? scored.managerScore : (item.ai_score ?? item.self_score ?? 0), + managerExplanation: scored ? scored.managerExplanation : '', + }; + }), + }; + + await PerformanceDAO.updateReview(Number(perfId), reviewData); + + // Log the review action + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, target_id, operation_detail) + VALUES (?, 'review_performance', 'performance', ?, ?)`, + [user.userId, perfId, JSON.stringify({ totalScore, level, rewardPunish })] + ); + + return res.json({ + code: 200, + message: '审核完成', + data: { totalScore, level, rewardPunish }, + }); + } catch (err) { + console.error('[manager/review]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 8.3 POST /api/performance/manager/reject ──────────────────────────────── +// Manager rejects a performance record, recording the reason and setting status to rejected. +// Requirements: 4.6 +router.post( + '/manager/reject', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { perfId, reason } = req.body; + + if (!perfId || !reason) { + return res.status(400).json({ code: 400, message: '绩效ID和驳回原因不能为空' }); + } + + try { + const detail = await PerformanceDAO.findDetailByPerfId(Number(perfId)); + if (!detail) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + // Verify subordinate relationship + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT manager_id FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + if (!empRows[0] || empRows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + + if ( + detail.performance.status !== 'submitted' && + detail.performance.status !== 'under_review' + ) { + return res.status(400).json({ code: 400, message: '当前状态不允许驳回' }); + } + + // Update status to rejected and record the reason in review_opinion + await pool.query( + `UPDATE performance_month + SET status = 'rejected', review_opinion = ?, review_time = NOW() + WHERE perf_id = ?`, + [reason, perfId] + ); + + // Log the rejection + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, target_id, operation_detail) + VALUES (?, 'reject_performance', 'performance', ?, ?)`, + [user.userId, perfId, JSON.stringify({ reason })] + ); + + console.log(`[驳回] 管理层 ${user.name}(${user.userId}) 驳回绩效 ${perfId},原因:${reason}`); + + return res.json({ code: 200, message: '驳回成功,已通知员工' }); + } catch (err) { + console.error('[manager/reject]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 8.4 POST /api/performance/manager/approve-modification ────────────────── +// Manager approves or rejects an employee's modification request. +// Approve: unlocks the performance record (status → draft). +// Reject: notifies employee (logged). +// Requirements: 12.3, 12.4 +router.post( + '/manager/approve-modification', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { perfId, approved, rejectReason } = req.body; + + if (!perfId || typeof approved !== 'boolean') { + return res.status(400).json({ code: 400, message: '绩效ID和审批结果不能为空' }); + } + if (!approved && !rejectReason) { + return res.status(400).json({ code: 400, message: '拒绝时必须填写拒绝原因' }); + } + + try { + const detail = await PerformanceDAO.findDetailByPerfId(Number(perfId)); + if (!detail) { + return res.status(404).json({ code: 404, message: '绩效记录不存在' }); + } + + // Verify subordinate relationship + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT manager_id, name FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + if (!empRows[0] || empRows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + + if (approved) { + // Unlock: reset status to draft so employee can re-edit + await pool.query( + `UPDATE performance_month SET status = 'draft' WHERE perf_id = ?`, + [perfId] + ); + + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, target_id, operation_detail) + VALUES (?, 'approve_modification', 'performance', ?, ?)`, + [user.userId, perfId, JSON.stringify({ approved: true })] + ); + + console.log(`[修改审批] 管理层 ${user.name}(${user.userId}) 同意员工 ${empRows[0].name} 修改绩效 ${perfId}`); + return res.json({ code: 200, message: '已同意修改申请,绩效表单已解锁' }); + } else { + // Reject: log the rejection reason as notification to employee + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, target_id, operation_detail) + VALUES (?, 'reject_modification', 'performance', ?, ?)`, + [user.userId, perfId, JSON.stringify({ approved: false, rejectReason })] + ); + + console.log(`[修改审批] 管理层 ${user.name}(${user.userId}) 拒绝员工 ${empRows[0].name} 修改绩效 ${perfId},原因:${rejectReason}`); + return res.json({ code: 200, message: '已拒绝修改申请,已通知员工' }); + } + } catch (err) { + console.error('[manager/approve-modification]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 10.3 GET /api/performance/export ──────────────────────────────────────── +// Exports performance records as an Excel file. +// Query params: +// - userId: export a single employee's history (employee self or manager/GM) +// - month: restrict to a specific month +// - scope: 'team' (manager's subordinates) | 'company' (GM, all employees) +// Requirements: 7.3, 7.6, 8.4 +router.get( + '/export', + authorize('employee', 'manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { userId, month, scope } = req.query as Record; + + try { + let filter: Parameters[0] = {}; + + if (user.role === 'employee') { + // Employees can only export their own data + filter.userId = user.userId; + if (month) filter.month = month; + } else if (user.role === 'manager') { + if (userId) { + // Export a specific subordinate's history — verify relationship + const pool = (await import('../config/database')).default; + const [empRows] = await pool.query( + 'SELECT manager_id FROM user WHERE user_id = ? LIMIT 1', + [Number(userId)] + ); + if (!empRows[0] || empRows[0].manager_id !== user.userId) { + return res.status(403).json({ code: 403, message: '权限不足,该员工不是您的下属' }); + } + filter.userId = Number(userId); + } else { + // Export entire team + filter.managerId = user.userId; + } + if (month) filter.month = month; + } else if (user.role === 'generalManager') { + if (userId) { + filter.userId = Number(userId); + } else if (scope === 'company') { + filter.allEmployees = true; + } else { + filter.allEmployees = true; + } + if (month) filter.month = month; + } + + const buffer = await exportPerformanceExcel(filter); + + const filename = encodeURIComponent(`绩效数据_${month ?? '全部'}.xlsx`); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${filename}`); + return res.send(buffer); + } catch (err) { + console.error('[performance/export]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── Internal helper ────────────────────────────────────────────────────────── +async function triggerAIEvaluation(perfId: number): Promise { + const { evaluatePerformance } = await import('../services/AIEvaluationService'); + await evaluatePerformance(perfId); +} + +export default router; diff --git a/backend/src/routes/statistics.ts b/backend/src/routes/statistics.ts new file mode 100644 index 0000000..93de186 --- /dev/null +++ b/backend/src/routes/statistics.ts @@ -0,0 +1,58 @@ +import { Router, Request, Response } from 'express'; +import { authenticate } from '../middlewares/authenticate'; +import { authorize } from '../middlewares/authorize'; +import { getTeamStatistics, getCompanyStatistics } from '../services/StatisticsService'; + +const router = Router(); + +router.use(authenticate); + +// ─── 10.1 GET /api/statistics/team ─────────────────────────────────────────── +// Returns team statistics for the authenticated manager's subordinates. +// Includes average score, count and rate per performance level. +// Requirements: 7.4, 7.5 +router.get( + '/team', + authorize('manager', 'generalManager'), + async (req: Request, res: Response) => { + const user = req.user!; + const { month } = req.query as Record; + + if (!month) { + return res.status(400).json({ code: 400, message: '月份参数不能为空' }); + } + + try { + const stats = await getTeamStatistics(user.userId, month); + return res.json({ code: 200, message: '查询成功', data: stats }); + } catch (err) { + console.error('[statistics/team]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +// ─── 10.2 GET /api/statistics/company ──────────────────────────────────────── +// Returns company-wide statistics broken down by department, position, and level. +// Requirements: 8.1, 8.2 +router.get( + '/company', + authorize('generalManager'), + async (req: Request, res: Response) => { + const { month } = req.query as Record; + + if (!month) { + return res.status(400).json({ code: 400, message: '月份参数不能为空' }); + } + + try { + const stats = await getCompanyStatistics(month); + return res.json({ code: 200, message: '查询成功', data: stats }); + } catch (err) { + console.error('[statistics/company]', err); + return res.status(500).json({ code: 500, message: '服务器内部错误' }); + } + } +); + +export default router; diff --git a/backend/src/services/AIEvaluationService.ts b/backend/src/services/AIEvaluationService.ts new file mode 100644 index 0000000..e3c1acb --- /dev/null +++ b/backend/src/services/AIEvaluationService.ts @@ -0,0 +1,377 @@ +import axios from 'axios'; +import * as PerformanceDAO from '../dao/PerformanceDAO'; +import * as AIResultDAO from '../dao/AIResultDAO'; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export interface AIScoreData { + aiScoreDetail: AIResultDAO.AIScoreItem[]; + aiTotalScore: number; + aiProblems: string[]; + aiSuggestions: string[]; +} + +// ─── Prompt Builder ─────────────────────────────────────────────────────────── + +/** + * Build the prompt sent to FastGPT. + * Includes employee position, month, performance items, attendance, and scoring rules. + * Requirements: 13.2 + */ +export function buildPrompt( + performance: PerformanceDAO.PerformanceRow, + items: PerformanceDAO.PerfItemRow[], + attendance: PerformanceDAO.AttendanceRow | null, + position: string +): string { + const itemsText = items + .map( + (item, idx) => + `${idx + 1}. 【${item.item_name}】(${item.item_category === 'business' ? '业务素质' : '综合素质'},权重${item.weight}分)\n` + + ` 员工填写内容:${item.user_content ?? '(未填写)'}\n` + + ` 员工自评分:${item.self_score ?? '(未填写)'}` + ) + .join('\n'); + + const attendanceText = attendance + ? `事假${attendance.leave_days}天,迟到${attendance.late_times}次,缺卡${attendance.lack_card_times}次,旷工${attendance.absent_days}天` + : '无考勤数据'; + + return `你是一名专业的HR绩效评估专家,请根据以下员工绩效填报内容进行客观评分。 + +## 员工信息 +- 岗位:${position} +- 考核月份:${performance.month} + +## 考核项目及填报内容 +${itemsText} + +## 考勤情况 +${attendanceText} + +## 工作汇总 +${performance.work_summary ?? '(未填写)'} + +## 评分规则 +- 业务素质考评占总分70%,综合素质考评占总分30% +- 每个考核项按0-100分评分,最终加权计算总分 +- 请根据员工填写内容的质量、完整性和实际工作表现进行评分 + +## 输出要求 +请严格按照以下JSON格式输出,不要包含任何其他内容: +{ + "ai_score_detail": [ + { + "itemName": "考核项名称", + "weight": 权重分值, + "aiScore": 评分(0-100), + "scoreExplanation": "评分说明" + } + ], + "ai_total_score": 加权总分(0-100), + "ai_problems": ["问题1", "问题2", "问题3"], + "ai_suggestions": ["建议1", "建议2", "建议3"] +} + +注意: +- ai_problems 和 ai_suggestions 各需要3到5条 +- ai_total_score 为各项加权分数之和,业务素质项权重之和为70,综合素质项权重之和为30 +- 所有字段必须存在且格式正确`; +} + +// ─── Response Parser ────────────────────────────────────────────────────────── + +/** + * Parse and validate the AI JSON response. + * Extracts ai_score_detail, ai_total_score, ai_problems, ai_suggestions. + * Throws on invalid format, recording the raw response. + * Requirements: 3.2, 3.3, 3.4, 13.3, 13.4 + */ +export function parseAIResponse(rawResponse: string): AIScoreData { + let parsed: any; + + // Extract JSON from the response (model may wrap it in markdown code blocks) + const jsonMatch = rawResponse.match(/```(?:json)?\s*([\s\S]*?)```/) || + rawResponse.match(/(\{[\s\S]*\})/); + + const jsonStr = jsonMatch ? jsonMatch[1].trim() : rawResponse.trim(); + + try { + parsed = JSON.parse(jsonStr); + } catch (e) { + throw new Error(`AI响应JSON解析失败,原始响应:${rawResponse}`); + } + + // Validate required fields + if (!Array.isArray(parsed.ai_score_detail)) { + throw new Error(`AI响应缺少 ai_score_detail 字段,原始响应:${rawResponse}`); + } + if (typeof parsed.ai_total_score !== 'number') { + throw new Error(`AI响应缺少有效的 ai_total_score 字段,原始响应:${rawResponse}`); + } + if (!Array.isArray(parsed.ai_problems) || parsed.ai_problems.length < 1) { + throw new Error(`AI响应缺少 ai_problems 字段,原始响应:${rawResponse}`); + } + if (!Array.isArray(parsed.ai_suggestions) || parsed.ai_suggestions.length < 1) { + throw new Error(`AI响应缺少 ai_suggestions 字段,原始响应:${rawResponse}`); + } + + // Validate and map score detail items + const aiScoreDetail: AIResultDAO.AIScoreItem[] = parsed.ai_score_detail.map((item: any, idx: number) => { + if (typeof item.itemName !== 'string' || typeof item.weight !== 'number' || + typeof item.aiScore !== 'number' || typeof item.scoreExplanation !== 'string') { + throw new Error(`AI响应 ai_score_detail[${idx}] 格式无效,原始响应:${rawResponse}`); + } + return { + itemName: item.itemName, + weight: item.weight, + aiScore: item.aiScore, + scoreExplanation: item.scoreExplanation, + }; + }); + + return { + aiScoreDetail, + aiTotalScore: parsed.ai_total_score, + aiProblems: parsed.ai_problems as string[], + aiSuggestions: parsed.ai_suggestions as string[], + }; +} + +// ─── FastGPT API Call ───────────────────────────────────────────────────────── + +const FASTGPT_API_URL = process.env.FASTGPT_API_URL || 'https://api.fastgpt.in/api/v1/chat/completions'; +const FASTGPT_API_KEY = process.env.FASTGPT_API_KEY || ''; +const AI_TIMEOUT_MS = 60_000; // 60秒超时 + +// 读取系统代理配置(支持 HTTP_PROXY / HTTPS_PROXY 环境变量) +function getProxyConfig() { + const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.https_proxy || process.env.http_proxy; + if (proxyUrl) { + try { + const url = new URL(proxyUrl); + return { + host: url.hostname, + port: parseInt(url.port, 10), + protocol: url.protocol.replace(':', '') as 'http' | 'https', + }; + } catch { + return undefined; + } + } + return undefined; +} + +/** + * Call FastGPT workflow API with structured variables. + * FastGPT workflow expects: variables (position, month) + userChatInput (绩效内容) + */ +async function callFastGPT( + position: string, + month: string, + contentText: string +): Promise { + console.log(`[AI] 调用 FastGPT API: ${FASTGPT_API_URL}`); + + const proxyConfig = getProxyConfig(); + if (proxyConfig) { + console.log(`[AI] 使用代理: ${proxyConfig.host}:${proxyConfig.port}`); + } + + const response = await axios.post( + FASTGPT_API_URL, + { + messages: [{ role: 'user', content: contentText }], + variables: { + zslh34AG: position, // 员工岗位 + month: month, // 考核月份 + }, + stream: false, + }, + { + headers: { + Authorization: `Bearer ${FASTGPT_API_KEY}`, + 'Content-Type': 'application/json', + }, + timeout: AI_TIMEOUT_MS, + proxy: proxyConfig, + } + ); + + console.log(`[AI] FastGPT 响应状态: ${response.status}`); + + // FastGPT workflow 返回的最终结果在 choices[0].message.content 里 + // 内容是 system_rawResponse 对象(JSON字符串或对象) + const content = response.data?.choices?.[0]?.message?.content; + if (!content) { + throw new Error(`FastGPT API 返回内容为空,响应: ${JSON.stringify(response.data).substring(0, 300)}`); + } + + console.log(`[AI] FastGPT 原始返回:`, typeof content === 'string' ? content.substring(0, 300) : JSON.stringify(content).substring(0, 300)); + + // content 可能是带 markdown 代码块的字符串,或纯 JSON 字符串,或对象 + let parsed: any; + if (typeof content === 'object') { + parsed = content; + } else { + // 先尝试去掉 markdown 代码块 ```json ... ``` + let jsonStr = content.trim(); + const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/); + if (codeBlockMatch) { + jsonStr = codeBlockMatch[1].trim(); + } else { + // 直接提取第一个完整 JSON 对象 + const objMatch = jsonStr.match(/\{[\s\S]*\}/); + if (objMatch) { + jsonStr = objMatch[0]; + } + } + try { + parsed = JSON.parse(jsonStr); + } catch (e: any) { + throw new Error(`FastGPT 返回内容无法解析为 JSON: ${e.message},内容片段: ${jsonStr.substring(0, 200)}`); + } + } + + // 校验必要字段 + if (!Array.isArray(parsed.ai_score_detail)) { + throw new Error(`响应缺少 ai_score_detail,内容: ${JSON.stringify(parsed).substring(0, 300)}`); + } + if (typeof parsed.ai_total_score !== 'number') { + throw new Error(`响应缺少有效的 ai_total_score,内容: ${JSON.stringify(parsed).substring(0, 300)}`); + } + + return { + aiScoreDetail: parsed.ai_score_detail.map((item: any) => ({ + itemName: item.itemName, + weight: item.weight, + aiScore: item.aiScore, + scoreExplanation: item.scoreExplanation, + })), + aiTotalScore: parsed.ai_total_score, + aiProblems: Array.isArray(parsed.ai_problems) ? parsed.ai_problems : [], + aiSuggestions: Array.isArray(parsed.ai_suggestions) ? parsed.ai_suggestions : [], + }; +} + +// ─── Core Evaluation ────────────────────────────────────────────────────────── + +/** + * Perform a single AI evaluation attempt for the given performance record. + */ +async function evaluateOnce( + perfId: number, + performance: PerformanceDAO.PerformanceRow, + items: PerformanceDAO.PerfItemRow[], + attendance: PerformanceDAO.AttendanceRow | null, + position: string +): Promise { + // 构建员工绩效内容文本(作为 userChatInput 传入 workflow) + const itemsText = items + .map( + (item, idx) => + `${idx + 1}. 【${item.item_name}】(权重${item.weight}分)\n` + + ` 员工填写:${item.user_content ?? '(未填写)'}\n` + + ` 自评分:${item.self_score ?? '(未填写)'}` + ) + .join('\n'); + + const attendanceText = attendance + ? `事假${attendance.leave_days}天,迟到${attendance.late_times}次,缺卡${attendance.lack_card_times}次,旷工${attendance.absent_days}天` + : '无考勤数据'; + + const contentText = `考勤情况:${attendanceText}\n\n工作汇总:${performance.work_summary ?? '(未填写)'}\n\n考核项目:\n${itemsText}`; + + const scoreData = await callFastGPT(position, performance.month, contentText); + + const aiId = await AIResultDAO.save({ + perfId, + aiScoreDetail: scoreData.aiScoreDetail, + aiTotalScore: scoreData.aiTotalScore, + aiProblems: scoreData.aiProblems, + aiSuggestions: scoreData.aiSuggestions, + apiResponse: JSON.stringify(scoreData), + }); + + return { + aiId, + perfId, + aiScoreDetail: scoreData.aiScoreDetail, + aiTotalScore: scoreData.aiTotalScore, + aiProblems: scoreData.aiProblems, + aiSuggestions: scoreData.aiSuggestions, + createTime: new Date(), + }; +} + +// ─── Retry + Degradation ────────────────────────────────────────────────────── + +const MAX_RETRIES = 3; + +/** + * Evaluate performance with up to MAX_RETRIES attempts. + * On all failures, logs the error and updates the performance status to 'ai_failed' (degradation). + * Requirements: 3.5 + */ +export async function evaluatePerformance(perfId: number): Promise { + // Load full performance detail + const detail = await PerformanceDAO.findDetailByPerfId(perfId); + if (!detail) { + throw new Error(`绩效记录不存在: perfId=${perfId}`); + } + + // Load employee position from user table + const pool = (await import('../config/database')).default; + const [userRows] = await pool.query( + 'SELECT position FROM user WHERE user_id = ? LIMIT 1', + [detail.performance.user_id] + ); + const position: string = userRows[0]?.position ?? '未知岗位'; + + let lastError: Error = new Error('未知错误'); + + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + const result = await evaluateOnce( + perfId, + detail.performance, + detail.items, + detail.attendance, + position + ); + + // Update ai_score on performance_month + await pool.query( + 'UPDATE performance_month SET ai_score = ? WHERE perf_id = ?', + [result.aiTotalScore, perfId] + ); + + // 回写 AI 评分到 perf_item 各考核项 + for (const scoreItem of result.aiScoreDetail) { + await pool.query( + `UPDATE perf_item SET ai_score = ?, ai_explanation = ? + WHERE perf_id = ? AND item_name = ?`, + [scoreItem.aiScore, scoreItem.scoreExplanation, perfId, scoreItem.itemName] + ); + } + + console.log(`[AI] 评分完成 perfId=${perfId},总分=${result.aiTotalScore}(第${attempt}次尝试)`); + return result; + } catch (err: any) { + lastError = err; + console.error(`[AI] 第${attempt}次评分失败 perfId=${perfId}:`, err.message); + if (attempt < MAX_RETRIES) { + // Brief delay before retry (exponential backoff: 1s, 2s) + await new Promise((resolve) => setTimeout(resolve, attempt * 1000)); + } + } + } + + // All retries exhausted — degradation: log error, notify admin via console + console.error(`[AI] 评分最终失败 perfId=${perfId},已重试${MAX_RETRIES}次。错误:${lastError.message}`); + + // Note: We don't log to operation_log here because there's no user_id context + // Admin can check console logs for AI evaluation failures + + throw lastError; +} diff --git a/backend/src/services/AuthService.ts b/backend/src/services/AuthService.ts new file mode 100644 index 0000000..7bea849 --- /dev/null +++ b/backend/src/services/AuthService.ts @@ -0,0 +1,39 @@ +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; +import { findByUsername } from '../dao/UserDAO'; +import { JWT_SECRET, JWT_EXPIRES_IN } from '../config/jwt'; +import { LoginResult, UserInfo, UserRole } from '../types'; + +export async function login( + username: string, + password: string, + role: string +): Promise { + const user = await findByUsername(username); + + if (!user || user.status !== 'active') { + throw new Error('用户名或密码错误'); + } + + const passwordMatch = await bcrypt.compare(password, user.password); + if (!passwordMatch) { + throw new Error('用户名或密码错误'); + } + + if (user.role !== role) { + throw new Error('角色不匹配'); + } + + const userInfo: UserInfo = { + userId: user.user_id, + name: user.name, + role: user.role as UserRole, + department: user.department, + position: user.position, + managerId: user.manager_id, + }; + + const token = jwt.sign(userInfo, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); + + return { token, userInfo }; +} diff --git a/backend/src/services/CalculationService.ts b/backend/src/services/CalculationService.ts new file mode 100644 index 0000000..7436224 --- /dev/null +++ b/backend/src/services/CalculationService.ts @@ -0,0 +1,83 @@ +import pool from '../config/database'; +import { PerformanceLevel } from '../dao/PerformanceDAO'; + +export interface AttendanceInput { + leaveDays: number; + lateTimes: number; + lackCardTimes: number; +} + +export interface LevelAndReward { + level: PerformanceLevel; + rewardPunish: string; +} + +export interface ConsecutiveLowScoreResult { + consecutiveMonths: number; + warning: 'none' | 'written_warning' | 'dismissal'; +} + +/** + * Calculate attendance score. + * Base: 10 points. Deduct 5 per leave day, 2 per late/lack-card occurrence. Minimum 0. + * Requirements: 11.1, 11.2, 11.3, 11.5 + */ +export function calculateAttendanceScore(input: AttendanceInput): number { + const { leaveDays, lateTimes, lackCardTimes } = input; + const deduction = leaveDays * 5 + lateTimes * 2 + lackCardTimes * 2; + return Math.max(0, 10 - deduction); +} + +/** + * Calculate performance level and reward/punishment description based on total score. + * Requirements: 5.1, 5.2, 5.3, 5.4, 5.5 + */ +export function calculateLevelAndReward(totalScore: number): LevelAndReward { + if (totalScore >= 90) { + return { level: 'excellent', rewardPunish: '优秀,按公司规定给予奖励' }; + } else if (totalScore >= 80) { + return { level: 'qualified', rewardPunish: '合格,扣除当月绩效工资100元' }; + } else if (totalScore >= 70) { + return { level: 'qualified', rewardPunish: '合格,扣除当月绩效工资200元' }; + } else if (totalScore >= 60) { + return { level: 'need_motivation', rewardPunish: '需激励,扣除当月绩效工资300元' }; + } else { + return { level: 'unqualified', rewardPunish: '不合格,扣除当月绩效工资600元' }; + } +} + +/** + * Check consecutive low score warning for an employee. + * Queries the most recent completed performance records and counts consecutive months below 60. + * Requirements: 5.6, 5.7 + */ +export async function checkConsecutiveLowScore( + userId: number, + lookbackMonths: number = 3 +): Promise { + const [rows] = await pool.query( + `SELECT total_score FROM performance_month + WHERE user_id = ? AND status = 'completed' AND total_score IS NOT NULL + ORDER BY month DESC + LIMIT ?`, + [userId, lookbackMonths] + ); + + let consecutiveMonths = 0; + for (const row of rows) { + if (row.total_score < 60) { + consecutiveMonths++; + } else { + break; + } + } + + let warning: ConsecutiveLowScoreResult['warning'] = 'none'; + if (consecutiveMonths >= 3) { + warning = 'dismissal'; + } else if (consecutiveMonths >= 2) { + warning = 'written_warning'; + } + + return { consecutiveMonths, warning }; +} diff --git a/backend/src/services/ConfigService.ts b/backend/src/services/ConfigService.ts new file mode 100644 index 0000000..f56c2d6 --- /dev/null +++ b/backend/src/services/ConfigService.ts @@ -0,0 +1,94 @@ +import pool from '../config/database'; +import { findAllRules, findRuleByKey, upsertRule, RuleRow } from '../dao/ConfigDAO'; + +export interface RuleDTO { + ruleKey: string; + ruleValue: unknown; + description?: string; + effectiveCycle?: string; +} + +export interface RuleResponse { + ruleId: number; + ruleKey: string; + ruleValue: unknown; + description: string | null; + effectiveCycle: string | null; + updatedBy: number | null; + updatedAt: Date; +} + +function toResponse(row: RuleRow): RuleResponse { + let parsed: unknown; + try { + parsed = JSON.parse(row.rule_value); + } catch { + parsed = row.rule_value; + } + return { + ruleId: row.rule_id, + ruleKey: row.rule_key, + ruleValue: parsed, + description: row.description, + effectiveCycle: row.effective_cycle, + updatedBy: row.updated_by, + updatedAt: row.updated_at, + }; +} + +/** Get all current rules */ +export async function getAllRules(): Promise { + const rows = await findAllRules(); + return rows.map(toResponse); +} + +/** Get a single rule by key */ +export async function getRuleByKey(ruleKey: string): Promise { + const row = await findRuleByKey(ruleKey); + return row ? toResponse(row) : null; +} + +/** + * Update (or create) a rule and record the change in operation_log. + * Requirements: 8.5, 8.6 + */ +export async function updateRule(dto: RuleDTO, operatorId: number): Promise { + // Persist the rule + await upsertRule({ + ruleKey: dto.ruleKey, + ruleValue: dto.ruleValue, + description: dto.description, + effectiveCycle: dto.effectiveCycle, + updatedBy: operatorId, + }); + + // Record operation log so changes are traceable (Requirement 8.6) + await pool.query( + `INSERT INTO operation_log (user_id, operation_type, target_type, operation_detail) + VALUES (?, 'update_rule', 'config', ?)`, + [ + operatorId, + JSON.stringify({ + ruleKey: dto.ruleKey, + ruleValue: dto.ruleValue, + effectiveCycle: dto.effectiveCycle ?? null, + }), + ] + ); + + const updated = await findRuleByKey(dto.ruleKey); + return toResponse(updated!); +} + +/** + * Batch update multiple rules in a single operation. + * Requirements: 8.5, 8.6 + */ +export async function updateRules(rules: RuleDTO[], operatorId: number): Promise { + const results: RuleResponse[] = []; + for (const dto of rules) { + const result = await updateRule(dto, operatorId); + results.push(result); + } + return results; +} diff --git a/backend/src/services/ExportService.ts b/backend/src/services/ExportService.ts new file mode 100644 index 0000000..1fe838e --- /dev/null +++ b/backend/src/services/ExportService.ts @@ -0,0 +1,136 @@ +import ExcelJS from 'exceljs'; +import pool from '../config/database'; + +export interface ExportFilter { + /** Export a single employee's history */ + userId?: number; + /** Export all subordinates of a manager */ + managerId?: number; + /** Restrict to a specific month (YYYY-MM) */ + month?: string; + /** For GM: export all employees */ + allEmployees?: boolean; +} + +/** + * Build and return an Excel workbook buffer for performance records. + * Supports: + * - Single employee history (userId) + * - Team export for a manager (managerId) + * - Full company export (allEmployees = true) + * Requirements: 7.3, 7.6, 8.4 + */ +export async function exportPerformanceExcel(filter: ExportFilter): Promise { + // Build query conditions + const conditions: string[] = ["pm.status = 'completed'"]; + const params: any[] = []; + + if (filter.userId) { + conditions.push('pm.user_id = ?'); + params.push(filter.userId); + } else if (filter.managerId) { + conditions.push('u.manager_id = ?'); + params.push(filter.managerId); + } + // allEmployees: no extra condition needed + + if (filter.month) { + conditions.push('pm.month = ?'); + params.push(filter.month); + } + + const where = conditions.join(' AND '); + + const [rows] = await pool.query( + `SELECT + pm.perf_id, + pm.month, + pm.status, + pm.self_score, + pm.ai_score, + pm.manager_score, + pm.total_score, + pm.level, + pm.reward_punish, + pm.work_summary, + pm.submit_time, + pm.review_time, + pm.review_opinion, + u.name AS employee_name, + u.department, + u.position + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + WHERE ${where} + ORDER BY u.department, u.name, pm.month DESC`, + params + ); + + const workbook = new ExcelJS.Workbook(); + workbook.creator = '员工绩效考核系统'; + workbook.created = new Date(); + + const sheet = workbook.addWorksheet('绩效数据'); + + // Header row + sheet.columns = [ + { header: '姓名', key: 'employee_name', width: 12 }, + { header: '部门', key: 'department', width: 16 }, + { header: '岗位', key: 'position', width: 16 }, + { header: '考核月份', key: 'month', width: 12 }, + { header: '自评分', key: 'self_score', width: 10 }, + { header: 'AI评分', key: 'ai_score', width: 10 }, + { header: '管理层评分', key: 'manager_score', width: 12 }, + { header: '最终总分', key: 'total_score', width: 12 }, + { header: '绩效等级', key: 'level', width: 14 }, + { header: '奖惩说明', key: 'reward_punish', width: 30 }, + { header: '工作汇总', key: 'work_summary', width: 40 }, + { header: '审核意见', key: 'review_opinion', width: 40 }, + { header: '提交时间', key: 'submit_time', width: 20 }, + { header: '审核时间', key: 'review_time', width: 20 }, + ]; + + // Style header row + const headerRow = sheet.getRow(1); + headerRow.font = { bold: true }; + headerRow.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFD9E1F2' }, + }; + headerRow.alignment = { vertical: 'middle', horizontal: 'center' }; + + const levelLabels: Record = { + excellent: '优秀', + qualified: '合格', + need_motivation: '需激励', + unqualified: '不合格', + }; + + for (const row of rows) { + sheet.addRow({ + employee_name: row.employee_name, + department: row.department, + position: row.position, + month: row.month, + self_score: row.self_score ?? '', + ai_score: row.ai_score ?? '', + manager_score: row.manager_score ?? '', + total_score: row.total_score ?? '', + level: row.level ? (levelLabels[row.level] ?? row.level) : '', + reward_punish: row.reward_punish ?? '', + work_summary: row.work_summary ?? '', + review_opinion: row.review_opinion ?? '', + submit_time: row.submit_time ? new Date(row.submit_time).toLocaleString('zh-CN') : '', + review_time: row.review_time ? new Date(row.review_time).toLocaleString('zh-CN') : '', + }); + } + + // Auto-fit row heights for wrapped text columns + sheet.getColumn('work_summary').alignment = { wrapText: true }; + sheet.getColumn('review_opinion').alignment = { wrapText: true }; + sheet.getColumn('reward_punish').alignment = { wrapText: true }; + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); +} diff --git a/backend/src/services/StatisticsService.ts b/backend/src/services/StatisticsService.ts new file mode 100644 index 0000000..ed4af6d --- /dev/null +++ b/backend/src/services/StatisticsService.ts @@ -0,0 +1,180 @@ +import pool from '../config/database'; +import { PerformanceLevel } from '../dao/PerformanceDAO'; + +export interface TeamStats { + averageScore: number; + totalCount: number; + excellentCount: number; + qualifiedCount: number; + needMotivationCount: number; + unqualifiedCount: number; + excellentRate: number; + qualifiedRate: number; + needMotivationRate: number; + unqualifiedRate: number; +} + +export interface DepartmentStat { + department: string; + averageScore: number; + totalCount: number; + levelDistribution: Record; +} + +export interface PositionStat { + position: string; + averageScore: number; + totalCount: number; +} + +export interface CompanyStats { + month: string; + totalCount: number; + averageScore: number; + departmentStats: DepartmentStat[]; + positionStats: PositionStat[]; + levelDistribution: Record; +} + +/** + * Get team statistics for a manager's subordinates for a given month. + * Requirements: 7.4, 7.5 + */ +export async function getTeamStatistics(managerId: number, month: string): Promise { + const [rows] = await pool.query( + `SELECT pm.total_score, pm.level + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + WHERE u.manager_id = ? AND pm.month = ? AND pm.status = 'completed' AND pm.total_score IS NOT NULL`, + [managerId, month] + ); + + const totalCount = rows.length; + if (totalCount === 0) { + return { + averageScore: 0, + totalCount: 0, + excellentCount: 0, + qualifiedCount: 0, + needMotivationCount: 0, + unqualifiedCount: 0, + excellentRate: 0, + qualifiedRate: 0, + needMotivationRate: 0, + unqualifiedRate: 0, + }; + } + + let sumScore = 0; + let excellentCount = 0; + let qualifiedCount = 0; + let needMotivationCount = 0; + let unqualifiedCount = 0; + + for (const row of rows) { + sumScore += Number(row.total_score); + switch (row.level as PerformanceLevel) { + case 'excellent': + excellentCount++; + break; + case 'qualified': + qualifiedCount++; + break; + case 'need_motivation': + needMotivationCount++; + break; + case 'unqualified': + unqualifiedCount++; + break; + } + } + + const averageScore = parseFloat((sumScore / totalCount).toFixed(2)); + const toRate = (n: number) => parseFloat(((n / totalCount) * 100).toFixed(2)); + + return { + averageScore, + totalCount, + excellentCount, + qualifiedCount, + needMotivationCount, + unqualifiedCount, + excellentRate: toRate(excellentCount), + qualifiedRate: toRate(qualifiedCount), + needMotivationRate: toRate(needMotivationCount), + unqualifiedRate: toRate(unqualifiedCount), + }; +} + +/** + * Get company-wide statistics for a given month, broken down by department and position. + * Requirements: 8.1, 8.2 + */ +export async function getCompanyStatistics(month: string): Promise { + const [rows] = await pool.query( + `SELECT pm.total_score, pm.level, u.department, u.position + FROM performance_month pm + JOIN user u ON pm.user_id = u.user_id + WHERE pm.month = ? AND pm.status = 'completed' AND pm.total_score IS NOT NULL`, + [month] + ); + + const totalCount = rows.length; + const levelDistribution: Record = { + excellent: 0, + qualified: 0, + need_motivation: 0, + unqualified: 0, + }; + + // Aggregate by department + const deptMap = new Map }>(); + // Aggregate by position + const posMap = new Map(); + + let totalSum = 0; + + for (const row of rows) { + const score = Number(row.total_score); + const level = row.level as PerformanceLevel; + const dept = row.department as string; + const pos = row.position as string; + + totalSum += score; + if (level in levelDistribution) levelDistribution[level]++; + + // Department + if (!deptMap.has(dept)) { + deptMap.set(dept, { scores: [], levels: { excellent: 0, qualified: 0, need_motivation: 0, unqualified: 0 } }); + } + const deptEntry = deptMap.get(dept)!; + deptEntry.scores.push(score); + if (level in deptEntry.levels) deptEntry.levels[level]++; + + // Position + if (!posMap.has(pos)) posMap.set(pos, []); + posMap.get(pos)!.push(score); + } + + const departmentStats: DepartmentStat[] = Array.from(deptMap.entries()).map(([department, data]) => ({ + department, + averageScore: parseFloat((data.scores.reduce((a, b) => a + b, 0) / data.scores.length).toFixed(2)), + totalCount: data.scores.length, + levelDistribution: data.levels, + })); + + const positionStats: PositionStat[] = Array.from(posMap.entries()).map(([position, scores]) => ({ + position, + averageScore: parseFloat((scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(2)), + totalCount: scores.length, + })); + + return { + month, + totalCount, + averageScore: totalCount > 0 ? parseFloat((totalSum / totalCount).toFixed(2)) : 0, + departmentStats, + positionStats, + levelDistribution, + }; +} diff --git a/backend/src/services/__tests__/AIEvaluationService.property.test.ts b/backend/src/services/__tests__/AIEvaluationService.property.test.ts new file mode 100644 index 0000000..b853e91 --- /dev/null +++ b/backend/src/services/__tests__/AIEvaluationService.property.test.ts @@ -0,0 +1,185 @@ +import * as fc from 'fast-check'; +import { parseAIResponse, AIScoreData } from '../AIEvaluationService'; +import type { AIScoreItem } from '../../dao/AIResultDAO'; + +// ─── Arbitraries ────────────────────────────────────────────────────────────── + +/** Generate a valid AIScoreItem */ +const aiScoreItemArb: fc.Arbitrary = fc.record({ + itemName: fc.string({ minLength: 1, maxLength: 50 }), + weight: fc.integer({ min: 1, max: 30 }), + aiScore: fc.float({ min: 0, max: 100, noNaN: true }), + scoreExplanation: fc.string({ minLength: 1, maxLength: 200 }), +}); + +/** Generate a valid AIScoreData object */ +const aiScoreDataArb: fc.Arbitrary = fc.record({ + aiScoreDetail: fc.array(aiScoreItemArb, { minLength: 1, maxLength: 17 }), + aiTotalScore: fc.float({ min: 0, max: 100, noNaN: true }), + aiProblems: fc.array(fc.string({ minLength: 1, maxLength: 100 }), { minLength: 3, maxLength: 5 }), + aiSuggestions: fc.array(fc.string({ minLength: 1, maxLength: 100 }), { minLength: 3, maxLength: 5 }), +}); + +/** + * Serialize an AIScoreData object into the JSON string format that + * parseAIResponse expects (matching the FastGPT output schema). + */ +function serializeToAIJson(data: AIScoreData): string { + return JSON.stringify({ + ai_score_detail: data.aiScoreDetail.map((item) => ({ + itemName: item.itemName, + weight: item.weight, + aiScore: item.aiScore, + scoreExplanation: item.scoreExplanation, + })), + ai_total_score: data.aiTotalScore, + ai_problems: data.aiProblems, + ai_suggestions: data.aiSuggestions, + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 9: AI 响应 JSON 解析往返一致性 +// For any valid AI score JSON string, parsing then re-serializing should +// produce a semantically equivalent object. +// Validates: Requirements 3.6, 13.3 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 9: AI 响应 JSON 解析往返一致性', () => { + it('parse then re-serialize produces equivalent object', () => { + fc.assert( + fc.property(aiScoreDataArb, (original) => { + const jsonStr = serializeToAIJson(original); + const parsed = parseAIResponse(jsonStr); + + // Round-trip: re-serialize and parse again + const roundTripped = parseAIResponse(serializeToAIJson(parsed)); + + // The re-parsed result must be semantically equivalent to the first parse + expect(roundTripped.aiTotalScore).toBeCloseTo(parsed.aiTotalScore, 5); + expect(roundTripped.aiProblems).toEqual(parsed.aiProblems); + expect(roundTripped.aiSuggestions).toEqual(parsed.aiSuggestions); + expect(roundTripped.aiScoreDetail.length).toBe(parsed.aiScoreDetail.length); + + roundTripped.aiScoreDetail.forEach((item, idx) => { + expect(item.itemName).toBe(parsed.aiScoreDetail[idx].itemName); + expect(item.weight).toBe(parsed.aiScoreDetail[idx].weight); + expect(item.aiScore).toBeCloseTo(parsed.aiScoreDetail[idx].aiScore, 5); + expect(item.scoreExplanation).toBe(parsed.aiScoreDetail[idx].scoreExplanation); + }); + }), + { numRuns: 100 } + ); + }); + + it('parsed result preserves all score detail fields from original JSON', () => { + fc.assert( + fc.property(aiScoreDataArb, (original) => { + const jsonStr = serializeToAIJson(original); + const parsed = parseAIResponse(jsonStr); + + expect(parsed.aiScoreDetail.length).toBe(original.aiScoreDetail.length); + parsed.aiScoreDetail.forEach((item, idx) => { + expect(item.itemName).toBe(original.aiScoreDetail[idx].itemName); + expect(item.weight).toBe(original.aiScoreDetail[idx].weight); + expect(item.scoreExplanation).toBe(original.aiScoreDetail[idx].scoreExplanation); + }); + expect(parsed.aiProblems).toEqual(original.aiProblems); + expect(parsed.aiSuggestions).toEqual(original.aiSuggestions); + }), + { numRuns: 100 } + ); + }); + + it('parseAIResponse handles JSON wrapped in markdown code blocks', () => { + fc.assert( + fc.property(aiScoreDataArb, (original) => { + const jsonStr = serializeToAIJson(original); + const wrapped = `\`\`\`json\n${jsonStr}\n\`\`\``; + const parsed = parseAIResponse(wrapped); + + expect(parsed.aiProblems).toEqual(original.aiProblems); + expect(parsed.aiSuggestions).toEqual(original.aiSuggestions); + expect(parsed.aiScoreDetail.length).toBe(original.aiScoreDetail.length); + }), + { numRuns: 100 } + ); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 10: AI 输出格式约束 +// For any valid AI response, ai_problems and ai_suggestions arrays must each +// have a length between 3 and 5 (inclusive). +// Validates: Requirements 3.3, 3.4 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 10: AI 输出格式约束', () => { + it('parsed ai_problems length is always between 3 and 5', () => { + fc.assert( + fc.property(aiScoreDataArb, (original) => { + const parsed = parseAIResponse(serializeToAIJson(original)); + expect(parsed.aiProblems.length).toBeGreaterThanOrEqual(3); + expect(parsed.aiProblems.length).toBeLessThanOrEqual(5); + }), + { numRuns: 100 } + ); + }); + + it('parsed ai_suggestions length is always between 3 and 5', () => { + fc.assert( + fc.property(aiScoreDataArb, (original) => { + const parsed = parseAIResponse(serializeToAIJson(original)); + expect(parsed.aiSuggestions.length).toBeGreaterThanOrEqual(3); + expect(parsed.aiSuggestions.length).toBeLessThanOrEqual(5); + }), + { numRuns: 100 } + ); + }); + + it('parseAIResponse rejects ai_problems with fewer than 3 items', () => { + fc.assert( + fc.property( + fc.record({ + detail: fc.array(aiScoreItemArb, { minLength: 1, maxLength: 5 }), + totalScore: fc.float({ min: 0, max: 100, noNaN: true }), + // 0, 1, or 2 problems — all invalid + problems: fc.array(fc.string({ minLength: 1 }), { minLength: 0, maxLength: 2 }), + suggestions: fc.array(fc.string({ minLength: 1 }), { minLength: 3, maxLength: 5 }), + }), + ({ detail, totalScore, problems, suggestions }) => { + const json = JSON.stringify({ + ai_score_detail: detail, + ai_total_score: totalScore, + ai_problems: problems, + ai_suggestions: suggestions, + }); + expect(() => parseAIResponse(json)).toThrow(); + } + ), + { numRuns: 100 } + ); + }); + + it('parseAIResponse rejects ai_suggestions with more than 5 items', () => { + fc.assert( + fc.property( + fc.record({ + detail: fc.array(aiScoreItemArb, { minLength: 1, maxLength: 5 }), + totalScore: fc.float({ min: 0, max: 100, noNaN: true }), + problems: fc.array(fc.string({ minLength: 1 }), { minLength: 3, maxLength: 5 }), + // 6 or more suggestions — all invalid + suggestions: fc.array(fc.string({ minLength: 1 }), { minLength: 6, maxLength: 10 }), + }), + ({ detail, totalScore, problems, suggestions }) => { + const json = JSON.stringify({ + ai_score_detail: detail, + ai_total_score: totalScore, + ai_problems: problems, + ai_suggestions: suggestions, + }); + expect(() => parseAIResponse(json)).toThrow(); + } + ), + { numRuns: 100 } + ); + }); +}); diff --git a/backend/src/services/__tests__/AuthService.property.test.ts b/backend/src/services/__tests__/AuthService.property.test.ts new file mode 100644 index 0000000..f6d9717 --- /dev/null +++ b/backend/src/services/__tests__/AuthService.property.test.ts @@ -0,0 +1,139 @@ +import * as fc from 'fast-check'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; +import { login } from '../AuthService'; +import * as UserDAO from '../../dao/UserDAO'; +import { JWT_SECRET } from '../../config/jwt'; +import { UserRole } from '../../types'; + +jest.mock('../../dao/UserDAO'); +const mockFindByUsername = UserDAO.findByUsername as jest.MockedFunction; + +// Feature: employee-performance-system, Property 1: 认证正确性 +// For any credentials, the authentication result is strictly consistent with credential validity. +describe('Property 1: 认证正确性', () => { + const ROLES: UserRole[] = ['employee', 'manager', 'generalManager']; + + afterEach(() => jest.clearAllMocks()); + + it('valid credentials always succeed and return a verifiable token', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + username: fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0), + password: fc.string({ minLength: 6, maxLength: 30 }), + role: fc.constantFrom(...ROLES), + userId: fc.integer({ min: 1, max: 9999 }), + name: fc.string({ minLength: 1, maxLength: 20 }), + department: fc.string({ minLength: 1, maxLength: 20 }), + position: fc.string({ minLength: 1, maxLength: 20 }), + }), + async ({ username, password, role, userId, name, department, position }) => { + const hashedPassword = bcrypt.hashSync(password, 1); // cost 1 for speed + const userRow: UserDAO.UserRow = { + user_id: userId, + username, + password: hashedPassword, + name, + role, + department, + position, + manager_id: null, + status: 'active', + }; + mockFindByUsername.mockResolvedValue(userRow); + + const result = await login(username, password, role); + + // Must return a token + expect(result.token).toBeTruthy(); + // Token must be verifiable and carry correct userId + const decoded = jwt.verify(result.token, JWT_SECRET) as any; + expect(decoded.userId).toBe(userId); + expect(decoded.role).toBe(role); + // userInfo must match + expect(result.userInfo.userId).toBe(userId); + expect(result.userInfo.role).toBe(role); + } + ), + { numRuns: 100 } + ); + }); + + it('invalid password always throws and never returns a token', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + username: fc.string({ minLength: 1, maxLength: 20 }), + correctPassword: fc.string({ minLength: 6, maxLength: 30 }), + wrongPassword: fc.string({ minLength: 1, maxLength: 30 }), + role: fc.constantFrom(...ROLES), + }).filter(({ correctPassword, wrongPassword }) => correctPassword !== wrongPassword), + async ({ username, correctPassword, wrongPassword, role }) => { + const hashedPassword = bcrypt.hashSync(correctPassword, 1); + mockFindByUsername.mockResolvedValue({ + user_id: 1, + username, + password: hashedPassword, + name: '测试', + role, + department: '部门', + position: '职位', + manager_id: null, + status: 'active', + }); + + await expect(login(username, wrongPassword, role)).rejects.toThrow(); + } + ), + { numRuns: 100 } + ); + }); + + it('non-existent user always throws', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + username: fc.string({ minLength: 1, maxLength: 20 }), + password: fc.string({ minLength: 1, maxLength: 30 }), + role: fc.constantFrom(...ROLES), + }), + async ({ username, password, role }) => { + mockFindByUsername.mockResolvedValue(null); + await expect(login(username, password, role)).rejects.toThrow(); + } + ), + { numRuns: 100 } + ); + }); + + it('role mismatch always throws', async () => { + await fc.assert( + fc.asyncProperty( + fc.record({ + username: fc.string({ minLength: 1, maxLength: 20 }), + password: fc.string({ minLength: 6, maxLength: 30 }), + storedRole: fc.constantFrom(...ROLES), + requestedRole: fc.constantFrom(...ROLES), + }).filter(({ storedRole, requestedRole }) => storedRole !== requestedRole), + async ({ username, password, storedRole, requestedRole }) => { + const hashedPassword = bcrypt.hashSync(password, 1); + mockFindByUsername.mockResolvedValue({ + user_id: 1, + username, + password: hashedPassword, + name: '测试', + role: storedRole, + department: '部门', + position: '职位', + manager_id: null, + status: 'active', + }); + + await expect(login(username, password, requestedRole)).rejects.toThrow(); + } + ), + { numRuns: 100 } + ); + }); +}); diff --git a/backend/src/services/__tests__/AuthService.test.ts b/backend/src/services/__tests__/AuthService.test.ts new file mode 100644 index 0000000..9c1a824 --- /dev/null +++ b/backend/src/services/__tests__/AuthService.test.ts @@ -0,0 +1,56 @@ +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; +import { login } from '../AuthService'; +import * as UserDAO from '../../dao/UserDAO'; +import { JWT_SECRET } from '../../config/jwt'; + +jest.mock('../../dao/UserDAO'); +const mockFindByUsername = UserDAO.findByUsername as jest.MockedFunction; + +const baseUser: UserDAO.UserRow = { + user_id: 1, + username: 'emp001', + password: bcrypt.hashSync('password123', 10), + name: '张三', + role: 'employee', + department: '研发部', + position: '工程师', + manager_id: 2, + status: 'active', +}; + +describe('AuthService.login', () => { + afterEach(() => jest.clearAllMocks()); + + it('valid credentials return token and userInfo', async () => { + mockFindByUsername.mockResolvedValue(baseUser); + const result = await login('emp001', 'password123', 'employee'); + + expect(result.token).toBeTruthy(); + expect(result.userInfo.userId).toBe(1); + expect(result.userInfo.role).toBe('employee'); + + const decoded = jwt.verify(result.token, JWT_SECRET) as any; + expect(decoded.userId).toBe(1); + }); + + it('wrong password throws error', async () => { + mockFindByUsername.mockResolvedValue(baseUser); + await expect(login('emp001', 'wrongpass', 'employee')).rejects.toThrow('用户名或密码错误'); + }); + + it('non-existent user throws error', async () => { + mockFindByUsername.mockResolvedValue(null); + await expect(login('nobody', 'pass', 'employee')).rejects.toThrow('用户名或密码错误'); + }); + + it('role mismatch throws error', async () => { + mockFindByUsername.mockResolvedValue(baseUser); + await expect(login('emp001', 'password123', 'manager')).rejects.toThrow('角色不匹配'); + }); + + it('inactive user throws error', async () => { + mockFindByUsername.mockResolvedValue({ ...baseUser, status: 'inactive' }); + await expect(login('emp001', 'password123', 'employee')).rejects.toThrow('用户名或密码错误'); + }); +}); diff --git a/backend/src/services/__tests__/CalculationService.property.test.ts b/backend/src/services/__tests__/CalculationService.property.test.ts new file mode 100644 index 0000000..ce6e890 --- /dev/null +++ b/backend/src/services/__tests__/CalculationService.property.test.ts @@ -0,0 +1,287 @@ +import * as fc from 'fast-check'; +import { + calculateAttendanceScore, + calculateLevelAndReward, + AttendanceInput, +} from '../CalculationService'; + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 5: 绩效等级与奖惩计算正确性 +// For any total score (0-100), the level and reward/punishment must strictly +// follow the defined rules. +// Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.5 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 5: 绩效等级与奖惩计算正确性', () => { + it('score >= 90 → excellent with reward description', () => { + fc.assert( + fc.property( + fc.integer({ min: 90, max: 100 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBe('excellent'); + expect(result.rewardPunish).toContain('奖励'); + } + ), + { numRuns: 100 } + ); + }); + + it('80 <= score < 90 → qualified, deduct 100', () => { + fc.assert( + fc.property( + // Use integer scores in [80, 89] to stay within 32-bit float constraints + fc.integer({ min: 80, max: 89 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBe('qualified'); + expect(result.rewardPunish).toContain('100'); + } + ), + { numRuns: 100 } + ); + }); + + it('70 <= score < 80 → qualified, deduct 200', () => { + fc.assert( + fc.property( + fc.integer({ min: 70, max: 79 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBe('qualified'); + expect(result.rewardPunish).toContain('200'); + } + ), + { numRuns: 100 } + ); + }); + + it('60 <= score < 70 → need_motivation, deduct 300', () => { + fc.assert( + fc.property( + fc.integer({ min: 60, max: 69 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBe('need_motivation'); + expect(result.rewardPunish).toContain('300'); + } + ), + { numRuns: 100 } + ); + }); + + it('score < 60 → unqualified, deduct 600', () => { + fc.assert( + fc.property( + fc.integer({ min: 0, max: 59 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBe('unqualified'); + expect(result.rewardPunish).toContain('600'); + } + ), + { numRuns: 100 } + ); + }); + + it('every score in [0,100] produces a non-empty level and rewardPunish', () => { + fc.assert( + fc.property( + fc.integer({ min: 0, max: 100 }), + (score) => { + const result = calculateLevelAndReward(score); + expect(result.level).toBeTruthy(); + expect(result.rewardPunish.length).toBeGreaterThan(0); + } + ), + { numRuns: 100 } + ); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 6: 连续低分预警正确性 +// For any sequence of completed performance scores, consecutive months below 60 +// must trigger the correct warning level. +// Validates: Requirements 5.6, 5.7 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 6: 连续低分预警正确性', () => { + /** + * We test the pure logic by extracting it from checkConsecutiveLowScore. + * The function queries the DB, so we replicate the counting logic here and + * verify it against the same rules the implementation uses. + */ + function deriveWarning(scores: number[]): { consecutiveMonths: number; warning: string } { + let consecutiveMonths = 0; + for (const score of scores) { + if (score < 60) { + consecutiveMonths++; + } else { + break; + } + } + let warning = 'none'; + if (consecutiveMonths >= 3) warning = 'dismissal'; + else if (consecutiveMonths >= 2) warning = 'written_warning'; + return { consecutiveMonths, warning }; + } + + it('0 or 1 consecutive low scores → no warning', () => { + fc.assert( + fc.property( + // Scores where the first element is >= 60 (no consecutive low streak) + fc.array(fc.float({ min: 0, max: 100, noNaN: true }), { minLength: 1, maxLength: 6 }).map( + (arr) => [arr[0] >= 60 ? arr[0] : arr[0] + 60, ...arr.slice(1)] + ), + (scores) => { + const { warning } = deriveWarning(scores); + expect(warning).toBe('none'); + } + ), + { numRuns: 100 } + ); + }); + + it('exactly 2 consecutive low scores → written_warning', () => { + fc.assert( + fc.property( + fc.record({ + low1: fc.integer({ min: 0, max: 59 }), + low2: fc.integer({ min: 0, max: 59 }), + // Third score is >= 60 to stop the streak at exactly 2 + third: fc.integer({ min: 60, max: 100 }), + }), + ({ low1, low2, third }) => { + const scores = [low1, low2, third]; + const { consecutiveMonths, warning } = deriveWarning(scores); + expect(consecutiveMonths).toBe(2); + expect(warning).toBe('written_warning'); + } + ), + { numRuns: 100 } + ); + }); + + it('3 or more consecutive low scores → dismissal', () => { + fc.assert( + fc.property( + fc.array(fc.integer({ min: 0, max: 59 }), { minLength: 3, maxLength: 6 }), + (lowScores) => { + const { warning } = deriveWarning(lowScores); + expect(warning).toBe('dismissal'); + } + ), + { numRuns: 100 } + ); + }); + + it('a high score resets the consecutive streak', () => { + fc.assert( + fc.property( + fc.record({ + highScore: fc.integer({ min: 60, max: 100 }), + lowScores: fc.array(fc.integer({ min: 0, max: 59 }), { minLength: 1, maxLength: 5 }), + }), + ({ highScore, lowScores }) => { + // High score first, then low scores — streak should be 0 + const scores = [highScore, ...lowScores]; + const { consecutiveMonths, warning } = deriveWarning(scores); + expect(consecutiveMonths).toBe(0); + expect(warning).toBe('none'); + } + ), + { numRuns: 100 } + ); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Feature: employee-performance-system, Property 8: 考勤分数计算正确性 +// For any attendance data, the score must follow the deduction rules and never +// fall below 0. +// Validates: Requirements 11.1, 11.2, 11.3, 11.5 +// ───────────────────────────────────────────────────────────────────────────── +describe('Property 8: 考勤分数计算正确性', () => { + const attendanceArb = fc.record({ + leaveDays: fc.integer({ min: 0, max: 10 }), + lateTimes: fc.integer({ min: 0, max: 10 }), + lackCardTimes: fc.integer({ min: 0, max: 10 }), + }); + + it('score is always >= 0 (floor protection)', () => { + fc.assert( + fc.property(attendanceArb, (input) => { + expect(calculateAttendanceScore(input)).toBeGreaterThanOrEqual(0); + }), + { numRuns: 100 } + ); + }); + + it('perfect attendance (all zeros) → full score of 10', () => { + fc.assert( + fc.property( + fc.constant({ leaveDays: 0, lateTimes: 0, lackCardTimes: 0 }), + (input) => { + expect(calculateAttendanceScore(input)).toBe(10); + } + ), + { numRuns: 1 } + ); + }); + + it('each leave day deducts exactly 5 points (when no other deductions)', () => { + fc.assert( + fc.property( + fc.integer({ min: 0, max: 2 }), // keep within range where score stays >= 0 + (leaveDays) => { + const score = calculateAttendanceScore({ leaveDays, lateTimes: 0, lackCardTimes: 0 }); + const expected = Math.max(0, 10 - leaveDays * 5); + expect(score).toBe(expected); + } + ), + { numRuns: 100 } + ); + }); + + it('each late/lack-card occurrence deducts exactly 2 points (when no other deductions)', () => { + fc.assert( + fc.property( + fc.integer({ min: 0, max: 5 }), + fc.integer({ min: 0, max: 5 }), + (lateTimes, lackCardTimes) => { + const score = calculateAttendanceScore({ leaveDays: 0, lateTimes, lackCardTimes }); + const expected = Math.max(0, 10 - lateTimes * 2 - lackCardTimes * 2); + expect(score).toBe(expected); + } + ), + { numRuns: 100 } + ); + }); + + it('score is always <= 10 (cannot exceed base score)', () => { + fc.assert( + fc.property(attendanceArb, (input) => { + expect(calculateAttendanceScore(input)).toBeLessThanOrEqual(10); + }), + { numRuns: 100 } + ); + }); + + it('more absences never produce a higher score (monotone deduction)', () => { + fc.assert( + fc.property( + fc.record({ + leaveDays: fc.integer({ min: 0, max: 5 }), + lateTimes: fc.integer({ min: 0, max: 5 }), + lackCardTimes: fc.integer({ min: 0, max: 5 }), + extraLeave: fc.integer({ min: 1, max: 3 }), + }), + ({ leaveDays, lateTimes, lackCardTimes, extraLeave }) => { + const base = calculateAttendanceScore({ leaveDays, lateTimes, lackCardTimes }); + const worse = calculateAttendanceScore({ leaveDays: leaveDays + extraLeave, lateTimes, lackCardTimes }); + expect(worse).toBeLessThanOrEqual(base); + } + ), + { numRuns: 100 } + ); + }); +}); diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts new file mode 100644 index 0000000..32cf790 --- /dev/null +++ b/backend/src/types/index.ts @@ -0,0 +1,24 @@ +export type UserRole = 'employee' | 'manager' | 'generalManager'; + +export interface UserInfo { + userId: number; + name: string; + role: UserRole; + department: string; + position: string; + managerId?: number | null; +} + +export interface LoginResult { + token: string; + userInfo: UserInfo; +} + +// Extend Express Request to carry authenticated user +declare global { + namespace Express { + interface Request { + user?: UserInfo; + } + } +} diff --git a/backend/test-db.ts b/backend/test-db.ts new file mode 100644 index 0000000..676636a --- /dev/null +++ b/backend/test-db.ts @@ -0,0 +1,42 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function testConnection() { + console.log('测试数据库连接...'); + console.log('配置信息:'); + console.log(' Host:', process.env.DB_HOST); + console.log(' Port:', process.env.DB_PORT); + console.log(' User:', process.env.DB_USER); + console.log(' Password:', process.env.DB_PASSWORD ? '***' : '(空)'); + console.log(' Database:', process.env.DB_NAME); + + try { + const connection = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + console.log('\n✅ 数据库连接成功!'); + + // 测试查询 + const [rows] = await connection.query('SELECT COUNT(*) as count FROM user'); + console.log('用户表记录数:', (rows as any)[0].count); + + await connection.end(); + process.exit(0); + } catch (error: any) { + console.error('\n❌ 数据库连接失败:', error.message); + console.error('\n请检查:'); + console.error('1. MySQL 服务是否正在运行'); + console.error('2. 用户名和密码是否正确'); + console.error('3. 数据库是否已创建'); + process.exit(1); + } +} + +testConnection(); diff --git a/backend/test-fastgpt.ts b/backend/test-fastgpt.ts new file mode 100644 index 0000000..0577a00 --- /dev/null +++ b/backend/test-fastgpt.ts @@ -0,0 +1,51 @@ +import axios from 'axios'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +const FASTGPT_API_URL = process.env.FASTGPT_API_URL!; +const FASTGPT_API_KEY = process.env.FASTGPT_API_KEY!; +const HTTPS_PROXY = process.env.HTTPS_PROXY; + +async function test() { + console.log('API URL:', FASTGPT_API_URL); + console.log('代理:', HTTPS_PROXY || '无'); + console.log('正在发送请求...\n'); + + const contentText = `考勤情况:全勤,无迟到缺卡\n\n工作汇总:本月完成了前端页面开发,项目顺利交付。\n\n考核项目:\n1. 【工作目标完成情况】(权重15分)\n 员工填写:已完成所有既定目标\n 自评分:90`; + + const proxyConfig = HTTPS_PROXY ? (() => { + const url = new URL(HTTPS_PROXY); + return { host: url.hostname, port: parseInt(url.port, 10), protocol: 'http' as const }; + })() : undefined; + + try { + const response = await axios.post( + FASTGPT_API_URL, + { + messages: [{ role: 'user', content: contentText }], + variables: { zslh34AG: '前端工程师', month: '2026-04' }, + stream: false, + }, + { + headers: { Authorization: `Bearer ${FASTGPT_API_KEY}`, 'Content-Type': 'application/json' }, + timeout: 60_000, + proxy: proxyConfig, + } + ); + + console.log('状态码:', response.status); + const content = response.data?.choices?.[0]?.message?.content; + console.log('content 类型:', typeof content); + console.log('content 内容:', typeof content === 'string' ? content.substring(0, 800) : JSON.stringify(content).substring(0, 800)); + } catch (err: any) { + if (err.response) { + console.error('HTTP 状态码:', err.response.status); + console.error('响应体:', JSON.stringify(err.response.data, null, 2)); + } else { + console.error('错误码:', err.code); + console.error('错误信息:', err.message); + } + } +} + +test(); diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..bb5605d --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/backend/update-accounts.ts b/backend/update-accounts.ts new file mode 100644 index 0000000..057df03 --- /dev/null +++ b/backend/update-accounts.ts @@ -0,0 +1,34 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; +import bcrypt from 'bcryptjs'; + +async function run() { + const hash = await bcrypt.hash('123456', 10); + + // 删除 mgr002 + await pool.query('DELETE FROM user WHERE username = ?', ['mgr002']); + console.log('删除 mgr002'); + + // 更新 gm001 → lister / 李总 / 总经理 + await pool.query( + 'UPDATE user SET username = ?, name = ?, password = ? WHERE username = ?', + ['lister', '李总', hash, 'gm001'] + ); + console.log('更新 gm001 → lister'); + + // 更新 mgr001 → xinxin / 孙薪薪 / 管理层 + await pool.query( + 'UPDATE user SET username = ?, name = ?, password = ? WHERE username = ?', + ['xinxin', '孙薪薪', hash, 'mgr001'] + ); + console.log('更新 mgr001 → xinxin'); + + const [rows] = await pool.query('SELECT user_id, username, name, role, department, position FROM user ORDER BY role'); + console.log('\n当前账号:'); + console.table(rows); + + await pool.end(); +} + +run().catch(console.error); diff --git a/backend/update-passwords-final.ts b/backend/update-passwords-final.ts new file mode 100644 index 0000000..c356f2a --- /dev/null +++ b/backend/update-passwords-final.ts @@ -0,0 +1,19 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import pool from './src/config/database'; +import bcrypt from 'bcryptjs'; + +async function run() { + const xinxinHash = await bcrypt.hash('sxx980623', 10); + const listerHash = await bcrypt.hash('lister123', 10); + + await pool.query('UPDATE user SET password = ? WHERE username = ?', [xinxinHash, 'xinxin']); + await pool.query('UPDATE user SET password = ? WHERE username = ?', [listerHash, 'lister']); + + console.log('xinxin 密码已更新为 sxx980623'); + console.log('lister 密码已更新为 lister123'); + + await pool.end(); +} + +run().catch(console.error); diff --git a/backend/update-passwords.ts b/backend/update-passwords.ts new file mode 100644 index 0000000..8f0b32d --- /dev/null +++ b/backend/update-passwords.ts @@ -0,0 +1,44 @@ +import mysql from 'mysql2/promise'; +import bcrypt from 'bcryptjs'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function updatePasswords() { + try { + const conn = await mysql.createConnection({ + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'employee_performance', + }); + + console.log('生成新的密码哈希...'); + const hash = await bcrypt.hash('123456', 10); + console.log('新哈希:', hash); + + // 测试旧哈希 + const oldHash = '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy'; + const matchOld = await bcrypt.compare('123456', oldHash); + console.log('旧哈希验证:', matchOld); + + // 更新所有用户密码 + await conn.query('UPDATE user SET password = ?', [hash]); + console.log('✓ 所有用户密码已更新'); + + // 验证更新 + const [rows] = await conn.query('SELECT username, password FROM user LIMIT 1'); + const user = (rows as any)[0]; + const matchNew = await bcrypt.compare('123456', user.password); + console.log('新密码验证:', matchNew); + + await conn.end(); + process.exit(0); + } catch (error: any) { + console.error('错误:', error.message); + process.exit(1); + } +} + +updatePasswords(); diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..aa0926a --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +*.log diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..37ae696 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + 员工月度绩效考核系统 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..a71a7f8 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3136 @@ +{ + "name": "employee-performance-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "employee-performance-frontend", + "version": "1.0.0", + "dependencies": { + "@ant-design/icons": "^5.2.6", + "antd": "^5.12.0", + "axios": "^1.6.0", + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.21.0" + }, + "devDependencies": { + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.2", + "vite": "^5.0.8" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/antd": { + "version": "5.29.3", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.16", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001786", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz", + "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/echarts-for-react/-/echarts-for-react-3.0.6.tgz", + "integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.332", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", + "integrity": "sha512-7OOtytmh/rINMLwaFTbcMVvYXO3AUm029X0LcyfYk0B557RlPkdpTpnH9+htMlfu5dKwOmT0+Zs2Aw+lnn6TeQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/size-sensor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.3.tgz", + "integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==", + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..670bdff --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "employee-performance-frontend", + "version": "1.0.0", + "private": true, + "dependencies": { + "@ant-design/icons": "^5.2.6", + "antd": "^5.12.0", + "axios": "^1.6.0", + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.21.0" + }, + "devDependencies": { + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.2", + "vite": "^5.0.8" + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..6ebe182 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { ConfigProvider } from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import { AuthProvider } from './context/AuthContext'; +import AppRouter from './router'; + +// 配置 dayjs 使用中文 +dayjs.locale('zh-cn'); + +const App: React.FC = () => { + return ( + + + + + + + + ); +}; + +export default App; diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..e549edc --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,28 @@ +import http from './http'; +import type { UserInfo } from '../context/AuthContext'; + +interface LoginResponse { + token: string; + userInfo: UserInfo; +} + +interface ApiResponse { + code: number; + message: string; + data: T; +} + +export const authApi = { + login: async ( + username: string, + password: string, + role: string + ): Promise => { + const { data } = await http.post>('/api/user/login', { + username, + password, + role, + }); + return data.data; // 返回 data.data,因为后端包装了一层 + }, +}; diff --git a/frontend/src/api/http.ts b/frontend/src/api/http.ts new file mode 100644 index 0000000..bf842dd --- /dev/null +++ b/frontend/src/api/http.ts @@ -0,0 +1,37 @@ +import axios from 'axios'; + +const TOKEN_KEY = 'auth_token'; + +const http = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001', + timeout: 15000, +}); + +// Request interceptor — attach token automatically +http.interceptors.request.use( + (config) => { + const token = localStorage.getItem(TOKEN_KEY); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) +); + +// Response interceptor — handle 401/403 +http.interceptors.response.use( + (response) => response, + (error) => { + const status = error?.response?.status; + if (status === 401 || status === 403) { + // Clear stored credentials and redirect to login + localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem('auth_user'); + window.location.href = '/login'; + } + return Promise.reject(error); + } +); + +export default http; diff --git a/frontend/src/api/performance.ts b/frontend/src/api/performance.ts new file mode 100644 index 0000000..e3a98cd --- /dev/null +++ b/frontend/src/api/performance.ts @@ -0,0 +1,122 @@ +import http from './http'; + +export interface AttendanceData { + leave: number; + late: number; + absent: number; + lackCard: number; + remark?: string; +} + +export interface PerformanceItemDTO { + itemName: string; + weight: number; + userContent: string; + selfScore: number; + evidence?: string; +} + +export interface PerformanceSubmitDTO { + month: string; + status: 'draft' | 'submitted'; + workSummary: string; + attendance: AttendanceData; + performanceItems: PerformanceItemDTO[]; +} + +export interface AIScoreItem { + itemName: string; + weight: number; + aiScore: number; + scoreExplanation: string; +} + +export interface AIResult { + aiId: number; + perfId: number; + aiScoreDetail: AIScoreItem[]; + aiTotalScore: number; + aiProblems: string[]; + aiSuggestions: string[]; + createTime: string; +} + +export interface PerformanceRecord { + perfId: number; + userId: number; + month: string; + status: 'draft' | 'submitted' | 'under_review' | 'completed' | 'rejected'; + selfScore?: number; + aiScore?: number; + managerScore?: number; + totalScore?: number; + level?: 'excellent' | 'qualified' | 'need_motivation' | 'unqualified'; + rewardPunish?: string; + workSummary?: string; + submitTime?: string; + reviewTime?: string; + reviewOpinion?: string; + performanceItems?: PerformanceItemDetail[]; + attendance?: AttendanceData & { attendanceScore?: number }; + aiResult?: AIResult; +} + +export interface PerformanceItemDetail extends PerformanceItemDTO { + itemId: number; + itemCategory: 'business' | 'comprehensive'; + aiScore?: number; + aiExplanation?: string; + managerScore?: number; + managerExplanation?: string; +} + +export interface PageResult { + list: T[]; + total: number; + page: number; + pageSize: number; +} + +export const performanceApi = { + submit: async (data: PerformanceSubmitDTO): Promise<{ perfId: number }> => { + const { data: res } = await http.post('/api/performance/submit', data); + return res.data; // 解包 data.data + }, + + getMyList: async (params?: { + month?: string; + page?: number; + pageSize?: number; + }): Promise> => { + const { data } = await http.get('/api/performance/employee/get', { params }); + // 后端返回格式: { code, message, data: { total, records } } + // 转换为前端期望的格式: { list, total, page, pageSize } + return { + list: data.data.records || [], + total: data.data.total || 0, + page: params?.page || 1, + pageSize: params?.pageSize || 10, + }; + }, + + getDetail: async (perfId: number): Promise => { + const { data } = await http.get(`/api/performance/employee/get`, { + params: { perfId }, + }); + return data.data; + }, + + requestModification: async (perfId: number, reason: string): Promise => { + await http.post('/api/performance/request-modification', { perfId, reason }); + }, + + // 查询 AI 评分是否完成 + checkAIResult: async (perfId: number): Promise<{ done: boolean; aiScore?: number }> => { + const { data } = await http.get('/api/performance/employee/get', { params: { perfId } }); + const rec = data.data; + return { + done: rec?.aiScore != null, + aiScore: rec?.aiScore, + }; + }, +}; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx new file mode 100644 index 0000000..2a7197a --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,55 @@ +import React, { createContext, useContext, useState, useCallback } from 'react'; + +export interface UserInfo { + userId: number; + name: string; + role: 'employee' | 'manager' | 'generalManager'; + department: string; + position: string; +} + +interface AuthContextValue { + token: string | null; + userInfo: UserInfo | null; + login: (token: string, userInfo: UserInfo) => void; + logout: () => void; +} + +const AuthContext = createContext(null); + +const TOKEN_KEY = 'auth_token'; +const USER_KEY = 'auth_user'; + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY)); + const [userInfo, setUserInfo] = useState(() => { + const stored = localStorage.getItem(USER_KEY); + return stored ? JSON.parse(stored) : null; + }); + + const login = useCallback((newToken: string, newUserInfo: UserInfo) => { + localStorage.setItem(TOKEN_KEY, newToken); + localStorage.setItem(USER_KEY, JSON.stringify(newUserInfo)); + setToken(newToken); + setUserInfo(newUserInfo); + }, []); + + const logout = useCallback(() => { + localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(USER_KEY); + setToken(null); + setUserInfo(null); + }, []); + + return ( + + {children} + + ); +}; + +export const useAuth = (): AuthContextValue => { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error('useAuth must be used within AuthProvider'); + return ctx; +}; diff --git a/frontend/src/hooks/useBreakpoint.ts b/frontend/src/hooks/useBreakpoint.ts new file mode 100644 index 0000000..d9a208e --- /dev/null +++ b/frontend/src/hooks/useBreakpoint.ts @@ -0,0 +1,13 @@ +import { useState, useEffect } from 'react'; + +export function useIsMobile(): boolean { + const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768); + + useEffect(() => { + const handler = () => setIsMobile(window.innerWidth < 768); + window.addEventListener('resize', handler); + return () => window.removeEventListener('resize', handler); + }, []); + + return isMobile; +} diff --git a/frontend/src/img/logo.png b/frontend/src/img/logo.png new file mode 100644 index 0000000..d1869d2 Binary files /dev/null and b/frontend/src/img/logo.png differ diff --git a/frontend/src/img/logo2.png b/frontend/src/img/logo2.png new file mode 100644 index 0000000..3d6f4be Binary files /dev/null and b/frontend/src/img/logo2.png differ diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..7da97d7 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import 'antd/dist/reset.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..3b7c5f1 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,179 @@ +import React, { useState } from 'react'; +import { Form, Input, Button, Select, Alert, Typography } from 'antd'; +import { UserOutlined, LockOutlined, TeamOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { authApi } from '../api/auth'; +import logo from '../img/logo2.png'; + +const { Option } = Select; +const { Text } = Typography; + +interface LoginFormValues { + username: string; + password: string; + role: 'employee' | 'manager' | 'generalManager'; +} + +const roleRedirect: Record = { + employee: '/employee', + manager: '/manager', + generalManager: '/gm', +}; + +const LoginPage: React.FC = () => { + const { login } = useAuth(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [alertInfo, setAlertInfo] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); + + const onFinish = async (values: LoginFormValues) => { + setLoading(true); + setAlertInfo(null); + try { + const { token, userInfo } = await authApi.login(values.username, values.password, values.role); + login(token, userInfo); + setAlertInfo({ type: 'success', msg: `欢迎回来,${userInfo.name}!正在跳转...` }); + setTimeout(() => navigate(roleRedirect[userInfo.role], { replace: true }), 800); + } catch (err: any) { + setAlertInfo({ type: 'error', msg: err?.response?.data?.message || '登录失败,请检查用户名、密码和角色' }); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* 光晕装饰 */} +
+
+
+ + {/* 双层纹理叠层:点阵 + 斜线 */} +
+ +
+ {/* Logo + 标题 */} +
+ logo +
员工月度绩效考核系统
+
+ + {/* 提示信息 */} + {alertInfo && ( + + )} + + + name="login" + onFinish={onFinish} + initialValues={{ role: 'employee' }} + size="large" + > + + } + placeholder="用户名(工号)" + style={styles.input} + /> + + + + } + placeholder="密码" + style={styles.input} + /> + + + + + + + + + + + +
+ 优一科技 © 2026 +
+
+
+ ); +}; + +const styles: Record = { + bg: { + minHeight: '100vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: 'linear-gradient(135deg, #f0f7ff 0%, #e8f0fe 35%, #fce4ec 70%, #f3e5f5 100%)', + position: 'relative', + overflow: 'hidden', + padding: 16, + }, + circle: { + position: 'absolute', + borderRadius: '50%', + background: 'radial-gradient(circle, rgba(99,102,241,0.12) 0%, transparent 70%)', + filter: 'blur(40px)', + }, + card: { + width: '100%', + maxWidth: 400, + background: '#ffffff', + borderRadius: 20, + padding: '36px 32px 28px', + boxShadow: '0 4px 24px rgba(99,102,241,0.12), 0 1px 4px rgba(0,0,0,0.06)', + position: 'relative', + zIndex: 1, + border: '1px solid rgba(99,102,241,0.08)', + }, + input: { + borderRadius: 8, + height: 44, + }, + btn: { + height: 46, + borderRadius: 10, + fontSize: 16, + fontWeight: 600, + background: 'linear-gradient(90deg, #6366f1, #8b5cf6)', + border: 'none', + letterSpacing: 2, + }, +}; + +export default LoginPage; diff --git a/frontend/src/pages/employee/Dashboard.tsx b/frontend/src/pages/employee/Dashboard.tsx new file mode 100644 index 0000000..c3e9d18 --- /dev/null +++ b/frontend/src/pages/employee/Dashboard.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { Routes, Route, Link, useLocation, useNavigate } from 'react-router-dom'; +import { Layout, Menu, Typography, Button } from 'antd'; +import { HomeOutlined, FormOutlined, HistoryOutlined, LogoutOutlined } from '@ant-design/icons'; +import PerformanceForm from './PerformanceForm'; +import PerformanceHistory from './PerformanceHistory'; +import { useIsMobile } from '../../hooks/useBreakpoint'; +import { useAuth } from '../../context/AuthContext'; +import logo from '../../img/logo.png'; + +const { Header, Content, Sider } = Layout; +const { Title } = Typography; + +const menuItems = [ + { key: '/employee', icon: , label: 首页, mobileLabel: '首页' }, + { key: '/employee/form', icon: , label: 绩效填报, mobileLabel: '填报' }, + { key: '/employee/history', icon: , label: 绩效历史, mobileLabel: '历史' }, +]; + +const EmployeeDashboard: React.FC = () => { + const location = useLocation(); + const navigate = useNavigate(); + const isMobile = useIsMobile(); + const { logout } = useAuth(); + + const selectedKey = menuItems.find(item => + item.key !== '/employee' ? location.pathname.startsWith(item.key) : location.pathname === item.key + )?.key ?? '/employee'; + + const handleLogout = () => { + logout(); + navigate('/login'); + }; + + return ( + +
+
+ logo + {!isMobile && 绩效考核系统} +
+ +
+ + + {!isMobile && ( + + ({ key, icon, label }))} + /> + + )} + + + + } /> + } /> + } /> + + + + + + {/* 移动端底部导航 */} + {isMobile && ( +
+ {menuItems.map(item => ( + + {item.icon} + {item.mobileLabel} + + ))} +
+ )} + + ); +}; + +const HomePage: React.FC = () => ( +
+ 欢迎使用员工绩效考核系统 + 请从菜单选择功能: +
    +
  • 绩效填报:填写月度绩效考核内容
  • +
  • 绩效历史:查看历史绩效记录和详情
  • +
+
+); + +export default EmployeeDashboard; diff --git a/frontend/src/pages/employee/PerformanceForm.tsx b/frontend/src/pages/employee/PerformanceForm.tsx new file mode 100644 index 0000000..16560c5 --- /dev/null +++ b/frontend/src/pages/employee/PerformanceForm.tsx @@ -0,0 +1,427 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { + Form, + Input, + InputNumber, + Button, + Card, + Divider, + Space, + Typography, + message, + Tag, + Row, + Col, + Alert, + DatePicker, + Result, + Spin, +} from 'antd'; +import { SaveOutlined, SendOutlined, LoadingOutlined, CheckCircleOutlined } from '@ant-design/icons'; +import { useAuth } from '../../context/AuthContext'; +import { performanceApi, PerformanceSubmitDTO } from '../../api/performance'; +import { ALL_PERFORMANCE_ITEMS } from './performanceItems'; +import dayjs, { Dayjs } from 'dayjs'; + +const { Title, Text } = Typography; +const { TextArea } = Input; + +interface FormValues { + assessmentMonth: Dayjs; + workSummary: string; + leave: number; + late: number; + absent: number; + lackCard: number; + attendanceRemark?: string; + items: Array<{ + userContent: string; + selfScore: number; + evidence?: string; + }>; +} + +const PerformanceForm: React.FC = () => { + const { userInfo } = useAuth(); + const [form] = Form.useForm(); + const [submitting, setSubmitting] = useState(false); + const [saving, setSaving] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [aiWaiting, setAiWaiting] = useState(false); + const [aiDone, setAiDone] = useState(false); + const pollTimerRef = useRef | null>(null); + const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; + + // 清理轮询定时器 + useEffect(() => { + return () => { + if (pollTimerRef.current) clearInterval(pollTimerRef.current); + }; + }, []); + + // 轮询 AI 结果 + const pollAIResult = (perfId: number) => { + setAiWaiting(true); + let attempts = 0; + const maxAttempts = 40; // 最多轮询 40 次(约 2 分钟) + + pollTimerRef.current = setInterval(async () => { + attempts++; + try { + const result = await performanceApi.checkAIResult(perfId); + if (result.done) { + clearInterval(pollTimerRef.current!); + setAiWaiting(false); + setAiDone(true); + } else if (attempts >= maxAttempts) { + clearInterval(pollTimerRef.current!); + setAiWaiting(false); + setAiDone(true); // 超时也跳转,让用户去历史记录查看 + } + } catch { + // 忽略轮询错误,继续重试 + } + }, 3000); // 每 3 秒轮询一次 + }; + + // 获取本月作为默认值 + const getPreviousMonth = (): Dayjs => { + return dayjs(); + }; + + const buildDTO = (values: FormValues, status: 'draft' | 'submitted'): PerformanceSubmitDTO => ({ + month: values.assessmentMonth.format('YYYY-MM'), + status, + workSummary: values.workSummary || '', + attendance: { + leave: values.leave ?? 0, + late: values.late ?? 0, + absent: values.absent ?? 0, + lackCard: values.lackCard ?? 0, + remark: values.attendanceRemark, + }, + performanceItems: ALL_PERFORMANCE_ITEMS.map((item, idx) => ({ + itemName: item.itemName, + weight: item.weight, + userContent: values.items?.[idx]?.userContent || '', + selfScore: values.items?.[idx]?.selfScore ?? 0, + evidence: values.items?.[idx]?.evidence, + })), + }); + + const handleSave = async () => { + const values = form.getFieldsValue(); + setSaving(true); + try { + await performanceApi.submit(buildDTO(values, 'draft')); + message.success('草稿已暂存'); + } catch (err: any) { + message.error(err?.response?.data?.message || '暂存失败,请重试'); + } finally { + setSaving(false); + } + }; + + const handleSubmit = async (values: FormValues) => { + setSubmitting(true); + try { + const result = await performanceApi.submit(buildDTO(values, 'submitted')); + setSubmitted(true); + form.resetFields(); + // 提交成功后开始轮询 AI 结果 + if (result?.perfId) { + pollAIResult(result.perfId); + } + } catch (err: any) { + message.error(err?.response?.data?.message || '提交失败,请重试'); + } finally { + setSubmitting(false); + } + }; + + if (submitted) { + // AI 分析完成 + if (aiDone) { + return ( + } + title="AI 分析完成!" + subTitle="绩效已提交,AI 评分已生成,可前往历史记录查看详情。" + style={{ margin: 24 }} + extra={ + + + + } + /> + ); + } + + // AI 分析中 + if (aiWaiting) { + return ( +
+ } /> +
+ AI 正在分析中,请稍等! + + 系统正在对您的绩效数据进行 AI 智能评分,通常需要 30 秒至 1 分钟... + +
+
+ ); + } + + // 提交成功但还未开始轮询(过渡状态) + return ( + setSubmitted(false)}>重新填报 + } + /> + ); + } + + return ( +
+ 绩效填报 + + {/* 基础信息 */} + + {isMobile ? ( + +
姓名:{userInfo?.name}
+
部门:{userInfo?.department}
+
岗位:{userInfo?.position}
+
+ ) : ( + + 姓名:{userInfo?.name} + 部门:{userInfo?.department} + 岗位:{userInfo?.position} + + )} +
+ +
({ selfScore: 0 })), + }} + > + {/* 考核月份选择 */} + + + { + // 不能选择未来的月份 + return current && current > dayjs().endOf('month'); + }} + /> + + + {/* 考核指标 */} + + + + {/* 业务素质 */} + + 业务素质考评 <Tag color="blue">占总分 70%</Tag> + + {ALL_PERFORMANCE_ITEMS.filter(i => i.category === 'business').map((item, idx) => ( + + {item.itemName} + 权重 {item.weight} 分 + + } + > + + {item.description} + + + + +