Compare commits
106 Commits
2.8.202509
...
master
Author | SHA1 | Date |
---|---|---|
|
388e225108 | |
|
25ee92602b | |
|
241df0beca | |
|
5ea7980a1b | |
|
a3416cfc0d | |
|
2ca47b8949 | |
|
10792d090c | |
|
bfc8454ac7 | |
|
02b14ec2c6 | |
|
acb4c802e4 | |
|
260c9893eb | |
|
666a9c169c | |
|
b869521221 | |
|
99d8144bdf | |
|
8a87ba356e | |
|
830bf18132 | |
|
b52e90a11f | |
|
ee67e6896a | |
|
54f8b82c98 | |
|
6125139fbf | |
|
2169bbea68 | |
|
6dfab46b4d | |
|
477976f86c | |
|
c37ff77eda | |
|
1aa7d51769 | |
|
e06cc8c38e | |
|
b93024ca44 | |
|
7d87c79dd1 | |
|
9f030ece6d | |
|
67f9cbb700 | |
|
8fe2b8ca48 | |
|
7732ddc88e | |
|
6713693c6c | |
|
20604ef7cb | |
|
eb2deb02c2 | |
|
f5f6c136d9 | |
|
8eee09678a | |
|
efd40d1d32 | |
|
3ab9682b07 | |
|
1983f7b121 | |
|
cec6837d00 | |
|
e214c6115a | |
|
6fb415a9f0 | |
|
727d190610 | |
|
a6e0fb4f0d | |
|
9cf900d2ef | |
|
69e8e7b025 | |
|
647603f986 | |
|
d21c1dc55d | |
|
7f206bd0a7 | |
|
bd763be83a | |
|
fe499ffac5 | |
|
0c1e93bf0b | |
|
5d3c4137fe | |
|
dbaf121685 | |
|
fe524e389c | |
|
bf8b886a2d | |
|
7b8ec7f9d6 | |
|
7620122c2d | |
|
de99f85259 | |
|
e7c121de15 | |
|
bdd686f50b | |
|
54140ba742 | |
|
c186b7d296 | |
|
3a03cd76ff | |
|
f9c9a592e3 | |
|
419b52f9be | |
|
fb71f0697a | |
|
34e217e468 | |
|
f34356057d | |
|
b35015b58d | |
|
c434c76605 | |
|
9909596cdb | |
|
6f4a1c4c88 | |
|
2445ae53f1 | |
|
0fc57a454f | |
|
d26e066769 | |
|
f347fa0d23 | |
|
362a5ef725 | |
|
ad98280458 | |
|
3c0fa9f244 | |
|
b520b69f95 | |
|
899a314f5d | |
|
dbb0d6ae75 | |
|
bc4953893b | |
|
a8722724e1 | |
|
1c447a86ef | |
|
820f814766 | |
|
1689683aa3 | |
|
cdb201a0ce | |
|
d8ad57fa7e | |
|
e32d4b816b | |
|
47ca272cee | |
|
4390992a14 | |
|
085978a6c4 | |
|
f6668b5d38 | |
|
f6720ad7ce | |
|
e5a9f77f3d | |
|
c2deb0ee45 | |
|
94f87df707 | |
|
694dca27cc | |
|
0284809933 | |
|
f60250bb21 | |
|
0b4524aa85 | |
|
f7e27c290f | |
|
2c9c74131c |
|
@ -62,7 +62,7 @@ class Mpoint(CommonBModel):
|
||||||
cal_coefficient = models.FloatField("计算系数", null=True, blank=True)
|
cal_coefficient = models.FloatField("计算系数", null=True, blank=True)
|
||||||
|
|
||||||
mpoint_from = models.ForeignKey("self", verbose_name="来源自采测点", related_name="mp_mpoint_from", on_delete=models.SET_NULL, null=True, blank=True)
|
mpoint_from = models.ForeignKey("self", verbose_name="来源自采测点", related_name="mp_mpoint_from", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
cal_related_mgroup_running = models.PositiveSmallIntegerField("与工段运行状态的关联", default=10, choices=[(10, "运行时统计")], null=True, blank=True)
|
cal_related_mgroup_running = models.PositiveSmallIntegerField("与工段运行状态的关联", default=10, choices=[(10, "不涉及"), (20, "运行时统计")], null=True, blank=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cache_key(cls, code: str):
|
def cache_key(cls, code: str):
|
||||||
|
|
|
@ -167,11 +167,11 @@ def get_first_stlog_time_from_duration(mgroup:Mgroup, dt_start:datetime, dt_end:
|
||||||
if st:
|
if st:
|
||||||
|
|
||||||
return st, "ending"
|
return st, "ending"
|
||||||
st = st_qs.filter(start_time__gte=dt_start, start_time__lte=dt_end, duration_sec__lte=600).order_by("start_time").last()
|
st = st_qs.filter(start_time__gte=dt_start, start_time__lte=dt_end, duration_sec__gte=600).order_by("start_time").last()
|
||||||
if st:
|
if st:
|
||||||
|
|
||||||
return st, "start"
|
return st, "start"
|
||||||
st = st_qs.filter(end_time__gte=dt_start, end_time__lte=dt_end, duration_sec__lte=600).order_by("end_time").first()
|
st = st_qs.filter(end_time__gte=dt_start, end_time__lte=dt_end, duration_sec__gte=600).order_by("end_time").first()
|
||||||
if st:
|
if st:
|
||||||
|
|
||||||
return st, "end"
|
return st, "end"
|
||||||
|
@ -213,7 +213,7 @@ def cal_mpointstat_hour(mpointId: str, year: int, month: int, day: int, hour: in
|
||||||
val = abs(first_val - last_val)
|
val = abs(first_val - last_val)
|
||||||
else:
|
else:
|
||||||
xtype = "normal"
|
xtype = "normal"
|
||||||
if mpointfrom and mpoint.cal_related_mgroup_running == 10:
|
if mpointfrom and mpoint.cal_related_mgroup_running == 20:
|
||||||
|
|
||||||
stlog, xtype = get_first_stlog_time_from_duration(mpoint.mgroup, dt, dt_hour_n)
|
stlog, xtype = get_first_stlog_time_from_duration(mpoint.mgroup, dt, dt_hour_n)
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from apps.enm.views import (MpointViewSet, MpLogxViewSet, MpointStatViewSet,
|
from apps.enm.views import (MpointViewSet, MpointStatViewSet,
|
||||||
EnStatViewSet, EnStat2ViewSet, XscriptViewSet)
|
EnStatViewSet, EnStat2ViewSet, XscriptViewSet, MpLogxAPIView)
|
||||||
|
|
||||||
API_BASE_URL = 'api/enm/'
|
API_BASE_URL = 'api/enm/'
|
||||||
HTML_BASE_URL = 'dhtml/enm/'
|
HTML_BASE_URL = 'dhtml/enm/'
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register('mpoint', MpointViewSet, basename='mpoint')
|
router.register('mpoint', MpointViewSet, basename='mpoint')
|
||||||
router.register('mplogx', MpLogxViewSet, basename='mplogx')
|
# router.register('mplogx', MpLogxViewSet, basename='mplogx')
|
||||||
router.register('mpointstat', MpointStatViewSet, basename='mpointstat')
|
router.register('mpointstat', MpointStatViewSet, basename='mpointstat')
|
||||||
router.register('enstat', EnStatViewSet, basename='enstat')
|
router.register('enstat', EnStatViewSet, basename='enstat')
|
||||||
router.register('enstat2', EnStat2ViewSet, basename='enstat2')
|
router.register('enstat2', EnStat2ViewSet, basename='enstat2')
|
||||||
router.register('xscript', XscriptViewSet, basename='xscript')
|
router.register('xscript', XscriptViewSet, basename='xscript')
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(API_BASE_URL, include(router.urls)),
|
path(API_BASE_URL, include(router.urls)),
|
||||||
|
path(f'{API_BASE_URL}mplogx/', MpLogxAPIView.as_view(), name='mplogx_list'),
|
||||||
]
|
]
|
|
@ -12,6 +12,7 @@ from apps.enm.tasks import cal_mpointstat_manual
|
||||||
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 rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.views import APIView
|
||||||
from apps.enm.tasks import cal_mpointstats_duration
|
from apps.enm.tasks import cal_mpointstats_duration
|
||||||
from apps.enm.services import king_sync, MpointCache
|
from apps.enm.services import king_sync, MpointCache
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -21,7 +22,13 @@ from apps.enm.services import get_analyse_data_mgroups_duration
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
import logging
|
import logging
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from apps.utils.sql import query_one_dict, query_all_dict
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
myLogger = logging.getLogger('log')
|
myLogger = logging.getLogger('log')
|
||||||
|
|
||||||
class MpointViewSet(CustomModelViewSet):
|
class MpointViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
list:测点
|
list:测点
|
||||||
|
@ -166,6 +173,97 @@ class XscriptViewSet(CustomModelViewSet):
|
||||||
# select_related_fields = ['mpoint']
|
# select_related_fields = ['mpoint']
|
||||||
# filterset_fields = ['mpoint', 'mpoint__mgroup', 'mpoint__mgroup__belong_dept']
|
# filterset_fields = ['mpoint', 'mpoint__mgroup', 'mpoint__mgroup__belong_dept']
|
||||||
|
|
||||||
|
class MpLogxAPIView(APIView):
|
||||||
|
"""
|
||||||
|
list:测点采集数据
|
||||||
|
|
||||||
|
测点采集数据
|
||||||
|
"""
|
||||||
|
perms_map = {"get": "*"}
|
||||||
|
|
||||||
|
@swagger_auto_schema(manual_parameters=[
|
||||||
|
openapi.Parameter('mpoint', openapi.IN_QUERY, description='测点ID', type=openapi.TYPE_STRING),
|
||||||
|
openapi.Parameter('timex__gte', openapi.IN_QUERY, description='开始时间', type=openapi.TYPE_STRING),
|
||||||
|
openapi.Parameter('timex__lte', openapi.IN_QUERY, description='结束时间', type=openapi.TYPE_STRING),
|
||||||
|
openapi.Parameter('page', openapi.IN_QUERY, description='页码', type=openapi.TYPE_INTEGER),
|
||||||
|
openapi.Parameter('page_size', openapi.IN_QUERY, description='每页数量', type=openapi.TYPE_INTEGER),
|
||||||
|
openapi.Parameter('ordering', openapi.IN_QUERY, description='排序字段,如 -timex', type=openapi.TYPE_STRING),
|
||||||
|
openapi.Parameter('fields', openapi.IN_QUERY, description='返回字段,如 timex,val_float,val_int', type=openapi.TYPE_STRING),
|
||||||
|
])
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
mpoint = request.query_params.get("mpoint", None)
|
||||||
|
timex__gte_str = request.query_params.get("timex__gte", None)
|
||||||
|
timex__lte_str = request.query_params.get("timex__lte", None)
|
||||||
|
page = int(request.query_params.get("page", 1))
|
||||||
|
page_size = int(request.query_params.get("page_size", 20))
|
||||||
|
fields = request.query_params.get("fields", None)
|
||||||
|
if page < 0 and page_size < 0:
|
||||||
|
raise ParseError("page, page_size must be positive")
|
||||||
|
ordering = request.query_params.get("ordering", "-timex") # 默认倒序
|
||||||
|
|
||||||
|
if mpoint is None or timex__gte_str is None:
|
||||||
|
raise ParseError("mpoint, timex__gte are required")
|
||||||
|
|
||||||
|
# 处理时间
|
||||||
|
timex__gte = timezone.make_aware(datetime.strptime(timex__gte_str, "%Y-%m-%d %H:%M:%S"))
|
||||||
|
timex__lte = timezone.make_aware(datetime.strptime(timex__lte_str, "%Y-%m-%d %H:%M:%S")) if timex__lte_str else timezone.now()
|
||||||
|
|
||||||
|
# 统计总数
|
||||||
|
count_sql = """SELECT COUNT(*) AS total_count FROM enm_mplogx
|
||||||
|
WHERE mpoint_id=%s AND timex >= %s AND timex <= %s"""
|
||||||
|
count_data = query_one_dict(count_sql, [mpoint, timex__gte, timex__lte], with_time_format=True)
|
||||||
|
|
||||||
|
# 排序白名单
|
||||||
|
allowed_fields = {"timex", "val_mrs", "val_int", "val_float"} # 根据表字段修改
|
||||||
|
order_fields = []
|
||||||
|
for field in ordering.split(","):
|
||||||
|
field = field.strip()
|
||||||
|
if not field:
|
||||||
|
continue
|
||||||
|
desc = field.startswith("-")
|
||||||
|
field_name = field[1:] if desc else field
|
||||||
|
if field_name in allowed_fields:
|
||||||
|
order_fields.append(f"{field_name} {'DESC' if desc else 'ASC'}")
|
||||||
|
|
||||||
|
# 如果没有合法字段,使用默认排序
|
||||||
|
if not order_fields:
|
||||||
|
order_fields = ["timex DESC"]
|
||||||
|
|
||||||
|
order_clause = "ORDER BY " + ", ".join(order_fields)
|
||||||
|
|
||||||
|
# 构造 SQL
|
||||||
|
if page == 0:
|
||||||
|
if fields:
|
||||||
|
# 过滤白名单,避免非法列
|
||||||
|
fields = [f for f in fields.split(",") if f in allowed_fields]
|
||||||
|
if not fields:
|
||||||
|
fields = ["timex", "val_float", "val_int"] # 默认列
|
||||||
|
select_clause = ", ".join(fields)
|
||||||
|
else:
|
||||||
|
select_clause = "timex, val_float, val_int" # 默认列
|
||||||
|
page_sql = f"""SELECT {select_clause} FROM enm_mplogx
|
||||||
|
WHERE mpoint_id=%s AND timex >= %s AND timex <= %s
|
||||||
|
{order_clause}"""
|
||||||
|
page_params = [mpoint, timex__gte, timex__lte]
|
||||||
|
else:
|
||||||
|
page_sql = f"""SELECT * FROM enm_mplogx
|
||||||
|
WHERE mpoint_id=%s AND timex >= %s AND timex <= %s
|
||||||
|
{order_clause} LIMIT %s OFFSET %s"""
|
||||||
|
page_params = [mpoint, timex__gte, timex__lte, page_size, (page-1)*page_size]
|
||||||
|
|
||||||
|
page_data = query_all_dict(page_sql, page_params, with_time_format=True)
|
||||||
|
|
||||||
|
if page == 0:
|
||||||
|
return Response(page_data)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"count": count_data["total_count"],
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"results": page_data
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MpLogxViewSet(CustomListModelMixin, CustomGenericViewSet):
|
class MpLogxViewSet(CustomListModelMixin, CustomGenericViewSet):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -37,7 +37,9 @@ class MioFilter(filters.FilterSet):
|
||||||
"item_mio__test_user": ["isnull"],
|
"item_mio__test_user": ["isnull"],
|
||||||
"item_mio__w_mioitem__number": ["exact"],
|
"item_mio__w_mioitem__number": ["exact"],
|
||||||
"mgroup": ["exact"],
|
"mgroup": ["exact"],
|
||||||
"item_mio__batch": ["exact"]
|
"item_mio__batch": ["exact"],
|
||||||
|
"inout_date": ["gte", "lte", "exact"],
|
||||||
|
"belong_dept": ["exact"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def filter_materials__type(self, queryset, name, value):
|
def filter_materials__type(self, queryset, name, value):
|
||||||
|
|
|
@ -45,7 +45,6 @@ class MIOItemwCreateUpdateSerializer(CustomModelSerializer):
|
||||||
ftest_sr.update(instance=ftest, validated_data=ftest_data)
|
ftest_sr.update(instance=ftest, validated_data=ftest_data)
|
||||||
return mioitemw
|
return mioitemw
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
wpr: Wpr = validated_data.get("wpr", None)
|
wpr: Wpr = validated_data.get("wpr", None)
|
||||||
if wpr:
|
if wpr:
|
||||||
|
@ -58,7 +57,6 @@ class MIOItemwCreateUpdateSerializer(CustomModelSerializer):
|
||||||
mioitemw = self.save_ftest(mioitemw, ftest_data)
|
mioitemw = self.save_ftest(mioitemw, ftest_data)
|
||||||
return mioitemw
|
return mioitemw
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop("mioitem")
|
validated_data.pop("mioitem")
|
||||||
ftest_data = validated_data.pop("ftest", None)
|
ftest_data = validated_data.pop("ftest", None)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from apps.wpmw.models import Wpr
|
||||||
from apps.qm.models import Ftest, Defect
|
from apps.qm.models import Ftest, Defect
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
|
|
||||||
def do_out(item: MIOItem):
|
def do_out(item: MIOItem, is_reverse: bool = False):
|
||||||
"""
|
"""
|
||||||
生产领料到车间
|
生产领料到车间
|
||||||
"""
|
"""
|
||||||
|
@ -23,8 +23,6 @@ def do_out(item: MIOItem):
|
||||||
mgroup = mio.mgroup
|
mgroup = mio.mgroup
|
||||||
do_user = mio.do_user
|
do_user = mio.do_user
|
||||||
material:Material = item.material
|
material:Material = item.material
|
||||||
if material.into_wm is False: # 用于混料的原料不与车间库存交互, 这个是配置项目
|
|
||||||
return
|
|
||||||
|
|
||||||
# 获取defect
|
# 获取defect
|
||||||
defect:Defect = None
|
defect:Defect = None
|
||||||
|
@ -94,26 +92,28 @@ def do_out(item: MIOItem):
|
||||||
raise ParseError(f"批次错误!{e}")
|
raise ParseError(f"批次错误!{e}")
|
||||||
mb.count = mb.count - xcount
|
mb.count = mb.count - xcount
|
||||||
if mb.count < 0:
|
if mb.count < 0:
|
||||||
raise ParseError(f"{mb.batch}-批次库存不足,操作失败")
|
raise ParseError(f"{mb.batch}-{str(mb.material)}-批次库存不足,操作失败")
|
||||||
else:
|
else:
|
||||||
mb.save()
|
mb.save()
|
||||||
|
|
||||||
|
if material.into_wm:
|
||||||
# 领到车间库存(或工段)
|
# 领到车间库存(或工段)
|
||||||
wm, new_create = WMaterial.objects.get_or_create(
|
wm, new_create = WMaterial.objects.get_or_create(
|
||||||
batch=xbatch, material=xmaterial,
|
batch=xbatch, material=xmaterial,
|
||||||
belong_dept=belong_dept, mgroup=mgroup,
|
belong_dept=belong_dept, mgroup=mgroup,
|
||||||
state=WMaterial.WM_OK, defect=defect)
|
state=WMaterial.WM_OK, defect=defect)
|
||||||
if new_create:
|
if new_create:
|
||||||
wm.create_by = do_user
|
wm.create_by = do_user
|
||||||
wm.batch_ofrom = mb.batch if mb else None
|
wm.batch_ofrom = mb.batch if mb else None
|
||||||
wm.material_ofrom = mb.material if mb else None
|
wm.material_ofrom = mb.material if mb else None
|
||||||
wm.count = wm.count + item.count
|
wm.count = wm.count + item.count
|
||||||
wm.update_by = do_user
|
wm.update_by = do_user
|
||||||
wm.save()
|
wm.save()
|
||||||
|
|
||||||
# 开始变动wpr
|
# 开始变动wpr
|
||||||
if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
|
if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
|
if material.into_wm is False:
|
||||||
|
raise ParseError("追踪单个物料不支持不进行车间库存的操作")
|
||||||
mioitemws = MIOItemw.objects.filter(mioitem=item)
|
mioitemws = MIOItemw.objects.filter(mioitem=item)
|
||||||
if mioitemws.count() != item.count:
|
if mioitemws.count() != item.count:
|
||||||
raise ParseError("出入库与明细数量不一致,操作失败")
|
raise ParseError("出入库与明细数量不一致,操作失败")
|
||||||
|
@ -141,8 +141,7 @@ def do_in(item: MIOItem):
|
||||||
mgroup = mio.mgroup
|
mgroup = mio.mgroup
|
||||||
do_user = mio.do_user
|
do_user = mio.do_user
|
||||||
material = item.material
|
material = item.material
|
||||||
if material.into_wm is False: # 根据配置不进行入车间库存的处理
|
|
||||||
return
|
|
||||||
action_list = []
|
action_list = []
|
||||||
mias = MIOItemA.objects.filter(mioitem=item)
|
mias = MIOItemA.objects.filter(mioitem=item)
|
||||||
is_zhj = False # 是否组合件入仓库
|
is_zhj = False # 是否组合件入仓库
|
||||||
|
@ -177,38 +176,39 @@ def do_in(item: MIOItem):
|
||||||
raise ParseError("存在非正数!")
|
raise ParseError("存在非正数!")
|
||||||
|
|
||||||
xbatchs.append(xbatch)
|
xbatchs.append(xbatch)
|
||||||
|
if material.into_wm:
|
||||||
wm_qs = WMaterial.objects.filter(
|
wm_qs = WMaterial.objects.filter(
|
||||||
batch=xbatch,
|
batch=xbatch,
|
||||||
material=xmaterial,
|
material=xmaterial,
|
||||||
belong_dept=belong_dept,
|
belong_dept=belong_dept,
|
||||||
mgroup=mgroup,
|
mgroup=mgroup,
|
||||||
defect=defect,
|
defect=defect,
|
||||||
state=WMaterial.WM_OK)
|
state=WMaterial.WM_OK)
|
||||||
count_x = wm_qs.count()
|
count_x = wm_qs.count()
|
||||||
if count_x == 1:
|
if count_x == 1:
|
||||||
wm = wm_qs.first()
|
wm = wm_qs.first()
|
||||||
elif count_x == 0:
|
elif count_x == 0:
|
||||||
raise ParseError(
|
raise ParseError(
|
||||||
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
|
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
|
||||||
else:
|
else:
|
||||||
raise ParseError(
|
raise ParseError(
|
||||||
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
|
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
|
||||||
|
|
||||||
|
# 扣减车间库存
|
||||||
|
new_count = wm.count - xcount
|
||||||
|
if new_count >= 0:
|
||||||
|
wm.count = new_count
|
||||||
|
wm.update_by = do_user
|
||||||
|
wm.save()
|
||||||
|
else:
|
||||||
|
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
|
||||||
|
|
||||||
|
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept
|
||||||
|
if production_dept is None:
|
||||||
|
production_dept = wm_production_dept
|
||||||
|
elif wm_production_dept and production_dept != wm_production_dept:
|
||||||
|
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不属于同一车间')
|
||||||
|
|
||||||
# 扣减车间库存
|
|
||||||
new_count = wm.count - xcount
|
|
||||||
if new_count >= 0:
|
|
||||||
wm.count = new_count
|
|
||||||
wm.update_by = do_user
|
|
||||||
wm.save()
|
|
||||||
else:
|
|
||||||
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
|
|
||||||
|
|
||||||
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept
|
|
||||||
if production_dept is None:
|
|
||||||
production_dept = wm_production_dept
|
|
||||||
elif wm_production_dept and production_dept != wm_production_dept:
|
|
||||||
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不属于同一车间')
|
|
||||||
# 增加mb
|
# 增加mb
|
||||||
if not is_zhj:
|
if not is_zhj:
|
||||||
mb, _ = MaterialBatch.objects.get_or_create(
|
mb, _ = MaterialBatch.objects.get_or_create(
|
||||||
|
@ -231,6 +231,8 @@ def do_in(item: MIOItem):
|
||||||
|
|
||||||
# 开始变动wpr
|
# 开始变动wpr
|
||||||
if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
|
if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
|
if material.into_wm is False:
|
||||||
|
raise ParseError("追踪单个物料不支持不进行车间库存的操作")
|
||||||
mioitemws = MIOItemw.objects.filter(mioitem=item)
|
mioitemws = MIOItemw.objects.filter(mioitem=item)
|
||||||
if mioitemws.count() != item.count:
|
if mioitemws.count() != item.count:
|
||||||
raise ParseError("出入库与明细数量不一致,操作失败")
|
raise ParseError("出入库与明细数量不一致,操作失败")
|
||||||
|
@ -440,7 +442,7 @@ class InmService:
|
||||||
elif in_or_out == -1:
|
elif in_or_out == -1:
|
||||||
mb.count = mb.count - change_count
|
mb.count = mb.count - change_count
|
||||||
if mb.count < 0:
|
if mb.count < 0:
|
||||||
raise ParseError(f"{mb.batch}-批次库存不足,操作失败")
|
raise ParseError(f"{mb.batch}-{str(mb.material)}-批次库存不足,操作失败")
|
||||||
else:
|
else:
|
||||||
mb.save()
|
mb.save()
|
||||||
if tracking == Material.MA_TRACKING_SINGLE:
|
if tracking == Material.MA_TRACKING_SINGLE:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
from apps.mtm.models import Goal, Material, Route
|
from apps.mtm.models import Goal, Material, Route, RoutePack
|
||||||
from django.db.models.expressions import F
|
from django.db.models.expressions import F
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
|
|
||||||
class MaterialFilter(filters.FilterSet):
|
class MaterialFilter(filters.FilterSet):
|
||||||
|
@ -45,6 +46,8 @@ class GoalFilter(filters.FilterSet):
|
||||||
|
|
||||||
|
|
||||||
class RouteFilter(filters.FilterSet):
|
class RouteFilter(filters.FilterSet):
|
||||||
|
nprocess_name = filters.CharFilter(method='filter_nprocess_name', label="nprocess_name")
|
||||||
|
material_in_has = filters.CharFilter(method='filter_material_in_has', label="material_in_has ID")
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Route
|
model = Route
|
||||||
fields = {
|
fields = {
|
||||||
|
@ -61,3 +64,15 @@ class RouteFilter(filters.FilterSet):
|
||||||
"mgroup__belong_dept__name": ["exact", "contains"],
|
"mgroup__belong_dept__name": ["exact", "contains"],
|
||||||
"from_route": ["exact", "isnull"],
|
"from_route": ["exact", "isnull"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def filter_nprocess_name(self, queryset, name, value):
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
def filter_material_in_has(self, queryset, name, value):
|
||||||
|
nprocess_name = self.data.get('nprocess_name', None)
|
||||||
|
if nprocess_name:
|
||||||
|
routepack_qs = queryset.filter(material_in__id=value, routepack__isnull=False, routepack__state=RoutePack.RP_S_CONFIRM).values_list('routepack', flat=True)
|
||||||
|
qs = queryset.filter(routepack__in=routepack_qs, process__name=nprocess_name)
|
||||||
|
return qs
|
||||||
|
raise ParseError("nprocess_name is required")
|
|
@ -246,34 +246,34 @@ class RouteSerializer(CustomModelSerializer):
|
||||||
# material = validated_data.get('material', None)
|
# material = validated_data.get('material', None)
|
||||||
# if material and process and Route.objects.filter(material=material, process=process).exists():
|
# if material and process and Route.objects.filter(material=material, process=process).exists():
|
||||||
# raise ValidationError('已选择该工序!!')
|
# raise ValidationError('已选择该工序!!')
|
||||||
with transaction.atomic():
|
|
||||||
instance:Route = super().create(validated_data)
|
instance:Route = super().create(validated_data)
|
||||||
material_out = instance.material_out
|
material_out = instance.material_out
|
||||||
if material_out:
|
if material_out:
|
||||||
if material_out.process is None:
|
if material_out.process is None:
|
||||||
material_out.process = process
|
material_out.process = process
|
||||||
if material_out_tracking != material_out.tracking:
|
if material_out_tracking != material_out.tracking:
|
||||||
raise ParseError("物料跟踪类型不一致!请前往物料处修改")
|
raise ParseError("物料跟踪类型不一致!请前往物料处修改")
|
||||||
if material_out.parent is None and instance.material:
|
if material_out.parent is None and instance.material:
|
||||||
material_out.parent = instance.material
|
material_out.parent = instance.material
|
||||||
material_out.save()
|
material_out.save()
|
||||||
# elif material_out.process != process:
|
# elif material_out.process != process:
|
||||||
# raise ParseError('物料工序错误!请重新选择')
|
# raise ParseError('物料工序错误!请重新选择')
|
||||||
else:
|
else:
|
||||||
if instance.material:
|
if instance.material:
|
||||||
instance.material_out = RouteSerializer.gen_material_out(instance, material_out_tracking, user=self.request.user)
|
instance.material_out = RouteSerializer.gen_material_out(instance, material_out_tracking, user=self.request.user)
|
||||||
instance.save()
|
|
||||||
rx = Route.objects.filter(
|
|
||||||
material_in=instance.material_in, material_out=instance.material_out,
|
|
||||||
process=process).exclude(id=instance.id).order_by("create_time").first()
|
|
||||||
if rx:
|
|
||||||
instance.from_route = rx
|
|
||||||
instance.save()
|
instance.save()
|
||||||
# msg = ""
|
rx = Route.objects.filter(
|
||||||
# if rx.routepack:
|
material_in=instance.material_in, material_out=instance.material_out,
|
||||||
# msg = rx.routepack.name
|
process=process).exclude(id=instance.id).order_by("create_time").first()
|
||||||
# raise ParseError(f"该工艺步骤已存在-{msg}")
|
if rx:
|
||||||
return instance
|
instance.from_route = rx
|
||||||
|
instance.save()
|
||||||
|
# msg = ""
|
||||||
|
# if rx.routepack:
|
||||||
|
# msg = rx.routepack.name
|
||||||
|
# raise ParseError(f"该工艺步骤已存在-{msg}")
|
||||||
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop('material', None)
|
validated_data.pop('material', None)
|
||||||
|
@ -281,34 +281,34 @@ class RouteSerializer(CustomModelSerializer):
|
||||||
material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH)
|
material_out_tracking = validated_data.pop("material_out_tracking", Material.MA_TRACKING_BATCH)
|
||||||
if material_out_tracking is None:
|
if material_out_tracking is None:
|
||||||
material_out_tracking = Material.MA_TRACKING_BATCH
|
material_out_tracking = Material.MA_TRACKING_BATCH
|
||||||
with transaction.atomic():
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
material_out = instance.material_out
|
material_out = instance.material_out
|
||||||
if material_out:
|
if material_out:
|
||||||
if material_out.process is None:
|
if material_out.process is None:
|
||||||
material_out.process = process
|
material_out.process = process
|
||||||
if material_out_tracking != material_out.tracking:
|
if material_out_tracking != material_out.tracking:
|
||||||
raise ParseError("物料跟踪类型不一致!请前往物料处修改")
|
raise ParseError("物料跟踪类型不一致!请前往物料处修改")
|
||||||
if material_out.parent is None and instance.material:
|
if material_out.parent is None and instance.material:
|
||||||
material_out.parent = instance.material
|
material_out.parent = instance.material
|
||||||
material_out.save()
|
material_out.save()
|
||||||
# elif material_out.process != process:
|
# elif material_out.process != process:
|
||||||
# raise ParseError('物料工序错误!请重新选择')
|
# raise ParseError('物料工序错误!请重新选择')
|
||||||
else:
|
else:
|
||||||
if instance.material:
|
if instance.material:
|
||||||
instance.material_out = RouteSerializer.gen_material_out(instance, material_out_tracking, user=self.request.user)
|
instance.material_out = RouteSerializer.gen_material_out(instance, material_out_tracking, user=self.request.user)
|
||||||
instance.save()
|
|
||||||
rx = Route.objects.filter(
|
|
||||||
material_in=instance.material_in, material_out=instance.material_out,
|
|
||||||
process=process).exclude(id=instance.id).order_by("create_time").first()
|
|
||||||
if rx:
|
|
||||||
instance.from_route = rx
|
|
||||||
instance.save()
|
instance.save()
|
||||||
# msg = ""
|
rx = Route.objects.filter(
|
||||||
# if rx.routepack:
|
material_in=instance.material_in, material_out=instance.material_out,
|
||||||
# msg = rx.routepack.name
|
process=process).exclude(id=instance.id).order_by("create_time").first()
|
||||||
# raise ParseError(f"该工艺步骤已存在-{msg}")
|
if rx:
|
||||||
return instance
|
instance.from_route = rx
|
||||||
|
instance.save()
|
||||||
|
# msg = ""
|
||||||
|
# if rx.routepack:
|
||||||
|
# msg = rx.routepack.name
|
||||||
|
# raise ParseError(f"该工艺步骤已存在-{msg}")
|
||||||
|
return instance
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
res = super().to_representation(instance)
|
res = super().to_representation(instance)
|
||||||
|
|
|
@ -58,7 +58,7 @@ def daoru_material(path: str):
|
||||||
i = 3
|
i = 3
|
||||||
if sheet['a2'].value != '物料编号':
|
if sheet['a2'].value != '物料编号':
|
||||||
raise ParseError('列错误导入失败')
|
raise ParseError('列错误导入失败')
|
||||||
while sheet[f'b{i}'].value is not None:
|
while sheet[f'b{i}'].value is not None or sheet[f'd{i}'].value is not None:
|
||||||
type_str = sheet[f'b{i}'].value.replace(' ', '')
|
type_str = sheet[f'b{i}'].value.replace(' ', '')
|
||||||
try:
|
try:
|
||||||
type = type_dict[type_str]
|
type = type_dict[type_str]
|
||||||
|
@ -171,8 +171,8 @@ def bind_routepack(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ticket.save()
|
ticket.save()
|
||||||
if routepack.ticket is None:
|
if routepack.ticket is None:
|
||||||
routepack.ticket = ticket
|
routepack.ticket = ticket
|
||||||
routepack.state = RoutePack.RP_S_AUDIT
|
routepack.state = RoutePack.RP_S_AUDIT
|
||||||
routepack.save()
|
routepack.save()
|
||||||
|
|
||||||
|
|
||||||
def routepack_audit_end(ticket: Ticket):
|
def routepack_audit_end(ticket: Ticket):
|
||||||
|
@ -182,7 +182,7 @@ def routepack_audit_end(ticket: Ticket):
|
||||||
|
|
||||||
def routepack_ticket_change(ticket: Ticket):
|
def routepack_ticket_change(ticket: Ticket):
|
||||||
routepack = RoutePack.objects.get(id=ticket.ticket_data['t_id'])
|
routepack = RoutePack.objects.get(id=ticket.ticket_data['t_id'])
|
||||||
if ticket.act_state == Ticket.TICKET_ACT_STATE_DRAFT:
|
if ticket.act_state in [Ticket.TICKET_ACT_STATE_DRAFT, Ticket.TICKET_ACT_STATE_BACK, Ticket.TICKET_ACT_STATE_RETREAT]:
|
||||||
routepack.state = RoutePack.RP_S_CREATE
|
routepack.state = RoutePack.RP_S_CREATE
|
||||||
routepack.save()
|
routepack.save()
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,8 @@ class MaterialViewSet(CustomModelViewSet):
|
||||||
ordering = ['name', 'model', 'specification',
|
ordering = ['name', 'model', 'specification',
|
||||||
'type', 'process', 'process__sort', 'sort', 'id', 'number']
|
'type', 'process', 'process__sort', 'sort', 'id', 'number']
|
||||||
ordering_fields = ['name', 'model', 'specification',
|
ordering_fields = ['name', 'model', 'specification',
|
||||||
'type', 'process', 'process__sort', 'sort', 'id', 'number']
|
'type', 'process', 'process__sort', 'sort', 'id', 'number', 'create_time']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
from apps.inm.models import MaterialBatch
|
from apps.inm.models import MaterialBatch
|
||||||
if MaterialBatch.objects.filter(material=instance).exists():
|
if MaterialBatch.objects.filter(material=instance).exists():
|
||||||
|
@ -368,9 +367,8 @@ class RouteViewSet(CustomModelViewSet):
|
||||||
select_related_fields = ['material',
|
select_related_fields = ['material',
|
||||||
'process', 'material_in', 'material_out', 'mgroup', 'routepack']
|
'process', 'material_in', 'material_out', 'mgroup', 'routepack']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
ins:Route = self.get_object()
|
ins:Route = serializer.instance
|
||||||
if ins.from_route is not None:
|
if ins.from_route is not None:
|
||||||
raise ParseError('该工艺步骤引用其他步骤, 无法编辑')
|
raise ParseError('该工艺步骤引用其他步骤, 无法编辑')
|
||||||
old_m_in, old_m_out, process = ins.material_in, ins.material_out, ins.process
|
old_m_in, old_m_out, process = ins.material_in, ins.material_out, ins.process
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-19 01:21
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('system', '0006_auto_20241213_1249'),
|
||||||
|
('ofm', '0009_borrowrecord_ticket'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='lendingseal',
|
||||||
|
name='seal_other',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='其他印章'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Publicity',
|
||||||
|
fields=[
|
||||||
|
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('number', models.CharField(max_length=50, verbose_name='记录编号')),
|
||||||
|
('title', models.CharField(max_length=100, verbose_name='送审稿件标题')),
|
||||||
|
('participants', models.CharField(max_length=50, verbose_name='所有撰稿人')),
|
||||||
|
('level', models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='用途')),
|
||||||
|
('content', models.JSONField(default=list, help_text=['武器装备科研生产综合事项', '其它'], verbose_name='稿件内容涉及')),
|
||||||
|
('other_content', models.CharField(blank=True, max_length=100, null=True, verbose_name='其它内容')),
|
||||||
|
('report_purpose', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道目的')),
|
||||||
|
('channel', models.JSONField(default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'], verbose_name='发布渠道')),
|
||||||
|
('channel_other', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
|
||||||
|
('other_channel', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
|
||||||
|
('report_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='报道名称')),
|
||||||
|
('review', models.JSONField(default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审')),
|
||||||
|
('dept_opinion', models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见')),
|
||||||
|
('dept_opinion_review', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门审查意见')),
|
||||||
|
('publicity_opinion', models.JSONField(default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见')),
|
||||||
|
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_belong_dept', to='system.dept', verbose_name='所属部门')),
|
||||||
|
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||||
|
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-24 05:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0010_auto_20250919_0921'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='channel_other',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='pfile',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='稿件路径'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='pub_dept',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='部室/研究院'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-24 06:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0003_workflow_view_path'),
|
||||||
|
('ofm', '0011_auto_20250924_1359'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='ticket',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_ticket', to='wf.ticket', verbose_name='关联工单'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-25 07:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0003_workflow_view_path'),
|
||||||
|
('ofm', '0012_publicity_ticket'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mroomslot',
|
||||||
|
name='ticket',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-28 02:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0003_workflow_view_path'),
|
||||||
|
('ofm', '0013_mroomslot_ticket'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mroombooking',
|
||||||
|
name='ticket',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mroomslot',
|
||||||
|
name='is_inuse',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='是否占用'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='mroomslot',
|
||||||
|
unique_together={('mroom', 'mdate', 'slot', 'is_inuse')},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='mroomslot',
|
||||||
|
name='ticket',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-28 06:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0014_auto_20250928_1023'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vehicle',
|
||||||
|
name='end_km',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='归还公里数'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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='参会人数'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-10 08:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0016_auto_20250929_1551'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='secret_period',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密期限'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='level',
|
||||||
|
field=models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='涉密等级'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-11 01:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0017_auto_20251010_1631'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='dept_opinion',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='number',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='记录编号'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='publicity_opinion',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='review',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-11 03:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0018_auto_20251011_0922'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='dept_opinion',
|
||||||
|
field=models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='publicity_opinion',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='review',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-11 06:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0019_auto_20251011_1128'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='dept_opinion',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='publicity_opinion',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='review',
|
||||||
|
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-13 01:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0020_auto_20251011_1427'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='publicity',
|
||||||
|
name='publicity_opinion',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道意见'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-17 06:50
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ofm', '0021_alter_publicity_publicity_opinion'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='mroomslot',
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-21 06:08
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0004_workflow_view_path2'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('system', '0006_auto_20241213_1249'),
|
||||||
|
('ofm', '0022_alter_mroomslot_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PatentInfo',
|
||||||
|
fields=[
|
||||||
|
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
|
||||||
|
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
|
||||||
|
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='拟申请专利名称')),
|
||||||
|
('author', models.CharField(max_length=100, verbose_name='发明人(设计人)')),
|
||||||
|
('type', models.CharField(choices=[('invention', '发明专利'), ('utility', '实用新型专利'), ('design', '外观设计专利')], default='invention', max_length=50, verbose_name='专利类型')),
|
||||||
|
('is_public', models.BooleanField(default=False, verbose_name='是否公开')),
|
||||||
|
('area', models.CharField(choices=[('Domestic', '国内申请'), ('Foreign', '国外申请'), (' PCT', 'PCT申请')], default='Domestic', max_length=50, verbose_name='拟申请地域')),
|
||||||
|
('identified', models.BooleanField(default=False, verbose_name='是否进行过科技成果鉴定')),
|
||||||
|
('published_article', models.BooleanField(default=False, verbose_name='是否发表过文章')),
|
||||||
|
('exhibited', models.BooleanField(default=False, verbose_name='是否参与过展会展出')),
|
||||||
|
('applied_to_production', models.BooleanField(default=False, verbose_name='是否参与应用于生产/销售')),
|
||||||
|
('participated_in_exchange', models.BooleanField(default=False, verbose_name='是否参与过技术交流')),
|
||||||
|
('tech_background_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术背景材料页数')),
|
||||||
|
('tech_disclosure_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术交底材料页数')),
|
||||||
|
('novelty_report_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='查新检索报告页数')),
|
||||||
|
('diagrams_or_photos_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='图/照片页数或张数')),
|
||||||
|
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_belong_dept', to='system.dept', verbose_name='所属部门')),
|
||||||
|
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||||
|
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentInfo_ticket', to='wf.ticket', verbose_name='关联工单')),
|
||||||
|
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from apps.utils.models import CommonADModel, BaseModel, CommonBDModel
|
from apps.utils.models import CommonADModel, BaseModel, CommonBDModel
|
||||||
from apps.system.models import User
|
from apps.system.models import User
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
|
from datetime import datetime
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,9 +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='关联会议室',
|
||||||
|
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):
|
||||||
|
@ -35,9 +43,7 @@ class MroomSlot(BaseModel):
|
||||||
booking = models.ForeignKey(MroomBooking, on_delete=models.CASCADE, related_name="slot_b")
|
booking = models.ForeignKey(MroomBooking, on_delete=models.CASCADE, related_name="slot_b")
|
||||||
mdate = models.DateField('会议日期', db_index=True)
|
mdate = models.DateField('会议日期', db_index=True)
|
||||||
slot = models.PositiveIntegerField('时段', help_text='0-47')
|
slot = models.PositiveIntegerField('时段', help_text='0-47')
|
||||||
|
is_inuse = models.BooleanField('是否占用', default=True)
|
||||||
class Meta:
|
|
||||||
unique_together = ('mroom', 'mdate', 'slot')
|
|
||||||
|
|
||||||
|
|
||||||
# class Seal(BaseModel):
|
# class Seal(BaseModel):
|
||||||
|
@ -49,6 +55,7 @@ class LendingSeal(CommonBDModel):
|
||||||
"""TN: 印章外出用印信息"""
|
"""TN: 印章外出用印信息"""
|
||||||
|
|
||||||
seal = models.JSONField('印章信息',default=list ,help_text='[公章,法人章,财务章,合同章,业务章,其他章]')
|
seal = models.JSONField('印章信息',default=list ,help_text='[公章,法人章,财务章,合同章,业务章,其他章]')
|
||||||
|
seal_other = models.CharField('其他印章', max_length=50, blank=True, null=True)
|
||||||
filename = models.TextField('文件名称')
|
filename = models.TextField('文件名称')
|
||||||
file = models.TextField('文件内容')
|
file = models.TextField('文件内容')
|
||||||
file_count = models.PositiveIntegerField('用印份数')
|
file_count = models.PositiveIntegerField('用印份数')
|
||||||
|
@ -71,15 +78,20 @@ class Vehicle(CommonBDModel):
|
||||||
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
|
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
|
||||||
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
|
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
|
||||||
start_km = models.PositiveIntegerField('出发公里数')
|
start_km = models.PositiveIntegerField('出发公里数')
|
||||||
end_km = models.PositiveIntegerField('归还公里数')
|
end_km = models.PositiveIntegerField('归还公里数', null=True, blank=True)
|
||||||
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
|
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
|
||||||
is_city = models.BooleanField('是否市内用车', default=True)
|
is_city = models.BooleanField('是否市内用车', default=True)
|
||||||
reason = models.CharField('用车事由', max_length=100)
|
reason = 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='vehicle_ticket', null=True, blank=True, db_constraint=False)
|
on_delete=models.SET_NULL, related_name='vehicle_ticket', null=True, blank=True, db_constraint=False)
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.end_km and self.start_km:
|
if self.end_km:
|
||||||
self.actual_km = self.end_km - self.start_km
|
if self.start_km <= self.end_km:
|
||||||
|
self.actual_km = self.end_km - self.start_km
|
||||||
|
else:
|
||||||
|
raise ParseError('归还公里数不能小于出发公里数')
|
||||||
|
else:
|
||||||
|
self.actual_km = 0
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,64 +117,75 @@ class BorrowRecord(CommonBDModel):
|
||||||
on_delete=models.SET_NULL, related_name='borrow_ticket', null=True, blank=True, db_constraint=False)
|
on_delete=models.SET_NULL, related_name='borrow_ticket', null=True, blank=True, db_constraint=False)
|
||||||
|
|
||||||
|
|
||||||
# class Publicity(CommonBDModel):
|
class Publicity(CommonBDModel):
|
||||||
# """TN: 公示栏"""
|
"""TN: 公示栏"""
|
||||||
# CHANNEL_CHOICES = [
|
number = models.CharField('记录编号', max_length=50, blank=True, null=True)
|
||||||
# ('internet', '互联网'),
|
title = models.CharField('送审稿件标题', max_length=100)
|
||||||
# ('platform', '信息平台'),
|
participants = models.CharField('所有撰稿人', max_length=50)
|
||||||
# ('official', '官微'),
|
pub_dept = models.CharField('部室/研究院', null=True, blank=True, max_length=50)
|
||||||
# ('publication', '公开发行物'),
|
pfile = models.CharField('稿件路径', null=True, blank=True, max_length=100)
|
||||||
# ('other', '其它'),
|
level = models.JSONField('涉密等级', default=list, help_text=['重要', '一般', '非涉密'])
|
||||||
# ]
|
content = models.JSONField('稿件内容涉及', default=list, help_text=[
|
||||||
# SECRET_LEVELS = (
|
"武器装备科研生产综合事项",
|
||||||
# (0, '重要'),
|
"其它"
|
||||||
# (1, '一般'),
|
])
|
||||||
# (2, '非涉密'),
|
other_content = models.CharField('其它内容', max_length=100, blank=True, null=True)
|
||||||
# )
|
report_purpose = models.CharField('宣传报道目的', max_length=100, blank=True, null=True)
|
||||||
# CONTENT_CHOICES = [
|
channel = models.JSONField('发布渠道', default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'])
|
||||||
# ('device', '武器装备科研生产综合事项'),
|
other_channel = models.CharField('其它渠道', max_length=50, blank=True, null=True)
|
||||||
# ('other', '其他'),
|
report_name = models.CharField('报道名称', max_length=50, blank=True, null=True)
|
||||||
# ]
|
review = models.JSONField('第一撰稿人自审', default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True,blank=True)
|
||||||
# number = models.CharField('记录编号', max_length=50)
|
dept_opinion = models.JSONField('部门负责人意见', default=list, help_text=['同意', '不同意'], null=True, blank=True)
|
||||||
# title = models.CharField('送审稿件标题', max_length=100)
|
secret_period = models.CharField('秘密期限', max_length=50, blank=True, null=True)
|
||||||
# participants = models.CharField('所有撰稿人', max_length=50)
|
dept_opinion_review = models.CharField('部门审查意见', max_length=100, blank=True, null=True)
|
||||||
# level = models.PositiveBigIntegerField('涉密等级', choices=SECRET_LEVELS, default=2, help_text=str(SECRET_LEVELS))
|
publicity_opinion = models.CharField('宣传报道意见', max_length=100, blank=True, null=True)
|
||||||
# content = models.CharField('稿件内容涉及', max_length=50, choices=CONTENT_CHOICES, default='device', blank=True)
|
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
|
||||||
# report_purpose = models.CharField('宣传报道目的', max_length=100, blank=True, null=True)
|
on_delete=models.SET_NULL, related_name='publicity_ticket', null=True, blank=True, db_constraint=False)
|
||||||
# channel = models.CharField('发布渠道', max_length=50, choices=CHANNEL_CHOICES, default='platform')
|
|
||||||
# other_channel = models.CharField('其它渠道', max_length=50, blank=True, null=True)
|
# 记录编号自动生成
|
||||||
# review = models.CharField('第一撰稿人自审', max_length=100, blank=True, null=True)
|
def save(self, *args, **kwargs):
|
||||||
# dept_opinion = models.CharField('部门负责人意见', max_length=100, blank=True, null=True)
|
if not self.number:
|
||||||
# manager_opinion = models.CharField('总经理', max_length=100, blank=True, null=True)
|
last_number = self.__class__.objects.filter(number__startswith=f"GXKG-{datetime.now().year}-").order_by('-number').first()
|
||||||
|
if last_number:
|
||||||
|
try:
|
||||||
|
last_num = int(last_number.number.split('-')[-1])
|
||||||
|
except ValueError:
|
||||||
|
last_num = 0
|
||||||
|
else:
|
||||||
|
last_num =0
|
||||||
|
# 格式化编号,带补零
|
||||||
|
self.number = f"GXKG-{datetime.now().year}-{last_num+1:02d}"
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# class PatentInfo(CommonBDModel):
|
class PatentInfo(CommonBDModel):
|
||||||
# """TN: 专利申密审批表单样式"""
|
"""TN: 专利申密审批表单样式"""
|
||||||
# PATENT_TYPE_CHOICES = (
|
PATENT_TYPE_CHOICES = (
|
||||||
# ('invention', '发明专利'),
|
('invention', '发明专利'),
|
||||||
# ('utility', '实用新型专利'),
|
('utility', '实用新型专利'),
|
||||||
# ('design', '外观设计专利'),
|
('design', '外观设计专利'),
|
||||||
# )
|
)
|
||||||
# APPLY_AREAS = (
|
APPLY_AREAS = (
|
||||||
# ('Domestic', '国内申请'),
|
('Domestic', '国内申请'),
|
||||||
# ('Foreign', '国外申请'),
|
('Foreign', '国外申请'),
|
||||||
# (' PCT', 'PCT申请'),
|
(' PCT', 'PCT申请'),
|
||||||
# )
|
)
|
||||||
# name = models.CharField('拟申请专利名称', max_length=100)
|
name = models.CharField('拟申请专利名称', max_length=100)
|
||||||
# author = models.CharField('发明人(设计人)', max_length=100)
|
author = models.CharField('发明人(设计人)', max_length=100)
|
||||||
# type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention')
|
type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention')
|
||||||
# is_public = models.BooleanField('是否公开', default=False)
|
is_public = models.BooleanField('是否公开', default=False)
|
||||||
# area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic')
|
area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic')
|
||||||
# identified = models.BooleanField('是否进行过科技成果鉴定', default=False)
|
identified = models.BooleanField('是否进行过科技成果鉴定', default=False)
|
||||||
# published_article = models.BooleanField('是否发表过文章', default=False)
|
published_article = models.BooleanField('是否发表过文章', default=False)
|
||||||
# exhibited = models.BooleanField('是否参与过展会展出', default=False)
|
exhibited = models.BooleanField('是否参与过展会展出', default=False)
|
||||||
# applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False)
|
applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False)
|
||||||
# participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False)
|
participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False)
|
||||||
# tech_background_pages = models.PositiveIntegerField('技术背景材料页数', null=True, blank=True)
|
tech_background_pages = models.PositiveIntegerField('技术背景材料页数', null=True, blank=True)
|
||||||
# tech_disclosure_pages = models.PositiveIntegerField('技术交底材料页数', null=True, blank=True)
|
tech_disclosure_pages = models.PositiveIntegerField('技术交底材料页数', null=True, blank=True)
|
||||||
# novelty_report_pages = models.PositiveIntegerField('查新检索报告页数', null=True, blank=True)
|
novelty_report_pages = models.PositiveIntegerField('查新检索报告页数', null=True, blank=True)
|
||||||
# diagrams_or_photos_pages = models.PositiveIntegerField('图/照片页数或张数', null=True, blank=True)
|
diagrams_or_photos_pages = models.PositiveIntegerField('图/照片页数或张数', null=True, blank=True)
|
||||||
|
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
|
||||||
|
on_delete=models.SET_NULL, related_name='patentInfo_ticket', null=True, blank=True, db_constraint=False)
|
||||||
|
|
||||||
# class PaperOfm(CommonADModel):
|
# class PaperOfm(CommonADModel):
|
||||||
# """TN: 论文申密审批表单"""
|
# """TN: 论文申密审批表单"""
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord )
|
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity, PatentInfo)
|
||||||
# , Publicity,
|
# Publicity, PatetInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
|
||||||
# Publicity, PatentInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
|
|
||||||
from apps.utils.serializers import CustomModelSerializer
|
from apps.utils.serializers import CustomModelSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from apps.utils.constants import EXCLUDE_FIELDS
|
from apps.utils.constants import EXCLUDE_FIELDS
|
||||||
|
from apps.wf.serializers import TicketSimpleSerializer
|
||||||
|
|
||||||
|
|
||||||
class MroomSerializer(CustomModelSerializer):
|
class MroomSerializer(CustomModelSerializer):
|
||||||
|
@ -19,28 +19,30 @@ 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)
|
||||||
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}}
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
mroom = validated_data.pop('mroom')
|
mroom = validated_data.pop('mroom')
|
||||||
slots = validated_data.pop('slots')
|
slots = validated_data.pop('slots')
|
||||||
mdate = validated_data.pop('mdate')
|
mdate = validated_data.pop('mdate')
|
||||||
booking = MroomBooking.objects.create(**validated_data)
|
booking = super().create(validated_data)
|
||||||
MroomSlot.objects.filter(booking=booking).delete()
|
MroomSlot.objects.filter(booking=booking).delete()
|
||||||
for slot in slots:
|
for slot in slots:
|
||||||
if slot < 0 or slot > 47:
|
if slot < 0 or slot > 47:
|
||||||
raise ParseError("时段索引超出范围")
|
raise ParseError("时段索引超出范围")
|
||||||
try:
|
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
|
||||||
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom)
|
if ms_exists:
|
||||||
except Exception as e:
|
raise ParseError("时段已预订,请刷新重选")
|
||||||
raise ParseError(f"时段已预订,请刷新重选-{e}")
|
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
|
||||||
return booking
|
return booking
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
mroom = validated_data.pop('mroom')
|
mroom = validated_data.pop('mroom')
|
||||||
slots = validated_data.pop('slots')
|
slots = validated_data.pop('slots')
|
||||||
|
@ -50,10 +52,10 @@ class MroomBookingSerializer(CustomModelSerializer):
|
||||||
for slot in slots:
|
for slot in slots:
|
||||||
if slot < 0 or slot > 47:
|
if slot < 0 or slot > 47:
|
||||||
raise ParseError("时段索引超出范围")
|
raise ParseError("时段索引超出范围")
|
||||||
try:
|
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
|
||||||
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom)
|
if ms_exists:
|
||||||
except Exception as e:
|
raise ParseError("时段已预订,请刷新重选")
|
||||||
raise ParseError(f"时段已预订,请刷新重选-{e}")
|
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
|
||||||
return booking
|
return booking
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,6 +69,7 @@ class MroomSlotSerializer(CustomModelSerializer):
|
||||||
class LendingSealSerializer(CustomModelSerializer):
|
class LendingSealSerializer(CustomModelSerializer):
|
||||||
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
||||||
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
||||||
|
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LendingSeal
|
model = LendingSeal
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -76,6 +79,7 @@ class LendingSealSerializer(CustomModelSerializer):
|
||||||
class VehicleSerializer(CustomModelSerializer):
|
class VehicleSerializer(CustomModelSerializer):
|
||||||
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
||||||
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
||||||
|
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Vehicle
|
model = Vehicle
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -85,6 +89,7 @@ class VehicleSerializer(CustomModelSerializer):
|
||||||
class FileRecordSerializer(CustomModelSerializer):
|
class FileRecordSerializer(CustomModelSerializer):
|
||||||
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
||||||
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
||||||
|
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FileRecord
|
model = FileRecord
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -97,6 +102,7 @@ class BorrowRecordSerializer(CustomModelSerializer):
|
||||||
borrow_file = serializers.PrimaryKeyRelatedField(queryset=FileRecord.objects.all(), many=True, write_only=True, label="借阅文件")
|
borrow_file = serializers.PrimaryKeyRelatedField(queryset=FileRecord.objects.all(), many=True, write_only=True, label="借阅文件")
|
||||||
file_detail = FileRecordSerializer(source='borrow_file', many=True, read_only=True, label="借阅文件详情")
|
file_detail = FileRecordSerializer(source='borrow_file', many=True, read_only=True, label="借阅文件详情")
|
||||||
file_name = serializers.SerializerMethodField()
|
file_name = serializers.SerializerMethodField()
|
||||||
|
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BorrowRecord
|
model = BorrowRecord
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -106,18 +112,21 @@ class BorrowRecordSerializer(CustomModelSerializer):
|
||||||
return [file.name for file in obj.borrow_file.all()]
|
return [file.name for file in obj.borrow_file.all()]
|
||||||
|
|
||||||
|
|
||||||
# class PublicitySerializer(CustomModelSerializer):
|
class PublicitySerializer(CustomModelSerializer):
|
||||||
# class Meta:
|
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
|
||||||
# model = Publicity
|
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
|
||||||
# fields = '__all__'
|
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||||
# read_only_fields = EXCLUDE_FIELDS
|
class Meta:
|
||||||
|
model = Publicity
|
||||||
|
fields = '__all__'
|
||||||
|
read_only_fields = EXCLUDE_FIELDS
|
||||||
|
|
||||||
|
|
||||||
# class PatentInfoSerializer(CustomModelSerializer):
|
class PatentInfoSerializer(CustomModelSerializer):
|
||||||
# class Meta:
|
class Meta:
|
||||||
# model = PatentInfo
|
model = PatentInfo
|
||||||
# fields = '__all__'
|
fields = '__all__'
|
||||||
# read_only_fields = EXCLUDE_FIELDS
|
read_only_fields = EXCLUDE_FIELDS
|
||||||
|
|
||||||
|
|
||||||
# class PaperSerializer(CustomModelSerializer):
|
# class PaperSerializer(CustomModelSerializer):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
from apps.wf.models import Ticket
|
from apps.wf.models import Ticket
|
||||||
# TicketFlow, Transition, Workflow, CustomField, State,
|
# TicketFlow, Transition, Workflow, CustomField, State,
|
||||||
from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord
|
from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord, Publicity, MroomBooking, MroomSlot, PatentInfo
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,9 +10,28 @@ def seal_submit_validate(ins: LendingSeal):
|
||||||
raise ParseError('该日志已提交!')
|
raise ParseError('该日志已提交!')
|
||||||
if ins.mtask and ins.mtask.state == LendingSeal.MTASK_STOP:
|
if ins.mtask and ins.mtask.state == LendingSeal.MTASK_STOP:
|
||||||
raise ParseError('该任务已停止!')
|
raise ParseError('该任务已停止!')
|
||||||
|
|
||||||
|
def bind_mroombooking(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
|
ins = MroomBooking.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
ticket_data = ticket.ticket_data
|
||||||
|
ticket_data.update({
|
||||||
|
't_model': 'mroombooking',
|
||||||
|
't_id': ins.id,
|
||||||
|
})
|
||||||
|
ticket.ticket_data = ticket_data
|
||||||
|
ticket.create_by = ins.create_by
|
||||||
|
ticket.save()
|
||||||
|
if ins.ticket is None:
|
||||||
|
ins.ticket = ticket
|
||||||
|
ins.save()
|
||||||
|
|
||||||
|
def mroombooking_reject(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
|
ins = MroomBooking.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
MroomSlot.objects.filter(booking=ins).update(is_inuse=False)
|
||||||
|
|
||||||
def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
|
def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ins = LendingSeal.objects.get(id=new_ticket_data['t_id'])
|
ins = LendingSeal.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
ins.actual_return_date = None
|
||||||
ticket_data = ticket.ticket_data
|
ticket_data = ticket.ticket_data
|
||||||
ticket_data.update({
|
ticket_data.update({
|
||||||
't_model': 'LendingSeal',
|
't_model': 'LendingSeal',
|
||||||
|
@ -24,6 +43,19 @@ def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
if ins.ticket is None:
|
if ins.ticket is None:
|
||||||
ins.ticket = ticket
|
ins.ticket = ticket
|
||||||
ins.save()
|
ins.save()
|
||||||
|
# 如果驳回到开始状态
|
||||||
|
|
||||||
|
def lending_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
|
||||||
|
try:
|
||||||
|
obj = LendingSeal.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
except LendingSeal.DoesNotExist:
|
||||||
|
raise ParseError("Publicity t_id 不存在")
|
||||||
|
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}
|
||||||
|
|
||||||
|
for k, v in data_save.items():
|
||||||
|
setattr(obj, k, v)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
|
def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ins = Vehicle.objects.get(id=new_ticket_data['t_id'])
|
ins = Vehicle.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
@ -32,6 +64,8 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
't_model': 'Vehicle',
|
't_model': 'Vehicle',
|
||||||
't_id': ins.id,
|
't_id': ins.id,
|
||||||
})
|
})
|
||||||
|
ins.actual_km = None
|
||||||
|
ins.end_time = None
|
||||||
ticket.ticket_data = ticket_data
|
ticket.ticket_data = ticket_data
|
||||||
ticket.create_by = ins.create_by
|
ticket.create_by = ins.create_by
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
@ -39,6 +73,16 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ins.ticket = ticket
|
ins.ticket = ticket
|
||||||
ins.save()
|
ins.save()
|
||||||
|
|
||||||
|
def vehicle_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
|
||||||
|
try:
|
||||||
|
obj = Vehicle.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
except Vehicle.DoesNotExist:
|
||||||
|
raise ParseError("Publicity t_id 不存在")
|
||||||
|
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}
|
||||||
|
|
||||||
|
for k, v in data_save.items():
|
||||||
|
setattr(obj, k, v)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
|
def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ins = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
|
ins = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
@ -47,6 +91,62 @@ def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
't_model': 'BorrowRecord',
|
't_model': 'BorrowRecord',
|
||||||
't_id': ins.id,
|
't_id': ins.id,
|
||||||
})
|
})
|
||||||
|
ins.return_date = None
|
||||||
|
ticket.ticket_data = ticket_data
|
||||||
|
ticket.create_by = ins.create_by
|
||||||
|
ticket.save()
|
||||||
|
if ins.ticket is None:
|
||||||
|
ins.ticket = ticket
|
||||||
|
ins.save()
|
||||||
|
|
||||||
|
def file_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
|
||||||
|
try:
|
||||||
|
obj = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
except BorrowRecord.DoesNotExist:
|
||||||
|
raise ParseError("Publicity t_id 不存在")
|
||||||
|
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}
|
||||||
|
|
||||||
|
for k, v in data_save.items():
|
||||||
|
setattr(obj, k, v)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
def bind_publicity(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
|
ins = Publicity.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
ticket_data = ticket.ticket_data
|
||||||
|
ticket_data.update({
|
||||||
|
't_model': 'publicity',
|
||||||
|
't_id': ins.id,
|
||||||
|
})
|
||||||
|
ins.dept_opinion = None
|
||||||
|
ins.secret_period = None
|
||||||
|
ins.dept_opinion_review = None
|
||||||
|
ins.publicity_opinion = None
|
||||||
|
ticket.ticket_data = ticket_data
|
||||||
|
ticket.create_by = ins.create_by
|
||||||
|
ticket.save()
|
||||||
|
if ins.ticket is None:
|
||||||
|
ins.ticket = ticket
|
||||||
|
ins.save()
|
||||||
|
|
||||||
|
def save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
|
||||||
|
try:
|
||||||
|
obj = Publicity.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
except Publicity.DoesNotExist:
|
||||||
|
raise ParseError("Publicity t_id 不存在")
|
||||||
|
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}
|
||||||
|
|
||||||
|
for k, v in data_save.items():
|
||||||
|
setattr(obj, k, v)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
def bind_patent(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
|
ins = PatentInfo.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
ticket_data = ticket.ticket_data
|
||||||
|
ticket_data.update({
|
||||||
|
't_model': 'patent',
|
||||||
|
't_id': ins.id,
|
||||||
|
})
|
||||||
ticket.ticket_data = ticket_data
|
ticket.ticket_data = ticket_data
|
||||||
ticket.create_by = ins.create_by
|
ticket.create_by = ins.create_by
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet, FileborrowViewSet)
|
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet,
|
||||||
|
FileborrowViewSet, PublicityViewSet, PatentInfoViewSet)
|
||||||
# SealModelViewSet,
|
# SealModelViewSet,
|
||||||
# , PublicityViewSet, PatentInfoViewSet, PaperViewSet, PlatformViewSet,
|
# , PublicityViewSet, , PaperViewSet, PlatformViewSet,
|
||||||
# ProjectViewSet, PatentRecordViewSet, PaperRecordViewSet, ProjectApprovalViewSet, ProjectInfoViewSet)
|
# ProjectViewSet, PatentRecordViewSet, PaperRecordViewSet, ProjectApprovalViewSet, ProjectInfoViewSet)
|
||||||
|
|
||||||
API_BASE_URL = 'api/ofm/'
|
API_BASE_URL = 'api/ofm/'
|
||||||
|
@ -17,9 +18,8 @@ router.register('lendingseal', LendingSealViewSet, basename='lendingseal')
|
||||||
router.register('vehicle', VehicleViewSet, basename='vehicle')
|
router.register('vehicle', VehicleViewSet, basename='vehicle')
|
||||||
router.register('filerecord', FilerecordViewSet, basename='filerecord')
|
router.register('filerecord', FilerecordViewSet, basename='filerecord')
|
||||||
router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
|
router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
|
||||||
# router.register('publicity', PublicityViewSet, basename='publicity')
|
router.register('publicity', PublicityViewSet, basename='publicity')
|
||||||
# router.register('patentinfo', PatentInfoViewSet, basename='patentinfo')
|
router.register('patentinfo', PatentInfoViewSet, basename='patentinfo')
|
||||||
|
|
||||||
# router.register('paper', PaperViewSet, basename='paper')
|
# router.register('paper', PaperViewSet, basename='paper')
|
||||||
# router.register('platform', PlatformViewSet, basename='platform')
|
# router.register('platform', PlatformViewSet, basename='platform')
|
||||||
# router.register('project', ProjectViewSet, basename='project')
|
# router.register('project', ProjectViewSet, basename='project')
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
|
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
|
||||||
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord
|
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity, PatentInfo
|
||||||
# Seal
|
# Publicity, , PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
|
||||||
# Publicity, PatentInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
|
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer,
|
||||||
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer, VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer)
|
VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer, PatentInfoSerializer)
|
||||||
# ,SealSerializer,
|
# ,SealSerializer,
|
||||||
# LendingSealSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,
|
# LendingSealSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,
|
||||||
# PatentInfoSerializer, PaperSerializer, PlatformSerializer, ProjectSerializer, ProjectMemberSerializer, PaperRecordSerializer, ProjectApprovalSerializer, ProjectInfoSerializer)
|
# PatentInfoSerializer, PaperSerializer, PlatformSerializer, ProjectSerializer, ProjectMemberSerializer, PaperRecordSerializer, ProjectApprovalSerializer, ProjectInfoSerializer)
|
||||||
|
@ -29,7 +29,7 @@ class MroomBookingViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = MroomBooking.objects.all()
|
queryset = MroomBooking.objects.all()
|
||||||
serializer_class = MroomBookingSerializer
|
serializer_class = MroomBookingSerializer
|
||||||
select_related_fields = ["create_by"]
|
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):
|
||||||
|
@ -67,7 +67,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"], {}))
|
||||||
|
@ -82,14 +82,26 @@ class MroomBookingViewSet(CustomModelViewSet):
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
ins:MroomBooking = self.get_object()
|
ins:MroomBooking = self.get_object()
|
||||||
if ins.create_by != self.request.user:
|
ticket = ins.ticket
|
||||||
|
if ticket is None or ticket.state.type == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ParseError("存在审批单,不允许修改")
|
||||||
|
if ins.create_by and ins.create_by != self.request.user:
|
||||||
raise ParseError("只允许创建者修改")
|
raise ParseError("只允许创建者修改")
|
||||||
return super().perform_update(serializer)
|
return super().perform_update(serializer)
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.create_by != self.request.user:
|
if instance.create_by and instance.create_by != self.request.user:
|
||||||
raise ParseError("只允许创建者删除")
|
raise ParseError("只允许创建者删除")
|
||||||
return super().perform_destroy(instance)
|
ticket = instance.ticket
|
||||||
|
if ticket is None or ticket.state.type == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ParseError("存在审批单,不允许删除")
|
||||||
|
if ticket:
|
||||||
|
ticket.delete()
|
||||||
|
instance.delete()
|
||||||
|
|
||||||
|
|
||||||
class MroomSlotViewSet(CustomListModelMixin, CustomGenericViewSet):
|
class MroomSlotViewSet(CustomListModelMixin, CustomGenericViewSet):
|
||||||
|
@ -99,7 +111,7 @@ class MroomSlotViewSet(CustomListModelMixin, CustomGenericViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = MroomSlot.objects.all()
|
queryset = MroomSlot.objects.all()
|
||||||
serializer_class = MroomSlotSerializer
|
serializer_class = MroomSlotSerializer
|
||||||
filterset_fields = ["mroom", "mdate", "booking"]
|
filterset_fields = ["mroom", "mdate", "booking", "is_inuse"]
|
||||||
|
|
||||||
|
|
||||||
class LendingSealViewSet(CustomModelViewSet):
|
class LendingSealViewSet(CustomModelViewSet):
|
||||||
|
@ -112,7 +124,7 @@ class LendingSealViewSet(CustomModelViewSet):
|
||||||
queryset = LendingSeal.objects.all()
|
queryset = LendingSeal.objects.all()
|
||||||
serializer_class = LendingSealSerializer
|
serializer_class = LendingSealSerializer
|
||||||
filterset_class = SealFilter
|
filterset_class = SealFilter
|
||||||
ordering = ["create_time"]
|
ordering = ["-create_time"]
|
||||||
data_filter = True
|
data_filter = True
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +135,7 @@ class VehicleViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
queryset = Vehicle.objects.all()
|
queryset = Vehicle.objects.all()
|
||||||
serializer_class = VehicleSerializer
|
serializer_class = VehicleSerializer
|
||||||
ordering = ["create_time"]
|
ordering = ["-create_time"]
|
||||||
|
|
||||||
|
|
||||||
class FilerecordViewSet(CustomModelViewSet):
|
class FilerecordViewSet(CustomModelViewSet):
|
||||||
|
@ -134,7 +146,7 @@ class FilerecordViewSet(CustomModelViewSet):
|
||||||
queryset = FileRecord.objects.all()
|
queryset = FileRecord.objects.all()
|
||||||
serializer_class = FileRecordSerializer
|
serializer_class = FileRecordSerializer
|
||||||
filterset_fields = [ "name", "number"]
|
filterset_fields = [ "name", "number"]
|
||||||
ordering = ["create_time", "number", "name"]
|
ordering = ["-create_time", "number", "name"]
|
||||||
|
|
||||||
|
|
||||||
class FileborrowViewSet(CustomModelViewSet):
|
class FileborrowViewSet(CustomModelViewSet):
|
||||||
|
@ -145,29 +157,29 @@ class FileborrowViewSet(CustomModelViewSet):
|
||||||
queryset = BorrowRecord.objects.all()
|
queryset = BorrowRecord.objects.all()
|
||||||
serializer_class = BorrowRecordSerializer
|
serializer_class = BorrowRecordSerializer
|
||||||
filterset_class = BorrowRecordFilter
|
filterset_class = BorrowRecordFilter
|
||||||
ordering = ["create_time"]
|
ordering = ["-create_time"]
|
||||||
|
|
||||||
|
|
||||||
# class PublicityViewSet(CustomModelViewSet):
|
class PublicityViewSet(CustomModelViewSet):
|
||||||
# """list: 公告
|
"""list: 公告
|
||||||
|
|
||||||
# 公告
|
公告
|
||||||
# """
|
"""
|
||||||
# queryset = Publicity.objects.all()
|
queryset = Publicity.objects.all()
|
||||||
# serializer_class = PublicitySerializer
|
serializer_class = PublicitySerializer
|
||||||
# filterset_fields = ["title","number"]
|
filterset_fields = ["title","number"]
|
||||||
# ordering = ["create_time", "number"]
|
ordering = ["-create_time", "number"]
|
||||||
|
|
||||||
|
|
||||||
# class PatentInfoViewSet(CustomModelViewSet):
|
class PatentInfoViewSet(CustomModelViewSet):
|
||||||
# """list: 专利
|
"""list: 专利
|
||||||
|
|
||||||
# 专利
|
专利
|
||||||
# """
|
"""
|
||||||
# queryset = PatentInfo.objects.all()
|
queryset = PatentInfo.objects.all()
|
||||||
# serializer_class = PatentInfoSerializer
|
serializer_class = PatentInfoSerializer
|
||||||
# filterset_fields = ["name", "author", "type"]
|
filterset_fields = ["name", "author", "type"]
|
||||||
# ordering = ["create_time", "name", "author", "type"]
|
ordering = ["-create_time", "name", "author", "type"]
|
||||||
|
|
||||||
|
|
||||||
# class PaperViewSet(CustomModelViewSet):
|
# class PaperViewSet(CustomModelViewSet):
|
||||||
|
|
|
@ -31,7 +31,6 @@ class UtaskSerializer(CustomModelSerializer):
|
||||||
"priority": {"required": False, "allow_null": True},
|
"priority": {"required": False, "allow_null": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
if not validated_data.get('number', None):
|
if not validated_data.get('number', None):
|
||||||
validated_data["number"] = Utask.get_a_number()
|
validated_data["number"] = Utask.get_a_number()
|
||||||
|
|
|
@ -43,7 +43,6 @@ class PuPlanItemSerializer(CustomModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = EXCLUDE_FIELDS + ['pu_order']
|
read_only_fields = EXCLUDE_FIELDS + ['pu_order']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
pu_plan = validated_data['pu_plan']
|
pu_plan = validated_data['pu_plan']
|
||||||
if pu_plan.state != PuPlan.PUPLAN_CREATE:
|
if pu_plan.state != PuPlan.PUPLAN_CREATE:
|
||||||
|
@ -65,7 +64,6 @@ class PuPlanItemSerializer(CustomModelSerializer):
|
||||||
else:
|
else:
|
||||||
validated_data['belong_dept'] = belong_dept
|
validated_data['belong_dept'] = belong_dept
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop('pu_plan')
|
validated_data.pop('pu_plan')
|
||||||
pu_plan = instance.pu_plan
|
pu_plan = instance.pu_plan
|
||||||
|
@ -114,7 +112,6 @@ class PuOrderItemSerializer(CustomModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = EXCLUDE_FIELDS_BASE + ['delivered_count']
|
read_only_fields = EXCLUDE_FIELDS_BASE + ['delivered_count']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
pu_order = validated_data['pu_order']
|
pu_order = validated_data['pu_order']
|
||||||
material = validated_data['material']
|
material = validated_data['material']
|
||||||
|
@ -126,7 +123,6 @@ class PuOrderItemSerializer(CustomModelSerializer):
|
||||||
PumService.cal_pu_order_total_price(pu_order)
|
PumService.cal_pu_order_total_price(pu_order)
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop('material')
|
validated_data.pop('material')
|
||||||
validated_data.pop('pu_order')
|
validated_data.pop('pu_order')
|
||||||
|
|
|
@ -80,7 +80,6 @@ class PuPlanItemViewSet(CustomModelViewSet):
|
||||||
ordering_fields = ['create_time', 'material', 'need_date', 'need_count']
|
ordering_fields = ['create_time', 'material', 'need_date', 'need_count']
|
||||||
ordering = ['create_time']
|
ordering = ['create_time']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
pu_plan = instance.pu_plan
|
pu_plan = instance.pu_plan
|
||||||
|
@ -104,7 +103,6 @@ class PuOrderViewSet(CustomModelViewSet):
|
||||||
search_fields = ['number', 'supplier__name', 'item_puorder__material__name', 'item_puorder__material__specification', 'item_puorder__material__model']
|
search_fields = ['number', 'supplier__name', 'item_puorder__material__name', 'item_puorder__material__specification', 'item_puorder__material__model']
|
||||||
select_related_fields = ['create_by', 'update_by', 'supplier']
|
select_related_fields = ['create_by', 'update_by', 'supplier']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.state != PuOrder.PUORDER_CREATE:
|
if instance.state != PuOrder.PUORDER_CREATE:
|
||||||
raise ParseError('采购订单非创建中不可删除')
|
raise ParseError('采购订单非创建中不可删除')
|
||||||
|
@ -145,7 +143,6 @@ class PuOrderItemViewSet(CustomModelViewSet):
|
||||||
filterset_fields = ['material', 'pu_order']
|
filterset_fields = ['material', 'pu_order']
|
||||||
ordering = ['create_time']
|
ordering = ['create_time']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
pu_order = instance.pu_order
|
pu_order = instance.pu_order
|
||||||
if pu_order.state != PuOrder.PUORDER_CREATE:
|
if pu_order.state != PuOrder.PUORDER_CREATE:
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-10 01:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('qm', '0053_alter_ftest_is_ok'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ptest',
|
||||||
|
name='val_xj',
|
||||||
|
field=models.CharField(blank=True, choices=[('S', '析晶'), ('R', '不析晶'), ('θ', '未化')], help_text=[('S', '析晶'), ('R', '不析晶'), ('θ', '未化')], max_length=10, null=True, verbose_name='析晶'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -353,13 +353,12 @@ class Ftest(CommonBDModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init_by_qct(cls, qct, test_user, test_date):
|
def init_by_qct(cls, qct, test_user, test_date):
|
||||||
with transaction.atomic():
|
ftest = Ftest.objects.create(qct=qct, test_user=test_user, test_date=test_date)
|
||||||
ftest = Ftest.objects.create(qct=qct, test_user=test_user, test_date=test_date)
|
for testitem in qct.testitems.all():
|
||||||
for testitem in qct.testitems.all():
|
FtestItem.objects.create(ftest=ftest, testitem=testitem)
|
||||||
FtestItem.objects.create(ftest=ftest, testitem=testitem)
|
for defect in qct.defects.all():
|
||||||
for defect in qct.defects.all():
|
FtestDefect.objects.create(ftest=ftest, defect=defect)
|
||||||
FtestDefect.objects.create(ftest=ftest, defect=defect)
|
return ftest
|
||||||
return ftest
|
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def cal_count_notok(cls, ftestwork: FtestWork):
|
# def cal_count_notok(cls, ftestwork: FtestWork):
|
||||||
|
@ -446,7 +445,7 @@ class Ptest(CommonAModel):
|
||||||
val_tg = models.FloatField("Tg", help_text='℃', null=True, blank=True)
|
val_tg = models.FloatField("Tg", help_text='℃', null=True, blank=True)
|
||||||
val_tf = models.FloatField("Tf", help_text='℃', null=True, blank=True)
|
val_tf = models.FloatField("Tf", help_text='℃', null=True, blank=True)
|
||||||
val_xj = models.CharField(
|
val_xj = models.CharField(
|
||||||
'析晶', max_length=10, default='S', choices=PTEST_XJ_VALS, help_text=list(PTEST_XJ_VALS))
|
'析晶', max_length=10, null=True, blank=True, choices=PTEST_XJ_VALS, help_text=list(PTEST_XJ_VALS))
|
||||||
val_pzxs = models.FloatField(
|
val_pzxs = models.FloatField(
|
||||||
'膨胀系数', help_text='30-300℃', null=True, blank=True)
|
'膨胀系数', help_text='30-300℃', null=True, blank=True)
|
||||||
val_zgwd = models.FloatField('升至最高温度', null=True, blank=True)
|
val_zgwd = models.FloatField('升至最高温度', null=True, blank=True)
|
||||||
|
|
|
@ -215,34 +215,33 @@ class FtestWorkCreateUpdateSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ftestworkdefect = validated_data.pop("ftestworkdefect", [])
|
ftestworkdefect = validated_data.pop("ftestworkdefect", [])
|
||||||
with transaction.atomic():
|
ins: FtestWork = super().create(validated_data)
|
||||||
ins: FtestWork = super().create(validated_data)
|
for ftestworkdefect in ftestworkdefect:
|
||||||
for ftestworkdefect in ftestworkdefect:
|
if ftestworkdefect['count'] > 0:
|
||||||
if ftestworkdefect['count'] > 0:
|
FtestworkDefect.objects.create(ftestwork=ins, **ftestworkdefect)
|
||||||
FtestworkDefect.objects.create(ftestwork=ins, **ftestworkdefect)
|
if ins.qct or ftestworkdefect:
|
||||||
if ins.qct or ftestworkdefect:
|
ins.cal_count()
|
||||||
ins.cal_count()
|
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
ftestworkdefect = validated_data.pop("ftestworkdefect", [])
|
ftestworkdefect = validated_data.pop("ftestworkdefect", [])
|
||||||
ins:FtestWork = super().update(instance, validated_data)
|
ins:FtestWork = super().update(instance, validated_data)
|
||||||
with transaction.atomic():
|
|
||||||
fd_ids = []
|
fd_ids = []
|
||||||
for item in ftestworkdefect:
|
for item in ftestworkdefect:
|
||||||
if item["count"] > 0:
|
if item["count"] > 0:
|
||||||
try:
|
try:
|
||||||
ins = FtestworkDefect.objects.get(ftestwork=ins, defect=item["defect"])
|
ins = FtestworkDefect.objects.get(ftestwork=ins, defect=item["defect"])
|
||||||
except FtestworkDefect.DoesNotExist:
|
except FtestworkDefect.DoesNotExist:
|
||||||
raise ParseError("新的缺陷项!")
|
raise ParseError("新的缺陷项!")
|
||||||
except FtestworkDefect.MultipleObjectsReturned:
|
except FtestworkDefect.MultipleObjectsReturned:
|
||||||
raise ParseError("缺陷项重复!")
|
raise ParseError("缺陷项重复!")
|
||||||
ins.count = item["count"]
|
ins.count = item["count"]
|
||||||
ins.save()
|
ins.save()
|
||||||
fd_ids.append(ins.id)
|
fd_ids.append(ins.id)
|
||||||
FtestworkDefect.objects.filter(ftestwork=ins).exclude(id__in=fd_ids).delete()
|
FtestworkDefect.objects.filter(ftestwork=ins).exclude(id__in=fd_ids).delete()
|
||||||
if ins.qct or ftestworkdefect:
|
if ins.qct or ftestworkdefect:
|
||||||
ins.cal_count()
|
ins.cal_count()
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
class FtestWorkSerializer(CustomModelSerializer):
|
class FtestWorkSerializer(CustomModelSerializer):
|
||||||
|
@ -292,24 +291,24 @@ class FtestSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ftestitems = validated_data.pop('ftestitems', [])
|
ftestitems = validated_data.pop('ftestitems', [])
|
||||||
with transaction.atomic():
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
for item in ftestitems:
|
for item in ftestitems:
|
||||||
FtestItem.objects.create(ftest=instance, **item)
|
FtestItem.objects.create(ftest=instance, **item)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
validated_data.pop('ftest_work', None)
|
validated_data.pop('ftest_work', None)
|
||||||
ftestitems = validated_data.pop('ftestitems', [])
|
ftestitems = validated_data.pop('ftestitems', [])
|
||||||
with transaction.atomic():
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
for item in ftestitems:
|
for item in ftestitems:
|
||||||
id = item.get('id', None)
|
id = item.get('id', None)
|
||||||
if id:
|
if id:
|
||||||
ftestitem = FtestItem.objects.get(id=id)
|
ftestitem = FtestItem.objects.get(id=id)
|
||||||
ftestitem.test_val = item['test_val']
|
ftestitem.test_val = item['test_val']
|
||||||
ftestitem.check_val = item['check_val']
|
ftestitem.check_val = item['check_val']
|
||||||
ftestitem.save()
|
ftestitem.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
@ -364,75 +363,75 @@ class FtestProcessSerializer(CustomModelSerializer):
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ftestitems = validated_data.pop('ftestitems', [])
|
ftestitems = validated_data.pop('ftestitems', [])
|
||||||
ftestdefects = validated_data.pop('ftestdefects', [])
|
ftestdefects = validated_data.pop('ftestdefects', [])
|
||||||
with transaction.atomic():
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
for item in ftestitems:
|
for item in ftestitems:
|
||||||
FtestItem.objects.create(ftest=instance, **item)
|
FtestItem.objects.create(ftest=instance, **item)
|
||||||
is_ok = True
|
is_ok = True
|
||||||
defect_main = None
|
defect_main = None
|
||||||
has_is_main = False
|
has_is_main = False
|
||||||
for item2 in ftestdefects:
|
for item2 in ftestdefects:
|
||||||
defect:Defect = item2["defect"]
|
defect:Defect = item2["defect"]
|
||||||
if defect.okcate in [Defect.DEFECT_NOTOK] and item2["has"]:
|
if defect.okcate in [Defect.DEFECT_NOTOK] and item2["has"]:
|
||||||
is_ok = False
|
is_ok = False
|
||||||
if not has_is_main:
|
if not has_is_main:
|
||||||
item2["is_main"] = True
|
item2["is_main"] = True
|
||||||
has_is_main = True
|
has_is_main = True
|
||||||
defect_main = defect
|
defect_main = defect
|
||||||
else:
|
else:
|
||||||
item2["is_main"] = False
|
item2["is_main"] = False
|
||||||
FtestDefect.objects.create(ftest=instance, **item2)
|
FtestDefect.objects.create(ftest=instance, **item2)
|
||||||
if not is_ok:
|
if not is_ok:
|
||||||
instance.defect_main = defect_main
|
instance.defect_main = defect_main
|
||||||
else:
|
else:
|
||||||
instance.defect_main = None
|
instance.defect_main = None
|
||||||
instance.is_ok = is_ok
|
instance.is_ok = is_ok
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
ftestitems = validated_data.pop('ftestitems', [])
|
ftestitems = validated_data.pop('ftestitems', [])
|
||||||
ftestdefects = validated_data.pop('ftestdefects', [])
|
ftestdefects = validated_data.pop('ftestdefects', [])
|
||||||
with transaction.atomic():
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
for item in ftestitems:
|
for item in ftestitems:
|
||||||
try:
|
try:
|
||||||
ins = FtestItem.objects.get(testitem = item["testitem"], ftest=instance)
|
ins = FtestItem.objects.get(testitem = item["testitem"], ftest=instance)
|
||||||
except FtestItem.DoesNotExist:
|
except FtestItem.DoesNotExist:
|
||||||
ins = FtestItem.objects.create(ftest=instance, **item)
|
ins = FtestItem.objects.create(ftest=instance, **item)
|
||||||
for k, v in item.items():
|
for k, v in item.items():
|
||||||
setattr(ins, k, v)
|
setattr(ins, k, v)
|
||||||
ins.save()
|
ins.save()
|
||||||
is_ok = True
|
is_ok = True
|
||||||
defect_main = None
|
defect_main = None
|
||||||
has_is_main = False
|
has_is_main = False
|
||||||
for item2 in ftestdefects:
|
for item2 in ftestdefects:
|
||||||
try:
|
try:
|
||||||
ins:FtestDefect = FtestDefect.objects.get(ftest=instance, defect=item2["defect"])
|
ins:FtestDefect = FtestDefect.objects.get(ftest=instance, defect=item2["defect"])
|
||||||
except FtestDefect.MultipleObjectsReturned:
|
except FtestDefect.MultipleObjectsReturned:
|
||||||
myLogger.error(f"缺陷项重复!-ftestid:{instance.id}-defectid:{item2['defect'].id}")
|
myLogger.error(f"缺陷项重复!-ftestid:{instance.id}-defectid:{item2['defect'].id}")
|
||||||
raise ParseError("获取到重复的缺陷项!")
|
raise ParseError("获取到重复的缺陷项!")
|
||||||
except FtestDefect.DoesNotExist:
|
except FtestDefect.DoesNotExist:
|
||||||
ins = FtestDefect.objects.create(ftest=instance, **item2)
|
ins = FtestDefect.objects.create(ftest=instance, **item2)
|
||||||
for k, v in item2.items():
|
for k, v in item2.items():
|
||||||
setattr(ins, k, v)
|
setattr(ins, k, v)
|
||||||
ins.save()
|
ins.save()
|
||||||
if ins.is_main:
|
if ins.is_main:
|
||||||
|
has_is_main = True
|
||||||
|
defect_main = ins.defect
|
||||||
|
if ins.has and ins.defect.okcate in [Defect.DEFECT_NOTOK]:
|
||||||
|
is_ok = False
|
||||||
|
if not has_is_main:
|
||||||
|
ins.is_main = True
|
||||||
has_is_main = True
|
has_is_main = True
|
||||||
defect_main = ins.defect
|
defect_main = ins.defect
|
||||||
if ins.has and ins.defect.okcate in [Defect.DEFECT_NOTOK]:
|
else:
|
||||||
is_ok = False
|
ins.is_main = False
|
||||||
if not has_is_main:
|
ins.save()
|
||||||
ins.is_main = True
|
if not is_ok:
|
||||||
has_is_main = True
|
instance.defect_main = defect_main
|
||||||
defect_main = ins.defect
|
else:
|
||||||
else:
|
instance.defect_main = None
|
||||||
ins.is_main = False
|
instance.is_ok = is_ok
|
||||||
ins.save()
|
instance.save()
|
||||||
if not is_ok:
|
|
||||||
instance.defect_main = defect_main
|
|
||||||
else:
|
|
||||||
instance.defect_main = None
|
|
||||||
instance.is_ok = is_ok
|
|
||||||
instance.save()
|
|
||||||
return instance
|
return instance
|
|
@ -33,7 +33,6 @@ class DefectViewSet(CustomModelViewSet):
|
||||||
filterset_fields = ["cate", "okcate"]
|
filterset_fields = ["cate", "okcate"]
|
||||||
search_fields = ["name", "code"]
|
search_fields = ["name", "code"]
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
QctDefect.objects.filter(defect=instance).delete()
|
QctDefect.objects.filter(defect=instance).delete()
|
||||||
instance.delete()
|
instance.delete()
|
||||||
|
@ -93,7 +92,6 @@ class QctDefectViewSet(CustomModelViewSet):
|
||||||
filterset_fields = ["qct", "defect"]
|
filterset_fields = ["qct", "defect"]
|
||||||
ordering = ["qct", "sort"]
|
ordering = ["qct", "sort"]
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
ins: QctDefect = serializer.save()
|
ins: QctDefect = serializer.save()
|
||||||
if ins.is_default:
|
if ins.is_default:
|
||||||
|
@ -151,7 +149,6 @@ class TestItemViewSet(CustomModelViewSet):
|
||||||
item["affects_name"] = ";".join([affects_dict.get(x, '未知') for x in affects])
|
item["affects_name"] = ";".join([affects_dict.get(x, '未知') for x in affects])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
QctTestItem.objects.filter(testitem=instance).delete()
|
QctTestItem.objects.filter(testitem=instance).delete()
|
||||||
instance.delete()
|
instance.delete()
|
||||||
|
@ -238,19 +235,16 @@ class FtestViewSet(CustomModelViewSet):
|
||||||
ftest_work.count_notok = all_count - ok_count
|
ftest_work.count_notok = all_count - ok_count
|
||||||
ftest_work.save()
|
ftest_work.save()
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
ins: Ftest = serializer.save()
|
ins: Ftest = serializer.save()
|
||||||
if ins.ftest_work:
|
if ins.ftest_work:
|
||||||
self.count_sampling(ins.ftest_work)
|
self.count_sampling(ins.ftest_work)
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
ins: Ftest = serializer.save()
|
ins: Ftest = serializer.save()
|
||||||
if ins.ftest_work:
|
if ins.ftest_work:
|
||||||
self.count_sampling(ins.ftest_work)
|
self.count_sampling(ins.ftest_work)
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
ftest_work = instance.ftest_work
|
ftest_work = instance.ftest_work
|
||||||
instance.delete()
|
instance.delete()
|
||||||
|
@ -284,27 +278,32 @@ class FtestWorkViewSet(CustomModelViewSet):
|
||||||
select_related_fields = ['material', 'mb', 'mb__material']
|
select_related_fields = ['material', 'mb', 'mb__material']
|
||||||
filterset_class = FtestWorkFilter
|
filterset_class = FtestWorkFilter
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
ins:FtestWork = self.get_object()
|
ins:FtestWork = self.get_object()
|
||||||
|
partial = kwargs.pop('partial', False)
|
||||||
if ins.submit_time is not None:
|
if ins.submit_time is not None:
|
||||||
raise ParseError('已提交无法修改')
|
raise ParseError('已提交无法修改')
|
||||||
if ins.ticket and ins.ticket.state.type != State.STATE_TYPE_START:
|
if ins.ticket and ins.ticket.state.type != State.STATE_TYPE_START:
|
||||||
raise ParseError('审批单已进行,无法修改')
|
raise ParseError('审批单已进行,无法修改')
|
||||||
x = super().update(request, *args, **kwargs)
|
serializer = self.get_serializer(ins, data=request.data, partial=partial)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
self.perform_update(serializer)
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
ana_batch_thread(xbatchs=[ins.batch])
|
ana_batch_thread(xbatchs=[ins.batch])
|
||||||
return x
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
ins:FtestWork = self.get_object()
|
ins:FtestWork = self.get_object()
|
||||||
if ins.submit_time is not None:
|
if ins.submit_time is not None:
|
||||||
raise ParseError('已提交无法删除')
|
raise ParseError('已提交无法删除')
|
||||||
if ins.ticket:
|
if ins.ticket:
|
||||||
raise ParseError('存在审批, 无法删除')
|
raise ParseError('存在审批, 无法删除')
|
||||||
x = super().destroy(request, *args, **kwargs)
|
self.perform_destroy(ins)
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
ana_batch_thread(xbatchs=[ins.batch])
|
ana_batch_thread(xbatchs=[ins.batch])
|
||||||
return x
|
return Response(status=204)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
ins = serializer.save()
|
ins = serializer.save()
|
||||||
|
|
|
@ -65,7 +65,6 @@ class OrderViewSet(CustomModelViewSet):
|
||||||
"state": ["exact", "in"],
|
"state": ["exact", "in"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.state != Order.ORDER_CREATE:
|
if instance.state != Order.ORDER_CREATE:
|
||||||
raise ParseError('订单非创建中不可删除')
|
raise ParseError('订单非创建中不可删除')
|
||||||
|
|
|
@ -275,13 +275,11 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
||||||
model = Dept
|
model = Dept
|
||||||
exclude = EXCLUDE_FIELDS + ['third_info']
|
exclude = EXCLUDE_FIELDS + ['third_info']
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ins = super().create(validated_data)
|
ins = super().create(validated_data)
|
||||||
sync_dahua_dept(ins)
|
sync_dahua_dept(ins)
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
ins = super().update(instance, validated_data)
|
ins = super().update(instance, validated_data)
|
||||||
sync_dahua_dept(ins)
|
sync_dahua_dept(ins)
|
||||||
|
|
|
@ -8,8 +8,7 @@ from django_celery_beat.models import (CrontabSchedule, IntervalSchedule,
|
||||||
from django_celery_results.models import TaskResult
|
from django_celery_results.models import TaskResult
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import ParseError, ValidationError, PermissionDenied
|
from rest_framework.exceptions import ParseError, ValidationError, PermissionDenied
|
||||||
from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin,
|
from rest_framework.mixins import RetrieveModelMixin
|
||||||
ListModelMixin, RetrieveModelMixin)
|
|
||||||
from rest_framework.parsers import (JSONParser,
|
from rest_framework.parsers import (JSONParser,
|
||||||
MultiPartParser)
|
MultiPartParser)
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
|
@ -20,7 +19,7 @@ from apps.hrm.models import Employee
|
||||||
from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_WRONG
|
from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_WRONG
|
||||||
from apps.system.filters import DeptFilterSet, UserFilterSet
|
from apps.system.filters import DeptFilterSet, UserFilterSet
|
||||||
# from django_q.models import Task as QTask, Schedule as QSchedule
|
# from django_q.models import Task as QTask, Schedule as QSchedule
|
||||||
from apps.utils.mixins import (CustomCreateModelMixin, MyLoggingMixin)
|
from apps.utils.mixins import (MyLoggingMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from apps.utils.permission import ALL_PERMS
|
from apps.utils.permission import ALL_PERMS
|
||||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||||
|
@ -229,7 +228,7 @@ class PTaskViewSet(CustomModelViewSet):
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
class PTaskResultViewSet(ListModelMixin, RetrieveModelMixin, CustomGenericViewSet):
|
class PTaskResultViewSet(CustomListModelMixin, RetrieveModelMixin, CustomGenericViewSet):
|
||||||
"""
|
"""
|
||||||
list:任务执行结果列表
|
list:任务执行结果列表
|
||||||
|
|
||||||
|
@ -373,7 +372,7 @@ class RoleViewSet(CustomModelViewSet):
|
||||||
ordering = ['create_time']
|
ordering = ['create_time']
|
||||||
|
|
||||||
|
|
||||||
class PostRoleViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet):
|
class PostRoleViewSet(BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, CustomGenericViewSet):
|
||||||
"""岗位/角色关系
|
"""岗位/角色关系
|
||||||
|
|
||||||
岗位/角色关系
|
岗位/角色关系
|
||||||
|
@ -385,7 +384,7 @@ class PostRoleViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
|
||||||
filterset_fields = ['post', 'role']
|
filterset_fields = ['post', 'role']
|
||||||
|
|
||||||
|
|
||||||
class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet):
|
class UserPostViewSet(BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, CustomGenericViewSet):
|
||||||
"""用户/岗位关系
|
"""用户/岗位关系
|
||||||
|
|
||||||
用户/岗位关系
|
用户/岗位关系
|
||||||
|
@ -398,37 +397,13 @@ class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
|
||||||
ordering = ['sort', 'create_time']
|
ordering = ['sort', 'create_time']
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
with transaction.atomic():
|
instance = serializer.save()
|
||||||
instance = serializer.save()
|
user = instance.user
|
||||||
user = instance.user
|
up = UserPost.objects.filter(user=user).order_by(
|
||||||
up = UserPost.objects.filter(user=user).order_by(
|
'sort', 'create_time').first()
|
||||||
'sort', 'create_time').first()
|
if up:
|
||||||
if up:
|
user.belong_dept = up.dept
|
||||||
user.belong_dept = up.dept
|
user.post = up.post
|
||||||
user.post = up.post
|
|
||||||
user.update_by = self.request.user
|
|
||||||
user.save()
|
|
||||||
# 更新人员表
|
|
||||||
ep = Employee.objects.get_queryset(
|
|
||||||
all=True).filter(user=user).first()
|
|
||||||
if ep:
|
|
||||||
ep.belong_dept = user.belong_dept
|
|
||||||
ep.post = user.post
|
|
||||||
ep.is_deleted = False
|
|
||||||
ep.save()
|
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
with transaction.atomic():
|
|
||||||
user = instance.user
|
|
||||||
instance.delete()
|
|
||||||
up = UserPost.objects.filter(user=user).order_by(
|
|
||||||
'sort', 'create_time').first()
|
|
||||||
if up:
|
|
||||||
user.belong_dept = up.dept
|
|
||||||
user.post = up.post
|
|
||||||
else:
|
|
||||||
user.belong_dept = None
|
|
||||||
user.post = None
|
|
||||||
user.update_by = self.request.user
|
user.update_by = self.request.user
|
||||||
user.save()
|
user.save()
|
||||||
# 更新人员表
|
# 更新人员表
|
||||||
|
@ -440,6 +415,28 @@ class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
|
||||||
ep.is_deleted = False
|
ep.is_deleted = False
|
||||||
ep.save()
|
ep.save()
|
||||||
|
|
||||||
|
def perform_destroy(self, instance):
|
||||||
|
user = instance.user
|
||||||
|
instance.delete()
|
||||||
|
up = UserPost.objects.filter(user=user).order_by(
|
||||||
|
'sort', 'create_time').first()
|
||||||
|
if up:
|
||||||
|
user.belong_dept = up.dept
|
||||||
|
user.post = up.post
|
||||||
|
else:
|
||||||
|
user.belong_dept = None
|
||||||
|
user.post = None
|
||||||
|
user.update_by = self.request.user
|
||||||
|
user.save()
|
||||||
|
# 更新人员表
|
||||||
|
ep = Employee.objects.get_queryset(
|
||||||
|
all=True).filter(user=user).first()
|
||||||
|
if ep:
|
||||||
|
ep.belong_dept = user.belong_dept
|
||||||
|
ep.post = user.post
|
||||||
|
ep.is_deleted = False
|
||||||
|
ep.save()
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(CustomModelViewSet):
|
class UserViewSet(CustomModelViewSet):
|
||||||
queryset = User.objects.get_queryset(all=True)
|
queryset = User.objects.get_queryset(all=True)
|
||||||
|
@ -610,7 +607,7 @@ class UserViewSet(CustomModelViewSet):
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
class FileViewSet(CustomCreateModelMixin, RetrieveModelMixin, ListModelMixin, CustomGenericViewSet):
|
class FileViewSet(BulkCreateModelMixin, RetrieveModelMixin, CustomListModelMixin, CustomGenericViewSet):
|
||||||
"""文件上传
|
"""文件上传
|
||||||
|
|
||||||
list:
|
list:
|
||||||
|
@ -651,7 +648,7 @@ class FileViewSet(CustomCreateModelMixin, RetrieveModelMixin, ListModelMixin, Cu
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
class ApkViewSet(MyLoggingMixin, ListModelMixin, CreateModelMixin, GenericViewSet):
|
class ApkViewSet(MyLoggingMixin, CustomListModelMixin, BulkCreateModelMixin, GenericViewSet):
|
||||||
perms_map = {'get': '*', 'post': 'apk.upload'}
|
perms_map = {'get': '*', 'post': 'apk.upload'}
|
||||||
serializer_class = ApkSerializer
|
serializer_class = ApkSerializer
|
||||||
|
|
||||||
|
@ -692,7 +689,7 @@ class ApkViewSet(MyLoggingMixin, ListModelMixin, CreateModelMixin, GenericViewSe
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
class MyScheduleViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, CustomGenericViewSet):
|
class MyScheduleViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
|
||||||
perms_map = {'get': '*', 'post': '*',
|
perms_map = {'get': '*', 'post': '*',
|
||||||
'delete': 'myschedule.delete'}
|
'delete': 'myschedule.delete'}
|
||||||
serializer_class = MyScheduleSerializer
|
serializer_class = MyScheduleSerializer
|
||||||
|
@ -717,7 +714,6 @@ class MyScheduleViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, Cus
|
||||||
return get_description(f"{data['minute']} {data['hour']} {data['day_of_month']} {data['month_of_year']} {data['day_of_week']}")
|
return get_description(f"{data['minute']} {data['hour']} {data['day_of_month']} {data['month_of_year']} {data['day_of_week']}")
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
vdata = serializer.validated_data
|
vdata = serializer.validated_data
|
||||||
vdata['create_by'] = self.request.user # 不可少
|
vdata['create_by'] = self.request.user # 不可少
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework.exceptions import ParseError
|
||||||
myLogger = logging.getLogger('log')
|
myLogger = logging.getLogger('log')
|
||||||
|
|
||||||
|
|
||||||
@auto_log(name='阿里云短信', raise_exception=True, send_mail=True)
|
@auto_log(name='阿里云短信', raise_exception=True, send_mail=False)
|
||||||
def send_sms(phone: str, template_code: int, template_param: dict):
|
def send_sms(phone: str, template_code: int, template_param: dict):
|
||||||
from aliyunsdkcore.client import AcsClient
|
from aliyunsdkcore.client import AcsClient
|
||||||
from aliyunsdkcore.request import CommonRequest
|
from aliyunsdkcore.request import CommonRequest
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
def execute_raw_sql(sql: str, params=None):
|
def execute_raw_sql(sql: str, params=None):
|
||||||
"""执行原始sql并返回rows, columns数据
|
"""执行原始sql并返回rows, columns数据
|
||||||
|
@ -23,7 +25,7 @@ def format_sqldata(columns, rows):
|
||||||
return [columns] + rows, [dict(zip(columns, row)) for row in rows]
|
return [columns] + rows, [dict(zip(columns, row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
def query_all_dict(sql, params=None):
|
def query_all_dict(sql, params=None, with_time_format=False):
|
||||||
'''
|
'''
|
||||||
查询所有结果返回字典类型数据
|
查询所有结果返回字典类型数据
|
||||||
:param sql:
|
:param sql:
|
||||||
|
@ -36,9 +38,19 @@ def query_all_dict(sql, params=None):
|
||||||
else:
|
else:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
columns = [desc[0] for desc in cursor.description]
|
columns = [desc[0] for desc in cursor.description]
|
||||||
|
if with_time_format:
|
||||||
|
results = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
row_dict = {}
|
||||||
|
for col, val in zip(columns, row):
|
||||||
|
if isinstance(val, datetime):
|
||||||
|
val = timezone.make_naive(val).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
row_dict[col] = val
|
||||||
|
results.append(row_dict)
|
||||||
|
return results
|
||||||
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
||||||
|
|
||||||
def query_one_dict(sql, params=None):
|
def query_one_dict(sql, params=None, with_time_format=False):
|
||||||
"""
|
"""
|
||||||
查询一个结果返回字典类型数据
|
查询一个结果返回字典类型数据
|
||||||
:param sql:
|
:param sql:
|
||||||
|
@ -49,6 +61,13 @@ def query_one_dict(sql, params=None):
|
||||||
cursor.execute(sql, params or ()) # 更简洁的参数处理
|
cursor.execute(sql, params or ()) # 更简洁的参数处理
|
||||||
columns = [desc[0] for desc in cursor.description]
|
columns = [desc[0] for desc in cursor.description]
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
if with_time_format:
|
||||||
|
row_dict = {}
|
||||||
|
for col, val in zip(columns, row):
|
||||||
|
if isinstance(val, datetime):
|
||||||
|
val = timezone.make_naive(val).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
row_dict[col] = val
|
||||||
|
return row_dict
|
||||||
return dict(zip(columns, row)) if row else None # 安全处理None情况
|
return dict(zip(columns, row)) if row else None # 安全处理None情况
|
||||||
|
|
||||||
import pymysql
|
import pymysql
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-09-19 01:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0002_alter_state_filter_dept'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflow',
|
||||||
|
name='view_path',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='前端自定义页面路径'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-16 08:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wf', '0003_workflow_view_path'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflow',
|
||||||
|
name='view_path2',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='前端自定义页面路径2'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -21,6 +21,8 @@ class Workflow(CommonAModel):
|
||||||
'标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
'标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
||||||
content_template = models.CharField(
|
content_template = models.CharField(
|
||||||
'内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
'内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
||||||
|
view_path = models.TextField('前端自定义页面路径', null=True, blank=True)
|
||||||
|
view_path2 = models.TextField('前端自定义页面路径2', null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = '工作流'
|
verbose_name = '工作流'
|
||||||
|
|
|
@ -27,7 +27,7 @@ class StateSerializer(CustomModelSerializer):
|
||||||
class WorkflowSimpleSerializer(CustomModelSerializer):
|
class WorkflowSimpleSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Workflow
|
model = Workflow
|
||||||
fields = ['id', 'name', 'key']
|
fields = ['id', 'name', 'key', 'view_path']
|
||||||
|
|
||||||
|
|
||||||
class StateSimpleSerializer(CustomModelSerializer):
|
class StateSimpleSerializer(CustomModelSerializer):
|
||||||
|
|
|
@ -77,7 +77,7 @@ class WfService(object):
|
||||||
"""
|
"""
|
||||||
获取状态可执行的操作
|
获取状态可执行的操作
|
||||||
"""
|
"""
|
||||||
return Transition.objects.filter(is_deleted=False, source_state=state).all()
|
return Transition.objects.filter(is_deleted=False, source_state=state).all().order_by("-attribute_type", "-id")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_ticket_steps(cls, ticket: Ticket):
|
def get_ticket_steps(cls, ticket: Ticket):
|
||||||
|
@ -442,11 +442,17 @@ class WfService(object):
|
||||||
last_log.intervene_type == Transition.TRANSITION_INTERVENE_TYPE_DELIVER or
|
last_log.intervene_type == Transition.TRANSITION_INTERVENE_TYPE_DELIVER or
|
||||||
ticket.in_add_node):
|
ticket.in_add_node):
|
||||||
# 如果状态变化或是转交加签的情况再发送通知
|
# 如果状态变化或是转交加签的情况再发送通知
|
||||||
Thread(target=send_ticket_notice_t, args=(ticket,), daemon=True).start()
|
cls.send_ticket_notice(ticketflow=last_log)
|
||||||
|
|
||||||
# 如果目标状态是脚本则异步执行
|
# 如果目标状态是脚本则异步执行
|
||||||
if state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
|
if state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
|
||||||
run_task.delay(ticket_id=ticket.id)
|
run_task.delay(ticket_id=ticket.id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send_ticket_notice(cls, ticketflow:TicketFlow):
|
||||||
|
# 根据ticketflow发送通知
|
||||||
|
Thread(target=send_ticket_notice_t, args=(ticketflow,), daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def close_by_task(cls, ticket: Ticket, suggestion: str):
|
def close_by_task(cls, ticket: Ticket, suggestion: str):
|
||||||
|
@ -479,10 +485,13 @@ class WfService(object):
|
||||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
||||||
participant=handler, transition=None)
|
participant=handler, transition=None)
|
||||||
def send_ticket_notice_t(ticket: Ticket):
|
cls.task_ticket(ticket=ticket)
|
||||||
|
|
||||||
|
def send_ticket_notice_t(ticketflow: TicketFlow):
|
||||||
"""
|
"""
|
||||||
发送通知
|
发送通知
|
||||||
"""
|
"""
|
||||||
|
ticket = ticketflow.ticket
|
||||||
params = {'workflow': ticket.workflow.name, 'state': ticket.state.name}
|
params = {'workflow': ticket.workflow.name, 'state': ticket.state.name}
|
||||||
if ticket.participant_type == 1:
|
if ticket.participant_type == 1:
|
||||||
# 发送短信通知
|
# 发送短信通知
|
||||||
|
|
|
@ -239,6 +239,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
raise ParseError('请指定查询分类')
|
raise ParseError('请指定查询分类')
|
||||||
return super().filter_queryset(queryset)
|
return super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
新建工单
|
新建工单
|
||||||
|
@ -263,25 +264,25 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
save_ticket_data[key] = ticket_data[key]
|
save_ticket_data[key] = ticket_data[key]
|
||||||
else:
|
else:
|
||||||
save_ticket_data = ticket_data
|
save_ticket_data = ticket_data
|
||||||
with transaction.atomic():
|
|
||||||
ticket = serializer.save(state=start_state,
|
ticket = serializer.save(state=start_state,
|
||||||
create_by=request.user,
|
create_by=request.user,
|
||||||
create_time=timezone.now(),
|
create_time=timezone.now(),
|
||||||
act_state=Ticket.TICKET_ACT_STATE_DRAFT,
|
act_state=Ticket.TICKET_ACT_STATE_DRAFT,
|
||||||
belong_dept=request.user.belong_dept,
|
belong_dept=request.user.belong_dept,
|
||||||
ticket_data=save_ticket_data) # 先创建出来
|
ticket_data=save_ticket_data) # 先创建出来
|
||||||
# 更新title和sn
|
# 更新title和sn
|
||||||
title = vdata.get('title', '')
|
title = vdata.get('title', '')
|
||||||
title_template = ticket.workflow.title_template
|
title_template = ticket.workflow.title_template
|
||||||
if title_template:
|
if title_template:
|
||||||
all_ticket_data = {**rdata, **ticket_data}
|
all_ticket_data = {**rdata, **ticket_data}
|
||||||
title = title_template.format(**all_ticket_data)
|
title = title_template.format(**all_ticket_data)
|
||||||
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
|
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
|
||||||
ticket.sn = sn
|
ticket.sn = sn
|
||||||
ticket.title = title
|
ticket.title = title
|
||||||
ticket.save()
|
ticket.save()
|
||||||
ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
|
ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
|
||||||
handler=request.user, created=True)
|
handler=request.user, created=True)
|
||||||
return Response(TicketSerializer(instance=ticket).data)
|
return Response(TicketSerializer(instance=ticket).data)
|
||||||
|
|
||||||
@action(methods=['get'], detail=False, perms_map={'get': '*'})
|
@action(methods=['get'], detail=False, perms_map={'get': '*'})
|
||||||
|
@ -297,6 +298,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||||
|
@transaction.atomic
|
||||||
def handle(self, request, pk=None):
|
def handle(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
处理工单
|
处理工单
|
||||||
|
@ -307,13 +309,13 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
vdata = serializer.validated_data
|
vdata = serializer.validated_data
|
||||||
new_ticket_data = ticket.ticket_data
|
new_ticket_data = ticket.ticket_data
|
||||||
new_ticket_data.update(**vdata['ticket_data'])
|
new_ticket_data.update(**vdata['ticket_data'])
|
||||||
with transaction.atomic():
|
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
|
||||||
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
|
new_ticket_data=new_ticket_data, handler=request.user,
|
||||||
new_ticket_data=new_ticket_data, handler=request.user,
|
suggestion=vdata.get('suggestion', ''))
|
||||||
suggestion=vdata.get('suggestion', ''))
|
|
||||||
return Response(TicketSerializer(instance=ticket).data)
|
return Response(TicketSerializer(instance=ticket).data)
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||||
|
@transaction.atomic
|
||||||
def deliver(self, request, pk=None):
|
def deliver(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
转交工单
|
转交工单
|
||||||
|
@ -325,15 +327,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
vdata = serializer.validated_data # 校验之后的数据
|
vdata = serializer.validated_data # 校验之后的数据
|
||||||
if not ticket.state.enable_deliver:
|
if not ticket.state.enable_deliver:
|
||||||
raise ParseError('不允许转交')
|
raise ParseError('不允许转交')
|
||||||
with transaction.atomic():
|
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
||||||
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
ticket.participant = vdata['target_user']
|
||||||
ticket.participant = vdata['target_user']
|
ticket.save()
|
||||||
ticket.save()
|
tf = TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
suggestion=vdata.get('suggestion', ''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||||
suggestion=vdata.get('suggestion', ''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_DELIVER,
|
||||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_DELIVER,
|
participant=request.user, transition=None)
|
||||||
participant=request.user, transition=None)
|
WfService.send_ticket_notice(ticketflow=tf)
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
@action(methods=['get'], detail=True, perms_map={'get': '*'})
|
@action(methods=['get'], detail=True, perms_map={'get': '*'})
|
||||||
|
@ -381,11 +383,12 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
ticket.save()
|
ticket.save()
|
||||||
# 接单日志
|
# 接单日志
|
||||||
# 更新工单流转记录
|
# 更新工单流转记录
|
||||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
tf = TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||||
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||||
intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
||||||
participant=request.user, transition=None)
|
participant=request.user, transition=None)
|
||||||
|
WfService.send_ticket_notice(ticketflow=tf)
|
||||||
return Response()
|
return Response()
|
||||||
else:
|
else:
|
||||||
raise ParseError('无需接单')
|
raise ParseError('无需接单')
|
||||||
|
@ -420,11 +423,12 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
ticket.save()
|
ticket.save()
|
||||||
# 更新流转记录
|
# 更新流转记录
|
||||||
suggestion = request.data.get('suggestion', '') # 加签说明
|
suggestion = request.data.get('suggestion', '') # 加签说明
|
||||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
tf = TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||||
participant=request.user, transition=None)
|
participant=request.user, transition=None)
|
||||||
|
WfService.send_ticket_notice(ticketflow=tf)
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer)
|
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer)
|
||||||
|
@ -444,11 +448,12 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
||||||
ticket.save()
|
ticket.save()
|
||||||
# 更新流转记录
|
# 更新流转记录
|
||||||
suggestion = request.data.get('suggestion', '') # 加签意见
|
suggestion = request.data.get('suggestion', '') # 加签意见
|
||||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
tf = TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||||
participant=request.user, transition=None)
|
participant=request.user, transition=None)
|
||||||
|
WfService.send_ticket_notice(ticketflow=tf)
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, perms_map={'post': '*'},
|
@action(methods=['post'], detail=True, perms_map={'post': '*'},
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-10-20 02:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('em', '0022_equipment_cd_req_addr'),
|
||||||
|
('wpm', '0123_mlogbdefect_count_has'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mloguser',
|
||||||
|
name='equipment',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mloguser_equipment', to='em.equipment', verbose_name='生产设备'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mloguser',
|
||||||
|
name='work_start_time',
|
||||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='生产开始时间'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -368,6 +368,9 @@ class MlogUser(BaseModel):
|
||||||
mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE)
|
mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE)
|
||||||
handle_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE)
|
handle_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE)
|
||||||
process = models.ForeignKey(Process, verbose_name='子工序', on_delete=models.CASCADE)
|
process = models.ForeignKey(Process, verbose_name='子工序', on_delete=models.CASCADE)
|
||||||
|
work_start_time = models.DateTimeField('生产开始时间', null=True, blank=True)
|
||||||
|
equipment = models.ForeignKey(
|
||||||
|
Equipment, verbose_name='生产设备', on_delete=models.CASCADE, null=True, blank=True, related_name='mloguser_equipment')
|
||||||
shift = models.ForeignKey(Shift, verbose_name='关联班次', on_delete=models.CASCADE)
|
shift = models.ForeignKey(Shift, verbose_name='关联班次', on_delete=models.CASCADE)
|
||||||
handle_date = models.DateField('操作日期')
|
handle_date = models.DateField('操作日期')
|
||||||
|
|
||||||
|
@ -524,7 +527,7 @@ class Mlogbw(BaseModel):
|
||||||
mlogb.count_real = count
|
mlogb.count_real = count
|
||||||
count_notok = 0
|
count_notok = 0
|
||||||
count_notok_full = 0
|
count_notok_full = 0
|
||||||
# 个追踪的合格b类不分批
|
# 个追踪的合格b类不分开,这里会导致count_ok_full与count_ok一样了,暂时不做处理
|
||||||
tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False)
|
tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False)
|
||||||
tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False).values("ftest__defect_main").annotate(xcount=Count('id'))
|
tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False).values("ftest__defect_main").annotate(xcount=Count('id'))
|
||||||
defect_ids = tqs.values_list("ftest__defect_main", flat=True)
|
defect_ids = tqs.values_list("ftest__defect_main", flat=True)
|
||||||
|
|
|
@ -355,12 +355,12 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
model = Mlog
|
model = Mlog
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = EXCLUDE_FIELDS + \
|
read_only_fields = EXCLUDE_FIELDS + \
|
||||||
['submit_time', 'submit_user', 'material_outs', "handle_date", "shift"]
|
['submit_time', 'submit_user', 'material_outs']
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"batch": {"required": True},
|
"batch": {"required": True},
|
||||||
"shift": {"required": False},
|
"shift": {"required": False},
|
||||||
"material_out": {"required": True},
|
"material_out": {"required": True},
|
||||||
"work_start_time": {"required": True}
|
"work_start_time": {"required": False}
|
||||||
}
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
@ -634,14 +634,20 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
# 时间
|
# 时间
|
||||||
mgroup:Mgroup = attrs['mgroup']
|
mgroup:Mgroup = attrs['mgroup']
|
||||||
work_start_time:datetime = attrs['work_start_time']
|
work_start_time:datetime = attrs.get('work_start_time', None)
|
||||||
handle_date, attrs["shift"] = mgroup.get_shift(work_start_time)
|
if work_start_time:
|
||||||
if mtask and mtask.start_date == mtask.end_date:
|
attrs['handle_date'], attrs["shift"] = mgroup.get_shift(work_start_time)
|
||||||
attrs['handle_date'] = mtask.end_date
|
|
||||||
if attrs['handle_date'] != handle_date:
|
|
||||||
raise ParseError('任务日期与生产日期不一致')
|
|
||||||
else:
|
else:
|
||||||
attrs['handle_date'] = handle_date
|
if "handle_date" in attrs and attrs["handle_date"]:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ParseError('缺少生产日期')
|
||||||
|
if mtask and mtask.start_date == mtask.end_date:
|
||||||
|
if attrs['handle_date'] != mtask.end_date:
|
||||||
|
if work_start_time:
|
||||||
|
raise ParseError('任务日期与生产日期不一致')
|
||||||
|
else:
|
||||||
|
attrs['handle_date'] = mtask.end_date
|
||||||
|
|
||||||
handle_user = attrs.get('handle_user', None)
|
handle_user = attrs.get('handle_user', None)
|
||||||
if handle_user is None and hasattr(self, "request"):
|
if handle_user is None and hasattr(self, "request"):
|
||||||
|
@ -721,11 +727,12 @@ class CountJsonFromSerializer(serializers.Serializer):
|
||||||
class MlogbInSerializer(CustomModelSerializer):
|
class MlogbInSerializer(CustomModelSerializer):
|
||||||
mlogbdefect = MlogbDefectSerializer(many=True, required=False)
|
mlogbdefect = MlogbDefectSerializer(many=True, required=False)
|
||||||
count_json_from = CountJsonFromSerializer(required=False, many=True)
|
count_json_from = CountJsonFromSerializer(required=False, many=True)
|
||||||
|
wprs_in = serializers.ListField(child=serializers.CharField(), label="单个ID的列表", required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Mlogb
|
model = Mlogb
|
||||||
fields = ['id', 'mlog', 'mtask', 'route', 'wm_in', 'count_use', 'count_pn_jgqbl',
|
fields = ['id', 'mlog', 'mtask', 'route', 'wm_in', 'count_use', 'count_pn_jgqbl',
|
||||||
'count_break', 'note', "parent", "mlogbdefect", "count_json_from"]
|
'count_break', 'note', "parent", "mlogbdefect", "count_json_from", "wprs_in"]
|
||||||
extra_kwargs = {'count_use': {'required': True}, 'mtask': {'required': False},
|
extra_kwargs = {'count_use': {'required': True}, 'mtask': {'required': False},
|
||||||
'wm_in': {'required': True, "allow_empty": False}}
|
'wm_in': {'required': True, "allow_empty": False}}
|
||||||
|
|
||||||
|
@ -777,12 +784,14 @@ class MlogbInSerializer(CustomModelSerializer):
|
||||||
mlog: Mlog = validated_data['mlog']
|
mlog: Mlog = validated_data['mlog']
|
||||||
mtask: Mtask = validated_data.get("mtask", None)
|
mtask: Mtask = validated_data.get("mtask", None)
|
||||||
mlogbdefect = validated_data.pop("mlogbdefect", None)
|
mlogbdefect = validated_data.pop("mlogbdefect", None)
|
||||||
|
wprs_in = validated_data.pop("wprs_in", [])
|
||||||
if Mlogb.objects.filter(mlog=mlog, mtask=mtask, wm_in=validated_data['wm_in'], parent=None).exists():
|
if Mlogb.objects.filter(mlog=mlog, mtask=mtask, wm_in=validated_data['wm_in'], parent=None).exists():
|
||||||
raise ParseError('该记录已存在')
|
raise ParseError('该记录已存在')
|
||||||
if mlog.submit_time is not None:
|
if mlog.submit_time is not None:
|
||||||
raise ParseError('生产日志已提交不可编辑')
|
raise ParseError('生产日志已提交不可编辑')
|
||||||
|
|
||||||
ins:Mlogb = super().create(validated_data)
|
ins:Mlogb = super().create(validated_data)
|
||||||
|
validated_data["wprs_in"] = wprs_in
|
||||||
if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
|
if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
|
||||||
mlogb_defect_objects = [
|
mlogb_defect_objects = [
|
||||||
MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()})
|
MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()})
|
||||||
|
@ -878,7 +887,9 @@ class MlogbwCreateUpdateSerializer(CustomModelSerializer):
|
||||||
if wpr:
|
if wpr:
|
||||||
mlogb: Mlogb = validated_data["mlogb"]
|
mlogb: Mlogb = validated_data["mlogb"]
|
||||||
if Mlogbw.objects.filter(mlogb=mlogb, wpr=wpr).exists():
|
if Mlogbw.objects.filter(mlogb=mlogb, wpr=wpr).exists():
|
||||||
raise ParseError('该产品已选入')
|
raise ParseError(f'{wpr.number}-该产品已选入')
|
||||||
|
if Mlogbw.objects.filter(mlogb__mlog__submit_time__isnull=True, wpr=wpr).exists():
|
||||||
|
raise ParseError(f'{wpr.number}-该产品已在其他日志中选入')
|
||||||
ftest_data = validated_data.pop("ftest", None)
|
ftest_data = validated_data.pop("ftest", None)
|
||||||
mlogbw = super().create(validated_data)
|
mlogbw = super().create(validated_data)
|
||||||
if ftest_data:
|
if ftest_data:
|
||||||
|
@ -1149,6 +1160,12 @@ class HandoverbSerializer(CustomModelSerializer):
|
||||||
read_only_fields = EXCLUDE_FIELDS_BASE + ['handover']
|
read_only_fields = EXCLUDE_FIELDS_BASE + ['handover']
|
||||||
extra_kwargs = {'wm': {'required': True}}
|
extra_kwargs = {'wm': {'required': True}}
|
||||||
|
|
||||||
|
class HandoverbListSerializer(CustomModelSerializer):
|
||||||
|
defect_name = serializers.CharField(source="wm.defect.name", read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = Handoverb
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
class HandoverSerializer(CustomModelSerializer):
|
class HandoverSerializer(CustomModelSerializer):
|
||||||
# wm = serializers.PrimaryKeyRelatedField(
|
# wm = serializers.PrimaryKeyRelatedField(
|
||||||
# label='车间库存ID', queryset=WMaterial.objects.all())
|
# label='车间库存ID', queryset=WMaterial.objects.all())
|
||||||
|
@ -1363,6 +1380,8 @@ class HandoverUpdateSerializer(CustomModelSerializer):
|
||||||
model = Handover
|
model = Handover
|
||||||
fields = ['id', 'send_date', 'send_user', 'count', 'count_eweight', 'recive_user', 'note']
|
fields = ['id', 'send_date', 'send_user', 'count', 'count_eweight', 'recive_user', 'note']
|
||||||
|
|
||||||
|
class HandoverListSerializer(HandoverSerializer):
|
||||||
|
handoverb = HandoverbListSerializer(many=True, required=False)
|
||||||
|
|
||||||
|
|
||||||
class GenHandoverSerializer(serializers.Serializer):
|
class GenHandoverSerializer(serializers.Serializer):
|
||||||
|
@ -1473,10 +1492,17 @@ class MlogUserSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MlogUser
|
model = MlogUser
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = EXCLUDE_FIELDS_BASE
|
read_only_fields = EXCLUDE_FIELDS_BASE + ["shift", "handle_date"]
|
||||||
|
extra_kwargs = {
|
||||||
|
"work_start_time": {"required": True}
|
||||||
|
}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
mlog:Mlog = validated_data["mlog"]
|
mlog:Mlog = validated_data["mlog"]
|
||||||
|
work_start_time:datetime = validated_data["work_start_time"]
|
||||||
|
if mlog.work_start_time < mlog.work_start_time:
|
||||||
|
raise ParseError("操作时间不能早于日志开始时间")
|
||||||
|
validated_data["handle_date"], validated_data["shift"] = mlog.mgroup.get_shift(work_start_time)
|
||||||
if mlog.submit_time is not None:
|
if mlog.submit_time is not None:
|
||||||
raise ParseError("该日志已提交")
|
raise ParseError("该日志已提交")
|
||||||
process:Process = validated_data["process"]
|
process:Process = validated_data["process"]
|
||||||
|
@ -1512,6 +1538,7 @@ class MlogQuickSerializer(serializers.Serializer):
|
||||||
team = serializers.CharField(label="班组ID", required=False)
|
team = serializers.CharField(label="班组ID", required=False)
|
||||||
equipment = serializers.CharField(label="设备ID", required=False)
|
equipment = serializers.CharField(label="设备ID", required=False)
|
||||||
wm_in = serializers.CharField(label="输入车间库存ID")
|
wm_in = serializers.CharField(label="输入车间库存ID")
|
||||||
|
wprs_in = serializers.ListField(child=serializers.CharField(), label="单个ID的列表", required=False)
|
||||||
count_use = serializers.IntegerField(label="领用数量")
|
count_use = serializers.IntegerField(label="领用数量")
|
||||||
is_fix = serializers.BooleanField(label="是否返修")
|
is_fix = serializers.BooleanField(label="是否返修")
|
||||||
mgroup = serializers.CharField(label="工段ID")
|
mgroup = serializers.CharField(label="工段ID")
|
||||||
|
|
|
@ -11,7 +11,7 @@ from apps.system.models import User
|
||||||
from apps.pm.models import Mtask
|
from apps.pm.models import Mtask
|
||||||
from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule
|
from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule
|
||||||
|
|
||||||
from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect, BatchLog, BatchSt
|
from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect, BatchLog, BatchSt, MlogUser
|
||||||
from apps.mtm.services_2 import cal_material_count
|
from apps.mtm.services_2 import cal_material_count
|
||||||
from apps.wf.models import Ticket
|
from apps.wf.models import Ticket
|
||||||
from apps.wf.services import WfService
|
from apps.wf.services import WfService
|
||||||
|
@ -152,7 +152,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
"""
|
"""
|
||||||
生产日志提交后需要执行的操作
|
生产日志提交后需要执行的操作
|
||||||
"""
|
"""
|
||||||
if mlog.work_start_time > timezone.now():
|
if mlog.work_start_time and mlog.work_start_time > timezone.now():
|
||||||
raise ParseError('操作开始时间不能晚于当前时间')
|
raise ParseError('操作开始时间不能晚于当前时间')
|
||||||
if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
|
if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
|
||||||
raise ParseError('操作结束时间不能早于操作开始时间')
|
raise ParseError('操作结束时间不能早于操作开始时间')
|
||||||
|
@ -385,7 +385,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
mlog.submit_user = user
|
mlog.submit_user = user
|
||||||
mlog.stored_notok = stored_notok
|
mlog.stored_notok = stored_notok
|
||||||
mlog.stored_mgroup = stored_mgroup
|
mlog.stored_mgroup = stored_mgroup
|
||||||
if mlog.work_end_time is None:
|
if mlog.work_end_time is None and mlog.work_start_time is not None:
|
||||||
mlog.work_end_time = now
|
mlog.work_end_time = now
|
||||||
mlog.save()
|
mlog.save()
|
||||||
|
|
||||||
|
@ -397,7 +397,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
|
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
|
xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
|
||||||
ana_batch_thread(xbatches)
|
ana_batch_thread(xbatchs=xbatches, mgroup=mlog.mgroup)
|
||||||
|
|
||||||
# 触发单个统计
|
# 触发单个统计
|
||||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||||
|
@ -619,7 +619,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
|
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
|
xbatches = list(Mlogb.objects.filter(mlog=mlog).values_list('batch', flat=True))
|
||||||
ana_batch_thread(xbatches)
|
ana_batch_thread(xbatches, mgroup=mlog.mgroup)
|
||||||
|
|
||||||
# 触发单个统计
|
# 触发单个统计
|
||||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||||
|
@ -739,6 +739,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
||||||
recive_mgroup = handover.recive_mgroup
|
recive_mgroup = handover.recive_mgroup
|
||||||
recive_dept = handover.recive_dept
|
recive_dept = handover.recive_dept
|
||||||
|
|
||||||
|
if handover.type == Handover.H_CHANGE:
|
||||||
|
handover.mtype = Handover.H_MERGE
|
||||||
new_batch = handover.new_batch
|
new_batch = handover.new_batch
|
||||||
if new_batch and mtype != Handover.H_MERGE:
|
if new_batch and mtype != Handover.H_MERGE:
|
||||||
raise ParseError("只有合并时才能提供新批次号")
|
raise ParseError("只有合并时才能提供新批次号")
|
||||||
|
@ -1024,6 +1026,10 @@ def mlog_submit_validate(ins: Mlog):
|
||||||
raise ParseError('该日志未指定消耗!')
|
raise ParseError('该日志未指定消耗!')
|
||||||
if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists():
|
if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists():
|
||||||
raise ParseError('产出数量不能为0!')
|
raise ParseError('产出数量不能为0!')
|
||||||
|
if ins.is_fix is False and ins.route:
|
||||||
|
process = ins.route.process
|
||||||
|
if Process.objects.filter(parent=process).exists() and not MlogUser.objects.filter(mlog=ins).exists():
|
||||||
|
raise ParseError('该日志子工序信息未完善!')
|
||||||
|
|
||||||
def bind_mlog(ticket: Ticket, transition, new_ticket_data: dict):
|
def bind_mlog(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
ins = Mlog.objects.get(id=new_ticket_data['t_id'])
|
ins = Mlog.objects.get(id=new_ticket_data['t_id'])
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,13 @@ class WprSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Wpr
|
model = Wpr
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
material_name = ret.get("material_name", "")
|
||||||
|
if material_name:
|
||||||
|
ret["process_name"] = material_name.split("|")[-1]
|
||||||
|
return ret
|
||||||
|
|
||||||
class WprDetailSerializer(WprSerializer):
|
class WprDetailSerializer(WprSerializer):
|
||||||
mb_ = MaterialBatchSerializer(source='mb', read_only=True)
|
mb_ = MaterialBatchSerializer(source='mb', read_only=True)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
41
changelog.md
41
changelog.md
|
@ -1,3 +1,44 @@
|
||||||
|
## 2.8.2025101011
|
||||||
|
- feat: 新增功能
|
||||||
|
- Ptest val_xj可为空 [caoqianming]
|
||||||
|
- quick调用serializer时传入request [caoqianming]
|
||||||
|
- ofm-修改model [zty]
|
||||||
|
- Mroombooking添加字段 [caoqianming]
|
||||||
|
- base add_info_for_item 可复用list逻辑 [caoqianming]
|
||||||
|
- 添加wpr查询参数 [caoqianming]
|
||||||
|
- base cquery支持annotate [caoqianming]
|
||||||
|
- mroombooking 返回slots [caoqianming]
|
||||||
|
- fix: 问题修复
|
||||||
|
- p_create_after 自动创建mlogbw时关于exclude语句导致的查询错误 [caoqianming]
|
||||||
|
## 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]
|
||||||
|
|
|
@ -75,46 +75,42 @@ class JSONRequestHandler(BaseHTTPRequestHandler):
|
||||||
with sc_lock: # 加锁,防止竞争
|
with sc_lock: # 加锁,防止竞争
|
||||||
sc = sc_all[addr]
|
sc = sc_all[addr]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if sc is None:
|
if sc is None:
|
||||||
sc = socket.socket()
|
sc = socket.socket()
|
||||||
sc.settimeout(5)
|
sc.settimeout(5)
|
||||||
sc.connect((host, int(port)))
|
sc.connect((host, int(port)))
|
||||||
with sc_lock: # 再次加锁,更新 sc_all
|
|
||||||
sc_all[addr] = sc
|
sc_all[addr] = sc
|
||||||
|
sc.settimeout(0.5)
|
||||||
sc.settimeout(0.5)
|
while True:
|
||||||
while True:
|
try:
|
||||||
try:
|
data = sc.recv(65536)
|
||||||
data = sc.recv(65536)
|
if not data:
|
||||||
if not data:
|
break
|
||||||
|
except (socket.timeout, BlockingIOError):
|
||||||
break
|
break
|
||||||
except (socket.timeout, BlockingIOError):
|
|
||||||
break
|
sc.settimeout(5)
|
||||||
|
sc.sendall(b"R")
|
||||||
sc.settimeout(5)
|
|
||||||
sc.sendall(b"R")
|
data = bytearray()
|
||||||
|
while len(data) < 8:
|
||||||
data = bytearray()
|
chunk = sc.recv(1024)
|
||||||
while len(data) < 8:
|
if not chunk:
|
||||||
chunk = sc.recv(1024)
|
raise ConnectionError("连接中断")
|
||||||
if not chunk:
|
data.extend(chunk)
|
||||||
raise ConnectionError("连接中断")
|
|
||||||
data.extend(chunk)
|
return sc, bytes(data)
|
||||||
|
|
||||||
return sc, bytes(data)
|
except Exception as e:
|
||||||
|
if sc is not None:
|
||||||
except Exception as e:
|
try:
|
||||||
if sc is not None:
|
sc.close()
|
||||||
try:
|
except Exception:
|
||||||
sc.close()
|
pass
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
with sc_lock: # 加锁,清除无效连接
|
|
||||||
sc_all[addr] = None
|
sc_all[addr] = None
|
||||||
self.error(f'采集器通信失败: {e}')
|
self.error(f'采集器通信失败: {e}')
|
||||||
print(traceback.format_exc())
|
return None, None
|
||||||
return None, None
|
|
||||||
|
|
||||||
sc, resp = connect_and_send()
|
sc, resp = connect_and_send()
|
||||||
if sc is None or resp is None:
|
if sc is None or resp is None:
|
||||||
|
@ -122,7 +118,17 @@ class JSONRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
res = handle_bytes(resp)
|
res = handle_bytes(resp)
|
||||||
if isinstance(res, str):
|
if isinstance(res, str):
|
||||||
self.error(res)
|
if res == "数据头不正确":
|
||||||
|
sc, resp = connect_and_send()
|
||||||
|
if sc is None or resp is None:
|
||||||
|
return
|
||||||
|
res = handle_bytes(resp)
|
||||||
|
if isinstance(res, str):
|
||||||
|
self.error(res)
|
||||||
|
else:
|
||||||
|
self.ok(res)
|
||||||
|
else:
|
||||||
|
self.error(res)
|
||||||
else:
|
else:
|
||||||
self.ok(res)
|
self.ok(res)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import django
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
BASE_DIR = os.path.dirname(CUR_DIR)
|
||||||
|
sys.path.insert(0, BASE_DIR)
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from apps.enm.services import insert_mplogx_item
|
||||||
|
from django.utils import timezone
|
||||||
|
from apps.utils.tasks import send_mail_task
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
BASE_DIR = os.path.dirname(CUR_DIR)
|
||||||
|
sys.path.insert(0, BASE_DIR)
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
SERVER = '192.168.1.37'
|
||||||
|
PORT = '8089'
|
||||||
|
PATH = f'http://{SERVER}:{PORT}/GetTagList'
|
||||||
|
|
||||||
|
SERVER2 = '192.168.1.43'
|
||||||
|
PORT2 = '8081'
|
||||||
|
PATH2 = f'http://{SERVER2}:{PORT2}/GetTagList'
|
||||||
|
|
||||||
|
#MIN_LIST = set(range(0,61,5))
|
||||||
|
MIN_LIST = [0]
|
||||||
|
myLogger = logging.getLogger("log")
|
||||||
|
|
||||||
|
|
||||||
|
class MailController:
|
||||||
|
def __init__(self):
|
||||||
|
self.last_sent_time = None
|
||||||
|
self.interval = timedelta(days=1)
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self._is_sending = False
|
||||||
|
|
||||||
|
def should_send_mail(self):
|
||||||
|
now = datetime.now()
|
||||||
|
# thread_name = threading.current_thread().name
|
||||||
|
with self.lock:
|
||||||
|
if self._is_sending:
|
||||||
|
# myLogger.info(f"线程 {thread_name}: 邮件正在发送中,跳过")
|
||||||
|
return False
|
||||||
|
if self.last_sent_time is None:
|
||||||
|
# myLogger.info(f"线程 {thread_name}: 首次发送邮件")
|
||||||
|
self._is_sending = True
|
||||||
|
return True
|
||||||
|
time_since_last = now - self.last_sent_time
|
||||||
|
if time_since_last > self.interval:
|
||||||
|
# myLogger.info(f"线程 {thread_name}: 距离上次发送已超过间隔,允许发送")
|
||||||
|
self._is_sending = True
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# myLogger.info(f"线程 {thread_name}: 距离上次发送不足间隔,跳过")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
with self.lock:
|
||||||
|
self.last_sent_time = datetime.now()
|
||||||
|
self._is_sending = False
|
||||||
|
myLogger.info("邮件发送状态已重置")
|
||||||
|
|
||||||
|
|
||||||
|
mail_controller = MailController()
|
||||||
|
|
||||||
|
|
||||||
|
def send_error_notification(error_message):
|
||||||
|
"""
|
||||||
|
发送错误通知
|
||||||
|
"""
|
||||||
|
if mail_controller.should_send_mail():
|
||||||
|
try:
|
||||||
|
send_mail_task.delay(subject='insert_kvt_error', message=str(error_message))
|
||||||
|
myLogger.error(f"请求组态王失败:{str(error_message)}")
|
||||||
|
except Exception as e:
|
||||||
|
myLogger.exception(f"发送错误邮件失败: {e}")
|
||||||
|
finally:
|
||||||
|
mail_controller.mark_as_sent()
|
||||||
|
|
||||||
|
def fetch_data(timex, enp_mpoint_dict, path):
|
||||||
|
"""
|
||||||
|
从数据库转存到超表
|
||||||
|
"""
|
||||||
|
response = None
|
||||||
|
try:
|
||||||
|
response = requests.get(path, timeout=5)
|
||||||
|
response.raise_for_status() # 如果响应码不是 200,将触发异常
|
||||||
|
except requests.RequestException as e:
|
||||||
|
send_error_notification(e)
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
lines = response.text
|
||||||
|
json_line = [line.strip() for line in lines.split('\n') if line.strip() ]
|
||||||
|
current_object = []
|
||||||
|
# 将碎片分组为完整的 JSON 对象
|
||||||
|
for line in json_line:
|
||||||
|
current_object.append(line.strip()) # 将当前行加入对象
|
||||||
|
if line.strip() == '}': # 遇到结束大括号时
|
||||||
|
try:
|
||||||
|
obj_str = ' '.join(current_object).replace(',}', '}')
|
||||||
|
obj_dict = json.loads(obj_str)
|
||||||
|
insert_mplogx_item(obj_dict.get('strVarName'), obj_dict.get('VarValue'), timex, enp_mpoint_dict)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
send_error_notification(e)
|
||||||
|
current_object = [] # 重置,准备处理下一个对象
|
||||||
|
except Exception as e:
|
||||||
|
send_error_notification(e)
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
last_triggered = None
|
||||||
|
while True:
|
||||||
|
now = timezone.now()
|
||||||
|
now = now.replace(microsecond=0)
|
||||||
|
if now.second in MIN_LIST:
|
||||||
|
if last_triggered != now:
|
||||||
|
last_triggered = now
|
||||||
|
enp_mpoint_dict= {}
|
||||||
|
threads = [
|
||||||
|
threading.Thread(target=fetch_data, args=(now, enp_mpoint_dict, PATH), daemon=True),
|
||||||
|
threading.Thread(target=fetch_data, args=(now, enp_mpoint_dict, PATH2), daemon=True)
|
||||||
|
]
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join(timeout=10) # 设置超时防止线程挂起
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
get_data()
|
|
@ -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.2025101011'
|
||||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
Loading…
Reference in New Issue