272 lines
11 KiB
Python
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'])
|