diff --git a/apps/ofm/migrations/0016_auto_20250929_1551.py b/apps/ofm/migrations/0016_auto_20250929_1551.py new file mode 100644 index 00000000..b8f001dd --- /dev/null +++ b/apps/ofm/migrations/0016_auto_20250929_1551.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.12 on 2025-09-29 07:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('system', '0006_auto_20241213_1249'), + ('ofm', '0015_alter_vehicle_end_km'), + ] + + operations = [ + migrations.AddField( + model_name='mroombooking', + name='belong_dept', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_belong_dept', to='system.dept', verbose_name='所属部门'), + ), + migrations.AddField( + model_name='mroombooking', + name='key_participants', + field=models.TextField(blank=True, null=True, verbose_name='主要参会领导'), + ), + migrations.AddField( + model_name='mroombooking', + name='note', + field=models.TextField(blank=True, null=True, verbose_name='备注'), + ), + migrations.AddField( + model_name='mroombooking', + name='participant_count', + field=models.PositiveIntegerField(default=0, verbose_name='参会人数'), + ), + ] diff --git a/apps/ofm/models.py b/apps/ofm/models.py index 9455783b..91a0a422 100644 --- a/apps/ofm/models.py +++ b/apps/ofm/models.py @@ -26,11 +26,15 @@ class Mroom(CommonADModel): location = models.CharField('位置', max_length=100) capacity = models.PositiveIntegerField('容纳人数') -class MroomBooking(CommonADModel): +class MroomBooking(CommonBDModel): """TN: 会议室预定信息""" + # belong_dept 是预定部门 title = models.CharField('会议主题', max_length=100) ticket = models.ForeignKey('wf.ticket', verbose_name='关联会议室', on_delete=models.SET_NULL, related_name='mrooms_ticket', null=True, blank=True, db_constraint=False) + note = models.TextField('备注', null=True, blank=True) + participant_count = models.PositiveIntegerField('参会人数', default=0) + key_participants = models.TextField("主要参会领导", null=True, blank=True) class MroomSlot(BaseModel): diff --git a/apps/ofm/serializers.py b/apps/ofm/serializers.py index 1ff0f3cc..c9aeee51 100644 --- a/apps/ofm/serializers.py +++ b/apps/ofm/serializers.py @@ -19,11 +19,14 @@ class MroomBookingSerializer(CustomModelSerializer): mdate = serializers.DateField(write_only=True, label="预订日期") slots = serializers.ListField(child=serializers.IntegerField(), write_only=True, label="时段索引") create_by_name = serializers.CharField(source='create_by.username', read_only=True) + create_by_phone = serializers.CharField(source='create_by.phone', read_only=True) + belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) ticket_ = TicketSimpleSerializer(source='ticket', read_only=True) class Meta: model = MroomBooking fields = '__all__' read_only_fields = EXCLUDE_FIELDS + extra_kwargs = {'belong_dept': {'required': True}} def create(self, validated_data): mroom = validated_data.pop('mroom') diff --git a/apps/ofm/views.py b/apps/ofm/views.py index f137b634..86b7b785 100644 --- a/apps/ofm/views.py +++ b/apps/ofm/views.py @@ -28,7 +28,7 @@ class MroomBookingViewSet(CustomModelViewSet): """ queryset = MroomBooking.objects.all() serializer_class = MroomBookingSerializer - select_related_fields = ["create_by", "ticket"] + select_related_fields = ["create_by", "ticket", "belong_dept"] filterset_class = MroomBookingFilterset def add_info_for_list(self, data): @@ -66,7 +66,7 @@ class MroomBookingViewSet(CustomModelViewSet): start_time = self._slot_to_time(info["current_slots"][0]) end_time = self._slot_to_time(info["current_slots"][-1] + 1) info["time_ranges"].append(f"{start_time}-{end_time}") - info.pop("current_slots") # 清理临时数据 + info["slots"] = info.pop("current_slots") # 清理临时数据 for item in data: item.update(booking_info.get(item["id"], {})) diff --git a/apps/utils/mixins.py b/apps/utils/mixins.py index 94703573..6e66a806 100755 --- a/apps/utils/mixins.py +++ b/apps/utils/mixins.py @@ -195,6 +195,8 @@ class CustomRetrieveModelMixin(RetrieveModelMixin): 给dict返回数据添加额外信息 """ + if hasattr(self, 'add_info_for_list'): + return self.add_info_for_list([data])[0] return data class CustomListModelMixin(ListModelMixin): @@ -243,6 +245,8 @@ class ComplexQueryMixin: vdata = sr.validated_data queryset = self.get_queryset() querys = vdata.get('querys', []) + annotate_field_list = vdata.get('annotate_field_list', []) + if not querys: new_qs = queryset else: @@ -264,17 +268,29 @@ class ComplexQueryMixin: new_qs = new_qs | one_qs except Exception as e: raise ParseError(str(e)) + + if annotate_field_list: + annotate_dict = getattr(self, "annotate_dict", {}) + if annotate_dict: + filtered_annotate_dict = { key: annotate_dict[key] for key in annotate_field_list if key in annotate_dict} + new_qs = new_qs.annotate(**filtered_annotate_dict) + ordering = vdata.get('ordering', None) if not ordering: ordering = getattr(self, 'ordering', None) if isinstance(ordering, str): + ordering = ordering.replace('\n', '').replace(' ', '') ordering = ordering.split(',') + order_fields = [] if ordering: for item in ordering: if item.startswith('-'): - new_qs = new_qs.order_by(F(item[1:]).desc(nulls_last=True)) + # JSONField 排序只能用字符串,不要 F + order_fields.append(F(item[1:]).desc(nulls_last=True) if '__' not in item else item) else: - new_qs = new_qs.order_by(item) + order_fields.append(F(item).asc(nulls_last=True) if '__' not in item else item) + new_qs = new_qs.order_by(*order_fields) + page = self.paginate_queryset(new_qs) if page is not None: serializer = self.get_serializer(page, many=True) diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 74a23cf2..263ec5f2 100755 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -85,3 +85,4 @@ class ComplexSerializer(serializers.Serializer): ordering = serializers.CharField(required=False) querys = serializers.ListField(child=QuerySerializer( many=True), label="查询列表", required=False) + annotate_field_list = serializers.ListField(child=serializers.CharField(), label="RawSQL字段列表", required=False) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index c81944ca..c601d2a3 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -505,7 +505,7 @@ class HandoverViewSet(CustomModelViewSet): Prefetch('b_handover', queryset=Handoverb.objects.select_related('wm__defect')) ] - def perform_destroy(self, instance): + def perform_destroy(self, instance:Handover): user = self.request.user if instance.submit_time is not None: raise ParseError('该交接记录已提交不可删除') @@ -570,6 +570,9 @@ class HandoverViewSet(CustomModelViewSet): 交接记录撤回 """ ins: Handover = self.get_object() + if ins.new_batch: # 如果是合批 + if Handoverb.objects.filter(batch=ins.new_batch, handover__submit_time__isnull=True).exists(): + raise ParseError("该合批存在未提交的交接记录,不可撤回") if ins.submit_time: handover_revert(ins, handler=request.user) return Response() diff --git a/apps/wpmw/views.py b/apps/wpmw/views.py index 149d1759..3c9a9a59 100644 --- a/apps/wpmw/views.py +++ b/apps/wpmw/views.py @@ -10,6 +10,7 @@ from rest_framework.exceptions import ParseError from django.db import transaction from apps.wpmw.filters import WprFilter from apps.utils.sql import query_one_dict +from django.db.models.expressions import RawSQL class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet): @@ -17,6 +18,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu 动态产品 """ + perms_map = {"get": "*"} select_related_fields = ["wm", "mb", "material", "wm__material_ofrom"] prefetch_related_fields = ["defects"] @@ -27,6 +29,10 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu ordering = ["number", "create_time"] ordering_fields = ["number", "create_time", "update_time"] search_fields = ["number", "material__name", "material__model", "material__specification", "number_out"] + annotate_dict = { + "number_prefix": RawSQL("regexp_replace(number, '(\\d+)$', '')", []), + "number_suffix": RawSQL("COALESCE(NULLIF(regexp_replace(number, '.*?(\\d+)$', '\\1'), ''), '0')::bigint", []), + } def filter_queryset(self, queryset): qs = super().filter_queryset(queryset) @@ -36,7 +42,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu qs.exclude(mb=None, wm=None) return qs - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WprNewSerializer) + @action(methods=["post"], detail=False, perms_map={"post": "*"}, serializer_class=WprNewSerializer) def new_number(self, request, *args, **kwargs): """获取新的编号""" data = request.data @@ -47,16 +53,17 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu wpr_last = wps_qs.order_by("number").last() count = wps_qs.count() last_number = wpr_last.number if count > 0 else None - last_number_count = int(last_number.split("-")[-1].lstrip('0')) + last_number_count = int(last_number.split("-")[-1].lstrip("0")) mat = Material.objects.get(id=material_start) - return Response({"count": count, "last_number": last_number, "material_model":mat.model, "last_number_count": last_number_count}) - - @action(methods=['get'], detail=False, perms_map={'get': '*'}) + return Response({"count": count, "last_number": last_number, "material_model": mat.model, "last_number_count": last_number_count}) + + @action(methods=["get"], detail=False, perms_map={"get": "*"}) def number_out_last(self, request, *args, **kwargs): """获取最新的出库对外编号 - + 获取最新的出库对外编号(get请求传入prefix参数和with_unsubmit参数,with_unsubmit默认为yes,表示是否包含未出库的记录,prefix为前缀,如'WPR-2023-0)""" from apps.inm.models import MIOItemw + prefix = request.query_params.get("prefix", None) if not prefix: raise ParseError("请传入前缀参数") @@ -67,7 +74,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu SELECT id, number_out FROM wpmw_wpr WHERE number_out ~ %s order by number_out desc limit 1 """ - pattern = f'^{prefix}[0-9]+$' + pattern = f"^{prefix}[0-9]+$" number_outs = [] wpr_qs_last = query_one_dict(query, [pattern]) if wpr_qs_last: @@ -87,7 +94,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu if number_outs: number_outs.sort() number_out_last = number_outs[-1] - number_int_last = number_out_last.lstrip(prefix).lstrip('0') + number_int_last = number_out_last.lstrip(prefix).lstrip("0") try: number_int_last = int(number_int_last) except ValueError: @@ -95,9 +102,8 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu return Response({"number_out_last": number_out_last, "number_out_int_last": int(number_int_last)}) else: return Response({"number_out_last": None}) - - @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer) + @action(methods=["post"], detail=False, perms_map={"post": "*"}, serializer_class=WproutListSerializer) @transaction.atomic def assgin_number_out(self, request, *args, **kwargs): """分配出库对外编号 @@ -108,7 +114,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu vdata = sr.validated_data items = vdata["items"] number_outs = [i["number_out"] for i in items] - existing_numbers = Wpr.objects.filter(number_out__in=number_outs, number_out__isnull=False).values_list('number_out', flat=True) + existing_numbers = Wpr.objects.filter(number_out__in=number_outs, number_out__isnull=False).values_list("number_out", flat=True) if existing_numbers.exists(): used_numbers = list(existing_numbers) raise ParseError(f"以下对外编号已被使用: {used_numbers[0]} 共{len(used_numbers)}个") @@ -116,4 +122,4 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu wpr = Wpr.objects.get(id=i["id"]) wpr.number_out = i["number_out"] wpr.save() - return Response() \ No newline at end of file + return Response() diff --git a/changelog.md b/changelog.md index 93ec2e1b..70a22eba 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,32 @@ +## 2.8.2025092816 +- feat: 新增功能 + - handover revert撤回时做校验 [caoqianming] + - ofm-models 修改车辆表的字段信息 [zty] + - iofm-serializer 增加ticket_ [zty] + - 会议室预定修改 [caoqianming] + - p_create_after 优化 [caoqianming] + - mlog quick增加wprs_in传参 [caoqianming] + - 车间领料时完善提示添加物料名 [caoqianming] + - ofm-修改会议室信息 [zty] + - 改版需提供新批次号 [caoqianming] + - ofm 修改publicity 的model [zty] + - 修改ofm-services.py [zty] + - base send_sms auto_log send_mail使用False [caoqianming] + - 优化bind_routepack [caoqianming] + - base 优化wf通知发送 [caoqianming] + - routepack_ticket_change 变为创建中状态 [caoqianming] + - route增加查询条件以支持获取后续工段的信息 [caoqianming] + - 导入物料优化一下 [caoqianming] + - 优化的mplogxview [caoqianming] + - base sql querydict可传入是否格式化时间参数 [caoqianming] + - base workflow list 添加并返回view_path [caoqianming] + - 优化handover list接口 [caoqianming] + - 玻纤拉丝采集问题 [caoqianming] + - base system和wf优化事务处理 [caoqianming] + - 优化cd.py [caoqianming] + - wpr list 返回process_name [caoqianming] +- fix: 问题修复 + - mroombooking 创建时未create_by [caoqianming] ## 2.8.2025091616 - feat: 新增功能 - mioitem处理事务处理 [caoqianming] diff --git a/server/settings.py b/server/settings.py index 2a36eb12..35f1295c 100755 --- a/server/settings.py +++ b/server/settings.py @@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) ALLOWED_HOSTS = ['*'] SYS_NAME = '星途工厂综合管理系统' -SYS_VERSION = '2.8.2025091616' +SYS_VERSION = '2.8.2025092816' X_FRAME_OPTIONS = 'SAMEORIGIN' # Application definition diff --git a/server/urls.py b/server/urls.py index 56fcf7f9..211b2007 100755 --- a/server/urls.py +++ b/server/urls.py @@ -21,10 +21,11 @@ from drf_yasg import openapi from drf_yasg.views import get_schema_view from rest_framework.documentation import include_docs_urls from django.views.generic import TemplateView +from server.settings import get_sysconfig schema_view = get_schema_view( openapi.Info( - title=settings.SYS_NAME, + title=f'{settings.SYS_NAME}--{get_sysconfig("base.base_name", "demo")}', default_version=settings.SYS_VERSION, contact=openapi.Contact(email="caoqianming@foxmail.com"), license=openapi.License(name="MIT License"),