Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shijing 2021-10-12 10:38:14 +08:00
commit d1fd46446a
21 changed files with 255 additions and 12 deletions

View File

@ -129,7 +129,7 @@ class RecordFormField(CommonAModel):
class ProductProcess(CommonAModel): class ProductProcess(CommonAModel):
""" """
产品生产工艺 产品生产工艺
""" """
product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) product = models.ForeignKey(Material, 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)

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class SamConfig(AppConfig):
name = 'apps.pm'
verbose_name = '生产计划管理'

View File

@ -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': '生产计划',
},
),
]

View File

@ -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='生产数量'),
),
]

View File

View File

@ -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

View File

@ -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__'

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

13
hb_server/apps/pm/urls.py Normal file
View File

@ -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)),
]

View File

@ -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()

View File

@ -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='已排数量'),
),
]

View File

@ -5,6 +5,7 @@ from django.db.models.base import Model
import django.utils.timezone as timezone import django.utils.timezone as timezone
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from utils.model import SoftModel, BaseModel from utils.model import SoftModel, BaseModel
from apps.mtm.models import Material 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) 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) product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE)
count = models.IntegerField('所需数量', default=0) count = models.IntegerField('所需数量', default=0)
planed_count = models.IntegerField('已排数量', default=0)
delivery_date = models.DateField('交货日期') delivery_date = models.DateField('交货日期')
class Meta: class Meta:
verbose_name = '订单信息' verbose_name = '订单信息'

View File

@ -3,7 +3,9 @@ from apps.sam.models import Contract, Customer, Order
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from apps.system.mixins import CreateUpdateCustomMixin from apps.system.mixins import CreateUpdateCustomMixin
from django.shortcuts import render 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. # Create your views here.
class CustomerViewSet(CreateUpdateCustomMixin, ModelViewSet): class CustomerViewSet(CreateUpdateCustomMixin, ModelViewSet):
""" """
@ -56,3 +58,13 @@ class OrderViewSet(CreateUpdateCustomMixin, ModelViewSet):
if self.action in ['create', 'update']: if self.action in ['create', 'update']:
return OrderCreateUpdateSerializer return OrderCreateUpdateSerializer
return OrderSerializer 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)

View File

@ -115,6 +115,13 @@ class TicketFlowSerializer(serializers.ModelSerializer):
model = TicketFlow model = TicketFlow
fields = '__all__' 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): class TicketHandleSerializer(serializers.Serializer):
transition = serializers.IntegerField(label="流转id") transition = serializers.IntegerField(label="流转id")

View File

@ -57,10 +57,11 @@ class WfService(object):
@classmethod @classmethod
def get_ticket_steps(cls, ticket:Ticket): def get_ticket_steps(cls, ticket:Ticket):
steps = cls.get_worlflow_states(ticket.workflow) steps = cls.get_worlflow_states(ticket.workflow)
nsteps_list = []
for i in steps: for i in steps:
if ticket.state.is_hidden and ticket.state != i: if ticket.state == i or (not i.is_hidden):
steps.remove(i) nsteps_list.append(i)
return steps return nsteps_list
@classmethod @classmethod
def get_ticket_transitions(cls, ticket:Ticket): def get_ticket_transitions(cls, ticket:Ticket):
@ -104,7 +105,7 @@ class WfService(object):
expression = i['expression'].format(**ticket_all_value) expression = i['expression'].format(**ticket_all_value)
import datetime, time # 用于支持条件表达式中对时间的操作 import datetime, time # 用于支持条件表达式中对时间的操作
if eval(expression): 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 return destination_state
@classmethod @classmethod

View File

@ -1,6 +1,6 @@
from django.db.models import base from django.db.models import base
from rest_framework import urlpatterns 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 django.urls import path, include
from rest_framework.routers import DefaultRouter 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('transition', TransitionViewSet, basename='wf_transitions')
router.register('customfield', CustomFieldViewSet, basename='wf_customfield') router.register('customfield', CustomFieldViewSet, basename='wf_customfield')
router.register('ticket', TicketViewSet, basename='wf_ticket') router.register('ticket', TicketViewSet, basename='wf_ticket')
router.register('ticketflow', TicketFlowViewSet, basename='wf_ticketflow')
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
] ]

View File

@ -3,7 +3,7 @@ from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import serializers from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin 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 django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
@ -248,6 +248,16 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
steps = WfService.get_ticket_steps(ticket) steps = WfService.get_ticket_steps(ticket)
return Response(StateSerializer(instance=steps, many=True).data) 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':'*'}) @action(methods=['get'], detail=True, perms_map={'get':'*'})
def transitions(self, request, pk=None): def transitions(self, request, pk=None):
""" """
@ -277,6 +287,13 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
else: else:
raise APIException('无需接单') raise APIException('无需接单')
@action(methods=['post'], detail=True, perms_map={'post':'*'})
def retreat(self, request, pk=None):
"""
撤回工单允许创建人在指定状态撤回工单至初始状态状态设置中开启允许撤回
"""
pass
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
""" """
工单日志 工单日志
@ -285,5 +302,5 @@ class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
queryset = TicketFlow.objects.all() queryset = TicketFlow.objects.all()
serializer_class = TicketFlowSerializer serializer_class = TicketFlowSerializer
search_fields = ['suggestion'] search_fields = ['suggestion']
filterset_fields = ['paticipant', 'state', 'ticket'] filterset_fields = ['ticket']
ordering = ['-create_time'] ordering = ['-create_time']

View File

@ -54,7 +54,8 @@ INSTALLED_APPS = [
'apps.mtm', 'apps.mtm',
'apps.inm', 'apps.inm',
'apps.sam', 'apps.sam',
'apps.qm' 'apps.qm',
'apps.pm'
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -67,7 +67,7 @@ urlpatterns = [
path('api/inm/', include('apps.inm.urls')), path('api/inm/', include('apps.inm.urls')),
path('api/sam/', include('apps.sam.urls')), path('api/sam/', include('apps.sam.urls')),
path('api/qm/', include('apps.qm.urls')), path('api/qm/', include('apps.qm.urls')),
path('api/pm/', include('apps.pm.urls')),
# 工具 # 工具
path('api/utils/signature/', GenSignature.as_view()), path('api/utils/signature/', GenSignature.as_view()),