factory/apps/inm/views.py

556 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.shortcuts import render
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 django.conf import settings
from rest_framework import serializers
from django.utils import timezone
from rest_framework.response import Response
from django.db.models import Sum
from apps.inm.models import WareHouse, MaterialBatch, MIO, MIOItem, MIOItemw, Pack
from apps.inm.serializers import (
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer, MIOItemListSimpleSerializer)
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.inm.services import InmService
from apps.inm.services_daoru import daoru_mb, daoru_mioitem_test, daoru_mioitems
from apps.utils.mixins import (BulkCreateModelMixin, BulkDestroyModelMixin, BulkUpdateModelMixin,
CustomListModelMixin)
from apps.utils.permission import has_perm
from .filters import MaterialBatchFilter, MioFilter
from apps.qm.serializers import FtestProcessSerializer
from apps.mtm.models import Material
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.db import connection
from datetime import datetime
# Create your views here.
class WarehouseVIewSet(CustomModelViewSet):
"""
list: 仓库信息
仓库信息
"""
queryset = WareHouse.objects.all()
serializer_class = WareHourseSerializer
search_fields = ['name', 'number', 'place']
ordering = ['create_time']
def perform_destroy(self, instance):
if MaterialBatch.objects.filter(warehouse=instance).exclude(count=0).exists():
raise ParseError('该仓库存在物料')
instance.delete()
class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet):
"""
list: 物料批次
物料批次
"""
perms_map = {'get': '*'}
queryset = MaterialBatch.objects.filter(count__gt=0)
serializer_class = MaterialBatchSerializer
retrieve_serializer_class = MaterialBatchDetailSerializer
select_related_fields = ['warehouse', 'material', 'supplier']
filterset_class = MaterialBatchFilter
search_fields = ['material__name', 'material__number',
'material__model', 'material__specification', 'batch']
ordering = ['-update_time']
@action(methods=['post'], detail=False, serializer_class=serializers.Serializer, perms_map={'post': 'materialbatch.daoru'})
@transaction.atomic
def daoru(self, request, *args, **kwargs):
"""导入物料批次
导入物料
"""
daoru_mb(settings.BASE_DIR + request.data.get('path', ''))
return Response()
class MioDoViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
perms_map = {'post': '*', 'put': 'mio.do'}
queryset = MIO.objects.filter(
type__in=[MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT])
serializer_class = MIODoSerializer
def create(self, request, *args, **kwargs):
"""
生产领料/入库
生产领料/入库
"""
return super().create(request, *args, **kwargs)
class MioSaleViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
perms_map = {'post': '*', 'put': 'mio.sale'}
queryset = MIO.objects.filter(
type__in=[MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT])
serializer_class = MIOSaleSerializer
def create(self, request, *args, **kwargs):
"""
销售出库
销售出库
"""
return super().create(request, *args, **kwargs)
class MioPurViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
perms_map = {'post': '*', 'put': 'mio.pur'}
queryset = MIO.objects.filter(
type__in=[MIO.MIO_TYPE_PUR_IN])
serializer_class = MIOPurSerializer
def create(self, request, *args, **kwargs):
"""
采购入库
采购入库
"""
return super().create(request, *args, **kwargs)
class MioOtherViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
perms_map = {'post': '*', 'put': 'mio.other'}
queryset = MIO.objects.filter(
type__in=[MIO.MIO_TYPE_OTHER_OUT, MIO.MIO_TYPE_OTHER_IN])
serializer_class = MIOOtherSerializer
def create(self, request, *args, **kwargs):
"""
其他出入库
其他出入库
"""
return super().create(request, *args, **kwargs)
class MIOViewSet(CustomModelViewSet):
"""
list: 出入库记录
出入库记录
"""
queryset = MIO.objects.all()
select_related_fields = ['create_by', 'belong_dept', 'do_user',
'submit_user', 'supplier', 'order', 'customer', 'pu_order', 'mgroup']
prefetch_related_fields = ['materials']
serializer_class = MIOListSerializer
retrieve_serializer_class = MIODetailSerializer
filterset_class = MioFilter
search_fields = ['id', 'number', 'item_mio__batch', 'item_mio__material__name', 'item_mio__material__specification', 'item_mio__material__model',
'item_mio__a_mioitem__batch']
data_filter = True
@classmethod
def lock_and_check_can_update(cls, mio:MIO):
if not connection.in_atomic_block:
raise ParseError("请在事务中调用该方法")
mio:MIO = MIO.objects.select_for_update().get(id=mio.id)
if mio.submit_time is not None:
raise ParseError("该记录已提交无法更改")
return mio
def add_info_for_list(self, data):
# 1. 收集所有mio的ID
mio_ids = [item['id'] for item in data]
# 2. 预初始化mio字典和items列表
mio_dict = {item['id']: {
**item,
'is_tested': False, # 默认值设为False
'mioitems': []
} for item in data}
# 3. 批量查询MIOItem数据
if mio_ids: # 避免空查询
mioitems = MIOItemListSimpleSerializer(
instance=MIOItem.objects.filter(
mio__id__in=mio_ids
).select_related("warehouse", "material"),
many=True
).data
# 4. 单次循环处理所有item
for item in mioitems:
mio_id = item['mio']
if mio_id in mio_dict:
mio_dict[mio_id]['mioitems'].append(item)
# 更新is_tested状态只要有一个item有test_date就为True
if item.get('test_date'):
mio_dict[mio_id]['is_tested'] = True
# 5. 直接返回原始data列表避免额外转换
for item in data:
item.update(mio_dict[item['id']])
return data
def get_serializer_class(self):
if self.action in ['create', 'update', 'partial_update']:
type = self.request.data.get('type')
user = self.request.user
if type in [MIO.MIO_TYPE_DO_IN, MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_BORROW_OUT, MIO.MIO_TYPE_RETURN_IN]:
if not has_perm(user, ['mio.do']):
raise PermissionDenied
return MIODoSerializer
elif type in [MIO.MIO_TYPE_OTHER_IN, MIO.MIO_TYPE_OTHER_OUT]:
if not has_perm(user, ['mio.other']):
raise PermissionDenied
return MIOOtherSerializer
elif type == MIO.MIO_TYPE_SALE_OUT:
if not has_perm(user, ['mio.sale']):
raise PermissionDenied
return MIOSaleSerializer
elif type in [MIO.MIO_TYPE_PUR_IN, MIO.MIO_TYPE_PUR_OUT]:
if not has_perm(user, ['mio.pur']):
raise PermissionDenied
return MIOPurSerializer
return super().get_serializer_class()
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.submit'}, serializer_class=serializers.Serializer)
@transaction.atomic
def submit(self, request, *args, **kwargs):
"""提交
提交
"""
ins:MIO = self.get_object()
if ins.inout_date is None:
raise ParseError('出入库日期未填写')
if ins.state != MIO.MIO_CREATE:
raise ParseError('记录状态异常')
now = timezone.now()
ins.submit_user = request.user
ins.submit_time = now
ins.update_by = request.user
ins.state = MIO.MIO_SUBMITED
ins.save()
InmService.update_inm(ins)
return Response(MIOListSerializer(instance=ins).data)
@action(methods=['post'], detail=True, perms_map={'post': 'mio.submit'}, serializer_class=serializers.Serializer)
@transaction.atomic
def revert(self, request, *args, **kwargs):
"""撤回
撤回
"""
ins = self.get_object()
user = self.request.user
if ins.state != MIO.MIO_SUBMITED:
raise ParseError('记录状态异常')
if ins.submit_user != user:
raise ParseError('非提交人不可撤回')
ins.submit_user = None
ins.update_by = user
ins.state = MIO.MIO_CREATE
ins.submit_time = None
ins.save()
InmService.update_inm(ins, is_reverse=True)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=PackMioSerializer)
@transaction.atomic
def pack_mioitem(self, request, *args, **kwargs):
"""装箱
装箱
"""
mio:MIO = self.get_object()
if mio.submit_time is not None:
raise ParseError('该出入库已提交不可装箱')
sr = PackMioSerializer(data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
pack_index = vdata["pack_index"]
mioitems = vdata["mioitems"]
if not mioitems:
raise ParseError('未选择明细')
for id in mioitems:
mioitem = MIOItem.objects.get(id=id)
if mioitem.mio != mio:
raise ParseError('存在明细不属于该箱')
mioitem.pack_index = pack_index
mioitem.save(update_fields=['pack_index', 'update_time'])
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
def daoru_mioitem(self, request, *args, **kwargs):
"""导入明细
导入明细
"""
daoru_mioitems(settings.BASE_DIR + request.data.get('path', ''), mio=self.get_object())
return Response()
class PackViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
"""
list: 装箱记录
装箱记录
"""
perms_map = {'get': '*', 'post': '*', 'delete': '*'}
queryset = Pack.objects.all()
serializer_class = PackSerializer
filterset_fields = ["mio"]
ordering = ["mio", "index"]
@action(methods=['post'], detail=False, perms_map={'post': 'mio.update'}, serializer_class=PackMioSerializer)
@transaction.atomic
def pack_mioitem(self, request, *args, **kwargs):
"""装箱
装箱
"""
vdata = PackMioSerializer(data=request.data)
packId = vdata["pack"]
pack:Pack = Pack.objects.get(id=packId)
mioitems = vdata["mioitems"]
if not mioitems:
raise ParseError('未选择明细')
for id in mioitems:
mioitem = MIOItem.objects.get(id=id)
if mioitem.mio != pack.mio:
raise ParseError('存在明细不属于该装箱记录')
mioitem.pack = pack
mioitem.save(update_fields=['pack', 'update_time'])
return Response()
class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
"""
list: 出入库明细
出入库明细
"""
perms_map = {'get': '*', 'post': '*', 'delete': '*'}
queryset = MIOItem.objects.all()
serializer_class = MIOItemSerializer
retrieve_serializer_class = MioItemDetailSerializer
create_serializer_class = MIOItemCreateSerializer
select_related_fields = ['warehouse', 'mio', 'material', 'test_user']
filterset_fields = {
"warehouse": ["exact"],
"mio": ["exact"],
"mio__state": ["exact"],
"mio__type": ["exact", "in"],
"mio__inout_date": ["gte", "lte", "exact"],
"material": ["exact"],
"material__type": ["exact"],
"test_date": ["isnull", "exact"]
}
ordering = ['create_time']
ordering_fields = ['create_time', 'test_date']
search_fields =['batch', 'a_mioitem__batch']
def add_info_for_list(self, data):
with_mio = self.request.query_params.get('with_mio', "no")
if with_mio == "yes" and isinstance(data, list):
mio_ids = [item['mio'] for item in data]
mio_qs = MIO.objects.filter(id__in=mio_ids)
mio_qs_= MIOListSerializer(mio_qs, many=True).data
mio_dict = {mio['id']: mio for mio in mio_qs_}
for item in data:
mioId = item['mio']
item['mio_'] = mio_dict[mioId]
return data
@swagger_auto_schema(manual_parameters=[
openapi.Parameter(name="with_mio", in_=openapi.IN_QUERY, description="是否返回出入库记录信息",
type=openapi.TYPE_STRING, required=False),
openapi.Parameter(name="query", in_=openapi.IN_QUERY, description="定制返回数据",
type=openapi.TYPE_STRING, required=False),
openapi.Parameter(name="with_children", in_=openapi.IN_QUERY, description="带有children(yes/no/count)",
type=openapi.TYPE_STRING, required=False)
])
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.validated_data["mio"] = MIOViewSet.lock_and_check_can_update(serializer.validated_data['mio'])
return super().perform_create(serializer)
def perform_destroy(self, instance):
MIOViewSet.lock_and_check_can_update(instance.mio)
if has_perm(self.request.user, ['mio.update']) is False and instance.mio.create_by != self.request.user:
raise PermissionDenied('无权限删除')
return super().perform_destroy(instance)
@action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
@transaction.atomic
def revert_and_del(self, request, *args, **kwargs):
"""撤回并删除
撤回并删除
"""
ins:MIOItem = self.get_object()
InmService.revert_and_del(ins)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemTestSerializer)
@transaction.atomic
def test(self, request, *args, **kwargs):
"""半成品检验
半成品检验
"""
ins: MIOItem = self.get_object()
if ins.material.tracking != Material.MA_TRACKING_BATCH:
raise ParseError('该物料非批次管理')
mio = ins.mio
if ins.test_date:
raise ParseError('该明细已检验')
if mio.state != MIO.MIO_SUBMITED:
raise ParseError('该出入库记录还未提交')
sr = MIOItemTestSerializer(instance=ins, data=request.data)
sr.is_valid(raise_exception=True)
sr.save()
# 开始变动库存
InmService.update_mb_item(ins, -1, 'count_notok')
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=serializers.Serializer)
@transaction.atomic
def test_revert(self, request, *args, **kwargs):
"""
检验撤回
"""
ins: MIOItem = self.get_object()
if ins.test_date is None:
raise ParseError('该明细还未检验')
if ins.count_notok > 0:
InmService.update_mb_item(ins, 1, 'count_notok')
elif ins.count_notok == 0:
pass
ins.test_date = None
ins.save()
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemPurInTestSerializer)
@transaction.atomic
def test_pur_in(self, request, *args, **kwargs):
"""入厂检验
入厂检验
"""
ins: MIOItem = self.get_object()
if ins.material.tracking != Material.MA_TRACKING_BATCH:
raise ParseError('该物料非批次管理')
if ins.test_date:
raise ParseError('该明细已检验')
sr = MIOItemPurInTestSerializer(instance=ins, data=request.data)
sr.is_valid(raise_exception=True)
sr.save()
return Response()
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MioItemAnaSerializer)
def sale_out_ana(self, request, *args, **kwargs):
"""交付统计数据
交付统计数据
"""
sr = MIOItemTestSerializer(data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
mioitems = MIOItem.objects.filter(
mio__type='sale_out', mio__state=MIO.MIO_SUBMITED)
if vdata.get('material_cate'):
mioitems = mioitems.filter(
material__cate=vdata['material_cate'])
if vdata.get('start_date', ''):
mioitems = mioitems.filter(
mio__inout_date__gte=vdata['start_date'])
if vdata.get('end_date', ''):
mioitems = mioitems.filter(mio__inout_date__lte=vdata['end_date'])
res = mioitems.aggregate(count=Sum('count'))
for i in res:
if res[i] is None:
res[i] = 0
return Response(res)
@action(methods=['post'], detail=True, perms_map={'post': 'mio.update'}, serializer_class=serializers.Serializer)
@transaction.atomic
def test_daoru_bg(self, request, *args, **kwargs):
"""导入棒管检验
导入棒管检验
"""
daoru_mioitem_test(path=settings.BASE_DIR + request.data.get('path', ''), mioitem=self.get_object())
return Response()
class MIOItemwViewSet(CustomModelViewSet):
perms_map = {'get': '*', 'post': 'mio.update', 'put': 'mio.update', 'delete': 'mio.update'}
queryset = MIOItemw.objects.all()
serializer_class = MIOItemwCreateUpdateSerializer
filterset_fields = {
'mioitem': ['exact'],
'mioitem__material__type': ['exact'],
"wpr": ['exact'],
"number": ["exact"],
"ftest": ["isnull"],
"mioitem__mio__state": ["exact"]
}
select_related_fields = ["ftest"]
ordering = ["number", "create_time"]
ordering_fields = ["number", "create_time"]
def filter_queryset(self, queryset):
if not self.detail:
if not self.request.query_params.get('mioitem', None):
if "ftest__isnull" in self.request.query_params:
pass
else:
raise ParseError('请指定所属出入库记录明细')
return super().filter_queryset(queryset)
def cal_mioitem_count(self, mioitem:MIOItem):
count = MIOItemw.objects.filter(mioitem=mioitem).count()
mioitem.count = count
mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
if mioitem.test_date is None:
mioitem.test_date = datetime.now()
if mioitem.test_user is None:
mioitem.test_user = self.request.user
mioitem.save()
def perform_create(self, serializer):
MIOViewSet.lock_and_check_can_update(serializer.validated_data['mioitem'].mio)
ins:MIOItemw = serializer.save()
self.cal_mioitem_count(ins.mioitem)
def perform_update(self, serializer):
ins:MIOItemw = serializer.instance
MIOViewSet.lock_and_check_can_update(ins.mioitem.mio)
ins:MIOItemw = serializer.save()
self.cal_mioitem_count(ins.mioitem)
def perform_destroy(self, instance: MIOItemw):
mioitem = instance.mioitem
MIOViewSet.lock_and_check_can_update(mioitem.mio)
ftest = instance.ftest
instance.delete()
if ftest:
ftest.delete()
self.cal_mioitem_count(mioitem)