feat(time-tracking): 添加个人工时记录系统设计文档

- 完成系统架构和数据模型设计,包括项目、工时记录、休息日和周期表模型
- 设计项目管理模块,支持传统项目与PSI项目管理及批量导入功能
- 规划工时记录模块,含日期、事件描述、项目选择及工时计算规则
- 定义休息日分类,支持周末、国定节假日、个人假期及调休工时管理
- 制定统计分析模块设计,支持按Cut-Off周期的周统计与项目工时分布
- 设计周期管理模块,提供周期设置及预设模板功能
- 制定用户界面布局及各页面表单、样式设计方案
- 规划RESTful API端点,涵盖项目、工时记录、休息日、周期及统计数据操作
- 设计数据流示意,阐明操作流程及前后端交互逻辑
- 制定数据存储方案,包括SQLite数据库配置及备份导出机制
This commit is contained in:
2025-09-04 15:19:35 +08:00
parent cda1360ce4
commit ef9432f6da
11 changed files with 1163 additions and 0 deletions

113
backend/models/utils.py Normal file
View File

@@ -0,0 +1,113 @@
import datetime
from typing import Optional, Dict, Any, List
from backend.models.models import Holiday
def is_weekend(date: datetime.date) -> bool:
"""判断是否为周末"""
return date.weekday() >= 5 # 周六=5, 周日=6
def is_holiday(date: datetime.date, holidays: List[Holiday] = None) -> Dict[str, Any]:
"""检测指定日期是否为休息日"""
day_of_week = date.weekday() # 0=周一, 6=周日
is_weekend_day = day_of_week >= 5 # 周六、周日
# 检查是否为配置的节假日
configured_holiday = None
if holidays:
for holiday in holidays:
if holiday.date == date:
configured_holiday = holiday
break
is_configured_holiday = configured_holiday is not None
return {
'is_holiday': is_weekend_day or is_configured_holiday,
'holiday_type': 'weekend' if is_weekend_day else (configured_holiday.holiday_type if configured_holiday else None),
'holiday_name': configured_holiday.holiday_name if configured_holiday else None,
'is_working_day': configured_holiday.is_working_day if configured_holiday else False
}
def calculate_hours(start_time: Optional[str], end_time: Optional[str], is_holiday_flag: bool = False) -> str:
"""工时计算函数"""
if not start_time or not end_time or start_time == '-' or end_time == '-':
return '0:00' if is_holiday_flag else '-' # 休息日默认显示0:00而不是-
try:
start = datetime.datetime.strptime(start_time, '%H:%M')
end = datetime.datetime.strptime(end_time, '%H:%M')
# 处理跨日情况
if end < start:
end += datetime.timedelta(days=1)
diff = end - start
total_minutes = int(diff.total_seconds() / 60)
hours = total_minutes // 60
minutes = total_minutes % 60
return f"{hours}:{minutes:02d}"
except ValueError:
return '0:00'
def format_hours_to_decimal(hours: str) -> float:
"""工时格式转换函数:将"2:42"格式转换为小数格式用于计算"""
if hours == '-' or not hours:
return 0.0
if ':' in hours:
try:
parts = hours.split(':')
h = int(parts[0])
m = int(parts[1]) if len(parts) > 1 else 0
return h + (m / 60)
except ValueError:
return 0.0
try:
return float(hours)
except ValueError:
return 0.0
def format_decimal_to_hours(decimal_hours: float) -> str:
"""将小数工时转换回"HH:MM"格式"""
hours = int(decimal_hours)
minutes = int((decimal_hours - hours) * 60)
return f"{hours}:{minutes:02d}"
def calculate_weekly_hours(records: list) -> Dict[str, float]:
"""工时统计函数(区分工作日和休息日)"""
workday_hours = sum([
format_hours_to_decimal(r.get('hours', '0:00'))
for r in records
if not r.get('is_holiday', False) and r.get('hours') not in ['-', '0:00', None]
])
holiday_hours = sum([
format_hours_to_decimal(r.get('hours', '0:00'))
for r in records
if r.get('is_holiday', False) and r.get('hours') not in ['-', '0:00', None]
])
return {
'workday_hours': workday_hours,
'holiday_hours': holiday_hours,
'total_hours': workday_hours + holiday_hours
}
def get_week_info(date: datetime.date) -> str:
"""获取周信息,如"51周/53周" """
year = date.year
week_num = date.isocalendar()[1]
# 计算该年总共有多少周
last_day = datetime.date(year, 12, 31)
total_weeks = last_day.isocalendar()[1]
return f"{week_num}周/{total_weeks}"
def get_day_of_week_chinese(date: datetime.date) -> str:
"""获取中文星期"""
weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
return weekdays[date.weekday()]