factory/apps/mpr/serializers.py

272 lines
11 KiB
Python

from decimal import Decimal
from rest_framework import serializers
from rest_framework.exceptions import ParseError
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS
from apps.mpr.models import (
PurchaseRequisition, PurchaseRequisitionItem,
WarehouseEntry, WarehouseEntryItem, WarehouseStock,
MaterialRequisition, MaterialRequisitionItem,
)
from apps.wf.serializers import TicketSimpleSerializer
# ========== 物资申购单 ==========
class PurchaseRequisitionItemSerializer(CustomModelSerializer):
class Meta:
model = PurchaseRequisitionItem
fields = '__all__'
read_only_fields = ['create_time', 'update_time', 'is_deleted']
class PurchaseRequisitionListSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = PurchaseRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class PurchaseRequisitionDetailSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
items_ = PurchaseRequisitionItemSerializer(source='items', many=True, read_only=True)
class Meta:
model = PurchaseRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class PurchaseRequisitionCreateSerializer(CustomModelSerializer):
items = serializers.ListField(child=serializers.DictField(), write_only=True, required=False, default=[])
class Meta:
model = PurchaseRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['belong_dept', 'number', 'total_amount']
def create(self, validated_data):
items_data = validated_data.pop('items', [])
validated_data['number'] = PurchaseRequisition.get_a_number()
instance = super().create(validated_data)
self._save_items(instance, items_data)
return instance
def update(self, instance, validated_data):
items_data = validated_data.pop('items', None)
instance = super().update(instance, validated_data)
if items_data is not None:
instance.items.all().delete()
self._save_items(instance, items_data)
return instance
def _save_items(self, instance, items_data):
total = 0
for item in items_data:
item.pop('id', None)
unit_price = float(item.get('unit_price', 0) or 0)
purchase_quantity = float(item.get('purchase_quantity', 0) or 0)
item['total_price'] = round(unit_price * purchase_quantity, 2)
total += item['total_price']
PurchaseRequisitionItem.objects.create(requisition=instance, **item)
instance.total_amount = total
instance.save(update_fields=['total_amount'])
# ========== 仓库入库单 ==========
class WarehouseEntryItemSerializer(CustomModelSerializer):
class Meta:
model = WarehouseEntryItem
fields = '__all__'
read_only_fields = ['create_time', 'update_time', 'is_deleted']
class WarehouseEntryListSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
entry_type_display = serializers.CharField(source='get_entry_type_display', read_only=True)
entry_method_display = serializers.CharField(source='get_entry_method_display', read_only=True)
class Meta:
model = WarehouseEntry
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class WarehouseEntryDetailSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
items_ = WarehouseEntryItemSerializer(source='items', many=True, read_only=True)
entry_type_display = serializers.CharField(source='get_entry_type_display', read_only=True)
entry_method_display = serializers.CharField(source='get_entry_method_display', read_only=True)
class Meta:
model = WarehouseEntry
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class WarehouseEntryCreateSerializer(CustomModelSerializer):
items = serializers.ListField(child=serializers.DictField(), write_only=True, required=False, default=[])
class Meta:
model = WarehouseEntry
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['belong_dept', 'number', 'total_amount']
def create(self, validated_data):
items_data = validated_data.pop('items', [])
validated_data['number'] = WarehouseEntry.get_a_number()
instance = super().create(validated_data)
self._save_items(instance, items_data)
return instance
def update(self, instance, validated_data):
items_data = validated_data.pop('items', None)
instance = super().update(instance, validated_data)
if items_data is not None:
instance.items.all().delete()
self._save_items(instance, items_data)
return instance
def _save_items(self, instance, items_data):
total = 0
for item in items_data:
item.pop('id', None)
unit_price = float(item.get('unit_price', 0) or 0)
quantity = float(item.get('quantity', 0) or 0)
item['amount'] = round(unit_price * quantity, 2)
total += item['amount']
WarehouseEntryItem.objects.create(entry=instance, **item)
instance.total_amount = total
instance.save(update_fields=['total_amount'])
# ========== 物料库存 ==========
class WarehouseStockSerializer(CustomModelSerializer):
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
entry_type_display = serializers.CharField(source='get_entry_type_display', read_only=True)
entry_method_display = serializers.CharField(source='get_entry_method_display', read_only=True)
status_display = serializers.SerializerMethodField()
class Meta:
model = WarehouseStock
fields = '__all__'
read_only_fields = ['create_time', 'update_time', 'is_deleted']
def get_status_display(self, obj):
if obj.quantity <= 0:
return '已领完'
return obj.get_status_display()
# ========== 物资领用单 ==========
class MaterialRequisitionItemSerializer(CustomModelSerializer):
stock_name = serializers.CharField(source='stock.name', read_only=True, default='')
class Meta:
model = MaterialRequisitionItem
fields = '__all__'
read_only_fields = ['create_time', 'update_time', 'is_deleted']
class MaterialRequisitionListSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = MaterialRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class MaterialRequisitionDetailSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
items_ = MaterialRequisitionItemSerializer(source='items', many=True, read_only=True)
class Meta:
model = MaterialRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class MaterialRequisitionCreateSerializer(CustomModelSerializer):
items = serializers.ListField(child=serializers.DictField(), write_only=True, required=False, default=[])
class Meta:
model = MaterialRequisition
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['belong_dept', 'number']
def create(self, validated_data):
items_data = validated_data.pop('items', [])
validated_data['number'] = MaterialRequisition.get_a_number()
instance = super().create(validated_data)
self._save_items(instance, items_data)
return instance
def update(self, instance, validated_data):
items_data = validated_data.pop('items', None)
instance = super().update(instance, validated_data)
if items_data is not None:
self._restore_stock(instance)
instance.items.all().delete()
self._save_items(instance, items_data)
return instance
def _save_items(self, instance, items_data):
for item in items_data:
item.pop('id', None)
is_stock_item = item.get('is_stock_item', True)
stock_id = item.pop('stock', None) or item.pop('stock_id', None)
quantity = Decimal(str(item.get('quantity', 0) or 0))
stock_obj = None
if is_stock_item and stock_id:
try:
stock_obj = WarehouseStock.objects.select_for_update().get(id=stock_id)
except WarehouseStock.DoesNotExist:
raise ParseError(f"库存记录不存在: {stock_id}")
if stock_obj.quantity < quantity:
raise ParseError(f"库存不足: {stock_obj.name} 库存{stock_obj.quantity} < 领用{quantity}")
stock_obj.quantity -= quantity
stock_obj.status = 'in_requisition'
stock_obj.save(update_fields=['quantity', 'status'])
MaterialRequisitionItem.objects.create(
requisition=instance,
is_stock_item=is_stock_item,
stock=stock_obj,
req_type=item.get('req_type', ''),
name=item.get('name', ''),
spec=item.get('spec', ''),
unit=item.get('unit', ''),
quantity=quantity,
note=item.get('note', ''),
)
@staticmethod
def _restore_stock(instance):
"""恢复库存(用于编辑或拒绝时)"""
for item in instance.items.filter(is_stock_item=True, stock__isnull=False):
stock = WarehouseStock.objects.select_for_update().get(id=item.stock_id)
stock.quantity += item.quantity
stock.status = 'idle'
stock.save(update_fields=['quantity', 'status'])