forked from erp-dev/erp
feat: added pre sales order and pre purchase order models and crud
This commit is contained in:
122
api_v1/test_pre_purchase_order_api.py
Normal file
122
api_v1/test_pre_purchase_order_api.py
Normal file
@@ -0,0 +1,122 @@
|
||||
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,
|
||||
Supplier,
|
||||
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 PrePurchaseOrderAPITestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = Merchant.objects.create(name='预采购商户', type=MerchantTypeEnum.FACTORY)
|
||||
self.supplier = Supplier.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='预采购供应商',
|
||||
)
|
||||
self.warehouse = WareHouse.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='预采购仓库',
|
||||
mode=WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
category = ProductCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='预采购品类',
|
||||
product_prefix='PPR',
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='预采购产品',
|
||||
human_id='PPR-001',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='pre_purchase_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 = {
|
||||
'supplier': self.supplier.id,
|
||||
'warehouse': self.warehouse.id,
|
||||
'kind': business_models.PurchaseOrderKindEnum.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-purchase-orders/', payload or self.payload, format='json')
|
||||
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||
return resp.data['id']
|
||||
|
||||
def test_create_pre_purchase_order_success(self):
|
||||
resp = self.client.post('/api/v1/pre-purchase-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['supplier'], self.supplier.id)
|
||||
self.assertEqual(resp.data['warehouse'], self.warehouse.id)
|
||||
self.assertEqual(len(resp.data['items']), 1)
|
||||
|
||||
def test_list_pre_purchase_orders(self):
|
||||
self._create()
|
||||
resp = self.client.get('/api/v1/pre-purchase-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-purchase-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_purchase_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-purchase-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_purchase_order(self):
|
||||
order_id = self._create()
|
||||
resp = self.client.delete(f'/api/v1/pre-purchase-orders/{order_id}/')
|
||||
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertFalse(business_models.PrePurchaseOrder.objects.filter(id=order_id).exists())
|
||||
@@ -1542,6 +1542,13 @@ class PrintingJobWorkStateAPITestCase(TestCase):
|
||||
|
||||
job_id = resp.data['id']
|
||||
detail = self.client.get(f'/api/v1/printing-jobs/{job_id}/')
|
||||
# 客户可见性过滤:customer 未绑定 created_by/visible_employees 时,任务对该员工不可见 => 404
|
||||
self.assertEqual(detail.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 将客户显式授权给当前 employee 后应可见 => 200
|
||||
self.customer.created_by = self.employee
|
||||
self.customer.save(update_fields=['created_by'])
|
||||
detail = self.client.get(f'/api/v1/printing-jobs/{job_id}/')
|
||||
self.assertEqual(detail.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(detail.data['work_state_display'], '待送货')
|
||||
|
||||
@@ -1558,10 +1565,29 @@ class PrintingJobWorkStateAPITestCase(TestCase):
|
||||
{'work_state': printing_models.PrintingJobWorkStateEnum.FINISHED},
|
||||
format='json',
|
||||
)
|
||||
# merchant 隔离:job.merchant 为空时,不属于当前员工商户 => 404
|
||||
self.assertEqual(patch_resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 创建一个显式属于当前商户的 job,并让 customer 对 employee 可见后应可更新/可查询
|
||||
self.customer.created_by = self.employee
|
||||
self.customer.save(update_fields=['created_by'])
|
||||
visible_job = printing_models.PrintingJob.objects.create(
|
||||
merchant=self.merchant,
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=5,
|
||||
unit='米',
|
||||
)
|
||||
visible_url = f'/api/v1/printing-jobs/{visible_job.id}/'
|
||||
patch_resp = self.client.patch(
|
||||
visible_url,
|
||||
{'work_state': printing_models.PrintingJobWorkStateEnum.FINISHED},
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(patch_resp.status_code, status.HTTP_200_OK)
|
||||
job.refresh_from_db()
|
||||
self.assertEqual(job.work_state, printing_models.PrintingJobWorkStateEnum.FINISHED)
|
||||
detail = self.client.get(url)
|
||||
visible_job.refresh_from_db()
|
||||
self.assertEqual(visible_job.work_state, printing_models.PrintingJobWorkStateEnum.FINISHED)
|
||||
detail = self.client.get(visible_url)
|
||||
self.assertEqual(detail.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(detail.data['work_state_display'], '已完结')
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ 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.business.pre_purchase import views as pre_purchase_views
|
||||
from .views.stock_change_views.snapshot import StockSnapshotListView
|
||||
from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet
|
||||
from .views.upload import UploadFileViewSet
|
||||
@@ -90,6 +91,8 @@ urlpatterns = [
|
||||
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('pre-purchase-orders/', pre_purchase_views.PrePurchaseOrderView.as_view(), name='pre_purchase_orders'),
|
||||
path('pre-purchase-orders/<int:pk>/', pre_purchase_views.PrePurchaseOrderDetailView.as_view(), name='pre_purchase_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'),
|
||||
|
||||
0
api_v1/views/business/pre_purchase/__init__.py
Normal file
0
api_v1/views/business/pre_purchase/__init__.py
Normal file
179
api_v1/views/business/pre_purchase/views.py
Normal file
179
api_v1/views/business/pre_purchase/views.py
Normal file
@@ -0,0 +1,179 @@
|
||||
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 PrePurchaseOrderItemSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = business_models.PrePurchaseOrderItem
|
||||
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 PrePurchaseOrderSerializer(serializers.ModelSerializer):
|
||||
human_id = serializers.CharField(read_only=True)
|
||||
merchant_name = serializers.CharField(source='merchant.name', read_only=True)
|
||||
supplier_name = serializers.CharField(source='supplier.name', read_only=True)
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
created_by_username = serializers.CharField(source='created_by.username', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
items = PrePurchaseOrderItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = business_models.PrePurchaseOrder
|
||||
fields = [
|
||||
'id',
|
||||
'human_id',
|
||||
'merchant',
|
||||
'merchant_name',
|
||||
'supplier',
|
||||
'supplier_name',
|
||||
'warehouse',
|
||||
'warehouse_name',
|
||||
'created_by',
|
||||
'created_by_username',
|
||||
'operator',
|
||||
'operator_name',
|
||||
'kind',
|
||||
'remarks',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'items',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'human_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'merchant_name',
|
||||
'supplier_name',
|
||||
'warehouse_name',
|
||||
'created_by_username',
|
||||
'operator_name',
|
||||
'items',
|
||||
]
|
||||
|
||||
|
||||
class PrePurchaseOrderPagination(pagination.LimitOffsetPagination):
|
||||
default_limit = 20
|
||||
max_limit = 100
|
||||
|
||||
|
||||
class PrePurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
"""预采购单查询与创建"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
pagination_class = PrePurchaseOrderPagination
|
||||
|
||||
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_purchase_orders(merchant=merchant)
|
||||
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(queryset.order_by('-created_at'), request, view=self)
|
||||
serializer = PrePurchaseOrderSerializer(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_purchase_order = pre_order_services.create_pre_purchase_order(
|
||||
merchant=merchant,
|
||||
supplier_id=data.get('supplier') or data.get('supplier_id'),
|
||||
warehouse_id=data.get('warehouse') or data.get('warehouse_id'),
|
||||
operator=employee,
|
||||
created_by=request.user,
|
||||
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(PrePurchaseOrderSerializer(pre_purchase_order).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class PrePurchaseOrderDetailView(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_purchase_order(
|
||||
merchant=merchant,
|
||||
pre_purchase_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(PrePurchaseOrderSerializer(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_purchase_order(
|
||||
pre_purchase_order=order,
|
||||
supplier_id=data.get('supplier') or data.get('supplier_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(PrePurchaseOrderSerializer(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_purchase_order(pre_purchase_order=order)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -33,7 +33,8 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
||||
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)
|
||||
created_by_username = serializers.CharField(source='created_by.username', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
items = PreSalesOrderItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -48,7 +49,9 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
||||
'warehouse',
|
||||
'warehouse_name',
|
||||
'created_by',
|
||||
'created_by_name',
|
||||
'created_by_username',
|
||||
'operator',
|
||||
'operator_name',
|
||||
'kind',
|
||||
'remarks',
|
||||
'created_at',
|
||||
@@ -63,7 +66,8 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
||||
'merchant_name',
|
||||
'customer_name',
|
||||
'warehouse_name',
|
||||
'created_by_name',
|
||||
'created_by_username',
|
||||
'operator_name',
|
||||
'items',
|
||||
]
|
||||
|
||||
@@ -104,7 +108,8 @@ class PreSalesOrderView(StockChangeViewMixin, views.APIView):
|
||||
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,
|
||||
operator=employee,
|
||||
created_by=request.user,
|
||||
kind=data.get('kind'),
|
||||
items=data.get('items'),
|
||||
remarks=data.get('remarks', ''),
|
||||
|
||||
Reference in New Issue
Block a user