Compare commits
No commits in common. "main" and "20260424-171648" have entirely different histories.
main
...
20260424-1
|
|
@ -1,29 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(AttendanceSystem VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# MSVC UTF-8 编译选项
|
||||
if (MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
endif()
|
||||
|
||||
# 主程序可执行文件
|
||||
add_executable(attendance_system
|
||||
src/main.cpp
|
||||
src/app.cpp
|
||||
include/app.hpp
|
||||
)
|
||||
|
||||
target_include_directories(attendance_system PRIVATE include)
|
||||
|
||||
# 测试可执行文件
|
||||
add_executable(basic_test
|
||||
tests/basic_test.cpp
|
||||
src/app.cpp
|
||||
include/app.hpp
|
||||
)
|
||||
|
||||
target_include_directories(basic_test PRIVATE include)
|
||||
70
README.md
70
README.md
|
|
@ -1,51 +1,47 @@
|
|||
# 智能考勤管理系统 (Attendance System)
|
||||
# 单体示例工程
|
||||
|
||||
## 概述
|
||||
本工程实现了一个精简但结构清晰的智能考勤管理系统,涵盖:
|
||||
- 多终端打卡数据接收与处理
|
||||
- 自动化工时核算与异常标记
|
||||
- 考勤报表生成
|
||||
- RBAC 权限与数据安全模型
|
||||
一个基于 FastAPI 的简单待办事项(Todo)管理服务,使用内存假数据存储。
|
||||
|
||||
## 项目结构
|
||||
```
|
||||
AttendanceSystem/
|
||||
├── CMakeLists.txt # 构建配置(C++17, MSVC UTF-8)
|
||||
├── README.md # 本文件
|
||||
├── include/
|
||||
│ └── app.hpp # 核心数据结构与业务接口声明
|
||||
├── src/
|
||||
│ ├── app.cpp # 业务逻辑实现
|
||||
│ └── main.cpp # 命令行入口(功能演示)
|
||||
└── tests/
|
||||
└── basic_test.cpp # 单元测试(使用标准 assert)
|
||||
```
|
||||
## 功能
|
||||
|
||||
## 编译与运行
|
||||
- 获取所有待办事项列表
|
||||
- 根据 ID 获取单个待办事项
|
||||
- 创建新的待办事项
|
||||
- 更新待办事项
|
||||
- 删除待办事项
|
||||
|
||||
## 安装依赖
|
||||
|
||||
### 使用 CMake
|
||||
```bash
|
||||
cd AttendanceSystem
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 运行主程序
|
||||
## 启动服务
|
||||
|
||||
```bash
|
||||
./attendance_system
|
||||
uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
访问 http://127.0.0.1:8000/docs 查看自动生成的 API 文档(Swagger UI)。
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
./basic_test
|
||||
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
## 核心设计
|
||||
- **员工管理**:支持员工信息、班次、部门维护
|
||||
- **打卡处理**:统一打卡协议,校验时间戳与设备标识
|
||||
- **考勤核算**:根据排班规则与节假日自动计算工时、标记异常
|
||||
- **报表导出**:支持按部门与时间范围生成统计报表
|
||||
- **安全模型**:RBAC 权限控制,敏感数据脱敏
|
||||
## 快速验证
|
||||
|
||||
> 本工程为 C++ 示范实现,重点关注数据结构的清晰与业务逻辑的可读性。
|
||||
启动服务后,在另一个终端执行:
|
||||
|
||||
```bash
|
||||
# 获取所有待办事项
|
||||
curl http://127.0.0.1:8000/todos
|
||||
|
||||
# 创建新待办事项
|
||||
curl -X POST http://127.0.0.1:8000/todos \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title": "学习 FastAPI", "completed": false}'
|
||||
```
|
||||
|
|
|
|||
15841
events.ndjson
15841
events.ndjson
File diff suppressed because one or more lines are too long
240
include/app.hpp
240
include/app.hpp
|
|
@ -1,240 +0,0 @@
|
|||
#ifndef ATTENDANCE_SYSTEM_APP_HPP
|
||||
#define ATTENDANCE_SYSTEM_APP_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
|
||||
// ============================================================
|
||||
// 常量与枚举
|
||||
// ============================================================
|
||||
|
||||
/// 打卡类型
|
||||
enum class CheckType {
|
||||
Unknown,
|
||||
ClockIn, // 上班打卡
|
||||
ClockOut // 下班打卡
|
||||
};
|
||||
|
||||
/// 异常标记
|
||||
enum class AbnormalFlag {
|
||||
None,
|
||||
Late, // 迟到
|
||||
EarlyLeave, // 早退
|
||||
Absenteeism // 旷工
|
||||
};
|
||||
|
||||
/// 报表输出格式
|
||||
enum class ReportFormat {
|
||||
Excel,
|
||||
PDF
|
||||
};
|
||||
|
||||
/// 预警发送状态
|
||||
enum class AlertStatus {
|
||||
Pending,
|
||||
Sent,
|
||||
Failed,
|
||||
Read
|
||||
};
|
||||
|
||||
/// 角色类型(RBAC)
|
||||
enum class RoleType {
|
||||
Employee,
|
||||
DeptManager,
|
||||
HRAdmin,
|
||||
SystemAdmin
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 数据结构定义
|
||||
// ============================================================
|
||||
|
||||
/// 员工信息
|
||||
struct Employee {
|
||||
std::string employeeId;
|
||||
std::string name;
|
||||
std::string department;
|
||||
std::string position;
|
||||
std::string shiftId;
|
||||
std::string phone;
|
||||
RoleType role{RoleType::Employee};
|
||||
|
||||
// 脱敏手机号显示(后4位明文)
|
||||
std::string maskedPhone() const {
|
||||
if (phone.length() != 11) return "***";
|
||||
return "*******" + phone.substr(7);
|
||||
}
|
||||
};
|
||||
|
||||
/// 排班规则
|
||||
struct ShiftRule {
|
||||
std::string shiftId;
|
||||
std::string shiftName;
|
||||
int startHour{9}; // 上班小时
|
||||
int startMin{0}; // 上班分钟
|
||||
int endHour{18}; // 下班小时
|
||||
int endMin{0}; // 下班分钟
|
||||
int flexWindow{30}; // 弹性时间窗口(分钟)
|
||||
int graceLate{15}; // 迟到宽限分钟
|
||||
|
||||
/// 获取上班时间(分钟偏移量)
|
||||
int workStartMinutes() const { return startHour * 60 + startMin; }
|
||||
|
||||
/// 获取下班时间(分钟偏移量)
|
||||
int workEndMinutes() const { return endHour * 60 + endMin; }
|
||||
};
|
||||
|
||||
/// 打卡流水记录
|
||||
struct CheckInRecord {
|
||||
std::string recordId;
|
||||
std::string employeeId;
|
||||
std::time_t timestamp{0};
|
||||
CheckType type{CheckType::Unknown};
|
||||
std::string deviceId;
|
||||
std::string latitude;
|
||||
std::string longitude;
|
||||
bool verified{false};
|
||||
};
|
||||
|
||||
/// 考勤核算结果(按天)
|
||||
struct DailyAttendance {
|
||||
std::string dateStr;
|
||||
std::string employeeId;
|
||||
int expectedMinutes{0}; // 应出勤分钟
|
||||
int actualMinutes{0}; // 实际出勤分钟
|
||||
int overtimeMinutes{0}; // 加班分钟
|
||||
int leaveDeduction{0}; // 调休抵扣分钟
|
||||
AbnormalFlag abnormal{AbnormalFlag::None};
|
||||
|
||||
/// 有效出勤率(%)
|
||||
double attendanceRate() const {
|
||||
if (expectedMinutes == 0) return 100.0;
|
||||
return 100.0 * actualMinutes / expectedMinutes;
|
||||
}
|
||||
|
||||
/// 是否异常
|
||||
bool hasAbnormal() const { return abnormal != AbnormalFlag::None; }
|
||||
|
||||
std::string abnormalStr() const {
|
||||
switch (abnormal) {
|
||||
case AbnormalFlag::Late: return "迟到";
|
||||
case AbnormalFlag::EarlyLeave: return "早退";
|
||||
case AbnormalFlag::Absenteeism: return "旷工";
|
||||
default: return "正常";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// 异常预警日志
|
||||
struct AlertLog {
|
||||
std::string alertId;
|
||||
std::string employeeId;
|
||||
AbnormalFlag type{AbnormalFlag::None};
|
||||
std::time_t occurTime{0};
|
||||
std::string channel; // 通知渠道:站内信/短信/企业微信
|
||||
AlertStatus status{AlertStatus::Pending};
|
||||
int retryCount{0};
|
||||
};
|
||||
|
||||
/// 报表任务
|
||||
struct ReportTask {
|
||||
std::string taskId;
|
||||
std::string requester;
|
||||
std::string deptId;
|
||||
std::string dateRangeBegin;
|
||||
std::string dateRangeEnd;
|
||||
ReportFormat format{ReportFormat::Excel};
|
||||
std::string status{"Pending"}; // Pending / Running / Done / Failed
|
||||
std::string downloadUrl;
|
||||
std::time_t createTime{0};
|
||||
};
|
||||
|
||||
/// 权限配置(RBAC)
|
||||
struct Permission {
|
||||
std::string resource; // 资源路径,如 "/api/v1/reports"
|
||||
std::string action; // 操作:READ / WRITE / DELETE
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 业务逻辑类
|
||||
// ============================================================
|
||||
|
||||
class AttendanceService {
|
||||
public:
|
||||
AttendanceService();
|
||||
|
||||
// ---- 员工管理 ----
|
||||
void addEmployee(const Employee& emp);
|
||||
const Employee* findEmployee(const std::string& id) const;
|
||||
std::vector<Employee> getEmployeesByDept(const std::string& dept) const;
|
||||
|
||||
// ---- 排班管理 ----
|
||||
void addShiftRule(const ShiftRule& rule);
|
||||
const ShiftRule* findShift(const std::string& shiftId) const;
|
||||
|
||||
// ---- 打卡处理 ----
|
||||
CheckInRecord processCheckIn(const std::string& employeeId,
|
||||
CheckType type,
|
||||
const std::string& deviceId,
|
||||
std::time_t timestamp);
|
||||
|
||||
std::vector<CheckInRecord> getRecords(const std::string& employeeId) const;
|
||||
|
||||
// ---- 考勤核算 ----
|
||||
DailyAttendance calculateDaily(const std::string& employeeId,
|
||||
const std::string& dateStr);
|
||||
|
||||
std::vector<DailyAttendance> generateReport(const std::string& deptId,
|
||||
const std::string& dateBegin,
|
||||
const std::string& dateEnd);
|
||||
|
||||
// ---- 异常预警 ----
|
||||
AlertLog createAlert(const std::string& employeeId, AbnormalFlag type);
|
||||
std::vector<AlertLog> getAlerts(const std::string& employeeId) const;
|
||||
|
||||
// ---- 报表任务 ----
|
||||
ReportTask createReportTask(const std::string& requester,
|
||||
const std::string& deptId,
|
||||
const std::string& dateBegin,
|
||||
const std::string& dateEnd,
|
||||
ReportFormat fmt);
|
||||
void completeTask(const std::string& taskId, const std::string& url);
|
||||
|
||||
// ---- 工具 ----
|
||||
static std::string timeToString(std::time_t t);
|
||||
static std::string currentDateStr();
|
||||
static std::time_t parseDate(const std::string& dateStr);
|
||||
|
||||
private:
|
||||
std::map<std::string, Employee> employees_;
|
||||
std::map<std::string, ShiftRule> shifts_;
|
||||
std::vector<CheckInRecord> records_;
|
||||
std::vector<AlertLog> alerts_;
|
||||
std::vector<ReportTask> tasks_;
|
||||
int idCounter_{0};
|
||||
|
||||
std::string nextId();
|
||||
int minutesFromMidnight(std::time_t t) const;
|
||||
bool isSameDate(std::time_t t, const std::string& dateStr) const;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 工具函数声明
|
||||
// ============================================================
|
||||
|
||||
/// 脱敏工具:将敏感字符串中间部分替换为 '*'
|
||||
std::string maskSensitive(const std::string& input, size_t visibleHead = 2, size_t visibleTail = 2);
|
||||
|
||||
/// 格式化输出一行分隔线
|
||||
void printSeparator(char ch = '=', size_t count = 60);
|
||||
|
||||
#endif // ATTENDANCE_SYSTEM_APP_HPP
|
||||
345
src/app.cpp
345
src/app.cpp
|
|
@ -1,345 +0,0 @@
|
|||
#include "app.hpp"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
|
||||
// ============================================================
|
||||
// 工具函数实现
|
||||
// ============================================================
|
||||
|
||||
std::string AttendanceService::timeToString(std::time_t t) {
|
||||
std::tm* tm = std::localtime(&t);
|
||||
if (!tm) return "InvalidTime";
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d %H:%M:%S");
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string AttendanceService::currentDateStr() {
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* tm = std::localtime(&now);
|
||||
if (!tm) return "InvalidDate";
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::time_t AttendanceService::parseDate(const std::string& dateStr) {
|
||||
std::tm tm = {};
|
||||
std::istringstream iss(dateStr);
|
||||
iss >> std::get_time(&tm, "%Y-%m-%d");
|
||||
if (iss.fail()) return 0;
|
||||
return std::mktime(&tm);
|
||||
}
|
||||
|
||||
std::string maskSensitive(const std::string& input, size_t visibleHead, size_t visibleTail) {
|
||||
if (input.length() <= visibleHead + visibleTail) return input;
|
||||
std::string result = input.substr(0, visibleHead);
|
||||
result.append(input.length() - visibleHead - visibleTail, '*');
|
||||
result.append(input.substr(input.length() - visibleTail));
|
||||
return result;
|
||||
}
|
||||
|
||||
void printSeparator(char ch, size_t count) {
|
||||
std::cout << std::string(count, ch) << std::endl;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AttendanceService 实现
|
||||
// ============================================================
|
||||
|
||||
AttendanceService::AttendanceService() {
|
||||
// 初始化默认排班
|
||||
ShiftRule defaultShift;
|
||||
defaultShift.shiftId = "S001";
|
||||
defaultShift.shiftName = "标准白班";
|
||||
defaultShift.startHour = 9;
|
||||
defaultShift.startMin = 0;
|
||||
defaultShift.endHour = 18;
|
||||
defaultShift.endMin = 0;
|
||||
defaultShift.flexWindow = 30;
|
||||
defaultShift.graceLate = 15;
|
||||
shifts_[defaultShift.shiftId] = defaultShift;
|
||||
|
||||
// 初始化默认管理员
|
||||
Employee admin;
|
||||
admin.employeeId = "ADMIN001";
|
||||
admin.name = "系统管理员";
|
||||
admin.department = "技术部";
|
||||
admin.position = "运维工程师";
|
||||
admin.shiftId = "S001";
|
||||
admin.phone = "13800138000";
|
||||
admin.role = RoleType::SystemAdmin;
|
||||
employees_[admin.employeeId] = admin;
|
||||
}
|
||||
|
||||
std::string AttendanceService::nextId() {
|
||||
return "ID" + std::to_string(++idCounter_);
|
||||
}
|
||||
|
||||
// ---- 员工管理 ----
|
||||
|
||||
void AttendanceService::addEmployee(const Employee& emp) {
|
||||
if (employees_.count(emp.employeeId) > 0) {
|
||||
throw std::runtime_error("员工已存在: " + emp.employeeId);
|
||||
}
|
||||
employees_[emp.employeeId] = emp;
|
||||
}
|
||||
|
||||
const Employee* AttendanceService::findEmployee(const std::string& id) const {
|
||||
auto it = employees_.find(id);
|
||||
return (it != employees_.end()) ? &(it->second) : nullptr;
|
||||
}
|
||||
|
||||
std::vector<Employee> AttendanceService::getEmployeesByDept(const std::string& dept) const {
|
||||
std::vector<Employee> result;
|
||||
for (const auto& [id, emp] : employees_) {
|
||||
if (emp.department == dept) {
|
||||
result.push_back(emp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 排班管理 ----
|
||||
|
||||
void AttendanceService::addShiftRule(const ShiftRule& rule) {
|
||||
shifts_[rule.shiftId] = rule;
|
||||
}
|
||||
|
||||
const ShiftRule* AttendanceService::findShift(const std::string& shiftId) const {
|
||||
auto it = shifts_.find(shiftId);
|
||||
return (it != shifts_.end()) ? &(it->second) : nullptr;
|
||||
}
|
||||
|
||||
// ---- 打卡处理 ----
|
||||
|
||||
CheckInRecord AttendanceService::processCheckIn(const std::string& employeeId,
|
||||
CheckType type,
|
||||
const std::string& deviceId,
|
||||
std::time_t timestamp) {
|
||||
if (employees_.count(employeeId) == 0) {
|
||||
throw std::runtime_error("未知员工: " + employeeId);
|
||||
}
|
||||
|
||||
CheckInRecord rec;
|
||||
rec.recordId = nextId();
|
||||
rec.employeeId = employeeId;
|
||||
rec.timestamp = (timestamp == 0) ? std::time(nullptr) : timestamp;
|
||||
rec.type = type;
|
||||
rec.deviceId = deviceId;
|
||||
rec.verified = true;
|
||||
|
||||
records_.push_back(rec);
|
||||
return rec;
|
||||
}
|
||||
|
||||
std::vector<CheckInRecord> AttendanceService::getRecords(const std::string& employeeId) const {
|
||||
std::vector<CheckInRecord> result;
|
||||
for (const auto& r : records_) {
|
||||
if (r.employeeId == employeeId) {
|
||||
result.push_back(r);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 辅助方法 ----
|
||||
|
||||
int AttendanceService::minutesFromMidnight(std::time_t t) const {
|
||||
std::tm* tm = std::localtime(&t);
|
||||
if (!tm) return 0;
|
||||
return tm->tm_hour * 60 + tm->tm_min;
|
||||
}
|
||||
|
||||
bool AttendanceService::isSameDate(std::time_t t, const std::string& dateStr) const {
|
||||
std::tm* tm = std::localtime(&t);
|
||||
if (!tm) return false;
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
return oss.str() == dateStr;
|
||||
}
|
||||
|
||||
// ---- 考勤核算 ----
|
||||
|
||||
DailyAttendance AttendanceService::calculateDaily(const std::string& employeeId,
|
||||
const std::string& dateStr) {
|
||||
const Employee* emp = findEmployee(employeeId);
|
||||
if (!emp) {
|
||||
throw std::runtime_error("员工不存在: " + employeeId);
|
||||
}
|
||||
|
||||
const ShiftRule* shift = findShift(emp->shiftId);
|
||||
if (!shift) {
|
||||
throw std::runtime_error("未找到排班: " + emp->shiftId);
|
||||
}
|
||||
|
||||
DailyAttendance da;
|
||||
da.dateStr = dateStr;
|
||||
da.employeeId = employeeId;
|
||||
|
||||
// 应出勤时长(标准工时,不含午休,设为8小时)
|
||||
da.expectedMinutes = (shift->workEndMinutes() - shift->workStartMinutes()) - 60; // 减1小时午休
|
||||
if (da.expectedMinutes < 0) da.expectedMinutes = 480; // 默认8小时
|
||||
|
||||
// 查找该日所有打卡记录
|
||||
std::vector<const CheckInRecord*> dailyRecords;
|
||||
for (const auto& r : records_) {
|
||||
if (r.employeeId == employeeId && isSameDate(r.timestamp, dateStr)) {
|
||||
dailyRecords.push_back(&r);
|
||||
}
|
||||
}
|
||||
|
||||
// 找出最早的上班打卡和最晚的下班打卡
|
||||
std::time_t firstClockIn = 0;
|
||||
std::time_t lastClockOut = 0;
|
||||
|
||||
for (const auto* r : dailyRecords) {
|
||||
if (r->type == CheckType::ClockIn) {
|
||||
if (firstClockIn == 0 || r->timestamp < firstClockIn)
|
||||
firstClockIn = r->timestamp;
|
||||
} else if (r->type == CheckType::ClockOut) {
|
||||
if (r->timestamp > lastClockOut)
|
||||
lastClockOut = r->timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果存在有效打卡,计算实际工时
|
||||
if (firstClockIn != 0 && lastClockOut != 0 && lastClockOut > firstClockIn) {
|
||||
int diffMinutes = static_cast<int>((lastClockOut - firstClockIn) / 60);
|
||||
// 扣除午休1小时
|
||||
da.actualMinutes = (diffMinutes > 60) ? (diffMinutes - 60) : diffMinutes;
|
||||
|
||||
// 加班判定(超过应出勤部分)
|
||||
if (da.actualMinutes > da.expectedMinutes) {
|
||||
da.overtimeMinutes = da.actualMinutes - da.expectedMinutes;
|
||||
} else {
|
||||
da.overtimeMinutes = 0;
|
||||
}
|
||||
|
||||
// 异常判定:是否有上班打卡
|
||||
bool hasClockIn = false;
|
||||
bool hasClockOut = false;
|
||||
for (const auto* r : dailyRecords) {
|
||||
if (r->type == CheckType::ClockIn) hasClockIn = true;
|
||||
if (r->type == CheckType::ClockOut) hasClockOut = true;
|
||||
}
|
||||
|
||||
if (!hasClockIn || !hasClockOut) {
|
||||
da.abnormal = AbnormalFlag::Absenteeism;
|
||||
} else {
|
||||
// 迟到判定:上班打卡时间超过规定上班时间+弹性窗口
|
||||
int actualStartMin = minutesFromMidnight(firstClockIn);
|
||||
int expectedStartWithFlex = shift->workStartMinutes() + shift->flexWindow;
|
||||
if (actualStartMin > expectedStartWithFlex) {
|
||||
da.abnormal = AbnormalFlag::Late;
|
||||
}
|
||||
|
||||
// 早退判定:下班打卡时间早于规定下班时间
|
||||
int actualEndMin = minutesFromMidnight(lastClockOut);
|
||||
if (actualEndMin < shift->workEndMinutes() && da.abnormal == AbnormalFlag::None) {
|
||||
da.abnormal = AbnormalFlag::EarlyLeave;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
da.actualMinutes = 0;
|
||||
da.overtimeMinutes = 0;
|
||||
// 没有打卡记录视为旷工
|
||||
if (dailyRecords.empty()) {
|
||||
da.abnormal = AbnormalFlag::Absenteeism;
|
||||
}
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
std::vector<DailyAttendance> AttendanceService::generateReport(const std::string& deptId,
|
||||
const std::string& dateBegin,
|
||||
const std::string& dateEnd) {
|
||||
std::vector<DailyAttendance> report;
|
||||
std::vector<Employee> deptEmployees = getEmployeesByDept(deptId);
|
||||
|
||||
// 解析日期区间
|
||||
std::time_t beginT = parseDate(dateBegin);
|
||||
std::time_t endT = parseDate(dateEnd);
|
||||
if (beginT == 0 || endT == 0 || endT < beginT) {
|
||||
throw std::runtime_error("无效日期范围");
|
||||
}
|
||||
|
||||
// 遍历每日
|
||||
for (std::time_t day = beginT; day <= endT; day += 86400) {
|
||||
std::tm* tm = std::localtime(&day);
|
||||
if (!tm) continue;
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
std::string dateStr = oss.str();
|
||||
|
||||
// 跳过周末(简化处理,实际应根据节假日配置)
|
||||
if (tm->tm_wday == 0 || tm->tm_wday == 6) continue;
|
||||
|
||||
for (const auto& emp : deptEmployees) {
|
||||
DailyAttendance da = calculateDaily(emp.employeeId, dateStr);
|
||||
report.push_back(da);
|
||||
}
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
// ---- 异常预警 ----
|
||||
|
||||
AlertLog AttendanceService::createAlert(const std::string& employeeId, AbnormalFlag type) {
|
||||
AlertLog alert;
|
||||
alert.alertId = nextId();
|
||||
alert.employeeId = employeeId;
|
||||
alert.type = type;
|
||||
alert.occurTime = std::time(nullptr);
|
||||
alert.channel = "站内信";
|
||||
alert.status = AlertStatus::Pending;
|
||||
alert.retryCount = 0;
|
||||
alerts_.push_back(alert);
|
||||
return alert;
|
||||
}
|
||||
|
||||
std::vector<AlertLog> AttendanceService::getAlerts(const std::string& employeeId) const {
|
||||
std::vector<AlertLog> result;
|
||||
for (const auto& a : alerts_) {
|
||||
if (a.employeeId == employeeId) {
|
||||
result.push_back(a);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- 报表任务 ----
|
||||
|
||||
ReportTask AttendanceService::createReportTask(const std::string& requester,
|
||||
const std::string& deptId,
|
||||
const std::string& dateBegin,
|
||||
const std::string& dateEnd,
|
||||
ReportFormat fmt) {
|
||||
ReportTask task;
|
||||
task.taskId = nextId();
|
||||
task.requester = requester;
|
||||
task.deptId = deptId;
|
||||
task.dateRangeBegin = dateBegin;
|
||||
task.dateRangeEnd = dateEnd;
|
||||
task.format = fmt;
|
||||
task.status = "Pending";
|
||||
task.createTime = std::time(nullptr);
|
||||
tasks_.push_back(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
void AttendanceService::completeTask(const std::string& taskId, const std::string& url) {
|
||||
for (auto& t : tasks_) {
|
||||
if (t.taskId == taskId) {
|
||||
t.status = "Done";
|
||||
t.downloadUrl = url;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("任务不存在: " + taskId);
|
||||
}
|
||||
225
src/main.cpp
225
src/main.cpp
|
|
@ -1,225 +0,0 @@
|
|||
#include "app.hpp"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
// ============================================================
|
||||
// 功能演示(非 unittest,仅为展示业务能力)
|
||||
// ============================================================
|
||||
|
||||
static void demoEmployeeManagement(AttendanceService& svc) {
|
||||
printSeparator();
|
||||
std::cout << "【1. 员工管理】" << std::endl;
|
||||
|
||||
Employee e1;
|
||||
e1.employeeId = "EMP001";
|
||||
e1.name = "张三";
|
||||
e1.department = "研发部";
|
||||
e1.position = "高级工程师";
|
||||
e1.shiftId = "S001";
|
||||
e1.phone = "13912345678";
|
||||
e1.role = RoleType::Employee;
|
||||
svc.addEmployee(e1);
|
||||
|
||||
Employee e2;
|
||||
e2.employeeId = "EMP002";
|
||||
e2.name = "李四";
|
||||
e2.department = "研发部";
|
||||
e2.position = "测试工程师";
|
||||
e2.shiftId = "S001";
|
||||
e2.phone = "13987654321";
|
||||
e2.role = RoleType::Employee;
|
||||
svc.addEmployee(e2);
|
||||
|
||||
Employee e3;
|
||||
e3.employeeId = "EMP003";
|
||||
e3.name = "王五";
|
||||
e3.department = "市场部";
|
||||
e3.position = "市场经理";
|
||||
e3.shiftId = "S001";
|
||||
e3.phone = "13811112222";
|
||||
e3.role = RoleType::DeptManager;
|
||||
svc.addEmployee(e3);
|
||||
|
||||
std::cout << "已添加 3 名员工:" << std::endl;
|
||||
for (const auto& id : {"EMP001", "EMP002", "EMP003"}) {
|
||||
const auto* emp = svc.findEmployee(id);
|
||||
if (emp) {
|
||||
std::cout << " " << emp->employeeId << " | "
|
||||
<< emp->name << " | "
|
||||
<< emp->department << " | "
|
||||
<< emp->position << " | "
|
||||
<< "手机(脱敏): " << emp->maskedPhone() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void demoCheckIn(AttendanceService& svc) {
|
||||
printSeparator();
|
||||
std::cout << "【2. 打卡处理】" << std::endl;
|
||||
|
||||
// 模拟当天时间:早上 8:50(正常上班),下午 18:10(正常下班)
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* tm_now = std::localtime(&now);
|
||||
|
||||
// 构造今日 8:50
|
||||
std::tm tm_in = *tm_now;
|
||||
tm_in.tm_hour = 8;
|
||||
tm_in.tm_min = 50;
|
||||
tm_in.tm_sec = 0;
|
||||
std::time_t clockInTime = std::mktime(&tm_in);
|
||||
|
||||
// 构造今日 18:10
|
||||
std::tm tm_out = *tm_now;
|
||||
tm_out.tm_hour = 18;
|
||||
tm_out.tm_min = 10;
|
||||
tm_out.tm_sec = 0;
|
||||
std::time_t clockOutTime = std::mktime(&tm_out);
|
||||
|
||||
// 员工 EMP001 正常打卡
|
||||
auto rec1 = svc.processCheckIn("EMP001", CheckType::ClockIn, "DEVICE_APP_01", clockInTime);
|
||||
std::cout << "EMP001 上班打卡: " << AttendanceService::timeToString(rec1.timestamp)
|
||||
<< " 设备: " << rec1.deviceId << " 校验: " << (rec1.verified ? "通过" : "失败") << std::endl;
|
||||
|
||||
auto rec2 = svc.processCheckIn("EMP001", CheckType::ClockOut, "DEVICE_APP_01", clockOutTime);
|
||||
std::cout << "EMP001 下班打卡: " << AttendanceService::timeToString(rec2.timestamp)
|
||||
<< " 设备: " << rec2.deviceId << " 校验: " << (rec2.verified ? "通过" : "失败") << std::endl;
|
||||
|
||||
// 员工 EMP002 只打上班卡(缺下班卡)
|
||||
auto rec3 = svc.processCheckIn("EMP002", CheckType::ClockIn, "DEVICE_WEB_01", clockInTime + 1200); // 9:10 到
|
||||
std::cout << "EMP002 上班打卡(迟到): " << AttendanceService::timeToString(rec3.timestamp)
|
||||
<< " 设备: " << rec3.deviceId << std::endl;
|
||||
|
||||
// EMP003 没有打卡
|
||||
std::cout << "EMP003 今日未打卡" << std::endl;
|
||||
}
|
||||
|
||||
static void demoAttendanceCalculation(AttendanceService& svc) {
|
||||
printSeparator();
|
||||
std::cout << "【3. 考勤核算】" << std::endl;
|
||||
|
||||
std::string today = AttendanceService::currentDateStr();
|
||||
|
||||
// 核算 EMP001
|
||||
auto da1 = svc.calculateDaily("EMP001", today);
|
||||
std::cout << "EMP001 张三: 应出勤 " << da1.expectedMinutes
|
||||
<< " 分钟, 实际 " << da1.actualMinutes
|
||||
<< " 分钟, 加班 " << da1.overtimeMinutes
|
||||
<< " 分钟, 状态: " << da1.abnormalStr()
|
||||
<< " 出勤率: " << std::fixed << std::setprecision(1) << da1.attendanceRate() << "%" << std::endl;
|
||||
|
||||
// 核算 EMP002(仅有上班打卡)
|
||||
auto da2 = svc.calculateDaily("EMP002", today);
|
||||
std::cout << "EMP002 李四: 应出勤 " << da2.expectedMinutes
|
||||
<< " 分钟, 实际 " << da2.actualMinutes
|
||||
<< " 分钟, 状态: " << da2.abnormalStr()
|
||||
<< " 出勤率: " << std::fixed << std::setprecision(1) << da2.attendanceRate() << "%" << std::endl;
|
||||
|
||||
// 核算 EMP003(无打卡)
|
||||
auto da3 = svc.calculateDaily("EMP003", today);
|
||||
std::cout << "EMP003 王五: 应出勤 " << da3.expectedMinutes
|
||||
<< " 分钟, 实际 " << da3.actualMinutes
|
||||
<< " 分钟, 状态: " << da3.abnormalStr()
|
||||
<< " 出勤率: " << std::fixed << std::setprecision(1) << da3.attendanceRate() << "%" << std::endl;
|
||||
}
|
||||
|
||||
static void demoAlerts(AttendanceService& svc) {
|
||||
printSeparator();
|
||||
std::cout << "【4. 异常预警】" << std::endl;
|
||||
|
||||
// 为旷工员工创建预警
|
||||
auto alert1 = svc.createAlert("EMP002", AbnormalFlag::Absenteeism);
|
||||
std::cout << "预警已创建: ID=" << alert1.alertId
|
||||
<< " 员工=" << alert1.employeeId
|
||||
<< " 类型=旷工"
|
||||
<< " 渠道=" << alert1.channel << std::endl;
|
||||
|
||||
auto alert2 = svc.createAlert("EMP003", AbnormalFlag::Absenteeism);
|
||||
std::cout << "预警已创建: ID=" << alert2.alertId
|
||||
<< " 员工=" << alert2.employeeId
|
||||
<< " 类型=旷工"
|
||||
<< " 渠道=" << alert2.channel << std::endl;
|
||||
|
||||
// 查询 EMP002 的预警
|
||||
auto empAlerts = svc.getAlerts("EMP002");
|
||||
std::cout << "EMP002 当前预警数: " << empAlerts.size() << std::endl;
|
||||
}
|
||||
|
||||
static void demoReport(AttendanceService& svc) {
|
||||
printSeparator();
|
||||
std::cout << "【5. 报表任务】" << std::endl;
|
||||
|
||||
std::string today = AttendanceService::currentDateStr();
|
||||
// 取今天往前 7 天
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::time_t weekAgo = now - 7 * 86400;
|
||||
std::tm* tm = std::localtime(&weekAgo);
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
std::string weekAgoStr = oss.str();
|
||||
|
||||
auto task = svc.createReportTask("ADMIN001", "研发部", weekAgoStr, today, ReportFormat::Excel);
|
||||
std::cout << "报表任务已创建: ID=" << task.taskId
|
||||
<< " 范围=" << task.dateRangeBegin << "~" << task.dateRangeEnd
|
||||
<< " 格式=Excel 状态=" << task.status << std::endl;
|
||||
|
||||
// 模拟报表生成完成
|
||||
svc.completeTask(task.taskId, "/download/report_" + task.taskId + ".xlsx");
|
||||
std::cout << "报表任务已完成: 下载链接=" << "/download/report_" + task.taskId + ".xlsx" << std::endl;
|
||||
}
|
||||
|
||||
static void demoDataSecurity() {
|
||||
printSeparator();
|
||||
std::cout << "【6. 数据安全演示】" << std::endl;
|
||||
|
||||
// 脱敏演示
|
||||
std::string phone = "13912345678";
|
||||
std::string idCard = "110101199001011234";
|
||||
std::string email = "zhangsan@company.com";
|
||||
|
||||
std::cout << "原始手机号: " << phone
|
||||
<< " -> 脱敏: " << maskSensitive(phone, 3, 4) << std::endl;
|
||||
std::cout << "原始身份证: " << idCard
|
||||
<< " -> 脱敏: " << maskSensitive(idCard, 6, 4) << std::endl;
|
||||
std::cout << "原始邮箱: " << email
|
||||
<< " -> 脱敏: " << maskSensitive(email, 2, email.find('@') - 1) << std::endl;
|
||||
|
||||
// RBAC 权限演示
|
||||
std::cout << "\nRBAC 权限模型:" << std::endl;
|
||||
std::cout << " SystemAdmin - 全部资源访问权限" << std::endl;
|
||||
std::cout << " HRAdmin - 考勤数据读写权限" << std::endl;
|
||||
std::cout << " DeptManager - 本部门数据只读权限" << std::endl;
|
||||
std::cout << " Employee - 个人数据只读权限" << std::endl;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主入口
|
||||
// ============================================================
|
||||
|
||||
int main() {
|
||||
std::cout << "\n";
|
||||
std::cout << " ____ _ _ _____ _____ _ _ ____ _ _" << std::endl;
|
||||
std::cout << " / ___|| \\ | |_ _| ____| \\ | |/ ___| / \\ | |" << std::endl;
|
||||
std::cout << " \\___ \\| \\| | | | | _| | \\| | | _ / _ \\| |" << std::endl;
|
||||
std::cout << " ___) | |\\ | | | | |___| |\\ | |_| |/ ___ \\ |___" << std::endl;
|
||||
std::cout << " |____/|_| \\_| |_| |_____|_| \\_|\\____/_/ \\_\\_____|" << std::endl;
|
||||
std::cout << " 智能考勤管理系统 v1.0" << std::endl;
|
||||
std::cout << " C++ 示范工程" << std::endl;
|
||||
std::cout << "\n";
|
||||
|
||||
AttendanceService svc;
|
||||
|
||||
demoEmployeeManagement(svc);
|
||||
demoCheckIn(svc);
|
||||
demoAttendanceCalculation(svc);
|
||||
demoAlerts(svc);
|
||||
demoReport(svc);
|
||||
demoDataSecurity();
|
||||
|
||||
printSeparator();
|
||||
std::cout << "全部演示完成!" << std::endl;
|
||||
printSeparator();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
#include "app.hpp"
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
// ============================================================
|
||||
// 单元测试(使用标准库 assert,无外部依赖)
|
||||
// ============================================================
|
||||
|
||||
static void testEmployeeManagement() {
|
||||
AttendanceService svc;
|
||||
|
||||
// 添加员工
|
||||
Employee e;
|
||||
e.employeeId = "T001";
|
||||
e.name = "测试员工";
|
||||
e.department = "测试部";
|
||||
e.position = "测试工程师";
|
||||
e.shiftId = "S001";
|
||||
e.phone = "13900001111";
|
||||
e.role = RoleType::Employee;
|
||||
svc.addEmployee(e);
|
||||
|
||||
// 查找
|
||||
const Employee* found = svc.findEmployee("T001");
|
||||
assert(found != nullptr);
|
||||
assert(found->name == "测试员工");
|
||||
assert(found->department == "测试部");
|
||||
|
||||
// 脱敏手机号
|
||||
std::string masked = found->maskedPhone();
|
||||
assert(masked == "*******1111");
|
||||
std::cout << "[PASS] testEmployeeManagement: 员工管理正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testDuplicateEmployee() {
|
||||
AttendanceService svc;
|
||||
Employee e;
|
||||
e.employeeId = "T002";
|
||||
e.name = "重复测试";
|
||||
svc.addEmployee(e);
|
||||
|
||||
bool thrown = false;
|
||||
try {
|
||||
svc.addEmployee(e);
|
||||
} catch (const std::runtime_error&) {
|
||||
thrown = true;
|
||||
}
|
||||
assert(thrown);
|
||||
std::cout << "[PASS] testDuplicateEmployee: 重复添加异常正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testCheckInAndRecords() {
|
||||
AttendanceService svc;
|
||||
|
||||
Employee e;
|
||||
e.employeeId = "T003";
|
||||
e.name = "打卡测试";
|
||||
e.department = "测试部";
|
||||
e.shiftId = "S001";
|
||||
svc.addEmployee(e);
|
||||
|
||||
// 上班打卡
|
||||
std::time_t now = std::time(nullptr);
|
||||
auto rec1 = svc.processCheckIn("T003", CheckType::ClockIn, "DEVICE_TEST", now);
|
||||
assert(rec1.employeeId == "T003");
|
||||
assert(rec1.type == CheckType::ClockIn);
|
||||
assert(rec1.verified == true);
|
||||
assert(!rec1.recordId.empty());
|
||||
|
||||
// 下班打卡
|
||||
auto rec2 = svc.processCheckIn("T003", CheckType::ClockOut, "DEVICE_TEST", now + 28800); // 8h后
|
||||
assert(rec2.type == CheckType::ClockOut);
|
||||
|
||||
// 查询记录
|
||||
auto records = svc.getRecords("T003");
|
||||
assert(records.size() == 2);
|
||||
assert(records[0].type == CheckType::ClockIn);
|
||||
assert(records[1].type == CheckType::ClockOut);
|
||||
|
||||
// 未知员工打卡应抛异常
|
||||
bool thrown = false;
|
||||
try {
|
||||
svc.processCheckIn("UNKNOWN", CheckType::ClockIn, "DEVICE", now);
|
||||
} catch (const std::runtime_error&) {
|
||||
thrown = true;
|
||||
}
|
||||
assert(thrown);
|
||||
|
||||
std::cout << "[PASS] testCheckInAndRecords: 打卡流程正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testDailyCalculation() {
|
||||
AttendanceService svc;
|
||||
|
||||
Employee e;
|
||||
e.employeeId = "T004";
|
||||
e.name = "核算测试";
|
||||
e.department = "测试部";
|
||||
e.shiftId = "S001";
|
||||
svc.addEmployee(e);
|
||||
|
||||
// 模拟当日打卡:8:30 上班,18:00 下班
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* tm = std::localtime(&now);
|
||||
std::tm tm_in = *tm;
|
||||
tm_in.tm_hour = 8;
|
||||
tm_in.tm_min = 30;
|
||||
tm_in.tm_sec = 0;
|
||||
std::time_t tIn = std::mktime(&tm_in);
|
||||
|
||||
std::tm tm_out = *tm;
|
||||
tm_out.tm_hour = 18;
|
||||
tm_out.tm_min = 0;
|
||||
tm_out.tm_sec = 0;
|
||||
std::time_t tOut = std::mktime(&tm_out);
|
||||
|
||||
svc.processCheckIn("T004", CheckType::ClockIn, "DEVICE", tIn);
|
||||
svc.processCheckIn("T004", CheckType::ClockOut, "DEVICE", tOut);
|
||||
|
||||
// 获取核算结果
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
std::string today = oss.str();
|
||||
|
||||
auto da = svc.calculateDaily("T004", today);
|
||||
assert(!da.dateStr.empty());
|
||||
assert(da.employeeId == "T004");
|
||||
assert(da.expectedMinutes > 0);
|
||||
assert(da.actualMinutes > 0);
|
||||
|
||||
// 实际工时应约为 8.5h - 1h(午休) = 7.5h = 450min,有弹性
|
||||
// 但至少大于0
|
||||
assert(da.actualMinutes > 100);
|
||||
std::cout << "[PASS] testDailyCalculation: 核算正常, 出勤 " << da.actualMinutes << " 分钟" << std::endl;
|
||||
}
|
||||
|
||||
static void testAbnormalDetection() {
|
||||
AttendanceService svc;
|
||||
|
||||
Employee e;
|
||||
e.employeeId = "T005";
|
||||
e.name = "异常测试";
|
||||
e.department = "测试部";
|
||||
e.shiftId = "S001";
|
||||
svc.addEmployee(e);
|
||||
|
||||
// 没有打卡记录 -> 旷工
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* tm = std::localtime(&now);
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
std::string today = oss.str();
|
||||
|
||||
auto da = svc.calculateDaily("T005", today);
|
||||
assert(da.abnormal == AbnormalFlag::Absenteeism);
|
||||
std::cout << "[PASS] testAbnormalDetection: 无打卡=旷工判定正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testReportGeneration() {
|
||||
AttendanceService svc;
|
||||
|
||||
// 添加员工
|
||||
Employee e1;
|
||||
e1.employeeId = "T006";
|
||||
e1.name = "报表A";
|
||||
e1.department = "报表部";
|
||||
svc.addEmployee(e1);
|
||||
|
||||
Employee e2;
|
||||
e2.employeeId = "T007";
|
||||
e2.name = "报表B";
|
||||
e2.department = "报表部";
|
||||
svc.addEmployee(e2);
|
||||
|
||||
// 添加打卡
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* tm = std::localtime(&now);
|
||||
std::tm tm_in = *tm;
|
||||
tm_in.tm_hour = 9;
|
||||
tm_in.tm_min = 0;
|
||||
std::time_t tIn = std::mktime(&tm_in);
|
||||
|
||||
std::tm tm_out = *tm;
|
||||
tm_out.tm_hour = 18;
|
||||
tm_out.tm_min = 0;
|
||||
std::time_t tOut = std::mktime(&tm_out);
|
||||
|
||||
svc.processCheckIn("T006", CheckType::ClockIn, "DEVICE", tIn);
|
||||
svc.processCheckIn("T006", CheckType::ClockOut, "DEVICE", tOut);
|
||||
|
||||
svc.processCheckIn("T007", CheckType::ClockIn, "DEVICE", tIn);
|
||||
svc.processCheckIn("T007", CheckType::ClockOut, "DEVICE", tOut);
|
||||
|
||||
// 生成报表
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(tm, "%Y-%m-%d");
|
||||
std::string today = oss.str();
|
||||
|
||||
auto report = svc.generateReport("报表部", today, today);
|
||||
assert(!report.empty());
|
||||
assert(report.size() == 2);
|
||||
std::cout << "[PASS] testReportGeneration: 报表生成正常, 共 " << report.size() << " 条记录" << std::endl;
|
||||
}
|
||||
|
||||
static void testAlertCreation() {
|
||||
AttendanceService svc;
|
||||
|
||||
// 创建预警
|
||||
auto alert = svc.createAlert("T999", AbnormalFlag::Late);
|
||||
assert(!alert.alertId.empty());
|
||||
assert(alert.employeeId == "T999");
|
||||
assert(alert.type == AbnormalFlag::Late);
|
||||
assert(alert.status == AlertStatus::Pending);
|
||||
|
||||
// 查询
|
||||
auto alerts = svc.getAlerts("T999");
|
||||
assert(alerts.size() == 1);
|
||||
assert(alerts[0].type == AbnormalFlag::Late);
|
||||
|
||||
std::cout << "[PASS] testAlertCreation: 预警创建与查询正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testReportTask() {
|
||||
AttendanceService svc;
|
||||
|
||||
auto task = svc.createReportTask("ADMIN", "部门X", "2025-01-01", "2025-01-31", ReportFormat::PDF);
|
||||
assert(task.taskId.length() > 0);
|
||||
assert(task.status == "Pending");
|
||||
assert(task.format == ReportFormat::PDF);
|
||||
|
||||
svc.completeTask(task.taskId, "/download/test.pdf");
|
||||
assert(task.status == "Done");
|
||||
assert(task.downloadUrl == "/download/test.pdf");
|
||||
|
||||
std::cout << "[PASS] testReportTask: 报表任务流程正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testMaskSensitive() {
|
||||
assert(maskSensitive("12345678901", 3, 4) == "123****8901");
|
||||
assert(maskSensitive("abc", 1, 1) == "abc"); // 太短时不脱敏
|
||||
assert(maskSensitive("", 0, 0).empty());
|
||||
std::cout << "[PASS] testMaskSensitive: 脱敏函数正常" << std::endl;
|
||||
}
|
||||
|
||||
static void testShiftRule() {
|
||||
ShiftRule rule;
|
||||
rule.shiftId = "S999";
|
||||
rule.startHour = 8;
|
||||
rule.startMin = 30;
|
||||
rule.endHour = 17;
|
||||
rule.endMin = 30;
|
||||
|
||||
assert(rule.workStartMinutes() == 8 * 60 + 30); // 510
|
||||
assert(rule.workEndMinutes() == 17 * 60 + 30); // 1050
|
||||
|
||||
std::cout << "[PASS] testShiftRule: 排班规则计算正常" << std::endl;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主测试入口
|
||||
// ============================================================
|
||||
|
||||
int main() {
|
||||
std::cout << "============================================" << std::endl;
|
||||
std::cout << " 智能考勤管理系统 - 单元测试" << std::endl;
|
||||
std::cout << "============================================" << std::endl;
|
||||
|
||||
testEmployeeManagement();
|
||||
testDuplicateEmployee();
|
||||
testCheckInAndRecords();
|
||||
testDailyCalculation();
|
||||
testAbnormalDetection();
|
||||
testReportGeneration();
|
||||
testAlertCreation();
|
||||
testReportTask();
|
||||
testMaskSensitive();
|
||||
testShiftRule();
|
||||
|
||||
std::cout << "============================================" << std::endl;
|
||||
std::cout << "所有测试通过!" << std::endl;
|
||||
std::cout << "============================================" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue