This commit is contained in:
zty 2025-04-30 14:33:05 +08:00
commit a4ba33550e
19 changed files with 388 additions and 115 deletions

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.12 on 2025-04-30 05:17
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cm', '0003_alter_lablemat_state'),
]
operations = [
migrations.CreateModel(
name='LabelTemplate',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.TextField(verbose_name='名称')),
('commands', models.JSONField(blank=True, default=list, verbose_name='指令模板')),
],
options={
'abstract': False,
},
),
]

View File

@ -13,4 +13,9 @@ class LableMat(BaseModel):
supplier = models.ForeignKey(Supplier, verbose_name='外协供应商', on_delete=models.SET_NULL, null=True, blank=True)
notok_sign = models.CharField('不合格标记', max_length=10, null=True, blank=True)
defect = models.ForeignKey("qm.defect", verbose_name='缺陷', on_delete=models.SET_NULL, null=True, blank=True)
material_origin = models.ForeignKey(Material, verbose_name='原始物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='lm_mo')
material_origin = models.ForeignKey(Material, verbose_name='原始物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='lm_mo')
class LabelTemplate(BaseModel):
name = models.TextField("名称")
commands = models.JSONField("指令模板", default=list, blank=True)

View File

@ -1,7 +1,8 @@
from rest_framework import serializers
from .models import LableMat
from .models import LableMat, LabelTemplate
from apps.qm.models import NotOkOption
from apps.wpm.models import WmStateOption
from apps.utils.serializers import CustomModelSerializer
class TidSerializer(serializers.Serializer):
@ -23,4 +24,10 @@ class LabelMatSerializer(serializers.ModelSerializer):
return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None
def get_state_name(self, obj):
return getattr(WmStateOption, str(obj.state), WmStateOption.OK).label if obj.state else None
return getattr(WmStateOption, str(obj.state), WmStateOption.OK).label if obj.state else None
class LabelTemplateSerializer(CustomModelSerializer):
class Meta:
model = LabelTemplate
fields = '__all__'

View File

@ -1,12 +1,13 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.cm.views import LableMatViewSet
from apps.cm.views import LableMatViewSet, LabelTemplateViewSet
API_BASE_URL = 'api/cm/'
HTML_BASE_URL = 'dhtml/cm/'
router = DefaultRouter()
router.register('labelmat', LableMatViewSet, basename='labelmat')
router.register('labeltemplate', LabelTemplateViewSet, basename='labeltemplate')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,11 +1,11 @@
from apps.cm.models import LableMat
from apps.cm.models import LableMat, LabelTemplate
from rest_framework.decorators import action
from apps.cm.serializers import TidSerializer, LabelMatSerializer
from apps.cm.serializers import TidSerializer, LabelMatSerializer, LabelTemplateSerializer
from apps.inm.models import MaterialBatch, MIOItem
from apps.wpm.models import WMaterial
from rest_framework.exceptions import ParseError, NotFound
from rest_framework.response import Response
from apps.utils.viewsets import CustomGenericViewSet, RetrieveModelMixin, CustomListModelMixin
from apps.utils.viewsets import CustomGenericViewSet, RetrieveModelMixin, CustomListModelMixin, CustomModelViewSet
# Create your views here.
SPLIT_FIELD = "#"
@ -68,3 +68,13 @@ class LableMatViewSet(CustomListModelMixin, RetrieveModelMixin, CustomGenericVie
rdata = LabelMatSerializer(obj).data
rdata["code_label"] = f"mat{SPLIT_FIELD}{obj.id}"
return Response(rdata)
class LabelTemplateViewSet(CustomModelViewSet):
"""
list: 标签模板
标签模板
"""
queryset = LabelTemplate.objects.all()
serializer_class = LabelTemplateSerializer

View File

@ -18,6 +18,7 @@ def do_out(item: MIOItem):
"""
if item.mb and item.mb.defect is not None:
raise ParseError("生产领料不支持不合格品")
from apps.inm.models import MaterialBatch
mio:MIO = item.mio
belong_dept = mio.belong_dept
@ -26,6 +27,16 @@ def do_out(item: MIOItem):
material:Material = item.material
if material.into_wm is False: # 用于混料的原料不与车间库存交互, 这个是配置项目
return
# 获取defect
defect:Defect = None
if item.wm and item.mb:
raise ParseError("车间和仓库库存不能同时存在")
if item.wm:
defect = item.wm.defect
elif item.mb:
defect = item.mb.defect
action_list = []
mias = MIOItemA.objects.filter(mioitem=item)
is_zhj = False # 是否组合件领料
@ -35,16 +46,18 @@ def do_out(item: MIOItem):
for i in range(len(mias_list)):
material, batch, rate = mias_list[i]
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count])
action_list.append([material, batch, new_count, None])
else:
action_list = [[item.material, item.batch, item.count]]
action_list = [[item.material, item.batch, item.count, defect]]
if is_zhj:
try:
mb = MaterialBatch.objects.get(
material=item.material,
warehouse=item.warehouse,
batch=item.batch
batch=item.batch,
state=WMaterial.WM_OK,
defect=None
)
except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e:
raise ParseError(f"组合件批次错误!{e}")
@ -58,10 +71,14 @@ def do_out(item: MIOItem):
raise ParseError("组合件暂不支持追踪单件")
xbatches = []
if is_zhj:
xbatches = [item.batch]
for al in action_list:
xmaterial:Material = al[0]
xbatch:str = al[1]
xcount:str = al[2]
defect:Defect = al[3]
xbatches.append(xbatch)
if xcount <= 0:
raise ParseError("存在非正数!")
@ -72,8 +89,8 @@ def do_out(item: MIOItem):
material=xmaterial,
warehouse=item.warehouse,
batch=xbatch,
state=10,
defect=None
state=WMaterial.WM_OK,
defect=defect
)
except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e:
raise ParseError(f"批次错误!{e}")
@ -85,9 +102,10 @@ def do_out(item: MIOItem):
# 领到车间库存(或工段)
wm, new_create = WMaterial.objects.get_or_create(batch=xbatch, material=xmaterial,
belong_dept=belong_dept, mgroup=mgroup,
state=WMaterial.WM_OK)
wm, new_create = WMaterial.objects.get_or_create(
batch=xbatch, material=xmaterial,
belong_dept=belong_dept, mgroup=mgroup,
state=WMaterial.WM_OK, defect=defect)
if new_create:
wm.create_by = do_user
wm.batch_ofrom = mb.batch if mb else None
@ -116,8 +134,9 @@ def do_in(item: MIOItem):
生产入库后更新车间物料
"""
mio = item.mio
if item.wm and item.wm.defect is not None:
raise ParseError("不合格物料无法入库")
wmin:WMaterial = item.wm
if wmin and wmin.state != WMaterial.WM_OK:
raise ParseError("非合格物料无法入库")
belong_dept = mio.belong_dept
mgroup = mio.mgroup
do_user = mio.do_user
@ -127,30 +146,44 @@ def do_in(item: MIOItem):
action_list = []
mias = MIOItemA.objects.filter(mioitem=item)
is_zhj = False # 是否组合件入仓库
# 获取defect
defect:Defect = None
if item.wm and item.mb:
raise ParseError("车间和仓库库存不能同时存在")
if item.wm:
defect = item.wm.defect
elif item.mb:
defect = item.mb.defect
if mias.exists():
is_zhj = True
mias_list = mias.values_list('material', 'batch', 'rate')
for i in mias_list:
material, batch, rate = i
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count])
action_list.append([material, batch, new_count, None])
else:
action_list = [[item.material, item.batch, item.count]]
action_list = [[item.material, item.batch, item.count, defect]]
production_dept = None
xbatchs = []
if is_zhj:
xbatchs = [item.batch]
for al in action_list:
xmaterial, xbatch, xcount = al
xmaterial, xbatch, xcount, defect = al
if xcount <= 0:
raise ParseError("存在非正数!")
xbatchs.append(xbatch)
# 扣减车间库存
wm_qs = WMaterial.objects.filter(
batch=xbatch,
material=xmaterial,
belong_dept=belong_dept,
mgroup=mgroup,
mgroup=mgroup,
defect=defect,
state=WMaterial.WM_OK)
count_x = wm_qs.count()
if count_x == 1:
@ -161,6 +194,8 @@ def do_in(item: MIOItem):
else:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
# 扣减车间库存
new_count = wm.count - xcount
if new_count >= 0:
wm.count = new_count
@ -180,8 +215,8 @@ def do_in(item: MIOItem):
material=xmaterial,
warehouse=item.warehouse,
batch=xbatch,
state=10,
defect=None,
state=WMaterial.WM_OK,
defect=defect,
defaults={
"count": 0,
"batch_ofrom": wm.batch_ofrom,
@ -208,6 +243,8 @@ def do_in(item: MIOItem):
material=item.material,
warehouse=item.warehouse,
batch=item.batch,
defect=None,
state=WMaterial.WM_OK,
defaults={"count": 0, "production_dept": production_dept}
)
if not is_created:
@ -351,7 +388,9 @@ class InmService:
defect = defects_map[defect_id]
m_list.append((material, warehouse, i.batch, xcount, defect, i))
xbatchs = []
for material, warehouse, batch, change_count, defect, mioitem in m_list:
xbatchs.append(batch)
if change_count <= 0:
raise ParseError("存在非正数!")
state = WMaterial.WM_OK
@ -402,6 +441,10 @@ class InmService:
else:
raise ParseError("不支持的操作")
# 批次统计分析
xbatchs = list(set(xbatchs))
for xbatch in xbatchs:
MyThread(target=get_alldata_with_batch_and_store, args=(xbatch,)).start()
def daoru_mb(path: str):
"""

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-04-28 06:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0056_mgroup_batch_append_code'),
]
operations = [
migrations.AddField(
model_name='process',
name='number_to_batch',
field=models.BooleanField(default=False, verbose_name='个号转批号'),
),
]

View File

@ -16,6 +16,7 @@ class Process(CommonBModel):
PRO_NORMAL = 10
PRO_DIV = 20
PRO_MERGE = 30
name = models.CharField('工序名称', max_length=100)
type = models.PositiveSmallIntegerField("工序类型", default=PRO_PROD, choices=((PRO_PROD, '生产工序'), (PRO_TEST, '检验工序')))
mtype = models.PositiveSmallIntegerField("工序生产类型", default=PRO_NORMAL, choices=((PRO_NORMAL, '常规'), (PRO_DIV, '切分'), (PRO_MERGE, '合并')))
@ -30,6 +31,7 @@ class Process(CommonBModel):
mlog_need_ticket = models.BooleanField('日志提交是否需要审批', default=False)
mstate_json = models.JSONField('中间状态', default=list, blank=True)
parent = models.ForeignKey('self', verbose_name='父工序', on_delete=models.CASCADE, null=True, blank=True)
number_to_batch = models.BooleanField('个号转批号', default=False)
class Meta:
verbose_name = '工序'

View File

@ -1,6 +1,6 @@
from apps.sam.models import Order, OrderItem
from rest_framework.exceptions import ValidationError
from django.db.models import F
from django.db.models import F, Sum
from apps.inm.models import MIO, MIOItem

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2025-04-28 05:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wpm', '0113_mlog_team'),
]
operations = [
migrations.AddField(
model_name='mlogb',
name='number_from',
field=models.TextField(blank=True, null=True, verbose_name='来源个编号'),
),
migrations.AddField(
model_name='wmaterial',
name='number_from',
field=models.TextField(blank=True, null=True, verbose_name='来源于个号'),
),
]

View File

@ -118,6 +118,7 @@ class WMaterial(CommonBDModel):
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):
@ -382,6 +383,7 @@ class Mlogb(BaseModel):
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):
@ -466,6 +468,8 @@ class Mlogbw(BaseModel):
@classmethod
def cal_count_notok(cls, mlogb: Mlog):
from apps.qm.models import Defect
# 锁定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
@ -631,6 +635,8 @@ class BatchSt(BaseModel):
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")
# 带有来源的批次获取,需检查批次号是否可用
if cls.objects.filter(batch=batch).exists():
if reuse_node:
@ -639,11 +645,8 @@ class BatchSt(BaseModel):
raise ParseError(f"{batch}-该批次号因物料不同不可引用")
return node, False
else:
raise ParseError(f"{batch}-该批次号不可使用")
# latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"]
# version = latest_version + 1
if mio is None and handover is None and mlog is None:
raise ParseError("mio or handover or mlog must be provided")
latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"]
version = latest_version + 1
ins = cls.objects.create(batch=batch, mio=mio, handover=handover, mlog=mlog, material_start=material_start, version=version)
return ins, True

View File

@ -724,6 +724,7 @@ class MlogbInSerializer(CustomModelSerializer):
attrs['batch'] = wm_in.batch
attrs["batch_ofrom"] = wm_in.batch_ofrom
attrs["material_ofrom"] = wm_in.material_ofrom
attrs["number_from"] = wm_in.number_from
if route and route.batch_bind and mtask is not None:
if not WMaterial.mat_in_qs(mtask).filter(id=wm_in.id).exists():
raise ParseError('该车间库存非本任务使用')
@ -799,8 +800,11 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer):
in_or_out, tracking = mlogb.get_tracking()
if tracking != Material.MA_TRACKING_SINGLE:
raise ParseError('非单件追踪不可使用')
if in_or_out == 'in' and not wpr:
raise ParseError('请选择相应产品')
if in_or_out == 'in':
if not wpr:
raise ParseError('请选择相应产品')
if mlogb.wm_in and wpr.wm != mlogb.wm_in:
raise ParseError(f'{wpr.number}-该产品非本批次')
return attrs
def save_ftest(self, mlogbw, ftest_data):

View File

@ -340,6 +340,10 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
elif isinstance(mlog_or_b, Mlogb):
wm.batch_ofrom = mlog_or_b.batch_ofrom
wm.material_ofrom = mlog_or_b.material_ofrom
if isinstance(mlog_or_b, Mlogb):
if mlog_or_b.number_from and wm.number_from is not None and wm.number_from != mlog_or_b.number_from:
raise ParseError(f'{wm.batch}-该批号现有库存来源于个号{wm.number_from}')
wm.number_from = mlog_or_b.number_from
wm.save()
if mo_ma.tracking == Material.MA_TRACKING_SINGLE:
if notok_sign_or_defect:
@ -358,7 +362,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
wpr_from = item.mlogbw_from.wpr
wpr = Wpr.change_or_new(number=item.number,
wm=wm, ftest=item.ftest,
wpr_from=wpr_from, batch_from=item.mlogb.batch)
wpr_from=wpr_from)
item.wpr = wpr
item.save()
@ -481,6 +485,9 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
if wm.count < 0:
raise ParseError('车间库存不足, 产物无法回退')
elif wm.count >= 0:
if isinstance(mlog_or_b, Mlogb):
if mlog_or_b.number_from == wm.number_from:
wm.number_from = None
wm.update_by = user
wm.save()
if mo_ma.tracking == Material.MA_TRACKING_SINGLE:
@ -793,7 +800,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
defaults={
"batch_ofrom": wm_from.batch_ofrom,
"material_ofrom": wm_from.material_ofrom,
"create_by": user
"create_by": user,
"number_from": wm_from.number_from
}
)
elif handover.type == Handover.H_REPAIR:
@ -816,7 +824,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
defaults={
"batch_ofrom": wm_from.batch_ofrom,
"material_ofrom": wm_from.material_ofrom,
"create_by": user
"create_by": user,
"number_from": wm_from.number_from
}
)
else:
@ -848,7 +857,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
defaults={
"batch_ofrom": wm_from.batch_ofrom,
"material_ofrom": wm_from.material_ofrom,
"create_by": user
"create_by": user,
"number_from": wm_from.number_from
}
)
else:
@ -867,7 +877,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
defaults={
"batch_ofrom": wm_from.batch_ofrom,
"material_ofrom": wm_from.material_ofrom,
"create_by": user
"create_by": user,
"number_from": wm_from.number_from
}
)
else:
@ -887,7 +898,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
defaults={
"batch_ofrom": wm_from.batch_ofrom,
"material_ofrom": wm_from.material_ofrom,
"create_by": user
"create_by": user,
"number_from": wm_from.number_from
}
)
else:

View File

@ -12,7 +12,7 @@ import decimal
import logging
myLogger = logging.getLogger('log')
def get_alldata_with_batch_and_store(batch: str):
def get_alldata_with_batch_and_store(batch: str, need_update_time=True):
"""
获取某个批次的整体生产数据并保存
"""
@ -29,7 +29,8 @@ def get_alldata_with_batch_and_store(batch: str):
bobj, _ = BatchSt.objects.get_or_create(batch=batch, defaults={
"last_time": last_time
})
bobj.last_time = last_time
if need_update_time:
bobj.last_time = last_time
bobj.data = json.loads(json.dumps(data, cls=MyJSONEncoder))
bobj.save()
@ -278,23 +279,6 @@ def get_alldata_with_batch(batch: str):
myLogger.error(f"六车间_{mgroup_name}_合格率decimal.InvalidOperation-{data}")
data[f'六车间_{mgroup_name}_合格率'] = 0
# 六车间入库/检验数据
# mioitem6_qs2 = MIOItem.objects.filter(mio__belong_dept=dept6, mio__type="do_in",
# batch=batch,
# mio__submit_time__isnull=False)
# if mioitem6_qs2.exists():
# data["六车间生产入库_日期"] = []
# for item in mioitem6_qs:
# data["六车间生产入库_日期"].append(item.mio.inout_date)
# for field in mioitem_count_fields:
# if getattr(item, field) > 0:
# if f'六车间生产入库_{field}' not in data:
# data[f'六车间生产入库_{field}'] = getattr(item, field)
# else:
# data[f'六车间生产入库_{field}'] += getattr(item, field)
# data["六车间生产入库_日期"] = list(set(data["六车间生产入库_日期"]))
# data["六车间生产入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_日期"]])
ftestwork_count_fields = FtestWork.count_fields()
# 六车间中检数据
ftestwork_qs = FtestWork.objects.filter(batch=batch, type="process")
@ -325,6 +309,34 @@ def get_alldata_with_batch(batch: str):
data['六车间中检_检验人'] = list(set(data['六车间中检_检验人']))
data['六车间中检_检验人'] = ";".join([item.name for item in data['六车间中检_检验人']])
# 六车间入库/检验数据
mioitem6_qs2 = MIOItem.objects.filter(mio__belong_dept=dept6, mio__type="do_in",
batch=batch,
mio__submit_time__isnull=False)
if mioitem6_qs2.exists():
data["六车间生产入库_日期"] = []
data["六车间生产入库_检验日期"] = []
data["六车间生产入库_检验人"] = []
for item in mioitem6_qs2:
data["六车间生产入库_日期"].append(item.mio.inout_date)
if item.test_date:
data["六车间生产入库_检验日期"].append(item.test_date)
for field in mioitem_count_fields:
if getattr(item, field) is not None and (getattr(item, field) > 0 or field in ["count", "count_notok"]):
if f'六车间生产入库_{field}' not in data:
data[f'六车间生产入库_{field}'] = getattr(item, field)
else:
data[f'六车间生产入库_{field}'] += getattr(item, field)
data["六车间生产入库_日期"] = list(set(data["六车间生产入库_日期"]))
data["六车间生产入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_日期"]])
data["六车间生产入库_检验日期"] = list(set(data["六车间生产入库_检验日期"]))
data["六车间生产入库_检验日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_检验日期"]])
try:
data['六车间生产入库_合格率'] = round((data['六车间生产入库_count'] - data['六车间生产入库_count_notok']) * 100/ data['六车间生产入库_count'], 1)
except decimal.InvalidOperation:
myLogger.error("六车间生产入库_合格率decimal.InvalidOperation-{data}")
data['六车间生产入库_合格率'] = 0
# 成品检验数据
ftestwork_qs = FtestWork.objects.filter(batch=batch, type="prod")
if ftestwork_qs.exists():
@ -354,9 +366,29 @@ def get_alldata_with_batch(batch: str):
data['成品检验_检验人'] = list(set(data['成品检验_检验人']))
data['成品检验_检验人'] = ";".join([item.name for item in data['成品检验_检验人']])
data['成品检验_合格率'] = round(data['成品检验_count_ok'] * 100/ data['成品检验_count'], 1)
data["六车间_批次生产合格率"] = round(data["成品检验_count_ok"] * 100/ data["六车间领料_count"], 1)
if data["棒料成型_count_real"]:
if getattr(data, "六车间领料_count", 0) > 0:
data["六车间_批次生产合格率"] = round(data["成品检验_count_ok"] * 100/ data["六车间领料_count"], 1)
if getattr(data, "棒料成型_count_real", 0) > 0:
data["七车间_批次应出合格率"] = round(data["成品检验_count_ok"] * 100/ data["棒料成型_count_real"], 1)
# 销售发货数据
mioitem_qs = MIOItem.objects.filter(batch=batch, mio__type="sale_out", mio__submit_time__isnull=False)
if mioitem_qs.exists():
data["销售发货_日期"] = []
data['销售发货_仓库执行人'] = []
data['销售发货_count'] = 0
for item in mioitem_qs:
last_time = item.mio.update_time if item.mio.update_time > last_time else last_time
if item.mio.inout_date:
data["销售发货_日期"].append(item.mio.inout_date)
if item.mio.do_user:
data['销售发货_仓库执行人'].append(item.mio.do_user)
data['销售发货_count']+= item.count
if getattr(data, "棒料成型_count_real", 0) > 0:
data["七车间_批次发货合格率"] = round(data["销售发货_count"] * 100/ data["棒料成型_count_real"], 1)
if getattr(data, "六车间领料_count", 0) > 0:
data["六车间_批次发货合格率"] = round(data["销售发货_count"] * 100/ data["六车间领料_count"], 1)
data['销售发货_仓库执行人'] = ";".join([item.name for item in data['销售发货_仓库执行人']])
return last_time, data

View File

@ -563,6 +563,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
mlogbin: Mlogb = serializer.save()
mlog:Mlog = mlogbin.mlog
route:Route = mlog.route
process: Process = route.process if route else None
mtype = route.process.mtype if route else None
is_fix = mlog.is_fix
qct = mlog.qct
@ -614,9 +615,17 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
elif mtype == Process.PRO_DIV: # 切分 支持批到批,个到个, 个到批
div_number = route.div_number
if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_BATCH:
lenx = Mlogbw.objects.filter(mlogb=mlogbin).count()
# 用个号做批号是用于后续在复用个号可以追踪到原先的个
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
m_dict["batch"] = mlogbwin.number
Mlogb.objects.get_or_create(mlogbw_from=mlogbwin, defaults=update_dict(m_dict, {"count_real": div_number, "count_ok": div_number}))
if process and process.number_to_batch:
m_dict["batch"] = mlogbwin.number
mlogbout, _ = Mlogb.objects.get_or_create(
mlogbw_from=mlogbwin, defaults=update_dict(m_dict, {"count_real": div_number, "count_ok": div_number}))
if lenx == 1:
mlogbout.mlogb_from = mlogbin
mlogbout.number_from = mlogbwin.number
mlogbout.save()
elif material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
d_count_real = mlogbin.count_use * div_number
d_count_ok = d_count_real
@ -635,13 +644,22 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,{"count_real": d_count_real, "count_ok": d_count_ok}))
mlogbout.count_json_from = mlogbin.count_json_from
mlogbout.save(update_fields=["count_json_from"])
elif mtype == Process.PRO_MERGE: # 支持批到批
elif mtype == Process.PRO_MERGE: # 支持批到批,批到个
xcount = math.floor( mlogbin.count_use / route.div_number)
d_count_real = xcount
d_count_ok = xcount
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict, {"count_real": d_count_real, "count_ok": d_count_ok}))
mlogbout.count_json_from = mlogbin.count_json_from
mlogbout.save(update_fields=["count_json_from"])
if material_out.tracking == Material.MA_TRACKING_SINGLE:
number = mlogbin.batch
if mlogbin.number_from:
number = mlogbin.number_from
if d_count_real == 1:
Mlogbw.objects.get_or_create(number=number, mlogb=mlogbout)
else:
for i in range(d_count_real):
Mlogbw.objects.get_or_create(number=f'{number}-{i+1}', mlogb=mlogbout)
elif is_fix:# 支持批到批,个到个
d_count_real = mlogbin.count_use
d_count_ok = mlogbin.count_use
@ -711,43 +729,57 @@ class MlogbwViewSet(CustomModelViewSet):
@transaction.atomic
def perform_create(self, serializer):
ins:Mlogbw = serializer.save()
route:Route = ins.mlogb.mlog.route
mlogb:Mlogb = ins.mlogb
Mlogbw.cal_count_notok(mlogb)
# 如果是输入且输出追踪到个,需同步创建
material_in:Material = mlogb.material_in
if material_in is not None:
mlogb_qs = Mlogb.objects.filter(mlogb_from=mlogb)
material_out:Material = mlogb.mlog.material_out
mtype = route.process.mtype if route.process else None
if mtype == Process.PRO_DIV:
mlogbin = ins.mlogb
wm_in = mlogbin.wm_in
mlog = mlogbin.mlog
div_number = route.div_number
m_dict = {
"mtask": mlogbin.mtask,
"mlog": mlog,
"batch": ins.number,
"material_out": material_out,
"batch_ofrom": wm_in.batch_ofrom,
"material_ofrom": wm_in.material_ofrom,
"count_real": div_number,
"count_ok": div_number, "qct": mlog.qct
}
mlogbout, _ = Mlogb.objects.get_or_create(mlogbw_from=ins, defaults=m_dict)
if material_out.tracking == Material.MA_TRACKING_SINGLE:
for i in range(div_number):
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f"{ins.number}-{i+1}", defaults={"mlogbw_from": ins})
Mlogbw.cal_count_notok(mlogbout)
elif mlogb_qs.exists() and material_out.tracking == Material.MA_TRACKING_SINGLE:
for mlogb in mlogb_qs:
if route.process.mtype == Process.PRO_NORMAL:
Mlogbw.objects.get_or_create(mlogb=mlogb, wpr=ins.wpr, defaults={"number": ins.number, "mlogbw_from": ins})
elif route.process.mtype == Process.PRO_DIV:
for i in range(route.div_number):
Mlogbw.objects.get_or_create(mlogb=mlogb, number=f'{ins.number}-{i+1}', defaults={"mlogbw_from": ins})
Mlogbw.cal_count_notok(mlogb)
if isinstance(ins, list):
insx = ins
else:
insx = [ins]
for ins in insx:
route:Route = ins.mlogb.mlog.route
mlogb:Mlogb = ins.mlogb
Mlogbw.cal_count_notok(mlogb)
# 如果是输入且输出追踪到个,需同步创建
material_in:Material = mlogb.material_in
if material_in is not None:
mlogb_qs = Mlogb.objects.filter(mlogb_from=mlogb)
material_out:Material = mlogb.mlog.material_out
mtype = route.process.mtype if route.process else None
if mtype == Process.PRO_DIV:
mlogbin = ins.mlogb
wm_in = mlogbin.wm_in
mlog = mlogbin.mlog
div_number = route.div_number
m_dict = {
"mtask": mlogbin.mtask,
"mlog": mlog,
"batch": ins.number,
"material_out": material_out,
"batch_ofrom": wm_in.batch_ofrom,
"material_ofrom": wm_in.material_ofrom,
"count_real": div_number,
"count_ok": div_number, "qct": mlog.qct
}
mlogbout, _ = Mlogb.objects.get_or_create(mlogbw_from=ins, defaults=m_dict)
if material_out.tracking == Material.MA_TRACKING_SINGLE:
for i in range(div_number):
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f"{ins.number}-{i+1}", defaults={"mlogbw_from": ins})
Mlogbw.cal_count_notok(mlogbout)
elif material_out.tracking == Material.MA_TRACKING_BATCH:
number_from = mlogbout.number_from
if number_from is None:
mlogbout.number_from = ins.number
mlogbout.save()
elif number_from == ins.number:
pass
else:
raise ParseError("该个号不可产生该批")
elif mlogb_qs.exists() and material_out.tracking == Material.MA_TRACKING_SINGLE:
for mlogb in mlogb_qs:
if route.process.mtype == Process.PRO_NORMAL:
Mlogbw.objects.get_or_create(mlogb=mlogb, wpr=ins.wpr, defaults={"number": ins.number, "mlogbw_from": ins})
elif route.process.mtype == Process.PRO_DIV:
for i in range(route.div_number):
Mlogbw.objects.get_or_create(mlogb=mlogb, number=f'{ins.number}-{i+1}', defaults={"mlogbw_from": ins})
Mlogbw.cal_count_notok(mlogb)
@transaction.atomic
def perform_update(self, serializer):

View File

@ -26,7 +26,7 @@ class Wpr(BaseModel):
wpr_from = models.ForeignKey("self", verbose_name="来源于", on_delete=models.CASCADE, null=True, blank=True)
@classmethod
def change_or_new(cls, wpr=None, number=None, mb=None, wm=None, old_mb=None, old_wm=None, ftest=None, wpr_from=None, batch_from=None):
def change_or_new(cls, wpr=None, number=None, mb=None, wm=None, old_mb=None, old_wm=None, ftest=None, wpr_from=None):
if wpr is None and number is None:
raise ParseError("id和number不能同时为空")
if mb and wm:
@ -38,30 +38,44 @@ class Wpr(BaseModel):
wpr.delete()
return
elif number:
try:
ins_x = cls.objects.get(number=number)
ins_x = cls.objects.filter(number=number).order_by("-version").first()
if ins_x:
if ins_x.wm is None and ins_x.mb is None:
if ins_x.version > 1: # 说明被复用了
if wpr_from is None:
wpr_from = ins_x
# 创建新的wpr
ins = cls(number=number)
ins.version = -1
ins.oinfo = {}
else:
raise ParseError(f"该物料编号{number}-已存在不可使用")
else:
raise ParseError(f"该物料编号{number}-已存在不可使用")
except cls.DoesNotExist:
else:
ins = cls(number=number)
ins.version = -1
ins.oinfo = {}
if batch_from: # 尝试从批号追踪来源
try:
ins_from = cls.objects.get(number=number)
if ins_from.wm is None and ins_from.mb is None:
if ins_from.version > 1: # 说明被复用了
wpr_from = ins_from
else:
raise ParseError(f"该物料编号{number}-已存在不可使用")
except cls.DoesNotExist:
pass
# if batch_from: # 尝试从批号追踪来源
# ins_from = cls.objects.filter(number=batch_from).order_by("-version").first()
# if ins_from:
# if ins_from.wm is None and ins_from.mb is None:
# if ins_from.version > 1: # 说明被复用了
# wpr_from = ins_from
# else:
# raise ParseError(f"该物料编号{number}-已存在不可使用")
# elif wpr_from is None:
# raise ParseError(f"该物料编号{number}-尝试从批号追踪来源失败")
# elif number_from: # 尝试从编号追踪来源
# ins_from = cls.objects.filter(number=number_from).order_by("-version").first()
# if ins_from:
# if ins_from.wm is None and ins_from.mb is None:
# if ins_from.version > 1: # 说明被复用了
# wpr_from = ins_from
# else:
# raise ParseError(f"该物料编号{number}-已存在不可使用")
# elif wpr_from is None:
# raise ParseError(f"该物料编号{number}-尝试历史编号追踪来源失败")
if old_mb and ins.mb != old_mb:
raise ParseError(f"请检查-{ins.number}-所属仓库批次")

View File

@ -1,3 +1,42 @@
## 2.6.2025043014
- feat: 新增功能
- 生产入库和领料支持b类合格品 [caoqianming]
- 添加标签模板接口 [caoqianming]
- 完善光子批次统计数据 [caoqianming]
- 组合件和入库检验追加统计分析 [caoqianming]
- get_alldata_with_batch添加内容 [caoqianming]
- fix: 问题修复
- mio_saleout缺少import [caoqianming]
## 2.6.2025042816
- feat: 新增功能
- 支持个号转批号及配置 [caoqianming]
- 批次追踪链条还是可以复用批次 [caoqianming]
- mlog结合工序支持批到个 [caoqianming]
- mlogbw支持批量创建 [caoqianming]
- mlogbwcreate校验增加wm所属 [caoqianming]
- ichat 修改LLM 的接口 [zty]
- wmaterial筛选条件优化来料已完成 [caoqianming]
- 获取该批次的dag数据需要传入version [caoqianming]
- 单填写mlog支持返工 [caoqianming]
- ichat 修改大模型接口 [zty]
- mlogbupdate支持变更批号 [caoqianming]
- 6车间合格率统计decimal invalid [caoqianming]
- 修改大模型文件 [zty]
- model add note [zty]
- ichat添加表 [caoqianming]
- 缺陷项分类字段数据库约束放开 [caoqianming]
- 通过django settings延迟获取BASE_PROJECT_CODE [caoqianming]
- base 将配置文件放到单独的config文件夹中防止误操作 [caoqianming]
- fix: 问题修复
- wpr复用number的bug [caoqianming]
- cal_count_notok锁定mlogb以防止并发修改 [caoqianming]
- 获取batchst时默认使用version=1 [caoqianming]
- mloginit在返工时不接收route [caoqianming]
- 生产入库时存入生产车间 [caoqianming]
- 返工校验输入物料选择错误 [caoqianming]
- 完善负数校验 [caoqianming]
- mlogbupdate时attrs遍历时修改的bug [caoqianming]
- batchst的version字段bug [caoqianming]
## 2.6.2025042311
- feat: 新增功能
- 切分工序切分数量支持1 [caoqianming]

View File

@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
ALLOWED_HOSTS = ['*']
SYS_NAME = '星途工厂综合管理系统'
SYS_VERSION = '2.6.2025042311'
SYS_VERSION = '2.6.2025043014'
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition

View File

@ -44,7 +44,7 @@ urlpatterns = [
# api
path('', include('apps.auth1.urls')),
path('', include('apps.ichat.urls')),
# path('', include('apps.ichat.urls')),
path('', include('apps.system.urls')),
path('', include('apps.monitor.urls')),
path('', include('apps.wf.urls')),