1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/printing/test_api.py

1180 lines
45 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.
"""
PrintingOrder API 测试
"""
from datetime import datetime, timezone as dt_timezone
from django.test import TestCase
from django.db import connection
from django.conf import settings
from django.utils import timezone
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from basic_info import models as basic_models
from printing import models as printing_models
from stateflow import models as stateflow_models
User = get_user_model()
class PrintingOrderAPITestCase(TestCase):
"""测试 PrintingOrder API"""
def setUp(self):
# 清除缓存,避免 cache_page 装饰器导致的测试干扰
from django.core.cache import cache
cache.clear()
self.client = APIClient()
# 创建商户
self.merchant = basic_models.Merchant.objects.create(
name='测试印花厂',
type=basic_models.MerchantTypeEnum.FACTORY
)
# 创建用户
self.user = User.objects.create_user(
username='testuser',
password='testpass123',
email='test@example.com'
)
# 创建员工并关联商户
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建客户(设置 created_by 以便客户可见性过滤)
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='测试客户',
mobile='13900139000',
area='测试地区',
created_by=self.employee
)
# 创建流程
self.state1 = stateflow_models.State.objects.create(name='待印染')
self.state2 = stateflow_models.State.objects.create(name='印染中')
self.state3 = stateflow_models.State.objects.create(name='已完成')
self.process = stateflow_models.Process.objects.create(name='印染流程')
self.process.replace_nodes([self.state1, self.state2, self.state3])
# 设置默认流程
settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
# 认证用户
self.client.force_authenticate(user=self.user)
# 给用户添加基础权限
view_perm = Permission.objects.get(codename='view_printingorder')
add_perm = Permission.objects.get(codename='add_printingorder')
change_perm = Permission.objects.get(codename='change_printingorder')
# 添加 view_all 权限以便测试 API 功能(权限过滤逻辑在专门的测试类中验证)
view_all_perm = Permission.objects.get(codename='view_all_printingorders')
self.user.user_permissions.add(view_perm, add_perm, change_perm, view_all_perm)
def test_create_printing_order(self):
"""测试创建印染订单"""
data = {
'customer': self.customer.id,
'fabric': '纯棉布料',
'width': '150cm',
'is_urgent': True,
'area': '广州',
'address': '白云区xxx',
'fabric_source': '客户提供',
'is_fabric_received': False,
'craft': '活性印花',
'description': '测试订单描述',
'outgoing_date': '2025-11-20',
'printing_warn': '注意颜色',
'rolling_warn': '注意温度',
'production_warn': '质量检查'
}
response = self.client.post('/api/v1/printing-orders/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# 检查响应数据
self.assertIn('fabric', response.data)
self.assertEqual(response.data['fabric'], '纯棉布料')
self.assertEqual(response.data['is_urgent'], True)
# 验证订单已创建 - 通过fabric查找因为响应可能不包含id
order = printing_models.PrintingOrder.objects.filter(fabric='纯棉布料').first()
self.assertIsNotNone(order)
self.assertEqual(order.customer.id, self.customer.id)
self.assertEqual(order.fabric, '纯棉布料')
# 验证 merchant 自动绑定
self.assertEqual(order.merchant.id, self.merchant.id)
# 验证 outgoing_date 接受日期字符串并归一为 00:00:00按当前时区
self.assertIsNotNone(order.outgoing_date)
local_dt = timezone.localtime(order.outgoing_date)
self.assertEqual(str(local_dt.date()), '2025-11-20')
self.assertEqual(local_dt.hour, 0)
self.assertEqual(local_dt.minute, 0)
def test_filter_outgoing_date_to_includes_whole_day(self):
"""
outgoing_date_to 传 YYYY-MM-DD 时应包含整天:
- outgoing_date < 次日 00:00:00
"""
tz = timezone.get_current_timezone()
d0 = timezone.make_aware(datetime(2025, 11, 20, 23, 0, 0), tz)
d1 = timezone.make_aware(datetime(2025, 11, 21, 0, 0, 0), tz)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料A',
width='150cm',
outgoing_date=d0,
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料B',
width='150cm',
outgoing_date=d1,
)
resp = self.client.get('/api/v1/printing-orders/?limit=50&offset=0&outgoing_date_to=2025-11-20')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
fabrics = [row['fabric'] for row in resp.data['results']]
self.assertIn('布料A', fabrics)
self.assertNotIn('布料B', fabrics)
def test_list_printing_orders(self):
"""测试获取订单列表"""
# 创建测试订单
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料1',
width='150cm'
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料2',
width='160cm'
)
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 分页响应格式
self.assertEqual(response.data['count'], 2)
def test_list_pagination_keeps_rows_with_dangling_customer_reference(self):
"""历史脏数据 customer_id 悬空时,列表分页不应被 INNER JOIN 推空。"""
base = timezone.now()
dangling = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='悬空客户订单',
width='150cm',
)
middle = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='中间订单',
width='150cm',
)
oldest = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='最旧订单',
width='150cm',
)
printing_models.PrintingOrder.objects.filter(id=dangling.id).update(
created_at=base + timezone.timedelta(minutes=2),
)
printing_models.PrintingOrder.objects.filter(id=middle.id).update(
created_at=base + timezone.timedelta(minutes=1),
)
printing_models.PrintingOrder.objects.filter(id=oldest.id).update(created_at=base)
missing_customer_id = 99999999
with connection.cursor() as cursor:
cursor.execute(
'UPDATE printing_printingorder SET customer_id = %s WHERE id = %s',
[missing_customer_id, dangling.id],
)
try:
response = self.client.get(
'/api/v1/printing-orders/?limit=1&offset=2&ordering=-created_at',
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 3)
self.assertEqual(len(response.data['results']), 1)
self.assertEqual(response.data['results'][0]['id'], oldest.id)
finally:
with connection.cursor() as cursor:
cursor.execute(
'UPDATE printing_printingorder SET customer_id = %s WHERE id = %s',
[self.customer.id, dangling.id],
)
def test_list_printing_orders_default_excludes_invalid(self):
"""默认不返回作废订单;显式传 is_invalid=true 时可查询作废订单"""
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='正常订单',
width='150cm',
is_invalid=False,
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='作废订单',
width='150cm',
is_invalid=True,
)
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['results'][0]['is_invalid'], False)
response = self.client.get('/api/v1/printing-orders/?is_invalid=true')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['results'][0]['is_invalid'], True)
def test_filter_by_external_order_id_and_serializer_fields(self):
"""支持按 external_order_id 过滤,且列表/详情返回外部字段"""
order1 = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料A',
width='150cm',
external_order_id='KD20410611',
external_customer_id='KH00991',
external_customer_name='李泽柔',
external_employee_name='丽容',
external_raw={
'first_record': {
'BianHaoKD': '7.07',
'KdRiQi': '2026-07-06T21:48:58Z',
}
},
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料B',
width='160cm',
external_order_id='KD20419999',
)
response = self.client.get('/api/v1/printing-orders/?external_order_id=KD20410611')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
item = response.data['results'][0]
self.assertEqual(item['external_order_id'], 'KD20410611')
self.assertEqual(item['external_customer_id'], 'KH00991')
self.assertEqual(item['external_customer_name'], '李泽柔')
self.assertEqual(item['external_employee_name'], '丽容')
self.assertEqual(item['bianhao_kd'], '7.07')
self.assertEqual(item['kd_riqi'], '2026-07-06T21:48:58Z')
detail = self.client.get(f'/api/v1/printing-orders/{order1.id}/')
self.assertEqual(detail.status_code, status.HTTP_200_OK)
self.assertEqual(detail.data['external_order_id'], 'KD20410611')
self.assertEqual(detail.data['external_customer_id'], 'KH00991')
self.assertEqual(detail.data['bianhao_kd'], '7.07')
self.assertEqual(detail.data['kd_riqi'], '2026-07-06T21:48:58Z')
def test_bianhao_kd_returns_null_without_external_order_id(self):
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='普通订单',
width='150cm',
external_raw={'first_record': {'BianHaoKD': '7.07'}},
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsNone(response.data['bianhao_kd'])
self.assertIsNone(response.data['kd_riqi'])
def test_filter_and_order_by_kd_riqi(self):
older = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='旧开单时间',
width='150cm',
external_order_id='KD-OLDER',
kd_riqi=datetime(2026, 7, 1, 10, 0, tzinfo=dt_timezone.utc),
)
newer = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='新开单时间',
width='150cm',
external_order_id='KD-NEWER',
kd_riqi=datetime(2026, 7, 2, 10, 0, tzinfo=dt_timezone.utc),
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='范围外开单时间',
width='150cm',
external_order_id='KD-OUTSIDE',
kd_riqi=datetime(2026, 7, 3, 10, 0, tzinfo=dt_timezone.utc),
)
response = self.client.get(
'/api/v1/printing-orders/?kd_riqi_from=2026-07-01&kd_riqi_to=2026-07-02&ordering=kd_riqi'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
result_ids = [item['id'] for item in response.data['results']]
self.assertEqual(result_ids, [older.id, newer.id])
self.assertEqual(response.data['results'][0]['kd_riqi'], '2026-07-01T18:00:00+08:00')
def test_retrieve_printing_order(self):
"""测试获取订单详情"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
craft='活性印花',
description='详情测试'
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['fabric'], '测试布料')
self.assertEqual(response.data['craft'], '活性印花')
self.assertIn('customer_name', response.data)
self.assertEqual(response.data['customer_name'], self.customer.name)
def test_get_printing_order_by_external_order_id(self):
"""测试通过 external_order_id 获取订单详情"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='通过外部编号查询',
width='150cm',
external_order_id='KD20410611',
)
response = self.client.get('/api/v1/printing-orders/by-external-order-id/KD20410611/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['id'], order.id)
self.assertEqual(response.data['external_order_id'], 'KD20410611')
self.assertEqual(response.data['fabric'], '通过外部编号查询')
def test_update_printing_order(self):
"""测试更新订单"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='旧布料',
width='150cm'
)
update_data = {
'customer': self.customer.id,
'fabric': '新布料',
'width': '160cm',
'is_urgent': True,
'craft': '更新的工艺'
}
response = self.client.put(
f'/api/v1/printing-orders/{order.id}/',
update_data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
order.refresh_from_db()
self.assertEqual(order.fabric, '新布料')
self.assertEqual(order.width, '160cm')
self.assertEqual(order.is_urgent, True)
def test_partial_update_printing_order(self):
"""测试部分更新订单"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='原布料',
width='150cm',
is_urgent=False
)
patch_data = {
'is_urgent': True,
'craft': '新工艺'
}
response = self.client.patch(
f'/api/v1/printing-orders/{order.id}/',
patch_data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
order.refresh_from_db()
self.assertEqual(order.is_urgent, True)
self.assertEqual(order.craft, '新工艺')
self.assertEqual(order.fabric, '原布料') # 未修改字段保持不变
def test_delete_printing_order_forbidden(self):
"""测试删除订单被禁用"""
# 添加删除权限以便测试destroy方法的自定义逻辑
delete_perm = Permission.objects.get(codename='delete_printingorder')
self.user.user_permissions.add(delete_perm)
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm'
)
response = self.client.delete(f'/api/v1/printing-orders/{order.id}/')
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertIn('不支持删除', response.data['detail'])
# 验证订单仍然存在
self.assertTrue(
printing_models.PrintingOrder.objects.filter(id=order.id).exists()
)
def test_invalidate_printing_order(self):
"""测试作废订单"""
# 添加作废权限
invalidate_perm = Permission.objects.get(codename='can_invalidate_printingorder')
self.user.user_permissions.add(invalidate_perm)
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
is_invalid=False
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/invalidate/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('已作废', response.data['detail'])
order.refresh_from_db()
self.assertTrue(order.is_invalid)
def test_invalidate_without_permission(self):
"""测试没有权限时作废订单失败"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm'
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/invalidate/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertIn('没有权限', response.data['detail'])
def test_activate_printing_order(self):
"""测试恢复订单"""
# 添加恢复权限
activate_perm = Permission.objects.get(codename='can_activate_printingorder')
self.user.user_permissions.add(activate_perm)
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
is_invalid=True
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/activate/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('已恢复', response.data['detail'])
order.refresh_from_db()
self.assertFalse(order.is_invalid)
def test_activate_without_permission(self):
"""测试没有权限时恢复订单失败"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
is_invalid=True
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/activate/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertIn('没有权限', response.data['detail'])
def test_mark_fabric_received(self):
"""测试标记布料已收"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
is_fabric_received=False
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/mark_fabric_received/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('已标记布料已收', response.data['detail'])
order.refresh_from_db()
self.assertTrue(order.is_fabric_received)
def test_mark_fabric_received_already_received(self):
"""测试重复标记布料已收"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
is_fabric_received=True
)
response = self.client.post(f'/api/v1/printing-orders/{order.id}/mark_fabric_received/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('已经标记', response.data['detail'])
def _create_product_for_job(self, name='测试产品'):
category = basic_models.ProductCategory.objects.create(
merchant=self.merchant,
name=f'{name}类别',
product_prefix='JOB',
)
return basic_models.Product.objects.create(
merchant=self.merchant,
category=category,
name=name,
unit=basic_models.ProductUnitEnum.METER,
)
def test_printing_order_ids_returns_job_ids(self):
"""测试获取订单下所有 PrintingJob ID。"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
)
product = self._create_product_for_job()
job1 = printing_models.PrintingJob.objects.create(
merchant=self.merchant,
printing_order=order,
product=product,
quantity=10,
unit='',
)
job2 = printing_models.PrintingJob.objects.create(
merchant=self.merchant,
printing_order=order,
product=product,
quantity=20,
unit='',
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/ids/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'ids': [job1.id, job2.id]})
def test_printing_order_ids_returns_empty_list_when_no_jobs(self):
"""订单存在但没有任务时返回空数组。"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/ids/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'ids': []})
def test_printing_order_ids_returns_404_when_order_missing(self):
"""订单不存在时返回 404。"""
response = self.client.get('/api/v1/printing-orders/99999999/ids/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_filter_by_customer(self):
"""测试按客户过滤"""
customer2 = basic_models.Customer.objects.create(
merchant=self.merchant,
name='客户2',
mobile='13900139001',
created_by=self.employee
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料1',
width='150cm'
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=customer2,
fabric='布料2',
width='160cm'
)
response = self.client.get(f'/api/v1/printing-orders/?customer={self.customer.id}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
def test_filter_by_urgent(self):
"""测试按紧急状态过滤"""
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料1',
width='150cm',
is_urgent=True
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料2',
width='160cm',
is_urgent=False
)
response = self.client.get('/api/v1/printing-orders/?is_urgent=true')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertTrue(response.data['results'][0]['is_urgent'])
def test_search_by_fabric(self):
"""测试按布料搜索"""
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='纯棉布料',
width='150cm'
)
printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='涤纶布料',
width='160cm'
)
response = self.client.get('/api/v1/printing-orders/?search=纯棉')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertIn('纯棉', response.data['results'][0]['fabric'])
def test_ordering(self):
"""测试排序"""
order1 = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料1',
width='150cm'
)
order2 = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='布料2',
width='160cm'
)
# 按 ID 升序
response = self.client.get('/api/v1/printing-orders/?ordering=id')
self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.data['results']
self.assertEqual(results[0]['id'], order1.id)
self.assertEqual(results[1]['id'], order2.id)
# 按 ID 降序
response = self.client.get('/api/v1/printing-orders/?ordering=-id')
results = response.data['results']
self.assertEqual(results[0]['id'], order2.id)
self.assertEqual(results[1]['id'], order1.id)
def test_human_id_generation(self):
"""测试 human_id 自动生成"""
from datetime import date
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm'
)
# human_id 格式: YYYYMMDD000001
self.assertIsNotNone(order.human_id)
self.assertTrue(len(order.human_id) >= 14)
today = date.today().strftime('%Y%m%d')
self.assertTrue(order.human_id.startswith(today))
def test_create_order_with_default_process(self):
"""测试创建订单使用默认流程"""
data = {
'customer': self.customer.id,
'fabric': '测试布料',
'width': '150cm',
}
response = self.client.post('/api/v1/printing-orders/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# 验证使用了默认流程
order = printing_models.PrintingOrder.objects.get(id=response.data['id'])
self.assertEqual(order.process.id, self.process.id)
def test_create_order_with_custom_process(self):
"""测试创建订单指定自定义流程"""
custom_process = stateflow_models.Process.objects.create(name='自定义流程')
data = {
'customer': self.customer.id,
'fabric': '测试布料',
'width': '150cm',
'process': custom_process.id,
}
response = self.client.post('/api/v1/printing-orders/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
order = printing_models.PrintingOrder.objects.get(id=response.data['id'])
self.assertEqual(order.process.id, custom_process.id)
def test_order_progress_in_list(self):
"""测试订单列表包含进度字段"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('progress', response.data['results'][0])
self.assertEqual(response.data['results'][0]['progress'], 0) # 没有任务时为0
def test_update_process_when_no_jobs(self):
"""测试没有任务时可以修改流程"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
new_process = stateflow_models.Process.objects.create(name='新流程')
data = {
'process': new_process.id,
}
response = self.client.patch(
f'/api/v1/printing-orders/{order.id}/',
data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
order.refresh_from_db()
self.assertEqual(order.process.id, new_process.id)
def test_update_process_with_jobs_not_started_relinks_jobs(self):
"""测试:有任务但均未开始时允许改流程,并对所有 jobs 重建/绑定新的 business_object"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
category = basic_models.ProductCategory.objects.create(
name='测试类别2',
merchant=self.merchant,
)
product = basic_models.Product.objects.create(
name='测试产品2',
category=category,
merchant=self.merchant,
)
job = printing_models.PrintingJob.objects.create(
printing_order=order,
product=product,
quantity=10,
unit='',
)
# 绑定一个旧的 business_object未推进过未开始
old_bo = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = old_bo
job.save()
new_process = stateflow_models.Process.objects.create(name='新流程2')
data = {'process': new_process.id}
response = self.client.patch(f'/api/v1/printing-orders/{order.id}/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
order.refresh_from_db()
self.assertEqual(order.process.id, new_process.id)
job.refresh_from_db()
self.assertIsNotNone(job.business_object)
self.assertNotEqual(job.business_object.id, old_bo.id)
self.assertEqual(job.business_object.process.id, new_process.id)
def test_cannot_update_process_when_job_started(self):
"""测试有已开始的任务时不能修改流程"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
# 创建产品类别和产品
category = basic_models.ProductCategory.objects.create(
name='测试类别',
merchant=self.merchant,
)
product = basic_models.Product.objects.create(
name='测试产品',
category=category,
merchant=self.merchant,
)
# 创建任务
job = printing_models.PrintingJob.objects.create(
printing_order=order,
product=product,
quantity=10,
unit='',
size='100x200',
pieces=5,
)
# 创建 BusinessObject 并推进状态
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user)
# 尝试修改流程
new_process = stateflow_models.Process.objects.create(name='新流程')
data = {
'process': new_process.id,
}
response = self.client.patch(
f'/api/v1/printing-orders/{order.id}/',
data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('已开始', str(response.data))
def test_jobs_status_summary_in_list(self):
"""测试订单列表包含 jobs_status_summary 字段PrintingJob 状态汇总)"""
# 创建订单
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
# 创建产品类别和产品
category = basic_models.ProductCategory.objects.create(
name='测试类别',
merchant=self.merchant,
)
product = basic_models.Product.objects.create(
name='测试产品',
category=category,
merchant=self.merchant,
)
# 创建 3 个任务
jobs = []
for i in range(3):
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-test-{i}',
process=self.process,
)
job = printing_models.PrintingJob.objects.create(
printing_order=order,
product=product,
quantity=10,
unit='',
business_object=business_object,
)
jobs.append(job)
# 推进第一个任务的状态(完成第一个节点)
from stateflow.services import advance_to_next_state
advance_to_next_state(jobs[0].business_object, self.user)
# 推进第二个任务的状态两次(完成前两个节点)
advance_to_next_state(jobs[1].business_object, self.user)
advance_to_next_state(jobs[1].business_object, self.user)
# 第三个任务保持初始状态(待印染)
# 获取订单列表
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 检查 jobs_status_summary 字段存在
self.assertIn('jobs_status_summary', response.data['results'][0])
summary = response.data['results'][0]['jobs_status_summary']
# 检查返回格式(列表)
self.assertIsInstance(summary, list)
# 汇总结果应该包含不同状态的计数
# - job0: 完成了第一个节点,下一个待执行是 "印染中"
# - job1: 完成了前两个节点,下一个待执行是 "已完成"
# - job2: 未开始,下一个待执行是 "待印染"
state_names = {item['state_name']: item['count'] for item in summary}
# 验证有3个任务被正确统计
total_count = sum(item['count'] for item in summary)
self.assertEqual(total_count, 3)
# 验证返回的每个项都包含必要字段
for item in summary:
self.assertIn('state_name', item)
self.assertIn('state_id', item)
self.assertIn('count', item)
def test_jobs_status_summary_empty_when_no_jobs(self):
"""测试没有任务时 jobs_status_summary 为空列表"""
order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
fabric='无任务订单',
width='150cm',
process=self.process,
)
# 获取特定订单详情(避免被其他测试数据影响)
response = self.client.get(f'/api/v1/printing-orders/{order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 详情接口不包含 jobs_status_summary这是列表专用字段
# 改用列表接口并按特定条件过滤
response = self.client.get('/api/v1/printing-orders/', {'fabric': '无任务订单'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
summary = response.data['results'][0]['jobs_status_summary']
self.assertEqual(summary, [])
class CustomerVisibilityFilterTestCase(TestCase):
"""测试客户可见性过滤"""
def setUp(self):
from django.core.cache import cache
cache.clear()
self.client = APIClient()
# 创建商户
self.merchant = basic_models.Merchant.objects.create(
name='测试印花厂',
type=basic_models.MerchantTypeEnum.FACTORY
)
# 创建第二个商户(用于测试 merchant 隔离)
self.merchant2 = basic_models.Merchant.objects.create(
name='其他印花厂',
type=basic_models.MerchantTypeEnum.FACTORY
)
# 创建用户和员工
self.user = User.objects.create_user(
username='testuser',
password='testpass123',
email='test@example.com'
)
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建第二个员工(同商户,用于测试客户可见性)
self.user2 = User.objects.create_user(
username='testuser2',
password='testpass123',
email='test2@example.com'
)
self.employee2 = basic_models.Employee.objects.create(
sys_user=self.user2,
merchant=self.merchant,
name='测试员工2',
mobile='13800138001',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建客户employee 创建的employee 可见)
self.customer_by_emp1 = basic_models.Customer.objects.create(
merchant=self.merchant,
name='员工1的客户',
mobile='13900139000',
created_by=self.employee
)
# 创建客户employee2 创建的,默认 employee 不可见)
self.customer_by_emp2 = basic_models.Customer.objects.create(
merchant=self.merchant,
name='员工2的客户',
mobile='13900139001',
created_by=self.employee2
)
# 创建 employee 可见客户的订单
self.order_visible = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer_by_emp1,
fabric='可见订单',
width='150cm'
)
# 创建 employee 不可见客户的订单
self.order_invisible = printing_models.PrintingOrder.objects.create(
merchant=self.merchant,
customer=self.customer_by_emp2,
fabric='不可见订单',
width='150cm'
)
# 创建其他商户的订单merchant 隔离测试)
self.customer_other_merchant = basic_models.Customer.objects.create(
merchant=self.merchant2,
name='其他商户客户',
mobile='13900139002'
)
self.order_other_merchant = printing_models.PrintingOrder.objects.create(
merchant=self.merchant2,
customer=self.customer_other_merchant,
fabric='其他商户订单',
width='150cm'
)
# 给用户添加基础权限(不含 view_all
view_perm = Permission.objects.get(codename='view_printingorder')
self.user.user_permissions.add(view_perm)
self.user2.user_permissions.add(view_perm)
self.client.force_authenticate(user=self.user)
def test_list_only_visible_orders(self):
"""测试普通员工只能看到可见客户的订单"""
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 应该只能看到自己创建的客户的订单 = 1
self.assertEqual(response.data['count'], 1)
fabrics = [r['fabric'] for r in response.data['results']]
self.assertIn('可见订单', fabrics)
self.assertNotIn('不可见订单', fabrics)
self.assertNotIn('其他商户订单', fabrics)
def test_retrieve_visible_order(self):
"""测试可以获取可见订单详情"""
response = self.client.get(f'/api/v1/printing-orders/{self.order_visible.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_retrieve_invisible_order_404(self):
"""测试无法获取不可见订单详情"""
response = self.client.get(f'/api/v1/printing-orders/{self.order_invisible.id}/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_retrieve_other_merchant_order_404(self):
"""测试无法获取其他商户的订单"""
response = self.client.get(f'/api/v1/printing-orders/{self.order_other_merchant.id}/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_customer_in_visible_employees_can_see(self):
"""测试被加入 visible_employees 后可以看到订单"""
# 将 employee 加入 customer_by_emp2 的可见员工列表
self.customer_by_emp2.visible_employees.add(self.employee)
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 现在应该能看到 2 个订单(自己创建的 + 被加入可见列表的)
self.assertEqual(response.data['count'], 2)
fabrics = [r['fabric'] for r in response.data['results']]
self.assertIn('不可见订单', fabrics)
def test_view_all_permission_bypasses_filter(self):
"""测试 view_all 权限可以突破客户可见性限制"""
# 添加 view_all 权限
view_all_perm = Permission.objects.get(codename='view_all_printingorders')
self.user.user_permissions.add(view_all_perm)
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 应该能看到本商户的所有订单2个但不能看到其他商户的
self.assertEqual(response.data['count'], 2)
fabrics = [r['fabric'] for r in response.data['results']]
self.assertIn('不可见订单', fabrics)
self.assertNotIn('其他商户订单', fabrics)
def test_superuser_sees_all(self):
"""测试超级用户可以看到所有订单"""
self.user.is_superuser = True
self.user.save()
response = self.client.get('/api/v1/printing-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 超级用户可以看到所有订单(包括其他商户)
self.assertEqual(response.data['count'], 3)