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.

672 lines
25 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: drug.py
@time: 2023/5/11 11:04
@desc: 药剂领用归还
"""
import asyncio
import datetime
from decimal import Decimal
from fastapi import APIRouter, Request, Depends
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from tortoise.queryset import Q, QuerySet
from tortoise.functions import Function
from pypika import CustomFunction
from conf import setting
from helper.drug import drugs_except_info, drugs_list_info, gram_to_milligram, milligram_to_gram, open_update_expired_at
from helper import respond_to, login_required
from models import Dictionary, DrawerBoard, Drawer, User
from models.drug import Drug, DrugStateEnum
from models.terminal import Terminal
from models.taboo import Taboo
from models.cabinet import Cabinet
from transfer.standard import Standard
from transfer.code_standard import CodeStandard
from helper.tool import parse_datetime
from helper.log import logger_wrapper
router = APIRouter(prefix='/drugs', dependencies=[Depends(login_required)])
return_router = APIRouter(prefix='/drugs/return',)
class DrugUpdateRequest(BaseModel):
open_date: str = "" # 开封日期
weight: int = 0 # 余量
use_weight: str | Decimal = 0.0 # 用量
rfid: str = ""
id: str = ""
cabinet_id: str = "" # 柜体id
selectDrawer_id:str = ""
class DrugUpdateExpired(BaseModel):
expired_at: str
rfid: str = ""
id: str = ""
class DrugRfids(BaseModel):
rfids: list
class DrugBackTrack(BaseModel):
drug_list: list[dict]
class DrugTakeOut(BaseModel):
terminal_id: str | None
page_no: int = 1
page_size: int = 20
class UpdateDictionary(BaseModel):
lack_stock_count: int = 0
expiration_alert: int = 0
class EmptyBottleDisposal(BaseModel):
rfid: str
weight: int | None
open_date: str | None
class JsonExtract(Function):
database_func=CustomFunction('JSON_EXTRACT', ['field', 'value'])
class BlinkLocation(BaseModel):
ip: str
position: list[int]
duration: int
function: int
# 查询药剂信息,
@router.get('/get_drug_position_info',summary='获取试剂信息')
async def get_drug_info(request:Request,rfid:str):
if len(rfid)==0:
return respond_to(404, desc="请传入药剂rfid")
drug = await Drug.get_or_none(rfid=rfid)
if not drug:
return respond_to(404, desc="药剂信息不存在")
drawer_obj = await Drawer.get_or_none(id=drug.drawer_id)
if not drawer_obj:
return respond_to(404, desc="药剂缺少drawer_id")
drawer_number = drawer_obj.line_no
drawer_board_obj = await DrawerBoard.filter(drawer_id =drawer_obj.id ).all()
if not drawer_board_obj:
return respond_to(404, desc="药剂绑定的层板drawer_board不存在")
board_addresses = []
for item in drawer_board_obj:
board_addresses.append(item.line_no)
res={}
res['drawer_number'] = drawer_number
res['board_addresses'] = board_addresses
return respond_to(200,desc="获取药剂位置信息成功",data=res)
@router.post('/empty', summary='试剂置为空瓶')
async def empty_bottle_disposal(request: Request, post: EmptyBottleDisposal):
"""
试剂置为空瓶
:param request: Request请求头信息
:param post: 空瓶信息的请求体
:return:
"""
users = request.state.users
standard = Standard(users)
rfid = post.rfid
drug = await Drug.get_or_none(rfid=rfid)
if not drug:
return respond_to(404, desc="药剂不存在")
# user_weight = post.weight
open_date = post.open_date
if open_date:
drug.open_date = parse_datetime(open_date, "%Y-%m-%d")
await drug.save()
await standard.empty(rfid)
return respond_to()
@router.post('/weight', summary="药剂用量或余量更新")
async def update(request: Request, keyword: DrugUpdateRequest):
"""
药剂用量或余量更新
接收id或rfid都可以更新重量
余量与用量二选一,只有一个有值
:param keyword: 包含更新信息的请求体
:return:
"""
print("open_date", keyword.open_date)
rfid = keyword.rfid if keyword.rfid else ""
if keyword.id:
drug_obj = await Drug.get_or_none(id=keyword.id).prefetch_related("template")
else:
drug_obj = await Drug.get_or_none(Q(barcode=rfid) | Q(rfid=rfid)).prefetch_related("template")
if not drug_obj:
return respond_to(404, desc="药剂不存在")
if keyword.weight:
if not drug_obj.remain_gross_weight:
drug_obj.remain_gross_weight = keyword.weight
await drug_obj.save()
else:
# 余量转用量,更新用量
mill_weight = drug_obj.remain_gross_weight - keyword.weight
await drug_obj.update_last_use_weight(weight=float(mill_weight))
elif keyword.use_weight:
mill_weight = keyword.use_weight if keyword.use_weight and keyword.use_weight != "NaN" else 0
await drug_obj.update_last_use_weight(weight=float(mill_weight))
if keyword.open_date:
if not drug_obj.open_date:
drug_obj.open_date = parse_datetime(keyword.open_date, "%Y-%m-%d")
template_obj = drug_obj.template
new_expired_at = open_update_expired_at(drug_obj, template_obj, keyword.open_date)
if new_expired_at:
drug_obj.expired_at = new_expired_at
await drug_obj.save()
# 一维条码扫码归还更新重量后直接修改药剂状态,生成流转记录
rfid = drug_obj.rfid
if len(rfid) < 16 and drug_obj.state == DrugStateEnum.OUT:
cabinet_id = keyword.cabinet_id if keyword.cabinet_id else request.state.client_id
if not cabinet_id:
cabinet_obj = await Cabinet.filter(terminal_id=setting.TERMINAL_ID).first()
else:
cabinet_obj = await Cabinet.get_or_none(id=cabinet_id)
if cabinet_obj:
# 位置更新
position_str = f"{cabinet_obj.label}"
drug_obj.position = position_str
drug_obj.last_return_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
open_date = drug_obj.open_date
if not open_date:
drug_obj.open_date = datetime.datetime.now()
drug_obj.last_return_id = request.state.current_user.id
drug_obj.cabinet_id = cabinet_obj.id
if keyword.selectDrawer_id:
drawer_board = await DrawerBoard.filter(id = keyword.selectDrawer_id).get_or_none()
if drawer_board:
drug_obj.drawer_id = drawer_board.drawer_id
drug_obj.drawer_board_id = keyword.selectDrawer_id
await drug_obj.save()
# 一维条码生成流转记录
standard = CodeStandard(request.state.users, drug_obj)
await standard.put_in(barcode=drug_obj.barcode if len(drug_obj.rfid) < 16 else drug_obj.rfid)
return respond_to(code=200, desc="更新成功")
return respond_to(code=200, desc="更新成功")
@router.post('/except', summary='RFID药剂异常信息')
async def index(request: DrugRfids):
"""
获取RFID药剂异常信息
:return:
"""
if not request.rfids:
return respond_to(code=200, desc="", data=[])
result = list()
drugs = await Drug.filter(rfid__in=request.rfids).prefetch_related('dictionary', 'template').all()
# terminal_obj = await Terminal.get(id=setting.TERMINAL_ID)
taboo_list = []
# 130个rfid如何快速获取里面的异常标签信息
# 并发执行多个任务
tasks = [drugs_except_info(drug, "") for drug in drugs]
results = await asyncio.gather(*tasks)
for drug, data in zip(drugs, results):
data["rfid"] = drug.rfid
result.append(data)
# 药剂禁忌判断
if not drug.taboo_species:
continue
else:
taboo_list_str = ",".join(set(taboo_list)) if taboo_list else ""
trigger, taboo_desc = await Taboo.conflict(drug.taboo_species, taboo_list_str)
if trigger:
result.append({
"rfid": "",
"is_rfid_except": True,
"sound": True,
"message": taboo_desc,
"state": 6,
})
taboo_list.append(drug.taboo_species)
# for drug in drugs:
# data = await drugs_except_info(drug, "")
# data["rfid"] = drug.rfid
# result.append(data)
# # 药剂禁忌判断
# if not drug.taboo_species:
# continue
# else:
# taboo_list_str = ",".join(set(taboo_list)) if taboo_list else ""
# trigger, taboo_desc = await Taboo.conflict(drug.taboo_species, taboo_list_str)
# if trigger:
# result.append({
# "rfid": "",
# "is_rfid_except": True,
# "sound": True,
# "message": taboo_desc,
# "state": 6,
# })
# taboo_list.append(drug.taboo_species)
# 传入rfid与data所有rfid比较差集就是非法标签
post_rfid = set(request.rfids)
effective_rfid = set([i["rfid"] for i in result])
diff_rfid = post_rfid - effective_rfid
if diff_rfid:
result.extend([{
"rfid": rfid,
"is_rfid_except": True,
"sound": True,
"message": "非法标签",
"state": 0,
} for rfid in diff_rfid])
return respond_to(code=200, desc="获取RFID药剂信息成功", data=result)
@router.post('', summary='RFID药剂详情')
async def index(request: DrugRfids):
"""
获取RFID药剂信息 药剂信息详情
:return:
"""
drugs = list()
for rfid in request.rfids:
drug_obj = await Drug.get_or_none(rfid=rfid).prefetch_related('dictionary', 'template', 'drawer_board', 'template__archive')
if drug_obj is None:
drugs.append({
"drugs_except_info": {
"sound": False,
"message": "非法标签",
"state": 0,
},
"rfid_drug": {rfid: f"[{rfid}]未知药剂"}
})
continue
result = jsonable_encoder(drug_obj)
template_obj = drug_obj.template
terminal_obj = await Terminal.get(id=setting.TERMINAL_ID)
data = await drugs_except_info(drug_obj, terminal_obj)
drug_info = await drug_obj.attribute_drug_info()
parse_fill_json_content = await drug_obj.parse_fill_json_content()
parse_fill_json_content.update({f'余重': f'{milligram_to_gram(drug_obj.remain_gross_weight)}'})
return_require_weigh = template_obj.archive.params.get("return_require_weigh", '')
drug_info_dict = {
"drugs_except_info": data,
"fill_json_content": parse_fill_json_content,
"rfid_drug": {drug_obj.rfid: ",".join(list(map(lambda x:str(x), drug_info.values())))},
"return_require_weigh": return_require_weigh
}
# 2000型号柜子需要灯光指引返回值添加灯光序号参数
if setting.CLIENT_NUMBER == "2000":
if drug_obj.drawer_board:
drawer_board_line_no = drug_obj.drawer_board.line_no
# 亮灯位置 = 层数*36+编号
blink_no = (drawer_board_line_no - 1) * 36 + drug_obj.hole
drug_info_dict.update({"blink_no": blink_no})
result.update(drug_info_dict)
drugs.append(result)
return respond_to(code=200, desc="获取RFID药剂信息成功", data=drugs)
@router.get('/take_out', summary='药剂领用查询')
async def take_out_index(request: Request, drug_name: str = '', take_out_type: int = 0, page_no: int = 1, page_size: int = 20):
"""
药剂领用列表查询
本终端的排前面
默认按照入库时间倒叙,条件查询按照有效期倒叙
take_out_type 本终端0 全局终端1
:return:
"""
terminal = await Terminal.get(id=setting.TERMINAL_ID)
current_user = request.state.current_user
order_key = "-created_at"
# 普通用户只能领用自己的药剂,管理员可以领取所有药剂
role = await current_user.role
if role.grade >= 50:
query = Drug.filter(state=DrugStateEnum.IN)
else:
query = Drug.filter(state=DrugStateEnum.IN, rfid__in=current_user.keys)
# 本终端的时候查看当前登录柜子的药剂, 全局终端查看大类所有柜子药剂
if not take_out_type:
query = query.filter(cabinet__terminal=terminal)
# 如果带辅柜则只查询选择的柜体药剂
if request.state and request.state.client_id:
query = query.filter(cabinet_id=request.state.client_id)
else:
# 全局终端 大类对应所有柜子
cabinet_ids = await Cabinet.filter(archive_id=request.state.archive_id).values_list('id', flat=True)
query = query.filter(cabinet_id__in=cabinet_ids)
if drug_name:
annotate = {}
annotate['json_batch_no'] = JsonExtract('fill_json_content', '$.cas_number')
query = query.annotate(**annotate).filter(
Q(json_batch_no__contains=drug_name) | Q(fill_json_content__filter={"seq_no": drug_name}) | Q(
dictionary__k1__contains=drug_name) | Q(dictionary__k2__contains=drug_name))
order_key = "-expired_at"
total_count = await query.count()
offset = (page_no - 1) * page_size
drugs = await query.offset(offset).order_by(order_key).limit(page_size).all()
result = await drugs_list_info(drugs)
return respond_to(200, data=dict(count=total_count, drugs=result))
@router.get('/put_in', summary='获取当前用户待归还清单')
async def put_in_index(request: Request):
"""
获取当前用户待归还清单
:return:
"""
current_user = request.state.current_user
cabinet_ids = await Cabinet.filter(terminal_id=setting.TERMINAL_ID).values_list('id', flat=True)
# 当前柜体待归还清单
query = Drug.filter(last_receive_id=current_user.id, state=2).filter(cabinet_id__in=cabinet_ids)
drugs = await query.all()
result = await drugs_list_info(drugs)
return respond_to(data=result)
@router.get('/keys_list', summary='获取钥匙清单')
async def get_keys_list(request: Request):
"""
获取钥匙清单
:return:
"""
current_user = request.state.current_user
role = await current_user.role
if role.grade >= 50:
query = Drug.filter(state__in=[DrugStateEnum.IN, DrugStateEnum.OUT])
drugs = await query.all()
result = await drugs_list_info(drugs)
return respond_to(data=result)
else:
return respond_to(code=403, desc="没有权限")
@router.put('/expired_at', summary='药剂信息修改')
@logger_wrapper
async def update(keyword: DrugUpdateExpired):
"""
药剂信息过期时间修改
:param id: 用户药剂模板条目id
:param keyword:
:return:
"""
rfid = keyword.rfid if keyword.rfid else ""
query_param = {"id": keyword.id} if keyword.id else {"rfid": rfid}
drug_obj = await Drug.get(**query_param)
drug_obj.expired_at = keyword.expired_at
await drug_obj.save()
return respond_to(code=200, desc="更新成功")
@router.get('/dictionaries', summary='获取药剂字典列表')
async def index(request: Request, keyword: str = '', page_no: int = 1, page_size: int = 10):
"""
获取本终端药剂字典列表
:param keyword: 按药剂信息搜索
:param page_no: 分页页码默认为1
:param page_size: 分页大小默认为10
:return:
"""
query = QuerySet(Dictionary).filter()
archive_id = request.state.archive_id
if archive_id:
query = query.filter(archive_id=archive_id)
if keyword:
keyword = keyword.strip()
query = query.filter(Q(k1__icontains=keyword) | Q(k2__icontains=keyword) | Q(k3__icontains=keyword)
| Q(k4__icontains=keyword) | Q(k5__icontains=keyword)
| Q(k6__icontains=keyword))
offset = (page_no - 1) * page_size
count = await query.count()
dictionary_objs = await query.limit(page_size).offset(offset).order_by('-created_at').values()
result_list = []
for obj in dictionary_objs:
drug_info = ",".join([value for key, value in obj.items() if key.startswith('k') and value is not None])
item = dict(obj)
if obj['params']:
for k, v in obj['params'].items():
item[k] = v
item.pop("params")
item['drug_info'] = drug_info
result_list.append(item)
return respond_to(data={'data': result_list, 'count': count})
@router.put('/dictionary/{id}', summary='编辑药剂字典')
async def update(id: str, post: UpdateDictionary):
"""
编辑药剂字典
:param id: id
:param post:
:return:
"""
dictionary = await Dictionary.get(id=id)
dictionary.params = post.dict()
await dictionary.save()
return respond_to()
@router.post('/backtrack', summary='药剂是否原路返回')
async def index(request: Request, keyword: DrugBackTrack):
"""
判断药剂是否原路返回
drug_list[{
rfid: str
cabinet_id: str
drawer_id: str
hole: int = 0
}]
:return:
"""
if setting.BACKTRACK == 0:
return respond_to(200, data=[], desc="药剂不需要原路返回")
drugs = list()
for drug in keyword.drug_list:
drug_obj = await Drug.get_or_none(rfid=drug.get("rfid")).prefetch_related('template__archive')
if drug_obj is None:
continue
if drug_obj.cabinet_id != drug.get("cabinet_id") or \
drug_obj.drawer_id != drug.get("drawer_id") or \
drug.get("hole") and drug_obj.hole != drug.get("hole"):
drug_info = await drug_obj.attribute_drug_info()
drug_info_comment = ",".join(list(map(lambda x: str(x), drug_info.values())))
drugs.append({
"drugs_except_info": {
"sound": False,
"message": f"[{drug_info_comment}]药剂放入位置不正确",
"state": 5,
},
})
return respond_to(200, data=drugs)
@router.post('/blink', summary='灯光闪烁接口')
async def blink(request: BlinkLocation):
import httpx
post_data={}
post_data["position"] = request.position
post_data["duration"] = request.duration
post_data["function"] = request.function
url = "http://"+request.ip+"/api/cabinet/v1/led"
async with httpx.AsyncClient() as client:
try:
response = await client.post(url, json=post_data)
except Exception:
return respond_to(code=400, desc='下位机通讯失败')
return respond_to()
@return_router.post("/info", summary='RFID药剂详情(免登录)')
async def drug_info(request: DrugRfids):
"""
获取RFID药剂信息 药剂信息详情
:return:
"""
drugs = list()
for rfid in request.rfids:
drug_obj = await Drug.get_or_none(rfid=rfid).prefetch_related('dictionary', 'template', 'drawer_board', 'template__archive')
if drug_obj is None:
drugs.append({
"drugs_except_info": {
"sound": False,
"message": "非法标签",
"state": 0,
},
"rfid_drug": {rfid: f"[{rfid}]未知药剂"}
})
continue
result = jsonable_encoder(drug_obj)
template_obj = drug_obj.template
terminal_obj = await Terminal.get(id=setting.TERMINAL_ID)
data = await drugs_except_info(drug_obj, terminal_obj)
drug_info = await drug_obj.attribute_drug_info()
parse_fill_json_content = await drug_obj.parse_fill_json_content()
parse_fill_json_content.update({f'余重': f'{milligram_to_gram(drug_obj.remain_gross_weight)}'})
return_require_weigh = template_obj.archive.params.get("return_require_weigh", '')
drug_info_dict = {
"drugs_except_info": data,
"fill_json_content": parse_fill_json_content,
"rfid_drug": {drug_obj.rfid: ",".join(list(map(lambda x:str(x), drug_info.values())))},
"return_require_weigh": return_require_weigh
}
# 2000型号柜子需要灯光指引返回值添加灯光序号参数
if setting.CLIENT_NUMBER == "2000":
if drug_obj.drawer_board:
drawer_board_line_no = drug_obj.drawer_board.line_no
# 亮灯位置 = 层数*36+编号
blink_no = (drawer_board_line_no - 1) * 36 + drug_obj.hole
drug_info_dict.update({"blink_no": blink_no})
result.update(drug_info_dict)
drugs.append(result)
return respond_to(code=200, desc="获取RFID药剂信息成功", data=drugs)
@return_router.post('/weight', summary="药剂用量或余量更新(快速归还)")
async def return_drug(request: Request, keyword: DrugUpdateRequest):
"""
药剂用量或余量更新
接收id或rfid都可以更新重量
余量与用量二选一,只有一个有值
:param keyword: 包含更新信息的请求体
:return:
"""
print("open_date", keyword.open_date)
rfid = keyword.rfid if keyword.rfid else ""
if keyword.id:
drug_obj = await Drug.filter(id=keyword.id).prefetch_related("template","last_receive").first()
else:
drug_obj = await Drug.filter(Q(barcode=rfid) | Q(rfid=rfid)).prefetch_related("template","last_receive").first()
if not drug_obj:
return respond_to(404, desc="药剂不存在")
syr = drug_obj.fill_json_content.get('syr') # 入库模板药剂使用人
syr_user = await User.get_or_none(name=syr.replace(' ', ''))
# 用户默认为药剂使用人
current_user = syr_user if syr_user else drug_obj.last_receive
# 柜子的当前使用人更新,盘点时需要
cabinet_id = keyword.cabinet_id
if not cabinet_id:
cabinet_obj = await Cabinet.filter(terminal_id=setting.TERMINAL_ID).first()
else:
cabinet_obj = await Cabinet.get_or_none(id=cabinet_id)
cabinet_obj.user_id = current_user.id
await cabinet_obj.save()
if keyword.weight:
if not drug_obj.remain_gross_weight:
drug_obj.remain_gross_weight = keyword.weight
await drug_obj.save()
else:
# 余量转用量,更新用量
mill_weight = drug_obj.remain_gross_weight - keyword.weight
await drug_obj.update_last_use_weight(weight=float(mill_weight))
elif keyword.use_weight:
mill_weight = keyword.use_weight if keyword.use_weight and keyword.use_weight != "NaN" else 0
await drug_obj.update_last_use_weight(weight=float(mill_weight))
if keyword.open_date:
if not drug_obj.open_date:
drug_obj.open_date = parse_datetime(keyword.open_date, "%Y-%m-%d")
template_obj = drug_obj.template
new_expired_at = open_update_expired_at(drug_obj, template_obj, keyword.open_date)
if new_expired_at:
drug_obj.expired_at = new_expired_at
await drug_obj.save()
# 一维条码扫码归还更新重量后直接修改药剂状态,生成流转记录
rfid = drug_obj.rfid
if len(rfid) < 16 and drug_obj.state == DrugStateEnum.OUT:
if cabinet_obj:
# 位置更新
position_str = f"{cabinet_obj.label}"
drug_obj.position = position_str
drug_obj.last_return_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
open_date = drug_obj.open_date
if not open_date:
drug_obj.open_date = datetime.datetime.now()
# drug_obj.last_return_id = request.state.current_user.id
drug_obj.last_return_id = current_user.id
drug_obj.cabinet_id = cabinet_obj.id
if keyword.selectDrawer_id:
drawer_board = await DrawerBoard.filter(id = keyword.selectDrawer_id).get_or_none()
if drawer_board:
drug_obj.drawer_id = drawer_board.drawer_id
drug_obj.drawer_board_id = keyword.selectDrawer_id
await drug_obj.save()
# 一维条码生成流转记录
standard = CodeStandard([current_user.id,], drug_obj)
await standard.put_in(barcode=drug_obj.barcode if len(drug_obj.rfid) < 16 else drug_obj.rfid)
return respond_to(code=200, desc="更新成功")
return respond_to(code=200, desc="更新成功")
@return_router.post('/empty', summary='试剂置为空瓶')
async def return_empty(request: Request, post: EmptyBottleDisposal):
"""
试剂置为空瓶
:param request: Request请求头信息
:param post: 空瓶信息的请求体
:return:
"""
# users = request.state.users
# standard = Standard(users)
rfid = post.rfid
drug = await Drug.filter(rfid=rfid).prefetch_related("last_receive").first()
if not drug:
return respond_to(404, desc="药剂不存在")
# user_weight = post.weight
open_date = post.open_date
if open_date:
drug.open_date = parse_datetime(open_date, "%Y-%m-%d")
await drug.save()
syr = drug.fill_json_content.get('syr') # 入库模板药剂使用人
syr_user = await User.get_or_none(name=syr.replace(' ', ''))
# 用户默认为药剂使用人
current_user = syr_user if syr_user else drug.last_receive
standard = Standard([current_user.id,])
await standard.empty(rfid)
return respond_to()