forked from erp-dev/erp
fix: added some fields to business.statements api
This commit is contained in:
@@ -1283,9 +1283,13 @@ class StatementRecordAPITestCase(TestCase):
|
||||
sales_order=self.sales_order,
|
||||
product=self.product,
|
||||
price=Decimal('12.50'),
|
||||
color='红',
|
||||
quantity=Decimal('5'),
|
||||
unit='米',
|
||||
empty_diff_percent=Decimal('0'),
|
||||
quantity_of_rolls='2,3',
|
||||
num_of_rolls=2,
|
||||
spec='32S',
|
||||
)
|
||||
|
||||
self.purchase_order = business_models.PurchaseOrder.objects.create(
|
||||
@@ -1300,9 +1304,13 @@ class StatementRecordAPITestCase(TestCase):
|
||||
purchase_order=self.purchase_order,
|
||||
product=self.product,
|
||||
price=Decimal('8.30'),
|
||||
color='蓝',
|
||||
quantity=Decimal('10'),
|
||||
unit='米',
|
||||
empty_diff_percent=Decimal('0'),
|
||||
quantity_of_rolls='6,4',
|
||||
num_of_rolls=2,
|
||||
spec='40S',
|
||||
)
|
||||
|
||||
def test_get_customer_sales_order_record(self):
|
||||
@@ -1323,6 +1331,14 @@ class StatementRecordAPITestCase(TestCase):
|
||||
self.assertEqual(record['source_type'], 'sales_order')
|
||||
self.assertEqual(record['source_id'], self.sales_order.id)
|
||||
self.assertEqual(record['counterparty'], self.customer.id)
|
||||
self.assertIsNotNone(record.get('warehouse'))
|
||||
self.assertEqual(record['warehouse']['id'], self.warehouse.id)
|
||||
self.assertEqual(record['warehouse']['name'], self.warehouse.name)
|
||||
self.assertTrue(record['items'])
|
||||
self.assertEqual(record['items'][0]['color'], '红')
|
||||
self.assertEqual(record['items'][0]['spec'], '32S')
|
||||
self.assertEqual(record['items'][0]['quantity_of_rolls'], [2, 3])
|
||||
self.assertEqual(record['items'][0]['num_of_rolls'], 2)
|
||||
|
||||
def test_get_supplier_purchase_order_record(self):
|
||||
response = self.client.get(
|
||||
@@ -1339,6 +1355,14 @@ class StatementRecordAPITestCase(TestCase):
|
||||
self.assertEqual(record['source_type'], 'purchase_order')
|
||||
self.assertEqual(record['source_id'], self.purchase_order.id)
|
||||
self.assertEqual(record['counterparty'], self.supplier.id)
|
||||
self.assertIsNotNone(record.get('warehouse'))
|
||||
self.assertEqual(record['warehouse']['id'], self.warehouse.id)
|
||||
self.assertEqual(record['warehouse']['name'], self.warehouse.name)
|
||||
self.assertTrue(record['items'])
|
||||
self.assertEqual(record['items'][0]['color'], '蓝')
|
||||
self.assertEqual(record['items'][0]['spec'], '40S')
|
||||
self.assertEqual(record['items'][0]['quantity_of_rolls'], [6, 4])
|
||||
self.assertEqual(record['items'][0]['num_of_rolls'], 2)
|
||||
|
||||
def test_statement_record_not_found(self):
|
||||
response = self.client.get(
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from business import services as business_services
|
||||
|
||||
|
||||
class StatementWarehouseSerializer(serializers.ModelSerializer):
|
||||
"""对账单中使用的仓库序列化器(输出完整仓库数据)"""
|
||||
|
||||
type_label = serializers.CharField(source='get_type_display', read_only=True)
|
||||
mode_label = serializers.CharField(source='get_mode_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = basic_models.WareHouse
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class StatementRecordSerializer(serializers.Serializer):
|
||||
source_type = serializers.CharField()
|
||||
source_label = serializers.CharField()
|
||||
@@ -13,6 +25,7 @@ class StatementRecordSerializer(serializers.Serializer):
|
||||
status_label = serializers.CharField()
|
||||
counterparty = serializers.IntegerField()
|
||||
counterparty_name = serializers.CharField()
|
||||
warehouse = StatementWarehouseSerializer(allow_null=True)
|
||||
positive_amount = serializers.DecimalField(max_digits=15, decimal_places=2)
|
||||
negative_amount = serializers.DecimalField(max_digits=15, decimal_places=2)
|
||||
cumulative_amount = serializers.CharField()
|
||||
|
||||
@@ -1869,6 +1869,7 @@ class _StatementBuilder:
|
||||
recorded_at,
|
||||
status: int,
|
||||
status_label: str,
|
||||
warehouse: basic_info_models.WareHouse | None = None,
|
||||
positive_amount,
|
||||
negative_amount,
|
||||
items: List[dict] | None = None,
|
||||
@@ -1885,6 +1886,8 @@ class _StatementBuilder:
|
||||
'recorded_at': recorded_at,
|
||||
'status': status,
|
||||
'status_label': status_label,
|
||||
# 注意:收款单/付款单等资金类单据无仓库概念,warehouse 将为 None
|
||||
'warehouse': warehouse,
|
||||
'positive_amount': _normalize_statement_amount(positive_amount),
|
||||
'negative_amount': _normalize_statement_amount(negative_amount),
|
||||
'items': items,
|
||||
@@ -1901,7 +1904,11 @@ class _StatementBuilder:
|
||||
product_name = getattr(product, 'name', '')
|
||||
unit = getattr(item, 'unit', '')
|
||||
price = getattr(item, 'price', Decimal('0'))
|
||||
key = (product_id, product_name, unit, price)
|
||||
# 这些字段均来自订单明细(*OrderItem),用于在对账单中对 items 进行合理聚合:
|
||||
# - 同一产品不同颜色/规格应拆分展示,避免合并后信息丢失
|
||||
color = getattr(item, 'color', None) or ''
|
||||
spec = getattr(item, 'spec', None) or ''
|
||||
key = (product_id, product_name, unit, price, color, spec)
|
||||
if key not in aggregated:
|
||||
aggregated[key] = {
|
||||
'product_id': product_id,
|
||||
@@ -1909,9 +1916,36 @@ class _StatementBuilder:
|
||||
'quantity': Decimal('0'),
|
||||
'price': price,
|
||||
'unit': unit,
|
||||
'color': color,
|
||||
'spec': spec,
|
||||
# 统一输出各条数数量(严格模式下为每条数量列表;非严格模式/未填则为空列表)
|
||||
# 来源于各 *OrderItem 模型新增的 split_quantity_of_rolls() 方法
|
||||
'quantity_of_rolls': [],
|
||||
# 条数:来自各 *OrderItem.num_of_rolls。聚合时对相同产品/单价的多行明细累加。
|
||||
'num_of_rolls': 0,
|
||||
}
|
||||
quantity_value = getattr(item, 'quantity', 0) or 0
|
||||
aggregated[key]['quantity'] += Decimal(str(quantity_value))
|
||||
|
||||
num_of_rolls_value = getattr(item, 'num_of_rolls', None)
|
||||
try:
|
||||
num_of_rolls_int = int(num_of_rolls_value) if num_of_rolls_value is not None else 0
|
||||
except (TypeError, ValueError): # pragma: no cover - defensive
|
||||
num_of_rolls_int = 0
|
||||
|
||||
split_quantity_of_rolls = getattr(item, 'split_quantity_of_rolls', None)
|
||||
if callable(split_quantity_of_rolls):
|
||||
try:
|
||||
rolls = split_quantity_of_rolls() or []
|
||||
except Exception: # pragma: no cover - defensive
|
||||
rolls = []
|
||||
if rolls:
|
||||
aggregated[key]['quantity_of_rolls'].extend(list(rolls))
|
||||
# 若无 num_of_rolls(或为 0),兜底使用 rolls 长度
|
||||
if not num_of_rolls_int:
|
||||
num_of_rolls_int = len(rolls)
|
||||
|
||||
aggregated[key]['num_of_rolls'] += num_of_rolls_int
|
||||
return list(aggregated.values())
|
||||
|
||||
def _sort_records(self, records: Iterable[dict]) -> List[dict]:
|
||||
@@ -1951,7 +1985,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
|
||||
customer=customer,
|
||||
status=models.SalesOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('customer')
|
||||
.select_related('customer', 'warehouse')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
@@ -1968,6 +2002,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
warehouse=order.warehouse,
|
||||
positive_amount=order.get_total_amount(),
|
||||
negative_amount=_STATEMENT_ZERO,
|
||||
items=items,
|
||||
@@ -1982,7 +2017,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
|
||||
customer=customer,
|
||||
status=models.SalesReturnStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('customer')
|
||||
.select_related('customer', 'warehouse')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
@@ -1999,6 +2034,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
warehouse=order.warehouse,
|
||||
positive_amount=_STATEMENT_ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
items=items,
|
||||
@@ -2050,7 +2086,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
|
||||
supplier=supplier,
|
||||
status=models.PurchaseOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('supplier')
|
||||
.select_related('supplier', 'warehouse')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
@@ -2067,6 +2103,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
warehouse=order.warehouse,
|
||||
positive_amount=order.get_total_amount(),
|
||||
negative_amount=_STATEMENT_ZERO,
|
||||
items=items,
|
||||
@@ -2081,7 +2118,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
|
||||
supplier=supplier,
|
||||
status=models.PurchaseReturnStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('supplier')
|
||||
.select_related('supplier', 'warehouse')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
@@ -2098,6 +2135,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
warehouse=order.warehouse,
|
||||
positive_amount=_STATEMENT_ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
items=items,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -60,3 +60,4 @@ PlateOrder 模型当前 **没有 `work_state` 字段**。主要字段如下(
|
||||
|
||||
由于 PlateOrder 未包含 `work_state`,前端无需提交该字段;若后续业务需要,可在模型与序列化器中新增字段并同步更新本文档。
|
||||
|
||||
|
||||
|
||||
231
stateflow/tests/test_business_object_crud_and_filters_api.py
Normal file
231
stateflow/tests/test_business_object_crud_and_filters_api.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
BusinessObject CRUD + 过滤器 API 测试
|
||||
|
||||
现有测试主要覆盖 BusinessObject 的 action(advance/reset/timeline/state-logs/...),
|
||||
这里补齐 list/retrieve/create/update/delete 以及 FilterSet 的关键分支。
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from stateflow import models, services
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class BusinessObjectCRUDAndFilterAPITestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create_user(username="testuser", password="testpass")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
# 通用状态/流程(3个节点)
|
||||
self.state1 = models.State.objects.create(name="状态1")
|
||||
self.state2 = models.State.objects.create(name="状态2")
|
||||
self.state3 = models.State.objects.create(name="状态3")
|
||||
|
||||
self.process_a = models.Process.objects.create(name="流程A", description="用于测试过滤器A")
|
||||
models.ProcessNode.objects.create(process=self.process_a, state=self.state1, order=0)
|
||||
models.ProcessNode.objects.create(process=self.process_a, state=self.state2, order=1)
|
||||
models.ProcessNode.objects.create(process=self.process_a, state=self.state3, order=2)
|
||||
|
||||
self.process_b = models.Process.objects.create(name="流程B", description="用于测试过滤器B")
|
||||
models.ProcessNode.objects.create(process=self.process_b, state=self.state1, order=0)
|
||||
|
||||
def test_business_object_crud(self):
|
||||
"""覆盖 create/list/retrieve/patch/delete 的基本 happy path"""
|
||||
# create(注意:create serializer 不包含 id,需要从 DB 获取)
|
||||
create_payload = {
|
||||
"name": "BO-CRUD-1",
|
||||
"process": self.process_a.id,
|
||||
"description": "测试 CRUD",
|
||||
}
|
||||
resp = self.client.post("/api/v1/stateflow/business-objects/", create_payload, format="json")
|
||||
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
bo = models.BusinessObject.objects.get(name="BO-CRUD-1")
|
||||
self.assertEqual(bo.process_id, self.process_a.id)
|
||||
|
||||
# list
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertGreaterEqual(resp.data["count"], 1)
|
||||
|
||||
# list 返回字段校验(挑关键字段)
|
||||
results = resp.data["results"]
|
||||
item = next(x for x in results if x["id"] == bo.id)
|
||||
self.assertEqual(item["name"], "BO-CRUD-1")
|
||||
self.assertEqual(item["process"], self.process_a.id)
|
||||
self.assertEqual(item["process_name"], "流程A")
|
||||
self.assertIn("current_state_name", item)
|
||||
self.assertIn("overall_status", item)
|
||||
self.assertIn("progress_percentage", item)
|
||||
|
||||
# retrieve(详情含 timeline/state_logs 等)
|
||||
resp = self.client.get(f"/api/v1/stateflow/business-objects/{bo.id}/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["id"], bo.id)
|
||||
self.assertEqual(resp.data["name"], "BO-CRUD-1")
|
||||
self.assertIn("process_detail", resp.data)
|
||||
self.assertIn("timeline", resp.data)
|
||||
self.assertIn("state_logs", resp.data)
|
||||
self.assertEqual(len(resp.data["timeline"]), 3)
|
||||
|
||||
# patch 更新 name
|
||||
resp = self.client.patch(
|
||||
f"/api/v1/stateflow/business-objects/{bo.id}/",
|
||||
{"name": "BO-CRUD-1-RENAMED"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
bo.refresh_from_db()
|
||||
self.assertEqual(bo.name, "BO-CRUD-1-RENAMED")
|
||||
|
||||
# delete
|
||||
resp = self.client.delete(f"/api/v1/stateflow/business-objects/{bo.id}/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertFalse(models.BusinessObject.objects.filter(id=bo.id).exists())
|
||||
|
||||
def test_create_business_object_with_content_type_str(self):
|
||||
"""覆盖 create serializer 的 content_type_str 解析与一致性校验"""
|
||||
# 正确:提供 content_type_str + object_id
|
||||
resp = self.client.post(
|
||||
"/api/v1/stateflow/business-objects/",
|
||||
{
|
||||
"name": "BO-Linked",
|
||||
"process": self.process_a.id,
|
||||
"content_type_str": "stateflow.process",
|
||||
"object_id": self.process_a.id,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||
bo = models.BusinessObject.objects.get(name="BO-Linked")
|
||||
self.assertIsNotNone(bo.content_type)
|
||||
self.assertEqual(bo.content_type.app_label, "stateflow")
|
||||
self.assertEqual(bo.content_type.model, "process")
|
||||
self.assertEqual(bo.object_id, self.process_a.id)
|
||||
|
||||
# 错误:content_type_str 格式不合法
|
||||
resp = self.client.post(
|
||||
"/api/v1/stateflow/business-objects/",
|
||||
{
|
||||
"name": "BO-Bad-CT",
|
||||
"process": self.process_a.id,
|
||||
"content_type_str": "badformat",
|
||||
"object_id": self.process_a.id,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("content_type_str", resp.data)
|
||||
|
||||
# 错误:提供 content_type_str 但缺 object_id
|
||||
resp = self.client.post(
|
||||
"/api/v1/stateflow/business-objects/",
|
||||
{
|
||||
"name": "BO-Missing-ObjectId",
|
||||
"process": self.process_a.id,
|
||||
"content_type_str": "stateflow.process",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("object_id", resp.data)
|
||||
|
||||
def test_business_object_filters(self):
|
||||
"""覆盖 BusinessObjectFilterSet 的关键过滤条件"""
|
||||
bo_in_progress = models.BusinessObject.objects.create(
|
||||
name="BO-InProgress-Alpha",
|
||||
process=self.process_a,
|
||||
description="alpha desc",
|
||||
)
|
||||
bo_completed = models.BusinessObject.objects.create(
|
||||
name="BO-Completed-Beta",
|
||||
process=self.process_a,
|
||||
description="beta desc",
|
||||
)
|
||||
bo_other_process = models.BusinessObject.objects.create(
|
||||
name="BO-OtherProcess-Gamma",
|
||||
process=self.process_b,
|
||||
description="gamma desc",
|
||||
)
|
||||
|
||||
# 让 bo_completed 完成(该流程无必填参数,直接推进即可)
|
||||
services.advance_to_next_state(bo_completed, self.user)
|
||||
services.advance_to_next_state(bo_completed, self.user)
|
||||
services.advance_to_next_state(bo_completed, self.user)
|
||||
self.assertEqual(services.get_overall_status(bo_completed), "completed")
|
||||
|
||||
# name contains
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?name=Alpha&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 1)
|
||||
self.assertEqual(resp.data["results"][0]["id"], bo_in_progress.id)
|
||||
|
||||
# process id
|
||||
resp = self.client.get(f"/api/v1/stateflow/business-objects/?process={self.process_b.id}&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 1)
|
||||
self.assertEqual(resp.data["results"][0]["id"], bo_other_process.id)
|
||||
|
||||
# process_name contains
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?process_name=流程A&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 2)
|
||||
returned_ids = {x["id"] for x in resp.data["results"]}
|
||||
self.assertSetEqual(returned_ids, {bo_in_progress.id, bo_completed.id})
|
||||
|
||||
# overall_status=completed
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?overall_status=completed&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 1)
|
||||
self.assertEqual(resp.data["results"][0]["id"], bo_completed.id)
|
||||
|
||||
# overall_status=in_progress
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?overall_status=in_progress&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
returned_ids = {x["id"] for x in resp.data["results"]}
|
||||
self.assertSetEqual(returned_ids, {bo_in_progress.id, bo_other_process.id})
|
||||
|
||||
# content_type_str + has_content_object
|
||||
ct = ContentType.objects.get_for_model(models.Process)
|
||||
bo_linked = models.BusinessObject.objects.create(
|
||||
name="BO-Linked-Process",
|
||||
process=self.process_a,
|
||||
content_type=ct,
|
||||
object_id=self.process_a.id,
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?has_content_object=true&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
returned_ids = {x["id"] for x in resp.data["results"]}
|
||||
self.assertIn(bo_linked.id, returned_ids)
|
||||
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?has_content_object=false&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
returned_ids = {x["id"] for x in resp.data["results"]}
|
||||
self.assertNotIn(bo_linked.id, returned_ids)
|
||||
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?content_type_str=stateflow.process&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
returned_ids = {x["id"] for x in resp.data["results"]}
|
||||
self.assertSetEqual(returned_ids, {bo_linked.id})
|
||||
|
||||
# 无效 content_type_str:返回空
|
||||
resp = self.client.get("/api/v1/stateflow/business-objects/?content_type_str=badformat&limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 0)
|
||||
|
||||
|
||||
class AuthenticationGuardAPITestCase(TestCase):
|
||||
def test_stateflow_endpoints_require_authentication(self):
|
||||
"""默认权限 IsAuthenticated:未登录应 401"""
|
||||
client = APIClient()
|
||||
resp = client.get("/api/v1/stateflow/states/?limit=1&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
|
||||
39
stateflow/tests/test_process_list_node_count_api.py
Normal file
39
stateflow/tests/test_process_list_node_count_api.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
补充 Process 列表接口的 node_count 字段覆盖
|
||||
|
||||
ProcessViewSet.list() 会对 queryset annotate(node_count),对应 ProcessListSerializer 的只读字段。
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from stateflow import models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class ProcessListNodeCountAPITestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create_user(username="testuser", password="testpass")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
self.state1 = models.State.objects.create(name="状态1")
|
||||
self.state2 = models.State.objects.create(name="状态2")
|
||||
|
||||
def test_process_list_contains_node_count(self):
|
||||
process = models.Process.objects.create(name="带节点计数的流程")
|
||||
models.ProcessNode.objects.create(process=process, state=self.state1, order=0)
|
||||
models.ProcessNode.objects.create(process=process, state=self.state2, order=1)
|
||||
|
||||
resp = self.client.get("/api/v1/stateflow/processes/?limit=10&offset=0")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(resp.data["count"], 1)
|
||||
|
||||
item = resp.data["results"][0]
|
||||
self.assertIn("node_count", item)
|
||||
self.assertEqual(item["node_count"], 2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user