|
|
#!/usr/bin/env python
|
|
|
# encoding: utf-8
|
|
|
"""
|
|
|
@author: tx
|
|
|
@file: drug.py
|
|
|
@time: 2023/5/9 9:14
|
|
|
@desc:
|
|
|
"""
|
|
|
from decimal import Decimal
|
|
|
from enum import IntEnum
|
|
|
import re
|
|
|
|
|
|
from tortoise.expressions import Q
|
|
|
from tortoise import fields, models
|
|
|
from tortoise.queryset import QuerySet
|
|
|
|
|
|
from models.user import User
|
|
|
from models.drug_item import DrugItem, DrugItemStateEnum
|
|
|
from models.dictionary import Dictionary
|
|
|
from conf.setting import setting
|
|
|
|
|
|
class DrugStateEnum(IntEnum):
|
|
|
# 0 = 待入库, 1 = 在库, 2 = 出库, 3 = 空瓶, 4 = 报废
|
|
|
INITIAL = 0
|
|
|
IN = 1
|
|
|
OUT = 2
|
|
|
EMPTY = 3
|
|
|
SCRAP = 4
|
|
|
|
|
|
|
|
|
class Drug(models.Model):
|
|
|
id = fields.UUIDField(pk=True)
|
|
|
dictionary = fields.ForeignKeyField('rms.Dictionary', null=True, to_field='id')
|
|
|
rfid = fields.CharField(max_length=255, unique=True, description='电子标签')
|
|
|
# barcode = fields.CharField(null=True, max_length=255, description='标签条码')
|
|
|
state = fields.IntEnumField(DrugStateEnum, default=DrugStateEnum.INITIAL,
|
|
|
description='0=待入库,1=在库,2=出库,3=空瓶,4=报废')
|
|
|
# hole = fields.SmallIntField(null=True, description='芯片序号')
|
|
|
position = fields.CharField(null=True, max_length=255, description='位置描述')
|
|
|
# remain_gross_weight = fields.IntField(null=True, description='试剂余重')
|
|
|
# last_use_weight = fields.IntField(null=True, description='最后使用重量')
|
|
|
# total_use_weight = fields.IntField(null=True, default=0, description='总用量 耗材为总使用数量')
|
|
|
fill_json_content = fields.JSONField(null=True, description='扩展字段')
|
|
|
# last_return_odd = fields.BooleanField(default=False, description='归还是否异常')
|
|
|
bind = fields.ForeignKeyField('rms.User', related_name="bind", on_delete=fields.SET_NULL, null=True, description='绑定人')
|
|
|
bind_at = fields.DatetimeField(null=True, description='绑定时间')
|
|
|
storage = fields.ForeignKeyField('rms.User', related_name="storage", on_delete=fields.SET_NULL, null=True, description='入库人')
|
|
|
storage_at = fields.DatetimeField(null=True, description='入库时间')
|
|
|
last_receive = fields.ForeignKeyField('rms.User',related_name="last_receive", on_delete=fields.SET_NULL, null=True, description='最后领用人')
|
|
|
last_receive_at = fields.DatetimeField(null=True, description='最后领用时间')
|
|
|
last_return = fields.ForeignKeyField('rms.User',related_name="last_return", on_delete=fields.SET_NULL, null=True, description='最后归还人')
|
|
|
last_return_at = fields.DatetimeField(null=True, description='最后归还时间')
|
|
|
# expired_at = fields.DatetimeField(null=True, description='过期时间')
|
|
|
# open_date = fields.DatetimeField(null=True, description='开封日期')
|
|
|
# board = fields.ForeignKeyField('rms.Board', null=True, on_delete=fields.SET_NULL, description='定位板规格ID')
|
|
|
# drawer = fields.ForeignKeyField('rms.Drawer', null=True, on_delete=fields.SET_NULL, description='抽屉ID')
|
|
|
# drawer_board = fields.ForeignKeyField('rms.DrawerBoard', null=True, on_delete=fields.SET_NULL, description='所属柜体')
|
|
|
cabinet = fields.ForeignKeyField('rms.Cabinet', null=True, on_delete=fields.SET_NULL, description='所属柜体')
|
|
|
template = fields.ForeignKeyField('rms.Template', null=True, description='所属模版')
|
|
|
# taboo_species = fields.CharField(null=True, max_length=50, description="药剂禁忌种类")
|
|
|
# drug_type = fields.CharField(null=True, max_length=50, default='normal',description="药剂类型 普通药剂normal 盒装药剂box")
|
|
|
|
|
|
# files = fields.JSONField(null=True, default="[]")
|
|
|
# near = fields.BooleanField(default=False, description='是否临期')
|
|
|
created_at = fields.DatetimeField(auto_now_add=True)
|
|
|
updated_at = fields.DatetimeField(auto_now=True)
|
|
|
|
|
|
class Meta:
|
|
|
table = 'drugs'
|
|
|
table_description = '药剂表'
|
|
|
|
|
|
@classmethod
|
|
|
async def conflict(cls, rfid) -> bool:
|
|
|
'''药剂标签是否已存在'''
|
|
|
drug = await Drug.get_or_none(rfid=rfid).values_list('id')
|
|
|
return True if drug else False
|
|
|
|
|
|
async def update_last_weight(self, weight: Decimal):
|
|
|
"""
|
|
|
称重更新重量
|
|
|
:param weight: 重量值
|
|
|
:return:
|
|
|
"""
|
|
|
self.last_weight = weight
|
|
|
await self.save()
|
|
|
|
|
|
async def update_last_use_weight(self, weight: Decimal):
|
|
|
"""
|
|
|
更新用量
|
|
|
:param weight: 用量值
|
|
|
:return:
|
|
|
"""
|
|
|
self.last_use_weight = weight
|
|
|
await self.save()
|
|
|
|
|
|
def attribute(self, key, default=None):
|
|
|
return self.fill_json_content.get(key, default)
|
|
|
|
|
|
async def parse_fill_json_content(self, pop_count: bool = True):
|
|
|
"""
|
|
|
解析药剂信息
|
|
|
:return: {"药剂名称": "xx", "药剂规格": "xx"}
|
|
|
"""
|
|
|
fill_json_content = self.fill_json_content
|
|
|
template_obj = await self.template
|
|
|
xlsx_transfer = template_obj.parse_xlsx_transfer().get("xlsx_transfer")
|
|
|
if pop_count and 'import_count' in fill_json_content:
|
|
|
fill_json_content.pop('import_count')
|
|
|
if fill_json_content.get("gross_weight") is not None:
|
|
|
fill_json_content.pop('gross_weight')
|
|
|
result = {
|
|
|
xlsx_transfer.get(key): fill_json_content.get(key)
|
|
|
for key in fill_json_content.keys() if xlsx_transfer.get(key)}
|
|
|
return result
|
|
|
|
|
|
async def attribute_cabinet_id(self):
|
|
|
# drawer_board_obj = await DrawerBoard.get(id = self.drawer_board_id).prefetch_related("cabinet")
|
|
|
if self.drawer_board_id:
|
|
|
drawer_board_obj = await self.drawer_board.prefetch_related("cabinet")
|
|
|
else:
|
|
|
return self.cabinet_id
|
|
|
return drawer_board_obj.cabinet.id
|
|
|
|
|
|
async def attribute_drug_info(self):
|
|
|
"""
|
|
|
full_json_content内部k1-k6数据
|
|
|
:return:
|
|
|
"""
|
|
|
template_obj = await self.template
|
|
|
xlsx = list(filter(lambda i: i.get("checked"), template_obj.xlsx))
|
|
|
xlsx_transfer = {f"{item['key']}": f"{item['text']}" for item in xlsx}
|
|
|
|
|
|
if self.drug_type == 'box':
|
|
|
box_dict = setting.BOX_TRANSFER_DICT
|
|
|
result = {box_dict.get(key): self.fill_json_content.get(key) for key in box_dict}
|
|
|
else:
|
|
|
result = {xlsx_transfer.get(f"k{i}"): self.fill_json_content.get(f"k{i}")
|
|
|
for i in range(1, 7) if self.fill_json_content.get(f"k{i}")}
|
|
|
|
|
|
return result
|
|
|
|
|
|
async def attribute_last_user(self):
|
|
|
"""
|
|
|
最后使用人
|
|
|
判断最后领用人,最后归还人
|
|
|
:return:
|
|
|
"""
|
|
|
if self.last_return_at and not self.last_receive_at:
|
|
|
last_user_id = self.last_return_id
|
|
|
elif self.last_receive_id and self.last_return_id:
|
|
|
last_user_id = self.last_receive_id if self.last_receive_at < self.last_return_at else self.last_return_id
|
|
|
else:
|
|
|
last_user_id = self.last_receive_id or self.last_return_id or self.storage_id
|
|
|
user_obj = await User.get_or_none(id=last_user_id)
|
|
|
if user_obj is None:
|
|
|
return ""
|
|
|
return user_obj.name
|
|
|
|
|
|
async def format_weight(self):
|
|
|
"""
|
|
|
余重
|
|
|
:return:
|
|
|
"""
|
|
|
remain_gross_weight = self.remain_gross_weight
|
|
|
|
|
|
if self.remain_gross_weight is None:
|
|
|
return '-'
|
|
|
|
|
|
net_weight = self.fill_json_content.get('net_weight') # 净含量
|
|
|
density = self.fill_json_content.get('density') # 密度
|
|
|
total_use_weight = self.total_use_weight # 总用量
|
|
|
|
|
|
if self.state == DrugStateEnum.EMPTY:
|
|
|
if net_weight is None or net_weight == '':
|
|
|
return '0g'
|
|
|
else:
|
|
|
unit = re.findall('.*?(mg|g|ml)$', net_weight)[0]
|
|
|
return "0{}".format(unit)
|
|
|
else:
|
|
|
if net_weight is None or net_weight == '':
|
|
|
return "%.1fg" % (remain_gross_weight / 1000) if remain_gross_weight > 1000 else "{}mg".format(
|
|
|
remain_gross_weight)
|
|
|
else:
|
|
|
if net_weight.endswith('mg'):
|
|
|
# 总用量大于净含量,则余量显示为0
|
|
|
if total_use_weight >= float(net_weight[:-2]):
|
|
|
return "{}mg".format(0)
|
|
|
return "{}mg".format(float(net_weight[:-2]) - total_use_weight)
|
|
|
|
|
|
if net_weight.endswith('g'):
|
|
|
return "%.1fg" % ((float(net_weight[:-1]) * 1000 - total_use_weight) / 1000)
|
|
|
|
|
|
# 如下的代码不是BUG
|
|
|
# 如果是液体(单位:ml),有密度,前端入参是重量,需要:容量=重量/密度,获取容量
|
|
|
# 如果是液体,没有密度,则返回余重
|
|
|
if net_weight.endswith('ml'):
|
|
|
if density:
|
|
|
density = density[:-4] if density.endswith("g/ml") else density
|
|
|
return "%.1fml" % (float(net_weight[:-2]) - total_use_weight / (float(density) * 1000))
|
|
|
else:
|
|
|
return "%.1fg" % (remain_gross_weight / 1000) if remain_gross_weight > 1000 else "{}mg".format(
|
|
|
remain_gross_weight)
|
|
|
|
|
|
async def parse_archive_params(self, key):
|
|
|
"""
|
|
|
解析获取大类参数
|
|
|
drug_obj -> template ->archive params
|
|
|
:param key:
|
|
|
:return:
|
|
|
"""
|
|
|
|
|
|
archive_obj = self.template.archive
|
|
|
archive_params = archive_obj.params.get(key)
|
|
|
return archive_params
|
|
|
|
|
|
async def box_item_generate(self, *args, **kwargs):
|
|
|
"""
|
|
|
盒装药剂明细条目生成
|
|
|
:param args:
|
|
|
:param kwargs: drug_items=[{xx}]
|
|
|
:return:
|
|
|
"""
|
|
|
drug_items = kwargs.get("drug_items")
|
|
|
if not drug_items:
|
|
|
return False
|
|
|
for drug_item in drug_items:
|
|
|
k_args = {f'k{i}': drug_item.get(f'k{i}') for i in range(1, 7) if drug_item.get(f'k{i}')}
|
|
|
k_args_with_null = {k: None for k, v in k_args.items() if v is None}
|
|
|
query = Q(**k_args) & Q(**k_args_with_null)
|
|
|
dictionary_obj = await Dictionary.filter(query).first()
|
|
|
if not dictionary_obj:
|
|
|
k_args["archive"] = self.template.archive
|
|
|
k_args["params"] = {"lack_stock_count": 10, "expiration_alert": 30}
|
|
|
dictionary_obj = await Dictionary.create(**k_args)
|
|
|
# 根据导入数量生成对应数量条目
|
|
|
if not drug_item.get("import_count"):
|
|
|
import_count = 1
|
|
|
else:
|
|
|
import_count = int(drug_item.get("import_count", 1))
|
|
|
|
|
|
drug_item["import_count"] = 1
|
|
|
data = {
|
|
|
"drug_id": self.id,
|
|
|
"name": drug_item.get("k1"),
|
|
|
"fill_json_content": drug_item,
|
|
|
"dictionary": dictionary_obj,
|
|
|
"rfid": self.rfid,
|
|
|
"barcode": self.barcode,
|
|
|
"state": DrugItemStateEnum.IN if len(self.rfid) < 10 else DrugItemStateEnum.INITIAL,
|
|
|
"bind_id": self.bind_id,
|
|
|
"bind_at": self.bind_at,
|
|
|
}
|
|
|
if drug_item.get("expire_date"):
|
|
|
data.update({
|
|
|
"expired_at": drug_item.get("expire_date")
|
|
|
})
|
|
|
|
|
|
for i in range(import_count):
|
|
|
await DrugItem.create(**data)
|
|
|
return True
|
|
|
|
|
|
async def attribute_box_item_info(self, *args, **kwargs):
|
|
|
"""
|
|
|
盒内药剂明细信息
|
|
|
:param args:
|
|
|
:param kwargs:
|
|
|
:return: {”box_count“: 10, "box_name_items": ""}
|
|
|
"""
|
|
|
if self.drug_type != "box":
|
|
|
return {"box_count": 0, "box_name_items": ""}
|
|
|
query = QuerySet(DrugItem).filter(drug_id=self.id)
|
|
|
total_count = await query.count()
|
|
|
drug_item_objs = await query.all()
|
|
|
name_list = []
|
|
|
# 相同名称药剂,只需要显示一次
|
|
|
for drug_item_obj in drug_item_objs:
|
|
|
name = drug_item_obj.fill_json_content.get("k1")
|
|
|
if name and name not in name_list:
|
|
|
name_list.append(name)
|
|
|
name_str = ",".join(name_list) if name_list else ""
|
|
|
return {"box_count": total_count, "box_name_items": name_str}
|