1
0
forked from erp-dev/erp

feat: added pre sales order in business module, and explode tests of business module

This commit is contained in:
2026-01-31 22:17:02 +08:00
parent 1af18c9388
commit a093174913
19 changed files with 2386 additions and 1404 deletions

View File

@@ -0,0 +1,123 @@
import copy
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from rest_framework import status
from rest_framework.test import APIClient
from basic_info.models import (
Merchant,
MerchantTypeEnum,
Customer,
WareHouse,
WareHouseModeEnum,
ProductCategory,
Product,
ProductUnitEnum,
Employee,
EmployeeStatusEnum,
)
from business import models as business_models
@override_settings(
CELERY_TASK_ALWAYS_EAGER=True,
CELERY_TASK_EAGER_PROPAGATES=True,
)
class PreSalesOrderAPITestCase(TestCase):
def setUp(self):
self.merchant = Merchant.objects.create(name='预销售商户', type=MerchantTypeEnum.FACTORY)
self.customer = Customer.objects.create(
merchant=self.merchant,
name='预销售客户',
created_by=None,
)
self.warehouse = WareHouse.objects.create(
merchant=self.merchant,
name='预销售仓库',
mode=WareHouseModeEnum.UNRESTRICTED,
)
category = ProductCategory.objects.create(
merchant=self.merchant,
name='预销售品类',
product_prefix='PRE',
)
self.product = Product.objects.create(
merchant=self.merchant,
category=category,
name='预销售产品',
human_id='PRE-001',
unit=ProductUnitEnum.METER,
)
User = get_user_model()
self.user = User.objects.create_user(username='pre_sales_user', password='pass123')
self.employee = Employee.objects.create(
merchant=self.merchant,
sys_user=self.user,
name='预销售员',
status=EmployeeStatusEnum.ACTIVE,
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
self.payload = {
'customer': self.customer.id,
'warehouse': self.warehouse.id,
'kind': business_models.SalesOrderKindEnum.WHOLESALE,
'remarks': '预销售单备注',
'items': [
{
'product_id': self.product.id,
'quantity': '12.5',
'unit': '',
'order_quantity': 7,
'remarks': '明细备注',
}
],
}
def _create(self, payload=None) -> int:
resp = self.client.post('/api/v1/pre-sales-orders/', payload or self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
return resp.data['id']
def test_create_pre_sales_order_success(self):
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
self.assertIn('id', resp.data)
self.assertIn('human_id', resp.data)
self.assertEqual(resp.data['customer'], self.customer.id)
self.assertEqual(resp.data['warehouse'], self.warehouse.id)
self.assertEqual(len(resp.data['items']), 1)
def test_list_pre_sales_orders(self):
self._create()
resp = self.client.get('/api/v1/pre-sales-orders/')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertIn('results', resp.data)
self.assertGreaterEqual(len(resp.data['results']), 1)
def test_get_detail(self):
order_id = self._create()
resp = self.client.get(f'/api/v1/pre-sales-orders/{order_id}/')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data['id'], order_id)
self.assertIn('items', resp.data)
def test_update_pre_sales_order(self):
order_id = self._create()
payload = copy.deepcopy(self.payload)
payload['remarks'] = '更新备注'
payload['items'][0]['quantity'] = '20'
resp = self.client.put(f'/api/v1/pre-sales-orders/{order_id}/', payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data['remarks'], '更新备注')
self.assertEqual(str(resp.data['items'][0]['quantity']), '20.00')
def test_delete_pre_sales_order(self):
order_id = self._create()
resp = self.client.delete(f'/api/v1/pre-sales-orders/{order_id}/')
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(business_models.PreSalesOrder.objects.filter(id=order_id).exists())

View File

@@ -19,6 +19,7 @@ from .views.business.balance import views as balance_views
from .views.business.purchase_return import views as purchase_return_views
from .views.business.sales_return import views as sales_return_views
from .views.business.statements import views as statement_views
from .views.business.pre_sales import views as pre_sales_views
from .views.stock_change_views.snapshot import StockSnapshotListView
from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet
from .views.upload import UploadFileViewSet
@@ -87,6 +88,8 @@ urlpatterns = [
path('sales-return-orders/', sales_return_views.SalesReturnOrderView.as_view(), name='sales_return_orders'),
path('sales-return-orders/<int:pk>/', sales_return_views.SalesReturnOrderDetailView.as_view(), name='sales_return_order_detail'),
path('sales-return-orders/<int:pk>/review/', sales_return_views.SalesReturnOrderReviewView.as_view(), name='sales_return_order_review'),
path('pre-sales-orders/', pre_sales_views.PreSalesOrderView.as_view(), name='pre_sales_orders'),
path('pre-sales-orders/<int:pk>/', pre_sales_views.PreSalesOrderDetailView.as_view(), name='pre_sales_order_detail'),
path('payment-orders/', payment_views.PaymentOrderView.as_view(), name='payment_orders'),
path('payment-orders/<int:pk>/review/', payment_views.PaymentOrderReviewView.as_view(), name='payment_order_review'),
path('receipt-orders/', receipt_views.ReceiptOrderView.as_view(), name='receipt_orders'),

View File

@@ -0,0 +1,174 @@
from rest_framework import status, views, serializers, pagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
from business import models as business_models
from business import pre_order_services
class PreSalesOrderItemSerializer(serializers.ModelSerializer):
class Meta:
model = business_models.PreSalesOrderItem
fields = [
'id',
'product_id',
'product_name',
'color',
'quantity',
'unit',
'spec',
'quantity_of_rolls',
'num_of_rolls',
'order_quantity',
'remarks',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at']
class PreSalesOrderSerializer(serializers.ModelSerializer):
human_id = serializers.CharField(read_only=True)
merchant_name = serializers.CharField(source='merchant.name', read_only=True)
customer_name = serializers.CharField(source='customer.name', read_only=True)
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
created_by_name = serializers.CharField(source='created_by.name', read_only=True)
items = PreSalesOrderItemSerializer(many=True, read_only=True)
class Meta:
model = business_models.PreSalesOrder
fields = [
'id',
'human_id',
'merchant',
'merchant_name',
'customer',
'customer_name',
'warehouse',
'warehouse_name',
'created_by',
'created_by_name',
'kind',
'remarks',
'created_at',
'updated_at',
'items',
]
read_only_fields = [
'id',
'human_id',
'created_at',
'updated_at',
'merchant_name',
'customer_name',
'warehouse_name',
'created_by_name',
'items',
]
class PreSalesOrderPagination(pagination.LimitOffsetPagination):
default_limit = 20
max_limit = 100
class PreSalesOrderView(StockChangeViewMixin, views.APIView):
"""预销售单查询与创建"""
permission_classes = [IsAuthenticated]
pagination_class = PreSalesOrderPagination
def get(self, request):
if not self.check_employee_permission(request):
return self.permission_error_response('无权限访问')
merchant = request.user.employee.merchant
queryset = pre_order_services.list_pre_sales_orders(merchant=merchant)
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset.order_by('-created_at'), request, view=self)
serializer = PreSalesOrderSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
def post(self, request):
if not self.check_employee_permission(request):
return self.permission_error_response('无权限访问')
merchant = request.user.employee.merchant
employee = request.user.employee
data = request.data or {}
try:
pre_sales_order = pre_order_services.create_pre_sales_order(
merchant=merchant,
customer_id=data.get('customer') or data.get('customer_id'),
warehouse_id=data.get('warehouse') or data.get('warehouse_id'),
created_by=employee,
kind=data.get('kind'),
items=data.get('items'),
remarks=data.get('remarks', ''),
)
except ValueError as exc:
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(PreSalesOrderSerializer(pre_sales_order).data, status=status.HTTP_201_CREATED)
class PreSalesOrderDetailView(StockChangeViewMixin, views.APIView):
permission_classes = [IsAuthenticated]
def _get_order(self, request, pk: int):
if not self.check_employee_permission(request):
return None, self.permission_error_response('无权限访问')
merchant = request.user.employee.merchant
try:
order = pre_order_services.get_pre_sales_order(
merchant=merchant,
pre_sales_order_id=pk,
)
return order, None
except ValueError:
return None, self.not_found_response('预销售单不存在')
def get(self, request, pk: int):
order, error_response = self._get_order(request, pk)
if error_response:
return error_response
return Response(PreSalesOrderSerializer(order).data, status=status.HTTP_200_OK)
def put(self, request, pk: int):
return self._update(request, pk)
def patch(self, request, pk: int):
return self._update(request, pk)
def _update(self, request, pk: int):
order, error_response = self._get_order(request, pk)
if error_response:
return error_response
data = request.data or {}
try:
updated = pre_order_services.update_pre_sales_order(
pre_sales_order=order,
customer_id=data.get('customer') or data.get('customer_id'),
warehouse_id=data.get('warehouse') or data.get('warehouse_id'),
kind=data.get('kind'),
items=data.get('items'),
remarks=data.get('remarks'),
)
except ValueError as exc:
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
return Response(PreSalesOrderSerializer(updated).data, status=status.HTTP_200_OK)
def delete(self, request, pk: int):
order, error_response = self._get_order(request, pk)
if error_response:
return error_response
pre_order_services.delete_pre_sales_order(pre_sales_order=order)
return Response(status=status.HTTP_204_NO_CONTENT)