forked from erp-dev/erp
feat: all the business models supported human_id now but it wont change statements api. Added new field 'order_quantity' into sales_order model (integer, allow null).
This commit is contained in:
@@ -220,8 +220,12 @@ class PurchaseOrderAPITestCase(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('id', response.data)
|
||||
self.assertIn('human_id', response.data)
|
||||
self.assertEqual(response.data['status'], 1)
|
||||
self.assertIn('等待审批', response.data['message'])
|
||||
order = business_models.PurchaseOrder.objects.get(id=response.data['id'])
|
||||
expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}"
|
||||
self.assertEqual(response.data['human_id'], expected_human_id)
|
||||
mock_delay.assert_not_called()
|
||||
|
||||
def test_create_purchase_order_invalid_supplier(self):
|
||||
@@ -239,7 +243,11 @@ class PurchaseOrderAPITestCase(TestCase):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.relaxed_payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('human_id', response.data)
|
||||
self.assertEqual(response.data['status'], 1)
|
||||
order = business_models.PurchaseOrder.objects.get(id=response.data['id'])
|
||||
expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}"
|
||||
self.assertEqual(response.data['human_id'], expected_human_id)
|
||||
mock_delay.assert_not_called()
|
||||
|
||||
def test_mode_mismatch_raises(self):
|
||||
@@ -652,7 +660,11 @@ class PurchaseReturnOrderAPITestCase(TestCase):
|
||||
response = self.client.post('/api/v1/purchase-return-orders/', self.strict_payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('id', response.data)
|
||||
self.assertIn('human_id', response.data)
|
||||
self.assertEqual(response.data['status'], business_models.PurchaseReturnStatusEnum.PENDING)
|
||||
order = business_models.PurchaseReturnOrder.objects.get(id=response.data['id'])
|
||||
expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}"
|
||||
self.assertEqual(response.data['human_id'], expected_human_id)
|
||||
|
||||
def test_create_purchase_return_requires_consume_ids_in_strict_out(self):
|
||||
payload = copy.deepcopy(self.strict_out_payload)
|
||||
@@ -802,7 +814,11 @@ class SalesReturnOrderAPITestCase(TestCase):
|
||||
def test_create_sales_return_order_success(self):
|
||||
response = self.client.post('/api/v1/sales-return-orders/', self.strict_payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('human_id', response.data)
|
||||
self.assertEqual(response.data['status'], business_models.SalesReturnStatusEnum.PENDING)
|
||||
order = business_models.SalesReturnOrder.objects.get(id=response.data['id'])
|
||||
expected_human_id = f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}"
|
||||
self.assertEqual(response.data['human_id'], expected_human_id)
|
||||
|
||||
def test_create_sales_return_order_invalid_customer(self):
|
||||
payload = {**self.strict_payload, 'customer': 999}
|
||||
|
||||
@@ -28,6 +28,7 @@ class PurchaseOrderSerializer(serializers.ModelSerializer):
|
||||
diff_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
total_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
human_id = serializers.CharField(read_only=True)
|
||||
|
||||
def get_total_amount(self, obj: business_models.PurchaseOrder):
|
||||
return obj.get_total_amount()
|
||||
@@ -49,7 +50,7 @@ class PurchaseOrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = business_models.PurchaseOrder
|
||||
fields = [
|
||||
'id', 'supplier', 'supplier_name', 'purchase_date', 'kind',
|
||||
'id', 'human_id', 'supplier', 'supplier_name', 'purchase_date', 'kind',
|
||||
'total_amount', 'diff_quantity', 'total_quantity',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
@@ -133,6 +134,7 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response(
|
||||
{
|
||||
'id': purchase_order.id,
|
||||
'human_id': purchase_order.human_id,
|
||||
'status': purchase_order.status,
|
||||
'message': '采购单创建成功,等待审批',
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ class PurchaseReturnOrderSerializer(serializers.ModelSerializer):
|
||||
diff_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
total_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
human_id = serializers.CharField(read_only=True)
|
||||
supplier_name = serializers.CharField(source='supplier.name', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
@@ -53,7 +54,7 @@ class PurchaseReturnOrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = business_models.PurchaseReturnOrder
|
||||
fields = [
|
||||
'id', 'supplier', 'supplier_name', 'purchase_order', 'return_date',
|
||||
'id', 'human_id', 'supplier', 'supplier_name', 'purchase_order', 'return_date',
|
||||
'total_amount', 'diff_quantity', 'total_quantity',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
@@ -140,6 +141,7 @@ class PurchaseReturnOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response(
|
||||
{
|
||||
'id': purchase_return.id,
|
||||
'human_id': purchase_return.human_id,
|
||||
'status': purchase_return.status,
|
||||
'message': '采购退货单创建成功,等待审批',
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@ class SalesOrderSerializer(serializers.ModelSerializer):
|
||||
'id', 'human_id', 'customer', 'customer_name', 'sales_date', 'kind',
|
||||
'total_amount', 'diff_quantity', 'total_quantity',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
'status', 'order_quantity', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
'quantity_of_rolls',
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'items', 'customer_name', 'operator_name', 'warehouse_name']
|
||||
|
||||
@@ -32,6 +32,7 @@ class SalesReturnOrderSerializer(serializers.ModelSerializer):
|
||||
diff_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
total_quantity = serializers.SerializerMethodField(read_only=True)
|
||||
quantity_of_rolls = serializers.SerializerMethodField(read_only=True)
|
||||
human_id = serializers.CharField(read_only=True)
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
@@ -52,7 +53,7 @@ class SalesReturnOrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = business_models.SalesReturnOrder
|
||||
fields = [
|
||||
'id', 'customer', 'customer_name', 'sales_order', 'return_date',
|
||||
'id', 'human_id', 'customer', 'customer_name', 'sales_order', 'return_date',
|
||||
'total_amount', 'diff_quantity', 'total_quantity', 'quantity_of_rolls',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
@@ -138,6 +139,7 @@ class SalesReturnOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response(
|
||||
{
|
||||
'id': sales_return.id,
|
||||
'human_id': sales_return.human_id,
|
||||
'status': sales_return.status,
|
||||
'message': '销售退货单创建成功,等待审批',
|
||||
},
|
||||
|
||||
@@ -127,7 +127,7 @@ class SalesReturnOrderItemAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(models.SalesOrder)
|
||||
class SalesOrderAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'human_id', 'customer', 'sales_date', 'operator', 'status', '_total_amount', '_diff_quantity', '_total_quantity', 'created_at')
|
||||
list_display = ('id', 'human_id', 'customer', 'sales_date', 'operator', 'status', 'order_quantity', '_total_amount', '_diff_quantity', '_total_quantity', 'created_at')
|
||||
search_fields = ('customer__name',)
|
||||
list_filter = ('operator', 'warehouse', 'status', 'created_at')
|
||||
ordering = ('-created_at',)
|
||||
|
||||
18
business/migrations/0020_salesorder_order_quantity.py
Normal file
18
business/migrations/0020_salesorder_order_quantity.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-01-30 10:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('business', '0019_alter_salesorderitem_quantity_of_rolls'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='salesorder',
|
||||
name='order_quantity',
|
||||
field=models.IntegerField(blank=True, null=True, verbose_name='下单数量'),
|
||||
),
|
||||
]
|
||||
@@ -133,6 +133,17 @@ class PurchaseOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounte
|
||||
def __str__(self):
|
||||
return f'采购订单 {self.id} - {self.supplier.name}'
|
||||
|
||||
@property
|
||||
def human_id(self) -> str:
|
||||
"""
|
||||
人类可读编号(虚拟编号,不落库)。
|
||||
|
||||
规则与 printing.PrintingOrder.human_id / SalesOrder.human_id 保持一致:YYYYMMDD{6位id}
|
||||
"""
|
||||
if self.id is None or self.created_at is None:
|
||||
return ""
|
||||
return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = '采购单'
|
||||
verbose_name_plural = '采购单'
|
||||
@@ -252,6 +263,7 @@ class SalesOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCounterpa
|
||||
default=SalesOrderStatusEnum.PENDING,
|
||||
verbose_name='状态',
|
||||
)
|
||||
order_quantity = models.IntegerField(null=True, blank=True, verbose_name='下单数量')
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||
|
||||
def __str__(self):
|
||||
@@ -405,6 +417,17 @@ class PurchaseReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, Order
|
||||
def __str__(self):
|
||||
return f'采购退货 {self.id} - {self.supplier.name}'
|
||||
|
||||
@property
|
||||
def human_id(self) -> str:
|
||||
"""
|
||||
人类可读编号(虚拟编号,不落库)。
|
||||
|
||||
规则与 printing.PrintingOrder.human_id / SalesOrder.human_id 保持一致:YYYYMMDD{6位id}
|
||||
"""
|
||||
if self.id is None or self.created_at is None:
|
||||
return ""
|
||||
return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
|
||||
|
||||
def get_direction(self) -> int:
|
||||
return -1
|
||||
|
||||
@@ -519,6 +542,17 @@ class SalesReturnOrder(OrderItemsAggregationMixin, OrderDirectionMixin, OrderCou
|
||||
def __str__(self):
|
||||
return f'销售退货 {self.id} - {self.customer.name}'
|
||||
|
||||
@property
|
||||
def human_id(self) -> str:
|
||||
"""
|
||||
人类可读编号(虚拟编号,不落库)。
|
||||
|
||||
规则与 printing.PrintingOrder.human_id / SalesOrder.human_id 保持一致:YYYYMMDD{6位id}
|
||||
"""
|
||||
if self.id is None or self.created_at is None:
|
||||
return ""
|
||||
return f"{self.created_at.year}{self.created_at.month:02d}{self.created_at.day:02d}{self.id:06d}"
|
||||
|
||||
def get_direction(self) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
@@ -150,6 +150,22 @@ class PurchaseOrderServiceTestCase(TestCase):
|
||||
self.assertEqual(item.split_quantity_of_rolls(), [10, 5])
|
||||
mock_delay.assert_not_called()
|
||||
|
||||
def test_purchase_order_human_id_generation(self):
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=self.strict_items,
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIsNotNone(purchase_order.human_id)
|
||||
self.assertEqual(
|
||||
purchase_order.human_id,
|
||||
f"{purchase_order.created_at.strftime('%Y%m%d')}{purchase_order.id:06d}",
|
||||
)
|
||||
|
||||
def test_review_purchase_order_approval_triggers_task(self):
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
@@ -605,6 +621,22 @@ class PurchaseReturnServiceTestCase(TestCase):
|
||||
self.assertEqual(item.quantity_of_rolls, '4,2')
|
||||
self.assertEqual(item.split_quantity_of_rolls(), [4, 2])
|
||||
|
||||
def test_purchase_return_order_human_id_generation(self):
|
||||
order = services.create_purchase_return_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
return_date=timezone.now().date(),
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=self.strict_items,
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIsNotNone(order.human_id)
|
||||
self.assertEqual(
|
||||
order.human_id,
|
||||
f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}",
|
||||
)
|
||||
|
||||
def test_review_purchase_return_order_triggers_task_and_balance(self):
|
||||
order = services.create_purchase_return_order(
|
||||
merchant=self.merchant,
|
||||
@@ -744,6 +776,22 @@ class SalesReturnServiceTestCase(TestCase):
|
||||
item = order.items.first()
|
||||
self.assertEqual(item.split_quantity_of_rolls(), [])
|
||||
|
||||
def test_sales_return_order_human_id_generation(self):
|
||||
order = services.create_sales_return_order(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
return_date=timezone.now().date(),
|
||||
warehouse=self.warehouse_relaxed,
|
||||
operator=self.operator,
|
||||
items=[{'product_id': self.product.id, 'quantity': 50, 'num_of_rolls': 2, 'price': '19.5'}],
|
||||
created_by=self.user,
|
||||
)
|
||||
self.assertIsNotNone(order.human_id)
|
||||
self.assertEqual(
|
||||
order.human_id,
|
||||
f"{order.created_at.strftime('%Y%m%d')}{order.id:06d}",
|
||||
)
|
||||
|
||||
def test_review_sales_return_order_updates_balance(self):
|
||||
order = services.create_sales_return_order(
|
||||
merchant=self.merchant,
|
||||
|
||||
Reference in New Issue
Block a user