452 lines
20 KiB
Python
452 lines
20 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):
|
|
"""TN:缺陷项"""
|
|
DEFECT_OK = 10
|
|
DEFECT_OK_B = 20
|
|
DEFECT_NOTOK = 30
|
|
cate_list = ["尺寸", "外观", "内质", "性能"]
|
|
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="分类", help_text=str(cate_list))
|
|
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):
|
|
"""TN:不合格项"""
|
|
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):
|
|
"""
|
|
TN:检测项目
|
|
"""
|
|
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):
|
|
"""TN:质检表"""
|
|
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/
|
|
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, type:str=None):
|
|
try:
|
|
if type == "in":
|
|
qct = Qct.objects.get(qctmat__material=material, tags__contains=tag, qctmat__use_for_in=True)
|
|
elif type == "out":
|
|
qct = Qct.objects.get(qctmat__material=material, tags__contains=tag, qctmat__use_for_out=True)
|
|
else:
|
|
qct = Qct.objects.get(qctmat__material=material, tags__contains=tag)
|
|
except Qct.DoesNotExist:
|
|
try:
|
|
qct = Qct.objects.get(name="默认检验表")
|
|
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, type:str):
|
|
qct_qs = Qct.objects.filter(qctmat__material__id=materialId, tags__contains=tag)
|
|
if type == "in":
|
|
qct_qs = qct_qs.filter(qctmat__use_for_in=True)
|
|
elif type == "out":
|
|
qct_qs = qct_qs.filter(qctmat__use_for_out=True)
|
|
if not qct_qs.exists():
|
|
qct_qs = Qct.objects.filter(name="默认检验表")
|
|
return qct_qs
|
|
|
|
class QctTestItem(BaseModel):
|
|
"""TN:质检模板检测项关系表"""
|
|
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):
|
|
"""TN:质检模板缺陷项关系表"""
|
|
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):
|
|
"""TN:质检模板物料关系表"""
|
|
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))
|
|
use_for_in = models.BooleanField("可用于消耗", default=True)
|
|
use_for_out = models.BooleanField("可用于产出", default=True)
|
|
max_defect_rate = models.FloatField('最大不合格率', default=0.5, null=True, blank=True)
|
|
|
|
|
|
class QuaStat(CommonBDModel):
|
|
"""
|
|
TN:质量数据表
|
|
"""
|
|
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):
|
|
"""
|
|
TN:检验工作
|
|
"""
|
|
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.TextField('生产批次', db_index=True)
|
|
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, without_count_ok_full=True):
|
|
ftestwork_count_fields = []
|
|
for f in FtestWork._meta.fields:
|
|
if f.name.startswith("count"):
|
|
ftestwork_count_fields.append(f.name)
|
|
if without_count_ok_full:
|
|
ftestwork_count_fields.remove('count_ok_full')
|
|
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):
|
|
"""
|
|
TN:检验记录
|
|
"""
|
|
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=True)
|
|
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):
|
|
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):
|
|
"""
|
|
TN:检测明细
|
|
"""
|
|
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):
|
|
"""
|
|
TN:缺陷明细
|
|
"""
|
|
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):
|
|
"""
|
|
TN:性能测试记录
|
|
"""
|
|
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, null=True, blank=True, 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)
|