You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python
# encoding: utf-8
"""
@author: tx
@file: report.py
@time: 2023/6/9 15:33
@desc:
"""
from datetime import datetime, timedelta
import pytz
from fastapi import APIRouter, Request, Depends, Response
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from tortoise.functions import Count, Sum
from tortoise.queryset import Q
from tortoise.queryset import QuerySet
from conf import setting
from .export import *
from .report_cab import report_inventory_drug, report_use_drug, report_slack_drug, report_put_in_drug, \
report_remain_drug, report_dosage_drug, report_cabinet_environmental
from helper import respond_to, login_required
from helper.tool import parse_datetime
from models import User
from models.dictionary import Dictionary
from models.drug import Drug, DrugStateEnum
from models.drug_use_log import DrugUseLog, DrugUseStateEnum
from models.log import EnvironmentLogs
from models.template import Template
from models.cabinet import Cabinet
router = APIRouter(prefix='', dependencies=[Depends(login_required)])
class SearchWord(BaseModel):
slack_day: int = 0
drug_name: str | None
dictionary_id: str | None
user_id: str | None
state: int | None
stime: str | None
etime: str | None
slack_day: int | None
page_no: int = 1
page_size: int = 10
cabinet_ids: list = []
cabinet_id: str | None
async def parse_cabinet_ids():
cabinet_obj = await Cabinet.filter(terminal_id=setting.TERMINAL_ID).prefetch_related("archive").first()
cabinets_ids = await Cabinet.filter(archive_id=cabinet_obj.archive.id).values_list("id", flat=True)
return cabinets_ids
async def parse_archive_cabinet_ids(archive_id: str, client_ids: list):
"""
根据大类ID获取柜体
如果传递客户端ids参数则返回用户有权限的大类柜体列表
:param archive_id:
:param client_ids: 用户有权限的柜体id如果不传则代表全部柜体
:return:
"""
query = QuerySet(Cabinet).filter()
if archive_id:
query = query.filter(archive_id=archive_id)
if client_ids:
query = query.filter(id__in=client_ids)
# 用户有权限的柜体
cabinets_ids = await query.values_list("id", flat=True)
return cabinets_ids
class DrugSearch(BaseModel):
drug_name: str | None
dictionary_id: str | None
cabinet_ids: list | None
user_id: str | None
state: int | None
stime: str | None
etime: str | None
slack_day: int | None
cabinet_id: str | None
page_no: int = 1
page_size: int = 10
rfid:str | None
spec:str | None
purity:str | None
batch_no:str | None
manufacturer:str | None
position:str | None
report_ids:str | None
@router.get('/archives/{drug_name}', summary='大类下拉選項')
async def index(drug_name: str):
"""
大类下拉選項
drug_name: 试剂名称
:return:
"""
result_list = []
dictionary = await Dictionary.all()
for dic_obj in dictionary:
if drug_name in dic_obj.k1:
keys = ['k1', 'k2', 'k3', 'k4', 'k5', 'k6']
result = ','.join([str(getattr(dic_obj, key)) for key in keys if getattr(dic_obj, key)])
result_list.append({'drug_info': result, 'id': dic_obj.id})
return respond_to(data=result_list)
# 库存明细
@router.get('/inventory', summary="库存明细")
async def index(request: Request, drug_type: str = '', drug_name: str = '', dictionary_id: str = '', page_no: int = 1,
page_size: int = 20, state: int = -1, cabinet_id: str = ''):
"""
库存明细
根据大类展示库存明细
库存明细增加状态和位置筛选
查询条件药剂名称目前暂定K1字段为药剂名称 -> 刷选后列出对应其它关键信息
查询条件如果有药剂名称则查询药剂名称对应字典对应药剂如果参数有字典id直接查询对应字典对应药剂如果两个都有则联合查询
- 药剂信息药剂名称、药剂规格、药剂纯度等组合、CAS码、保质期、最后使用人、余量、状态在库出库、位置按药剂信息和保质期最快到期在前排序
- 查询成单独一类(以药剂字典为准)后,按余量少的排序在前
:return:
"""
keyword = {
"drug_name": drug_name,
"dictionary_id": dictionary_id,
"page_no": page_no,
"page_size": page_size,
"state": state,
"cabinet_id": cabinet_id
}
if drug_type == "box":
# 盒装药剂库存明细
# data = await box_report_inventory_drug(request, keyword)
data = await report_inventory_drug(request, keyword)
else:
data = await report_inventory_drug(request, keyword)
return respond_to(code=200, data=data)
@router.post('/use', summary="使用明细")
async def index(request: Request, keyword: DrugSearch):
"""
使用明细
- 管理员可通过【下拉用户列表】查询,普通用户无查询功能
- 查询条件药剂名称目前暂定K1字段为药剂名称 -> 刷选后列出对应其它关键信息
- 药剂信息药剂名称、药剂规格、药剂纯度等组合、CAS码按领用时间倒序
逻辑:查询所有未归还药剂,所有其他药剂
- 未归还药剂使用记录,按时间倒叙
- 其他药剂使用记录,按时间倒叙
- 列表合并之后获取总数与分页
:return:
"""
data = await report_use_drug(request, keyword)
return respond_to(data=data)
@router.post('/remain', summary="库存量统计")
async def index(request: Request, keyword: DrugSearch):
"""
库存量统计
# - 查询条件药剂名称目前暂定K1字段为药剂名称 -> 刷选后列出对应其它关键信息
# - 药剂信息(药剂名称、药剂规格、药剂纯度等组合,以药剂字典为准)、瓶数,查看明细进入库存明细表
"""
result = await report_remain_drug(request, keyword)
return respond_to(data=result)
@router.post('/put_in', summary="入库统计")
async def post(request: Request, keyword: DrugSearch):
"""
入库统计
- 提供时间区间查询默认近365天。
- 药剂信息(药剂名称、药剂规格、药剂纯度等组合),入库量。
"""
result = await report_put_in_drug(request, keyword)
return respond_to(200, data=result)
@router.post('/dosage', summary="药剂用量")
async def index(request: Request, keyword: DrugSearch):
"""
药剂用量
- 提供时间区间查询默认近30天。
- 用户每类(药剂字典)领用药剂的使用总量
"""
result = await report_dosage_drug(request, keyword)
return respond_to(200, desc=result.get("desc"), data=result)
@router.get('/slack', summary="呆滞物料")
async def index(request: Request, drug_name: str = "", slack_day: int = 30, page_no: int = 1, page_size: int = 20):
"""
呆滞物料
- 查询多少天内未领用的药剂默认30天可自定义填写。
- 显示每一瓶药剂信息
"""
keyword = {
"drug_name": drug_name,
"slack_day": slack_day,
"page_no": page_no,
"page_size": page_size
}
result = await report_slack_drug(request, keyword)
return respond_to(200, data=result)
@router.get('/environmental', summary="环境记录")
async def index(request: Request, cabinet_id: str = "", stime: str = "", etime: str = "", page_no: int = 1, page_size:
int = 10):
"""
环境记录报表
- 温度、湿度、voc信息由服务端定期发起采集
- 记录下位机日志
- 提供时间区间查询默认近30天。
"""
offset = (page_no - 1) * page_size
query = QuerySet(EnvironmentLogs).filter(cabinet__terminal=setting.TERMINAL_ID)
# 都为空则不显示
query = query.filter(
Q(left_temperature__isnull=False) | Q(right_temperature__isnull=False) | Q(voc__isnull=False) | Q(
humidity__isnull=False))
if cabinet_id:
query = query.filter(cabinet_id=cabinet_id)
if stime:
query = query.filter(created_at__gte=f"{stime} 00:00:00")
if etime:
query = query.filter(created_at__lte=f"{etime} 23:59:59")
if not stime and not etime:
stime = datetime.now() - timedelta(days=30)
etime = datetime.now()
query = query.filter(created_at__gte=stime, created_at__lte=etime)
count = await query.count()
en_log_objs = await query.prefetch_related("cabinet").limit(page_size).offset(offset).order_by("-created_at")
result = list()
for en_log_obj in en_log_objs:
result.append({
**jsonable_encoder(en_log_obj),
"cabinet_label": en_log_obj.cabinet.label
})
return respond_to(200, data=dict(count=count, data=result))
@router.post('/export/{key}', summary="报表导出")
async def index(request: Request, key: str, keyword: SearchWord):
export_dict = {
"drug_logs": ReportDrugUseLogs, # 流转日志
"inventory": ReportInventory, # 库存明细
"use": ReportUse, # 使用明细
"slack": ReportSlack, # 呆滞物料
"put_in": ReportPutIn, # 入库统计
"put_in1": ReportPutIn1,#入库验证f
"remain": ReportRemain, # 库存量统计
"dosage": ReportDosage, # 试剂用量
"reagentExpiration": ReportReagentExpiration, # 试剂过期
"environmental": ReportEnvironmental, # 环境记录报表
}
if not export_dict.get(key):
return respond_to(404, "请求参数错误,该报表导出暂未实现")
export_obj = export_dict.get(key)(request, keyword)
await export_obj.main()
kwargs = {
'data_list': export_obj.data_list,
'key_list': export_obj.key_list,
'finds_list': export_obj.finds_list,
}
export_obj.build_file(title=export_obj.title,**kwargs)
if key !='put_in1':
binary, encoded_filename = export_obj.export('usb')
else:
binary, encoded_filename = export_obj.export('',download_type='web')
if isinstance(binary, int):
return respond_to(binary, desc=encoded_filename)
return Response(content=binary.getvalue(),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={'Content-Disposition': f'attachment; filename={encoded_filename}'})