推送代码工程
This commit is contained in:
parent
ff76e7467f
commit
083c7fb454
|
|
@ -1,29 +1,38 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(AttendanceSystem VERSION 1.0.0 LANGUAGES CXX)
|
||||
project(AttendanceSystem LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebugDLL")
|
||||
|
||||
# MSVC UTF-8 编译选项
|
||||
# 可选:处理 MSVC 的 utf-8 BOM
|
||||
if(MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
add_compile_options("/utf-8")
|
||||
endif()
|
||||
|
||||
# 主程序可执行文件
|
||||
add_executable(attendance_system
|
||||
src/main.cpp
|
||||
# 引入 Google Test
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
# 主库目标
|
||||
add_library(attendance_lib STATIC
|
||||
src/app.cpp
|
||||
include/app.hpp
|
||||
)
|
||||
target_include_directories(attendance_lib PUBLIC include)
|
||||
|
||||
target_include_directories(attendance_system PRIVATE include)
|
||||
|
||||
# 测试可执行文件
|
||||
add_executable(basic_test
|
||||
tests/basic_test.cpp
|
||||
src/app.cpp
|
||||
include/app.hpp
|
||||
)
|
||||
# 主程序
|
||||
target_link_libraries(attendance_lib PUBLIC)
|
||||
add_executable(attendance_system src/main.cpp)
|
||||
target_link_libraries(attendance_system PRIVATE attendance_lib)
|
||||
|
||||
# 测试目标
|
||||
get_filename_component(TEST_SRC tests/basic_test.cpp NAME)
|
||||
add_executable(basic_test tests/basic_test.cpp)
|
||||
target_link_libraries(basic_test PRIVATE attendance_lib GTest::GTest GTest::Main)
|
||||
target_include_directories(basic_test PRIVATE include)
|
||||
gtest_discover_tests(basic_test)
|
||||
|
||||
# 旧式测试目标(可选保留)
|
||||
# add_executable(basic_test_old tests/basic_test_old.cpp)
|
||||
# target_link_libraries(basic_test_old PRIVATE attendance_lib)
|
||||
310
include/app.hpp
310
include/app.hpp
|
|
@ -1,240 +1,146 @@
|
|||
#ifndef ATTENDANCE_SYSTEM_APP_HPP
|
||||
#define ATTENDANCE_SYSTEM_APP_HPP
|
||||
/**
|
||||
* @file app.hpp
|
||||
* @brief 智能考勤管理系统核心数据结构与业务接口声明
|
||||
*
|
||||
* 需求追溯:
|
||||
* - SRS-ATT_F-001: 多终端打卡支持
|
||||
* - SRS-ATT_F-002: 考勤数据自动核算
|
||||
* - SRS-ATT_F-003: 异常考勤预警
|
||||
* - SRS-ATT_F-004: 考勤报表生成导出
|
||||
* - SRS-ATT_F-005: 第三方系统集成
|
||||
*/
|
||||
|
||||
#ifndef APP_HPP
|
||||
#define 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 // 下班打卡
|
||||
// 打卡请求(SRS-ATT_F-001 输入)
|
||||
struct PunchRequest {
|
||||
std::string employeeId; // 员工唯一标识
|
||||
std::string terminalType; // 终端类型:MOBILE / WEB / MACHINE
|
||||
std::string deviceId; // 设备标识
|
||||
std::time_t timestamp; // 打卡时间戳(降级模式下仅缓存此字段)
|
||||
double latitude; // 纬度(可选)
|
||||
double longitude; // 经度(可选)
|
||||
};
|
||||
|
||||
/// 异常标记
|
||||
enum class AbnormalFlag {
|
||||
None,
|
||||
Late, // 迟到
|
||||
EarlyLeave, // 早退
|
||||
Absenteeism // 旷工
|
||||
// 打卡结果响应(SRS-ATT_F-001 输出)
|
||||
struct PunchResponse {
|
||||
bool success;
|
||||
std::string message;
|
||||
std::time_t serverTime;
|
||||
};
|
||||
|
||||
/// 报表输出格式
|
||||
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;
|
||||
// 考勤规则配置(SRS-ATT_F-002 输入)
|
||||
struct AttendanceRule {
|
||||
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);
|
||||
}
|
||||
std::string shift; // 班次
|
||||
int startHour; // 上班时间(小时)
|
||||
int startMinute;
|
||||
int endHour; // 下班时间(小时)
|
||||
int endMinute;
|
||||
int lateThreshold; // 迟到容忍分钟数
|
||||
int earlyLeaveThreshold; // 早退容忍分钟数
|
||||
std::vector<std::string> holidays; // 节假日列表
|
||||
};
|
||||
|
||||
/// 排班规则
|
||||
struct ShiftRule {
|
||||
std::string shiftId;
|
||||
// 原始打卡记录(SRS-ATT_F-002 输入)
|
||||
struct RawRecord {
|
||||
std::string employeeId;
|
||||
std::time_t punchTime;
|
||||
std::string terminalType;
|
||||
};
|
||||
|
||||
// 核算结果(SRS-ATT_F-002 输出,SRS-ATT_F-003 输入)
|
||||
struct CalculationResult {
|
||||
std::string employeeId;
|
||||
std::time_t date;
|
||||
enum Status { NORMAL, LATE, EARLY_LEAVE, ABSENT, MISSING_CARD };
|
||||
Status status;
|
||||
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; }
|
||||
std::time_t actualArrival;
|
||||
std::time_t actualDeparture;
|
||||
};
|
||||
|
||||
/// 打卡流水记录
|
||||
struct CheckInRecord {
|
||||
std::string recordId;
|
||||
// 预警通知(SRS-ATT_F-003 输出)
|
||||
struct WarningNotification {
|
||||
std::string employeeId;
|
||||
std::time_t timestamp{0};
|
||||
CheckType type{CheckType::Unknown};
|
||||
std::string deviceId;
|
||||
std::string latitude;
|
||||
std::string longitude;
|
||||
bool verified{false};
|
||||
std::string managerId; // 管理员ID(严重异常时填充)
|
||||
CalculationResult::Status anomalyType;
|
||||
std::string message;
|
||||
long long sendTimestamp;
|
||||
};
|
||||
|
||||
/// 考勤核算结果(按天)
|
||||
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 "正常";
|
||||
}
|
||||
}
|
||||
// 报表查询条件(SRS-ATT_F-004 输入)
|
||||
struct ReportQuery {
|
||||
std::string department;
|
||||
std::string employeeId; // 可选,若为空则查部门
|
||||
std::time_t startDate;
|
||||
std::time_t endDate;
|
||||
enum Format { EXCEL, PDF, ONLINE };
|
||||
Format format;
|
||||
};
|
||||
|
||||
/// 异常预警日志
|
||||
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
|
||||
// 统计报表文件(SRS-ATT_F-004 输出)
|
||||
struct ReportFile {
|
||||
std::string fileName;
|
||||
std::string downloadUrl;
|
||||
std::time_t createTime{0};
|
||||
std::vector<char> rawData; // 二进制数据
|
||||
};
|
||||
|
||||
/// 权限配置(RBAC)
|
||||
struct Permission {
|
||||
std::string resource; // 资源路径,如 "/api/v1/reports"
|
||||
std::string action; // 操作:READ / WRITE / DELETE
|
||||
// 第三方同步请求(SRS-ATT_F-005 输入)
|
||||
struct ThirdPartySyncRequest {
|
||||
std::string apiKey; // 鉴权密钥
|
||||
std::string systemType; // HR / OA / DoorAccess
|
||||
std::vector<RawRecord> records; // 需要同步的打卡记录
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 业务逻辑类
|
||||
// ============================================================
|
||||
// 考勤同步数据(SRS-ATT_F-005 输出)
|
||||
struct SyncResponse {
|
||||
bool success;
|
||||
int syncedCount;
|
||||
int failedCount;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class AttendanceService {
|
||||
// ---------- 业务接口 ----------
|
||||
class AttendanceSystem {
|
||||
public:
|
||||
AttendanceService();
|
||||
AttendanceSystem();
|
||||
~AttendanceSystem();
|
||||
|
||||
// ---- 员工管理 ----
|
||||
void addEmployee(const Employee& emp);
|
||||
const Employee* findEmployee(const std::string& id) const;
|
||||
std::vector<Employee> getEmployeesByDept(const std::string& dept) const;
|
||||
// SRS-ATT_F-001: 处理打卡请求(移动端、Web端、考勤机)
|
||||
PunchResponse processPunch(const PunchRequest& req);
|
||||
|
||||
// ---- 排班管理 ----
|
||||
void addShiftRule(const ShiftRule& rule);
|
||||
const ShiftRule* findShift(const std::string& shiftId) const;
|
||||
// SRS-ATT_F-002: 自动核算考勤数据(定时/事件触发)
|
||||
void autoCalculateAttendance();
|
||||
|
||||
// ---- 打卡处理 ----
|
||||
CheckInRecord processCheckIn(const std::string& employeeId,
|
||||
CheckType type,
|
||||
const std::string& deviceId,
|
||||
std::time_t timestamp);
|
||||
// SRS-ATT_F-003: 异常预警通知
|
||||
void sendWarningForAnomaly(const CalculationResult& result);
|
||||
|
||||
std::vector<CheckInRecord> getRecords(const std::string& employeeId) const;
|
||||
// SRS-ATT_F-004: 生成并导出报表
|
||||
ReportFile generateReport(const ReportQuery& query);
|
||||
|
||||
// ---- 考勤核算 ----
|
||||
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);
|
||||
// SRS-ATT_F-005: 与第三方系统同步数据
|
||||
SyncResponse syncWithThirdParty(const ThirdPartySyncRequest& request);
|
||||
|
||||
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;
|
||||
// 辅助函数
|
||||
bool validateEmployee(const std::string& employeeId);
|
||||
AttendanceRule getRuleForEmployee(const std::string& employeeId);
|
||||
void storeRawRecord(const RawRecord& rec);
|
||||
void storeCalculationResult(const CalculationResult& res);
|
||||
std::vector<RawRecord> fetchUncalculatedRecords();
|
||||
CalculationResult calculateSingleRecord(const RawRecord& rec, const AttendanceRule& rule);
|
||||
void logWarningSend(const WarningNotification& notif);
|
||||
std::vector<std::string> getManagersForEmployee(const std::string& employeeId);
|
||||
bool authenticateThirdParty(const std::string& apiKey);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 工具函数声明
|
||||
// ============================================================
|
||||
|
||||
/// 脱敏工具:将敏感字符串中间部分替换为 '*'
|
||||
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
|
||||
#endif // APP_HPP
|
||||
|
|
|
|||
542
src/app.cpp
542
src/app.cpp
|
|
@ -1,345 +1,277 @@
|
|||
/**
|
||||
* @file app.cpp
|
||||
* @brief 智能考勤管理系统业务逻辑实现
|
||||
*
|
||||
* 所有函数实现均遵循对应需求的处理流程,注释中标明步骤编号。
|
||||
*/
|
||||
|
||||
#include "app.hpp"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
// ============================================================
|
||||
// 工具函数实现
|
||||
// ============================================================
|
||||
// 构造函数与析构函数
|
||||
AttendanceSystem::AttendanceSystem() {}
|
||||
AttendanceSystem::~AttendanceSystem() {}
|
||||
|
||||
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();
|
||||
// SRS-ATT_F-001: 多终端打卡支持
|
||||
// 处理流程:步骤1接收打卡请求 -> 步骤2终端识别与身份校验 -> 步骤4获取位置与时间戳 -> 步骤5规则匹配 -> 步骤6/7生成记录 -> 步骤8返回结果
|
||||
PunchResponse AttendanceSystem::processPunch(const PunchRequest& req) {
|
||||
PunchResponse resp;
|
||||
// 步骤2: 终端类型识别与员工身份合法性校验
|
||||
if (!validateEmployee(req.employeeId)) {
|
||||
// 步骤3: 拒绝打卡并返回失败提示
|
||||
resp.success = false;
|
||||
resp.message = "员工身份校验失败";
|
||||
resp.serverTime = std::time(nullptr);
|
||||
return resp;
|
||||
}
|
||||
|
||||
std::string AttendanceService::currentDateStr() {
|
||||
// 步骤4: 获取员工位置信息与当前时间戳(降级模式下仅缓存时间戳)
|
||||
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();
|
||||
double lat = req.latitude;
|
||||
double lon = req.longitude;
|
||||
|
||||
// 步骤5: 将时间与位置数据与预设考勤规则进行匹配判断
|
||||
AttendanceRule rule = getRuleForEmployee(req.employeeId);
|
||||
// 降级模式:仅缓存打卡时间戳,不进行规则匹配
|
||||
if (/* 降级模式判断条件 */ false) {
|
||||
RawRecord rec;
|
||||
rec.employeeId = req.employeeId;
|
||||
rec.punchTime = now;
|
||||
rec.terminalType = req.terminalType;
|
||||
storeRawRecord(rec);
|
||||
// 步骤8: 组装响应
|
||||
resp.success = true;
|
||||
resp.message = "打卡成功(降级模式)";
|
||||
resp.serverTime = now;
|
||||
return resp;
|
||||
}
|
||||
|
||||
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;
|
||||
// 正常模式:进行匹配
|
||||
// 步骤6/7: 根据匹配结果生成记录(此处简化,实际需对比时间)
|
||||
bool match = (/* 实际时间与规则时间对比 */);
|
||||
if (match) {
|
||||
// 步骤6: 生成正常打卡记录并存储
|
||||
RawRecord rec;
|
||||
rec.employeeId = req.employeeId;
|
||||
rec.punchTime = now;
|
||||
rec.terminalType = req.terminalType;
|
||||
storeRawRecord(rec);
|
||||
resp.success = true;
|
||||
resp.message = "打卡成功";
|
||||
} else {
|
||||
da.overtimeMinutes = 0;
|
||||
// 步骤7: 标记异常状态并缓存数据等待核算
|
||||
RawRecord rec;
|
||||
rec.employeeId = req.employeeId;
|
||||
rec.punchTime = now;
|
||||
rec.terminalType = req.terminalType;
|
||||
storeRawRecord(rec);
|
||||
// 异常标记由后续核算模块处理
|
||||
resp.success = true; // 仍返回成功,异常在核算中处理
|
||||
resp.message = "打卡已记录,等待核算";
|
||||
}
|
||||
|
||||
// 异常判定:是否有上班打卡
|
||||
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;
|
||||
// 步骤8: 返回响应
|
||||
resp.serverTime = now;
|
||||
return resp;
|
||||
}
|
||||
|
||||
if (!hasClockIn || !hasClockOut) {
|
||||
da.abnormal = AbnormalFlag::Absenteeism;
|
||||
// SRS-ATT_F-002: 考勤数据自动核算
|
||||
// 处理流程:步骤1触发核算任务 -> 步骤2提取未核算记录 -> 步骤3加载规则 -> 步骤4匹配计算 -> 步骤5/6生成结果 -> 步骤7更新档案 -> 步骤8输出
|
||||
void AttendanceSystem::autoCalculateAttendance() {
|
||||
// 步骤1: 定时任务或事件触发(由外部调用)
|
||||
// 步骤2: 从数据库提取未核算的原始打卡记录
|
||||
std::vector<RawRecord> records = fetchUncalculatedRecords();
|
||||
for (const auto& rec : records) {
|
||||
// 步骤3: 根据员工部门与班次加载对应的考勤规则配置
|
||||
AttendanceRule rule = getRuleForEmployee(rec.employeeId);
|
||||
// 步骤4: 对打卡时间与规则时间阈值进行匹配计算
|
||||
CalculationResult result = calculateSingleRecord(rec, rule);
|
||||
// 步骤5/6: 生成正常或异常考勤记录
|
||||
storeCalculationResult(result);
|
||||
// 步骤7: 更新至员工考勤综合档案(简化,直接存储)
|
||||
// 步骤8: 输出核算结果至异常预警模块与报表生成模块
|
||||
if (result.status != CalculationResult::NORMAL) {
|
||||
sendWarningForAnomaly(result); // 触发预警(SRS-ATT_F-003)
|
||||
}
|
||||
// 同时将结果传递给报表模块(由报表生成时查询)
|
||||
}
|
||||
}
|
||||
|
||||
// SRS-ATT_F-003: 异常考勤预警
|
||||
// 处理流程:步骤1接收异常结果 -> 步骤2解析异常类型与员工信息 -> 步骤3判断严重级别 -> 步骤4/5发送通知 -> 步骤6记录日志
|
||||
void AttendanceSystem::sendWarningForAnomaly(const CalculationResult& result) {
|
||||
WarningNotification notif;
|
||||
notif.employeeId = result.employeeId;
|
||||
notif.anomalyType = result.status;
|
||||
|
||||
// 步骤2: 解析异常类型与员工信息
|
||||
switch (result.status) {
|
||||
case CalculationResult::LATE:
|
||||
notif.message = "您今日迟到,请留意考勤规则。";
|
||||
break;
|
||||
case CalculationResult::EARLY_LEAVE:
|
||||
notif.message = "您今日早退,请留意考勤规则。";
|
||||
break;
|
||||
case CalculationResult::MISSING_CARD:
|
||||
notif.message = "您今日存在缺卡记录,请及时补卡。";
|
||||
break;
|
||||
case CalculationResult::ABSENT:
|
||||
notif.message = "您今日缺勤,请说明原因。";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 步骤3: 判断异常严重级别(此示例中连续缺卡为严重)
|
||||
bool isSevere = (result.status == CalculationResult::ABSENT);
|
||||
// 查询历史记录判断连续缺卡(简化:用随机或逻辑)
|
||||
if (isSevere) {
|
||||
// 步骤5: 向员工与人事管理员同时发送预警通知
|
||||
std::vector<std::string> managers = getManagersForEmployee(result.employeeId);
|
||||
for (const auto& mgr : managers) {
|
||||
// 发送给管理员(此处模拟打印)
|
||||
std::cout << "[警告-管理员] 员工 " << result.employeeId << " 缺勤,需关注。" << std::endl;
|
||||
}
|
||||
// 仍给员工发送
|
||||
std::cout << "[警告-员工] " << notif.message << std::endl;
|
||||
} else {
|
||||
// 迟到判定:上班打卡时间超过规定上班时间+弹性窗口
|
||||
int actualStartMin = minutesFromMidnight(firstClockIn);
|
||||
int expectedStartWithFlex = shift->workStartMinutes() + shift->flexWindow;
|
||||
if (actualStartMin > expectedStartWithFlex) {
|
||||
da.abnormal = AbnormalFlag::Late;
|
||||
// 步骤4: 仅向员工发送提醒通知
|
||||
std::cout << "[提醒-员工] " << notif.message << std::endl;
|
||||
}
|
||||
|
||||
// 早退判定:下班打卡时间早于规定下班时间
|
||||
int actualEndMin = minutesFromMidnight(lastClockOut);
|
||||
if (actualEndMin < shift->workEndMinutes() && da.abnormal == AbnormalFlag::None) {
|
||||
da.abnormal = AbnormalFlag::EarlyLeave;
|
||||
}
|
||||
// 步骤6: 将预警通知发送状态与时间记录至系统日志
|
||||
notif.sendTimestamp = std::time(nullptr);
|
||||
logWarningSend(notif);
|
||||
}
|
||||
|
||||
// SRS-ATT_F-004: 考勤报表生成导出
|
||||
// 处理流程:步骤1接收请求或定时触发 -> 步骤2解析维度条件 -> 步骤3聚合数据 -> 步骤4渲染 -> 步骤5/6导出或展示
|
||||
ReportFile AttendanceSystem::generateReport(const ReportQuery& query) {
|
||||
// 步骤2: 解析报表维度
|
||||
std::string dept = query.department;
|
||||
std::string emp = query.employeeId;
|
||||
std::time_t start = query.startDate;
|
||||
std::time_t end = query.endDate;
|
||||
|
||||
// 步骤3: 从档案中聚合考勤数据统计指标(模拟)
|
||||
// 此处略去实际数据库查询,直接构造示例数据
|
||||
std::stringstream content;
|
||||
content << "考勤报表 - 部门:" << dept << " 时间:" << start << "-" << end << std::endl;
|
||||
content << "出勤天数: 20, 迟到: 1, 早退: 0, 缺勤: 0" << std::endl;
|
||||
|
||||
// 步骤4: 利用渲染引擎生成报表图表与表格视图(此处仅文本模拟)
|
||||
ReportFile file;
|
||||
file.fileName = "attendance_report_" + dept + ".csv";
|
||||
std::string dataStr = content.str();
|
||||
file.rawData.assign(dataStr.begin(), dataStr.end());
|
||||
file.downloadUrl = "http://localhost/reports/" + file.fileName;
|
||||
|
||||
// 步骤5: 判断导出格式选择
|
||||
if (query.format == ReportQuery::EXCEL || query.format == ReportQuery::PDF) {
|
||||
// 步骤6: 生成文件并提供下载链接(此处模拟返回)
|
||||
// 实际需要调用格式转换库
|
||||
return file;
|
||||
} else {
|
||||
da.actualMinutes = 0;
|
||||
da.overtimeMinutes = 0;
|
||||
// 没有打卡记录视为旷工
|
||||
if (dailyRecords.empty()) {
|
||||
da.abnormal = AbnormalFlag::Absenteeism;
|
||||
// 步骤7: 直接返回页面展示结果(此处返回模拟 URL)
|
||||
file.downloadUrl = "";
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return da;
|
||||
// SRS-ATT_F-005: 第三方系统集成
|
||||
// 处理流程:步骤1接收同步请求 -> 步骤2接口鉴权与协议校验 -> 步骤4数据格式转换 -> 步骤5双向同步 -> 步骤6日志 -> 步骤7响应
|
||||
SyncResponse AttendanceSystem::syncWithThirdParty(const ThirdPartySyncRequest& request) {
|
||||
SyncResponse resp;
|
||||
// 步骤2: 接口鉴权与通信协议解析校验
|
||||
if (!authenticateThirdParty(request.apiKey)) {
|
||||
// 步骤3: 拒绝同步并记录攻击日志
|
||||
resp.success = false;
|
||||
resp.message = "鉴权失败";
|
||||
resp.syncedCount = 0;
|
||||
resp.failedCount = 0;
|
||||
// 记录攻击日志(此处简化)
|
||||
std::cout << "[安全日志] 第三方鉴权失败,API Key: " << request.apiKey << std::endl;
|
||||
// 步骤8: 返回失败响应
|
||||
return resp;
|
||||
}
|
||||
// 步骤4: 数据格式转换与字段映射匹配(此处假设直接使用原始记录)
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (const auto& rec : request.records) {
|
||||
// 步骤5: 执行考勤核算结果与第三方系统的数据双向同步写入
|
||||
// 模拟写入,实际应调用第三方SDK
|
||||
// 假设全部成功
|
||||
successCount++;
|
||||
}
|
||||
// 步骤6: 生成同步成功与失败条目的详细日志
|
||||
std::cout << "[同步日志] 成功: " << successCount << " 条, 失败: " << failCount << " 条" << std::endl;
|
||||
// 步骤7: 向第三方系统返回同步成功响应与结果统计
|
||||
resp.success = true;
|
||||
resp.syncedCount = successCount;
|
||||
resp.failedCount = failCount;
|
||||
resp.message = "同步完成";
|
||||
return resp;
|
||||
}
|
||||
|
||||
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("无效日期范围");
|
||||
// ---------- 辅助函数实现(占位) ----------
|
||||
bool AttendanceSystem::validateEmployee(const std::string& employeeId) {
|
||||
// 实际应查询数据库或缓存,此处模拟有效
|
||||
return !employeeId.empty();
|
||||
}
|
||||
|
||||
// 遍历每日
|
||||
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);
|
||||
}
|
||||
AttendanceRule AttendanceSystem::getRuleForEmployee(const std::string& employeeId) {
|
||||
// 模拟返回默认规则
|
||||
AttendanceRule rule;
|
||||
rule.department = "DEFAULT";
|
||||
rule.shift = "DAY";
|
||||
rule.startHour = 9;
|
||||
rule.startMinute = 0;
|
||||
rule.endHour = 18;
|
||||
rule.endMinute = 0;
|
||||
rule.lateThreshold = 30;
|
||||
rule.earlyLeaveThreshold = 30;
|
||||
return rule;
|
||||
}
|
||||
|
||||
return report;
|
||||
void AttendanceSystem::storeRawRecord(const RawRecord& rec) {
|
||||
// 模拟存储到数据库
|
||||
std::cout << "[存储] 原始打卡记录: " << rec.employeeId << " @ " << rec.punchTime << std::endl;
|
||||
}
|
||||
|
||||
// ---- 异常预警 ----
|
||||
|
||||
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;
|
||||
void AttendanceSystem::storeCalculationResult(const CalculationResult& res) {
|
||||
// 模拟存储核算结果
|
||||
std::cout << "[存储] 核算结果: " << res.employeeId << " 状态: " << res.status << std::endl;
|
||||
}
|
||||
|
||||
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;
|
||||
std::vector<RawRecord> AttendanceSystem::fetchUncalculatedRecords() {
|
||||
// 模拟返回未核算记录
|
||||
return std::vector<RawRecord>();
|
||||
}
|
||||
|
||||
// ---- 报表任务 ----
|
||||
|
||||
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;
|
||||
CalculationResult AttendanceSystem::calculateSingleRecord(const RawRecord& rec, const AttendanceRule& rule) {
|
||||
// 模拟核算逻辑,返回正常
|
||||
CalculationResult res;
|
||||
res.employeeId = rec.employeeId;
|
||||
res.date = rec.punchTime;
|
||||
res.status = CalculationResult::NORMAL;
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
void AttendanceSystem::logWarningSend(const WarningNotification& notif) {
|
||||
// 模拟日志记录
|
||||
std::cout << "[日志] 预警通知已发送至 " << notif.employeeId << " 于时间戳 " << notif.sendTimestamp << std::endl;
|
||||
}
|
||||
|
||||
std::vector<std::string> AttendanceSystem::getManagersForEmployee(const std::string& employeeId) {
|
||||
// 模拟返回管理员列表
|
||||
return {"manager001"};
|
||||
}
|
||||
throw std::runtime_error("任务不存在: " + taskId);
|
||||
|
||||
bool AttendanceSystem::authenticateThirdParty(const std::string& apiKey) {
|
||||
// 模拟鉴权,简单比较
|
||||
return apiKey == "VALID_API_KEY";
|
||||
}
|
||||
|
|
|
|||
267
src/main.cpp
267
src/main.cpp
|
|
@ -1,225 +1,62 @@
|
|||
/**
|
||||
* @file main.cpp
|
||||
* @brief 智能考勤管理系统命令行入口
|
||||
*
|
||||
* 本文件提供了各需求点的功能演示流程:
|
||||
* - SRS-ATT_F-001: 模拟移动端打卡
|
||||
* - SRS-ATT_F-002: 触发自动核算
|
||||
* - SRS-ATT_F-003: 异常预警演示(基于核算结果)
|
||||
* - SRS-ATT_F-004: 生成报表
|
||||
* - SRS-ATT_F-005: 与第三方同步
|
||||
*/
|
||||
|
||||
#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";
|
||||
AttendanceSystem system;
|
||||
|
||||
AttendanceService svc;
|
||||
// SRS-ATT_F-001: 多终端打卡支持 - 模拟移动端打卡请求
|
||||
std::cout << "=== SRS-ATT_F-001: 多终端打卡支持 ===" << std::endl;
|
||||
PunchRequest mobileReq;
|
||||
mobileReq.employeeId = "EMP001";
|
||||
mobileReq.terminalType = "MOBILE";
|
||||
mobileReq.deviceId = "iPhone14Pro";
|
||||
mobileReq.timestamp = std::time(nullptr);
|
||||
mobileReq.latitude = 31.2304;
|
||||
mobileReq.longitude = 121.4737;
|
||||
PunchResponse resp = system.processPunch(mobileReq);
|
||||
std::cout << "打卡结果: " << (resp.success ? "成功" : "失败") << " - " << resp.message << std::endl;
|
||||
|
||||
demoEmployeeManagement(svc);
|
||||
demoCheckIn(svc);
|
||||
demoAttendanceCalculation(svc);
|
||||
demoAlerts(svc);
|
||||
demoReport(svc);
|
||||
demoDataSecurity();
|
||||
// SRS-ATT_F-002: 考勤数据自动核算
|
||||
std::cout << "\n=== SRS-ATT_F-002: 考勤数据自动核算 ===" << std::endl;
|
||||
system.autoCalculateAttendance();
|
||||
|
||||
printSeparator();
|
||||
std::cout << "全部演示完成!" << std::endl;
|
||||
printSeparator();
|
||||
// SRS-ATT_F-003: 异常考勤预警(由核算内部触发,此处仅示意)
|
||||
std::cout << "\n=== SRS-ATT_F-003: 异常考勤预警 ===" << std::endl;
|
||||
// 预警已在 autoCalculate 中自动调用,此处可额外模拟
|
||||
CalculationResult anomaly;
|
||||
anomaly.employeeId = "EMP002";
|
||||
anomaly.status = CalculationResult::ABSENT;
|
||||
system.sendWarningForAnomaly(anomaly);
|
||||
|
||||
// SRS-ATT_F-004: 考勤报表生成导出
|
||||
std::cout << "\n=== SRS-ATT_F-004: 考勤报表生成导出 ===" << std::endl;
|
||||
ReportQuery query;
|
||||
query.department = "DEPT_A";
|
||||
query.startDate = std::time(nullptr) - 7*24*3600;
|
||||
query.endDate = std::time(nullptr);
|
||||
query.format = ReportQuery::EXCEL;
|
||||
ReportFile report = system.generateReport(query);
|
||||
std::cout << "报表文件名: " << report.fileName << std::endl;
|
||||
|
||||
// SRS-ATT_F-005: 第三方系统集成
|
||||
std::cout << "\n=== SRS-ATT_F-005: 第三方系统集成 ===" << std::endl;
|
||||
ThirdPartySyncRequest syncReq;
|
||||
syncReq.apiKey = "VALID_API_KEY";
|
||||
syncReq.systemType = "HR";
|
||||
SyncResponse syncResp = system.syncWithThirdParty(syncReq);
|
||||
std::cout << "同步结果: " << (syncResp.success ? "成功" : "失败") << " - " << syncResp.message << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,285 +1,84 @@
|
|||
/**
|
||||
* @file basic_test.cpp
|
||||
* @brief 智能考勤管理系统单元测试
|
||||
*
|
||||
* 每个测试用例关联对应的需求标识:
|
||||
* - TestPunchFunction: 覆盖 SRS-ATT_F-001
|
||||
* - TestAutoCalculate: 覆盖 SRS-ATT_F-002
|
||||
* - TestWarning: 覆盖 SRS-ATT_F-003
|
||||
* - TestReport: 覆盖 SRS-ATT_F-004
|
||||
* - TestThirdPartySync: 覆盖 SRS-ATT_F-005
|
||||
*/
|
||||
|
||||
#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;
|
||||
// 测试 SRS-ATT_F-001: 多终端打卡支持
|
||||
void testPunchFunction() {
|
||||
AttendanceSystem sys;
|
||||
PunchRequest req;
|
||||
req.employeeId = "EMP_TEST";
|
||||
req.terminalType = "MOBILE";
|
||||
req.timestamp = std::time(nullptr);
|
||||
PunchResponse resp = sys.processPunch(req);
|
||||
assert(resp.success);
|
||||
std::cout << "TestPunchFunction passed.\n";
|
||||
}
|
||||
|
||||
static void testDuplicateEmployee() {
|
||||
AttendanceService svc;
|
||||
Employee e;
|
||||
e.employeeId = "T002";
|
||||
e.name = "重复测试";
|
||||
svc.addEmployee(e);
|
||||
|
||||
bool thrown = false;
|
||||
// 测试 SRS-ATT_F-002: 考勤数据自动核算(简单验证不崩溃)
|
||||
void testAutoCalculate() {
|
||||
AttendanceSystem sys;
|
||||
// 正常情况下不应抛出异常
|
||||
try {
|
||||
svc.addEmployee(e);
|
||||
} catch (const std::runtime_error&) {
|
||||
thrown = true;
|
||||
sys.autoCalculateAttendance();
|
||||
std::cout << "TestAutoCalculate passed.\n";
|
||||
} catch (...) {
|
||||
assert(false && "autoCalculate threw exception");
|
||||
}
|
||||
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;
|
||||
// 测试 SRS-ATT_F-003: 异常考勤预警(验证报警触发)
|
||||
void testWarning() {
|
||||
AttendanceSystem sys;
|
||||
CalculationResult res;
|
||||
res.employeeId = "EMP_WARN";
|
||||
res.status = CalculationResult::LATE;
|
||||
// 只需确保函数执行不崩溃
|
||||
sys.sendWarningForAnomaly(res);
|
||||
std::cout << "TestWarning passed.\n";
|
||||
}
|
||||
|
||||
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;
|
||||
// 测试 SRS-ATT_F-004: 考勤报表生成导出
|
||||
void testReport() {
|
||||
AttendanceSystem sys;
|
||||
ReportQuery query;
|
||||
query.department = "TEST_DEPT";
|
||||
query.startDate = 0;
|
||||
query.endDate = std::time(nullptr);
|
||||
query.format = ReportQuery::EXCEL;
|
||||
ReportFile file = sys.generateReport(query);
|
||||
assert(!file.fileName.empty());
|
||||
std::cout << "TestReport passed.\n";
|
||||
}
|
||||
|
||||
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;
|
||||
// 测试 SRS-ATT_F-005: 第三方系统集成
|
||||
void testThirdPartySync() {
|
||||
AttendanceSystem sys;
|
||||
ThirdPartySyncRequest req;
|
||||
req.apiKey = "VALID_API_KEY";
|
||||
req.systemType = "HR";
|
||||
SyncResponse resp = sys.syncWithThirdParty(req);
|
||||
assert(resp.success);
|
||||
std::cout << "TestThirdPartySync passed.\n";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
testPunchFunction();
|
||||
testAutoCalculate();
|
||||
testWarning();
|
||||
testReport();
|
||||
testThirdPartySync();
|
||||
std::cout << "All tests passed!\n";
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue