from django.db import models from apps.utils.models import CommonADModel, CommonBDModel, BaseModel from apps.mtm.models import Mgroup, Team, Shift, Material, Route, Process from apps.pm.models import Mtask, Mtaskb from apps.system.models import User from django.utils.timezone import localtime from apps.em.models import Equipment from apps.system.models import Dept from apps.pum.models import Supplier from django.db.models import Sum, Subquery from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import ParseError from django.db.models import Count from django.db import transaction from django.db.models import Max # Create your models here. class SfLog(CommonADModel): """TN: 值班记录 """ mgroup = models.ForeignKey( Mgroup, verbose_name='关联工段', on_delete=models.CASCADE) team = models.ForeignKey(Team, verbose_name='班组', on_delete=models.CASCADE, null=True, blank=True) shift = models.ForeignKey( Shift, verbose_name='当班班次', on_delete=models.CASCADE) leader = models.ForeignKey( 'system.user', verbose_name='班长', on_delete=models.CASCADE, null=True, blank=True) work_date = models.DateField('值班日期', null=True, blank=True) start_time = models.DateTimeField('值班开始') end_time = models.DateTimeField('值班结束') note = models.TextField('其他备注', null=True, blank=True) stlogs = models.ManyToManyField( 'wpm.stlog', verbose_name='关联异常记录', through='wpm.sflogexp', related_name='sflog_stlogs') last_test_time = models.DateTimeField('最后质检时间', null=True, blank=True) total_sec_now = models.PositiveIntegerField('总时长动', default=0) total_sec = models.PositiveIntegerField('总时长', default=43200) shut_sec = models.PositiveIntegerField('停机时长', default=0) pcoal_heat = models.FloatField('煤粉热值', null=True, blank=True) @property def get_ymd(self): """ 返回值班记录所属年月日 """ start_time_local = localtime(self.start_time) return start_time_local.year, start_time_local.month, start_time_local.day class StLog(CommonADModel): """ TN: 生产异常记录 """ title = models.CharField('异常名称', max_length=20, default='') is_shutdown = models.BooleanField('是否是停机', default=False) mgroup = models.ForeignKey( Mgroup, verbose_name='关联工段', on_delete=models.CASCADE) sflog = models.ForeignKey( SfLog, on_delete=models.SET_NULL, verbose_name='发生时所在值班', null=True, blank=True) sflogs = models.ManyToManyField( 'wpm.sflog', verbose_name='关联所有当班', through='wpm.sflogexp', related_name='stlog_sflogs') start_time = models.DateTimeField('发生时间') end_time = models.DateTimeField('结束时间', null=True, blank=True) duration_sec = models.PositiveIntegerField('持续时长(s)', null=True, blank=True) cate = models.CharField('原因类别', max_length=10, null=True, blank=True) reason = models.TextField('事件原因', null=True, blank=True) measure = models.TextField('处置措施', null=True, blank=True) handler = models.CharField('处理人', null=True, blank=True, max_length=100) class Meta: unique_together = ('mgroup', 'start_time') class SfLogExp(CommonADModel): """ TN: 生产异常处理 """ sflog = models.ForeignKey( SfLog, on_delete=models.CASCADE, verbose_name='关联值班记录') stlog = models.ForeignKey( StLog, verbose_name='关联异常记录', on_delete=models.CASCADE) duration_sec = models.PositiveIntegerField('持续时长(s)', null=True, blank=True) note = models.TextField('处理备注', default='', blank=True) class Meta: unique_together = ('sflog', 'stlog') class WmStateOption(models.IntegerChoices): OK = 10, _("合格") NOTOK = 20, _("不合格") REPAIR = 30, _("返修") REPAIRED = 34, _("返修完成") TEST = 40, _("检验") SCRAP = 50, _("报废") class WMaterial(CommonBDModel): """ TN: 车间库存 """ WM_OK = 10 WM_NOTOK = 20 WM_REPAIR = 30 WM_REPAIRED = 34 WM_TEST = 40 WM_SCRAP = 50 state = models.PositiveSmallIntegerField('状态', default=10, choices=WmStateOption.choices) material = models.ForeignKey( Material, verbose_name='物料', on_delete=models.CASCADE, related_name='wm_m') supplier = models.ForeignKey(Supplier, verbose_name='外协供应商', on_delete=models.SET_NULL, null=True, blank=True) mgroup = models.ForeignKey(Mgroup, verbose_name='所在工段', on_delete=models.CASCADE, null=True, blank=True) batch = models.TextField('批次号') count = models.DecimalField('当前数量', default=0, max_digits=11, decimal_places=1) count_eweight = models.FloatField('单数重量', default=0) defect = models.ForeignKey('qm.defect', verbose_name='缺陷', on_delete=models.SET_NULL, null=True, blank=True) notok_sign = models.CharField('不合格标记', max_length=10, null=True, blank=True) material_origin = models.ForeignKey(Material, verbose_name='原始物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='wm_mo') count_xtest = models.DecimalField('已检数量', null=True, blank=True, max_digits=11, decimal_places=1) batch_ofrom = models.TextField('原料批次号', null=True, blank=True) material_ofrom = models.ForeignKey(Material, verbose_name='原料物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='wm_mofrom') @property def count_working(self): return Mlogb.objects.filter(wm_in=self, mlog__submit_time__isnull=True).aggregate(count=Sum('count_use'))['count'] or 0 @classmethod def mat_in_qs(cls, mtask: Mtask, qs=None): """ 可用于该子任务的queryset """ if qs is None: qs = cls.objects return qs.filter( mgroup=mtask.mgroup, material=mtask.material_in, batch__in=Subquery( Mlogb.objects.filter( mtask__utask=mtask.utask, mlog__submit_time__isnull=False, material_out=mtask.material_in ).values_list('batch', flat=True) ), state__in=[WMaterial.WM_OK, WMaterial.WM_REPAIR] ) class Fmlog(CommonADModel): """TN: 父级生产日志 """ mtask = models.ForeignKey(Mtask, verbose_name='任务', on_delete=models.CASCADE, related_name='fmlog_mtask', null=True, blank=True) route = models.ForeignKey(Route, verbose_name="所用步骤", on_delete=models.SET_NULL, related_name="fmlog_route", null=True, blank=True) mgroup = models.ForeignKey(Mgroup, verbose_name='工段', on_delete=models.CASCADE, related_name='fmlog_mgroup') note = models.TextField('备注', default='', blank=True) is_fix = models.BooleanField('是否用于返修', default=False) # 返工/复检 enabled = models.BooleanField("是否启用", default=True) class Mlog(CommonADModel): """ TN: 生产/检验日志 """ # 变成父级的字段 MLOG_2 = 10 MLOG_23 = 20 MLOG_12 = 30 MTYPE_SELF = 10 MTYPE_OUT = 20 fmlog = models.ForeignKey(Fmlog, verbose_name='关联生产日志', on_delete=models.SET_NULL, null=True, blank=True, related_name='mlog_fmlog') mtaskb = models.ForeignKey(Mtaskb, verbose_name='关联个人任务', on_delete=models.CASCADE, related_name='mlog_mtaskb', null=True, blank=True) fill_way = models.PositiveSmallIntegerField("填写方式", default=10, help_text='10:仅二级;20:二三级;30:一二级') mtype = models.PositiveSmallIntegerField('生产类型', default=10, help_text='10:自生产;20:外协生产', choices=((10, '自生产'), (20, '外协生产'))) is_fix = models.BooleanField('是否用于返修', default=False) # 返工/复检 qct = models.ForeignKey("qm.qct", verbose_name='所用质检表', on_delete=models.SET_NULL, null=True, blank=True) supplier = models.ForeignKey(Supplier, verbose_name='外协供应商', on_delete=models.SET_NULL, null=True, blank=True) work_start_time = models.DateTimeField('生产开始时间', null=True, blank=True) work_end_time = models.DateTimeField('生产结束时间', null=True, blank=True) hour_work = models.FloatField('预计工时', null=True, blank=True) reminder_interval_list = models.JSONField('提醒间隔', default=list, blank=True) stored_mgroup = models.BooleanField('入库到工段', default=False) stored_notok = models.BooleanField('不合格品是否已入库', default=False) route = models.ForeignKey(Route, verbose_name='生产路线', on_delete=models.SET_NULL, null=True, blank=True) mtask = models.ForeignKey( Mtask, verbose_name='关联任务', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_mtask') mgroup = models.ForeignKey( Mgroup, verbose_name='工段', on_delete=models.CASCADE, null=True, blank=True) wm_in = models.ForeignKey(WMaterial, verbose_name='投入物料所在库存', on_delete=models.SET_NULL, null=True, blank=True, related_name='mlog_wm_in') material_in = models.ForeignKey( Material, verbose_name='消耗物', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_material_in') material_out = models.ForeignKey( Material, verbose_name='产物', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_material_out') equipment = models.ForeignKey( Equipment, verbose_name='生产设备', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_equipment') equipment_2 = models.ForeignKey( Equipment, verbose_name='生产设备2', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_equipment_2') equipments = models.ManyToManyField( Equipment, verbose_name='多选生产设备', blank=True) index = models.PositiveSmallIntegerField('第几锅', default=0) shift = models.ForeignKey( Shift, verbose_name='关联班次', on_delete=models.SET_NULL, null=True, blank=True) batch = models.TextField('批次号', null=True, blank=True) count_use = models.DecimalField('领用数', default=0, max_digits=11, decimal_places=1) count_real = models.DecimalField('实际生产数', default=0, max_digits=11, decimal_places=1) count_real_eweight = models.FloatField('单数重量', default=0) count_break = models.DecimalField('加工碎料数', default=0, max_digits=11, decimal_places=1) count_ok = models.DecimalField('合格数', default=0, max_digits=11, decimal_places=1) count_ok_full = models.DecimalField('完全合格数', null=True, blank=True, max_digits=11, decimal_places=1) count_notok = models.DecimalField('不合格数', default=0, max_digits=11, decimal_places=1) count_break_t = models.DecimalField('检验碎料数', default=0, max_digits=11, decimal_places=1) count_pn_jgqbl = models.DecimalField('加工前不良', default=0, max_digits=11, decimal_places=1) count_n_zw = models.DecimalField('炸纹', default=0, max_digits=11, decimal_places=1) count_n_tw = models.DecimalField('条纹', default=0, max_digits=11, decimal_places=1) count_n_qp = models.DecimalField('气泡', default=0, max_digits=11, decimal_places=1) count_n_wq = models.DecimalField('弯曲', default=0, max_digits=11, decimal_places=1) count_n_dl = models.DecimalField('断裂', default=0, max_digits=11, decimal_places=1) count_n_pb = models.DecimalField('偏壁', default=0, max_digits=11, decimal_places=1) count_n_dxt = models.DecimalField('大小头', default=0, max_digits=11, decimal_places=1) count_n_js = models.DecimalField('结石', default=0, max_digits=11, decimal_places=1) count_n_qx = models.DecimalField('气线', default=0, max_digits=11, decimal_places=1) count_n_hs = models.DecimalField('划伤', default=0, max_digits=11, decimal_places=1) count_n_cs = models.DecimalField('挫伤', default=0, max_digits=11, decimal_places=1) count_n_bl = models.DecimalField('不亮', default=0, max_digits=11, decimal_places=1) count_n_zz = models.DecimalField('杂质', default=0, max_digits=11, decimal_places=1) count_n_d = models.DecimalField('短', default=0, max_digits=11, decimal_places=1) count_n_zdd = models.DecimalField('锥度大', default=0, max_digits=11, decimal_places=1) count_n_hw = models.DecimalField('横纹', default=0, max_digits=11, decimal_places=1) count_n_yp = models.DecimalField('有皮', default=0, max_digits=11, decimal_places=1) count_n_bp = models.DecimalField('爆皮', default=0, max_digits=11, decimal_places=1) count_n_sc = models.DecimalField('色差', default=0, max_digits=11, decimal_places=1) count_n_tydd = models.DecimalField('椭圆度大', default=0, max_digits=11, decimal_places=1) count_n_sw = models.DecimalField('水雾', default=0, max_digits=11, decimal_places=1) count_n_zjx = models.DecimalField('直径小', default=0, max_digits=11, decimal_places=1) count_n_zjd = models.DecimalField('直径大', default=0, max_digits=11, decimal_places=1) count_n_bhpcd = models.DecimalField('壁厚偏差大', default=0, max_digits=11, decimal_places=1) count_n_xzp = models.DecimalField('箱中破', default=0, max_digits=11, decimal_places=1) count_n_thhs = models.DecimalField('退火后碎', default=0, max_digits=11, decimal_places=1) count_n_swen = models.DecimalField('水纹', default=0, max_digits=11, decimal_places=1) count_n_bb = models.DecimalField('崩边', default=0, max_digits=11, decimal_places=1) count_n_xbb = models.DecimalField('小崩边', default=0, max_digits=11, decimal_places=1) count_n_wm = models.DecimalField('雾面', default=0, max_digits=11, decimal_places=1) count_n_md = models.DecimalField('麻点', default=0, max_digits=11, decimal_places=1) count_n_xh = models.DecimalField('线痕', default=0, max_digits=11, decimal_places=1) count_n_ps = models.DecimalField('破损', default=0, max_digits=11, decimal_places=1) count_n_wj = models.DecimalField('外经', default=0, max_digits=11, decimal_places=1) count_n_yd = models.DecimalField('圆度', default=0, max_digits=11, decimal_places=1) count_n_txd = models.DecimalField('同心度', default=0, max_digits=11, decimal_places=1) count_n_hd = models.DecimalField('厚度', default=0, max_digits=11, decimal_places=1) count_n_zt = models.DecimalField('炸头', default=0, max_digits=11, decimal_places=1) count_n_b = models.DecimalField('扁', default=0, max_digits=11, decimal_places=1) # 光芯七车间 count_n_qt = models.DecimalField('其他', default=0, max_digits=11, decimal_places=1) handle_date = models.DateField('操作日期', null=True, blank=True) handle_user = models.ForeignKey( User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True) # 成型人 handle_user_2 = models.ForeignKey( User, verbose_name='操作人2', on_delete=models.CASCADE, related_name='mlog_handle_user_2', null=True, blank=True) # 切料人 handle_users = models.ManyToManyField( User, verbose_name='操作人(多选)', blank=True) handle_leader = models.ForeignKey( User, verbose_name='班长', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_handle_leader') note = models.TextField('备注', default='', blank=True) material_outs = models.ManyToManyField( Material, verbose_name='多个产出', blank=True, through='wpm.mlogb', related_name='mlog_material_outs', through_fields=('mlog', 'material_out')) submit_time = models.DateTimeField('提交时间', null=True, blank=True) submit_user = models.ForeignKey( User, verbose_name='提交人', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_submit_user') oinfo_json = models.JSONField('其他信息', default=dict, blank=True) ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, related_name='mlog_ticket', null=True, blank=True, db_constraint=False) test_file = models.TextField('检验文件', null=True, blank=True) test_user = models.ForeignKey( User, verbose_name='检验人', on_delete=models.CASCADE, null=True, blank=True, related_name='mlog_test_user') test_time = models.DateTimeField('检验时间', null=True, blank=True) @property def mlogb(self): return Mlogb.objects.filter(mlog=self).exclude(material_out=None) @property def mlogb_full(self): return Mlogb.objects.filter(mlog=self) @property def audit_ignore_fields(self): return ['create_by', 'update_by', 'create_time', 'update_time', 'id'] @property def mlogdefect(self): return MlogbDefect.objects.filter(mlogb__mlog=self, mlogb__material_out__isnull=False) @property def mlogindefect(self): return MlogbDefect.objects.filter(mlogb__mlog=self, mlogb__material_in__isnull=False) @classmethod def count_fields(cls, without_count_ok_full=True): mlog_count_fields = [] for f in Mlog._meta.fields: if f.name.startswith("count"): mlog_count_fields.append(f.name) mlog_count_fields.remove("count_real_eweight") if without_count_ok_full: mlog_count_fields.remove("count_ok_full") return mlog_count_fields class MlogUser(BaseModel): """TN: 子级生产/检验日志操作人""" mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE) handle_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='子工序', on_delete=models.CASCADE) shift = models.ForeignKey(Shift, verbose_name='关联班次', on_delete=models.CASCADE) handle_date = models.DateField('操作日期') class Mlogb(BaseModel): """ TN: 子级生产/检验日志 """ qct = models.ForeignKey("qm.qct", verbose_name='所用质检表', on_delete=models.SET_NULL, null=True, blank=True) mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE, related_name='b_mlog') note = models.TextField('备注', default='', blank=True) batch = models.TextField('批次号', null=True, blank=True) mtask = models.ForeignKey(Mtask, verbose_name='关联任务', on_delete=models.CASCADE, related_name='mlogb_mtask', null=True, blank=True) wm_in = models.ForeignKey(WMaterial, verbose_name='投入物料所在库存', on_delete=models.SET_NULL, null=True, blank=True) material_in = models.ForeignKey( Material, verbose_name='投入物料', on_delete=models.CASCADE, related_name='mlogb_material_in', null=True, blank=True) parent = models.ForeignKey("self", verbose_name='父级物料', on_delete=models.CASCADE, null=True, blank=True, related_name='mlogb_parent') mlogb_from = models.ForeignKey("self", verbose_name='来源批', on_delete=models.CASCADE, null=True, blank=True, related_name='mlogb_from_mlogb') mlogbw_from = models.ForeignKey("wpm.mlogbw", verbose_name='来源个', on_delete=models.CASCADE, null=True, blank=True, related_name='mlogb_from_mlogb') material_out = models.ForeignKey( Material, verbose_name='产物', on_delete=models.CASCADE, related_name='mlogb_material_out', null=True, blank=True) batch_ofrom = models.TextField('原料批次号', null=True, blank=True) material_ofrom = models.ForeignKey(Material, verbose_name='原料物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='mlogb_mofrom') count_use = models.DecimalField('领用数量', default=0, max_digits=11, decimal_places=1) count_break = models.DecimalField('加工破碎数', default=0, max_digits=11, decimal_places=1) count_break_t = models.DecimalField('检验碎料数', default=0, max_digits=11, decimal_places=1) count_real = models.DecimalField('实际生产数', default=0, max_digits=11, decimal_places=1) count_ok = models.DecimalField('合格数量', default=0, max_digits=11, decimal_places=1) count_ok_full = models.DecimalField('完全合格数', null=True, blank=True, max_digits=11, decimal_places=1) count_notok = models.DecimalField('不合格数', default=0, max_digits=11, decimal_places=1) count_pn_jgqbl = models.DecimalField('加工前不良', default=0, max_digits=11, decimal_places=1) # 添加不合格字段后需要更改cal_mlog_count_from_mlogb count_n_hs = models.DecimalField('划伤', default=0, max_digits=11, decimal_places=1) count_n_qp = models.DecimalField('气泡', default=0, max_digits=11, decimal_places=1) count_n_swen = models.DecimalField('水纹', default=0, max_digits=11, decimal_places=1) count_n_bb = models.DecimalField('崩边', default=0, max_digits=11, decimal_places=1) count_n_xbb = models.DecimalField('小崩边', default=0, max_digits=11, decimal_places=1) count_n_wm = models.DecimalField('雾面', default=0, max_digits=11, decimal_places=1) count_n_md = models.DecimalField('麻点', default=0, max_digits=11, decimal_places=1) count_n_xh = models.DecimalField('线痕', default=0, max_digits=11, decimal_places=1) count_n_ps = models.DecimalField('破损', default=0, max_digits=11, decimal_places=1) count_n_wj = models.DecimalField('外经', default=0, max_digits=11, decimal_places=1) count_n_yd = models.DecimalField('圆度', default=0, max_digits=11, decimal_places=1) count_n_txd = models.DecimalField('同心度', default=0, max_digits=11, decimal_places=1) count_n_hd = models.DecimalField('厚度', default=0, max_digits=11, decimal_places=1) count_n_zt = models.DecimalField('炸头', default=0, max_digits=11, decimal_places=1) count_n_qt = models.DecimalField('其他', default=0, max_digits=11, decimal_places=1) count_notok_json = models.JSONField('不合格情况', default=list, blank=True) count_json = models.JSONField('各物料数量', default=list, blank=True) count_json_from = models.JSONField("层布局数据", default=list, blank=True) count_json_wx = models.JSONField('外协自定义数据', default=list, blank=True) test_user = models.ForeignKey( User, verbose_name='抽检人', on_delete=models.CASCADE, null=True, blank=True, related_name='mlogb_test_user') need_inout = models.BooleanField('是否需要出入库', default=True) def get_tracking(self): if self.material_in: return "in", self.material_in.tracking elif self.material_out: return "out", self.material_out.tracking @property def mlogbdefect(self): return MlogbDefect.objects.filter(mlogb=self) def cal_count_pn_jgqbl(self): mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="in") count_pn_jgqbl = mqs.aggregate(total=Sum("count"))["total"] or 0 self.count_pn_jgqbl = count_pn_jgqbl self.save(update_fields=["count_pn_jgqbl"]) mlog = self.mlog if mlog: count_pn_jgqbl = MlogbDefect.get_defect_qs_from_mlog(mlog, ftype="in").aggregate(total=Sum("count"))["total"] or 0 mlog.count_pn_jgqbl = count_pn_jgqbl if mlog.count_real == mlog.count_use: mlog.count_real = mlog.count_use - count_pn_jgqbl mlog.save(update_fields=["count_pn_jgqbl", "count_real"]) def cal_count_notok(self): mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="out") count_notok = mqs.filter(defect__okcate=30).aggregate(total=Sum("count"))["total"] or 0 count_notok_full = mqs.exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0 self.count_notok = count_notok self.count_ok = self.count_real - count_notok self.count_ok_full = self.count_real - count_notok_full self.save(update_fields=["count_ok", "count_notok", "count_ok_full"]) mlog = self.mlog if mlog: mqs2 = MlogbDefect.get_defect_qs_from_mlog(mlog, "out") count_notok = mqs2.aggregate(total=Sum("count"))["total"] or 0 count_notok_full = mqs2.exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0 mlog.count_ok_full = self.count_real - count_notok_full mlog.count_notok = count_notok mlog.count_ok = self.count_real - count_notok mlog.save(update_fields=["count_ok", "count_notok", "count_ok_full"]) class MlogbDefect(BaseModel): """TN: 生成记录的缺陷记录""" mlogb = models.ForeignKey(Mlogb, verbose_name='生产记录', on_delete=models.CASCADE) defect = models.ForeignKey("qm.Defect", verbose_name='缺陷', on_delete=models.CASCADE, null=True, blank=True) count = models.DecimalField('数量', default=0, max_digits=11, decimal_places=1) @classmethod def get_defect_qs(cls, ftype="all"): qs = MlogbDefect.objects.filter(defect__isnull=False) if ftype == "in": qs = qs.filter(mlogb__material_in__isnull=False) elif ftype == "out": qs = qs.filter(mlogb__material_out__isnull=False) return qs @classmethod def get_defect_qs_from_mlogb(cls, mlogb:Mlogb, ftype="all"): return cls.get_defect_qs(ftype).filter(mlogb=mlogb) @classmethod def get_defect_qs_from_mlog(cls, mlog:Mlog, ftype="all"): return cls.get_defect_qs(ftype).filter(mlogb__mlog=mlog) class Mlogbw(BaseModel): """TN: 单个产品生产/检验日志 """ number = models.TextField('单个编号') mlogb = models.ForeignKey(Mlogb, verbose_name='生产记录', on_delete=models.CASCADE, related_name="w_mlogb") mlogbw_from = models.ForeignKey("self", verbose_name='来源个', on_delete=models.CASCADE, null=True, blank=True, related_name="w_mlogbw_from") wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.SET_NULL , related_name='wpr_mlogbw', null=True, blank=True) equip = models.ForeignKey(Equipment, verbose_name='设备', on_delete=models.SET_NULL, null=True, blank=True) work_start_time = models.DateTimeField('开始加工时间', null=True, blank=True) work_end_time = models.DateTimeField('结束加工时间', null=True, blank=True) ftest = models.OneToOneField("qm.ftest", verbose_name='关联检验', on_delete=models.PROTECT, null=True, blank=True, related_name="mlogbw_ftest") note = models.TextField('备注', null=True, blank=True) @classmethod def cal_count_notok(cls, mlogb: Mlog): from apps.qm.models import Defect count = Mlogbw.objects.filter(mlogb=mlogb).count() if mlogb.material_in: mlogb.count_use = count elif mlogb.material_out: mlogb.count_real = count count_notok = 0 tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False) tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__is_ok=False).values("ftest__defect_main").annotate(xcount=Count('id')) defects = {defect.id: defect for defect in Defect.objects.filter(id__in=tqs.values_list("ftest__defect_main", flat=True))} md_ids = [] for t in tqs_a: md, _ = MlogbDefect.objects.get_or_create(mlogb=mlogb, defect=defects[t["ftest__defect_main"]]) md.count = t["xcount"] md.save() md_ids.append(md.id) count_notok += t["xcount"] MlogbDefect.objects.filter(mlogb=mlogb).exclude(id__in=md_ids).delete() mlogb.count_notok = count_notok mlogb.count_ok = count - mlogb.count_notok mlogb.save() class Handover(CommonADModel): """ TN: 交接记录 """ H_NORMAL = 10 H_REPAIR = 20 H_TEST = 30 H_SCRAP = 40 H_CHANGE = 50 H_BACK = 60 H_MERGE = 30 H_DIV = 20 new_batch = models.TextField('新批次号', null=True, blank=True) new_wm = models.ForeignKey(WMaterial, on_delete=models.SET_NULL, null=True, blank=True) mtype = models.PositiveSmallIntegerField("合并类型", default=H_NORMAL, choices= [(H_NORMAL, '正常'), (H_DIV, '分批'), (H_MERGE, '合批')]) type = models.PositiveSmallIntegerField('交接类型', choices=[ (H_NORMAL, '正常交接'), (H_REPAIR, '返修交接'), (H_TEST, '检验交接'), (H_SCRAP, '报废交接'), (H_CHANGE, '改版交接'), (H_BACK, '退料交接')], default=H_NORMAL) send_date = models.DateField('送料日期') send_user = models.ForeignKey( User, verbose_name='交送人', on_delete=models.CASCADE, related_name='handover_send_user') send_mgroup = models.ForeignKey( Mgroup, verbose_name='送料工段', on_delete=models.CASCADE, null=True, blank=True) send_dept = models.ForeignKey( Dept, verbose_name='送料部门', on_delete=models.CASCADE, related_name='handover_send_dept') batch = models.TextField('批次号', null=True, blank=True) material = models.ForeignKey( Material, verbose_name='物料', on_delete=models.CASCADE, related_name='h_ma') material_changed = models.ForeignKey(Material, verbose_name='变更后物料', on_delete=models.CASCADE, null=True, blank=True, related_name='h_ma_c') count = models.DecimalField('送料数', default=0, max_digits=11, decimal_places=1) count_eweight = models.FloatField('单数重量', default=0) recive_dept = models.ForeignKey( Dept, verbose_name='接收部门', on_delete=models.CASCADE, related_name='handover_recive_dept') recive_mgroup = models.ForeignKey(Mgroup, verbose_name='接收工段', on_delete=models.CASCADE, related_name='handover_recive_mgroup', null=True, blank=True) recive_user = models.ForeignKey( User, verbose_name='接收人', on_delete=models.CASCADE, related_name='handover_recive_user', null=True, blank=True) wm = models.ForeignKey(WMaterial, verbose_name='关联车间库存', on_delete=models.SET_NULL, null=True, blank=True, related_name='handover_wm') mlog = models.ForeignKey(Mlog, verbose_name='关联日志记录', on_delete=models.SET_NULL, null=True, blank=True, related_name='handover_mlog') doin_date = models.DateField('加料日期', null=True, blank=True) doout_date = models.DateField('出料日期', null=True, blank=True) submit_time = models.DateTimeField('提交时间', null=True, blank=True) submit_user = models.ForeignKey( User, verbose_name='提交人', on_delete=models.CASCADE, null=True, blank=True, related_name='handover_submit_user') note = models.TextField('备注', null=True, blank=True) @property def handoverb(self): return Handoverb.objects.filter(handover=self) class Handoverb(BaseModel): """TN: 子级交接记录 """ handover = models.ForeignKey(Handover, verbose_name='关联交接记录', on_delete=models.CASCADE, related_name="b_handover") batch = models.TextField("批次号", null=True, blank=True) wm = models.ForeignKey(WMaterial, verbose_name='关联车间库存', on_delete=models.SET_NULL, null=True, blank=True, related_name='handoverb_wm') wm_to = models.ForeignKey(WMaterial, verbose_name='所到车间库存', on_delete=models.SET_NULL, null=True, blank=True, related_name='handoverb_wm_to') count = models.DecimalField('送料数', default=0, max_digits=11, decimal_places=1) @property def handoverbw(self): return Handoverbw.objects.filter(handoverb=self) class Handoverbw(BaseModel): """TN: 单个产品交接记录 """ handoverb = models.ForeignKey(Handoverb, verbose_name='关联交接记录', on_delete=models.CASCADE) number = models.TextField('单个编号') wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.CASCADE , related_name='wpr_handoverbw', null=True, blank=True) note = models.TextField('备注', null=True, blank=True) class AttLog(CommonADModel): """ TN: 到岗记录 """ ATT_STATE_CHOICES = [ ('pending', '待定'), ('normal', '正常'), ('late', '迟到'), ('early_leave', '早退'), ('absent', '未到岗'), ('leave', '请假'), # 可以根据需要添加更多状态 ] sflog = models.ForeignKey( SfLog, verbose_name='关联值班记录', on_delete=models.CASCADE) user = models.ForeignKey( 'system.user', verbose_name='到岗人', on_delete=models.CASCADE) post = models.ForeignKey( 'system.post', verbose_name='岗位', on_delete=models.CASCADE) state = models.CharField('状态', max_length=20, choices=ATT_STATE_CHOICES, default='pending', help_text=str(ATT_STATE_CHOICES)) note = models.TextField('备注信息', default='', blank=True) class OtherLog(CommonADModel): """ TN: 其他生产日志 """ product = models.CharField('产品', max_length=10) handle_date = models.DateField('操作日期') count_real = models.PositiveIntegerField('实际生产数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0) count_delivered = models.PositiveIntegerField('交付数', default=0) class BatchSt(BaseModel): """ TN: 产品批次统计 """ batch = models.TextField("批次号", db_index=True) version = models.IntegerField("版本号", default=1, db_index=True) last_time = models.DateTimeField("最后操作时间", null=True, blank=True) data = models.JSONField("数据", default=list, blank=True) material_start = models.ForeignKey(Material, verbose_name="起始物料", on_delete=models.SET_NULL, null=True, blank=True) mio = models.ForeignKey("inm.mio", verbose_name="由何出入库记录创建", on_delete=models.CASCADE, null=True, blank=True) handover = models.ForeignKey(Handover, verbose_name='由何交接记录创建', on_delete=models.CASCADE, null=True, blank=True) mlog = models.ForeignKey(Mlog, verbose_name='由何日志创建', on_delete=models.CASCADE, null=True, blank=True) class Meta: unique_together = [("batch", "version")] @classmethod def g_create(cls, batch:str, mio=None, handover=None, mlog=None, material_start=None): """ 创建新的批次 """ if mio is None and handover is None and mlog is None: return cls.objects.get_or_create(batch=batch) else: latest_version = 0 # 带有来源的批次获取,需检查批次号是否可用 if cls.objects.filter(batch=batch, version=0).exists(): latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"] if mio is None and handover is None and mlog is None: raise ParseError("mio or handover or mlog must be provided") ins = cls.objects.create(batch=batch, mio=mio, handover=handover, mlog=mlog, material_start=material_start, version=latest_version+1) return ins, True @classmethod @transaction.atomic def init_dag(cls, batch:str): """ 更新批次数据关系链(初步) """ ins, _ = cls.g_create(batch) if ins.mio is None and ins.handover is None and ins.mlog is None: from apps.inm.models import MIOItem mioitem = MIOItem.objects.filter(batch=batch, mio__submit_time__isnull=False).order_by('mio__submit_time').first() handover = Handover.objects.filter(mtype=20, b_handover__batch=batch, submit_time__isnull=False).order_by('submit_time').first() # 拆分 handover2 = Handover.objects.filter(mtype=10, b_handover__batch=batch, submit_time__isnull=False).order_by('submit_time').first() # 合并 return ins class BatchLog(BaseModel): """ TN: 拆合批变更记录 """ source = models.ForeignKey(BatchSt, verbose_name='来源批次', on_delete=models.CASCADE, related_name="batch_s") target = models.ForeignKey(BatchSt, verbose_name='目标批次', on_delete=models.CASCADE, related_name="batch_t") handover = models.ForeignKey(Handover, verbose_name='关联交接记录', on_delete=models.CASCADE, null=True, blank=True) mlog = models.ForeignKey(Mlog, verbose_name='关联生产记录', on_delete=models.CASCADE, null=True, blank=True) relation_type = models.CharField('关联类型', max_length=20, help_text="split/merge", default="split") @classmethod def g_create(cls, source:str, target:str=None, relation_type="split", handover=None, mlog=None): """ 创建新的关系 """ if relation_type not in ["split", "merge"]: raise ParseError("relation_type must be split or merge") if handover is None and mlog is None: raise ParseError("handover or mlog must be provided") return cls.objects.get_or_create(source=source, target=target, handover=handover, mlog=mlog, defaults={"relation_type": relation_type}) @classmethod def clear(cls, handover=None, mlog=None, mio=None): if handover: cls.objects.filter(handover=handover).delete() BatchSt.objects.filter(handover=handover).delete() if mlog: cls.objects.filter(mlog=mlog).delete() BatchSt.objects.filter(mlog=mlog).delete() if mio: BatchSt.objects.filter(mio=mio).delete()