1
0
forked from erp-dev/erp
Files
erpnew/api_v1/test_pre_sales_order_api.py
2026-02-02 18:29:17 +08:00

410 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
from stock import models as stock_models
from business import allocation_services
from stock import models as stock_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,
)
self.warehouse_strict_out = WareHouse.objects.create(
merchant=self.merchant,
name='预销售严出仓',
mode=WareHouseModeEnum.RESTRICT_IN_OUT,
)
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': '明细备注',
}
],
}
self.strict_payload = {
'customer': self.customer.id,
'warehouse': self.warehouse_strict_out.id,
'kind': business_models.SalesOrderKindEnum.WHOLESALE,
'remarks': '预销售单备注',
'items': [
{
'product_id': self.product.id,
'quantity': '10',
'unit': '',
}
],
}
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_get_detail_includes_allocation_records_with_stock_details(self):
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
order_id = resp.data['id']
item_id = resp.data['items'][0]['id']
stock_record = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
created_by=self.user,
)
stock_detail = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='12.5',
)
create_resp = self.client.post(
f'/api/v1/pre-sales-order-items/{item_id}/allocations/',
{
'stock_ids': [stock_detail.id],
'quantity': '5.5',
'unit': '',
},
format='json',
)
self.assertEqual(create_resp.status_code, status.HTTP_201_CREATED)
detail_resp = self.client.get(f'/api/v1/pre-sales-orders/{order_id}/')
self.assertEqual(detail_resp.status_code, status.HTTP_200_OK)
records = detail_resp.data['items'][0]['allocation_records']
self.assertEqual(records[0]['stock_ids'], [stock_detail.id])
self.assertEqual(records[0]['stock_details'][0]['product_id'], self.product.id)
self.assertEqual(records[0]['stock_details'][0]['product_name'], self.product.name)
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())
def test_convert_to_sales_order_rejects_non_strict_out(self):
order_id = self._create()
resp = self.client.post(f'/api/v1/pre-sales-orders/{order_id}/convert-to-sales/', {}, format='json')
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
self.assertIn('仅支持严进严出仓库模式', resp.data.get('error', ''))
def test_convert_to_sales_order_success(self):
resp = self.client.post('/api/v1/pre-sales-orders/', self.strict_payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
order_id = resp.data['id']
order = business_models.PreSalesOrder.objects.get(id=order_id)
item = order.items.first()
stock_record = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse_strict_out,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
created_by=self.user,
)
stock_detail = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='10',
)
allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail.id],
quantity='10',
unit='',
scanned_by=self.employee,
)
convert_resp = self.client.post(
f'/api/v1/pre-sales-orders/{order_id}/convert-to-sales/',
{},
format='json',
)
self.assertEqual(convert_resp.status_code, status.HTTP_201_CREATED)
self.assertIn('id', convert_resp.data)
self.assertEqual(convert_resp.data['status'], business_models.SalesOrderStatusEnum.PENDING)
sales_order = business_models.SalesOrder.objects.get(id=convert_resp.data['id'])
self.assertEqual(sales_order.from_pre_sales_order_id, order_id)
self.assertEqual(sales_order.items.first().quantity_of_rolls, '10')
def test_detail_filters_cancelled_allocation_records(self):
"""单体查询应过滤掉已撤销的配货记录"""
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
order_id = resp.data['id']
item_id = resp.data['items'][0]['id']
# 创建库存明细
stock_record = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
created_by=self.user,
)
stock_detail1 = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='10',
)
stock_detail2 = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='5',
)
# 创建两条配货记录
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
allocation1 = allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail1.id],
quantity='10',
unit='',
scanned_by=self.employee,
)
allocation2 = allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail2.id],
quantity='5',
unit='',
scanned_by=self.employee,
)
# 撤销第一条记录
allocation_services.cancel_allocation_record_with_unfreeze(record=allocation1)
# 查询单体
detail_resp = self.client.get(f'/api/v1/pre-sales-orders/{order_id}/')
self.assertEqual(detail_resp.status_code, status.HTTP_200_OK)
records = detail_resp.data['items'][0]['allocation_records']
# 应该只包含未撤销的记录
self.assertEqual(len(records), 1)
self.assertEqual(records[0]['id'], allocation2.id)
self.assertEqual(records[0]['status'], business_models.AllocationRecordStatusEnum.ACTIVE)
def test_list_includes_progress_fields(self):
"""列表查询应包含allocated_quantity和progress字段"""
# 创建订单
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
order_id = resp.data['id']
item_id = resp.data['items'][0]['id']
# 创建库存并配货
stock_record = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
created_by=self.user,
)
stock_detail = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='12.5',
)
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail.id],
quantity='5.0',
unit='',
scanned_by=self.employee,
)
# 查询列表
list_resp = self.client.get('/api/v1/pre-sales-orders/')
self.assertEqual(list_resp.status_code, status.HTTP_200_OK)
self.assertIn('results', list_resp.data)
# 查找当前订单
order_data = None
for order in list_resp.data['results']:
if order['id'] == order_id:
order_data = order
break
self.assertIsNotNone(order_data)
# 验证进度字段存在
self.assertIn('allocated_quantity', order_data)
self.assertIn('progress', order_data)
# 需求量12.5已配货5.0
self.assertEqual(order_data['allocated_quantity'], '5.00')
# 进度 = 5.0 / 12.5 = 0.4
self.assertEqual(order_data['progress'], '0.4000')
def test_list_progress_excludes_cancelled_allocations(self):
"""列表查询的进度字段应排除已撤销的配货记录"""
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
order_id = resp.data['id']
item_id = resp.data['items'][0]['id']
# 创建库存
stock_record = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
created_by=self.user,
)
stock_detail1 = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='10',
)
stock_detail2 = stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
product=self.product,
unit=ProductUnitEnum.METER,
stock_change_record=stock_record,
quantity='5',
)
# 创建两条配货,然后撤销一条
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
allocation1 = allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail1.id],
quantity='10',
unit='',
scanned_by=self.employee,
)
allocation2 = allocation_services.create_allocation_record(
item=item,
stock_ids=[stock_detail2.id],
quantity='2.5',
unit='',
scanned_by=self.employee,
)
allocation_services.cancel_allocation_record_with_unfreeze(record=allocation1)
# 查询列表
list_resp = self.client.get('/api/v1/pre-sales-orders/')
self.assertEqual(list_resp.status_code, status.HTTP_200_OK)
order_data = None
for order in list_resp.data['results']:
if order['id'] == order_id:
order_data = order
break
self.assertIsNotNone(order_data)
# 应该只统计未撤销的配货2.5而不是10+2.5
self.assertEqual(order_data['allocated_quantity'], '2.50')
# 进度 = 2.5 / 12.5 = 0.2
self.assertEqual(order_data['progress'], '0.2000')