This commit is contained in:
zty 2024-12-18 14:16:14 +08:00
commit c08dff56bf
17 changed files with 270 additions and 45 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2024-12-17 06:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0045_process_type'),
]
operations = [
migrations.AddField(
model_name='process',
name='mtype',
field=models.PositiveSmallIntegerField(choices=[(10, '常规'), (20, '切分'), (30, '合并')], default=10, verbose_name='工序生产类型'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2024-12-18 00:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0046_process_mtype'),
]
operations = [
migrations.AddField(
model_name='route',
name='div_number',
field=models.PositiveSmallIntegerField(blank=True, default=1, verbose_name='切分数量'),
),
]

View File

@ -1,7 +1,6 @@
from django.db import models
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
from django.db.models import Subquery, OuterRef
from rest_framework.exceptions import ValidationError, ParseError
from rest_framework.exceptions import ParseError
from apps.utils.models import CommonBDModel
class Process(CommonBModel):
@ -10,8 +9,14 @@ class Process(CommonBModel):
"""
PRO_PROD = 10
RPO_TEST = 20
PRO_NORMAL = 10
PRO_DIV = 20
PRO_MERGE = 30
name = models.CharField('工序名称', max_length=100)
type = models.PositiveSmallIntegerField("工序类型", default=PRO_PROD, choices=((PRO_PROD, '生产工序'), (RPO_TEST, '检验工序')))
mtype = models.PositiveSmallIntegerField("工序生产类型", default=PRO_NORMAL, choices=((PRO_NORMAL, '常规'), (PRO_DIV, '切分'), (PRO_MERGE, '合并')))
cate = models.CharField('大类', max_length=10, default='')
sort = models.PositiveSmallIntegerField('排序', default=1)
instruction = models.ForeignKey(
@ -221,6 +226,7 @@ class Route(CommonADModel):
Material, verbose_name='主要输出物料', on_delete=models.CASCADE, related_name='route_material_out', null=True, blank=True)
is_count_utask = models.BooleanField('是否主任务统计', default=False)
out_rate = models.FloatField('出材率', default=100, blank=True)
div_number = models.PositiveSmallIntegerField('切分数量', default=1, blank=True)
hour_work = models.FloatField('工时', null=True, blank=True)
batch_bind = models.BooleanField('是否绑定批次', default=True)

View File

@ -170,8 +170,11 @@ class RouteSerializer(CustomModelSerializer):
attrs['material'] = attrs['routepack'].material
if 'mgroup' in attrs and attrs['mgroup']:
attrs['process'] = attrs['mgroup'].process
if attrs.get('process', None) is None:
process: Process = attrs.get('process', None)
if process is None:
raise ParseError('未提供操作工序')
if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) <= 1:
raise ParseError('切分数量必须大于1')
return super().validate(attrs)
def gen_material_out(self, instance: Route):

View File

@ -25,7 +25,8 @@ class PmService:
while indx < rate_len:
if indx + 1 == rate_len: # 循环到最后一步
break
xcount = xcount/(rate_list[indx+1]/100)
rate, div_number = rate_list[indx+1]
xcount = xcount/(rate/100)/div_number
indx = indx + 1
r_list.append(math.ceil(xcount))
return r_list
@ -130,7 +131,7 @@ class PmService:
if not rqs.exists():
raise ParseError('未配置工艺路线')
# 通过出材率校正任务数, out_rate 默认为 100
out_rate_list = [rq.out_rate for rq in rqs]
out_rate_list = [(rq.out_rate, rq.div_number) for rq in rqs]
count_task_list = cls.cal_real_task_count(count,out_rate_list)
# 创建小任务
for ind, val in enumerate(rqs):

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.12 on 2024-12-17 01:38
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),
('qm', '0027_ftestwork_ticket'),
]
operations = [
migrations.CreateModel(
name='Defect',
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=50, verbose_name='名称')),
('code', models.CharField(max_length=50, verbose_name='标识')),
('cate', models.CharField(choices=[('尺寸', '尺寸'), ('外观', '外观'), ('内质', '内质')], max_length=50, verbose_name='分类')),
('okcate', models.PositiveSmallIntegerField(choices=[(10, '合格'), (20, '合格B类'), (30, '不合格')], default=30, verbose_name='不合格分类')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='defect_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='defect_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,11 +1,26 @@
from django.db import models
from apps.system.models import CommonAModel, CommonADModel, User
from apps.system.models import CommonAModel, User
from apps.utils.models import CommonBDModel, BaseModel
from apps.mtm.models import Material, Mgroup, Team, Shift
from apps.mtm.models import Material, Shift
from apps.em.models import Equipment
from apps.wpm.models import SfLog, WMaterial
from django.utils.translation import gettext_lazy as _
class Defect(CommonAModel):
# 缺陷项
DEFECT_OK = 10
DEFECT_OK_B = 20
DEFECT_NOTOK = 30
name = models.CharField(max_length=50, verbose_name="名称")
code = models.CharField(max_length=50, verbose_name="标识")
cate = models.CharField(max_length=50, verbose_name="分类", choices=(("尺寸", "尺寸"), ("外观", "外观"), ("内质", "内质")))
okcate= models.PositiveSmallIntegerField(verbose_name="不合格分类",
choices=((DEFECT_OK, "合格"), (DEFECT_OK_B, "合格B类"), (DEFECT_NOTOK, "不合格")),
default=DEFECT_NOTOK)
def __str__(self):
return self.name
class NotOkOption(models.TextChoices):
# 不合格项
zw = "zw", _("炸纹")
@ -85,6 +100,7 @@ FTEST_TYPE_CHOICES = (
('process', '过程检验'),
('prod', '成品检验')
)
class TestItem(CommonAModel):
"""
检测项目

View File

@ -1,13 +1,27 @@
from apps.qm.models import QuaStat, TestItem, Ftest, FtestItem, FtestWork, Ptest, NotOkOption
from apps.qm.models import QuaStat, TestItem, Ftest, FtestItem, FtestWork, Ptest, NotOkOption, Defect
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from apps.system.models import Dept, Dictionary
from apps.wpm.models import SfLog, WMaterial
from django.db import transaction
from apps.inm.serializers import MaterialBatchDetailSerializer
class DefectSerializer(CustomModelSerializer):
class Meta:
model = Defect
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
def create(self, validated_data):
code = validated_data["code"]
if Defect.objects.get_queryset(all=True).filter(code=code).exists():
raise ValidationError("缺陷标识已存在")
return super().create(validated_data)
def update(self, instance, validated_data):
validated_data.pop("code", None)
return super().update(instance, validated_data)
class TestItemSerializer(CustomModelSerializer):
class Meta:

View File

@ -5,6 +5,8 @@ from rest_framework.exceptions import ParseError
from django.utils import timezone
from apps.wf.models import Ticket
from apps.qm.models import NotOkOption
from apps.utils.thread import MyThread
from apps.wpm.services_2 import get_alldata_with_batch_and_store
def ftestwork_submit_validate(ins: FtestWork):
wm:WMaterial = ins.wm
@ -114,3 +116,5 @@ def bind_ftestwork(ticket: Ticket, transition, new_ticket_data: dict):
def ftestwork_audit_end(ticket: Ticket):
ins = FtestWork.objects.get(id=ticket.ticket_data['t_id'])
ftestwork_submit(ins, ticket.create_by)
if ins.batch:
MyThread(target=get_alldata_with_batch_and_store, args=(ins.batch,)).start()

View File

@ -1,7 +1,9 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.qm.views import QuaStatViewSet, TestItemViewSet, FtestWorkViewSet, FtestViewSet, PtestViewSet, NotOkOptionView
from apps.qm.views import (QuaStatViewSet, TestItemViewSet,
FtestWorkViewSet, FtestViewSet, PtestViewSet,
NotOkOptionView, DefectViewSet)
API_BASE_URL = 'api/qm/'
HTML_BASE_URL = 'qm/'
@ -12,6 +14,7 @@ router.register('testitem', TestItemViewSet, basename='testitem')
router.register('ftest', FtestViewSet, basename='ftest')
router.register('ftestwork', FtestWorkViewSet, basename='ftestwork')
router.register('ptest', PtestViewSet, basename='ptest')
router.register("defect", DefectViewSet, basename="defect")
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
path(API_BASE_URL + 'notok_option/', NotOkOptionView.as_view()),

View File

@ -1,27 +1,38 @@
from django.shortcuts import render
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin
from rest_framework.mixins import ListModelMixin
from rest_framework.decorators import action
from rest_framework.exceptions import ParseError
from rest_framework.views import APIView
from rest_framework.serializers import Serializer
from apps.qm.models import QuaStat, TestItem, Ftest, Ptest, FtestWork
from apps.qm.serializers import QuaStatSerializer, TestItemSerializer, QuaStatUpdateSerializer, FtestSerializer, PtestSerializer, \
FtestWorkCreateUpdateSerializer, FtestWorkSerializer
FtestWorkCreateUpdateSerializer, FtestWorkSerializer, DefectSerializer
from apps.qm.tasks import cal_quastat_sflog
from rest_framework.response import Response
from apps.utils.mixins import BulkCreateModelMixin, BulkUpdateModelMixin
from apps.utils.mixins import BulkUpdateModelMixin
import datetime
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.wpm.models import SfLog
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter
from django.db import transaction
from apps.qm.models import NotOkOption
from apps.qm.models import NotOkOption, Defect
from apps.qm.services import ftestwork_submit
from apps.utils.thread import MyThread
from apps.wpm.services_2 import get_alldata_with_batch_and_store
from apps.wf.models import State
# Create your views here.
class DefectViewSet(CustomModelViewSet):
"""
list:缺陷项
缺陷项
"""
queryset = Defect.objects.all()
serializer_class = DefectSerializer
filterset_fields = ["cate", "okcate"]
search_fields = ["name", "code"]
class NotOkOptionView(APIView):
perms_map = {'get': '*'}
@ -146,6 +157,8 @@ class FtestWorkViewSet(CustomModelViewSet):
ins:FtestWork = self.get_object()
if ins.submit_time is not None:
raise ParseError('已提交无法修改')
if ins.ticket and ins.ticket.state.type != State.STATE_TYPE_START:
raise ParseError('审批单已进行,无法修改')
x = super().update(request, *args, **kwargs)
# 触发批次统计分析
if ins.batch:
@ -156,6 +169,8 @@ class FtestWorkViewSet(CustomModelViewSet):
ins:FtestWork = self.get_object()
if ins.submit_time is not None:
raise ParseError('已提交无法删除')
if ins.ticket:
raise ParseError('存在审批, 无法删除')
x = super().destroy(request, *args, **kwargs)
# 触发批次统计分析
if ins.batch:
@ -177,6 +192,8 @@ class FtestWorkViewSet(CustomModelViewSet):
提交检验工作
"""
ins:FtestWork = self.get_object()
if ins.ticket:
raise ParseError('该检验工作存在审批!')
if ins.wm is None:
raise ParseError('该检验工作未关联车间库存')
if ins.submit_time is None:

View File

@ -226,6 +226,18 @@ class PermissionSerializer(CustomModelSerializer):
model = Permission
fields = '__all__'
def validate(self, attrs):
type = attrs["type"]
if type in [Permission.PERM_TYPE_MODULE, Permission.PERM_TYPE_API]:
attrs.pop("component", None)
attrs.pop("is_hidden", None)
attrs.pop("is_fullpage", None)
if type == Permission.PERM_TYPE_API:
attrs.pop("route_name", None)
attrs.pop("icon", None)
attrs.pop("path", None)
return super().validate(attrs)
class PermissionCreateUpdateSerializer(CustomModelSerializer):
"""

View File

@ -44,6 +44,7 @@ from drf_yasg.utils import swagger_auto_schema
from server.settings import get_sysconfig, update_sysconfig, update_dict
from apps.utils.constants import DEFAULT_PWD
from django.core.cache import cache
from apps.utils.permission import get_user_route
# logger.info('请求成功! response_code:{}response_headers:{}
# response_body:{}'.format(response_code, response_headers, response_body[:251]))
@ -544,6 +545,14 @@ class UserViewSet(CustomModelViewSet):
user = request.user
return Response(UserFullInfoSerializer(user).data)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def route(self, request, pk=None):
"""登录用户路由
获取登录用户路由
"""
return Response(get_user_route(request.user))
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def bind_wxmp(self, request, pk=None):
"""

View File

@ -4,6 +4,8 @@ from apps.utils.queryset import get_child_queryset2
from apps.system.models import DataFilter, Dept, Permission, PostRole, UserPost, User
from django.db.models.query import QuerySet
from typing import List
from apps.utils.tools import build_tree_from_list
from django.db.models import Q
# 后端代码里有的权限标识
ALL_PERMS = [
@ -21,6 +23,42 @@ def get_alld_perms(update_cache=False) -> List[str]:
cache.set(key, perms_alld_list, timeout=60*5)
return perms_alld_list
def get_user_route(user: User) -> List[str]:
"""
获取用户PC前端路由
"""
perm_qs = Permission.objects.filter(
type__in=[Permission.PERM_TYPE_MODULE, Permission.PERM_TYPE_PAGE]).exclude(
Q(path=None) | Q(path='')| Q(route_name=None)|Q(route_name=''))
user_routes_qs = None
if user.is_superuser:
user_routes_qs = perm_qs
else:
user_routes_qs = perm_qs.filter(role_perms__in=PostRole.objects.filter(
post__in=UserPost.objects.filter(user=user).values_list("post", flat=True)).values_list("role", flat=True))
user_routes_qs = user_routes_qs.order_by('sort')
user_routes_list = list(user_routes_qs.values("id", "name", "type", "route_name", "icon", "path", "component", "is_hidden", "is_fullpage", "parent"))
for item in user_routes_list:
item["meta"] = {}
item["meta"]["title"] = item["name"]
item.pop("name")
item["meta"]["icon"] = item["icon"]
item.pop("icon")
if item["type"] == Permission.PERM_TYPE_MODULE:
item["meta"]["type"] = "menu"
item.pop("component", None)
item.pop("type")
item["meta"]["hidden"] = item["is_hidden"]
item.pop("is_hidden")
item["meta"]["fullpage"] = item["is_fullpage"]
item.pop("is_fullpage")
item["name"] = item["route_name"]
item.pop("route_name")
return build_tree_from_list(user_routes_list)
def get_user_perms_map(user, update_cache=False):
"""
获取权限字典,可用redis存取(包括功能和数据权限)

View File

@ -290,3 +290,21 @@ def compare_values(val1, val2, ignore_order=False):
return compare_dicts(val1, val2, ignore_order)
else:
return val1 == val2
def build_tree_from_list(data, parent_field="parent"):
id_map = {item["id"]: item for item in data}
tree = []
for item in data:
parent_id = item.get(parent_field, None)
if parent_id is None:
tree.append(item)
else:
parent = id_map.get(parent_id, None)
if parent:
parent.setdefault("children", []).append(item)
else:
tree.append(item)
return tree

View File

@ -265,6 +265,12 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
mlog.stored_mgroup = stored_mgroup
mlog.save()
# 更新任务进度
cal_mtask_progress_from_mlog(mlog)
# 更新物料数量
MyThread(target=cal_material_count_from_mlog,args=(mlog,)).start()
# 触发批次统计分析
if mlog.batch:
MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
@ -386,10 +392,32 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
if update_mtaskIds:
Mtask.objects.filter(id__in=update_mtaskIds, state=Mtask.MTASK_SUBMIT).update(state=Mtask.MTASK_ASSGINED)
# 更新任务进度
cal_mtask_progress_from_mlog(mlog)
# 更新物料数量
MyThread(target=cal_material_count_from_mlog,args=(mlog,)).start()
# 触发批次统计分析
if mlog.batch:
MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
def cal_mtask_progress_from_mlog(mlog):
"""
更新mlog关联的任务进度(可线程中执行)
"""
if mlog.fill_way in [Mlog.MLOG_2, Mlog.MLOG_12] and mlog.mtask:
update_mtask(mlog.mtask, fill_way=mlog.fill_way)
elif mlog.fill_way == Mlog.MLOG_23:
cal_mlog_count_from_mlogb(mlog)
m_outs_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
caled_mtask = []
for item in m_outs_qs.all():
mtask = item.mtask
if mtask in caled_mtask:
continue
update_mtask(mtask, fill_way=mlog.fill_way)
caled_mtask.append(mtask)
def cal_mlog_count_from_mlogb(mlog: Mlog):
"""
@ -422,23 +450,6 @@ def cal_mlog_count_from_mlogb(mlog: Mlog):
# 保存更新后的Mlog对象
mlog.save()
def cal_mtask_progress_from_mlog(mlog: Mlog):
"""
更新mlog关联的任务进度(可线程中执行)
"""
if mlog.fill_way in [Mlog.MLOG_2, Mlog.MLOG_12] and mlog.mtask:
update_mtask(mlog.mtask, fill_way=mlog.fill_way)
elif mlog.fill_way == Mlog.MLOG_23:
cal_mlog_count_from_mlogb(mlog)
m_outs_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
caled_mtask = []
for item in m_outs_qs.all():
mtask = item.mtask
if mtask in caled_mtask:
continue
update_mtask(mtask, fill_way=mlog.fill_way)
caled_mtask.append(mtask)
def cal_material_count_from_mlog(mlog: Mlog):
"""
更新mlog关联的物料数量(可单独执行)
@ -458,6 +469,7 @@ def cal_material_count_from_mlog(mlog: Mlog):
def update_mtask(mtask: Mtask, fill_way: int = 10):
mtask = Mtask.objects.get(id=mtask.id) # 防止并发修改获取最新的mtask
from apps.pm.models import Utask
if fill_way == Mlog.MLOG_2:
res = Mlog.objects.filter(mtask=mtask).exclude(submit_time=None).aggregate(sum_count_real=Sum(
@ -656,5 +668,4 @@ def mlog_audit_end(ticket: Ticket):
now = timezone.now()
ins = Mlog.objects.get(id=ticket.ticket_data['t_id'])
mlog_submit(ins, ticket.create_by, now)
MyThread(target=cal_mtask_progress_from_mlog,args=(ins,)).start()
MyThread(target=cal_material_count_from_mlog,args=(ins,)).start()

View File

@ -1,6 +1,4 @@
from django.db import transaction
from django.db.models import Prefetch
from django.shortcuts import render
from rest_framework.decorators import action
from rest_framework.exceptions import ParseError
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin, CreateModelMixin
@ -11,7 +9,6 @@ from django.utils import timezone
from apps.system.models import User
from apps.mtm.models import Material, Process
from apps.pm.models import Mtask
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter
@ -22,10 +19,9 @@ from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer,
AttLogSerializer, OtherLogSerializer, MlogInitSerializer, MlogChangeSerializer,
MlogbDetailSerializer, MlogbInSerializer, MlogbInUpdateSerializer,
MlogbOutUpdateSerializer, FmlogSerializer, FmlogUpdateSerializer, BatchStSerializer)
from .services import mlog_submit, update_mtask, handover_submit, mlog_revert, cal_material_count_from_mlog, cal_mtask_progress_from_mlog
from apps.utils.thread import MyThread
from apps.monitor.services import create_auditlog, delete_auditlog
from .services import mlog_submit, handover_submit, mlog_revert
from apps.wpm.services import mlog_submit_validate, generate_new_batch
from apps.wf.models import State
# Create your views here.
@ -169,12 +165,18 @@ class MlogViewSet(CustomModelViewSet):
def perform_destroy(self, instance):
if instance.submit_time is not None:
raise ParseError('日志已提交不可变动')
if instance.ticket and instance.ticket.state != State.STATE_TYPE_START:
raise ParseError('该日志存在审批!')
# delete_auditlog(instance, instance.id)
if instance.ticket:
instance.ticket.delete()
instance.delete()
@transaction.atomic
def perform_update(self, serializer):
ins = serializer.instance
if ins.ticket and ins.ticket.state != State.STATE_TYPE_START:
raise ParseError('该日志在审批中不可修改!')
if ins.submit_time is not None:
raise ParseError('该日志已提交!')
# val_old = MlogSerializer(instance=ins).data
@ -200,6 +202,8 @@ class MlogViewSet(CustomModelViewSet):
修改日志
"""
ins = self.get_object()
if ins.ticket and ins.ticket.state != State.STATE_TYPE_START:
raise ParseError('该日志在审批中不可修改!')
sr = MlogChangeSerializer(instance=ins, data=request.data)
sr.is_valid(raise_exception=True)
sr.save()
@ -225,8 +229,6 @@ class MlogViewSet(CustomModelViewSet):
vdata_new = MlogSerializer(ins).data
# create_auditlog('submit', ins, vdata_new,
# vdata_old, now, self.request.user)
MyThread(target=cal_mtask_progress_from_mlog,args=(ins,)).start()
MyThread(target=cal_material_count_from_mlog,args=(ins,)).start()
return Response(vdata_new)
@action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer)
@ -236,6 +238,8 @@ class MlogViewSet(CustomModelViewSet):
撤回日志提交
"""
ins: Mlog = self.get_object()
if ins.ticket:
raise ParseError('该日志存在审批!')
user = request.user
if ins.submit_time is None:
raise ParseError('日志未提交不可撤销')
@ -246,8 +250,6 @@ class MlogViewSet(CustomModelViewSet):
mlog_revert(ins, user, now)
# create_auditlog('revert', ins, {}, {}, now, user,
# request.data.get('change_reason', ''))
MyThread(target=cal_mtask_progress_from_mlog,args=(ins,)).start()
MyThread(target=cal_material_count_from_mlog,args=(ins,)).start()
return Response(MlogSerializer(instance=ins).data)
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MlogRelatedSerializer)