factory/apps/qm/models.py

435 lines
19 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, Process
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="标识", null=True, blank=True)
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 = (
('purin', '入厂检验'),
('first', '首件检验'),
('process', '过程检验'),
('prod', '成品检验'),
('performance', '性能检验')
)
class TestItem(CommonAModel):
"""
检测项目
"""
T_TEST = 10
T_OPER = 20
type = models.PositiveSmallIntegerField('类型', choices=((T_TEST, "检测"), (T_OPER, "操作")),
default=T_TEST)
process = models.ForeignKey(Process, verbose_name="工序", on_delete=models.SET_NULL, null=True, blank=True)
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.FloatField('排序', default=1)
description = models.TextField('描述', null=True, blank=True)
readonly = models.BooleanField('只读', default=False)
formula = models.TextField('计算公式', null=True, blank=True)
affects = models.JSONField('影响项列表', default=list, blank=True)
cd_expr = models.TextField('采集表达式', null=True, 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) # process/inm/default
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')
max_defect_rate = models.FloatField('最大不合格率', default=0.5, null=True, blank=True)
@property
def qct_testitems(self):
return QctTestItem.objects.filter(qct=self).order_by("sort")
@property
def qct_defects(self):
return QctDefect.objects.filter(qct=self).order_by("sort")
@property
def qct_mats(self):
return QctMat.objects.filter(qct=self)
@classmethod
def get(cls, material:Material, tag:str):
try:
qct = Qct.objects.get(qctmat__material=material, tags__contains=tag)
except Qct.DoesNotExist:
try:
qct = Qct.objects.get(tags__contains="default")
except Qct.DoesNotExist:
qct = None
except Qct.MultipleObjectsReturned:
raise ParseError("存在多个通用质检表,请手动选择")
except Qct.MultipleObjectsReturned:
raise ParseError("存在多个质检表,请手动选择")
return qct
@property
def defect_default(self):
qctdefect = QctDefect.objects.filter(qct=self, is_default=True)
if qctdefect.exists():
return qctdefect.first().defect
else:
return None
@classmethod
def get_qs(cls, materialId:str, tag:str):
qct_qs = Qct.objects.filter(qctmat__material__id=materialId, tags__contains=tag)
if not qct_qs.exists():
qct_qs = Qct.objects.filter(tags__contains="default")
return qct_qs
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)
addto_wpr = models.BooleanField('加入WPR作为信息', default=False)
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")
is_default = models.BooleanField('是否默认缺陷', default=False)
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))
max_defect_rate = models.FloatField('最大不合格率', default=0.5, null=True, blank=True)
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)
rate_pass_t = 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_ok_full = models.PositiveIntegerField('完全合格数量', null=True, blank=True)
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
count_notok_full = FtestworkDefect.objects.filter(
ftestwork=self).exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0
self.count_ok = self.count - self.count_notok
self.count_ok_full = self.count - count_notok_full
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)
defect_main = models.ForeignKey(Defect, verbose_name='主要缺陷', on_delete=models.SET_NULL, 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
# @classmethod
# def cal_count_notok(cls, ftestwork: FtestWork):
# count = Ftest.objects.filter(ftest_work=ftestwork).count()
# 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 FtestItem(BaseModel):
"""
检测明细
"""
ftest = models.ForeignKey(
Ftest, verbose_name='关联检验', on_delete=models.CASCADE)
testitem = models.ForeignKey(
TestItem, verbose_name='质检项目', on_delete=models.CASCADE)
test_equip = models.ForeignKey(Equipment, verbose_name='检测设备', on_delete=models.SET_NULL, null=True, blank=True)
test_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE, null=True, blank=True)
test_val = models.FloatField('测量值', null=True, blank=True)
addto_wpr = models.BooleanField('加入WPR作为信息', default=False)
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, null=True, blank=True)
sample_number = models.TextField('样品编号')
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)