合并
This commit is contained in:
zty 2025-04-23 16:49:00 +08:00
commit 8183073026
17 changed files with 153 additions and 21 deletions

2
.gitignore vendored
View File

@ -20,6 +20,8 @@ db.sqlite3
server/conf*.py server/conf*.py
server/conf.ini server/conf.ini
server/conf*.json server/conf*.json
config/conf*.py
config/conf*.json
sh/* sh/*
temp/* temp/*
nohup.out nohup.out

View File

@ -151,11 +151,20 @@ class MpLogxViewSet(CustomListModelMixin, CustomGenericViewSet):
serializer_class = MpLogxSerializer serializer_class = MpLogxSerializer
filterset_fields = { filterset_fields = {
"timex": ["exact", "gte", "lte", "year", "month", "day"], "timex": ["exact", "gte", "lte", "year", "month", "day"],
"mpoint": ["exact"], "mpoint": ["exact", "in"],
"mpoint__ep_monitored": ["exact"]
} }
ordering_fields = ["timex"] ordering_fields = ["timex"]
ordering = ["-timex"] ordering = ["-timex"]
@action(methods=["get"], detail=False, perms_map={"get": "*"})
def to_wide(self, request, *args, **kwargs):
"""转换为宽表
转换为宽表
"""
queryset = self.filter_queryset(self.get_queryset())
class MpointStatViewSet(BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, CustomGenericViewSet): class MpointStatViewSet(BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, CustomGenericViewSet):
""" """

View File

@ -1,9 +1,17 @@
from django.db import models from django.db import models
from apps.system.models import CommonADModel from apps.system.models import CommonADModel, BaseModel
# Create your models here. # Create your models here.
class Conversation(CommonADModel): class Conversation(CommonADModel):
""" """
TN: 对话 TN: 对话
""" """
title = models.CharField(max_length=200, default='新对话',verbose_name='对话标题') title = models.CharField(max_length=200, default='新对话',verbose_name='对话标题')
class Message(BaseModel):
"""
TN: 消息
"""
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, verbose_name='对话')
content = models.TextField(verbose_name='消息内容')
role = models.CharField("角色", max_length=10, help_text="system/user")

View File

@ -186,8 +186,8 @@ class RouteSerializer(CustomModelSerializer):
raise ParseError('未提供操作工序') raise ParseError('未提供操作工序')
if process.parent is not None: if process.parent is not None:
raise ParseError('操作工序不可为子工序') raise ParseError('操作工序不可为子工序')
if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) <= 1: if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) < 1:
raise ParseError('切分数量必须大于1') raise ParseError('切分数量必须大于等于1')
return super().validate(attrs) return super().validate(attrs)
@classmethod @classmethod

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-04-23 05:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0049_alter_ptest_sample_number'),
]
operations = [
migrations.AlterField(
model_name='defect',
name='cate',
field=models.CharField(help_text="['尺寸', '外观', '内质', '性能']", max_length=50, verbose_name='分类'),
),
]

View File

@ -14,9 +14,10 @@ class Defect(CommonAModel):
DEFECT_OK = 10 DEFECT_OK = 10
DEFECT_OK_B = 20 DEFECT_OK_B = 20
DEFECT_NOTOK = 30 DEFECT_NOTOK = 30
cate_list = ["尺寸", "外观", "内质", "性能"]
name = models.CharField(max_length=50, verbose_name="名称") name = models.CharField(max_length=50, verbose_name="名称")
code = models.CharField(max_length=50, verbose_name="标识", null=True, blank=True) code = models.CharField(max_length=50, verbose_name="标识", null=True, blank=True)
cate = models.CharField(max_length=50, verbose_name="分类", choices=(("尺寸", "尺寸"), ("外观", "外观"), ("内质", "内质"))) cate = models.CharField(max_length=50, verbose_name="分类", help_text=str(cate_list))
okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类", okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类",
choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")), choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")),
default=DEFECT_NOTOK) default=DEFECT_NOTOK)

View File

@ -15,6 +15,12 @@ class DefectSerializer(CustomModelSerializer):
model = Defect model = Defect
fields = '__all__' fields = '__all__'
read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
def validate(self, attrs):
cate = attrs["cate"]
if cate not in Defect.cate_list:
raise ParseError("缺陷类别错误")
return attrs
# def create(self, validated_data): # def create(self, validated_data):
# code = validated_data["code"] # code = validated_data["code"]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-04-22 07:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0056_mgroup_batch_append_code'),
('wpm', '0112_auto_20250422_1430'),
]
operations = [
migrations.AddField(
model_name='mlog',
name='team',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mtm.team', verbose_name='班组'),
),
]

View File

@ -252,6 +252,7 @@ class Mlog(CommonADModel):
count_n_qt = 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) handle_date = models.DateField('操作日期', null=True, blank=True)
team = models.ForeignKey(Team, verbose_name='班组', on_delete=models.SET_NULL, null=True, blank=True)
handle_user = models.ForeignKey( handle_user = models.ForeignKey(
User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True) # 成型人 User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True) # 成型人
handle_user_2 = models.ForeignKey( handle_user_2 = models.ForeignKey(
@ -613,7 +614,7 @@ class BatchSt(BaseModel):
class Meta: class Meta:
unique_together = [("batch", "version")] unique_together = [("batch", "version")]
@classmethod @classmethod
def g_create(cls, batch:str, mio=None, handover=None, mlog=None, material_start=None): def g_create(cls, batch:str, mio=None, handover=None, mlog=None, material_start=None):
""" """
@ -622,13 +623,14 @@ class BatchSt(BaseModel):
if mio is None and handover is None and mlog is None: if mio is None and handover is None and mlog is None:
return cls.objects.get_or_create(batch=batch) return cls.objects.get_or_create(batch=batch)
else: else:
latest_version = 0 version = 1
# 带有来源的批次获取,需检查批次号是否可用 # 带有来源的批次获取,需检查批次号是否可用
if cls.objects.filter(batch=batch, version=0).exists(): if cls.objects.filter(batch=batch, version=1).exists():
latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"] 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: if mio is None and handover is None and mlog is None:
raise ParseError("mio or handover or mlog must be provided") raise ParseError("mio or handover or mlog must be provided")
ins = cls.objects.create(batch=batch, mio=mio, handover=handover, mlog=mlog, material_start=material_start, 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 return ins, True
@classmethod @classmethod

View File

@ -234,6 +234,7 @@ class MlogbSerializer(CustomModelSerializer):
class MlogListSerializer(CustomModelSerializer): class MlogListSerializer(CustomModelSerializer):
team_name = serializers.CharField(source="team.name", read_only=True)
mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True) mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True)
supplier_name = serializers.CharField(source='supplier.name', read_only=True) supplier_name = serializers.CharField(source='supplier.name', read_only=True)
# routepack_name = serializers.CharField(source='route.routepack.name', read_only=True) # routepack_name = serializers.CharField(source='route.routepack.name', read_only=True)
@ -291,6 +292,7 @@ class MlogbDetailSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
class MlogSerializer(CustomModelSerializer): class MlogSerializer(CustomModelSerializer):
team_name = serializers.CharField(source="team.name", read_only=True)
mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True) mstate_json = serializers.JSONField(source='mgroup.process.mstate_json', read_only=True)
supplier_name = serializers.CharField(source='supplier.name', read_only=True) supplier_name = serializers.CharField(source='supplier.name', read_only=True)
# routepack_name = serializers.CharField(source='route.routepack.name', read_only=True) # routepack_name = serializers.CharField(source='route.routepack.name', read_only=True)
@ -748,7 +750,7 @@ class MlogbInUpdateSerializer(CustomModelSerializer):
mlogbdefect = MlogbDefectSerializer(many=True, required=False) mlogbdefect = MlogbDefectSerializer(many=True, required=False)
class Meta: class Meta:
model = Mlogb model = Mlogb
fields = ['id', 'count_use', 'count_pn_jgqbl', 'note', 'mlogbdefect'] fields = ['id', 'count_use', 'count_pn_jgqbl', 'note', 'mlogbdefect', "need_inout"]
def validate(self, attrs): def validate(self, attrs):
if attrs["count_use"] < 0 or attrs["count_pn_jgqbl"] < 0: if attrs["count_use"] < 0 or attrs["count_pn_jgqbl"] < 0:
@ -858,6 +860,16 @@ class MlogbOutUpdateSerializer(CustomModelSerializer):
mlogbdefect = validated_data.pop("mlogbdefect", None) mlogbdefect = validated_data.pop("mlogbdefect", None)
with transaction.atomic(): with transaction.atomic():
ins:Mlogb = super().update(instance, validated_data) ins:Mlogb = super().update(instance, validated_data)
if ins.need_inout is False:
if ins.mlogb_from:
if Mlogb.objects.filter(mlog=ins.mlog, material_out__isnull=False, mlogb_from=ins.mlogb_from).count() == 1:
ins_from =Mlogb.objects.filter(mlog=ins.mlog, material_out__isnull=False, mlogb_from=ins.mlogb_from).first()
ins_from.need_inout = False
ins_from.save(update_fields=["need_inout"])
else:
raise ParseError("对应消耗的产出有多个, 需手动指定消耗是否出库")
else:
raise ParseError("该产出需入库!")
if mlogbdefect is not None and ins.material_out.tracking == Material.MA_TRACKING_BATCH: if mlogbdefect is not None and ins.material_out.tracking == Material.MA_TRACKING_BATCH:
MlogbDefect.objects.filter(mlogb=ins).delete() MlogbDefect.objects.filter(mlogb=ins).delete()
mlogb_defect_objects = [ mlogb_defect_objects = [

View File

@ -190,6 +190,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
m_ins_list = [] m_ins_list = []
m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False) m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
if m_ins.exists(): if m_ins.exists():
m_ins = m_ins.filter(need_inout=True)
m_ins_list = [(mi.material_in, mi.batch if mi.batch else mi.batch, mi.count_use, None, mi) for mi in m_ins.all()] m_ins_list = [(mi.material_in, mi.batch if mi.batch else mi.batch, mi.count_use, None, mi) for mi in m_ins.all()]
for item in m_ins: for item in m_ins:
mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item) mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
@ -260,7 +261,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
stored_notok = need_store_notok stored_notok = need_store_notok
stored_mgroup = need_store_notok stored_mgroup = need_store_notok
if mlogb_out_qs.exists(): if mlogb_out_qs.exists():
mlogb_out_qs = mlogb_out_qs.filter(need_inout=True)
m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) for mo in mlogb_out_qs.all()] m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) for mo in mlogb_out_qs.all()]
if need_store_notok: if need_store_notok:
for item in mlogb_out_qs: for item in mlogb_out_qs:
@ -402,6 +404,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
# 需要考虑不合格品退回的情况 # 需要考虑不合格品退回的情况
mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False) mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
if mlogb_out_qs.exists(): if mlogb_out_qs.exists():
mlogb_out_qs = mlogb_out_qs.filter(need_inout=True)
m_outs_list = [ m_outs_list = [
(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) (mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo)
for mo in mlogb_out_qs.all()] for mo in mlogb_out_qs.all()]
@ -497,6 +500,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
into_wm_mgroup = process.into_wm_mgroup into_wm_mgroup = process.into_wm_mgroup
m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False) m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
if m_ins.exists(): if m_ins.exists():
m_ins = m_ins.filter(need_inout=True)
for mi in m_ins.all(): for mi in m_ins.all():
m_ins_list.append((mi.material_in, mi.batch, mi.count_use, None, mi)) m_ins_list.append((mi.material_in, mi.batch, mi.count_use, None, mi))
for item in m_ins: for item in m_ins:

View File

@ -5,7 +5,7 @@ from apps.inm.models import MIOItem
from apps.qm.models import FtestWork from apps.qm.models import FtestWork
from django.utils import timezone from django.utils import timezone
from datetime import datetime from datetime import datetime
from server.conf import BASE_PROJECT_CODE from django.conf import settings
import json import json
from apps.utils.tools import MyJSONEncoder from apps.utils.tools import MyJSONEncoder
import decimal import decimal
@ -17,6 +17,7 @@ def get_alldata_with_batch_and_store(batch: str):
获取某个批次的整体生产数据并保存 获取某个批次的整体生产数据并保存
""" """
need_update = False need_update = False
BASE_PROJECT_CODE = getattr(settings, "BASE_PROJECT_CODE", None)
if BASE_PROJECT_CODE == "gzerp": if BASE_PROJECT_CODE == "gzerp":
need_update = True need_update = True
last_time, data = get_alldata_with_batch(batch) last_time, data = get_alldata_with_batch(batch)

View File

@ -174,13 +174,13 @@ class MlogViewSet(CustomModelViewSet):
select_related_fields = ['create_by', 'update_by', 'mtask', 'mtaskb', select_related_fields = ['create_by', 'update_by', 'mtask', 'mtaskb',
'handle_user', 'handle_user_2', 'equipment', 'mgroup__belong_dept', 'handle_user', 'handle_user_2', 'equipment', 'mgroup__belong_dept',
'equipment_2', 'material_in', 'material_out', 'route__routepack', 'equipment_2', 'material_in', 'material_out', 'route__routepack',
'supplier', 'ticket', 'mgroup__process', 'test_user', 'handle_leader', 'test_user'] 'supplier', 'ticket', 'mgroup__process', 'test_user', 'handle_leader', 'test_user', 'team']
prefetch_related_fields = ['handle_users', prefetch_related_fields = ['handle_users',
'material_outs', 'b_mlog', 'equipments'] 'material_outs', 'b_mlog', 'equipments']
filterset_class = MlogFilter filterset_class = MlogFilter
search_fields = ['material_in__name', search_fields = ['material_in__name',
'material_in__number', 'material_in__specification', 'batch', 'material_in__model', 'material_in__number', 'material_in__specification', 'batch', 'material_in__model',
'material_out__name', 'material_out__number', 'material_out__specification', 'material_out__model',] 'material_out__name', 'material_out__number', 'material_out__specification', 'material_out__model']
def add_info_for_item(self, data): def add_info_for_item(self, data):
if data.get("oinfo_json", {}): if data.get("oinfo_json", {}):
@ -624,8 +624,11 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
{"count_real": d_count_real, "count_ok": d_count_ok})) {"count_real": d_count_real, "count_ok": d_count_ok}))
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
wpr_ = mlogbwin.wpr wpr_ = mlogbwin.wpr
for i in range(div_number): if div_number == 1:
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f'{wpr_.number}-{i+1}', defaults={"mlogbw_from": mlogbwin}) Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin})
else:
for i in range(div_number):
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f'{wpr_.number}-{i+1}', defaults={"mlogbw_from": mlogbwin})
elif material_in.tracking == Material.MA_TRACKING_BATCH and material_out.tracking == Material.MA_TRACKING_BATCH: elif material_in.tracking == Material.MA_TRACKING_BATCH and material_out.tracking == Material.MA_TRACKING_BATCH:
d_count_real = mlogbin.count_use * div_number d_count_real = mlogbin.count_use * div_number
d_count_ok = d_count_real d_count_ok = d_count_real

View File

@ -1,3 +1,37 @@
## 2.6.2025042311
- feat: 新增功能
- 切分工序切分数量支持1 [caoqianming]
- mlog增加team字段 [caoqianming]
- mlog的need_inout逻辑 [caoqianming]
- 批次关系链时创建新批次支持使用已有批次号 [caoqianming]
- mlogb添加test_user/need_inout字段用于处理抽检逻辑 [caoqianming]
- 工艺步骤返回组合而成的name [caoqianming]
- 校验只有合并时才能提供新批次号 [caoqianming]
- 交接查询可查询子批次号 [caoqianming]
- 日志完善负值校验 [caoqianming]
- 批号追加工段标识 [caoqianming]
- base增加PositiveDecimalField [caoqianming]
- 改版交接需要触发统计数量 [caoqianming]
- 物料统计数量接口 [caoqianming]
- update_material_count时更新组合件数量 [caoqianming]
- 采购和其他入库可入已有批次 [caoqianming]
- 交接需要校验物料是否一致 [caoqianming]
- 返工可选择不合格品/根据工艺路线决定返工后是合格不合格还是返修完成 [caoqianming]
- 检验项和缺陷项删除的时候同步删除qct [caoqianming]
- fmlog添加工艺步骤字段及相应返回数据 [caoqianming]
- fix: 问题修复
- mlog的need_inout逻辑 [caoqianming]
- count_working获取逻辑优化 [caoqianming]
- mlogbin解决负值校验存在的bug [caoqianming]
- 日志和交接记录操作数正值校验 [caoqianming]
- inm校验非正数 [caoqianming]
- 其他入库时batchst.g_create传参错误 [caoqianming]
- : 交接记录提交时校验count>0 [caoqianming]
- 正常交接后的物料状态不变 [caoqianming]
- 存在fmlog时将route带给mlog并进行物料校验 [caoqianming]
- do_in保证production_dept赋值 [caoqianming]
- 订单检索条件错误 [caoqianming]
- fmlog填写mtask或route即可 [caoqianming]
## 2.6.2025041613 ## 2.6.2025041613
- feat: 新增功能 - feat: 新增功能
- 添加count_json_from字段及相应逻辑 [caoqianming] - 添加count_json_from字段及相应逻辑 [caoqianming]

12
config/e.conf.py Normal file
View File

@ -0,0 +1,12 @@
SECRET_KEY = 'xx'
DEBUG = False
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'xx',
'USER': 'postgres',
'PASSWORD': 'xx',
'HOST': 'xx',
'PORT': '5432',
}
}

View File

@ -1,5 +1,5 @@
import os import os
from . import conf from config import conf
from celery import Celery from celery import Celery
from celery.app.control import Control, Inspect from celery.app.control import Control, Inspect

View File

@ -14,7 +14,7 @@ from datetime import datetime, timedelta
import os import os
import json import json
import sys import sys
from .conf import * from config.conf import *
from django.core.cache import cache from django.core.cache import cache
import logging import logging
@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
SYS_NAME = '星途工厂综合管理系统' SYS_NAME = '星途工厂综合管理系统'
SYS_VERSION = '2.6.2025041613' SYS_VERSION = '2.6.2025042311'
X_FRAME_OPTIONS = 'SAMEORIGIN' X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition # Application definition
@ -369,7 +369,7 @@ LOGGING = {
} }
##### 加载客户可自定义配置并提供操作方法 ##### ##### 加载客户可自定义配置并提供操作方法 #####
SYS_JSON_PATH = os.path.join(BASE_DIR, 'server/conf.json') SYS_JSON_PATH = os.path.join(BASE_DIR, 'config/conf.json')
def get_sysconfig(key='', default='raise_error', reload=False): def get_sysconfig(key='', default='raise_error', reload=False):
"""获取系统配置可指定key字符串 """获取系统配置可指定key字符串