diff --git a/apps/inm/models.py b/apps/inm/models.py index e611d264..52108b4d 100644 --- a/apps/inm/models.py +++ b/apps/inm/models.py @@ -1,5 +1,7 @@ from django.db import models -from apps.utils.models import BaseModel, CommonBModel +from apps.utils.models import BaseModel, CommonBModel, CommonBDModel +from apps.pum.models import Supplier, PuOrder +from apps.sam.models import Customer, Order from apps.mtm.models import Material # Create your models here. @@ -23,3 +25,52 @@ class MaterialBatch(BaseModel): WareHouse, on_delete=models.CASCADE, verbose_name='所在仓库') count = models.PositiveIntegerField('存量', default=0) expiration_date = models.DateField('有效期', null=True, blank=True) + + +class MIO(CommonBDModel): + """ + 出入库记录 + """ + MIO_TYPE_DO_OUT = 'do_out' + MIO_TYPE_SALE_OUT = 'sale_out' + MIO_TYPE_PUR_IN = 'pur_in' + MIO_TYPE_DO_IN = 'do_in' + MIO_TYPE_OTHER_IN = 'other_in' + MIO_TYPE_OTHER_OUT = 'other_out' + + MIO_TYPES = ( + (MIO_TYPE_DO_OUT, '生产领料'), + (MIO_TYPE_SALE_OUT, '销售发货'), + (MIO_TYPE_PUR_IN, '采购入库'), + (MIO_TYPE_DO_IN, '生产入库'), + (MIO_TYPE_OTHER_IN, '其他入库'), + (MIO_TYPE_OTHER_OUT, '其他出库') + ) + MIO_CREATE = 10 + MIO_SUBMITED = 20 + MIO_STATES = ( + (MIO_CREATE, '创建中'), + (MIO_SUBMITED, '已提交') + ) + number = models.CharField('编号', max_length=20) + state = models.PositiveSmallIntegerField('状态', choices=MIO_STATES, default=10, help_text=str(MIO_CREATE)) + type = models.CharField('出入库类型', max_length=10, default=MIO_TYPE_DO_OUT, choices=MIO_TYPES, help_text=str(MIO_TYPES)) + inout_date = models.DateField('出入库日期', null=True, blank=True) + supplier = models.ForeignKey(Supplier, verbose_name='供应商', on_delete=models.CASCADE, null=True, blank=True) + customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE, null=True, blank=True) + pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', on_delete=models.CASCADE, null=True, blank=True) + order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True) + note = models.CharField('备注', max_length=1000, default='') + expiration_date = models.DateField('有效期', null=True, blank=True) + submit_time = models.DateTimeField('提交时间', null=True, blank=True) + + +class MIOItem(BaseModel): + """ + 出入库明细 + """ + mio = models.ForeignKey(MIO, verbose_name='关联出入库', on_delete=models.CASCADE) + warehouse = models.ForeignKey(WareHouse, on_delete=models.CASCADE, verbose_name='仓库') + material = models.ForeignKey(Material, verbose_name='物料', on_delete=models.CASCADE) + batch = models.CharField('批次号', max_length=20) + count = models.PositiveIntegerField('数量', default=0) \ No newline at end of file diff --git a/apps/inm/serializers.py b/apps/inm/serializers.py index 3938fb16..0f1d34d5 100644 --- a/apps/inm/serializers.py +++ b/apps/inm/serializers.py @@ -1,7 +1,9 @@ from rest_framework import serializers from apps.utils.serializers import CustomModelSerializer -from apps.inm.models import WareHouse, MaterialBatch +from apps.inm.models import WareHouse, MaterialBatch, MIO, MIOItem from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE +from apps.pum.models import PuOrder +from rest_framework.exceptions import ValidationError class WareHourseSerializer(CustomModelSerializer): @@ -17,4 +19,33 @@ class MaterialBatchSerializer(CustomModelSerializer): class Meta: model = MaterialBatch fields = '__all__' - read_only_fields = EXCLUDE_FIELDS_BASE \ No newline at end of file + read_only_fields = EXCLUDE_FIELDS_BASE + + +class MIOSerializer(CustomModelSerializer): + create_by_name = serializers.CharField(source='create_by.name', read_only=True) + class Meta: + model = MIO + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_DEPT + ['state', 'submit_time'] + + def validate(self, attrs): + if 'pu_order' in attrs and attrs['pu_order']: + attrs['supplier'] = attrs['pu_order'].supplier + elif 'order' in attrs and attrs['order']: + attrs['customer'] = attrs['order'].customer + return super().validate(attrs) + + def create(self, validated_data): + type = validated_data['type'] + if type == MIO.MIO_TYPE_PUR_IN: + pu_order = validated_data.get('pu_order', None) + if pu_order and pu_order.state in [PuOrder.PUORDER_SUBMITED, PuOrder.PUORDER_SHIP]: + pass + else: + raise ValidationError('该采购订单不可选') + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data.pop('type') + return super().update(instance, validated_data) \ No newline at end of file diff --git a/apps/inm/services.py b/apps/inm/services.py new file mode 100644 index 00000000..f42ca335 --- /dev/null +++ b/apps/inm/services.py @@ -0,0 +1,42 @@ +from apps.inm.models import MIO, MIOItem, MaterialBatch +from rest_framework.exceptions import ValidationError + + +class InmService: + + @classmethod + def update_inm(cls, instance: MIO): + """ + 更新库存, 暂不支持反向操作 + """ + if instance.type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_OTHER_IN]: # 采购入库, 生产入库, 其他入库 + cls.update_mb(instance) + if instance.type == MIO.MIO_TYPE_PUR_IN: # 需要更新订单 + from apps.pum.services import PumService + PumService.mio_purin(instance) + elif instance.type in [MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_SALE_OUT, MIO.MIO_TYPE_OTHER_OUT]: # 生产领料 销售出库 + cls.update_mb(instance, -1) + if instance.type == MIO.MIO_TYPE_SALE_OUT: + from apps.sam.services import SamService + SamService.mio_saleout(instance) + + @classmethod + def update_mb(cls, instance: MIO, in_or_out: int =1): + """ + 更新物料批次 + """ + for i in MIOItem.objects.filter(mio=instance): + material = i.material + warehouse = i.warehouse + mb, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch,\ + defaults={'material':material, 'warehouse':warehouse, 'count':0, 'batch':i.batch}) + if in_or_out == 1: + mb.count = mb.count + i.count + if mb.expiration_date is None: + mb.expiration_date = i.expiration_date + mb.save() + elif in_or_out == -1: + mb.count = mb.count - i.count + if mb.count < 0: + raise ValidationError('批次库存不足,操作失败') + mb.save() \ No newline at end of file diff --git a/apps/inm/urls.py b/apps/inm/urls.py index d2daaf55..31f4d54b 100644 --- a/apps/inm/urls.py +++ b/apps/inm/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.inm.views import (WarehouseVIewSet, MaterialBatchViewSet) +from apps.inm.views import (WarehouseVIewSet, MaterialBatchViewSet, MIOViewSet) API_BASE_URL = 'api/inm/' HTML_BASE_URL = 'inm/' @@ -8,6 +8,7 @@ HTML_BASE_URL = 'inm/' router = DefaultRouter() router.register('warehouse', WarehouseVIewSet, basename='warehouse') router.register('materialbatch', MaterialBatchViewSet, basename='materialbatch') +router.register('mio', MIOViewSet, basename='mio') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] \ No newline at end of file diff --git a/apps/inm/views.py b/apps/inm/views.py index 06c9453b..7ad3507b 100644 --- a/apps/inm/views.py +++ b/apps/inm/views.py @@ -1,10 +1,16 @@ from django.shortcuts import render -from rest_framework.mixins import ListModelMixin -from rest_framework.exceptions import ParseError +from rest_framework.mixins import ListModelMixin, DestroyModelMixin +from rest_framework.exceptions import ParseError, PermissionDenied +from rest_framework.decorators import action +from django.db import transaction +from rest_framework import serializers +from django.utils import timezone +from rest_framework.response import Response -from apps.inm.models import WareHouse, MaterialBatch -from apps.inm.serializers import MaterialBatchSerializer, WareHourseSerializer +from apps.inm.models import WareHouse, MaterialBatch, MIO +from apps.inm.serializers import (MaterialBatchSerializer, WareHourseSerializer, MIOSerializer) from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet +from apps.inm.services import InmService # Create your views here. @@ -36,4 +42,42 @@ class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet): serializer_class = MaterialBatchSerializer select_related_fields = ['warehouse', 'material'] filterset_fields = ['warehouse', 'material'] - \ No newline at end of file + + +class MIOViewSet(ListModelMixin, DestroyModelMixin, CustomGenericViewSet): + """ + list: 出入库记录 + + 出入库记录 + """ + perms_map = {'get': '*', 'delete': 'mio.delete'} + queryset = MIO.objects.all() + select_related_fields = ['create_by'] + serializer_class = MIOSerializer + + def perform_destroy(self, instance): + if instance.state != MIO.MIO_CREATE: + raise ParseError('非创建中不可删除') + return super().perform_destroy(instance) + + @action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer) + @transaction.atomic + def submit(self, request, *args, **kwargs): + """提交 + + 提交 + """ + ins = self.get_object() + user = request.user + if ins.create_by != user: + raise PermissionDenied('非创建人不可提交') + if ins.state != MIO.MIO_CREATE: + raise ParseError('订单非创建中') + ins.submit_time = timezone.now() + ins.state = MIO.MIO_SUBMITED + ins.save() + InmService.update_inm(ins) + return Response() + +class MIOItemViewSet(CustomModelViewSet): + pass \ No newline at end of file