diff --git a/api_v1/views/business/payment/views.py b/api_v1/views/business/payment/views.py index f5c922c..64d7a9f 100644 --- a/api_v1/views/business/payment/views.py +++ b/api_v1/views/business/payment/views.py @@ -12,12 +12,13 @@ class PaymentOrderSerializer(serializers.ModelSerializer): supplier_name = serializers.CharField(source='supplier.name', read_only=True) operator_name = serializers.CharField(source='operator.name', read_only=True) bank_account_name = serializers.CharField(source='bank_account.name', read_only=True) + human_id = serializers.CharField(read_only=True) settlement_amount = serializers.SerializerMethodField() class Meta: model = business_models.PaymentOrder fields = [ - 'id', 'supplier', 'supplier_name', 'bank_account', 'bank_account_name', + 'id', 'human_id', 'supplier', 'supplier_name', 'bank_account', 'bank_account_name', 'payment_date', 'amount', 'discount_amount', 'settlement_amount', 'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id', 'is_red_flushed', 'red_flush_id', 'red_flushed_at', @@ -25,6 +26,7 @@ class PaymentOrderSerializer(serializers.ModelSerializer): ] read_only_fields = [ 'id', + 'human_id', 'supplier_name', 'operator_name', 'bank_account_name', diff --git a/api_v1/views/business/receipt/views.py b/api_v1/views/business/receipt/views.py index 9842752..7872950 100644 --- a/api_v1/views/business/receipt/views.py +++ b/api_v1/views/business/receipt/views.py @@ -12,12 +12,13 @@ class ReceiptOrderSerializer(serializers.ModelSerializer): customer_name = serializers.CharField(source='customer.name', read_only=True) operator_name = serializers.CharField(source='operator.name', read_only=True) bank_account_name = serializers.CharField(source='bank_account.name', read_only=True) + human_id = serializers.CharField(read_only=True) settlement_amount = serializers.SerializerMethodField() class Meta: model = business_models.ReceiptOrder fields = [ - 'id', 'customer', 'customer_name', 'bank_account', 'bank_account_name', + 'id', 'human_id', 'customer', 'customer_name', 'bank_account', 'bank_account_name', 'receipt_date', 'amount', 'discount_amount', 'settlement_amount', 'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id', 'is_red_flushed', 'red_flush_id', 'red_flushed_at', @@ -25,6 +26,7 @@ class ReceiptOrderSerializer(serializers.ModelSerializer): ] read_only_fields = [ 'id', + 'human_id', 'customer_name', 'operator_name', 'bank_account_name', diff --git a/api_v1/views/business/statements/serializers.py b/api_v1/views/business/statements/serializers.py index 930ccf0..b345851 100644 --- a/api_v1/views/business/statements/serializers.py +++ b/api_v1/views/business/statements/serializers.py @@ -19,6 +19,7 @@ class StatementRecordSerializer(serializers.Serializer): source_type = serializers.CharField() source_label = serializers.CharField() source_id = serializers.IntegerField() + human_id = serializers.CharField() occurred_at = serializers.DateField() recorded_at = serializers.DateTimeField() status = serializers.IntegerField() diff --git a/api_v2/tests.py b/api_v2/tests.py index 47fb305..4bbb0e5 100644 --- a/api_v2/tests.py +++ b/api_v2/tests.py @@ -282,6 +282,131 @@ class PrintingJobByCustomerAPITest(TestCase): self.assertEqual(data['billed_quantity'], '20.00') +class PrintingOrderWithoutSalesOrderAPITest(TestCase): + def setUp(self): + self.client = APIClient() + self.url = '/api/v2/printing-orders/without-sales-order/' + + self.merchant = basic_models.Merchant.objects.create( + name='印染商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + self.other_merchant = basic_models.Merchant.objects.create( + name='其他商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + self.user = get_user_model().objects.create_user(username='factory_order_user', password='pass12345') + basic_models.Employee.objects.create( + merchant=self.merchant, + sys_user=self.user, + name='工厂员工', + ) + self.client.force_authenticate(user=self.user) + + self.customer = basic_models.Customer.objects.create( + merchant=self.merchant, + name='客户A', + created_by=None, + ) + self.other_customer = basic_models.Customer.objects.create( + merchant=self.other_merchant, + name='客户B', + created_by=None, + ) + category = basic_models.ProductCategory.objects.create( + merchant=self.merchant, + name='品类', + product_prefix='FAB', + ) + self.product = basic_models.Product.objects.create( + merchant=self.merchant, + category=category, + name='产品A', + human_id='FAB-PO-001', + unit=basic_models.ProductUnitEnum.METER, + ) + self.warehouse = basic_models.WareHouse.objects.create( + merchant=self.merchant, + name='仓库A', + mode=basic_models.WareHouseModeEnum.UNRESTRICTED, + ) + self.operator = basic_models.Employee.objects.create( + merchant=self.merchant, + name='操作员', + ) + + self.unbound_order = self._create_printing_order('未绑定订单') + self.legacy_unbound_order = self._create_printing_order('历史未绑定订单', merchant=None) + self.active_bound_order = self._create_printing_order('已绑定有效销售单') + self.cancelled_bound_order = self._create_printing_order('只绑定作废销售单') + self.other_customer_order = self._create_printing_order('其他客户订单', customer=self.other_customer, merchant=self.other_merchant) + + active_job = self._create_job(self.active_bound_order) + cancelled_job = self._create_job(self.cancelled_bound_order) + self._create_sales_order_item(active_job, status=business_models.SalesOrderStatusEnum.PENDING) + self._create_sales_order_item(cancelled_job, status=business_models.SalesOrderStatusEnum.CANCELLED) + + def _create_printing_order(self, fabric, customer=None, merchant='__default__'): + if merchant == '__default__': + merchant = self.merchant + return printing_models.PrintingOrder.objects.create( + merchant=merchant, + customer=customer or self.customer, + fabric=fabric, + width='150cm', + ) + + def _create_job(self, printing_order): + return printing_models.PrintingJob.objects.create( + merchant=self.merchant, + printing_order=printing_order, + product=self.product, + quantity=10, + unit='米', + ) + + def _create_sales_order_item(self, printing_job, status): + sales_order = business_models.SalesOrder.objects.create( + merchant=self.merchant, + customer=self.customer, + sales_date=datetime.date(2026, 6, 24), + operator=self.operator, + warehouse=self.warehouse, + status=status, + ) + return business_models.SalesOrderItem.objects.create( + sales_order=sales_order, + product=self.product, + printing_job=printing_job, + price=Decimal('10'), + quantity=Decimal('10'), + unit='米', + empty_diff_percent=Decimal('0'), + num_of_rolls=1, + ) + + def test_list_orders_without_non_cancelled_sales_order(self): + response = self.client.get(self.url, {'customer_id': self.customer.id}) + self.assertEqual(response.status_code, 200) + + result_ids = {item['id'] for item in response.data} + self.assertIn(self.unbound_order.id, result_ids) + self.assertIn(self.legacy_unbound_order.id, result_ids) + self.assertIn(self.cancelled_bound_order.id, result_ids) + self.assertNotIn(self.active_bound_order.id, result_ids) + self.assertNotIn(self.other_customer_order.id, result_ids) + + def test_customer_id_is_required(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.data['detail'], 'customer_id 为必填参数') + + def test_customer_id_must_be_integer(self): + response = self.client.get(self.url, {'customer_id': 'abc'}) + self.assertEqual(response.status_code, 400) + self.assertEqual(response.data['detail'], 'customer_id 必须为数字') + + @override_settings(AGENT_ACCESS_KEY='agent-test-key') class AgentUnshippedShipmentListAPITest(TestCase): def setUp(self): diff --git a/api_v2/urls.py b/api_v2/urls.py index e5247b4..f18ebdd 100644 --- a/api_v2/urls.py +++ b/api_v2/urls.py @@ -6,6 +6,7 @@ from api_v2.views import ( QuickCreateEmployeeUserView, RoleListView, PrintingJobByCustomerView, + PrintingOrderWithoutSalesOrderView, PrintingJobBatchAdvancePreviewView, PrintingJobBatchAdvanceSubmitView, PrintingJobBatchAddParametersView, @@ -57,6 +58,7 @@ urlpatterns = [ path('customers/bind-employee/', CustomerEmployeeBindingView.as_view(), name='api_v2_customer_bind_employee'), path('me/visible-pages/', MyVisiblePagesView.as_view(), name='api_v2_my_visible_pages'), path('printing-jobs/by-customer/', PrintingJobByCustomerView.as_view(), name='api_v2_printing_job_by_customer'), + path('printing-orders/without-sales-order/', PrintingOrderWithoutSalesOrderView.as_view(), name='api_v2_printing_order_without_sales_order'), 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-add-parameters/', PrintingJobBatchAddParametersView.as_view(), name='api_v2_printing_job_batch_add_parameters'), diff --git a/api_v2/views/__init__.py b/api_v2/views/__init__.py index 9a8e97f..d8eb113 100644 --- a/api_v2/views/__init__.py +++ b/api_v2/views/__init__.py @@ -7,6 +7,7 @@ from .users import QuickCreateEmployeeUserView, RoleListView from .printing import ( PrintingJobByCustomerView, PrintingJobV2Serializer, + PrintingOrderWithoutSalesOrderView, PrintingJobBatchAdvancePreviewView, PrintingJobBatchAdvanceSubmitView, PrintingJobBatchAddParametersView, @@ -62,6 +63,7 @@ __all__ = [ 'RoleListView', 'PrintingJobByCustomerView', 'PrintingJobV2Serializer', + 'PrintingOrderWithoutSalesOrderView', 'PrintingJobBatchAdvancePreviewView', 'PrintingJobBatchAdvanceSubmitView', 'PrintingJobBatchAddParametersView', diff --git a/api_v2/views/printing.py b/api_v2/views/printing.py index 0fe3f3f..96f5b4f 100644 --- a/api_v2/views/printing.py +++ b/api_v2/views/printing.py @@ -12,6 +12,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from basic_info import models as basic_models +from business import models as business_models from printing import models as printing_models from api_v1.tasks import ( ExternalPrintingOrderSnapshotSyncError, @@ -180,6 +181,73 @@ class PrintingJobByCustomerView(APIView): return Response(serializer.data) +class PrintingOrderWithoutSalesOrderSerializer(serializers.ModelSerializer): + """未关联非作废销售单的印染订单列表序列化器。""" + + customer_name = serializers.CharField(source='customer.name', read_only=True) + + class Meta: + model = printing_models.PrintingOrder + fields = [ + 'id', + 'human_id', + 'external_order_id', + 'customer', + 'customer_name', + 'fabric', + 'width', + 'is_urgent', + 'is_invalid', + 'outgoing_date', + 'created_at', + 'updated_at', + ] + read_only_fields = fields + + +class PrintingOrderWithoutSalesOrderView(APIView): + """ + 按客户查询尚未关联非作废销售单的印染订单。 + + 关联路径: + PrintingOrder -> PrintingJob -> SalesOrderItem -> SalesOrder + 已作废 SalesOrder 不计为有效关联。 + """ + + serializer_class = PrintingOrderWithoutSalesOrderSerializer + permission_classes = [permissions.IsAuthenticated, IsPrintingFactory] + + def get(self, request): + customer_id = request.query_params.get('customer_id') + if not customer_id: + return Response({'detail': 'customer_id 为必填参数'}, status=status.HTTP_400_BAD_REQUEST) + + try: + customer_id_int = int(customer_id) + except (TypeError, ValueError): + return Response({'detail': 'customer_id 必须为数字'}, status=status.HTTP_400_BAD_REQUEST) + + merchant = request.user.employee.merchant + active_sales_order_items = business_models.SalesOrderItem.objects.filter( + printing_job__printing_order_id=OuterRef('pk'), + ).exclude( + sales_order__status=business_models.SalesOrderStatusEnum.CANCELLED, + ) + + queryset = ( + printing_models.PrintingOrder.objects + .select_related('customer') + .filter(customer_id=customer_id_int) + .filter(Q(merchant=merchant) | Q(merchant__isnull=True, customer__merchant=merchant)) + .annotate(_has_active_sales_order=Exists(active_sales_order_items)) + .filter(_has_active_sales_order=False) + .order_by('-created_at', '-id') + ) + + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data) + + class PrintingJobBatchAdvancePreviewRequestSerializer(serializers.Serializer): """批量推进:预览/校验请求""" diff --git a/business/models.py b/business/models.py index a541208..121b3e8 100644 --- a/business/models.py +++ b/business/models.py @@ -9,6 +9,13 @@ from flower.common import ModelBase from basic_info import models as basic_info_models +def build_business_human_id(prefix: str, created_at, object_id) -> str: + """生成业务单据的人类可读编号(虚拟编号,不落库)。""" + if object_id is None or created_at is None: + return '' + return f"{prefix}{created_at.year}{created_at.month:02d}{created_at.day:02d}{object_id:06d}" + + class PurchaseOrderKindEnum(models.IntegerChoices): """采购单类型""" WHOLESALE = 1, '大货' @@ -154,9 +161,7 @@ class PurchaseOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounte 规则与 SalesOrder.human_id 保持一致:CG + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return "" - return f"CG{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('CG', self.created_at, self.id) class Meta: verbose_name = '采购单' @@ -309,9 +314,7 @@ class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpa 规则:XS + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return "" - return f"XS{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('XS', self.created_at, self.id) class Meta: verbose_name = '销售单' @@ -505,9 +508,7 @@ class PreSalesOrder(ModelBase): 规则:YS + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return "" - return f"YS{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('YS', self.created_at, self.id) class Meta: verbose_name = '预销售单' @@ -710,9 +711,7 @@ class PrePurchaseOrder(ModelBase): 规则:YC + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return '' - return f"YC{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('YC', self.created_at, self.id) class Meta: verbose_name = '预采购单' @@ -823,9 +822,7 @@ class PurchaseReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, Order 规则与 SalesOrder.human_id 保持一致:CT + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return "" - return f"CT{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('CT', self.created_at, self.id) def get_direction(self) -> int: return -1 @@ -955,9 +952,7 @@ class SalesReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCou 规则与 SalesOrder.human_id 保持一致:XT + YYYYMMDD{6位id} """ - if self.id is None or self.created_at is None: - return "" - return f"XT{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}" + return build_business_human_id('XT', self.created_at, self.id) def get_direction(self) -> int: return 1 @@ -1149,6 +1144,11 @@ class PaymentOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase): def __str__(self): return f'付款单 {self.id} - {self.supplier.name}' + @property + def human_id(self) -> str: + """人类可读编号(虚拟编号,不落库):FK + YYYYMMDD{6位id}。""" + return build_business_human_id('FK', self.created_at, self.id) + @property def settlement_amount(self) -> Decimal: discount = self.discount_amount or Decimal('0') @@ -1235,6 +1235,11 @@ class ReceiptOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase): def __str__(self): return f'收款单 {self.id} - {self.customer.name}' + @property + def human_id(self) -> str: + """人类可读编号(虚拟编号,不落库):SK + YYYYMMDD{6位id}。""" + return build_business_human_id('SK', self.created_at, self.id) + @property def settlement_amount(self) -> Decimal: discount = self.discount_amount or Decimal('0') diff --git a/business/services.py b/business/services.py index 2bab41f..abe1c2f 100644 --- a/business/services.py +++ b/business/services.py @@ -2434,6 +2434,7 @@ class _StatementBuilder: remarks: str | None = '', items: List[dict] | None = None, extra: dict | None = None, + human_id: str | None = None, ) -> dict: items = items or [] record = { @@ -2442,6 +2443,7 @@ class _StatementBuilder: 'source_type': source_type, 'source_label': source_label, 'source_id': source_id, + 'human_id': str(human_id if human_id is not None else source_id), 'occurred_at': occurred_at, 'recorded_at': recorded_at, 'status': status, @@ -2565,6 +2567,7 @@ class _CustomerStatementBuilder(_StatementBuilder): source_type='sales_order', source_label='销售单', source_id=order.id, + human_id=order.human_id, occurred_at=order.sales_date, recorded_at=order.created_at, status=order.status, @@ -2599,6 +2602,7 @@ class _CustomerStatementBuilder(_StatementBuilder): source_type='sales_return_order', source_label='销售退货单', source_id=order.id, + human_id=order.human_id, occurred_at=order.return_date, recorded_at=order.created_at, status=order.status, @@ -2634,6 +2638,7 @@ class _CustomerStatementBuilder(_StatementBuilder): source_type='receipt_order', source_label='收款单', source_id=order.id, + human_id=order.human_id, occurred_at=order.receipt_date, recorded_at=order.created_at, status=order.status, @@ -2674,6 +2679,7 @@ class _CustomerStatementBuilder(_StatementBuilder): source_type=source_type, source_label=source_label, source_id=order.id, + human_id=str(order.id), occurred_at=order.occurred_at, recorded_at=order.recorded_at or order.created_at, status=models.ReceiptOrderStatusEnum.APPROVED, @@ -2721,6 +2727,7 @@ class _SupplierStatementBuilder(_StatementBuilder): source_type='purchase_order', source_label='采购单', source_id=order.id, + human_id=order.human_id, occurred_at=order.purchase_date, recorded_at=order.created_at, status=order.status, @@ -2755,6 +2762,7 @@ class _SupplierStatementBuilder(_StatementBuilder): source_type='purchase_return_order', source_label='采购退货单', source_id=order.id, + human_id=order.human_id, occurred_at=order.return_date, recorded_at=order.created_at, status=order.status, @@ -2787,6 +2795,7 @@ class _SupplierStatementBuilder(_StatementBuilder): source_type='payment_order', source_label='付款单', source_id=order.id, + human_id=order.human_id, occurred_at=order.payment_date, recorded_at=order.created_at, status=order.status, diff --git a/business/tests/test_payment_receipt.py b/business/tests/test_payment_receipt.py index ede7e39..3cbf133 100644 --- a/business/tests/test_payment_receipt.py +++ b/business/tests/test_payment_receipt.py @@ -260,6 +260,7 @@ class PaymentReceiptServiceTestCase(TestCase): ) self.assertFalse(order.is_external_source) self.assertIsNone(order.external_source_id) + self.assertEqual(order.human_id, f'FK{order.created_at:%Y%m%d}{order.id:06d}') order.is_external_source = True order.external_source_id = 'XT20260001' @@ -269,6 +270,7 @@ class PaymentReceiptServiceTestCase(TestCase): serializer = PaymentOrderSerializer(order) self.assertTrue(serializer.data['is_external_source']) self.assertEqual(serializer.data['external_source_id'], 'XT20260001') + self.assertEqual(serializer.data['human_id'], order.human_id) def test_receipt_order_external_source_fields_default_and_serialized(self): order = services.create_receipt_order( @@ -280,6 +282,7 @@ class PaymentReceiptServiceTestCase(TestCase): ) self.assertFalse(order.is_external_source) self.assertIsNone(order.external_source_id) + self.assertEqual(order.human_id, f'SK{order.created_at:%Y%m%d}{order.id:06d}') order.is_external_source = True order.external_source_id = 'SK20225323' @@ -289,3 +292,4 @@ class PaymentReceiptServiceTestCase(TestCase): serializer = ReceiptOrderSerializer(order) self.assertTrue(serializer.data['is_external_source']) self.assertEqual(serializer.data['external_source_id'], 'SK20225323') + self.assertEqual(serializer.data['human_id'], order.human_id) diff --git a/business/tests/test_statement_services.py b/business/tests/test_statement_services.py index c38ea73..cd97763 100644 --- a/business/tests/test_statement_services.py +++ b/business/tests/test_statement_services.py @@ -66,7 +66,7 @@ class BusinessStatementServiceTestCase(TestCase): target_status=business_models.ReceiptOrderStatusEnum.APPROVED, ) - business_models.ExternalCustomerStatementOrder.objects.create( + external_sale_order = business_models.ExternalCustomerStatementOrder.objects.create( merchant=merchant, customer=customer, category=business_models.ExternalCustomerStatementCategoryEnum.SALE, @@ -77,7 +77,7 @@ class BusinessStatementServiceTestCase(TestCase): zk_amount=Decimal('3'), items_payload=[{'product_name': '外部销售'}], ) - business_models.ExternalCustomerStatementOrder.objects.create( + external_return_order = business_models.ExternalCustomerStatementOrder.objects.create( merchant=merchant, customer=customer, category=business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN, @@ -102,11 +102,19 @@ class BusinessStatementServiceTestCase(TestCase): }, ) sales_record = next(record for record in payload['records'] if record['source_type'] == 'sales_order') + self.assertEqual(sales_record['human_id'], sales_order.human_id) self.assertEqual(sales_record['items'][0]['quantity'], Decimal('10')) self.assertEqual(sales_record['items'][0]['quantity_of_rolls'], [6, 4]) + return_record = next(record for record in payload['records'] if record['source_type'] == 'sales_return_order') + self.assertEqual(return_record['human_id'], return_order.human_id) receipt_record = next(record for record in payload['records'] if record['source_type'] == 'receipt_order') + self.assertEqual(receipt_record['human_id'], receipt_order.human_id) self.assertEqual(receipt_record['positive_amount'], Decimal('-2.00')) self.assertEqual(receipt_record['negative_amount'], Decimal('20.00')) + external_sale_record = next(record for record in payload['records'] if record['source_type'] == 'external_sales_order') + self.assertEqual(external_sale_record['human_id'], str(external_sale_order.id)) + external_return_record = next(record for record in payload['records'] if record['source_type'] == 'external_sales_return_order') + self.assertEqual(external_return_record['human_id'], str(external_return_order.id)) summary = services.build_statement_summary(payload) self.assertIn('positive_total', summary) self.assertIn('negative_total', summary) @@ -162,9 +170,13 @@ class BusinessStatementServiceTestCase(TestCase): source_types = {record['source_type'] for record in payload['records']} self.assertEqual(source_types, {'purchase_order', 'purchase_return_order', 'payment_order'}) purchase_record = next(record for record in payload['records'] if record['source_type'] == 'purchase_order') + self.assertEqual(purchase_record['human_id'], purchase_order.human_id) self.assertEqual(purchase_record['items'][0]['quantity'], Decimal('10')) self.assertEqual(purchase_record['items'][0]['num_of_rolls'], 2) + return_record = next(record for record in payload['records'] if record['source_type'] == 'purchase_return_order') + self.assertEqual(return_record['human_id'], return_order.human_id) payment_record = next(record for record in payload['records'] if record['source_type'] == 'payment_order') + self.assertEqual(payment_record['human_id'], payment_order.human_id) self.assertEqual(payment_record['negative_amount'], Decimal('15.00')) diff --git a/flower-app_latest.tar.gz b/flower-app_latest.tar.gz index 8443872..7330098 100644 Binary files a/flower-app_latest.tar.gz and b/flower-app_latest.tar.gz differ diff --git a/flower/middleware.py b/flower/middleware.py index 4c25ae3..9de5c1d 100644 --- a/flower/middleware.py +++ b/flower/middleware.py @@ -1,10 +1,14 @@ """ 自定义中间件模块 """ +import io import json import logging from django.conf import settings +from django.http import QueryDict +from django.http.multipartparser import MultiPartParserError +from django.utils.datastructures import MultiValueDict from rest_framework_simplejwt.authentication import JWTAuthentication logger = logging.getLogger(__name__) diff --git a/printing/admin.py b/printing/admin.py index 7c5b7cc..7da5d0f 100644 --- a/printing/admin.py +++ b/printing/admin.py @@ -528,6 +528,53 @@ class PrintingJobBatchAdvanceRecordAdmin(admin.ModelAdmin): return obj.printing_jobs.count() +@admin.register(models.PrintingOrderExternalSnapshotSyncAudit) +class PrintingOrderExternalSnapshotSyncAuditAdmin(admin.ModelAdmin): + list_display = ( + 'id', + 'external_order_id', + 'printing_order', + 'operator_user', + 'operator_employee', + 'allow_reset_stateflow', + 'is_success', + 'created_at', + 'updated_at', + ) + list_filter = ( + 'is_success', + 'allow_reset_stateflow', + 'created_at', + ) + search_fields = ( + 'external_order_id', + 'printing_order__id', + 'operator_user__username', + 'operator_employee__name', + 'failure_reason', + ) + readonly_fields = ( + 'printing_order', + 'external_order_id', + 'operator_user', + 'operator_employee', + 'before_snapshot', + 'allow_reset_stateflow', + 'is_success', + 'failure_reason', + 'created_at', + 'updated_at', + ) + date_hierarchy = 'created_at' + ordering = ('-created_at', '-id') + + def has_add_permission(self, request): + return False + + def has_change_permission(self, request, obj=None): + return False + + @admin.register(models.PlateOrderTiiaUploadFailure) class PlateOrderTiiaUploadFailureAdmin(admin.ModelAdmin): list_display = ('id', 'run_date', 'plate_order_id', 'attempts', 'last_attempt_at', 'created_at')