from apps.inm.models import (MIO, MIOItem, MaterialBatch, MaterialBatchA, MIOItemA, MIOItemw) from rest_framework.exceptions import ParseError from apps.mtm.models import Material from apps.mtm.services_2 import cal_material_count from apps.wpm.models import WMaterial, BatchSt, BatchLog from apps.wpm.services_2 import ana_batch_thread from apps.wpmw.models import Wpr from apps.qm.models import Ftest, Defect from django.db.models import Count, Q def do_out(item: MIOItem): """ 生产领料到车间 """ if item.mb and item.mb.defect is not None: raise ParseError("生产领料不支持不合格品") from apps.inm.models import MaterialBatch mio:MIO = item.mio belong_dept = mio.belong_dept mgroup = mio.mgroup do_user = mio.do_user material:Material = item.material if material.into_wm is False: # 用于混料的原料不与车间库存交互, 这个是配置项目 return # 获取defect defect:Defect = None if item.wm and item.mb: raise ParseError("车间和仓库库存不能同时存在") if item.wm: defect = item.wm.defect elif item.mb: defect = item.mb.defect action_list = [] mias = MIOItemA.objects.filter(mioitem=item) is_zhj = False # 是否组合件领料 if mias.exists(): is_zhj = True mias_list = list(mias.values_list('material', 'batch', 'rate')) for i in range(len(mias_list)): material, batch, rate = mias_list[i] new_count = rate * item.count # 假设 item.count 存在 action_list.append([material, batch, new_count, None]) else: action_list = [[item.material, item.batch, item.count, defect]] if is_zhj: try: mb = MaterialBatch.objects.get( material=item.material, warehouse=item.warehouse, batch=item.batch, state=WMaterial.WM_OK, defect=None ) except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e: raise ParseError(f"组合件批次错误!{e}") mb.count = mb.count - item.count if mb.count < 0: raise ParseError("组合件批次库存不足,操作失败") else: mb.save() if material.tracking == Material.MA_TRACKING_SINGLE: raise ParseError("组合件暂不支持追踪单件") xbatches = [] if is_zhj: xbatches = [item.batch] for al in action_list: xmaterial:Material = al[0] xbatch:str = al[1] xcount:str = al[2] defect:Defect = al[3] xbatches.append(xbatch) if xcount <= 0: raise ParseError("存在非正数!") mb = None if not is_zhj: try: mb = MaterialBatch.objects.get( material=xmaterial, warehouse=item.warehouse, batch=xbatch, state=WMaterial.WM_OK, defect=defect ) except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e: raise ParseError(f"批次错误!{e}") mb.count = mb.count - xcount if mb.count < 0: raise ParseError(f"{mb.batch}-批次库存不足,操作失败") else: mb.save() # 领到车间库存(或工段) wm, new_create = WMaterial.objects.get_or_create( batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup=mgroup, state=WMaterial.WM_OK, defect=defect) if new_create: wm.create_by = do_user wm.batch_ofrom = mb.batch if mb else None wm.material_ofrom = mb.material if mb else None wm.count = wm.count + item.count wm.update_by = do_user wm.save() # 开始变动wpr if xmaterial.tracking == Material.MA_TRACKING_SINGLE: mioitemws = MIOItemw.objects.filter(mioitem=item) if mioitemws.count() != item.count: raise ParseError("出入库与明细数量不一致,操作失败") for mioitemw in mioitemws: Wpr.change_or_new(wpr=mioitemw.wpr, wm=wm, old_mb=mb) # 触发批次统计分析 ana_batch_thread(xbatches) def do_in(item: MIOItem): """ 生产入库后更新车间物料 """ mio = item.mio wmin:WMaterial = item.wm if wmin and wmin.state != WMaterial.WM_OK: raise ParseError("非合格物料无法入库") belong_dept = mio.belong_dept mgroup = mio.mgroup do_user = mio.do_user material = item.material if material.into_wm is False: # 根据配置不进行入车间库存的处理 return action_list = [] mias = MIOItemA.objects.filter(mioitem=item) is_zhj = False # 是否组合件入仓库 # 获取defect defect:Defect = None if item.wm and item.mb: raise ParseError("车间和仓库库存不能同时存在") if item.wm: defect = item.wm.defect elif item.mb: defect = item.mb.defect if mias.exists(): is_zhj = True mias_list = mias.values_list('material', 'batch', 'rate') for i in mias_list: material, batch, rate = i new_count = rate * item.count # 假设 item.count 存在 action_list.append([material, batch, new_count, None]) else: action_list = [[item.material, item.batch, item.count, defect]] production_dept = None xbatchs = [] if is_zhj: xbatchs = [item.batch] for al in action_list: xmaterial, xbatch, xcount, defect = al if xcount <= 0: raise ParseError("存在非正数!") xbatchs.append(xbatch) wm_qs = WMaterial.objects.filter( batch=xbatch, material=xmaterial, belong_dept=belong_dept, mgroup=mgroup, defect=defect, state=WMaterial.WM_OK) count_x = wm_qs.count() if count_x == 1: wm = wm_qs.first() elif count_x == 0: raise ParseError( f'{str(xmaterial)}-{xbatch}-批次库存不存在!') else: raise ParseError( f'{str(xmaterial)}-{xbatch}-存在多个相同批次!') # 扣减车间库存 new_count = wm.count - xcount if new_count >= 0: wm.count = new_count wm.update_by = do_user wm.save() else: raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足') wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept if production_dept is None: production_dept = wm_production_dept elif wm_production_dept and production_dept != wm_production_dept: raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不属于同一车间') # 增加mb if not is_zhj: mb, _ = MaterialBatch.objects.get_or_create( material=xmaterial, warehouse=item.warehouse, batch=xbatch, state=WMaterial.WM_OK, defect=defect, defaults={ "count": 0, "batch_ofrom": wm.batch_ofrom, "material_ofrom": wm.material_ofrom, "production_dept": production_dept } ) if mb.production_dept is None: mb.production_dept = production_dept mb.count = mb.count + xcount mb.save() # 开始变动wpr if xmaterial.tracking == Material.MA_TRACKING_SINGLE: mioitemws = MIOItemw.objects.filter(mioitem=item) if mioitemws.count() != item.count: raise ParseError("出入库与明细数量不一致,操作失败") for mioitemw in mioitemws: Wpr.change_or_new(wpr=mioitemw.wpr, mb=mb, old_wm=wm) if is_zhj: # 组合件单独处理并且不做追踪单个处理 mb, is_created = MaterialBatch.objects.get_or_create( material=item.material, warehouse=item.warehouse, batch=item.batch, defect=None, state=WMaterial.WM_OK, defaults={"count": 0, "production_dept": production_dept} ) if not is_created: raise ParseError("该批次组合件已存在") if mb.production_dept is None: mb.production_dept = production_dept mb.count = mb.count + item.count mb.save() for mia in mias: MaterialBatchA.objects.create(mb=mb, material=mia.material, batch=mia.batch, rate=mia.rate) # 批次统计分析 ana_batch_thread(xbatchs) class InmService: @classmethod def update_material_count(cls, instance: MIO): """ 更新物料数量 """ # 统计物料数量 m_ids = list(MIOItem.objects.filter(mio=instance).values_list('material_id', flat=True)) m_ids2 = list(MIOItemA.objects.filter(mioitem__mio=instance).values_list('material_id', flat=True)) cal_material_count(m_ids+m_ids2) @classmethod def update_inm(cls, instance: MIO, is_reverse: bool = False): """ 更新库存, 支持反向操作 """ if not MIOItem.objects.filter(mio=instance).exists(): raise ParseError("出入库记录缺失明细,无法操作") if instance.type == MIO.MIO_TYPE_PUR_IN: # 需要更新订单 # 这里还需要对入厂检验进行处理 if is_reverse: BatchLog.clear(mio=instance) else: for item in MIOItem.objects.filter(mio=instance): BatchSt.g_create( batch=item.batch, mio=instance, material_start=item.material) from apps.pum.services import PumService if is_reverse: cls.update_mb(instance, -1) else: cls.update_mb(instance, 1) PumService.mio_pur(instance, is_reverse) elif instance.type == MIO.MIO_TYPE_PUR_OUT: from apps.pum.services import PumService if is_reverse: cls.update_mb(instance, 1) else: cls.update_mb(instance, -1) PumService.mio_pur(instance, is_reverse) elif instance.type == MIO.MIO_TYPE_OTHER_IN: if is_reverse: BatchLog.clear(mio=instance) else: for item in MIOItem.objects.filter(mio=instance): BatchSt.g_create( batch=item.batch, mio=instance, material_start=item.material) if is_reverse: cls.update_mb(instance, -1) else: cls.update_mb(instance, 1) elif instance.type in [MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_RETURN_IN]: mioitems = MIOItem.objects.filter(mio=instance) if is_reverse: for item in mioitems: do_out(item) else: for item in mioitems: do_in(item) elif instance.type in [MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_BORROW_OUT]: mioitems = MIOItem.objects.filter(mio=instance) if is_reverse: for item in mioitems: do_in(item) else: for item in mioitems: do_out(item) elif instance.type == MIO.MIO_TYPE_SALE_OUT: from apps.sam.services import SamService if is_reverse: cls.update_mb(instance, 1) else: cls.update_mb(instance, -1) SamService.mio_saleout(instance, is_reverse) elif instance.type == MIO.MIO_TYPE_OTHER_OUT: if is_reverse: cls.update_mb(instance, 1) else: cls.update_mb(instance, -1) else: raise ParseError('不支持该出入库操作') @classmethod def update_mb(cls, instance: MIO, in_or_out: int = 1): """ 更新物料批次 in = 1 out = -1 """ mioitems = MIOItem.objects.filter(mio=instance) if not mioitems.exists(): raise ParseError("未填写物料明细") for i in mioitems: cls.update_mb_item(i, in_or_out, 'count') @classmethod def update_mb_item(cls, i: MIOItem, in_or_out: int = 1, field:str='count'): """ 更新物料批次(根据明细) in = 1 out = -1 默认使用count字段做加减 """ mio_type = i.mio.type material: Material = i.material warehouse = i.warehouse tracking = material.tracking ddict = {"count": 0, "supplier": i.mio.supplier} if i.material.type in [Material.MA_TYPE_MAINSO]: ddict["batch_ofrom"] = i.batch ddict["material_ofrom"] = i.material if field == "count": count_change = i.count - i.count_notok elif field == "count_notok": count_change = getattr(i, field) else: raise ParseError("不支持该字段") m_list = [(material, warehouse, i.batch, count_change, None, i)] if tracking == Material.MA_TRACKING_SINGLE: # 获取所有主要的不合格项 mw_qs = MIOItemw.objects.filter(mioitem=i) defectIds= Ftest.objects.filter(mioitemw_ftest__in=mw_qs).exclude(defect_main=None).values_list("defect_main__id", flat=True).distinct() defects_map = {d.id: d for d in Defect.objects.filter(id__in=defectIds)} # 过滤并统计相关数据 filtered_mw_qs = mw_qs.filter( ftest__defect_main__id__in=defects_map.keys(), ).values('ftest__defect_main__id').annotate(xcount=Count('id')) for defect_data in filtered_mw_qs: defect_id = defect_data['ftest__defect_main__id'] xcount = defect_data['xcount'] if xcount > 0: defect = defects_map[defect_id] m_list.append((material, warehouse, i.batch, xcount, defect, i)) xbatchs = [] for material, warehouse, batch, change_count, defect, mioitem in m_list: xbatchs.append(batch) if change_count < 0: raise ParseError("存在负数!") state = WMaterial.WM_OK if defect and defect.okcate in [Defect.DEFECT_NOTOK]: state = WMaterial.WM_NOTOK mb, _ = MaterialBatch.objects.get_or_create( material=material, warehouse=warehouse, batch=batch, defect=defect, state=state, defaults=ddict ) if in_or_out == 1: mb.count = mb.count + change_count mb.save() if tracking == Material.MA_TRACKING_SINGLE: if defect: mioitemws = MIOItemw.objects.filter(mioitem=i, ftest__defect_main=defect) else: mioitemws = MIOItemw.objects.filter(Q(ftest=None) | Q(ftest__defect_main=None), mioitem=i) if mioitemws.count() != change_count: raise ParseError("出入库与明细数量不一致,操作失败") for mioitemw in mioitemws: if mioitemw.wpr: Wpr.change_or_new(wpr=mioitemw.wpr, mb=mb, ftest=mioitemw.ftest) else: wpr = Wpr.change_or_new(number=mioitemw.number, mb=mb, ftest=mioitemw.ftest) mioitemw.wpr = wpr mioitemw.save() elif in_or_out == -1: mb.count = mb.count - change_count if mb.count < 0: raise ParseError(f"{mb.batch}-批次库存不足,操作失败") else: mb.save() if tracking == Material.MA_TRACKING_SINGLE: if defect: mioitemws = MIOItemw.objects.filter(mioitem=i, ftest__defect_main=defect) else: mioitemws = MIOItemw.objects.filter(Q(ftest=None) | Q(ftest__defect_main=None), mioitem=i) if mioitemws.count() != change_count: raise ParseError("出入库与明细数量不一致,操作失败") for mioitemw in mioitemws: Wpr.change_or_new(wpr=mioitemw.wpr, old_mb=mb, number_out=mioitemw.number_out) else: raise ParseError("不支持的操作") # 批次统计分析 ana_batch_thread(xbatchs) @classmethod def revert_and_del(cls, mioitem: MIOItem): mio = mioitem.mio if mio.submit_time is None: raise ParseError("未提交的出入库明细不允许撤销") if mioitem.test_date is not None: raise ParseError("已检验的出入库明细不允许撤销") if mio.type == MIO.MIO_TYPE_PUR_IN: from apps.pum.services import PumService cls.update_mb_item(mioitem, -1) BatchLog.clear(mioitem=mioitem) PumService.mio_pur(mio=mio, is_reverse=True, mioitem=mioitem) mioitem.delete() elif mio.type == MIO.MIO_TYPE_OTHER_IN: cls.update_mb_item(mioitem, -1) BatchLog.clear(mioitem=mioitem) mioitem.delete() elif mio.type == MIO.MIO_TYPE_DO_OUT: do_in(mioitem) BatchLog.clear(mioitem=mioitem) mioitem.delete() else: raise ParseError("不支持该出入库单明细撤销")