feat: 增加出入库记录
This commit is contained in:
parent
e4c3977c52
commit
c6af3f7f62
|
@ -1,5 +1,7 @@
|
||||||
from django.db import models
|
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
|
from apps.mtm.models import Material
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
|
@ -23,3 +25,52 @@ class MaterialBatch(BaseModel):
|
||||||
WareHouse, on_delete=models.CASCADE, verbose_name='所在仓库')
|
WareHouse, on_delete=models.CASCADE, verbose_name='所在仓库')
|
||||||
count = models.PositiveIntegerField('存量', default=0)
|
count = models.PositiveIntegerField('存量', default=0)
|
||||||
expiration_date = models.DateField('有效期', null=True, blank=True)
|
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)
|
|
@ -1,7 +1,9 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from apps.utils.serializers import CustomModelSerializer
|
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.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE
|
||||||
|
from apps.pum.models import PuOrder
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
class WareHourseSerializer(CustomModelSerializer):
|
class WareHourseSerializer(CustomModelSerializer):
|
||||||
|
@ -18,3 +20,32 @@ class MaterialBatchSerializer(CustomModelSerializer):
|
||||||
model = MaterialBatch
|
model = MaterialBatch
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
read_only_fields = EXCLUDE_FIELDS_BASE
|
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)
|
|
@ -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()
|
|
@ -1,6 +1,6 @@
|
||||||
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.inm.views import (WarehouseVIewSet, MaterialBatchViewSet)
|
from apps.inm.views import (WarehouseVIewSet, MaterialBatchViewSet, MIOViewSet)
|
||||||
|
|
||||||
API_BASE_URL = 'api/inm/'
|
API_BASE_URL = 'api/inm/'
|
||||||
HTML_BASE_URL = 'inm/'
|
HTML_BASE_URL = 'inm/'
|
||||||
|
@ -8,6 +8,7 @@ HTML_BASE_URL = 'inm/'
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register('warehouse', WarehouseVIewSet, basename='warehouse')
|
router.register('warehouse', WarehouseVIewSet, basename='warehouse')
|
||||||
router.register('materialbatch', MaterialBatchViewSet, basename='materialbatch')
|
router.register('materialbatch', MaterialBatchViewSet, basename='materialbatch')
|
||||||
|
router.register('mio', MIOViewSet, basename='mio')
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(API_BASE_URL, include(router.urls)),
|
path(API_BASE_URL, include(router.urls)),
|
||||||
]
|
]
|
|
@ -1,10 +1,16 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from rest_framework.mixins import ListModelMixin
|
from rest_framework.mixins import ListModelMixin, DestroyModelMixin
|
||||||
from rest_framework.exceptions import ParseError
|
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.models import WareHouse, MaterialBatch, MIO
|
||||||
from apps.inm.serializers import MaterialBatchSerializer, WareHourseSerializer
|
from apps.inm.serializers import (MaterialBatchSerializer, WareHourseSerializer, MIOSerializer)
|
||||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||||
|
from apps.inm.services import InmService
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
@ -37,3 +43,41 @@ class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet):
|
||||||
select_related_fields = ['warehouse', 'material']
|
select_related_fields = ['warehouse', 'material']
|
||||||
filterset_fields = ['warehouse', 'material']
|
filterset_fields = ['warehouse', 'material']
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in New Issue