feat: 增加组合件出入库
This commit is contained in:
parent
38ad10d445
commit
bc2a16a9a4
|
@ -0,0 +1,13 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from apps.inm.models import MaterialBatch
|
||||
|
||||
|
||||
class MaterialBatchFilter(filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = MaterialBatch
|
||||
fields = {
|
||||
"warehouse": ["exact"],
|
||||
"material": ["exact"],
|
||||
"count": ["exact", "gte", "lte"]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
# Generated by Django 3.2.12 on 2023-10-24 02:25
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0017_auto_20231018_1033'),
|
||||
('inm', '0004_auto_20230927_1700'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MIOItemA',
|
||||
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='删除标记')),
|
||||
('batch', models.CharField(max_length=50, verbose_name='批次号')),
|
||||
('rate', models.PositiveIntegerField(default=1, verbose_name='比例')),
|
||||
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料')),
|
||||
('mioitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='a_mioitem', to='inm.mioitem', verbose_name='关联出入库明细')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MaterialBatchA',
|
||||
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='删除标记')),
|
||||
('batch', models.CharField(max_length=100, verbose_name='批次号')),
|
||||
('rate', models.PositiveIntegerField(default=1, verbose_name='比例')),
|
||||
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料')),
|
||||
('mb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='a_mb', to='inm.materialbatch', verbose_name='关联物料批次')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -29,6 +29,18 @@ class MaterialBatch(BaseModel):
|
|||
expiration_date = models.DateField('有效期', null=True, blank=True)
|
||||
|
||||
|
||||
class MaterialBatchA(BaseModel):
|
||||
"""
|
||||
组合件物料批次
|
||||
"""
|
||||
batch = models.CharField('批次号', max_length=100)
|
||||
material = models.ForeignKey(
|
||||
Material, on_delete=models.CASCADE, verbose_name='物料')
|
||||
rate = models.PositiveIntegerField('比例', default=1)
|
||||
mb = models.ForeignKey(
|
||||
MaterialBatch, verbose_name='关联物料批次', on_delete=models.CASCADE, related_name='a_mb')
|
||||
|
||||
|
||||
class MIO(CommonBDModel):
|
||||
"""
|
||||
出入库记录
|
||||
|
@ -89,3 +101,15 @@ class MIOItem(BaseModel):
|
|||
Material, verbose_name='物料', on_delete=models.CASCADE)
|
||||
batch = models.CharField('批次号', max_length=50)
|
||||
count = models.PositiveIntegerField('数量', default=0)
|
||||
|
||||
|
||||
class MIOItemA(BaseModel):
|
||||
"""
|
||||
组合件出入库明细
|
||||
"""
|
||||
material = models.ForeignKey(
|
||||
Material, verbose_name='物料', on_delete=models.CASCADE)
|
||||
batch = models.CharField('批次号', max_length=50)
|
||||
rate = models.PositiveIntegerField('比例', default=1)
|
||||
mioitem = models.ForeignKey(
|
||||
MIOItem, verbose_name='关联出入库明细', on_delete=models.CASCADE, related_name='a_mioitem')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.exceptions import ValidationError, ParseError
|
||||
|
||||
from apps.mtm.serializers import MaterialSerializer
|
||||
from apps.pum.models import PuOrder
|
||||
|
@ -8,7 +8,8 @@ from apps.system.models import Dept, User
|
|||
from apps.utils.constants import EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
|
||||
from .models import MIO, MaterialBatch, MIOItem, WareHouse
|
||||
from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBatchA
|
||||
from django.db import transaction
|
||||
|
||||
|
||||
class WareHourseSerializer(CustomModelSerializer):
|
||||
|
@ -18,6 +19,14 @@ class WareHourseSerializer(CustomModelSerializer):
|
|||
read_only_fields = EXCLUDE_FIELDS_DEPT
|
||||
|
||||
|
||||
class MaterialBatchAListSerializer(CustomModelSerializer):
|
||||
material_ = MaterialSerializer(source='material', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MaterialBatchA
|
||||
fields = ['material', 'batch', 'rate', 'mb', 'id', 'material_']
|
||||
|
||||
|
||||
class MaterialBatchSerializer(CustomModelSerializer):
|
||||
warehouse_name = serializers.CharField(
|
||||
source='warehouse.name', read_only=True)
|
||||
|
@ -31,6 +40,20 @@ class MaterialBatchSerializer(CustomModelSerializer):
|
|||
read_only_fields = EXCLUDE_FIELDS_BASE
|
||||
|
||||
|
||||
class MaterialBatchDetailSerializer(CustomModelSerializer):
|
||||
warehouse_name = serializers.CharField(
|
||||
source='warehouse.name', read_only=True)
|
||||
material_name = serializers.CharField(
|
||||
source='material.name', read_only=True)
|
||||
material_ = MaterialSerializer(source='material', read_only=True)
|
||||
assemb = MaterialBatchAListSerializer(
|
||||
source='a_mb', read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = MaterialBatch
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MIOListSerializer(CustomModelSerializer):
|
||||
create_by_name = serializers.CharField(
|
||||
source='create_by.name', read_only=True)
|
||||
|
@ -78,20 +101,61 @@ class MIOListSerializer(CustomModelSerializer):
|
|||
# return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class MIOItemACreateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = MIOItem
|
||||
fields = ['material', 'batch']
|
||||
|
||||
|
||||
class MIOItemCreateSerializer(CustomModelSerializer):
|
||||
assemb = MIOItemACreateSerializer(
|
||||
label='组合件信息', many=True, write_only=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = MIOItem
|
||||
fields = ['mio', 'warehouse', 'material', 'batch', 'count', 'assemb']
|
||||
|
||||
def create(self, validated_data):
|
||||
mio = validated_data['mio']
|
||||
material = validated_data['material']
|
||||
if mio.state != MIO.MIO_CREATE:
|
||||
raise ValidationError('出入库记录非创建中不可新增')
|
||||
with transaction.atomic():
|
||||
assemb = validated_data.pop('assemb', [])
|
||||
instance = super().create(validated_data)
|
||||
assemb_dict = {}
|
||||
for i in assemb:
|
||||
assemb_dict[i['material'].id] = i
|
||||
if material.is_assemb and '_in' in mio.type: # 仅入库且是组合件的时候需要填写下一级
|
||||
components = material.components
|
||||
for k, v in components.items():
|
||||
if k in assemb_dict:
|
||||
mia = assemb_dict[k]
|
||||
MIOItemA.objects.create(
|
||||
mioitem=instance, material=mia['material'], batch=mio['batch'], count=v)
|
||||
else:
|
||||
raise ParseError('缺少组合件')
|
||||
return instance
|
||||
|
||||
|
||||
class MIOItemAListSerializer(CustomModelSerializer):
|
||||
material_ = MaterialSerializer(source='material', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MIOItemA
|
||||
fields = ['material', 'batch', 'rate', 'mioitem', 'id', 'material_']
|
||||
|
||||
|
||||
class MIOItemSerializer(CustomModelSerializer):
|
||||
warehouse_name = serializers.CharField(source='warehouse', read_only=True)
|
||||
material_ = MaterialSerializer(source='material', read_only=True)
|
||||
assemb = MIOItemAListSerializer(
|
||||
source='a_mioitem', read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = MIOItem
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data):
|
||||
mio = validated_data['mio']
|
||||
if mio.state != MIO.MIO_CREATE:
|
||||
raise ValidationError('出入库记录非创建中不可新增')
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class MIODoSerializer(CustomModelSerializer):
|
||||
belong_dept = serializers.PrimaryKeyRelatedField(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from apps.inm.models import MIO, MIOItem, MaterialBatch
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from apps.inm.models import MIO, MIOItem, MaterialBatch, MaterialBatchA, MIOItemA
|
||||
from rest_framework.exceptions import ValidationError, ParseError
|
||||
from django.db.models.aggregates import Sum
|
||||
from apps.wpm.services import do_out, do_in
|
||||
from apps.mtm.models import Material
|
||||
|
@ -35,20 +35,27 @@ class InmService:
|
|||
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})
|
||||
mb, is_created = 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()
|
||||
mias = MIOItemA.objects.filter(mioitem=i)
|
||||
if mias.exists(): # 组合件入库
|
||||
if not is_created:
|
||||
raise ParseError('该批次组合件已存在')
|
||||
for mia in mias:
|
||||
MaterialBatchA.objects.create(
|
||||
mb=mb, material=mia.material, batch=mia.batch, count=mia.count)
|
||||
elif in_or_out == -1:
|
||||
mb.count = mb.count - i.count
|
||||
if mb.count < 0:
|
||||
raise ValidationError('批次库存不足,操作失败')
|
||||
raise ParseError('批次库存不足,操作失败')
|
||||
mb.save()
|
||||
else:
|
||||
raise ValidationError('不支持的操作')
|
||||
raise ParseError('不支持的操作')
|
||||
material_count = MaterialBatch.objects.filter(
|
||||
material=material).aggregate(total=Sum('count')).get('total', 0)
|
||||
Material.objects.filter(id=material.id).update(
|
||||
|
|
|
@ -9,11 +9,13 @@ from rest_framework.response import Response
|
|||
|
||||
from apps.inm.models import WareHouse, MaterialBatch, MIO, MIOItem
|
||||
from apps.inm.serializers import (
|
||||
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer)
|
||||
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer,
|
||||
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer, MaterialBatchDetailSerializer)
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from apps.inm.services import InmService
|
||||
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, BulkUpdateModelMixin
|
||||
from apps.utils.permission import has_perm
|
||||
from .filters import MaterialBatchFilter
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
@ -43,8 +45,9 @@ class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
perms_map = {'get': '*'}
|
||||
queryset = MaterialBatch.objects.all()
|
||||
serializer_class = MaterialBatchSerializer
|
||||
retrieve_serializer_class = MaterialBatchDetailSerializer
|
||||
select_related_fields = ['warehouse', 'material']
|
||||
filterset_fields = ['warehouse', 'material']
|
||||
filterset_class = MaterialBatchFilter
|
||||
search_fields = ['material__name']
|
||||
|
||||
|
||||
|
@ -168,6 +171,7 @@ class MIOItemViewSet(ListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin
|
|||
perms_map = {'get': '*', 'post': '*', 'delete': '*'}
|
||||
queryset = MIOItem.objects.all()
|
||||
serializer_class = MIOItemSerializer
|
||||
create_serializer_class = MIOItemCreateSerializer
|
||||
select_related_fields = ['warehouse', 'mio', 'material']
|
||||
filterset_fields = ['warehouse', 'mio', 'material']
|
||||
ordering = ['create_time']
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from apps.wpm.models import SfLog, SfLogExp
|
||||
from apps.wpm.models import SfLog, SfLogExp, WMaterial
|
||||
['mgroup', 'shift', 'team', 'leader', 'team__belong_dept']
|
||||
|
||||
|
||||
class SfLogFilter(filters.FilterSet):
|
||||
class Meta:
|
||||
model = SfLog
|
||||
|
@ -14,8 +16,10 @@ class SfLogFilter(filters.FilterSet):
|
|||
"start_time": ["gte"]
|
||||
}
|
||||
|
||||
|
||||
class SfLogExpFilter(filters.FilterSet):
|
||||
is_st = filters.BooleanFilter(method='filter_is_st', label='是否停机')
|
||||
|
||||
class Meta:
|
||||
model = SfLogExp
|
||||
fields = {
|
||||
|
@ -28,8 +32,20 @@ class SfLogExpFilter(filters.FilterSet):
|
|||
"stlog__start_time": ["day", "month", "year"],
|
||||
"stlog__end_time": ["day", "month", "year"]
|
||||
}
|
||||
|
||||
|
||||
def filter_is_st(self, queryset, name, value):
|
||||
if value:
|
||||
return queryset.exclude(stlog=None)
|
||||
return queryset.filter(stlog=None)
|
||||
return queryset.filter(stlog=None)
|
||||
|
||||
|
||||
class WMaterialFilter(filters.FilterSet):
|
||||
class Meta:
|
||||
model = WMaterial
|
||||
fields = {
|
||||
"material": ["exact", "in"],
|
||||
"belong_dept": ["exact"],
|
||||
"belong_dept__name": ["exact"],
|
||||
"batch": ["exact"],
|
||||
"count": ["gte", "lte", "exact"]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
|||
|
||||
from rest_framework.exceptions import ParseError
|
||||
|
||||
from apps.inm.models import MIO, MIOItem
|
||||
from apps.inm.models import MIO, MIOItem, MIOItemA
|
||||
from apps.pm.models import Mtask
|
||||
from apps.mtm.models import Mgroup, Shift, Material
|
||||
|
||||
|
@ -80,28 +80,38 @@ def do_out(mio: MIO):
|
|||
|
||||
def do_in(mio: MIO):
|
||||
"""
|
||||
生产入库
|
||||
生产入库后更新车间物料
|
||||
"""
|
||||
belong_dept = mio.belong_dept
|
||||
do_user = mio.do_user
|
||||
mioitems = MIOItem.objects.filter(mio=mio)
|
||||
for item in mioitems:
|
||||
try:
|
||||
wm = WMaterial.objects.get(
|
||||
batch=item.batch, material=item.material, belong_dept=belong_dept)
|
||||
except ObjectDoesNotExist:
|
||||
raise ParseError('车间物料不存在!')
|
||||
except MultipleObjectsReturned:
|
||||
raise ParseError('存在多行车间物料!')
|
||||
new_count = wm.count - item.count
|
||||
if new_count > 0:
|
||||
wm.count = new_count
|
||||
wm.update_by = do_user
|
||||
wm.save()
|
||||
elif new_count == 0:
|
||||
wm.delete()
|
||||
action_list = []
|
||||
mias = MIOItemA.objects.filter(mioitem=item)
|
||||
if mias.exists(): # 组合件入库
|
||||
action_list = list(mias.values_list('material', 'batch', 'rate'))
|
||||
for i in action_list:
|
||||
i[2] = i[2]*item.count
|
||||
else:
|
||||
raise ParseError('车间物料不足')
|
||||
action_list = [item.material, item.batch, item.count]
|
||||
for al in action_list:
|
||||
xmaterial, xbatch, xcount = al
|
||||
try:
|
||||
wm = WMaterial.objects.get(
|
||||
batch=xbatch, material=xmaterial, belong_dept=belong_dept)
|
||||
except ObjectDoesNotExist:
|
||||
raise ParseError('车间物料不存在!')
|
||||
except MultipleObjectsReturned:
|
||||
raise ParseError('存在多行车间物料!')
|
||||
new_count = wm.count - xcount
|
||||
if new_count > 0:
|
||||
wm.count = new_count
|
||||
wm.update_by = do_user
|
||||
wm.save()
|
||||
elif new_count == 0:
|
||||
pass
|
||||
else:
|
||||
raise ParseError('车间物料不足')
|
||||
|
||||
|
||||
def mlog_confirm(mlog: Mlog):
|
||||
|
|
|
@ -10,7 +10,7 @@ from apps.pm.models import Mtask
|
|||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from apps.utils.mixins import BulkCreateModelMixin
|
||||
|
||||
from .filters import SfLogExpFilter, SfLogFilter
|
||||
from .filters import SfLogExpFilter, SfLogFilter, WMaterialFilter
|
||||
from .models import SfLog, SfLogExp, StLog, WMaterial, Mlog
|
||||
from .serializers import SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer, MlogSerializer, MlogRelatedSerializer, DeptBatchSerializer
|
||||
|
||||
|
@ -95,7 +95,7 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
select_related_fields = ['material', 'belong_dept']
|
||||
search_fields = ['material__name',
|
||||
'material__number', 'material__specification']
|
||||
filterset_fields = ['material', 'belong_dept']
|
||||
filterset_class = WMaterialFilter
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=DeptBatchSerializer)
|
||||
@transaction.atomic
|
||||
|
|
Loading…
Reference in New Issue