forked from erp-dev/erp
fix: rebuild stock change record
This commit is contained in:
0
api_v1/__init__.py
Normal file
0
api_v1/__init__.py
Normal file
6
api_v1/apps.py
Normal file
6
api_v1/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiV1Config(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api_v1'
|
||||
86
api_v1/serializers.py
Normal file
86
api_v1/serializers.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from rest_framework import serializers
|
||||
from decimal import Decimal
|
||||
from typing import List, Dict, Any
|
||||
from stock import models as stock_models
|
||||
from basic_info import models as basic_info_models
|
||||
|
||||
|
||||
class ProductStockChangeSerializer(serializers.Serializer):
|
||||
"""产品库存变动序列化器"""
|
||||
product = serializers.IntegerField(help_text="产品ID")
|
||||
quantity = serializers.ListField(
|
||||
child=serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.01')),
|
||||
min_length=1,
|
||||
help_text="数量列表,每个数量对应一条明细记录"
|
||||
)
|
||||
|
||||
def validate_product(self, value: int) -> int:
|
||||
"""验证产品是否存在"""
|
||||
try:
|
||||
basic_info_models.Product.objects.get(id=value)
|
||||
except basic_info_models.Product.DoesNotExist:
|
||||
raise serializers.ValidationError(f"产品ID {value} 不存在")
|
||||
return value
|
||||
|
||||
|
||||
class CreateStockChangeSerializer(serializers.Serializer):
|
||||
"""创建库存变动记录序列化器"""
|
||||
stock_change_record_id = serializers.IntegerField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="库存变动记录ID,如果不提供则创建新记录"
|
||||
)
|
||||
products = ProductStockChangeSerializer(many=True, help_text="产品列表")
|
||||
|
||||
def validate_stock_change_record_id(self, value: int) -> int:
|
||||
"""验证库存变动记录是否存在"""
|
||||
if value is not None:
|
||||
try:
|
||||
stock_models.StockChangeRecord.objects.get(id=value)
|
||||
except stock_models.StockChangeRecord.DoesNotExist:
|
||||
raise serializers.ValidationError(f"库存变动记录ID {value} 不存在")
|
||||
return value
|
||||
|
||||
def validate_products(self, value: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""验证产品列表"""
|
||||
if not value:
|
||||
raise serializers.ValidationError("产品列表不能为空")
|
||||
|
||||
# 检查是否有重复的产品ID
|
||||
product_ids = [item['product'] for item in value]
|
||||
if len(product_ids) != len(set(product_ids)):
|
||||
raise serializers.ValidationError("产品列表中存在重复的产品ID")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class StockChangeRecordResponseSerializer(serializers.ModelSerializer):
|
||||
"""库存变动记录响应序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = stock_models.StockChangeRecord
|
||||
fields = [
|
||||
'id', 'type', 'warehouse', 'source_type', 'source_id',
|
||||
'is_finished', 'finished_at', 'created_at', 'updated_at'
|
||||
]
|
||||
|
||||
|
||||
class StockChangeDetailResponseSerializer(serializers.ModelSerializer):
|
||||
"""库存变动明细响应序列化器"""
|
||||
product_name = serializers.CharField(source='product.name', read_only=True)
|
||||
unit_display = serializers.CharField(source='get_unit_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = stock_models.StockChangeDetail
|
||||
fields = [
|
||||
'id', 'product', 'product_name', 'quantity',
|
||||
'unit', 'unit_display', 'stock_change_record'
|
||||
]
|
||||
|
||||
|
||||
class CreateStockChangeResponseSerializer(serializers.Serializer):
|
||||
"""创建库存变动记录响应序列化器"""
|
||||
stock_change_record = StockChangeRecordResponseSerializer()
|
||||
details = StockChangeDetailResponseSerializer(many=True)
|
||||
message = serializers.CharField()
|
||||
created_details_count = serializers.IntegerField()
|
||||
3
api_v1/tests.py
Normal file
3
api_v1/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
api_v1/urls.py
Normal file
7
api_v1/urls.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
# 库存变动相关API
|
||||
path('stock-change/', views.create_full_stock_change, name='create_full_stock_change'),
|
||||
]
|
||||
136
api_v1/views.py
Normal file
136
api_v1/views.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from django.db import transaction
|
||||
from typing import List, Dict, Any
|
||||
import logging
|
||||
|
||||
from stock import models as stock_models
|
||||
from basic_info import models as basic_info_models
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from . import serializers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@extend_schema(tags=['创建出入库'])
|
||||
@api_view(['POST'])
|
||||
def create_full_stock_change(request):
|
||||
"""
|
||||
创建完整的库存变动记录(包含记录创建参数)
|
||||
|
||||
POST /api/v1/stock-change/
|
||||
|
||||
请求参数:
|
||||
{
|
||||
"type": 1, // 1=入库, 2=出库
|
||||
"warehouse": 1, // 仓库ID
|
||||
"source_type": 1, // 来源类型
|
||||
"source_id": 1, // 可选,来源单据ID
|
||||
"products": [
|
||||
{
|
||||
"product": 1,
|
||||
"quantity": [85, 75, 90]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
# 提取库存变动记录参数
|
||||
record_data = {
|
||||
'type': request.data.get('type'),
|
||||
'warehouse': request.data.get('warehouse'),
|
||||
'source_type': request.data.get('source_type'),
|
||||
'source_id': request.data.get('source_id'),
|
||||
}
|
||||
|
||||
# 验证必要参数
|
||||
if not all([record_data['type'], record_data['warehouse'], record_data['source_type']]):
|
||||
return Response({
|
||||
'error': '缺少必要参数',
|
||||
'message': '请提供 type, warehouse, source_type'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
products_data = request.data.get('products', [])
|
||||
if not products_data:
|
||||
return Response({
|
||||
'error': '产品列表不能为空'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 验证产品数据
|
||||
product_serializer = serializers.CreateStockChangeSerializer(data={
|
||||
'products': products_data
|
||||
})
|
||||
if not product_serializer.is_valid():
|
||||
return Response({
|
||||
'error': '产品数据验证失败',
|
||||
'details': product_serializer.errors
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# 1. 创建库存变动记录
|
||||
try:
|
||||
warehouse = basic_info_models.WareHouse.objects.get(id=record_data['warehouse'])
|
||||
except basic_info_models.WareHouse.DoesNotExist:
|
||||
return Response({
|
||||
'error': f'仓库ID {record_data["warehouse"]} 不存在'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
stock_change_record = stock_models.StockChangeRecord.objects.create(
|
||||
type=record_data['type'],
|
||||
warehouse=warehouse,
|
||||
source_type=record_data['source_type'],
|
||||
source_id=record_data['source_id'],
|
||||
# created_by=request.user
|
||||
)
|
||||
|
||||
logger.info(f"创建新库存变动记录 ID: {stock_change_record.id}")
|
||||
|
||||
# 2. 创建库存变动明细
|
||||
created_details = []
|
||||
created_count = 0
|
||||
|
||||
for product_data in products_data:
|
||||
product_id = product_data['product']
|
||||
quantities = product_data['quantity']
|
||||
|
||||
# 获取产品信息
|
||||
try:
|
||||
product = basic_info_models.Product.objects.get(id=product_id)
|
||||
except basic_info_models.Product.DoesNotExist:
|
||||
raise ValueError(f'产品ID {product_id} 不存在')
|
||||
|
||||
# 为每个数量创建明细记录
|
||||
for quantity in quantities:
|
||||
detail = stock_models.StockChangeDetail.objects.create(
|
||||
stock_change_record=stock_change_record,
|
||||
product=product,
|
||||
quantity=quantity,
|
||||
unit=product.unit
|
||||
)
|
||||
created_details.append(detail)
|
||||
created_count += 1
|
||||
|
||||
# 3. 构建响应
|
||||
response_serializer = serializers.CreateStockChangeResponseSerializer({
|
||||
'stock_change_record': stock_change_record,
|
||||
'details': created_details,
|
||||
'message': f'成功创建库存变动记录及 {created_count} 条明细',
|
||||
'created_details_count': created_count
|
||||
})
|
||||
|
||||
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
except ValueError as e:
|
||||
return Response({
|
||||
'error': str(e)
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建完整库存变动记录失败: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'error': '创建库存变动记录失败',
|
||||
'message': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user