diff --git a/.gitignore b/.gitignore index 8bfbf25b..fcc1f04f 100755 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ db.sqlite3 server/conf*.py server/conf.ini server/conf*.json +config/conf*.py +config/conf*.json sh/* temp/* nohup.out diff --git a/apps/enm/views.py b/apps/enm/views.py index 77db0ebc..2411a9db 100644 --- a/apps/enm/views.py +++ b/apps/enm/views.py @@ -151,11 +151,20 @@ class MpLogxViewSet(CustomListModelMixin, CustomGenericViewSet): serializer_class = MpLogxSerializer filterset_fields = { "timex": ["exact", "gte", "lte", "year", "month", "day"], - "mpoint": ["exact"], + "mpoint": ["exact", "in"], + "mpoint__ep_monitored": ["exact"] } ordering_fields = ["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): """ diff --git a/apps/ichat/models.py b/apps/ichat/models.py index 800ea968..daf6944c 100644 --- a/apps/ichat/models.py +++ b/apps/ichat/models.py @@ -1,9 +1,17 @@ from django.db import models -from apps.system.models import CommonADModel +from apps.system.models import CommonADModel, BaseModel # Create your models here. class Conversation(CommonADModel): """ TN: 对话 """ - title = models.CharField(max_length=200, default='新对话',verbose_name='对话标题') \ No newline at end of file + 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") diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 8429b627..015c5d9c 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -186,8 +186,8 @@ class RouteSerializer(CustomModelSerializer): raise ParseError('未提供操作工序') if process.parent is not None: raise ParseError('操作工序不可为子工序') - if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) <= 1: - raise ParseError('切分数量必须大于1') + if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) < 1: + raise ParseError('切分数量必须大于等于1') return super().validate(attrs) @classmethod diff --git a/apps/qm/migrations/0050_alter_defect_cate.py b/apps/qm/migrations/0050_alter_defect_cate.py new file mode 100644 index 00000000..d1216389 --- /dev/null +++ b/apps/qm/migrations/0050_alter_defect_cate.py @@ -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='分类'), + ), + ] diff --git a/apps/qm/models.py b/apps/qm/models.py index 65e40b00..9ab4985d 100644 --- a/apps/qm/models.py +++ b/apps/qm/models.py @@ -14,9 +14,10 @@ class Defect(CommonAModel): DEFECT_OK = 10 DEFECT_OK_B = 20 DEFECT_NOTOK = 30 + cate_list = ["尺寸", "外观", "内质", "性能"] name = models.CharField(max_length=50, verbose_name="名称") code = models.CharField(max_length=50, verbose_name="标识", null=True, blank=True) - cate = models.CharField(max_length=50, verbose_name="分类", choices=(("尺寸", "尺寸"), ("外观", "外观"), ("内质", "内质"))) + cate = models.CharField(max_length=50, verbose_name="分类", help_text=str(cate_list)) okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类", choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")), default=DEFECT_NOTOK) diff --git a/apps/qm/serializers.py b/apps/qm/serializers.py index a1dbeff4..872f1826 100644 --- a/apps/qm/serializers.py +++ b/apps/qm/serializers.py @@ -15,6 +15,12 @@ class DefectSerializer(CustomModelSerializer): model = Defect fields = '__all__' 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): # code = validated_data["code"] diff --git a/apps/wpm/migrations/0113_mlog_team.py b/apps/wpm/migrations/0113_mlog_team.py new file mode 100644 index 00000000..9e483838 --- /dev/null +++ b/apps/wpm/migrations/0113_mlog_team.py @@ -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='班组'), + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 82c39983..77d68057 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -252,6 +252,7 @@ class Mlog(CommonADModel): count_n_qt = models.DecimalField('其他', default=0, max_digits=11, decimal_places=1) 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( User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True) # 成型人 handle_user_2 = models.ForeignKey( @@ -613,7 +614,7 @@ class BatchSt(BaseModel): class Meta: unique_together = [("batch", "version")] - + @classmethod 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: return cls.objects.get_or_create(batch=batch) 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"] + 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") - 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 @classmethod diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 6f9ac81b..ba3fab46 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -234,6 +234,7 @@ class MlogbSerializer(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) supplier_name = serializers.CharField(source='supplier.name', read_only=True) # routepack_name = serializers.CharField(source='route.routepack.name', read_only=True) @@ -291,6 +292,7 @@ class MlogbDetailSerializer(CustomModelSerializer): fields = '__all__' 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) supplier_name = serializers.CharField(source='supplier.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) class Meta: 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): if attrs["count_use"] < 0 or attrs["count_pn_jgqbl"] < 0: @@ -858,6 +860,16 @@ class MlogbOutUpdateSerializer(CustomModelSerializer): mlogbdefect = validated_data.pop("mlogbdefect", None) with transaction.atomic(): 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: MlogbDefect.objects.filter(mlogb=ins).delete() mlogb_defect_objects = [ diff --git a/apps/wpm/services.py b/apps/wpm/services.py index afaeb86a..c1061103 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -190,6 +190,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): m_ins_list = [] m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False) 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()] for item in m_ins: 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) stored_notok = 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()] if need_store_notok: 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) 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()] @@ -497,6 +500,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]): into_wm_mgroup = process.into_wm_mgroup m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False) if m_ins.exists(): + m_ins = m_ins.filter(need_inout=True) for mi in m_ins.all(): m_ins_list.append((mi.material_in, mi.batch, mi.count_use, None, mi)) for item in m_ins: diff --git a/apps/wpm/services_2.py b/apps/wpm/services_2.py index 9ff9241f..d2a25062 100644 --- a/apps/wpm/services_2.py +++ b/apps/wpm/services_2.py @@ -5,7 +5,7 @@ from apps.inm.models import MIOItem from apps.qm.models import FtestWork from django.utils import timezone from datetime import datetime -from server.conf import BASE_PROJECT_CODE +from django.conf import settings import json from apps.utils.tools import MyJSONEncoder import decimal @@ -17,6 +17,7 @@ def get_alldata_with_batch_and_store(batch: str): 获取某个批次的整体生产数据并保存 """ need_update = False + BASE_PROJECT_CODE = getattr(settings, "BASE_PROJECT_CODE", None) if BASE_PROJECT_CODE == "gzerp": need_update = True last_time, data = get_alldata_with_batch(batch) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index eec9d259..e3525e24 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -174,13 +174,13 @@ class MlogViewSet(CustomModelViewSet): select_related_fields = ['create_by', 'update_by', 'mtask', 'mtaskb', 'handle_user', 'handle_user_2', 'equipment', 'mgroup__belong_dept', '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', 'material_outs', 'b_mlog', 'equipments'] filterset_class = MlogFilter search_fields = ['material_in__name', '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): 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})) for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): wpr_ = mlogbwin.wpr - for i in range(div_number): - Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f'{wpr_.number}-{i+1}', defaults={"mlogbw_from": mlogbwin}) + if div_number == 1: + 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: d_count_real = mlogbin.count_use * div_number d_count_ok = d_count_real diff --git a/changelog.md b/changelog.md index 2862038b..9b22aba2 100644 --- a/changelog.md +++ b/changelog.md @@ -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 - feat: 新增功能 - 添加count_json_from字段及相应逻辑 [caoqianming] diff --git a/config/e.conf.py b/config/e.conf.py new file mode 100644 index 00000000..b6175ee6 --- /dev/null +++ b/config/e.conf.py @@ -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', + } +} diff --git a/server/celery.py b/server/celery.py index 4081020e..96f71eef 100755 --- a/server/celery.py +++ b/server/celery.py @@ -1,5 +1,5 @@ import os -from . import conf +from config import conf from celery import Celery from celery.app.control import Control, Inspect diff --git a/server/settings.py b/server/settings.py index b4558da6..cdfee897 100755 --- a/server/settings.py +++ b/server/settings.py @@ -14,7 +14,7 @@ from datetime import datetime, timedelta import os import json import sys -from .conf import * +from config.conf import * from django.core.cache import cache import logging @@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) ALLOWED_HOSTS = ['*'] SYS_NAME = '星途工厂综合管理系统' -SYS_VERSION = '2.6.2025041613' +SYS_VERSION = '2.6.2025042311' X_FRAME_OPTIONS = 'SAMEORIGIN' # 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): """获取系统配置可指定key字符串