Merge branch 'master' of https://e.coding.net/ctcdevteam/ehs/ehs_server
This commit is contained in:
commit
c08dff56bf
|
@ -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='工序生产类型'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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='切分数量'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,6 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
|
from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel
|
||||||
from django.db.models import Subquery, OuterRef
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.exceptions import ValidationError, ParseError
|
|
||||||
from apps.utils.models import CommonBDModel
|
from apps.utils.models import CommonBDModel
|
||||||
|
|
||||||
class Process(CommonBModel):
|
class Process(CommonBModel):
|
||||||
|
@ -10,8 +9,14 @@ class Process(CommonBModel):
|
||||||
"""
|
"""
|
||||||
PRO_PROD = 10
|
PRO_PROD = 10
|
||||||
RPO_TEST = 20
|
RPO_TEST = 20
|
||||||
|
|
||||||
|
|
||||||
|
PRO_NORMAL = 10
|
||||||
|
PRO_DIV = 20
|
||||||
|
PRO_MERGE = 30
|
||||||
name = models.CharField('工序名称', max_length=100)
|
name = models.CharField('工序名称', max_length=100)
|
||||||
type = models.PositiveSmallIntegerField("工序类型", default=PRO_PROD, choices=((PRO_PROD, '生产工序'), (RPO_TEST, '检验工序')))
|
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='')
|
cate = models.CharField('大类', max_length=10, default='')
|
||||||
sort = models.PositiveSmallIntegerField('排序', default=1)
|
sort = models.PositiveSmallIntegerField('排序', default=1)
|
||||||
instruction = models.ForeignKey(
|
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)
|
Material, verbose_name='主要输出物料', on_delete=models.CASCADE, related_name='route_material_out', null=True, blank=True)
|
||||||
is_count_utask = models.BooleanField('是否主任务统计', default=False)
|
is_count_utask = models.BooleanField('是否主任务统计', default=False)
|
||||||
out_rate = models.FloatField('出材率', default=100, blank=True)
|
out_rate = models.FloatField('出材率', default=100, blank=True)
|
||||||
|
div_number = models.PositiveSmallIntegerField('切分数量', default=1, blank=True)
|
||||||
hour_work = models.FloatField('工时', null=True, blank=True)
|
hour_work = models.FloatField('工时', null=True, blank=True)
|
||||||
batch_bind = models.BooleanField('是否绑定批次', default=True)
|
batch_bind = models.BooleanField('是否绑定批次', default=True)
|
||||||
|
|
||||||
|
|
|
@ -170,8 +170,11 @@ class RouteSerializer(CustomModelSerializer):
|
||||||
attrs['material'] = attrs['routepack'].material
|
attrs['material'] = attrs['routepack'].material
|
||||||
if 'mgroup' in attrs and attrs['mgroup']:
|
if 'mgroup' in attrs and attrs['mgroup']:
|
||||||
attrs['process'] = attrs['mgroup'].process
|
attrs['process'] = attrs['mgroup'].process
|
||||||
if attrs.get('process', None) is None:
|
process: Process = attrs.get('process', None)
|
||||||
|
if process is None:
|
||||||
raise ParseError('未提供操作工序')
|
raise ParseError('未提供操作工序')
|
||||||
|
if process.mtype == Process.PRO_DIV and attrs.get('div_number', 1) <= 1:
|
||||||
|
raise ParseError('切分数量必须大于1')
|
||||||
return super().validate(attrs)
|
return super().validate(attrs)
|
||||||
|
|
||||||
def gen_material_out(self, instance: Route):
|
def gen_material_out(self, instance: Route):
|
||||||
|
|
|
@ -25,7 +25,8 @@ class PmService:
|
||||||
while indx < rate_len:
|
while indx < rate_len:
|
||||||
if indx + 1 == rate_len: # 循环到最后一步
|
if indx + 1 == rate_len: # 循环到最后一步
|
||||||
break
|
break
|
||||||
xcount = xcount/(rate_list[indx+1]/100)
|
rate, div_number = rate_list[indx+1]
|
||||||
|
xcount = xcount/(rate/100)/div_number
|
||||||
indx = indx + 1
|
indx = indx + 1
|
||||||
r_list.append(math.ceil(xcount))
|
r_list.append(math.ceil(xcount))
|
||||||
return r_list
|
return r_list
|
||||||
|
@ -130,7 +131,7 @@ class PmService:
|
||||||
if not rqs.exists():
|
if not rqs.exists():
|
||||||
raise ParseError('未配置工艺路线')
|
raise ParseError('未配置工艺路线')
|
||||||
# 通过出材率校正任务数, out_rate 默认为 100
|
# 通过出材率校正任务数, 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)
|
count_task_list = cls.cal_real_task_count(count,out_rate_list)
|
||||||
# 创建小任务
|
# 创建小任务
|
||||||
for ind, val in enumerate(rqs):
|
for ind, val in enumerate(rqs):
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,11 +1,26 @@
|
||||||
from django.db import models
|
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.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.em.models import Equipment
|
||||||
from apps.wpm.models import SfLog, WMaterial
|
from apps.wpm.models import SfLog, WMaterial
|
||||||
from django.utils.translation import gettext_lazy as _
|
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):
|
class NotOkOption(models.TextChoices):
|
||||||
# 不合格项
|
# 不合格项
|
||||||
zw = "zw", _("炸纹")
|
zw = "zw", _("炸纹")
|
||||||
|
@ -85,6 +100,7 @@ FTEST_TYPE_CHOICES = (
|
||||||
('process', '过程检验'),
|
('process', '过程检验'),
|
||||||
('prod', '成品检验')
|
('prod', '成品检验')
|
||||||
)
|
)
|
||||||
|
|
||||||
class TestItem(CommonAModel):
|
class TestItem(CommonAModel):
|
||||||
"""
|
"""
|
||||||
检测项目
|
检测项目
|
||||||
|
|
|
@ -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.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
|
||||||
from apps.utils.serializers import CustomModelSerializer
|
from apps.utils.serializers import CustomModelSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from apps.system.models import Dept, Dictionary
|
|
||||||
from apps.wpm.models import SfLog, WMaterial
|
from apps.wpm.models import SfLog, WMaterial
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from apps.inm.serializers import MaterialBatchDetailSerializer
|
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 TestItemSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -5,6 +5,8 @@ from rest_framework.exceptions import ParseError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from apps.wf.models import Ticket
|
from apps.wf.models import Ticket
|
||||||
from apps.qm.models import NotOkOption
|
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):
|
def ftestwork_submit_validate(ins: FtestWork):
|
||||||
wm:WMaterial = ins.wm
|
wm:WMaterial = ins.wm
|
||||||
|
@ -113,4 +115,6 @@ def bind_ftestwork(ticket: Ticket, transition, new_ticket_data: dict):
|
||||||
|
|
||||||
def ftestwork_audit_end(ticket: Ticket):
|
def ftestwork_audit_end(ticket: Ticket):
|
||||||
ins = FtestWork.objects.get(id=ticket.ticket_data['t_id'])
|
ins = FtestWork.objects.get(id=ticket.ticket_data['t_id'])
|
||||||
ftestwork_submit(ins, ticket.create_by)
|
ftestwork_submit(ins, ticket.create_by)
|
||||||
|
if ins.batch:
|
||||||
|
MyThread(target=get_alldata_with_batch_and_store, args=(ins.batch,)).start()
|
|
@ -1,7 +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.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/'
|
API_BASE_URL = 'api/qm/'
|
||||||
HTML_BASE_URL = 'qm/'
|
HTML_BASE_URL = 'qm/'
|
||||||
|
@ -12,6 +14,7 @@ router.register('testitem', TestItemViewSet, basename='testitem')
|
||||||
router.register('ftest', FtestViewSet, basename='ftest')
|
router.register('ftest', FtestViewSet, basename='ftest')
|
||||||
router.register('ftestwork', FtestWorkViewSet, basename='ftestwork')
|
router.register('ftestwork', FtestWorkViewSet, basename='ftestwork')
|
||||||
router.register('ptest', PtestViewSet, basename='ptest')
|
router.register('ptest', PtestViewSet, basename='ptest')
|
||||||
|
router.register("defect", DefectViewSet, basename="defect")
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(API_BASE_URL, include(router.urls)),
|
path(API_BASE_URL, include(router.urls)),
|
||||||
path(API_BASE_URL + 'notok_option/', NotOkOptionView.as_view()),
|
path(API_BASE_URL + 'notok_option/', NotOkOptionView.as_view()),
|
||||||
|
|
|
@ -1,27 +1,38 @@
|
||||||
from django.shortcuts import render
|
from rest_framework.mixins import ListModelMixin
|
||||||
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin
|
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
from apps.qm.models import QuaStat, TestItem, Ftest, Ptest, FtestWork
|
from apps.qm.models import QuaStat, TestItem, Ftest, Ptest, FtestWork
|
||||||
from apps.qm.serializers import QuaStatSerializer, TestItemSerializer, QuaStatUpdateSerializer, FtestSerializer, PtestSerializer, \
|
from apps.qm.serializers import QuaStatSerializer, TestItemSerializer, QuaStatUpdateSerializer, FtestSerializer, PtestSerializer, \
|
||||||
FtestWorkCreateUpdateSerializer, FtestWorkSerializer
|
FtestWorkCreateUpdateSerializer, FtestWorkSerializer, DefectSerializer
|
||||||
from apps.qm.tasks import cal_quastat_sflog
|
from apps.qm.tasks import cal_quastat_sflog
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from apps.utils.mixins import BulkCreateModelMixin, BulkUpdateModelMixin
|
from apps.utils.mixins import BulkUpdateModelMixin
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||||
from apps.wpm.models import SfLog
|
from apps.wpm.models import SfLog
|
||||||
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter
|
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter
|
||||||
from django.db import transaction
|
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.qm.services import ftestwork_submit
|
||||||
from apps.utils.thread import MyThread
|
from apps.utils.thread import MyThread
|
||||||
from apps.wpm.services_2 import get_alldata_with_batch_and_store
|
from apps.wpm.services_2 import get_alldata_with_batch_and_store
|
||||||
|
from apps.wf.models import State
|
||||||
# Create your views here.
|
# 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):
|
class NotOkOptionView(APIView):
|
||||||
perms_map = {'get': '*'}
|
perms_map = {'get': '*'}
|
||||||
|
|
||||||
|
@ -146,6 +157,8 @@ class FtestWorkViewSet(CustomModelViewSet):
|
||||||
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 and ins.ticket.state.type != State.STATE_TYPE_START:
|
||||||
|
raise ParseError('审批单已进行,无法修改')
|
||||||
x = super().update(request, *args, **kwargs)
|
x = super().update(request, *args, **kwargs)
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
if ins.batch:
|
if ins.batch:
|
||||||
|
@ -156,6 +169,8 @@ class FtestWorkViewSet(CustomModelViewSet):
|
||||||
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:
|
||||||
|
raise ParseError('存在审批, 无法删除')
|
||||||
x = super().destroy(request, *args, **kwargs)
|
x = super().destroy(request, *args, **kwargs)
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
if ins.batch:
|
if ins.batch:
|
||||||
|
@ -177,6 +192,8 @@ class FtestWorkViewSet(CustomModelViewSet):
|
||||||
提交检验工作
|
提交检验工作
|
||||||
"""
|
"""
|
||||||
ins:FtestWork = self.get_object()
|
ins:FtestWork = self.get_object()
|
||||||
|
if ins.ticket:
|
||||||
|
raise ParseError('该检验工作存在审批!')
|
||||||
if ins.wm is None:
|
if ins.wm is None:
|
||||||
raise ParseError('该检验工作未关联车间库存')
|
raise ParseError('该检验工作未关联车间库存')
|
||||||
if ins.submit_time is None:
|
if ins.submit_time is None:
|
||||||
|
|
|
@ -225,6 +225,18 @@ class PermissionSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Permission
|
model = Permission
|
||||||
fields = '__all__'
|
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):
|
class PermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||||
|
|
|
@ -44,6 +44,7 @@ from drf_yasg.utils import swagger_auto_schema
|
||||||
from server.settings import get_sysconfig, update_sysconfig, update_dict
|
from server.settings import get_sysconfig, update_sysconfig, update_dict
|
||||||
from apps.utils.constants import DEFAULT_PWD
|
from apps.utils.constants import DEFAULT_PWD
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from apps.utils.permission import get_user_route
|
||||||
|
|
||||||
# logger.info('请求成功! response_code:{};response_headers:{};
|
# logger.info('请求成功! response_code:{};response_headers:{};
|
||||||
# response_body:{}'.format(response_code, response_headers, response_body[:251]))
|
# response_body:{}'.format(response_code, response_headers, response_body[:251]))
|
||||||
|
@ -543,6 +544,14 @@ class UserViewSet(CustomModelViewSet):
|
||||||
"""
|
"""
|
||||||
user = request.user
|
user = request.user
|
||||||
return Response(UserFullInfoSerializer(user).data)
|
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])
|
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
|
||||||
def bind_wxmp(self, request, pk=None):
|
def bind_wxmp(self, request, pk=None):
|
||||||
|
|
|
@ -4,6 +4,8 @@ from apps.utils.queryset import get_child_queryset2
|
||||||
from apps.system.models import DataFilter, Dept, Permission, PostRole, UserPost, User
|
from apps.system.models import DataFilter, Dept, Permission, PostRole, UserPost, User
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from apps.utils.tools import build_tree_from_list
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
# 后端代码里有的权限标识
|
# 后端代码里有的权限标识
|
||||||
ALL_PERMS = [
|
ALL_PERMS = [
|
||||||
|
@ -21,6 +23,42 @@ def get_alld_perms(update_cache=False) -> List[str]:
|
||||||
cache.set(key, perms_alld_list, timeout=60*5)
|
cache.set(key, perms_alld_list, timeout=60*5)
|
||||||
return perms_alld_list
|
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):
|
def get_user_perms_map(user, update_cache=False):
|
||||||
"""
|
"""
|
||||||
获取权限字典,可用redis存取(包括功能和数据权限)
|
获取权限字典,可用redis存取(包括功能和数据权限)
|
||||||
|
|
|
@ -290,3 +290,21 @@ def compare_values(val1, val2, ignore_order=False):
|
||||||
return compare_dicts(val1, val2, ignore_order)
|
return compare_dicts(val1, val2, ignore_order)
|
||||||
else:
|
else:
|
||||||
return val1 == val2
|
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
|
|
@ -265,6 +265,12 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
mlog.stored_mgroup = stored_mgroup
|
mlog.stored_mgroup = stored_mgroup
|
||||||
mlog.save()
|
mlog.save()
|
||||||
|
|
||||||
|
# 更新任务进度
|
||||||
|
cal_mtask_progress_from_mlog(mlog)
|
||||||
|
|
||||||
|
# 更新物料数量
|
||||||
|
MyThread(target=cal_material_count_from_mlog,args=(mlog,)).start()
|
||||||
|
|
||||||
# 触发批次统计分析
|
# 触发批次统计分析
|
||||||
if mlog.batch:
|
if mlog.batch:
|
||||||
MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
|
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:
|
if update_mtaskIds:
|
||||||
Mtask.objects.filter(id__in=update_mtaskIds, state=Mtask.MTASK_SUBMIT).update(state=Mtask.MTASK_ASSGINED)
|
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:
|
if mlog.batch:
|
||||||
MyThread(target=get_alldata_with_batch_and_store, args=(mlog.batch,)).start()
|
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):
|
def cal_mlog_count_from_mlogb(mlog: Mlog):
|
||||||
"""
|
"""
|
||||||
|
@ -422,23 +450,6 @@ def cal_mlog_count_from_mlogb(mlog: Mlog):
|
||||||
# 保存更新后的Mlog对象
|
# 保存更新后的Mlog对象
|
||||||
mlog.save()
|
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):
|
def cal_material_count_from_mlog(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):
|
def update_mtask(mtask: Mtask, fill_way: int = 10):
|
||||||
|
mtask = Mtask.objects.get(id=mtask.id) # 防止并发修改获取最新的mtask
|
||||||
from apps.pm.models import Utask
|
from apps.pm.models import Utask
|
||||||
if fill_way == Mlog.MLOG_2:
|
if fill_way == Mlog.MLOG_2:
|
||||||
res = Mlog.objects.filter(mtask=mtask).exclude(submit_time=None).aggregate(sum_count_real=Sum(
|
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()
|
now = timezone.now()
|
||||||
ins = Mlog.objects.get(id=ticket.ticket_data['t_id'])
|
ins = Mlog.objects.get(id=ticket.ticket_data['t_id'])
|
||||||
mlog_submit(ins, ticket.create_by, now)
|
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()
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from django.db import transaction
|
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.decorators import action
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin, CreateModelMixin
|
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.system.models import User
|
||||||
|
|
||||||
from apps.mtm.models import Material, Process
|
from apps.mtm.models import Material, Process
|
||||||
from apps.pm.models import Mtask
|
|
||||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||||
|
|
||||||
from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter
|
from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter, MlogbFilter, BatchStFilter
|
||||||
|
@ -22,10 +19,9 @@ from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer,
|
||||||
AttLogSerializer, OtherLogSerializer, MlogInitSerializer, MlogChangeSerializer,
|
AttLogSerializer, OtherLogSerializer, MlogInitSerializer, MlogChangeSerializer,
|
||||||
MlogbDetailSerializer, MlogbInSerializer, MlogbInUpdateSerializer,
|
MlogbDetailSerializer, MlogbInSerializer, MlogbInUpdateSerializer,
|
||||||
MlogbOutUpdateSerializer, FmlogSerializer, FmlogUpdateSerializer, BatchStSerializer)
|
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 .services import mlog_submit, handover_submit, mlog_revert
|
||||||
from apps.utils.thread import MyThread
|
|
||||||
from apps.monitor.services import create_auditlog, delete_auditlog
|
|
||||||
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
||||||
|
from apps.wf.models import State
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,12 +165,18 @@ class MlogViewSet(CustomModelViewSet):
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.submit_time is not None:
|
if instance.submit_time is not None:
|
||||||
raise ParseError('日志已提交不可变动')
|
raise ParseError('日志已提交不可变动')
|
||||||
|
if instance.ticket and instance.ticket.state != State.STATE_TYPE_START:
|
||||||
|
raise ParseError('该日志存在审批!')
|
||||||
# delete_auditlog(instance, instance.id)
|
# delete_auditlog(instance, instance.id)
|
||||||
|
if instance.ticket:
|
||||||
|
instance.ticket.delete()
|
||||||
instance.delete()
|
instance.delete()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
ins = serializer.instance
|
ins = serializer.instance
|
||||||
|
if ins.ticket and ins.ticket.state != State.STATE_TYPE_START:
|
||||||
|
raise ParseError('该日志在审批中不可修改!')
|
||||||
if ins.submit_time is not None:
|
if ins.submit_time is not None:
|
||||||
raise ParseError('该日志已提交!')
|
raise ParseError('该日志已提交!')
|
||||||
# val_old = MlogSerializer(instance=ins).data
|
# val_old = MlogSerializer(instance=ins).data
|
||||||
|
@ -200,6 +202,8 @@ class MlogViewSet(CustomModelViewSet):
|
||||||
修改日志
|
修改日志
|
||||||
"""
|
"""
|
||||||
ins = self.get_object()
|
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 = MlogChangeSerializer(instance=ins, data=request.data)
|
||||||
sr.is_valid(raise_exception=True)
|
sr.is_valid(raise_exception=True)
|
||||||
sr.save()
|
sr.save()
|
||||||
|
@ -225,8 +229,6 @@ class MlogViewSet(CustomModelViewSet):
|
||||||
vdata_new = MlogSerializer(ins).data
|
vdata_new = MlogSerializer(ins).data
|
||||||
# create_auditlog('submit', ins, vdata_new,
|
# create_auditlog('submit', ins, vdata_new,
|
||||||
# vdata_old, now, self.request.user)
|
# 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)
|
return Response(vdata_new)
|
||||||
|
|
||||||
@action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=MlogRevertSerializer)
|
@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()
|
ins: Mlog = self.get_object()
|
||||||
|
if ins.ticket:
|
||||||
|
raise ParseError('该日志存在审批!')
|
||||||
user = request.user
|
user = request.user
|
||||||
if ins.submit_time is None:
|
if ins.submit_time is None:
|
||||||
raise ParseError('日志未提交不可撤销')
|
raise ParseError('日志未提交不可撤销')
|
||||||
|
@ -246,8 +250,6 @@ class MlogViewSet(CustomModelViewSet):
|
||||||
mlog_revert(ins, user, now)
|
mlog_revert(ins, user, now)
|
||||||
# create_auditlog('revert', ins, {}, {}, now, user,
|
# create_auditlog('revert', ins, {}, {}, now, user,
|
||||||
# request.data.get('change_reason', ''))
|
# 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)
|
return Response(MlogSerializer(instance=ins).data)
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MlogRelatedSerializer)
|
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MlogRelatedSerializer)
|
||||||
|
|
Loading…
Reference in New Issue