feat: 增加组合件出入库

This commit is contained in:
caoqianming 2023-10-24 10:26:13 +08:00
parent 38ad10d445
commit bc2a16a9a4
9 changed files with 224 additions and 38 deletions

13
apps/inm/filters.py Normal file
View File

@ -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"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]
}

View File

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

View File

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