forked from erp-dev/erp
feat: log formatted
This commit is contained in:
23
api_v1/migrations/0015_alter_apiauditlog_method_and_more.py
Normal file
23
api_v1/migrations/0015_alter_apiauditlog_method_and_more.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.8 on 2026-06-25 05:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api_v1', '0014_alter_printingexternalsyncfailure_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='apiauditlog',
|
||||
name='method',
|
||||
field=models.CharField(help_text='HTTP方法,如POST、PUT、PATCH', max_length=10, verbose_name='请求方法'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='apiauditlog',
|
||||
name='request_data',
|
||||
field=models.JSONField(default=dict, help_text='请求的body数据(JSON格式)', verbose_name='请求体数据'),
|
||||
),
|
||||
]
|
||||
@@ -255,9 +255,9 @@ class PrintingExternalSyncFailure(ModelBase):
|
||||
|
||||
|
||||
class ApiAuditLog(models.Model):
|
||||
"""API审计日志 - 记录创建操作的历史现场
|
||||
|
||||
用于保存特定模块的POST请求,记录"创建"操作的完整请求信息。
|
||||
"""API审计日志 - 记录创建/更新操作的历史现场
|
||||
|
||||
用于保存特定模块的POST/PUT/PATCH请求,记录"创建/更新"操作的完整请求信息。
|
||||
通过Celery异步写入,避免影响API响应性能。
|
||||
"""
|
||||
url = models.CharField(
|
||||
@@ -268,12 +268,12 @@ class ApiAuditLog(models.Model):
|
||||
method = models.CharField(
|
||||
max_length=10,
|
||||
verbose_name='请求方法',
|
||||
help_text='HTTP方法,如POST'
|
||||
help_text='HTTP方法,如POST、PUT、PATCH'
|
||||
)
|
||||
request_data = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name='请求体数据',
|
||||
help_text='POST请求的body数据(JSON格式)'
|
||||
help_text='请求的body数据(JSON格式)'
|
||||
)
|
||||
query_params = models.JSONField(
|
||||
default=dict,
|
||||
|
||||
@@ -448,6 +448,7 @@ def _build_printing_order_before_snapshot(printing_order):
|
||||
{
|
||||
'id': job.id,
|
||||
'original_id': job.original_id,
|
||||
'sub_id': job.sub_id,
|
||||
'product_id': job.product_id,
|
||||
'product_name': getattr(job.product, 'name', None),
|
||||
'quantity': job.quantity,
|
||||
@@ -939,6 +940,7 @@ def _upsert_external_printing_job(*, merchant, printing_order, record: dict, syn
|
||||
'pieces': _extract_pieces(record.get('BeiZhuC')),
|
||||
'description': None,
|
||||
'original_id': record_id,
|
||||
'sub_id': _extract_positive_int(record.get('SubID'), field_name='SubID'),
|
||||
'created_by': created_by_user,
|
||||
'external_product_name': None,
|
||||
'external_raw': record,
|
||||
|
||||
@@ -85,6 +85,7 @@ class ExternalPrintingRecordsSyncTaskTest(TestCase):
|
||||
'SeHao': '1.51',
|
||||
'ShuLiang': '10.00',
|
||||
'ShuLiangZ': '2.68',
|
||||
'SubID': 1,
|
||||
'YanSe': product_name,
|
||||
'area': '周边',
|
||||
'customer': {
|
||||
@@ -116,6 +117,7 @@ class ExternalPrintingRecordsSyncTaskTest(TestCase):
|
||||
|
||||
job = printing_models.PrintingJob.objects.get(original_id=1000000)
|
||||
self.assertEqual(job.product, self.existing_product)
|
||||
self.assertEqual(job.sub_id, 1)
|
||||
self.assertIsNone(job.external_product_name)
|
||||
|
||||
@patch('api_v1.tasks._advance_external_printing_cursor')
|
||||
@@ -336,6 +338,7 @@ class ExternalPrintingRecordsSyncTaskTest(TestCase):
|
||||
updated_record['ShuLiang'] = '20.00'
|
||||
updated_record['ShuLiangZ'] = '3.15'
|
||||
updated_record['BeiZhuC'] = '20件'
|
||||
updated_record['SubID'] = 2
|
||||
mock_fetch_image.return_value = {'bytes': b'', 'content_type': 'image/jpeg', 'name': 'unused.jpg'}
|
||||
mock_advance_cursor.return_value = {'updated': True}
|
||||
|
||||
@@ -354,6 +357,7 @@ class ExternalPrintingRecordsSyncTaskTest(TestCase):
|
||||
self.assertEqual(jobs.count(), 1)
|
||||
job = jobs.get()
|
||||
self.assertEqual(job.original_id, 1000001)
|
||||
self.assertEqual(job.sub_id, 2)
|
||||
self.assertEqual(job.quantity, 20)
|
||||
self.assertEqual(job.size, '3.15')
|
||||
self.assertEqual(job.pieces, 20)
|
||||
|
||||
@@ -369,7 +369,7 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'original_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name',
|
||||
'id', 'original_id', 'sub_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name',
|
||||
'product_image_url', 'has_started',
|
||||
'quantity', 'billed_quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state', 'work_state_display',
|
||||
@@ -480,7 +480,7 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'original_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name', 'product_code',
|
||||
'id', 'original_id', 'sub_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name', 'product_code',
|
||||
'quantity', 'billed_quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state', 'work_state_display',
|
||||
'status', 'status_id', 'is_completed', 'is_production_completed', 'has_started',
|
||||
@@ -543,7 +543,7 @@ class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.PrintingJob
|
||||
fields = [
|
||||
'id', 'original_id', 'printing_order', 'external_order_id', 'product', 'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'id', 'original_id', 'sub_id', 'printing_order', 'external_order_id', 'product', 'quantity', 'unit', 'size', 'pieces', 'description',
|
||||
'work_state',
|
||||
'batch_advance_records',
|
||||
]
|
||||
|
||||
@@ -248,6 +248,22 @@ class PrintingOrderAPITestCase(TestCase):
|
||||
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):
|
||||
"""测试更新订单"""
|
||||
|
||||
@@ -114,12 +114,14 @@ class PrintingJobAPITestCase(TestCase):
|
||||
'unit': '米',
|
||||
'size': '50*60',
|
||||
'pieces': 10,
|
||||
'sub_id': 7,
|
||||
'description': '测试备注'
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['external_order_id'], 'KD20410611')
|
||||
self.assertEqual(response.data['sub_id'], 7)
|
||||
|
||||
# 新增字段:批量推进记录(稳定输出 key,默认空数组)
|
||||
self.assertIn('batch_advance_records', response.data)
|
||||
@@ -135,6 +137,7 @@ class PrintingJobAPITestCase(TestCase):
|
||||
self.assertEqual(job.quantity, 100)
|
||||
self.assertEqual(job.unit, '米')
|
||||
self.assertEqual(job.pieces, 10)
|
||||
self.assertEqual(job.sub_id, 7)
|
||||
self.assertFalse(job.is_production_completed)
|
||||
|
||||
def test_create_printing_job_ignores_is_production_completed(self):
|
||||
@@ -162,7 +165,8 @@ class PrintingJobAPITestCase(TestCase):
|
||||
quantity=100,
|
||||
unit='米',
|
||||
size='50*60',
|
||||
pieces=10
|
||||
pieces=10,
|
||||
sub_id=101
|
||||
)
|
||||
job2 = printing_models.PrintingJob.objects.create(
|
||||
printing_order=self.printing_order,
|
||||
@@ -170,12 +174,16 @@ class PrintingJobAPITestCase(TestCase):
|
||||
quantity=200,
|
||||
unit='米',
|
||||
size='60*70',
|
||||
pieces=20
|
||||
pieces=20,
|
||||
sub_id=202
|
||||
)
|
||||
|
||||
response = self.client.get('/api/v1/printing-jobs/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
sub_ids_by_id = {item['id']: item['sub_id'] for item in response.data['results']}
|
||||
self.assertEqual(sub_ids_by_id[job1.id], 101)
|
||||
self.assertEqual(sub_ids_by_id[job2.id], 202)
|
||||
|
||||
# 新增字段:批量推进记录(稳定输出 key)
|
||||
for item in response.data['results']:
|
||||
@@ -640,6 +648,7 @@ class PrintingJobAPITestCase(TestCase):
|
||||
'unit': '码',
|
||||
'size': '60*70',
|
||||
'pieces': 20,
|
||||
'sub_id': 8,
|
||||
'description': '更新后的备注'
|
||||
}
|
||||
|
||||
@@ -650,11 +659,13 @@ class PrintingJobAPITestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['external_order_id'], 'KD20410611')
|
||||
self.assertEqual(response.data['sub_id'], 8)
|
||||
|
||||
job.refresh_from_db()
|
||||
self.assertEqual(job.quantity, 200)
|
||||
self.assertEqual(job.unit, '码')
|
||||
self.assertEqual(job.pieces, 20)
|
||||
self.assertEqual(job.sub_id, 8)
|
||||
self.assertFalse(job.is_production_completed)
|
||||
|
||||
def test_update_printing_job_ignores_is_production_completed(self):
|
||||
@@ -724,7 +735,8 @@ class PrintingJobAPITestCase(TestCase):
|
||||
|
||||
patch_data = {
|
||||
'quantity': 150,
|
||||
'pieces': 15
|
||||
'pieces': 15,
|
||||
'sub_id': None
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
@@ -734,10 +746,12 @@ class PrintingJobAPITestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['external_order_id'], 'KD20410611')
|
||||
self.assertIsNone(response.data['sub_id'])
|
||||
|
||||
job.refresh_from_db()
|
||||
self.assertEqual(job.quantity, 150)
|
||||
self.assertEqual(job.pieces, 15)
|
||||
self.assertIsNone(job.sub_id)
|
||||
self.assertEqual(job.unit, '米') # 未修改字段保持不变
|
||||
|
||||
def test_delete_printing_job_forbidden(self):
|
||||
|
||||
@@ -301,7 +301,7 @@ class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
|
||||
):
|
||||
queryset = queryset.filter(is_invalid=False)
|
||||
|
||||
if self.action in ["list", "retrieve"]:
|
||||
if self.action in ["list", "retrieve", "by_external_order_id"]:
|
||||
queryset = queryset.select_related("customer")
|
||||
# 预取 printing_jobs 及其 business_object,用于状态汇总统计
|
||||
# 同时预取 state_logs 和 process 相关数据以减少 job.status 属性调用时的 N+1 查询
|
||||
@@ -327,6 +327,29 @@ class PrintingOrderViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
|
||||
status=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
)
|
||||
|
||||
@action(
|
||||
detail=False,
|
||||
methods=["get"],
|
||||
url_path=r"by-external-order-id/(?P<external_order_id>[^/.]+)",
|
||||
)
|
||||
def by_external_order_id(self, request, external_order_id=None):
|
||||
"""通过 external_order_id 获取印染订单详情"""
|
||||
normalized_external_order_id = str(external_order_id or "").strip()
|
||||
printing_order = (
|
||||
self.get_queryset()
|
||||
.filter(external_order_id=normalized_external_order_id)
|
||||
.order_by("id")
|
||||
.first()
|
||||
)
|
||||
if not printing_order:
|
||||
return Response(
|
||||
{"detail": "未找到该 external_order_id 对应的印染订单"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
serializer = self.get_serializer(printing_order)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def invalidate(self, request, pk=None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user