feat(wpm): recommend equipment for production logs
This commit is contained in:
parent
596f187d3d
commit
d1693799b9
|
|
@ -41,6 +41,18 @@ WM_STATE_NAMES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MlogEquipmentOptionSerializer(serializers.ModelSerializer):
|
||||||
|
mgroup_name = serializers.CharField(source="mgroup.name", read_only=True)
|
||||||
|
full_name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_full_name(self, obj):
|
||||||
|
return f"{obj.number}|{obj.name}|{obj.model}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Equipment
|
||||||
|
fields = ["id", "name", "number", "model", "mgroup_name", "full_name"]
|
||||||
|
|
||||||
|
|
||||||
class OtherLogSerializer(CustomModelSerializer):
|
class OtherLogSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OtherLog
|
model = OtherLog
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
|
|
@ -27,6 +28,44 @@ from django.db.models import F
|
||||||
|
|
||||||
myLogger = logging.getLogger('log')
|
myLogger = logging.getLogger('log')
|
||||||
|
|
||||||
|
RECENT_EQUIPMENT_LOG_LIMIT = 50
|
||||||
|
|
||||||
|
|
||||||
|
def get_recent_mgroup_equipment_ids(
|
||||||
|
mgroup_id, log_limit=RECENT_EQUIPMENT_LOG_LIMIT
|
||||||
|
):
|
||||||
|
"""按日志时间倒序返回工段最近使用过的设备 ID,空值和重复值忽略。"""
|
||||||
|
recent_logs = list(
|
||||||
|
Mlog.objects.filter(mgroup_id=mgroup_id)
|
||||||
|
.order_by("-create_time", "-id")
|
||||||
|
.values_list("id", "equipment_id", "equipment_2_id")[:log_limit]
|
||||||
|
)
|
||||||
|
if not recent_logs:
|
||||||
|
return []
|
||||||
|
|
||||||
|
log_ids = [log_id for log_id, _, _ in recent_logs]
|
||||||
|
multiple_equipment_ids = defaultdict(list)
|
||||||
|
for log_id, equipment_id in (
|
||||||
|
Mlog.equipments.through.objects.filter(mlog_id__in=log_ids)
|
||||||
|
.order_by("id")
|
||||||
|
.values_list("mlog_id", "equipment_id")
|
||||||
|
):
|
||||||
|
multiple_equipment_ids[log_id].append(equipment_id)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
seen = set()
|
||||||
|
for log_id, equipment_id, equipment_2_id in recent_logs:
|
||||||
|
candidate_ids = [
|
||||||
|
equipment_id,
|
||||||
|
equipment_2_id,
|
||||||
|
*multiple_equipment_ids[log_id],
|
||||||
|
]
|
||||||
|
for candidate_id in candidate_ids:
|
||||||
|
if candidate_id and candidate_id not in seen:
|
||||||
|
seen.add(candidate_id)
|
||||||
|
result.append(candidate_id)
|
||||||
|
return result
|
||||||
|
|
||||||
def inherit_zt_batch(source: BatchSt, target: BatchSt):
|
def inherit_zt_batch(source: BatchSt, target: BatchSt):
|
||||||
"""拆批/报工改号时目标批继承来源批的直通统计大批归属(纯继承, 不做判定)
|
"""拆批/报工改号时目标批继承来源批的直通统计大批归属(纯继承, 不做判定)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from apps.wpm.services import get_recent_mgroup_equipment_ids
|
||||||
|
|
||||||
|
|
||||||
|
class RecentMgroupEquipmentTests(SimpleTestCase):
|
||||||
|
@patch("apps.wpm.services.Mlog")
|
||||||
|
def test_collects_all_equipment_fields_in_log_order_without_duplicates(
|
||||||
|
self, mlog
|
||||||
|
):
|
||||||
|
log_queryset = MagicMock()
|
||||||
|
mlog.objects.filter.return_value = log_queryset
|
||||||
|
log_queryset.order_by.return_value.values_list.return_value.__getitem__.return_value = [
|
||||||
|
("log-new", "equipment-a", None),
|
||||||
|
("log-middle", "equipment-b", "equipment-a"),
|
||||||
|
("log-empty", None, None),
|
||||||
|
]
|
||||||
|
through_queryset = MagicMock()
|
||||||
|
mlog.equipments.through.objects.filter.return_value = through_queryset
|
||||||
|
through_queryset.order_by.return_value.values_list.return_value = [
|
||||||
|
("log-new", "equipment-c"),
|
||||||
|
("log-middle", "equipment-c"),
|
||||||
|
("log-middle", "equipment-d"),
|
||||||
|
]
|
||||||
|
|
||||||
|
result = get_recent_mgroup_equipment_ids("mgroup-1", log_limit=50)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
result,
|
||||||
|
["equipment-a", "equipment-c", "equipment-b", "equipment-d"],
|
||||||
|
)
|
||||||
|
log_queryset.order_by.return_value.values_list.return_value.__getitem__.assert_called_once_with(
|
||||||
|
slice(None, 50, None)
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("apps.wpm.services.Mlog")
|
||||||
|
def test_returns_empty_when_recent_logs_have_no_equipment(self, mlog):
|
||||||
|
log_queryset = MagicMock()
|
||||||
|
mlog.objects.filter.return_value = log_queryset
|
||||||
|
log_queryset.order_by.return_value.values_list.return_value.__getitem__.return_value = [
|
||||||
|
("log-1", None, None),
|
||||||
|
("log-2", None, None),
|
||||||
|
]
|
||||||
|
through_queryset = MagicMock()
|
||||||
|
mlog.equipments.through.objects.filter.return_value = through_queryset
|
||||||
|
through_queryset.order_by.return_value.values_list.return_value = []
|
||||||
|
|
||||||
|
result = get_recent_mgroup_equipment_ids("mgroup-1")
|
||||||
|
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
@ -7,7 +7,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
from django.db.models import Sum
|
from django.db.models import Case, IntegerField, Sum, When
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from apps.system.models import User
|
from apps.system.models import User
|
||||||
|
|
||||||
|
|
@ -54,13 +54,23 @@ from .serializers import (
|
||||||
MlogUserSerializer,
|
MlogUserSerializer,
|
||||||
BatchLogSerializer,
|
BatchLogSerializer,
|
||||||
MlogQuickSerializer,
|
MlogQuickSerializer,
|
||||||
|
MlogEquipmentOptionSerializer,
|
||||||
MlogbwStartTestSerializer,
|
MlogbwStartTestSerializer,
|
||||||
HandoverListSerializer,
|
HandoverListSerializer,
|
||||||
BatchChangeSerializer,
|
BatchChangeSerializer,
|
||||||
MlogbOutPatchUpdateSerializer
|
MlogbOutPatchUpdateSerializer
|
||||||
)
|
)
|
||||||
from .services import mlog_submit, handover_submit, mlog_revert, get_batch_dag, handover_revert
|
from .services import (
|
||||||
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
RECENT_EQUIPMENT_LOG_LIMIT,
|
||||||
|
generate_new_batch,
|
||||||
|
get_batch_dag,
|
||||||
|
get_recent_mgroup_equipment_ids,
|
||||||
|
handover_revert,
|
||||||
|
handover_submit,
|
||||||
|
mlog_revert,
|
||||||
|
mlog_submit,
|
||||||
|
mlog_submit_validate,
|
||||||
|
)
|
||||||
from apps.wf.models import State, Ticket
|
from apps.wf.models import State, Ticket
|
||||||
from apps.wpmw.models import Wpr
|
from apps.wpmw.models import Wpr
|
||||||
from apps.qm.models import Qct, Ftest, TestItem
|
from apps.qm.models import Qct, Ftest, TestItem
|
||||||
|
|
@ -332,6 +342,92 @@ class MlogViewSet(CustomModelViewSet):
|
||||||
]
|
]
|
||||||
ordering_fields = ["create_time", "update_time"]
|
ordering_fields = ["create_time", "update_time"]
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
name="mgroup",
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
description="日志所属工段",
|
||||||
|
type=openapi.TYPE_STRING,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
openapi.Parameter(
|
||||||
|
name="search",
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
description="按设备名称或编号搜索全部生产设备",
|
||||||
|
type=openapi.TYPE_STRING,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@action(
|
||||||
|
methods=["get"],
|
||||||
|
detail=False,
|
||||||
|
perms_map={"get": "*"},
|
||||||
|
serializer_class=MlogEquipmentOptionSerializer,
|
||||||
|
)
|
||||||
|
def equipment_options(self, request, *args, **kwargs):
|
||||||
|
"""返回本工段设备、最近 50 条日志用过的设备或搜索结果。"""
|
||||||
|
mgroup_id = request.query_params.get("mgroup")
|
||||||
|
if not mgroup_id:
|
||||||
|
raise ParseError("请传入mgroup参数")
|
||||||
|
|
||||||
|
search = request.query_params.get("search", "").strip()
|
||||||
|
owned_ids = list(
|
||||||
|
Equipment.objects.filter(
|
||||||
|
type=Equipment.EQUIP_TYPE_PRO,
|
||||||
|
mgroup_id=mgroup_id,
|
||||||
|
)
|
||||||
|
.order_by("name", "number")
|
||||||
|
.values_list("id", flat=True)
|
||||||
|
)
|
||||||
|
owned_id_set = set(owned_ids)
|
||||||
|
|
||||||
|
if search:
|
||||||
|
queryset = (
|
||||||
|
Equipment.objects.filter(type=Equipment.EQUIP_TYPE_PRO)
|
||||||
|
.filter(Q(name__icontains=search) | Q(number__icontains=search))
|
||||||
|
.order_by("name", "number")
|
||||||
|
)
|
||||||
|
option_group = "搜索结果"
|
||||||
|
else:
|
||||||
|
recent_ids = get_recent_mgroup_equipment_ids(
|
||||||
|
mgroup_id, RECENT_EQUIPMENT_LOG_LIMIT
|
||||||
|
)
|
||||||
|
option_ids = list(dict.fromkeys([*owned_ids, *recent_ids]))
|
||||||
|
if option_ids:
|
||||||
|
order = Case(
|
||||||
|
*[
|
||||||
|
When(id=equipment_id, then=position)
|
||||||
|
for position, equipment_id in enumerate(option_ids)
|
||||||
|
],
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
queryset = Equipment.objects.filter(
|
||||||
|
id__in=option_ids,
|
||||||
|
type=Equipment.EQUIP_TYPE_PRO,
|
||||||
|
).order_by(order)
|
||||||
|
else:
|
||||||
|
queryset = Equipment.objects.none()
|
||||||
|
option_group = None
|
||||||
|
|
||||||
|
queryset = queryset.select_related("mgroup")
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
|
equipment_list = page if page is not None else queryset
|
||||||
|
data = MlogEquipmentOptionSerializer(
|
||||||
|
equipment_list,
|
||||||
|
many=True,
|
||||||
|
context=self.get_serializer_context(),
|
||||||
|
).data
|
||||||
|
for item in data:
|
||||||
|
item["option_group"] = option_group or (
|
||||||
|
"本工段设备" if item["id"] in owned_id_set else "近期使用"
|
||||||
|
)
|
||||||
|
|
||||||
|
if page is not None:
|
||||||
|
return self.get_paginated_response(data)
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
def add_info_for_item(self, data):
|
def add_info_for_item(self, data):
|
||||||
if data.get("oinfo_json", {}):
|
if data.get("oinfo_json", {}):
|
||||||
czx_dict = dict(TestItem.objects.filter(id__in=data.get("oinfo_json", {}).keys()).values_list("id", "name"))
|
czx_dict = dict(TestItem.objects.filter(id__in=data.get("oinfo_json", {}).keys()).values_list("id", "name"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue