This commit is contained in:
zty 2025-09-30 09:58:56 +08:00
commit d21c1dc55d
11 changed files with 118 additions and 20 deletions

View File

@ -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='参会人数'),
),
]

View File

@ -26,11 +26,15 @@ class Mroom(CommonADModel):
location = models.CharField('位置', max_length=100) location = models.CharField('位置', max_length=100)
capacity = models.PositiveIntegerField('容纳人数') capacity = models.PositiveIntegerField('容纳人数')
class MroomBooking(CommonADModel): class MroomBooking(CommonBDModel):
"""TN: 会议室预定信息""" """TN: 会议室预定信息"""
# belong_dept 是预定部门
title = models.CharField('会议主题', max_length=100) title = models.CharField('会议主题', max_length=100)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联会议室', ticket = models.ForeignKey('wf.ticket', verbose_name='关联会议室',
on_delete=models.SET_NULL, related_name='mrooms_ticket', null=True, blank=True, db_constraint=False) 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): class MroomSlot(BaseModel):

View File

@ -19,11 +19,14 @@ class MroomBookingSerializer(CustomModelSerializer):
mdate = serializers.DateField(write_only=True, label="预订日期") mdate = serializers.DateField(write_only=True, label="预订日期")
slots = serializers.ListField(child=serializers.IntegerField(), 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_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) ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta: class Meta:
model = MroomBooking model = MroomBooking
fields = '__all__' fields = '__all__'
read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
extra_kwargs = {'belong_dept': {'required': True}}
def create(self, validated_data): def create(self, validated_data):
mroom = validated_data.pop('mroom') mroom = validated_data.pop('mroom')

View File

@ -28,7 +28,7 @@ class MroomBookingViewSet(CustomModelViewSet):
""" """
queryset = MroomBooking.objects.all() queryset = MroomBooking.objects.all()
serializer_class = MroomBookingSerializer serializer_class = MroomBookingSerializer
select_related_fields = ["create_by", "ticket"] select_related_fields = ["create_by", "ticket", "belong_dept"]
filterset_class = MroomBookingFilterset filterset_class = MroomBookingFilterset
def add_info_for_list(self, data): def add_info_for_list(self, data):
@ -66,7 +66,7 @@ class MroomBookingViewSet(CustomModelViewSet):
start_time = self._slot_to_time(info["current_slots"][0]) start_time = self._slot_to_time(info["current_slots"][0])
end_time = self._slot_to_time(info["current_slots"][-1] + 1) end_time = self._slot_to_time(info["current_slots"][-1] + 1)
info["time_ranges"].append(f"{start_time}-{end_time}") info["time_ranges"].append(f"{start_time}-{end_time}")
info.pop("current_slots") # 清理临时数据 info["slots"] = info.pop("current_slots") # 清理临时数据
for item in data: for item in data:
item.update(booking_info.get(item["id"], {})) item.update(booking_info.get(item["id"], {}))

View File

@ -195,6 +195,8 @@ class CustomRetrieveModelMixin(RetrieveModelMixin):
给dict返回数据添加额外信息 给dict返回数据添加额外信息
""" """
if hasattr(self, 'add_info_for_list'):
return self.add_info_for_list([data])[0]
return data return data
class CustomListModelMixin(ListModelMixin): class CustomListModelMixin(ListModelMixin):
@ -243,6 +245,8 @@ class ComplexQueryMixin:
vdata = sr.validated_data vdata = sr.validated_data
queryset = self.get_queryset() queryset = self.get_queryset()
querys = vdata.get('querys', []) querys = vdata.get('querys', [])
annotate_field_list = vdata.get('annotate_field_list', [])
if not querys: if not querys:
new_qs = queryset new_qs = queryset
else: else:
@ -264,17 +268,29 @@ class ComplexQueryMixin:
new_qs = new_qs | one_qs new_qs = new_qs | one_qs
except Exception as e: except Exception as e:
raise ParseError(str(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) ordering = vdata.get('ordering', None)
if not ordering: if not ordering:
ordering = getattr(self, 'ordering', None) ordering = getattr(self, 'ordering', None)
if isinstance(ordering, str): if isinstance(ordering, str):
ordering = ordering.replace('\n', '').replace(' ', '')
ordering = ordering.split(',') ordering = ordering.split(',')
order_fields = []
if ordering: if ordering:
for item in ordering: for item in ordering:
if item.startswith('-'): 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: 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) page = self.paginate_queryset(new_qs)
if page is not None: if page is not None:
serializer = self.get_serializer(page, many=True) serializer = self.get_serializer(page, many=True)

View File

@ -85,3 +85,4 @@ class ComplexSerializer(serializers.Serializer):
ordering = serializers.CharField(required=False) ordering = serializers.CharField(required=False)
querys = serializers.ListField(child=QuerySerializer( querys = serializers.ListField(child=QuerySerializer(
many=True), label="查询列表", required=False) many=True), label="查询列表", required=False)
annotate_field_list = serializers.ListField(child=serializers.CharField(), label="RawSQL字段列表", required=False)

View File

@ -505,7 +505,7 @@ class HandoverViewSet(CustomModelViewSet):
Prefetch('b_handover', queryset=Handoverb.objects.select_related('wm__defect')) 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 user = self.request.user
if instance.submit_time is not None: if instance.submit_time is not None:
raise ParseError('该交接记录已提交不可删除') raise ParseError('该交接记录已提交不可删除')
@ -570,6 +570,9 @@ class HandoverViewSet(CustomModelViewSet):
交接记录撤回 交接记录撤回
""" """
ins: Handover = self.get_object() 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: if ins.submit_time:
handover_revert(ins, handler=request.user) handover_revert(ins, handler=request.user)
return Response() return Response()

View File

@ -10,6 +10,7 @@ from rest_framework.exceptions import ParseError
from django.db import transaction from django.db import transaction
from apps.wpmw.filters import WprFilter from apps.wpmw.filters import WprFilter
from apps.utils.sql import query_one_dict from apps.utils.sql import query_one_dict
from django.db.models.expressions import RawSQL
class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet): class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet):
@ -17,6 +18,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
动态产品 动态产品
""" """
perms_map = {"get": "*"} perms_map = {"get": "*"}
select_related_fields = ["wm", "mb", "material", "wm__material_ofrom"] select_related_fields = ["wm", "mb", "material", "wm__material_ofrom"]
prefetch_related_fields = ["defects"] prefetch_related_fields = ["defects"]
@ -27,6 +29,10 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
ordering = ["number", "create_time"] ordering = ["number", "create_time"]
ordering_fields = ["number", "create_time", "update_time"] ordering_fields = ["number", "create_time", "update_time"]
search_fields = ["number", "material__name", "material__model", "material__specification", "number_out"] 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): def filter_queryset(self, queryset):
qs = super().filter_queryset(queryset) qs = super().filter_queryset(queryset)
@ -36,7 +42,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
qs.exclude(mb=None, wm=None) qs.exclude(mb=None, wm=None)
return qs 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): def new_number(self, request, *args, **kwargs):
"""获取新的编号""" """获取新的编号"""
data = request.data data = request.data
@ -47,16 +53,17 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
wpr_last = wps_qs.order_by("number").last() wpr_last = wps_qs.order_by("number").last()
count = wps_qs.count() count = wps_qs.count()
last_number = wpr_last.number if count > 0 else None 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) 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}) 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': '*'}) @action(methods=["get"], detail=False, perms_map={"get": "*"})
def number_out_last(self, request, *args, **kwargs): def number_out_last(self, request, *args, **kwargs):
"""获取最新的出库对外编号 """获取最新的出库对外编号
获取最新的出库对外编号(get请求传入prefix参数和with_unsubmit参数with_unsubmit默认为yes表示是否包含未出库的记录prefix为前缀'WPR-2023-0)""" 获取最新的出库对外编号(get请求传入prefix参数和with_unsubmit参数with_unsubmit默认为yes表示是否包含未出库的记录prefix为前缀'WPR-2023-0)"""
from apps.inm.models import MIOItemw from apps.inm.models import MIOItemw
prefix = request.query_params.get("prefix", None) prefix = request.query_params.get("prefix", None)
if not prefix: if not prefix:
raise ParseError("请传入前缀参数") raise ParseError("请传入前缀参数")
@ -67,7 +74,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
SELECT id, number_out FROM wpmw_wpr SELECT id, number_out FROM wpmw_wpr
WHERE number_out ~ %s order by number_out desc limit 1 WHERE number_out ~ %s order by number_out desc limit 1
""" """
pattern = f'^{prefix}[0-9]+$' pattern = f"^{prefix}[0-9]+$"
number_outs = [] number_outs = []
wpr_qs_last = query_one_dict(query, [pattern]) wpr_qs_last = query_one_dict(query, [pattern])
if wpr_qs_last: if wpr_qs_last:
@ -87,7 +94,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
if number_outs: if number_outs:
number_outs.sort() number_outs.sort()
number_out_last = number_outs[-1] 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: try:
number_int_last = int(number_int_last) number_int_last = int(number_int_last)
except ValueError: 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)}) return Response({"number_out_last": number_out_last, "number_out_int_last": int(number_int_last)})
else: else:
return Response({"number_out_last": None}) 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 @transaction.atomic
def assgin_number_out(self, request, *args, **kwargs): def assgin_number_out(self, request, *args, **kwargs):
"""分配出库对外编号 """分配出库对外编号
@ -108,7 +114,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
vdata = sr.validated_data vdata = sr.validated_data
items = vdata["items"] items = vdata["items"]
number_outs = [i["number_out"] for i in 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(): if existing_numbers.exists():
used_numbers = list(existing_numbers) used_numbers = list(existing_numbers)
raise ParseError(f"以下对外编号已被使用: {used_numbers[0]}{len(used_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 = Wpr.objects.get(id=i["id"])
wpr.number_out = i["number_out"] wpr.number_out = i["number_out"]
wpr.save() wpr.save()
return Response() return Response()

View File

@ -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 ## 2.8.2025091616
- feat: 新增功能 - feat: 新增功能
- mioitem处理事务处理 [caoqianming] - mioitem处理事务处理 [caoqianming]

View File

@ -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.8.2025091616' SYS_VERSION = '2.8.2025092816'
X_FRAME_OPTIONS = 'SAMEORIGIN' X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition # Application definition

View File

@ -21,10 +21,11 @@ from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from rest_framework.documentation import include_docs_urls from rest_framework.documentation import include_docs_urls
from django.views.generic import TemplateView from django.views.generic import TemplateView
from server.settings import get_sysconfig
schema_view = get_schema_view( schema_view = get_schema_view(
openapi.Info( openapi.Info(
title=settings.SYS_NAME, title=f'{settings.SYS_NAME}--{get_sysconfig("base.base_name", "demo")}',
default_version=settings.SYS_VERSION, default_version=settings.SYS_VERSION,
contact=openapi.Contact(email="caoqianming@foxmail.com"), contact=openapi.Contact(email="caoqianming@foxmail.com"),
license=openapi.License(name="MIT License"), license=openapi.License(name="MIT License"),