463 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			463 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
| 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 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_purin(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 == MIO.MIO_TYPE_DO_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 == 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)
 | |
|         elif instance.type == MIO.MIO_TYPE_DO_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)
 | |
|         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字段做加减
 | |
|         """
 | |
|         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:
 | |
|                 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)
 | |
|             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_purin(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("不支持该出入库单明细撤销") |