1
0
forked from erp-dev/erp

feat: human_id for all objects in business module

This commit is contained in:
2026-06-25 13:41:17 +08:00
parent 70b3a0d246
commit e8bf4b5e49
14 changed files with 305 additions and 22 deletions

View File

@@ -12,12 +12,13 @@ class PaymentOrderSerializer(serializers.ModelSerializer):
supplier_name = serializers.CharField(source='supplier.name', read_only=True) supplier_name = serializers.CharField(source='supplier.name', read_only=True)
operator_name = serializers.CharField(source='operator.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) bank_account_name = serializers.CharField(source='bank_account.name', read_only=True)
human_id = serializers.CharField(read_only=True)
settlement_amount = serializers.SerializerMethodField() settlement_amount = serializers.SerializerMethodField()
class Meta: class Meta:
model = business_models.PaymentOrder model = business_models.PaymentOrder
fields = [ 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', 'payment_date', 'amount', 'discount_amount', 'settlement_amount',
'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id', 'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id',
'is_red_flushed', 'red_flush_id', 'red_flushed_at', 'is_red_flushed', 'red_flush_id', 'red_flushed_at',
@@ -25,6 +26,7 @@ class PaymentOrderSerializer(serializers.ModelSerializer):
] ]
read_only_fields = [ read_only_fields = [
'id', 'id',
'human_id',
'supplier_name', 'supplier_name',
'operator_name', 'operator_name',
'bank_account_name', 'bank_account_name',

View File

@@ -12,12 +12,13 @@ class ReceiptOrderSerializer(serializers.ModelSerializer):
customer_name = serializers.CharField(source='customer.name', read_only=True) customer_name = serializers.CharField(source='customer.name', read_only=True)
operator_name = serializers.CharField(source='operator.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) bank_account_name = serializers.CharField(source='bank_account.name', read_only=True)
human_id = serializers.CharField(read_only=True)
settlement_amount = serializers.SerializerMethodField() settlement_amount = serializers.SerializerMethodField()
class Meta: class Meta:
model = business_models.ReceiptOrder model = business_models.ReceiptOrder
fields = [ 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', 'receipt_date', 'amount', 'discount_amount', 'settlement_amount',
'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id', 'operator', 'operator_name', 'status', 'is_external_source', 'external_source_id',
'is_red_flushed', 'red_flush_id', 'red_flushed_at', 'is_red_flushed', 'red_flush_id', 'red_flushed_at',
@@ -25,6 +26,7 @@ class ReceiptOrderSerializer(serializers.ModelSerializer):
] ]
read_only_fields = [ read_only_fields = [
'id', 'id',
'human_id',
'customer_name', 'customer_name',
'operator_name', 'operator_name',
'bank_account_name', 'bank_account_name',

View File

@@ -19,6 +19,7 @@ class StatementRecordSerializer(serializers.Serializer):
source_type = serializers.CharField() source_type = serializers.CharField()
source_label = serializers.CharField() source_label = serializers.CharField()
source_id = serializers.IntegerField() source_id = serializers.IntegerField()
human_id = serializers.CharField()
occurred_at = serializers.DateField() occurred_at = serializers.DateField()
recorded_at = serializers.DateTimeField() recorded_at = serializers.DateTimeField()
status = serializers.IntegerField() status = serializers.IntegerField()

View File

@@ -282,6 +282,131 @@ class PrintingJobByCustomerAPITest(TestCase):
self.assertEqual(data['billed_quantity'], '20.00') 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') @override_settings(AGENT_ACCESS_KEY='agent-test-key')
class AgentUnshippedShipmentListAPITest(TestCase): class AgentUnshippedShipmentListAPITest(TestCase):
def setUp(self): def setUp(self):

View File

@@ -6,6 +6,7 @@ from api_v2.views import (
QuickCreateEmployeeUserView, QuickCreateEmployeeUserView,
RoleListView, RoleListView,
PrintingJobByCustomerView, PrintingJobByCustomerView,
PrintingOrderWithoutSalesOrderView,
PrintingJobBatchAdvancePreviewView, PrintingJobBatchAdvancePreviewView,
PrintingJobBatchAdvanceSubmitView, PrintingJobBatchAdvanceSubmitView,
PrintingJobBatchAddParametersView, PrintingJobBatchAddParametersView,
@@ -57,6 +58,7 @@ urlpatterns = [
path('customers/bind-employee/', CustomerEmployeeBindingView.as_view(), name='api_v2_customer_bind_employee'), 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('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-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/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-jobs/batch-add-parameters/', PrintingJobBatchAddParametersView.as_view(), name='api_v2_printing_job_batch_add_parameters'), path('printing-jobs/batch-add-parameters/', PrintingJobBatchAddParametersView.as_view(), name='api_v2_printing_job_batch_add_parameters'),

View File

@@ -7,6 +7,7 @@ from .users import QuickCreateEmployeeUserView, RoleListView
from .printing import ( from .printing import (
PrintingJobByCustomerView, PrintingJobByCustomerView,
PrintingJobV2Serializer, PrintingJobV2Serializer,
PrintingOrderWithoutSalesOrderView,
PrintingJobBatchAdvancePreviewView, PrintingJobBatchAdvancePreviewView,
PrintingJobBatchAdvanceSubmitView, PrintingJobBatchAdvanceSubmitView,
PrintingJobBatchAddParametersView, PrintingJobBatchAddParametersView,
@@ -62,6 +63,7 @@ __all__ = [
'RoleListView', 'RoleListView',
'PrintingJobByCustomerView', 'PrintingJobByCustomerView',
'PrintingJobV2Serializer', 'PrintingJobV2Serializer',
'PrintingOrderWithoutSalesOrderView',
'PrintingJobBatchAdvancePreviewView', 'PrintingJobBatchAdvancePreviewView',
'PrintingJobBatchAdvanceSubmitView', 'PrintingJobBatchAdvanceSubmitView',
'PrintingJobBatchAddParametersView', 'PrintingJobBatchAddParametersView',

View File

@@ -12,6 +12,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from basic_info import models as basic_models from basic_info import models as basic_models
from business import models as business_models
from printing import models as printing_models from printing import models as printing_models
from api_v1.tasks import ( from api_v1.tasks import (
ExternalPrintingOrderSnapshotSyncError, ExternalPrintingOrderSnapshotSyncError,
@@ -180,6 +181,73 @@ class PrintingJobByCustomerView(APIView):
return Response(serializer.data) 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): class PrintingJobBatchAdvancePreviewRequestSerializer(serializers.Serializer):
"""批量推进:预览/校验请求""" """批量推进:预览/校验请求"""

View File

@@ -9,6 +9,13 @@ from flower.common import ModelBase
from basic_info import models as basic_info_models 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): class PurchaseOrderKindEnum(models.IntegerChoices):
"""采购单类型""" """采购单类型"""
WHOLESALE = 1, '大货' WHOLESALE = 1, '大货'
@@ -154,9 +161,7 @@ class PurchaseOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounte
规则与 SalesOrder.human_id 保持一致CG + YYYYMMDD{6位id} 规则与 SalesOrder.human_id 保持一致CG + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('CG', self.created_at, self.id)
return ""
return f"CG{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
class Meta: class Meta:
verbose_name = '采购单' verbose_name = '采购单'
@@ -309,9 +314,7 @@ class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpa
规则XS + YYYYMMDD{6位id} 规则XS + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('XS', self.created_at, self.id)
return ""
return f"XS{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
class Meta: class Meta:
verbose_name = '销售单' verbose_name = '销售单'
@@ -505,9 +508,7 @@ class PreSalesOrder(ModelBase):
规则YS + YYYYMMDD{6位id} 规则YS + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('YS', self.created_at, self.id)
return ""
return f"YS{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
class Meta: class Meta:
verbose_name = '预销售单' verbose_name = '预销售单'
@@ -710,9 +711,7 @@ class PrePurchaseOrder(ModelBase):
规则YC + YYYYMMDD{6位id} 规则YC + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('YC', self.created_at, self.id)
return ''
return f"YC{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
class Meta: class Meta:
verbose_name = '预采购单' verbose_name = '预采购单'
@@ -823,9 +822,7 @@ class PurchaseReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, Order
规则与 SalesOrder.human_id 保持一致CT + YYYYMMDD{6位id} 规则与 SalesOrder.human_id 保持一致CT + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('CT', self.created_at, self.id)
return ""
return f"CT{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
def get_direction(self) -> int: def get_direction(self) -> int:
return -1 return -1
@@ -955,9 +952,7 @@ class SalesReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCou
规则与 SalesOrder.human_id 保持一致XT + YYYYMMDD{6位id} 规则与 SalesOrder.human_id 保持一致XT + YYYYMMDD{6位id}
""" """
if self.id is None or self.created_at is None: return build_business_human_id('XT', self.created_at, self.id)
return ""
return f"XT{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
def get_direction(self) -> int: def get_direction(self) -> int:
return 1 return 1
@@ -1149,6 +1144,11 @@ class PaymentOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
def __str__(self): def __str__(self):
return f'付款单 {self.id} - {self.supplier.name}' 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 @property
def settlement_amount(self) -> Decimal: def settlement_amount(self) -> Decimal:
discount = self.discount_amount or Decimal('0') discount = self.discount_amount or Decimal('0')
@@ -1235,6 +1235,11 @@ class ReceiptOrder(OrderDirectionMixin, OrderCounterpartyMixin, ModelBase):
def __str__(self): def __str__(self):
return f'收款单 {self.id} - {self.customer.name}' 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 @property
def settlement_amount(self) -> Decimal: def settlement_amount(self) -> Decimal:
discount = self.discount_amount or Decimal('0') discount = self.discount_amount or Decimal('0')

View File

@@ -2434,6 +2434,7 @@ class _StatementBuilder:
remarks: str | None = '', remarks: str | None = '',
items: List[dict] | None = None, items: List[dict] | None = None,
extra: dict | None = None, extra: dict | None = None,
human_id: str | None = None,
) -> dict: ) -> dict:
items = items or [] items = items or []
record = { record = {
@@ -2442,6 +2443,7 @@ class _StatementBuilder:
'source_type': source_type, 'source_type': source_type,
'source_label': source_label, 'source_label': source_label,
'source_id': source_id, 'source_id': source_id,
'human_id': str(human_id if human_id is not None else source_id),
'occurred_at': occurred_at, 'occurred_at': occurred_at,
'recorded_at': recorded_at, 'recorded_at': recorded_at,
'status': status, 'status': status,
@@ -2565,6 +2567,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
source_type='sales_order', source_type='sales_order',
source_label='销售单', source_label='销售单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.sales_date, occurred_at=order.sales_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,
@@ -2599,6 +2602,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
source_type='sales_return_order', source_type='sales_return_order',
source_label='销售退货单', source_label='销售退货单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.return_date, occurred_at=order.return_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,
@@ -2634,6 +2638,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
source_type='receipt_order', source_type='receipt_order',
source_label='收款单', source_label='收款单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.receipt_date, occurred_at=order.receipt_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,
@@ -2674,6 +2679,7 @@ class _CustomerStatementBuilder(_StatementBuilder):
source_type=source_type, source_type=source_type,
source_label=source_label, source_label=source_label,
source_id=order.id, source_id=order.id,
human_id=str(order.id),
occurred_at=order.occurred_at, occurred_at=order.occurred_at,
recorded_at=order.recorded_at or order.created_at, recorded_at=order.recorded_at or order.created_at,
status=models.ReceiptOrderStatusEnum.APPROVED, status=models.ReceiptOrderStatusEnum.APPROVED,
@@ -2721,6 +2727,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
source_type='purchase_order', source_type='purchase_order',
source_label='采购单', source_label='采购单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.purchase_date, occurred_at=order.purchase_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,
@@ -2755,6 +2762,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
source_type='purchase_return_order', source_type='purchase_return_order',
source_label='采购退货单', source_label='采购退货单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.return_date, occurred_at=order.return_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,
@@ -2787,6 +2795,7 @@ class _SupplierStatementBuilder(_StatementBuilder):
source_type='payment_order', source_type='payment_order',
source_label='付款单', source_label='付款单',
source_id=order.id, source_id=order.id,
human_id=order.human_id,
occurred_at=order.payment_date, occurred_at=order.payment_date,
recorded_at=order.created_at, recorded_at=order.created_at,
status=order.status, status=order.status,

View File

@@ -260,6 +260,7 @@ class PaymentReceiptServiceTestCase(TestCase):
) )
self.assertFalse(order.is_external_source) self.assertFalse(order.is_external_source)
self.assertIsNone(order.external_source_id) 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.is_external_source = True
order.external_source_id = 'XT20260001' order.external_source_id = 'XT20260001'
@@ -269,6 +270,7 @@ class PaymentReceiptServiceTestCase(TestCase):
serializer = PaymentOrderSerializer(order) serializer = PaymentOrderSerializer(order)
self.assertTrue(serializer.data['is_external_source']) self.assertTrue(serializer.data['is_external_source'])
self.assertEqual(serializer.data['external_source_id'], 'XT20260001') 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): def test_receipt_order_external_source_fields_default_and_serialized(self):
order = services.create_receipt_order( order = services.create_receipt_order(
@@ -280,6 +282,7 @@ class PaymentReceiptServiceTestCase(TestCase):
) )
self.assertFalse(order.is_external_source) self.assertFalse(order.is_external_source)
self.assertIsNone(order.external_source_id) 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.is_external_source = True
order.external_source_id = 'SK20225323' order.external_source_id = 'SK20225323'
@@ -289,3 +292,4 @@ class PaymentReceiptServiceTestCase(TestCase):
serializer = ReceiptOrderSerializer(order) serializer = ReceiptOrderSerializer(order)
self.assertTrue(serializer.data['is_external_source']) self.assertTrue(serializer.data['is_external_source'])
self.assertEqual(serializer.data['external_source_id'], 'SK20225323') self.assertEqual(serializer.data['external_source_id'], 'SK20225323')
self.assertEqual(serializer.data['human_id'], order.human_id)

View File

@@ -66,7 +66,7 @@ class BusinessStatementServiceTestCase(TestCase):
target_status=business_models.ReceiptOrderStatusEnum.APPROVED, target_status=business_models.ReceiptOrderStatusEnum.APPROVED,
) )
business_models.ExternalCustomerStatementOrder.objects.create( external_sale_order = business_models.ExternalCustomerStatementOrder.objects.create(
merchant=merchant, merchant=merchant,
customer=customer, customer=customer,
category=business_models.ExternalCustomerStatementCategoryEnum.SALE, category=business_models.ExternalCustomerStatementCategoryEnum.SALE,
@@ -77,7 +77,7 @@ class BusinessStatementServiceTestCase(TestCase):
zk_amount=Decimal('3'), zk_amount=Decimal('3'),
items_payload=[{'product_name': '外部销售'}], items_payload=[{'product_name': '外部销售'}],
) )
business_models.ExternalCustomerStatementOrder.objects.create( external_return_order = business_models.ExternalCustomerStatementOrder.objects.create(
merchant=merchant, merchant=merchant,
customer=customer, customer=customer,
category=business_models.ExternalCustomerStatementCategoryEnum.SALE_RETURN, 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') 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'], Decimal('10'))
self.assertEqual(sales_record['items'][0]['quantity_of_rolls'], [6, 4]) 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') 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['positive_amount'], Decimal('-2.00'))
self.assertEqual(receipt_record['negative_amount'], Decimal('20.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) summary = services.build_statement_summary(payload)
self.assertIn('positive_total', summary) self.assertIn('positive_total', summary)
self.assertIn('negative_total', summary) self.assertIn('negative_total', summary)
@@ -162,9 +170,13 @@ class BusinessStatementServiceTestCase(TestCase):
source_types = {record['source_type'] for record in payload['records']} source_types = {record['source_type'] for record in payload['records']}
self.assertEqual(source_types, {'purchase_order', 'purchase_return_order', 'payment_order'}) 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') 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]['quantity'], Decimal('10'))
self.assertEqual(purchase_record['items'][0]['num_of_rolls'], 2) 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') 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')) self.assertEqual(payment_record['negative_amount'], Decimal('15.00'))

Binary file not shown.

View File

@@ -1,10 +1,14 @@
""" """
自定义中间件模块 自定义中间件模块
""" """
import io
import json import json
import logging import logging
from django.conf import settings 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 from rest_framework_simplejwt.authentication import JWTAuthentication
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -528,6 +528,53 @@ class PrintingJobBatchAdvanceRecordAdmin(admin.ModelAdmin):
return obj.printing_jobs.count() 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) @admin.register(models.PlateOrderTiiaUploadFailure)
class PlateOrderTiiaUploadFailureAdmin(admin.ModelAdmin): class PlateOrderTiiaUploadFailureAdmin(admin.ModelAdmin):
list_display = ('id', 'run_date', 'plate_order_id', 'attempts', 'last_attempt_at', 'created_at') list_display = ('id', 'run_date', 'plate_order_id', 'attempts', 'last_attempt_at', 'created_at')