#!/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}