855 lines
46 KiB
Python
855 lines
46 KiB
Python
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
|
||
import re
|
||
from django.db.models import Q
|
||
import django.utils.timezone as timezone
|
||
from apps.utils.sql import query_all_dict
|
||
|
||
# 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('批次号', db_index=True)
|
||
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')
|
||
number_from = models.TextField("来源于个号", null=True, blank=True)
|
||
|
||
@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
|
||
|
||
@property
|
||
def count_handovering(self):
|
||
return Handoverb.objects.filter(wm=self, handover__submit_time__isnull=True).aggregate(count=Sum('count'))['count'] or 0
|
||
|
||
@classmethod
|
||
def ava_qs(cls, mgroup:Mgroup, qs=None):
|
||
"""
|
||
可用的queryset
|
||
"""
|
||
if qs is None:
|
||
qs = cls.objects
|
||
return qs.filter(Q(mgroup=mgroup)|Q(mgroup=None, belong_dept=mgroup.belong_dept)|Q(mgroup=None, belong_dept=None))
|
||
|
||
@classmethod
|
||
def mat_in_qs(cls, mtask: Mtask, qs=None):
|
||
"""
|
||
TN: 可用于该子任务的queryset
|
||
"""
|
||
if qs is None:
|
||
qs = cls.objects
|
||
return WMaterial.ava_qs(mgroup=mtask.mgroup, qs=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, db_index=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, db_index=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, db_index=True)
|
||
team = models.ForeignKey(Team, verbose_name='班组', on_delete=models.SET_NULL, null=True, blank=True)
|
||
handle_user = models.ForeignKey(
|
||
User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True, db_index=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)
|
||
create_time = models.DateTimeField(
|
||
default=timezone.now, verbose_name='创建时间', help_text='创建时间', db_index=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
|
||
|
||
def cal_mlog_count_from_mlogb(self):
|
||
"""
|
||
通过mlogb计算mlog count 合计
|
||
"""
|
||
mlog = self
|
||
if mlog.fill_way in [Mlog.MLOG_23, Mlog.MLOG_12]:
|
||
a_dict = {
|
||
"total_count_use": Sum('count_use'),
|
||
"total_count_break": Sum('count_break'),
|
||
"total_count_break_t": Sum('count_break_t'),
|
||
"total_count_real": Sum('count_real'),
|
||
"total_count_ok": Sum('count_ok'),
|
||
"total_count_ok_full": Sum('count_ok_full'),
|
||
"total_count_notok": Sum('count_notok'),
|
||
}
|
||
f_names = [f.name for f in Mlogb._meta.fields if 'count_n_' in f.name]
|
||
for f in f_names:
|
||
a_dict[f'total_{f}'] = Sum(f)
|
||
mlogb_summary = Mlogb.objects.filter(mlog=mlog, need_inout=True).aggregate(
|
||
**a_dict
|
||
)
|
||
# 更新Mlog对象的相应字段
|
||
mlog.count_use = mlogb_summary['total_count_use'] or 0
|
||
mlog.count_break = mlogb_summary['total_count_break'] or 0
|
||
mlog.count_break_t = mlogb_summary['total_count_break_t'] or 0
|
||
mlog.count_real = mlogb_summary['total_count_real'] or 0
|
||
mlog.count_ok = mlogb_summary['total_count_ok'] or 0
|
||
mlog.count_ok_full = mlogb_summary['total_count_ok_full'] or 0
|
||
mlog.count_notok = mlogb_summary['total_count_notok'] or 0
|
||
for f in f_names:
|
||
setattr(mlog, f, mlogb_summary[f'total_{f}'] or 0)
|
||
# 保存更新后的Mlog对象
|
||
mlog.save()
|
||
|
||
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, db_index=True)
|
||
mtask = models.ForeignKey(Mtask, verbose_name='关联任务',
|
||
on_delete=models.CASCADE, related_name='mlogb_mtask', null=True, blank=True)
|
||
route = models.ForeignKey(Route, verbose_name='生产步骤', on_delete=models.SET_NULL, 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)
|
||
number_from = models.TextField('来源个编号', null=True, blank=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, cal_mlog=False):
|
||
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 and cal_mlog:
|
||
mlog.cal_mlog_count_from_mlogb()
|
||
|
||
def cal_count_notok(self, cal_mlog=True):
|
||
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
|
||
if self.count_ok_full < 0:
|
||
raise ParseError(f"完全合格数不能小于0:{self.count_real}-{self.count_ok}-{self.count_notok}-{self.count_ok_full}")
|
||
self.save(update_fields=["count_ok", "count_notok", "count_ok_full"])
|
||
mlog = self.mlog
|
||
if mlog and cal_mlog:
|
||
mlog.cal_mlog_count_from_mlogb()
|
||
|
||
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)
|
||
count_has = 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: Mlogb):
|
||
from apps.qm.models import Defect, FtestDefect
|
||
# 锁定mlogb以防止并发修改
|
||
# mlogb:Mlogb = Mlogb.objects.select_for_update().get(pk=mlogb.pk)
|
||
count = Mlogbw.objects.filter(mlogb=mlogb).count()
|
||
if mlogb.material_in:
|
||
mlogb.count_use = count
|
||
mlogb.save(update_fields=["count_use"])
|
||
elif mlogb.material_out:
|
||
mlogb.count_real = count
|
||
count_notok = 0
|
||
count_notok_full = 0
|
||
# 个追踪的合格b类不分批
|
||
tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False)
|
||
tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False).values("ftest__defect_main").annotate(xcount=Count('id'))
|
||
defect_ids = tqs.values_list("ftest__defect_main", flat=True)
|
||
tqs_has_a = FtestDefect.objects.filter(ftest__mlogbw_ftest__mlogb=mlogb, has=True).values("defect").annotate(xcount=Count('id'))
|
||
defect_ids = defect_ids.union(tqs_has_a.values_list("defect", flat=True))
|
||
defects = {defect.id: defect for defect in Defect.objects.filter(id__in=defect_ids)}
|
||
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"]
|
||
if defects[t["ftest__defect_main"]].okcate != 10:
|
||
count_notok_full += t["xcount"]
|
||
|
||
for t2 in tqs_has_a:
|
||
md, _ = MlogbDefect.objects.get_or_create(mlogb=mlogb, defect=defects[t2["defect"]], defaults={"count": 0})
|
||
md.count_has = t2["xcount"]
|
||
md.save()
|
||
md_ids.append(md.id)
|
||
|
||
MlogbDefect.objects.filter(mlogb=mlogb).exclude(id__in=md_ids).delete()
|
||
mlogb.count_notok = count_notok
|
||
mlogb.count_ok = count - mlogb.count_notok
|
||
mlogb.count_ok_full = count - count_notok_full
|
||
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, db_index=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, db_index=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)
|
||
oinfo_json = models.JSONField('其他信息', default=dict, blank=True)
|
||
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
|
||
on_delete=models.SET_NULL, related_name='handover_ticket', null=True, blank=True, db_constraint=False)
|
||
|
||
@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, db_index=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, related_name="w_handoverb")
|
||
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)
|
||
first_time = models.DateTimeField("首次操作时间", null=True, blank=True)
|
||
last_time = models.DateTimeField("最后操作时间", null=True, blank=True)
|
||
data = models.JSONField("数据", default=dict, 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)
|
||
mioitem = models.ForeignKey("inm.mioitem", 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, mioitem=None, handover=None, mlog=None, material_start=None, check_mat_start=False, exclude_batchst_ids=[]):
|
||
"""
|
||
创建新的批次
|
||
"""
|
||
if mioitem:
|
||
mio = mioitem.mio
|
||
node, created = cls.safe_get_or_create(batch=batch, version=1, defaults={
|
||
"mio":mio, "mioitem":mioitem, "handover":handover, "mlog":mlog, "material_start":material_start})
|
||
if not created and check_mat_start:
|
||
if node.material_start is None:
|
||
node.material_start = material_start
|
||
node.save(update_fields = ["material_start"])
|
||
if node.material_start != material_start:
|
||
raise ParseError(f"{batch}-该批次号因物料不同不可引用")
|
||
return node, created
|
||
|
||
# if mio is None and handover is None and mlog is None:
|
||
# try:
|
||
# node = cls.objects.exclude(id__in=exclude_batchst_ids).get(batch=batch)
|
||
# except cls.DoesNotExist:
|
||
# return cls.objects.create(batch=batch), True
|
||
# except cls.MultipleObjectsReturned:
|
||
# # 兼容性处理
|
||
# node = cls.objects.filter(batch=batch).exclude(id__in=exclude_batchst_ids).order_by('-version').first()
|
||
# if node is None:
|
||
# raise ParseError(f"{node.batch}-该批次号本次操作不可引用")
|
||
# return node, False
|
||
# else:
|
||
# version = 1
|
||
# if mio is None and handover is None and mlog is None:
|
||
# raise ParseError("mio or handover or mlog must be provided")
|
||
# # 带有来源的批次获取,需检查批次号是否可用
|
||
# cls_qs = cls.objects.filter(batch=batch)
|
||
# if cls_qs.exists():
|
||
# if reuse_node:
|
||
# node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(handover=None, mio=None, mlog=None)).order_by('-version').first()
|
||
# if node is None:
|
||
# raise ParseError(f"{batch}-该批次号因物料不同不可引用")
|
||
# elif node.material_start is None:
|
||
# node.material_start = material_start
|
||
# node.save(update_fields = ["material_start"])
|
||
# # elif node.material_start is not None and node.material_start != material_start:
|
||
# # raise ParseError(f"{batch}-该批次号因物料不同不可引用-{str(node.material_start)} vs {str(material_start)}")
|
||
# return node, False
|
||
# else:
|
||
# latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"]
|
||
# version = latest_version + 1
|
||
# ins = cls.objects.create(batch=batch, mio=mio, mioitem=mioitem, handover=handover, mlog=mlog, material_start=material_start, version=version)
|
||
# return ins, True
|
||
|
||
@classmethod
|
||
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
|
||
|
||
def custom_key(s):
|
||
match = re.search(r'(\d+)(?!.*\d)', s) # 匹配最后一个数字
|
||
return int(match.group(1)) if match else float('inf')
|
||
|
||
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, mioitem=None):
|
||
if handover:
|
||
logs = cls.objects.filter(handover=handover)
|
||
for log in logs:
|
||
source = log.source
|
||
target = log.target
|
||
log.delete()
|
||
if not BatchLog.objects.filter(Q(source=source) | Q(target=source)).exists():
|
||
if source.mio is None and source.mioitem is None:
|
||
source.delete()
|
||
if not BatchLog.objects.filter(Q(source=target) | Q(target=target)).exists():
|
||
if target.mio is None and target.mioitem is None:
|
||
target.delete()
|
||
if mlog:
|
||
logs = cls.objects.filter(mlog=mlog)
|
||
for log in logs:
|
||
source = log.source
|
||
target = log.target
|
||
log.delete()
|
||
if not BatchLog.objects.filter(Q(source=source) | Q(target=source)).exists():
|
||
if source.mio is None and source.mioitem is None:
|
||
source.delete()
|
||
if not BatchLog.objects.filter(Q(source=target) | Q(target=target)).exists():
|
||
if target.mio is None and target.mioitem is None:
|
||
target.delete()
|
||
if mio:
|
||
ts = BatchSt.objects.filter(mio=mio)
|
||
for t in ts:
|
||
if not BatchLog.objects.filter(Q(source=t) | Q(target=t)).exists():
|
||
t.delete()
|
||
if mioitem:
|
||
ts = BatchSt.objects.filter(mioitem=mioitem)
|
||
for t in ts:
|
||
if not BatchLog.objects.filter(Q(source=t) | Q(target=t)).exists():
|
||
t.delete()
|
||
|
||
@classmethod
|
||
def batches_to(cls, batch:str):
|
||
|
||
# query = """
|
||
# SELECT batch FROM wpm_batchst
|
||
# WHERE batch ~ %s
|
||
# """
|
||
query = """
|
||
SELECT batch
|
||
FROM wpm_batchst
|
||
WHERE batch ~ %s
|
||
ORDER BY
|
||
-- 先按前缀部分排序(例如 'A')
|
||
SUBSTRING(batch FROM '^(.*)-') DESC,
|
||
-- 再按后缀的数值部分排序(将 '2', '11' 转为整数)
|
||
CAST(SUBSTRING(batch FROM '-([0-9]+)$') AS INTEGER) DESC
|
||
""" # 排序可在sql层处理
|
||
query_ = """SELECT batch FROM wpm_batchst WHERE batch ~ %s"""
|
||
pattern = f'^{batch}-[0-9]+$'
|
||
|
||
"""可以用如下方法直接查询
|
||
"""
|
||
# batches = BatchLog.objects.filter(source__batch=batch, relation_type="split").values_list("target__batch", flat=True).distinct()
|
||
# batches = sorted(list(batches), key=custom_key)
|
||
batches_r = query_all_dict(query_, params=(pattern,))
|
||
batches = [b["batch"] for b in batches_r]
|
||
batches = sorted(list(batches), key=custom_key)
|
||
last_batch_num = None
|
||
if batches:
|
||
last_batch = batches[-1]
|
||
last_batch_list = last_batch.split("-")
|
||
if last_batch_list:
|
||
try:
|
||
last_batch_num = int(last_batch_list[-1])
|
||
except Exception:
|
||
pass
|
||
return {"batches": batches, "last_batch_num": last_batch_num, "last_batch": last_batch}
|
||
return {"batches": [], "last_batch_num": None, "last_batch": None}
|
||
|
||
|