418 lines
14 KiB
Python
418 lines
14 KiB
Python
import utils.jwtauth
|
||
from flask import request, jsonify, Blueprint, make_response
|
||
from sqlalchemy import distinct
|
||
from models import db, Doctor, Office, DoctorOffice, DoctorHospital, Patient, Case, Image
|
||
from datetime import datetime
|
||
|
||
hospital_bp = Blueprint('hospital', __name__)
|
||
|
||
@hospital_bp.route('/api/hospital/office', methods=['GET'])
|
||
@utils.jwtauth.jwt_required
|
||
def get_doctor_offices():
|
||
"""
|
||
获取当前登录医生在指定医院负责的所有科室列表。
|
||
如果医生负责的是一个父科室,则返回该科室及其所有子科室。
|
||
结果会自动去重。
|
||
|
||
请求参数:
|
||
- hospital_id: 医院ID
|
||
|
||
返回:
|
||
{
|
||
"code": 200,
|
||
"message": "获取科室列表成功",
|
||
"data": [
|
||
{
|
||
"id": 科室ID,
|
||
"name": "科室名称",
|
||
"parent_id": 父科室ID,
|
||
"hospital_id": 医院ID,
|
||
"created_at": "创建时间",
|
||
"parent_path": [
|
||
{
|
||
"id": 父科室ID,
|
||
"name": "父科室名称",
|
||
"parent_id": 上级父科室ID,
|
||
"hospital_id": 医院ID,
|
||
"created_at": "创建时间"
|
||
},
|
||
...
|
||
]
|
||
},
|
||
...
|
||
]
|
||
}
|
||
"""
|
||
user_id = request.user_id
|
||
hospital_id = request.args.get('hospital_id')
|
||
|
||
if not hospital_id:
|
||
return jsonify({
|
||
'code': 400,
|
||
'message': '缺少必要参数: hospital_id'
|
||
}), 400
|
||
|
||
try:
|
||
hospital_id = int(hospital_id)
|
||
except ValueError:
|
||
return jsonify({
|
||
'code': 400,
|
||
'message': 'hospital_id必须是整数'
|
||
}), 400
|
||
|
||
# 验证医生是否属于该医院
|
||
doctor_hospital = DoctorHospital.query.filter_by(
|
||
doc_id=user_id,
|
||
hosp_id=hospital_id
|
||
).first()
|
||
|
||
if not doctor_hospital:
|
||
return jsonify({
|
||
'code': 403,
|
||
'message': '您不属于该医院或该医院不存在'
|
||
}), 403
|
||
|
||
# 1. 获取医生在该医院直接负责的所有科室
|
||
doctor_offices = db.session.query(Office).join(
|
||
DoctorOffice, Office.id == DoctorOffice.off_id
|
||
).filter(
|
||
DoctorOffice.doc_id == user_id,
|
||
Office.hospital_id == hospital_id
|
||
).all()
|
||
|
||
# 2. 收集所有相关科室(直接负责的 + 子科室),并去重
|
||
final_offices_map = {} # 使用字典去重, key: office.id, value: office object
|
||
|
||
def get_all_descendants(office):
|
||
# 递归函数,获取一个科室及其所有子孙科室
|
||
if office.id not in final_offices_map:
|
||
final_offices_map[office.id] = office
|
||
# office.children 来自于 models.py 中的 backref
|
||
for child in office.children:
|
||
get_all_descendants(child)
|
||
|
||
for office in doctor_offices:
|
||
get_all_descendants(office)
|
||
|
||
# 3. 为每个科室构建完整的父科室路径
|
||
result = []
|
||
# 预先获取医院所有科室,以高效构建父路径,避免循环查库
|
||
all_hospital_offices = {o.id: o for o in Office.query.filter_by(hospital_id=hospital_id).all()}
|
||
|
||
for office in final_offices_map.values():
|
||
office_data = office.to_dict()
|
||
parent_path = []
|
||
|
||
current_parent_id = office.parent_id
|
||
while current_parent_id:
|
||
# 使用预先获取的字典来查找父科室
|
||
parent_office = all_hospital_offices.get(current_parent_id)
|
||
if parent_office:
|
||
parent_path.append(parent_office.to_dict())
|
||
current_parent_id = parent_office.parent_id
|
||
else:
|
||
break
|
||
|
||
# 反转路径,使其从顶层父科室开始
|
||
parent_path.reverse()
|
||
office_data['parent_path'] = parent_path
|
||
result.append(office_data)
|
||
|
||
return make_response(jsonify({
|
||
'code': 200,
|
||
'message': '获取科室列表成功',
|
||
'data': result
|
||
}))
|
||
|
||
def get_all_child_offices(parent_office_id):
|
||
"""
|
||
递归获取一个科室下的所有子科室ID
|
||
"""
|
||
# 使用集合以避免重复
|
||
all_office_ids = {int(parent_office_id)}
|
||
# 待检查的科室ID队列
|
||
offices_to_check = [int(parent_office_id)]
|
||
|
||
while offices_to_check:
|
||
current_id = offices_to_check.pop(0)
|
||
# 查询以当前科室为父科室的所有子科室
|
||
children = Office.query.filter_by(parent_id=current_id).all()
|
||
for child in children:
|
||
if child.id not in all_office_ids:
|
||
all_office_ids.add(child.id)
|
||
offices_to_check.append(child.id)
|
||
|
||
return list(all_office_ids)
|
||
|
||
@hospital_bp.route('/api/hospital/case', methods=['GET'])
|
||
@utils.jwtauth.jwt_required
|
||
def get_cases_by_office():
|
||
"""
|
||
根据科室ID获取所有病历(包括子科室),支持分页、病人姓名搜索和只看我的病历
|
||
"""
|
||
user_id = request.user_id
|
||
office_id = request.args.get('office_id')
|
||
patient_name = request.args.get('patient_name')
|
||
my_case = request.args.get('my_case') # 'true' or 'false'
|
||
page = request.args.get('page', 1, type=int)
|
||
per_page = request.args.get('per_page', 10, type=int)
|
||
|
||
if not office_id:
|
||
return jsonify({'code': 400, 'message': '缺少office_id参数'}), 400
|
||
|
||
try:
|
||
office_id = int(office_id)
|
||
except ValueError:
|
||
return jsonify({'code': 400, 'message': 'office_id必须是整数'}), 400
|
||
|
||
# 检查科室是否存在
|
||
office = Office.query.get(office_id)
|
||
if not office:
|
||
return jsonify({'code': 404, 'message': '科室不存在'}), 404
|
||
|
||
# 获取该科室及其所有子科室的ID
|
||
all_office_ids = get_all_child_offices(office_id)
|
||
|
||
# 在这些科室中查询病历
|
||
cases_query = Case.query.filter(Case.office_id.in_(all_office_ids))
|
||
|
||
# 如果提供了病人姓名,则加入查询条件
|
||
if patient_name:
|
||
cases_query = cases_query.join(Patient).filter(Patient.name.contains(patient_name))
|
||
|
||
# 如果勾选了"只显示我创建的"
|
||
if my_case == 'true':
|
||
cases_query = cases_query.filter(Case.doctor_id == user_id)
|
||
|
||
# 按病历日期降序排序
|
||
cases_pagination = cases_query.order_by(Case.case_date.desc(), Case.created_at.desc()).paginate(
|
||
page=page, per_page=per_page, error_out=False
|
||
)
|
||
|
||
cases = cases_pagination.items
|
||
|
||
# 优化:一次性获取所有相关影像及其预览图
|
||
images_by_case_id = {}
|
||
case_ids = [c.id for c in cases]
|
||
|
||
if case_ids:
|
||
# 1. 获取所有病例关联的主影像
|
||
main_images = db.session.query(Image).filter(
|
||
Image.case_id.in_(case_ids),
|
||
Image.parent_image_id.is_(None)
|
||
).all()
|
||
|
||
main_image_ids = [img.id for img in main_images]
|
||
previews_by_parent_id = {}
|
||
|
||
if main_image_ids:
|
||
# 2. 为兼容旧版MySQL,采用两步查询法获取预览图
|
||
# 首先,获取所有主影像关联的子影像
|
||
all_child_images = db.session.query(Image).filter(
|
||
Image.parent_image_id.in_(main_image_ids)
|
||
).order_by(Image.parent_image_id, Image.id.asc()).all()
|
||
|
||
# 然后,在Python中处理,为每个父ID找到第一个子影像
|
||
for child in all_child_images:
|
||
if child.parent_image_id not in previews_by_parent_id:
|
||
previews_by_parent_id[child.parent_image_id] = child
|
||
|
||
# 3. 构建影像信息并按case_id分组
|
||
base_url = "https://cdn.ember.ac.cn"
|
||
for img in main_images:
|
||
preview_url = f"{base_url}/{img.oss_key}" # 默认是源文件
|
||
|
||
if img.format in ['dicom', 'nii']:
|
||
preview_image = previews_by_parent_id.get(img.id)
|
||
if preview_image:
|
||
preview_url = f"{base_url}/{preview_image.oss_key}"
|
||
|
||
image_info = {
|
||
'image_id': img.id,
|
||
'name': img.name,
|
||
'preview_url': preview_url,
|
||
'dim': img.dim
|
||
}
|
||
if img.case_id not in images_by_case_id:
|
||
images_by_case_id[img.case_id] = []
|
||
images_by_case_id[img.case_id].append(image_info)
|
||
|
||
# 格式化返回数据
|
||
data = []
|
||
for case in cases:
|
||
case_data = case.to_dict()
|
||
case_data['patient_name'] = case.patient.name
|
||
case_data['doctor_name'] = case.doctor.name
|
||
case_data['office_name'] = case.office.name
|
||
case_data['images'] = images_by_case_id.get(case.id, []) # 添加影像列表
|
||
data.append(case_data)
|
||
|
||
return jsonify({
|
||
'code': 200,
|
||
'message': '查询成功',
|
||
'data': data,
|
||
'pagination': {
|
||
'page': cases_pagination.page,
|
||
'per_page': cases_pagination.per_page,
|
||
'total_pages': cases_pagination.pages,
|
||
'total_items': cases_pagination.total
|
||
}
|
||
})
|
||
|
||
@hospital_bp.route('/api/hospital/add_patient', methods=['POST'])
|
||
@utils.jwtauth.jwt_required
|
||
def add_patient():
|
||
"""
|
||
添加新病人
|
||
"""
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({'code': 400, 'message': '请求体不能为空'}), 400
|
||
|
||
name = data.get('name')
|
||
gender = data.get('gender')
|
||
birthday = data.get('birthday') # birthday是可选的
|
||
|
||
if not name or not gender:
|
||
return jsonify({'code': 400, 'message': '缺少必要参数: name, gender'}), 400
|
||
|
||
if gender not in ['男', '女', '未知']:
|
||
return jsonify({'code': 400, 'message': 'gender参数无效,应为 "男"、"女" 或 "未知"'}), 400
|
||
|
||
try:
|
||
new_patient = Patient(name=name, gender=gender, birthday=birthday)
|
||
db.session.add(new_patient)
|
||
db.session.commit()
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify({'code': 500, 'message': f'数据库错误: {str(e)}'}), 500
|
||
|
||
return jsonify({
|
||
'code': 201,
|
||
'message': '添加病人成功',
|
||
'data': new_patient.to_dict()
|
||
}), 201
|
||
|
||
@hospital_bp.route('/api/hospital/case', methods=['POST'])
|
||
@utils.jwtauth.jwt_required
|
||
def add_case():
|
||
"""
|
||
创建新病历
|
||
"""
|
||
doctor_id = request.user_id
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({'code': 400, 'message': '请求体不能为空'}), 400
|
||
|
||
patient_id = data.get('patient_id')
|
||
office_id = data.get('office_id')
|
||
case_date_str = data.get('case_date')
|
||
|
||
if not all([patient_id, office_id, case_date_str]):
|
||
return jsonify({'code': 400, 'message': '缺少必要参数: patient_id, office_id, case_date'}), 400
|
||
|
||
# 验证数据
|
||
if not Patient.query.get(patient_id):
|
||
return jsonify({'code': 404, 'message': '病人不存在'}), 404
|
||
|
||
office = Office.query.get(office_id)
|
||
if not office:
|
||
return jsonify({'code': 404, 'message': '科室不存在'}), 404
|
||
|
||
# 验证医生是否属于该科室所在的医院
|
||
if not DoctorHospital.query.filter_by(doc_id=doctor_id, hosp_id=office.hospital_id).first():
|
||
return jsonify({'code': 403, 'message': '您不属于该科室所在的医院,无法创建病历'}), 403
|
||
|
||
try:
|
||
case_date = datetime.strptime(case_date_str, '%Y-%m-%d').date()
|
||
except ValueError:
|
||
return jsonify({'code': 400, 'message': 'case_date格式不正确,应为YYYY-MM-DD'}), 400
|
||
|
||
# 创建新病历
|
||
new_case = Case(
|
||
doctor_id=doctor_id,
|
||
patient_id=patient_id,
|
||
office_id=office_id,
|
||
case_date=case_date,
|
||
chief_complaint=data.get('chief_complaint'),
|
||
present_illness_history=data.get('present_illness_history'),
|
||
past_medical_history=data.get('past_medical_history'),
|
||
personal_history=data.get('personal_history'),
|
||
family_history=data.get('family_history'),
|
||
physical_examination=data.get('physical_examination'),
|
||
diagnosis=data.get('diagnosis'),
|
||
treatment_plan=data.get('treatment_plan'),
|
||
medication_details=data.get('medication_details'),
|
||
notes=data.get('notes')
|
||
)
|
||
|
||
try:
|
||
db.session.add(new_case)
|
||
db.session.commit()
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify({'code': 500, 'message': f'数据库错误: {str(e)}'}), 500
|
||
|
||
return jsonify({'code': 201, 'message': '病历创建成功', 'data': new_case.to_dict()}), 201
|
||
|
||
@hospital_bp.route('/api/hospital/case', methods=['PUT'])
|
||
@utils.jwtauth.jwt_required
|
||
def update_case():
|
||
"""
|
||
更新病历信息
|
||
"""
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({'code': 400, 'message': '请求体不能为空'}), 400
|
||
|
||
case_id = data.get('id')
|
||
if not case_id:
|
||
return jsonify({'code': 400, 'message': '缺少病历ID (id)'}), 400
|
||
|
||
case = Case.query.get(case_id)
|
||
if not case:
|
||
return jsonify({'code': 404, 'message': '病历不存在'}), 404
|
||
|
||
# 任何医生都可以编辑病历,也可以添加权限控制
|
||
# if case.doctor_id != request.user_id:
|
||
# return jsonify({'code': 403, 'message': '您无权编辑此病历'}), 403
|
||
|
||
# 更新字段
|
||
for key, value in data.items():
|
||
# id和创建时间不能被修改
|
||
if key not in ['id', 'created_at', 'doctor_id', 'patient_id'] and hasattr(case, key):
|
||
if key == 'case_date':
|
||
try:
|
||
setattr(case, key, datetime.strptime(value, '%Y-%m-%d').date())
|
||
except (ValueError, TypeError):
|
||
# 如果日期格式错误,可以忽略或返回错误
|
||
pass
|
||
else:
|
||
setattr(case, key, value)
|
||
|
||
try:
|
||
db.session.commit()
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify({'code': 500, 'message': f'数据库错误: {str(e)}'}), 500
|
||
|
||
return jsonify({'code': 200, 'message': '病历更新成功', 'data': case.to_dict()})
|
||
|
||
# 根据姓名搜索病人
|
||
@hospital_bp.route('/api/hospital/patient', methods=['GET'])
|
||
@utils.jwtauth.jwt_required
|
||
def search_patient():
|
||
"""
|
||
根据姓名搜索病人
|
||
"""
|
||
name = request.args.get('name')
|
||
if not name:
|
||
return jsonify({'code': 400, 'message': '缺少必要参数: name'}), 400
|
||
|
||
patients = Patient.query.filter(Patient.name.contains(name)).all()
|
||
return jsonify({
|
||
'code': 200,
|
||
'message': '搜索成功',
|
||
'data': [patient.to_dict() for patient in patients]
|
||
})
|