refactor(api): 重构数据库访问为SQLAlchemy绑定的session

- 统一移除手动创建的数据库session,统一使用models模块中的db.session
- 修正项目创建接口,增加开始和结束日期的格式验证与处理
- 更新导入项目接口,使用枚举类型校验项目类型并优化异常处理
- 更新统计接口,避免多次查询假期数据,优化日期字符串处理
- 删除回滚前多余的session关闭调用,改为使用db.session.rollback()
- app.py中重构数据库初始化:统一配置SQLAlchemy,动态创建数据库路径和表
- 项目模型新增开始日期和结束日期字段支持
- 添加导入批次历史记录模型支持
- 优化工具函数中日期类型提示,移除无用导入
- 更新requirements.txt依赖版本回退,确保兼容性
- 前端菜单添加导入历史导航入口,实现页面访问路由绑定
This commit is contained in:
2025-09-04 18:12:24 +08:00
parent ef9432f6da
commit 8938ce2708
29 changed files with 3490 additions and 150 deletions

View File

@@ -1,25 +1,17 @@
from flask import Blueprint, request, jsonify
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from backend.models.models import Project, ProjectType
from backend.models.utils import *
from models.models import db, Project, ProjectType
from models.utils import *
import csv
import io
from datetime import datetime
projects_bp = Blueprint('projects', __name__)
def get_db_session():
"""获取数据库会话"""
engine = create_engine('sqlite:///data/timetrack.db')
Session = sessionmaker(bind=engine)
return Session()
@projects_bp.route('/api/projects', methods=['GET'])
def get_projects():
"""获取所有项目列表"""
try:
session = get_db_session()
projects = session.query(Project).filter_by(is_active=True).all()
projects = db.session.query(Project).filter_by(is_active=True).all()
result = []
for project in projects:
@@ -32,10 +24,10 @@ def get_projects():
result.append(project_dict)
session.close()
return jsonify({'success': True, 'data': result})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
@projects_bp.route('/api/projects', methods=['POST'])
@@ -43,14 +35,19 @@ def create_project():
"""创建新项目"""
try:
data = request.json
session = get_db_session()
# 验证必填字段
if not data.get('project_name') or not data.get('customer_name') or not data.get('project_type'):
return jsonify({'success': False, 'error': '项目名称、客户名和项目类型为必填项'}), 400
project_type_str = data['project_type']
try:
project_type = ProjectType(project_type_str)
except ValueError:
return jsonify({'success': False, 'error': f'无效的项目类型: {project_type_str}'}), 400
# 根据项目类型设置字段
if data['project_type'] == 'traditional':
if project_type == ProjectType.TRADITIONAL:
if not data.get('project_code'):
return jsonify({'success': False, 'error': '传统项目需要填写项目代码'}), 400
project_code = data['project_code']
@@ -60,47 +57,64 @@ def create_project():
return jsonify({'success': False, 'error': 'PSI项目需要填写合同号'}), 400
project_code = 'PSI-PROJ' # PSI项目统一代码
contract_number = data['contract_number']
# 处理结束日期
end_date = None
if data.get('end_date') and data.get('end_date') != '' :
try:
end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date()
except ValueError:
return jsonify({'success': False, 'error': '结束日期格式不正确,应为 YYYY-MM-DD'}), 400
# 处理开始日期
start_date = None
if data.get('start_date') and data.get('start_date') != '' :
try:
start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date()
except ValueError:
return jsonify({'success': False, 'error': '开始日期格式不正确,应为 YYYY-MM-DD'}), 400
# 检查唯一性约束
existing_project = None
if data['project_type'] == 'traditional':
if project_type == ProjectType.TRADITIONAL:
# 传统项目:客户名+项目代码唯一
existing_project = session.query(Project).filter_by(
existing_project = db.session.query(Project).filter_by(
customer_name=data['customer_name'],
project_code=project_code,
project_type=ProjectType.TRADITIONAL
).first()
else:
# PSI项目客户名+合同号唯一
existing_project = session.query(Project).filter_by(
existing_project = db.session.query(Project).filter_by(
customer_name=data['customer_name'],
contract_number=contract_number,
project_type=ProjectType.PSI
).first()
if existing_project:
session.close()
return jsonify({'success': False, 'error': '项目已存在'}), 400
# 创建新项目
project = Project(
project_name=data['project_name'],
project_type=ProjectType(data['project_type']),
project_type=project_type,
project_code=project_code,
customer_name=data['customer_name'],
contract_number=contract_number,
description=data.get('description', '')
description=data.get('description', ''),
start_date=start_date,
end_date=end_date
)
session.add(project)
session.commit()
db.session.add(project)
db.session.commit()
result = project.to_dict()
session.close()
return jsonify({'success': True, 'data': result})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
@projects_bp.route('/api/projects/<int:project_id>', methods=['PUT'])
@@ -108,11 +122,9 @@ def update_project(project_id):
"""更新项目信息"""
try:
data = request.json
session = get_db_session()
project = session.query(Project).get(project_id)
project = db.session.query(Project).get(project_id)
if not project:
session.close()
return jsonify({'success': False, 'error': '项目不存在'}), 404
# 更新字段
@@ -121,33 +133,30 @@ def update_project(project_id):
if 'description' in data:
project.description = data['description']
session.commit()
db.session.commit()
result = project.to_dict()
session.close()
return jsonify({'success': True, 'data': result})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
@projects_bp.route('/api/projects/<int:project_id>', methods=['DELETE'])
def delete_project(project_id):
"""删除项目(软删除)"""
try:
session = get_db_session()
project = session.query(Project).get(project_id)
project = db.session.query(Project).get(project_id)
if not project:
session.close()
return jsonify({'success': False, 'error': '项目不存在'}), 404
project.is_active = False
session.commit()
session.close()
db.session.commit()
return jsonify({'success': True, 'message': '项目已删除'})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
@projects_bp.route('/api/projects/import', methods=['POST'])
@@ -161,8 +170,6 @@ def import_projects():
if file.filename == '' or not file.filename.endswith('.csv'):
return jsonify({'success': False, 'error': '请选择有效的CSV文件'}), 400
session = get_db_session()
# 读取CSV文件
stream = io.StringIO(file.stream.read().decode("utf-8"))
csv_reader = csv.DictReader(stream)
@@ -177,13 +184,15 @@ def import_projects():
errors.append(f"{row_num}行:项目名称、客户名和项目类型为必填项")
continue
project_type = row['项目类型'].lower()
if project_type not in ['traditional', 'psi']:
project_type_str = row['项目类型'].lower()
try:
project_type = ProjectType(project_type_str)
except ValueError:
errors.append(f"{row_num}行:项目类型只能是 traditional 或 psi")
continue
# 根据项目类型设置字段
if project_type == 'traditional':
if project_type == ProjectType.TRADITIONAL:
if not row.get('项目代码'):
errors.append(f"{row_num}行:传统项目需要填写项目代码")
continue
@@ -198,14 +207,14 @@ def import_projects():
# 检查重复
existing_project = None
if project_type == 'traditional':
existing_project = session.query(Project).filter_by(
if project_type == ProjectType.TRADITIONAL:
existing_project = db.session.query(Project).filter_by(
customer_name=row['客户名'],
project_code=project_code,
project_type=ProjectType.TRADITIONAL
).first()
else:
existing_project = session.query(Project).filter_by(
existing_project = db.session.query(Project).filter_by(
customer_name=row['客户名'],
contract_number=contract_number,
project_type=ProjectType.PSI
@@ -218,21 +227,20 @@ def import_projects():
# 创建项目
project = Project(
project_name=row['项目名称'],
project_type=ProjectType(project_type),
project_type=project_type,
project_code=project_code,
customer_name=row['客户名'],
contract_number=contract_number,
description=row.get('描述', '')
)
session.add(project)
db.session.add(project)
created_count += 1
except Exception as e:
errors.append(f"{row_num}行:{str(e)}")
session.commit()
session.close()
db.session.commit()
return jsonify({
'success': True,
@@ -242,4 +250,5 @@ def import_projects():
})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500