diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index 6fca06f..36f2c04 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -129,7 +129,7 @@ class RecordFormField(CommonAModel): class ProductProcess(CommonAModel): """ - 产品生产工艺 + 产品生产工艺集 """ product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE) diff --git a/hb_server/apps/pm/__init__.py b/hb_server/apps/pm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/pm/admin.py b/hb_server/apps/pm/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hb_server/apps/pm/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hb_server/apps/pm/apps.py b/hb_server/apps/pm/apps.py new file mode 100644 index 0000000..c0c4b31 --- /dev/null +++ b/hb_server/apps/pm/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + +class SamConfig(AppConfig): + name = 'apps.pm' + verbose_name = '生产计划管理' + + diff --git a/hb_server/apps/pm/migrations/0001_initial.py b/hb_server/apps/pm/migrations/0001_initial.py new file mode 100644 index 0000000..7f16d5a --- /dev/null +++ b/hb_server/apps/pm/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.6 on 2021-10-08 08:02 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0018_material_count'), + ('sam', '0004_order_planed_count'), + ] + + operations = [ + migrations.CreateModel( + name='ProductionPlan', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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, unique=True, verbose_name='编号')), + ('count', models.IntegerField(default=0, verbose_name='生产数量')), + ('start_date', models.DateField(verbose_name='计划开工日期')), + ('end_date', models.DateField(verbose_name='计划完工日期')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='productionplan_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sam.order', verbose_name='关联订单')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='生产产品')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='productionplan_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'verbose_name': '生产计划', + 'verbose_name_plural': '生产计划', + }, + ), + ] diff --git a/hb_server/apps/pm/migrations/0002_alter_productionplan_count.py b/hb_server/apps/pm/migrations/0002_alter_productionplan_count.py new file mode 100644 index 0000000..6a41103 --- /dev/null +++ b/hb_server/apps/pm/migrations/0002_alter_productionplan_count.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-10-08 08:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='productionplan', + name='count', + field=models.IntegerField(default=1, verbose_name='生产数量'), + ), + ] diff --git a/hb_server/apps/pm/migrations/__init__.py b/hb_server/apps/pm/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py new file mode 100644 index 0000000..062e0f1 --- /dev/null +++ b/hb_server/apps/pm/models.py @@ -0,0 +1,30 @@ +from apps.system.models import CommonAModel +from django.db import models +from django.contrib.auth.models import AbstractUser +from django.db.models.base import Model +import django.utils.timezone as timezone +from django.db.models.query import QuerySet + +from utils.model import SoftModel, BaseModel +from apps.mtm.models import Material +from apps.sam.models import Order + + +class ProductionPlan(CommonAModel): + """ + 生产计划 + """ + number = models.CharField('编号', max_length=50, unique=True) + order = models.ForeignKey(Order, verbose_name='关联订单', null=True, blank=True, on_delete=models.SET_NULL) + product = models.ForeignKey(Material, verbose_name='生产产品', on_delete=models.CASCADE) + count = models.IntegerField('生产数量', default=1) + start_date = models.DateField('计划开工日期') + end_date = models.DateField('计划完工日期') + + class Meta: + verbose_name = '生产计划' + verbose_name_plural = verbose_name + + def __str__(self): + return self.number + diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py new file mode 100644 index 0000000..e3975c1 --- /dev/null +++ b/hb_server/apps/pm/serializers.py @@ -0,0 +1,17 @@ +from apps.pm.models import ProductionPlan +from rest_framework import serializers +from apps.sam.serializers import OrderSerializer +from apps.mtm.serializers import MaterialSimpleSerializer + + +class ProductionPlanCreateFromOrderSerializer(serializers.ModelSerializer): + class Meta: + model = ProductionPlan + fields = ['order', 'number', 'count', 'start_date', 'end_date'] + +class ProductionPlanSerializer(serializers.ModelSerializer): + order_ = OrderSerializer(source='order', read_only=True) + product_ = MaterialSimpleSerializer(source='product', read_only=True) + class Meta: + model = ProductionPlan + fields ='__all__' diff --git a/hb_server/apps/pm/tests.py b/hb_server/apps/pm/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hb_server/apps/pm/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hb_server/apps/pm/urls.py b/hb_server/apps/pm/urls.py new file mode 100644 index 0000000..69e52f0 --- /dev/null +++ b/hb_server/apps/pm/urls.py @@ -0,0 +1,13 @@ +from apps.pm.views import ProductionPlanViewSet +from django.db.models import base +from rest_framework import urlpatterns +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register('productionplan', ProductionPlanViewSet, basename='productionplan') + +urlpatterns = [ + path('', include(router.urls)), +] + diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py new file mode 100644 index 0000000..25509dc --- /dev/null +++ b/hb_server/apps/pm/views.py @@ -0,0 +1,52 @@ +from apps.system.mixins import CreateUpdateModelAMixin +from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer +from rest_framework.mixins import CreateModelMixin, ListModelMixin +from apps.pm.models import ProductionPlan +from rest_framework.viewsets import GenericViewSet, ModelViewSet +from django.shortcuts import render +from apps.sam.models import Order +from rest_framework.exceptions import APIException +from rest_framework.response import Response +# Create your views here. + +def updateOrderPlanedCount(order): + """ + 更新订单已排数量 + """ + planed_count = 0 + plans = ProductionPlan.objects.filter(order=order) + for i in plans: + planed_count = planed_count + i.count + order.planed_count = planed_count + order.save() + +class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModelMixin, GenericViewSet): + """ + 生产计划 + """ + perms_map = {'*': '*'} + queryset = ProductionPlan.objects.select_related('order', 'order__contract', 'product') + serializer_class = ProductionPlanSerializer + search_fields = ['number'] + filterset_fields = [] + ordering_fields = ['id'] + ordering = ['-id'] + + def get_serializer_class(self): + if self.action in ['create']: + return ProductionPlanCreateFromOrderSerializer + return ProductionPlanSerializer + + def create(self, request, *args, **kwargs): + data = request.data + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + if data.get('order', None): + order = Order.objects.get(pk=data['order']) + if order.planed_count >= data['count'] or data['count'] > 0: + pass + else: + raise APIException('排产数量错误') + instance = serializer.save(create_by=request.user, product=order.product) + updateOrderPlanedCount(instance.order) + return Response() \ No newline at end of file diff --git a/hb_server/apps/sam/migrations/0004_order_planed_count.py b/hb_server/apps/sam/migrations/0004_order_planed_count.py new file mode 100644 index 0000000..df4b48a --- /dev/null +++ b/hb_server/apps/sam/migrations/0004_order_planed_count.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-10-08 07:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0003_contract_invoice'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='planed_count', + field=models.IntegerField(default=0, verbose_name='已排数量'), + ), + ] diff --git a/hb_server/apps/sam/models.py b/hb_server/apps/sam/models.py index 4683844..583fe87 100644 --- a/hb_server/apps/sam/models.py +++ b/hb_server/apps/sam/models.py @@ -5,6 +5,7 @@ from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet + from utils.model import SoftModel, BaseModel from apps.mtm.models import Material @@ -62,6 +63,7 @@ class Order(CommonAModel): contract = models.ForeignKey(Contract, verbose_name='所属合同', null=True, blank=True, on_delete=models.SET_NULL) product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE) count = models.IntegerField('所需数量', default=0) + planed_count = models.IntegerField('已排数量', default=0) delivery_date = models.DateField('交货日期') class Meta: verbose_name = '订单信息' diff --git a/hb_server/apps/sam/views.py b/hb_server/apps/sam/views.py index 8f66873..f1f4be7 100644 --- a/hb_server/apps/sam/views.py +++ b/hb_server/apps/sam/views.py @@ -3,7 +3,9 @@ from apps.sam.models import Contract, Customer, Order from rest_framework.viewsets import ModelViewSet from apps.system.mixins import CreateUpdateCustomMixin from django.shortcuts import render - +from rest_framework.decorators import action +from django.db.models import F +from rest_framework.response import Response # Create your views here. class CustomerViewSet(CreateUpdateCustomMixin, ModelViewSet): """ @@ -55,4 +57,14 @@ class OrderViewSet(CreateUpdateCustomMixin, ModelViewSet): def get_serializer_class(self): if self.action in ['create', 'update']: return OrderCreateUpdateSerializer - return OrderSerializer \ No newline at end of file + return OrderSerializer + + @action(methods=['get'], detail=False, perms_map={'get':'*'}) + def toplan(self, request, pk=None): + queryset = Order.objects.filter(count__gt=F('planed_count')).order_by('-id') + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) \ No newline at end of file diff --git a/hb_server/apps/wf/serializers.py b/hb_server/apps/wf/serializers.py index 81512ed..697a96a 100644 --- a/hb_server/apps/wf/serializers.py +++ b/hb_server/apps/wf/serializers.py @@ -115,6 +115,13 @@ class TicketFlowSerializer(serializers.ModelSerializer): model = TicketFlow fields = '__all__' +class TicketFlowSimpleSerializer(serializers.ModelSerializer): + participant_ = UserSimpleSerializer(source='participant', read_only=True) + state_ = StateSimpleSerializer(source='state', read_only=True) + class Meta: + model = TicketFlow + exclude = ['ticket_data'] + class TicketHandleSerializer(serializers.Serializer): transition = serializers.IntegerField(label="流转id") diff --git a/hb_server/apps/wf/services.py b/hb_server/apps/wf/services.py index 7fd7026..c86fcb7 100644 --- a/hb_server/apps/wf/services.py +++ b/hb_server/apps/wf/services.py @@ -57,10 +57,11 @@ class WfService(object): @classmethod def get_ticket_steps(cls, ticket:Ticket): steps = cls.get_worlflow_states(ticket.workflow) + nsteps_list = [] for i in steps: - if ticket.state.is_hidden and ticket.state != i: - steps.remove(i) - return steps + if ticket.state == i or (not i.is_hidden): + nsteps_list.append(i) + return nsteps_list @classmethod def get_ticket_transitions(cls, ticket:Ticket): @@ -104,7 +105,7 @@ class WfService(object): expression = i['expression'].format(**ticket_all_value) import datetime, time # 用于支持条件表达式中对时间的操作 if eval(expression): - destination_state = State.objects.get(i['expression'].get('target_state')) + destination_state = State.objects.get(pk=i['target_state']) return destination_state @classmethod diff --git a/hb_server/apps/wf/urls.py b/hb_server/apps/wf/urls.py index 7d169f9..b03a965 100644 --- a/hb_server/apps/wf/urls.py +++ b/hb_server/apps/wf/urls.py @@ -1,6 +1,6 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.wf.views import CustomFieldViewSet, StateViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet +from apps.wf.views import CustomFieldViewSet, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -10,6 +10,7 @@ router.register('state', StateViewSet, basename='wf_state') router.register('transition', TransitionViewSet, basename='wf_transitions') router.register('customfield', CustomFieldViewSet, basename='wf_customfield') router.register('ticket', TicketViewSet, basename='wf_ticket') +router.register('ticketflow', TicketFlowViewSet, basename='wf_ticketflow') urlpatterns = [ path('', include(router.urls)), ] diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index cd33e99..187d70f 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -3,7 +3,7 @@ from django.core.exceptions import AppRegistryNotReady from rest_framework.response import Response from rest_framework import serializers from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin -from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketHandleSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer +from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer from django.shortcuts import get_object_or_404, render from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.decorators import action, api_view @@ -247,6 +247,16 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin ticket = self.get_object() steps = WfService.get_ticket_steps(ticket) return Response(StateSerializer(instance=steps, many=True).data) + + @action(methods=['get'], detail=True, perms_map={'get':'*'}) + def flowlogs(self, request, pk=None): + """ + 工单流转记录 + """ + ticket = self.get_object() + flowlogs = TicketFlow.objects.filter(ticket=ticket).order_by('-create_time') + serializer = TicketFlowSerializer(instance=flowlogs, many=True) + return Response(serializer.data) @action(methods=['get'], detail=True, perms_map={'get':'*'}) def transitions(self, request, pk=None): @@ -276,6 +286,13 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin return Response() else: raise APIException('无需接单') + + @action(methods=['post'], detail=True, perms_map={'post':'*'}) + def retreat(self, request, pk=None): + """ + 撤回工单,允许创建人在指定状态撤回工单至初始状态,状态设置中开启允许撤回 + """ + pass class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ @@ -285,5 +302,5 @@ class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): queryset = TicketFlow.objects.all() serializer_class = TicketFlowSerializer search_fields = ['suggestion'] - filterset_fields = ['paticipant', 'state', 'ticket'] + filterset_fields = ['ticket'] ordering = ['-create_time'] \ No newline at end of file diff --git a/hb_server/server/settings.py b/hb_server/server/settings.py index 652d71e..b2e7755 100644 --- a/hb_server/server/settings.py +++ b/hb_server/server/settings.py @@ -54,7 +54,8 @@ INSTALLED_APPS = [ 'apps.mtm', 'apps.inm', 'apps.sam', - 'apps.qm' + 'apps.qm', + 'apps.pm' ] MIDDLEWARE = [ diff --git a/hb_server/server/urls.py b/hb_server/server/urls.py index e27e660..af315af 100644 --- a/hb_server/server/urls.py +++ b/hb_server/server/urls.py @@ -67,7 +67,7 @@ urlpatterns = [ path('api/inm/', include('apps.inm.urls')), path('api/sam/', include('apps.sam.urls')), path('api/qm/', include('apps.qm.urls')), - + path('api/pm/', include('apps.pm.urls')), # 工具 path('api/utils/signature/', GenSignature.as_view()),