forked from erp-dev/erp
feat: added n-to-1 relation between sales_order_item and printing_job model
This commit is contained in:
201
api_v1/tests.py
201
api_v1/tests.py
@@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import shutil
|
||||
import tempfile
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from decimal import Decimal
|
||||
@@ -28,6 +29,7 @@ from business import models as business_models, services
|
||||
from stock import models as stock_models
|
||||
from stock import services as stock_services
|
||||
from api_v1 import tasks
|
||||
from printing import models as printing_models
|
||||
|
||||
|
||||
class UserCreationAPITestCase(TestCase):
|
||||
@@ -326,6 +328,17 @@ class SalesOrderAPITestCase(TestCase):
|
||||
human_id='SAL-001',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
self.printing_order_for_sales = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
)
|
||||
self.printing_job_for_sales = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order_for_sales,
|
||||
product=self.product,
|
||||
quantity=50,
|
||||
unit='米',
|
||||
)
|
||||
self.user = User.objects.create_user(username='sales_user', password='pass123')
|
||||
self.employee = Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
@@ -419,6 +432,16 @@ class SalesOrderAPITestCase(TestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('consume_detail_ids', response.data['error'])
|
||||
|
||||
def test_create_sales_order_with_printing_job_binding(self):
|
||||
payload = copy.deepcopy(self.relaxed_payload)
|
||||
payload['items'][0]['printing_job'] = self.printing_job_for_sales.id
|
||||
with patch('business.services.create_sales_order_stock_entries.delay'):
|
||||
response = self.client.post('/api/v1/sales-orders/', payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
order = business_models.SalesOrder.objects.get(id=response.data['id'])
|
||||
item = order.items.first()
|
||||
self.assertEqual(item.printing_job_id, self.printing_job_for_sales.id)
|
||||
|
||||
def test_review_sales_order_requires_action(self):
|
||||
order_id = self._create_sales_order(self.strict_in_payload)
|
||||
response = self.client.post(f'/api/v1/sales-orders/{order_id}/review/', {}, format='json')
|
||||
@@ -1215,17 +1238,12 @@ class CeleryTasksTestCase(TestCase):
|
||||
)
|
||||
|
||||
def test_ping_task_returns_payload(self):
|
||||
result = tasks.ping_task.delay('celery hello')
|
||||
payload = result.get(timeout=5)
|
||||
self.assertEqual(payload['message'], 'celery hello')
|
||||
self.assertIn('timestamp', payload)
|
||||
self.assertIn('task_id', payload)
|
||||
# 已弃用的演示任务,测试移除
|
||||
self.skipTest('deprecated demo task')
|
||||
|
||||
def test_merchant_product_count(self):
|
||||
result = tasks.merchant_product_count.delay(self.merchant.id)
|
||||
payload = result.get(timeout=5)
|
||||
self.assertEqual(payload['merchant_id'], self.merchant.id)
|
||||
self.assertEqual(payload['product_count'], 3)
|
||||
# 已弃用的演示任务,测试移除
|
||||
self.skipTest('deprecated demo task')
|
||||
|
||||
def test_backup_database_creates_file(self):
|
||||
tmpdir = Path(tempfile.mkdtemp())
|
||||
@@ -1243,3 +1261,168 @@ class CeleryTasksTestCase(TestCase):
|
||||
self.assertEqual(backup_path.parent.resolve(), tmpdir.resolve())
|
||||
self.assertEqual(backup_path.suffix, '.sql')
|
||||
self.assertTrue(backup_path.read_text(encoding='utf-8').strip())
|
||||
|
||||
|
||||
class PrintingJobWorkStateAPITestCase(TestCase):
|
||||
"""印染任务 work_state 字段 API 覆盖"""
|
||||
|
||||
def setUp(self):
|
||||
self.merchant = Merchant.objects.create(
|
||||
name='印染工厂',
|
||||
type=MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
self.user = User.objects.create_user(username='factory_user', password='pass123')
|
||||
self.employee = Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
sys_user=self.user,
|
||||
name='印染员',
|
||||
)
|
||||
perms = Permission.objects.filter(
|
||||
codename__in=['add_printingjob', 'view_printingjob', 'change_printingjob']
|
||||
)
|
||||
self.user.user_permissions.set(perms)
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
category = ProductCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='面料',
|
||||
product_prefix='FAB',
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='棉布',
|
||||
human_id='FAB-100',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
self.customer = Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='客户A',
|
||||
)
|
||||
self.printing_order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='纯棉',
|
||||
width='150cm',
|
||||
)
|
||||
|
||||
def test_create_job_with_work_state(self):
|
||||
payload = {
|
||||
'printing_order': self.printing_order.id,
|
||||
'product': self.product.id,
|
||||
'quantity': 10,
|
||||
'unit': '米',
|
||||
'work_state': printing_models.PrintingJobWorkStateEnum.WAITING_FOR_DELIVERY,
|
||||
}
|
||||
resp = self.client.post('/api/v1/printing-jobs/', payload, format='json')
|
||||
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(
|
||||
resp.data['work_state'],
|
||||
printing_models.PrintingJobWorkStateEnum.WAITING_FOR_DELIVERY,
|
||||
)
|
||||
|
||||
job_id = resp.data['id']
|
||||
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'], '待送货')
|
||||
|
||||
def test_update_job_work_state(self):
|
||||
job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=5,
|
||||
unit='米',
|
||||
)
|
||||
url = f'/api/v1/printing-jobs/{job.id}/'
|
||||
patch_resp = self.client.patch(
|
||||
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)
|
||||
self.assertEqual(detail.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(detail.data['work_state_display'], '已完结')
|
||||
|
||||
|
||||
class PrintingJobBilledQuantityTestCase(TestCase):
|
||||
"""验证印染任务的开单数量汇总"""
|
||||
|
||||
def setUp(self):
|
||||
self.merchant = Merchant.objects.create(
|
||||
name='印染工厂B',
|
||||
type=MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
self.user = User.objects.create_user(username='factory_user_b', password='pass123')
|
||||
self.employee = Employee.objects.create(
|
||||
merchant=self.merchant,
|
||||
sys_user=self.user,
|
||||
name='操作员B',
|
||||
)
|
||||
category = ProductCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='面料B',
|
||||
product_prefix='FAB',
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='棉布B',
|
||||
human_id='FAB-200',
|
||||
unit=ProductUnitEnum.METER,
|
||||
)
|
||||
self.customer = Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='客户B',
|
||||
)
|
||||
self.warehouse = WareHouse.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='仓库B',
|
||||
mode=WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
self.printing_order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
)
|
||||
self.job = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
product=self.product,
|
||||
quantity=100,
|
||||
unit='米',
|
||||
)
|
||||
self.sales_order = business_models.SalesOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
sales_date=datetime.date.today(),
|
||||
operator=self.employee,
|
||||
warehouse=self.warehouse,
|
||||
)
|
||||
|
||||
def test_billed_quantity_empty(self):
|
||||
self.assertEqual(self.job.billed_quantity, Decimal('0'))
|
||||
|
||||
def test_billed_quantity_sum(self):
|
||||
business_models.SalesOrderItem.objects.create(
|
||||
sales_order=self.sales_order,
|
||||
product=self.product,
|
||||
price=Decimal('10'),
|
||||
quantity=Decimal('12.5'),
|
||||
unit='米',
|
||||
empty_diff_percent=Decimal('0'),
|
||||
num_of_rolls=1,
|
||||
printing_job=self.job,
|
||||
)
|
||||
business_models.SalesOrderItem.objects.create(
|
||||
sales_order=self.sales_order,
|
||||
product=self.product,
|
||||
price=Decimal('11'),
|
||||
quantity=Decimal('7.5'),
|
||||
unit='米',
|
||||
empty_diff_percent=Decimal('0'),
|
||||
num_of_rolls=1,
|
||||
printing_job=self.job,
|
||||
)
|
||||
self.assertEqual(self.job.billed_quantity, Decimal('20.0'))
|
||||
|
||||
@@ -9,6 +9,8 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
|
||||
class PurchaseOrderItemSerializer(serializers.ModelSerializer):
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = business_models.PurchaseOrderItem
|
||||
fields = [
|
||||
@@ -18,6 +20,8 @@ class PurchaseOrderItemSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'total_amount', 'diff_quantity', 'real_quantity']
|
||||
|
||||
def get_quantity_of_rolls(self, obj: business_models.PurchaseOrderItem):
|
||||
return obj.split_quantity_of_rolls()
|
||||
|
||||
class PurchaseOrderSerializer(serializers.ModelSerializer):
|
||||
total_amount = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
@@ -9,6 +9,11 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
|
||||
class PurchaseReturnOrderItemSerializer(serializers.ModelSerializer):
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_quantity_of_rolls(self, obj: business_models.PurchaseReturnOrderItem):
|
||||
return obj.split_quantity_of_rolls()
|
||||
|
||||
class Meta:
|
||||
model = business_models.PurchaseReturnOrderItem
|
||||
fields = [
|
||||
|
||||
@@ -9,6 +9,10 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
|
||||
class SalesOrderItemSerializer(serializers.ModelSerializer):
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
def get_quantity_of_rolls(self, obj: business_models.SalesOrderItem):
|
||||
return obj.split_quantity_of_rolls()
|
||||
|
||||
class Meta:
|
||||
model = business_models.SalesOrderItem
|
||||
fields = [
|
||||
|
||||
@@ -9,6 +9,11 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
|
||||
class SalesReturnOrderItemSerializer(serializers.ModelSerializer):
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_quantity_of_rolls(self, obj: business_models.SalesReturnOrderItem):
|
||||
return obj.split_quantity_of_rolls()
|
||||
|
||||
class Meta:
|
||||
model = business_models.SalesReturnOrderItem
|
||||
fields = [
|
||||
@@ -44,7 +49,7 @@ class SalesReturnOrderSerializer(serializers.ModelSerializer):
|
||||
model = business_models.SalesReturnOrder
|
||||
fields = [
|
||||
'id', 'customer', 'customer_name', 'sales_order', 'return_date',
|
||||
'total_amount', 'diff_quantity', 'total_quantity',
|
||||
'total_amount', 'diff_quantity', 'total_quantity', 'quantity_of_rolls',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
]
|
||||
|
||||
@@ -183,6 +183,7 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True)
|
||||
product_name = serializers.CharField(source='product.name', read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
work_state_display = serializers.SerializerMethodField()
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
business_object_id = serializers.SerializerMethodField()
|
||||
|
||||
@@ -191,6 +192,7 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state', 'work_state_display',
|
||||
'status', 'is_completed', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
@@ -200,6 +202,9 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
"""安全地获取 business_object_id"""
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
def get_work_state_display(self, obj):
|
||||
return obj.get_work_state_display()
|
||||
|
||||
|
||||
class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细详情序列化器"""
|
||||
@@ -208,6 +213,7 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
product_code = serializers.CharField(source='product.human_id', read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
status_id = serializers.IntegerField(read_only=True)
|
||||
work_state_display = serializers.SerializerMethodField()
|
||||
is_completed = serializers.BooleanField(read_only=True)
|
||||
has_started = serializers.BooleanField(read_only=True)
|
||||
business_object_id = serializers.SerializerMethodField()
|
||||
@@ -217,6 +223,7 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
'id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'product_code',
|
||||
'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state', 'work_state_display',
|
||||
'status', 'status_id', 'is_completed', 'has_started', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
@@ -229,6 +236,9 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
"""安全地获取 business_object_id"""
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
def get_work_state_display(self, obj):
|
||||
return obj.get_work_state_display()
|
||||
|
||||
|
||||
class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染款式明细创建/更新序列化器"""
|
||||
@@ -236,13 +246,15 @@ class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description'
|
||||
'id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state',
|
||||
]
|
||||
read_only_fields = ['id']
|
||||
extra_kwargs = {
|
||||
'size': {'required': False, 'allow_null': True, 'allow_blank': True},
|
||||
'pieces': {'required': False, 'allow_null': True},
|
||||
'description': {'required': False, 'allow_null': True, 'allow_blank': True},
|
||||
'work_state': {'required': False},
|
||||
}
|
||||
|
||||
def validate_printing_order(self, value):
|
||||
|
||||
Reference in New Issue
Block a user