forked from erp-dev/erp
feat: added new api for query batch_advance_records
This commit is contained in:
138
api_v2/tests.py
138
api_v2/tests.py
@@ -1516,3 +1516,141 @@ class MyVisiblePagesAPITest(TestCase):
|
|||||||
self.client.logout()
|
self.client.logout()
|
||||||
response = self.client.get('/api/v2/me/visible-pages/')
|
response = self.client.get('/api/v2/me/visible-pages/')
|
||||||
self.assertEqual(response.status_code, 401)
|
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)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from api_v2.views import (
|
|||||||
PlateOrderByProcessView,
|
PlateOrderByProcessView,
|
||||||
PlateOrderByStateStatusView,
|
PlateOrderByStateStatusView,
|
||||||
PlateOrderBatchUpdateView,
|
PlateOrderBatchUpdateView,
|
||||||
|
PrintingOrderBatchAdvanceRecordsView,
|
||||||
BusinessObjectCloneView,
|
BusinessObjectCloneView,
|
||||||
)
|
)
|
||||||
from api_v2.views.basic_info import CustomerEmployeeBindingView, MyVisiblePagesView
|
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/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/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-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-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-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'),
|
path('plate-orders/by-state-status/', PlateOrderByStateStatusView.as_view(), name='api_v2_plate_order_by_state_status'),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .printing import (
|
|||||||
PlateOrderByProcessView,
|
PlateOrderByProcessView,
|
||||||
PlateOrderByStateStatusView,
|
PlateOrderByStateStatusView,
|
||||||
PlateOrderBatchUpdateView,
|
PlateOrderBatchUpdateView,
|
||||||
|
PrintingOrderBatchAdvanceRecordsView,
|
||||||
)
|
)
|
||||||
from .stateflow import BusinessObjectCloneView
|
from .stateflow import BusinessObjectCloneView
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ __all__ = [
|
|||||||
'PlateOrderByProcessView',
|
'PlateOrderByProcessView',
|
||||||
'PlateOrderByStateStatusView',
|
'PlateOrderByStateStatusView',
|
||||||
'PlateOrderBatchUpdateView',
|
'PlateOrderBatchUpdateView',
|
||||||
|
'PrintingOrderBatchAdvanceRecordsView',
|
||||||
'BusinessObjectCloneView',
|
'BusinessObjectCloneView',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1345,3 +1345,116 @@ class PlateOrderBatchUpdateView(APIView):
|
|||||||
"plate_order_ids": found_ids,
|
"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(),
|
||||||
|
})
|
||||||
|
|||||||
145
docs/api_v2_printing_order_batch_advance_records.md
Normal file
145
docs/api_v2_printing_order_batch_advance_records.md
Normal 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` - 流程节点
|
||||||
|
|
||||||
Reference in New Issue
Block a user