1
0
forked from erp-dev/erp

feat: added new api for query batch_advance_records

This commit is contained in:
2026-01-07 15:06:55 +08:00
parent 4566c2a2c8
commit 5b07332015
5 changed files with 400 additions and 0 deletions

View File

@@ -1516,3 +1516,141 @@ class MyVisiblePagesAPITest(TestCase):
self.client.logout()
response = self.client.get('/api/v2/me/visible-pages/')
self.assertEqual(response.status_code, 401)
class PrintingOrderBatchAdvanceRecordsAPITest(TestCase):
"""测试印染订单批量推进记录查询 API"""
def setUp(self):
self.client = APIClient()
self.merchant = basic_models.Merchant.objects.create(
name='测试商户',
type=basic_models.MerchantTypeEnum.FACTORY,
)
self.user = get_user_model().objects.create_user(username='testuser', password='pass12345')
self.client.force_authenticate(user=self.user)
# 创建客户
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='测试客户'
)
# 创建产品类别和产品
self.category = basic_models.ProductCategory.objects.create(
merchant=self.merchant,
name='测试类别'
)
self.product = basic_models.Product.objects.create(
merchant=self.merchant,
name='测试产品',
category=self.category,
unit=basic_models.ProductUnitEnum.METER
)
# 创建印染订单
self.printing_order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='测试面料',
width='150cm'
)
# 创建印染明细
self.job1 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit=''
)
self.job2 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=200,
unit=''
)
# 创建流程状态
self.state = stateflow_models.State.objects.create(
name='染色',
description='染色工序'
)
# 创建批量推进记录
self.record = printing_models.PrintingJobBatchAdvanceRecord.objects.create(
printing_order=self.printing_order,
state=self.state,
created_by=self.user,
parameters={'temperature': '25.5', 'operator': '张三'}
)
self.record.printing_jobs.add(self.job1, self.job2)
def test_get_batch_advance_records_success(self):
"""测试成功获取批量推进记录"""
response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/')
self.assertEqual(response.status_code, 200)
data = response.data
self.assertEqual(data['printing_order_id'], self.printing_order.id)
self.assertEqual(data['total_count'], 1)
self.assertEqual(len(data['records']), 1)
record = data['records'][0]
self.assertEqual(record['id'], self.record.id)
self.assertEqual(record['state_id'], self.state.id)
self.assertEqual(record['state_name'], '染色')
self.assertEqual(record['created_by'], self.user.id)
self.assertEqual(record['created_by_username'], 'testuser')
self.assertEqual(record['parameters'], {'temperature': '25.5', 'operator': '张三'})
self.assertEqual(record['printing_job_count'], 2)
self.assertIn(self.job1.id, record['printing_job_ids'])
self.assertIn(self.job2.id, record['printing_job_ids'])
def test_get_batch_advance_records_empty(self):
"""测试没有批量推进记录时返回空列表"""
# 创建另一个没有记录的订单
another_order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='测试面料2',
width='160cm'
)
response = self.client.get(f'/api/v2/printing-orders/{another_order.id}/batch-advance-records/')
self.assertEqual(response.status_code, 200)
data = response.data
self.assertEqual(data['printing_order_id'], another_order.id)
self.assertEqual(data['total_count'], 0)
self.assertEqual(data['records'], [])
def test_get_batch_advance_records_order_not_found(self):
"""测试订单不存在时返回 404"""
response = self.client.get('/api/v2/printing-orders/99999/batch-advance-records/')
self.assertEqual(response.status_code, 404)
self.assertIn('error', response.data)
def test_get_batch_advance_records_multiple_records(self):
"""测试多条批量推进记录按时间倒序返回"""
# 创建第二条记录
state2 = stateflow_models.State.objects.create(name='整理', description='整理工序')
record2 = printing_models.PrintingJobBatchAdvanceRecord.objects.create(
printing_order=self.printing_order,
state=state2,
created_by=self.user,
parameters={'notes': '完成'}
)
record2.printing_jobs.add(self.job1)
response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/')
self.assertEqual(response.status_code, 200)
data = response.data
self.assertEqual(data['total_count'], 2)
# 最新的记录应该在前面
self.assertEqual(data['records'][0]['id'], record2.id)
self.assertEqual(data['records'][1]['id'], self.record.id)
def test_get_batch_advance_records_unauthenticated(self):
"""测试未登录时返回 401"""
self.client.logout()
response = self.client.get(f'/api/v2/printing-orders/{self.printing_order.id}/batch-advance-records/')
self.assertEqual(response.status_code, 401)

View File

@@ -11,6 +11,7 @@ from api_v2.views import (
PlateOrderByProcessView,
PlateOrderByStateStatusView,
PlateOrderBatchUpdateView,
PrintingOrderBatchAdvanceRecordsView,
BusinessObjectCloneView,
)
from api_v2.views.basic_info import CustomerEmployeeBindingView, MyVisiblePagesView
@@ -24,6 +25,7 @@ urlpatterns = [
path('printing-jobs/by-customer/', PrintingJobByCustomerView.as_view(), name='api_v2_printing_job_by_customer'),
path('printing-jobs/batch-advance/preview/', PrintingJobBatchAdvancePreviewView.as_view(), name='api_v2_printing_job_batch_advance_preview'),
path('printing-jobs/batch-advance/', PrintingJobBatchAdvanceSubmitView.as_view(), name='api_v2_printing_job_batch_advance_submit'),
path('printing-orders/<int:printing_order_id>/batch-advance-records/', PrintingOrderBatchAdvanceRecordsView.as_view(), name='api_v2_printing_order_batch_advance_records'),
path('plate-orders/by-process-node/', PlateOrderByProcessNodeView.as_view(), name='api_v2_plate_order_by_process_node'),
path('plate-orders/by-process/', PlateOrderByProcessView.as_view(), name='api_v2_plate_order_by_process'),
path('plate-orders/by-state-status/', PlateOrderByStateStatusView.as_view(), name='api_v2_plate_order_by_state_status'),

View File

@@ -13,6 +13,7 @@ from .printing import (
PlateOrderByProcessView,
PlateOrderByStateStatusView,
PlateOrderBatchUpdateView,
PrintingOrderBatchAdvanceRecordsView,
)
from .stateflow import BusinessObjectCloneView
@@ -28,6 +29,7 @@ __all__ = [
'PlateOrderByProcessView',
'PlateOrderByStateStatusView',
'PlateOrderBatchUpdateView',
'PrintingOrderBatchAdvanceRecordsView',
'BusinessObjectCloneView',
]

View File

@@ -1345,3 +1345,116 @@ class PlateOrderBatchUpdateView(APIView):
"plate_order_ids": found_ids,
}
)
class BatchAdvanceRecordSerializer(serializers.ModelSerializer):
"""批量推进记录序列化器"""
state_id = serializers.IntegerField(source='state.id', read_only=True)
state_name = serializers.CharField(source='state.name', read_only=True)
created_by_username = serializers.CharField(source='created_by.username', read_only=True, allow_null=True)
created_by_name = serializers.SerializerMethodField()
printing_job_ids = serializers.SerializerMethodField()
printing_job_count = serializers.SerializerMethodField()
class Meta:
model = printing_models.PrintingJobBatchAdvanceRecord
fields = [
'id',
'printing_order',
'state',
'state_id',
'state_name',
'created_by',
'created_by_username',
'created_by_name',
'parameters',
'printing_job_ids',
'printing_job_count',
'created_at',
]
read_only_fields = fields
def get_created_by_name(self, obj):
"""获取操作人员工姓名"""
user = getattr(obj, 'created_by', None)
if not user:
return None
emp = getattr(user, 'employee', None)
return getattr(emp, 'name', None)
def get_printing_job_ids(self, obj):
"""获取涉及的印染明细 ID 列表"""
# 使用预取的数据避免 N+1
if hasattr(obj, '_prefetched_objects_cache') and 'printing_jobs' in obj._prefetched_objects_cache:
return [job.id for job in obj.printing_jobs.all()]
return list(obj.printing_jobs.values_list('id', flat=True))
def get_printing_job_count(self, obj):
"""获取涉及的印染明细数量"""
if hasattr(obj, '_prefetched_objects_cache') and 'printing_jobs' in obj._prefetched_objects_cache:
return len(obj.printing_jobs.all())
return obj.printing_jobs.count()
class PrintingOrderBatchAdvanceRecordsView(APIView):
"""
获取印染订单的批量推进记录
根据 printing_order_id 查询该订单下所有的批量推进审计记录。
GET /api/v2/printing-orders/{printing_order_id}/batch-advance-records/
响应示例:
{
"printing_order_id": 55,
"records": [
{
"id": 9,
"printing_order": 55,
"state": 7,
"state_id": 7,
"state_name": "染色",
"created_by": 1001,
"created_by_username": "factory_user",
"created_by_name": "张三",
"parameters": {"temperature": "25.5"},
"printing_job_ids": [101, 102, 103],
"printing_job_count": 3,
"created_at": "2025-12-14T10:00:00+08:00"
}
],
"total_count": 1
}
"""
permission_classes = [permissions.IsAuthenticated]
def get(self, request, printing_order_id):
# 验证订单是否存在
try:
printing_order = printing_models.PrintingOrder.objects.get(id=printing_order_id)
except printing_models.PrintingOrder.DoesNotExist:
return Response(
{'error': f'未找到 ID 为 {printing_order_id} 的印染订单'},
status=status.HTTP_404_NOT_FOUND
)
# 查询批量推进记录,按创建时间倒序
records = printing_models.PrintingJobBatchAdvanceRecord.objects.filter(
printing_order_id=printing_order_id
).select_related(
'state',
'created_by',
'created_by__employee',
).prefetch_related(
'printing_jobs',
).order_by('-created_at')
serializer = BatchAdvanceRecordSerializer(records, many=True)
return Response({
'printing_order_id': printing_order_id,
'records': serializer.data,
'total_count': records.count(),
})

View File

@@ -0,0 +1,145 @@
# 获取印染订单批量推进记录 API
## 概述
根据印染订单ID查询该订单下所有的批量推进审计记录。
- **端点**: `GET /api/v2/printing-orders/{printing_order_id}/batch-advance-records/`
- **认证**: 需要登录
- **权限**: 任意已认证用户
## 背景
`PrintingJob`(印染订单明细)在批量推进流程时,会产生 `PrintingJobBatchAdvanceRecord` 审计记录。此 API 用于查询某个印染订单下的所有批量推进历史。
## 请求
### 路径参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `printing_order_id` | int | 是 | 印染订单ID |
### 请求示例
```bash
curl -X GET "http://localhost:8000/api/v2/printing-orders/55/batch-advance-records/" \
-H "Authorization: Token <your-token>"
```
## 响应
### 成功响应 (200 OK)
```json
{
"printing_order_id": 55,
"records": [
{
"id": 9,
"printing_order": 55,
"state": 7,
"state_id": 7,
"state_name": "染色",
"created_by": 1001,
"created_by_username": "factory_user",
"created_by_name": "张三",
"parameters": {
"temperature": "25.5",
"operator": "张三"
},
"printing_job_ids": [101, 102, 103],
"printing_job_count": 3,
"created_at": "2025-12-14T10:00:00+08:00"
}
],
"total_count": 1
}
```
### 响应字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `printing_order_id` | int | 印染订单ID |
| `records` | array | 批量推进记录列表(按创建时间倒序) |
| `total_count` | int | 记录总数 |
### records 对象结构
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 批量推进记录ID |
| `printing_order` | int | 印染订单ID |
| `state` | int | 完成的流程节点ID |
| `state_id` | int | 同 `state`(便于前端直接取用) |
| `state_name` | string | 流程节点名称 |
| `created_by` | int \| null | 操作人用户ID |
| `created_by_username` | string \| null | 操作人用户名 |
| `created_by_name` | string \| null | 操作人员工姓名 |
| `parameters` | object | 批量推进时提交的工艺参数 |
| `printing_job_ids` | array[int] | 涉及的印染明细ID列表 |
| `printing_job_count` | int | 涉及的印染明细数量 |
| `created_at` | string | 创建时间ISO8601 |
### 无记录时的响应
```json
{
"printing_order_id": 100,
"records": [],
"total_count": 0
}
```
### 错误响应
#### 订单不存在 (404 Not Found)
```json
{
"error": "未找到 ID 为 999 的印染订单"
}
```
#### 未认证 (401 Unauthorized)
```json
{
"detail": "Authentication credentials were not provided."
}
```
## 前端使用示例
```javascript
// 获取印染订单的批量推进记录
async function getBatchAdvanceRecords(printingOrderId) {
const response = await fetch(
`/api/v2/printing-orders/${printingOrderId}/batch-advance-records/`,
{ headers: { 'Authorization': `Token ${token}` } }
);
const data = await response.json();
// 显示每次批量推进的参数和涉及的明细
data.records.forEach(record => {
console.log(`${record.state_name}: ${record.printing_job_count} 条明细`);
console.log('参数:', record.parameters);
});
return data;
}
```
## 相关文档
- [批量推进记录字段说明](./batch_submit_state_params.md) - `api_v1` 中的 `batch_advance_records` 字段说明
- 批量推进接口: `POST /api/v2/printing-jobs/batch-advance/`
## 相关模型
- `printing.PrintingJobBatchAdvanceRecord` - 批量推进记录
- `printing.PrintingOrder` - 印染订单
- `printing.PrintingJob` - 印染明细
- `stateflow.State` - 流程节点