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

254 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Blueprint, request, jsonify
from models.models import db, Project, ProjectType
from models.utils import *
import csv
import io
from datetime import datetime
projects_bp = Blueprint('projects', __name__)
@projects_bp.route('/api/projects', methods=['GET'])
def get_projects():
"""获取所有项目列表"""
try:
projects = db.session.query(Project).filter_by(is_active=True).all()
result = []
for project in projects:
project_dict = project.to_dict()
# 为前端显示格式化项目信息
if project.project_type == ProjectType.TRADITIONAL:
project_dict['display_name'] = f"{project.project_name} ({project.customer_name}-{project.project_code})"
else: # PSI项目
project_dict['display_name'] = f"{project.project_name} ({project.customer_name}-{project.contract_number})"
result.append(project_dict)
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'])
def create_project():
"""创建新项目"""
try:
data = request.json
# 验证必填字段
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 project_type == ProjectType.TRADITIONAL:
if not data.get('project_code'):
return jsonify({'success': False, 'error': '传统项目需要填写项目代码'}), 400
project_code = data['project_code']
contract_number = None
else: # PSI项目
if not data.get('contract_number'):
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 project_type == ProjectType.TRADITIONAL:
# 传统项目:客户名+项目代码唯一
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 = db.session.query(Project).filter_by(
customer_name=data['customer_name'],
contract_number=contract_number,
project_type=ProjectType.PSI
).first()
if existing_project:
return jsonify({'success': False, 'error': '项目已存在'}), 400
# 创建新项目
project = Project(
project_name=data['project_name'],
project_type=project_type,
project_code=project_code,
customer_name=data['customer_name'],
contract_number=contract_number,
description=data.get('description', ''),
start_date=start_date,
end_date=end_date
)
db.session.add(project)
db.session.commit()
result = project.to_dict()
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'])
def update_project(project_id):
"""更新项目信息"""
try:
data = request.json
project = db.session.query(Project).get(project_id)
if not project:
return jsonify({'success': False, 'error': '项目不存在'}), 404
# 更新字段
if 'project_name' in data:
project.project_name = data['project_name']
if 'description' in data:
project.description = data['description']
db.session.commit()
result = project.to_dict()
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:
project = db.session.query(Project).get(project_id)
if not project:
return jsonify({'success': False, 'error': '项目不存在'}), 404
project.is_active = False
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'])
def import_projects():
"""批量导入项目"""
try:
if 'file' not in request.files:
return jsonify({'success': False, 'error': '请选择CSV文件'}), 400
file = request.files['file']
if file.filename == '' or not file.filename.endswith('.csv'):
return jsonify({'success': False, 'error': '请选择有效的CSV文件'}), 400
# 读取CSV文件
stream = io.StringIO(file.stream.read().decode("utf-8"))
csv_reader = csv.DictReader(stream)
created_count = 0
errors = []
for row_num, row in enumerate(csv_reader, start=2):
try:
# 验证必填字段
if not row.get('项目名称') or not row.get('客户名') or not row.get('项目类型'):
errors.append(f"{row_num}行:项目名称、客户名和项目类型为必填项")
continue
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 == ProjectType.TRADITIONAL:
if not row.get('项目代码'):
errors.append(f"{row_num}行:传统项目需要填写项目代码")
continue
project_code = row['项目代码']
contract_number = None
else: # PSI项目
if not row.get('合同号'):
errors.append(f"{row_num}PSI项目需要填写合同号")
continue
project_code = 'PSI-PROJ'
contract_number = row['合同号']
# 检查重复
existing_project = None
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 = db.session.query(Project).filter_by(
customer_name=row['客户名'],
contract_number=contract_number,
project_type=ProjectType.PSI
).first()
if existing_project:
errors.append(f"{row_num}行:项目已存在")
continue
# 创建项目
project = Project(
project_name=row['项目名称'],
project_type=project_type,
project_code=project_code,
customer_name=row['客户名'],
contract_number=contract_number,
description=row.get('描述', '')
)
db.session.add(project)
created_count += 1
except Exception as e:
errors.append(f"{row_num}行:{str(e)}")
db.session.commit()
return jsonify({
'success': True,
'message': f'成功导入{created_count}个项目',
'created_count': created_count,
'errors': errors
})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 500