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

137
backend/models/models.py Normal file
View File

@@ -0,0 +1,137 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text, Date, Time, ForeignKey, Enum
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
Base = declarative_base()
class ProjectType(enum.Enum):
TRADITIONAL = "traditional" # 传统项目
PSI = "psi" # PSI项目
class Project(Base):
"""项目表模型"""
__tablename__ = 'projects'
id = Column(Integer, primary_key=True, autoincrement=True)
project_name = Column(String(200), nullable=False)
project_type = Column(Enum(ProjectType), nullable=False)
# 通用字段
project_code = Column(String(50), nullable=False)
customer_name = Column(String(200), nullable=False)
# PSI项目特有字段
contract_number = Column(String(100)) # 合同号PSI项目必填
description = Column(Text)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
time_records = relationship("TimeRecord", back_populates="project")
def to_dict(self):
return {
'id': self.id,
'project_name': self.project_name,
'project_type': self.project_type.value if self.project_type else None,
'project_code': self.project_code,
'customer_name': self.customer_name,
'contract_number': self.contract_number,
'description': self.description,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
class TimeRecord(Base):
"""工时记录表模型"""
__tablename__ = 'time_records'
id = Column(Integer, primary_key=True, autoincrement=True)
date = Column(Date, nullable=False)
event_description = Column(String(500)) # 事件描述
project_id = Column(Integer, ForeignKey('projects.id'))
start_time = Column(Time) # 可为空,支持"-"占位符
end_time = Column(Time) # 可为空,支持"-"占位符
activity_num = Column(String(100)) # Activity Num
hours = Column(String(20)) # 工时,支持"2:42"或"8:00:00"格式
is_holiday = Column(Boolean, default=False) # 是否为休息日
is_working_on_holiday = Column(Boolean, default=False) # 休息日是否工作
holiday_type = Column(String(50)) # 休息日类型
week_info = Column(String(50)) # 周信息
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
project = relationship("Project", back_populates="time_records")
def to_dict(self):
return {
'id': self.id,
'date': self.date.isoformat() if self.date else None,
'event_description': self.event_description,
'project_id': self.project_id,
'start_time': self.start_time.strftime('%H:%M') if self.start_time else None,
'end_time': self.end_time.strftime('%H:%M') if self.end_time else None,
'activity_num': self.activity_num,
'hours': self.hours,
'is_holiday': self.is_holiday,
'is_working_on_holiday': self.is_working_on_holiday,
'holiday_type': self.holiday_type,
'week_info': self.week_info,
'project': self.project.to_dict() if self.project else None,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None
}
class Holiday(Base):
"""休息日配置表模型"""
__tablename__ = 'holidays'
id = Column(Integer, primary_key=True, autoincrement=True)
date = Column(Date, nullable=False, unique=True)
holiday_name = Column(String(100)) # 节假日名称
holiday_type = Column(String(50), nullable=False) # weekend/national_holiday/personal_leave/makeup_day
is_working_day = Column(Boolean, default=False) # 调休工作日标记
created_at = Column(DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'date': self.date.isoformat() if self.date else None,
'holiday_name': self.holiday_name,
'holiday_type': self.holiday_type,
'is_working_day': self.is_working_day,
'created_at': self.created_at.isoformat() if self.created_at else None
}
class CutoffPeriod(Base):
"""Cut-Off周期表模型"""
__tablename__ = 'cutoff_periods'
id = Column(Integer, primary_key=True, autoincrement=True)
period_name = Column(String(50), nullable=False)
start_date = Column(Date, nullable=False)
end_date = Column(Date, nullable=False)
target_hours = Column(Integer, default=160)
weeks = Column(Integer, default=4)
year = Column(Integer, nullable=False)
month = Column(Integer, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'period_name': self.period_name,
'start_date': self.start_date.isoformat() if self.start_date else None,
'end_date': self.end_date.isoformat() if self.end_date else None,
'target_hours': self.target_hours,
'weeks': self.weeks,
'year': self.year,
'month': self.month,
'created_at': self.created_at.isoformat() if self.created_at else None
}