forked from erp-dev/erp
feat: human_id for all objects in business module
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user