367 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			367 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
| from django.db import models
 | |
| from apps.system.models import CommonAModel, User
 | |
| from apps.utils.models import CommonBDModel, BaseModel
 | |
| from apps.mtm.models import Material, Shift
 | |
| from apps.em.models import Equipment
 | |
| from apps.wpm.models import SfLog, WMaterial
 | |
| from django.utils.translation import gettext_lazy as _
 | |
| from django.db import transaction
 | |
| from django.db.models import Sum
 | |
| from rest_framework.exceptions import ParseError
 | |
| 
 | |
| class Defect(CommonAModel):
 | |
|     # 缺陷项
 | |
|     DEFECT_OK = 10
 | |
|     DEFECT_OK_B  = 20
 | |
|     DEFECT_NOTOK = 30
 | |
|     name = models.CharField(max_length=50, verbose_name="名称")
 | |
|     code = models.CharField(max_length=50, verbose_name="标识")
 | |
|     cate = models.CharField(max_length=50, verbose_name="分类", choices=(("尺寸", "尺寸"), ("外观", "外观"), ("内质", "内质")))
 | |
|     okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类", 
 | |
|                                                choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")), 
 | |
|                                                default=DEFECT_NOTOK)
 | |
|     note = models.TextField('备注', null=True, blank=True)
 | |
|     
 | |
|     def __str__(self):
 | |
|         return self.name
 | |
| 
 | |
| 
 | |
| class NotOkOption(models.TextChoices):
 | |
|     # 不合格项
 | |
|     zw = "zw", _("炸纹")
 | |
|     zt = "zt", _("炸头")
 | |
|     tw = "tw", _("条纹")
 | |
|     qp = "qp", _("气泡")
 | |
|     wq = "wq", _("弯曲")
 | |
|     dl = "dl", _("断裂")
 | |
|     pb = "pb", _("偏壁")
 | |
|     dxt = "dxt", _("大小头")
 | |
|     js = "js", _("结石")
 | |
|     qx = "qx", _("气线")
 | |
|     hs = "hs", _("划伤")
 | |
|     cs = "cs", _("挫伤")
 | |
|     bl = "bl", _("不亮")
 | |
|     zz = "zz", _("杂质")
 | |
|     d = "d", _("短")
 | |
|     zdd = "zdd", _("锥度大")
 | |
|     hw = "hw", _("横纹")
 | |
|     yp = "yp", _("有皮")
 | |
|     bp = "bp", _("爆皮")
 | |
|     sc = "sc", _("色差")
 | |
|     tydd = "tydd", _("椭圆度大")
 | |
|     sw = "sw", _("水雾")
 | |
|     zjx = "zjx", _("直径小")
 | |
|     zjd = "zjd", _("直径大")
 | |
|     bhpcd = "bhpcd", _("壁厚偏差大")
 | |
|     xzp = "xzp", _("箱中破")
 | |
|     thhs = "thhs", _("退火后碎")
 | |
|     swen = "swen", _("水纹")
 | |
|     bb = "bb", _("崩边")
 | |
|     wm = "wm", _("雾面")
 | |
|     md = "md", _("麻点")
 | |
|     xh = "xh", _("线痕")
 | |
|     b = "b", _("扁")
 | |
|     zb = "zb", _("棕边")
 | |
|     zq = "zq", _("棕圈")
 | |
|     hqbx = "hqbx", _("黑圈变形")
 | |
|     dj = "dj", _("倒角")
 | |
|     ps = "ps", _("破损")
 | |
|     lq = "lq", _("蓝圈")
 | |
|     hqnj = "hqnj", _("黑圈内径")
 | |
|     hqnjyd = "hqnjyd", _("黑圈内径圆度")
 | |
|     hqwj = "hqwj", _("黑圈外径")
 | |
|     hqwjyd = "hqwjyd", _("黑圈外径圆度")
 | |
|     wj = "wj", _("外径不良")
 | |
|     yd = "yd", _("圆度不良")
 | |
|     txd = "txd", _("同心度不良")
 | |
|     hd = "hd", _("厚度不良")
 | |
|     z = "z", _("脏")
 | |
|     lg = "lg", _("漏光")
 | |
|     ccx = "ccx", _("尺寸小")
 | |
| 
 | |
|     # 合格B类项
 | |
|     zhg = "zhg", _("准合格")
 | |
|     yz = "yz", _("圆准")
 | |
|     xbb = "xbb", _("小崩边")
 | |
|     qsw = "qsw", _("浅水纹")
 | |
|     qhm = "qhm", _("浅划麻")
 | |
|     
 | |
|     jgqbl = "jgqbl", _("加工前不良")
 | |
|     qt = "qt", _("其它")
 | |
| 
 | |
|     @classmethod
 | |
|     def get_extra_info(cls, value):
 | |
|         extra_info = {
 | |
|             "zhg": {"cate": "ok_b"},
 | |
|             "yz": {"cate": "ok_b"},
 | |
|             "xbb": {"cate": "ok_b"},
 | |
|             "qsw": {"cate": "ok_b"},
 | |
|             "qhm": {"cate": "ok_b"}
 | |
|         }
 | |
|         return extra_info.get(value, {"cate": "notok"})
 | |
| 
 | |
| FTEST_TYPE_CHOICES = (
 | |
|     ('first', '首件检验'),
 | |
|     ('process', '过程检验'),
 | |
|     ('prod', '成品检验')
 | |
| )
 | |
| 
 | |
| class TestItem(CommonAModel):
 | |
|     """
 | |
|     检测项目
 | |
|     """
 | |
|     name = models.CharField('名称', max_length=100)
 | |
|     field_type = models.CharField('字段类型', max_length=20, default='input-number', choices=(
 | |
|         ('input-int', '整数输入'),
 | |
|         ('input-number', '小数输入'), 
 | |
|         ('input-text', '文本输入'), 
 | |
|         ('select-text', '单选文本'), 
 | |
|         ('selects-text', '多选文本')
 | |
|     ))
 | |
|     number = models.CharField('编号', max_length=20, default='', blank=True)
 | |
|     choices = models.JSONField('选项', default=list, blank=True)
 | |
|     tags = models.JSONField('检测类型', default=list, blank=True)
 | |
|     mcate_tags = models.JSONField('物料系列标签', default=list, blank=True)
 | |
|     sort = models.PositiveSmallIntegerField('排序', default=1)
 | |
|     description = models.TextField('描述', default='')
 | |
|     readonly = models.BooleanField('只读', default=False)
 | |
|     formula = models.TextField('计算公式', null=True, blank=True)
 | |
|     affects = models.JSONField('影响项列表', default=list, blank=True)
 | |
| 
 | |
|     class Meta:
 | |
|         ordering = ['sort', '-create_time']
 | |
| 
 | |
| 
 | |
| QC_T = 'test'
 | |
| QC_D = 'defect'
 | |
| 
 | |
| QC_TRACE_CHOICES = (
 | |
|     (QC_T, '检测项'),
 | |
|     (QC_D, '缺陷项')
 | |
| )
 | |
| 
 | |
| class Qct(CommonAModel):
 | |
|     # 检测模板
 | |
|     name = models.CharField(max_length=50, verbose_name="名称")
 | |
|     number = models.CharField(max_length=20, verbose_name="编号")
 | |
|     tags = models.JSONField('检测类型', default=list, blank=True)
 | |
|     testitems = models.ManyToManyField(TestItem, verbose_name="检测项", blank=True, through='qm.qcttestitem')
 | |
|     defects = models.ManyToManyField(Defect, verbose_name="缺陷项", blank=True, through='qm.qctdefect')
 | |
|     materials = models.ManyToManyField(Material, verbose_name="物料", blank=True, through='qm.qctmat')
 | |
| 
 | |
|     @property
 | |
|     def qct_testitems(self):
 | |
|         return QctTestItem.objects.filter(qct=self)
 | |
|     
 | |
|     @property
 | |
|     def qct_defects(self):
 | |
|         return QctDefect.objects.filter(qct=self)
 | |
|     
 | |
|     @property
 | |
|     def qct_mats(self):
 | |
|         return QctMat.objects.filter(qct=self)
 | |
| 
 | |
| class QctTestItem(BaseModel):
 | |
|     qct = models.ForeignKey(Qct, verbose_name="质检模板", on_delete=models.CASCADE, related_name="qcttestitem")
 | |
|     testitem = models.ForeignKey(TestItem, verbose_name="检测项", on_delete=models.CASCADE, null=True, blank=True)
 | |
|     note = models.TextField('备注', null=True, blank=True)
 | |
|     sort = models.FloatField('排序', default=1)
 | |
| 
 | |
| class QctDefect(BaseModel):
 | |
|     qct = models.ForeignKey(Qct, verbose_name="质检模板", on_delete=models.CASCADE, related_name="qctdefect")
 | |
|     defect = models.ForeignKey(Defect, verbose_name="缺陷项", on_delete=models.CASCADE)
 | |
|     rule_expression = models.TextField('判定表达式', null=True, blank=True)
 | |
|     note = models.TextField('备注', null=True, blank=True)
 | |
|     sort = models.FloatField('排序', default=1)
 | |
| 
 | |
| class QctMat(BaseModel):
 | |
|     qct = models.ForeignKey(Qct, verbose_name="质检模板", on_delete=models.CASCADE, related_name="qctmat")
 | |
|     material = models.ForeignKey(Material, verbose_name="物料", on_delete=models.CASCADE)
 | |
|     tracing = models.CharField('追溯层级', default=QC_T, choices=QC_TRACE_CHOICES,
 | |
|                                max_length=20, help_text=str(QC_TRACE_CHOICES))
 | |
|     
 | |
| 
 | |
| class QuaStat(CommonBDModel):
 | |
|     """
 | |
|     质量数据表
 | |
|     """
 | |
|     material = models.ForeignKey(
 | |
|         Material, verbose_name='关联产物', on_delete=models.CASCADE)
 | |
|     sflog = models.ForeignKey(
 | |
|         SfLog, verbose_name='关联值班记录', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     testitem = models.ForeignKey(
 | |
|         TestItem, verbose_name='质检项目', on_delete=models.CASCADE)
 | |
|     val_avg = models.FloatField('平均值', null=True, blank=True)
 | |
|     num_test = models.PositiveSmallIntegerField('检测次数', null=True, blank=True)
 | |
|     num_ok = models.PositiveSmallIntegerField('合格次数', null=True, blank=True)
 | |
|     rate_pass = models.FloatField('合格率', null=True, blank=True)
 | |
| 
 | |
| 
 | |
| class FtestWork(CommonBDModel):
 | |
|     """
 | |
|     检验工作
 | |
|     """
 | |
|     TYPE2_SOME = 10
 | |
|     TYPE2_ALL = 20
 | |
|     type = models.CharField('检验类型', max_length=20, choices=FTEST_TYPE_CHOICES, default='prod')
 | |
|     type2 = models.PositiveSmallIntegerField('检验类型2', choices=((TYPE2_SOME, '抽检'), (TYPE2_ALL, '全检')), default=10)
 | |
|     shift = models.ForeignKey(Shift, verbose_name='班次', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     wm = models.ForeignKey(WMaterial, verbose_name='关联车间库存', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     mb = models.ForeignKey('inm.materialbatch', verbose_name='关联仓库', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     test_date = models.DateField('检验日期')
 | |
|     material = models.ForeignKey(
 | |
|         Material, verbose_name='产品', on_delete=models.CASCADE)
 | |
|     batch = models.CharField('生产批次', max_length=50)
 | |
|     count = models.PositiveIntegerField('检验数量')
 | |
|     count_sampling = models.PositiveIntegerField('抽检数量', default=0)
 | |
|     count_sampling_ok = models.PositiveIntegerField('抽检合格数量', default=0)
 | |
|     count_ok = models.PositiveIntegerField('合格数量', default=0)
 | |
|     count_notok = models.PositiveIntegerField('不合格数量', default=0)
 | |
|     need_update_wm = models.BooleanField('是否更新车间库存', default=True)
 | |
|     count_notok_json = models.JSONField('不合格项数量统计', default=dict, null=False, blank=True)
 | |
|     test_user = models.ForeignKey(
 | |
|         User, verbose_name='操作人', on_delete=models.CASCADE, related_name='ftestwork_test_user', null=True, blank=True)
 | |
|     submit_time = models.DateTimeField('提交时间', null=True, blank=True)
 | |
|     submit_user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='提交人', null=True, blank=True)
 | |
|     note = models.TextField('备注', null=True, blank=True)
 | |
|     equipment = models.ForeignKey(Equipment, verbose_name='所属检验设备', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
 | |
|                                on_delete=models.SET_NULL, related_name='ftestwork_ticket', null=True, blank=True,
 | |
|                                db_constraint=False)
 | |
|     qct = models.ForeignKey(Qct, verbose_name='关联质检表', on_delete=models.SET_NULL, null=True, blank=True)
 | |
| 
 | |
|     @classmethod
 | |
|     def count_fields(cls):
 | |
|         ftestwork_count_fields = []
 | |
|         for f in FtestWork._meta.fields:
 | |
|             if f.name.startswith("count"):
 | |
|                 ftestwork_count_fields.append(f.name)
 | |
|         return ftestwork_count_fields
 | |
| 
 | |
|     def cal_count(self):
 | |
|         self.count_notok = FtestworkDefect.objects.filter(
 | |
|             ftestwork=self, defect__okcate=30).aggregate(total=Sum("count"))["total"] or 0
 | |
|         self.count_ok = self.count - self.count_notok
 | |
|         if self.type2 == 20: #全检
 | |
|             self.count_sampling = self.count
 | |
|             self.count_sampling_ok = self.count_ok
 | |
|         if self.count_ok < 0:
 | |
|             raise ParseError('合格数量不能小于0')
 | |
|         self.save(update_fields=['count_ok', 'count_notok'])
 | |
|     
 | |
|     @property
 | |
|     def ftestworkdefect(self):
 | |
|         return FtestworkDefect.objects.filter(ftestwork=self)
 | |
| 
 | |
| class FtestworkDefect(BaseModel):
 | |
|     ftestwork = models.ForeignKey(FtestWork, verbose_name='关联检验工作', on_delete=models.CASCADE,
 | |
|                                   related_name='ftestwork_defect')
 | |
|     defect = models.ForeignKey(Defect, verbose_name='缺陷', on_delete=models.CASCADE)
 | |
|     count = models.PositiveIntegerField('数量', default=0)
 | |
| 
 | |
| class Ftest(CommonBDModel):
 | |
|     """
 | |
|     检验记录
 | |
|     """
 | |
|     type = models.CharField('检验类型', max_length=20, choices=FTEST_TYPE_CHOICES)
 | |
|     test_date = models.DateField('检验日期')
 | |
|     qct = models.ForeignKey(Qct, verbose_name='所用质检表', on_delete=models.SET_NULL, null=True, blank=True)
 | |
|     test_numer = models.TextField('检测编号', default='')
 | |
|     test_group = models.CharField(
 | |
|         '检验工序集', max_length=20, default='', blank=True)
 | |
|     test_user = models.ForeignKey(
 | |
|         User, verbose_name='操作人', on_delete=models.CASCADE, related_name='ftest_test_user')
 | |
|     check_user = models.ForeignKey(
 | |
|         User, verbose_name='专检人', on_delete=models.CASCADE, related_name='ftest_check_user', null=True, blank=True)
 | |
|     is_ok = models.BooleanField('是否合格', default=False)
 | |
|     note = models.TextField('备注', default='', blank=True)
 | |
|     ftest_work = models.ForeignKey(
 | |
|         FtestWork, verbose_name='关联检验工作', on_delete=models.CASCADE, null=True, blank=True)
 | |
| 
 | |
|     @property
 | |
|     def ftestitems(self):
 | |
|         return FtestItem.objects.filter(ftest=self)
 | |
| 
 | |
|     @property
 | |
|     def ftestdefects(self):
 | |
|         return FtestDefect.objects.filter(ftest=self)
 | |
| 
 | |
|     @classmethod
 | |
|     def init_by_qct(cls, qct, test_user, test_date):
 | |
|         with transaction.atomic():
 | |
|             ftest = Ftest.objects.create(qct=qct, test_user=test_user, test_date=test_date)
 | |
|             for testitem in qct.testitems.all():
 | |
|                 FtestItem.objects.create(ftest=ftest, testitem=testitem)
 | |
|             for defect in qct.defects.all():
 | |
|                 FtestDefect.objects.create(ftest=ftest, defect=defect)
 | |
|             return ftest
 | |
| 
 | |
| 
 | |
| class FtestItem(BaseModel):
 | |
|     """
 | |
|     检测明细
 | |
|     """
 | |
|     ftest = models.ForeignKey(
 | |
|         Ftest, verbose_name='关联检验', on_delete=models.CASCADE)
 | |
|     testitem = models.ForeignKey(
 | |
|         TestItem, verbose_name='质检项目', on_delete=models.CASCADE)
 | |
|     test_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     test_val = models.FloatField('测量值', null=True, blank=True)
 | |
|     check_val = models.FloatField('专检测量值', null=True, blank=True)
 | |
|     test_val_json = models.JSONField('测量值', null=True, blank=True)
 | |
|     check_val_json = models.JSONField('专检测量值', null=True, blank=True)
 | |
| 
 | |
| 
 | |
| class FtestDefect(BaseModel):
 | |
|     """
 | |
|     缺陷明细
 | |
|     """
 | |
|     ftest = models.ForeignKey(
 | |
|         Ftest, verbose_name='关联检验', on_delete=models.CASCADE, related_name="defects_ftest")
 | |
|     test_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE, null=True, blank=True)
 | |
|     defect = models.ForeignKey(Defect, verbose_name='缺陷', on_delete=models.CASCADE)
 | |
|     has = models.BooleanField('是否发现', default=False)
 | |
|     is_main = models.BooleanField('是否主要缺陷', default=False)
 | |
| 
 | |
| 
 | |
| class Ptest(CommonAModel):
 | |
|     """
 | |
|     性能测试记录
 | |
|     """
 | |
|     PTEST_EQUIP_STATES = (
 | |
|         ('R', '正常'),
 | |
|         ('T', '异常')
 | |
|     )
 | |
|     PTEST_XJ_VALS = (
 | |
|         ('S', '析晶'),
 | |
|         ('R', '不析晶'),
 | |
|         ('θ', '未化')
 | |
|     )
 | |
|     test_date = models.DateField('实验日期')
 | |
|     test_user = models.ForeignKey(
 | |
|         User, verbose_name='检验员', on_delete=models.CASCADE)
 | |
|     testitem = models.ForeignKey(
 | |
|         TestItem, verbose_name='测试项目', on_delete=models.CASCADE)
 | |
|     sample_number = models.CharField('样品编号', max_length=20)
 | |
|     sample_count = models.PositiveIntegerField('样品数量', null=True, blank=True)
 | |
|     sample_density = models.FloatField('样品密度', null=True, blank=True)
 | |
|     specification = models.CharField(
 | |
|         '型号规格', max_length=20, null=True, blank=True)
 | |
|     equip_state = models.CharField(
 | |
|         '仪器状态', max_length=10, default='R', choices=PTEST_EQUIP_STATES, help_text=list(PTEST_EQUIP_STATES))
 | |
|     note = models.TextField('备注', default='', blank=True)
 | |
| 
 | |
|     val_hd = models.FloatField('厚度', help_text='mm', null=True, blank=True)
 | |
|     val_zsl = models.FloatField(
 | |
|         '折射率', help_text='589nm', null=True, blank=True)
 | |
|     val_tgl = models.FloatField(
 | |
|         '透过率', help_text='550nm', null=True, blank=True)
 | |
|     val_ts = models.FloatField("Ts", help_text='℃', null=True, blank=True)
 | |
|     val_tg = models.FloatField("Tg", help_text='℃', null=True, blank=True)
 | |
|     val_tf = models.FloatField("Tf", help_text='℃', null=True, blank=True)
 | |
|     val_xj = models.CharField(
 | |
|         '析晶', max_length=10, default='S', choices=PTEST_XJ_VALS, help_text=list(PTEST_XJ_VALS))
 | |
|     val_pzxs = models.FloatField(
 | |
|         '膨胀系数', help_text='30-300℃', null=True, blank=True)
 | |
|     val_zgwd = models.FloatField('升至最高温度', null=True, blank=True)
 |