forked from erp-dev/erp
fix
This commit is contained in:
@@ -229,3 +229,180 @@ class PreSalesOrderAPITestCase(TestCase):
|
|||||||
self.assertEqual(convert_resp.data['status'], business_models.SalesOrderStatusEnum.PENDING)
|
self.assertEqual(convert_resp.data['status'], business_models.SalesOrderStatusEnum.PENDING)
|
||||||
sales_order = business_models.SalesOrder.objects.get(id=convert_resp.data['id'])
|
sales_order = business_models.SalesOrder.objects.get(id=convert_resp.data['id'])
|
||||||
self.assertEqual(sales_order.from_pre_sales_order_id, order_id)
|
self.assertEqual(sales_order.from_pre_sales_order_id, order_id)
|
||||||
|
|
||||||
|
def test_detail_filters_cancelled_allocation_records(self):
|
||||||
|
"""单体查询应过滤掉已撤销的配货记录"""
|
||||||
|
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
order_id = resp.data['id']
|
||||||
|
item_id = resp.data['items'][0]['id']
|
||||||
|
|
||||||
|
# 创建库存明细
|
||||||
|
stock_record = stock_models.StockChangeRecord.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
type=stock_models.StockChangeTypeEnum.ADD,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||||
|
created_by=self.user,
|
||||||
|
)
|
||||||
|
stock_detail1 = stock_models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
product=self.product,
|
||||||
|
unit=ProductUnitEnum.METER,
|
||||||
|
stock_change_record=stock_record,
|
||||||
|
quantity='10',
|
||||||
|
)
|
||||||
|
stock_detail2 = stock_models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
product=self.product,
|
||||||
|
unit=ProductUnitEnum.METER,
|
||||||
|
stock_change_record=stock_record,
|
||||||
|
quantity='5',
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建两条配货记录
|
||||||
|
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
|
||||||
|
allocation1 = allocation_services.create_allocation_record(
|
||||||
|
item=item,
|
||||||
|
stock_ids=[stock_detail1.id],
|
||||||
|
quantity='10',
|
||||||
|
unit='米',
|
||||||
|
scanned_by=self.employee,
|
||||||
|
)
|
||||||
|
allocation2 = allocation_services.create_allocation_record(
|
||||||
|
item=item,
|
||||||
|
stock_ids=[stock_detail2.id],
|
||||||
|
quantity='5',
|
||||||
|
unit='米',
|
||||||
|
scanned_by=self.employee,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 撤销第一条记录
|
||||||
|
allocation_services.cancel_allocation_record_with_unfreeze(record=allocation1)
|
||||||
|
|
||||||
|
# 查询单体
|
||||||
|
detail_resp = self.client.get(f'/api/v1/pre-sales-orders/{order_id}/')
|
||||||
|
self.assertEqual(detail_resp.status_code, status.HTTP_200_OK)
|
||||||
|
records = detail_resp.data['items'][0]['allocation_records']
|
||||||
|
|
||||||
|
# 应该只包含未撤销的记录
|
||||||
|
self.assertEqual(len(records), 1)
|
||||||
|
self.assertEqual(records[0]['id'], allocation2.id)
|
||||||
|
self.assertEqual(records[0]['status'], business_models.AllocationRecordStatusEnum.ACTIVE)
|
||||||
|
|
||||||
|
def test_list_includes_progress_fields(self):
|
||||||
|
"""列表查询应包含allocated_quantity和progress字段"""
|
||||||
|
# 创建订单
|
||||||
|
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
order_id = resp.data['id']
|
||||||
|
item_id = resp.data['items'][0]['id']
|
||||||
|
|
||||||
|
# 创建库存并配货
|
||||||
|
stock_record = stock_models.StockChangeRecord.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
type=stock_models.StockChangeTypeEnum.ADD,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||||
|
created_by=self.user,
|
||||||
|
)
|
||||||
|
stock_detail = stock_models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
product=self.product,
|
||||||
|
unit=ProductUnitEnum.METER,
|
||||||
|
stock_change_record=stock_record,
|
||||||
|
quantity='12.5',
|
||||||
|
)
|
||||||
|
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
|
||||||
|
allocation_services.create_allocation_record(
|
||||||
|
item=item,
|
||||||
|
stock_ids=[stock_detail.id],
|
||||||
|
quantity='5.0',
|
||||||
|
unit='米',
|
||||||
|
scanned_by=self.employee,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查询列表
|
||||||
|
list_resp = self.client.get('/api/v1/pre-sales-orders/')
|
||||||
|
self.assertEqual(list_resp.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertIn('results', list_resp.data)
|
||||||
|
|
||||||
|
# 查找当前订单
|
||||||
|
order_data = None
|
||||||
|
for order in list_resp.data['results']:
|
||||||
|
if order['id'] == order_id:
|
||||||
|
order_data = order
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertIsNotNone(order_data)
|
||||||
|
# 验证进度字段存在
|
||||||
|
self.assertIn('allocated_quantity', order_data)
|
||||||
|
self.assertIn('progress', order_data)
|
||||||
|
# 需求量12.5,已配货5.0
|
||||||
|
self.assertEqual(order_data['allocated_quantity'], '5.00')
|
||||||
|
# 进度 = 5.0 / 12.5 = 0.4
|
||||||
|
self.assertEqual(order_data['progress'], '0.4000')
|
||||||
|
|
||||||
|
def test_list_progress_excludes_cancelled_allocations(self):
|
||||||
|
"""列表查询的进度字段应排除已撤销的配货记录"""
|
||||||
|
resp = self.client.post('/api/v1/pre-sales-orders/', self.payload, format='json')
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
order_id = resp.data['id']
|
||||||
|
item_id = resp.data['items'][0]['id']
|
||||||
|
|
||||||
|
# 创建库存
|
||||||
|
stock_record = stock_models.StockChangeRecord.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
type=stock_models.StockChangeTypeEnum.ADD,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||||
|
created_by=self.user,
|
||||||
|
)
|
||||||
|
stock_detail1 = stock_models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
product=self.product,
|
||||||
|
unit=ProductUnitEnum.METER,
|
||||||
|
stock_change_record=stock_record,
|
||||||
|
quantity='10',
|
||||||
|
)
|
||||||
|
stock_detail2 = stock_models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
|
product=self.product,
|
||||||
|
unit=ProductUnitEnum.METER,
|
||||||
|
stock_change_record=stock_record,
|
||||||
|
quantity='5',
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建两条配货,然后撤销一条
|
||||||
|
item = business_models.PreSalesOrderItem.objects.get(id=item_id)
|
||||||
|
allocation1 = allocation_services.create_allocation_record(
|
||||||
|
item=item,
|
||||||
|
stock_ids=[stock_detail1.id],
|
||||||
|
quantity='10',
|
||||||
|
unit='米',
|
||||||
|
scanned_by=self.employee,
|
||||||
|
)
|
||||||
|
allocation2 = allocation_services.create_allocation_record(
|
||||||
|
item=item,
|
||||||
|
stock_ids=[stock_detail2.id],
|
||||||
|
quantity='2.5',
|
||||||
|
unit='米',
|
||||||
|
scanned_by=self.employee,
|
||||||
|
)
|
||||||
|
allocation_services.cancel_allocation_record_with_unfreeze(record=allocation1)
|
||||||
|
|
||||||
|
# 查询列表
|
||||||
|
list_resp = self.client.get('/api/v1/pre-sales-orders/')
|
||||||
|
self.assertEqual(list_resp.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
order_data = None
|
||||||
|
for order in list_resp.data['results']:
|
||||||
|
if order['id'] == order_id:
|
||||||
|
order_data = order
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertIsNotNone(order_data)
|
||||||
|
# 应该只统计未撤销的配货(2.5),而不是10+2.5
|
||||||
|
self.assertEqual(order_data['allocated_quantity'], '2.50')
|
||||||
|
# 进度 = 2.5 / 12.5 = 0.2
|
||||||
|
self.assertEqual(order_data['progress'], '0.2000')
|
||||||
|
|||||||
@@ -122,13 +122,20 @@ class PreSalesOrderItemSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class PreSalesOrderItemDetailSerializer(PreSalesOrderItemSerializer):
|
class PreSalesOrderItemDetailSerializer(PreSalesOrderItemSerializer):
|
||||||
allocation_records = AllocationRecordSerializer(many=True, read_only=True)
|
allocation_records = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta(PreSalesOrderItemSerializer.Meta):
|
class Meta(PreSalesOrderItemSerializer.Meta):
|
||||||
fields = PreSalesOrderItemSerializer.Meta.fields + [
|
fields = PreSalesOrderItemSerializer.Meta.fields + [
|
||||||
'allocation_records',
|
'allocation_records',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_allocation_records(self, obj: business_models.PreSalesOrderItem):
|
||||||
|
"""返回非已撤销的配货记录"""
|
||||||
|
active_records = obj.allocation_records.exclude(
|
||||||
|
status=business_models.AllocationRecordStatusEnum.CANCELLED
|
||||||
|
)
|
||||||
|
return AllocationRecordSerializer(active_records, many=True).data
|
||||||
|
|
||||||
|
|
||||||
class PreSalesOrderSerializer(serializers.ModelSerializer):
|
class PreSalesOrderSerializer(serializers.ModelSerializer):
|
||||||
human_id = serializers.CharField(read_only=True)
|
human_id = serializers.CharField(read_only=True)
|
||||||
@@ -136,6 +143,8 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
|||||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||||
items = PreSalesOrderItemSerializer(many=True, read_only=True)
|
items = PreSalesOrderItemSerializer(many=True, read_only=True)
|
||||||
|
allocated_quantity = serializers.SerializerMethodField(read_only=True)
|
||||||
|
progress = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = business_models.PreSalesOrder
|
model = business_models.PreSalesOrder
|
||||||
@@ -153,6 +162,8 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
|||||||
'remarks',
|
'remarks',
|
||||||
'created_at',
|
'created_at',
|
||||||
'items',
|
'items',
|
||||||
|
'allocated_quantity',
|
||||||
|
'progress',
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'id',
|
'id',
|
||||||
@@ -162,8 +173,34 @@ class PreSalesOrderSerializer(serializers.ModelSerializer):
|
|||||||
'warehouse_name',
|
'warehouse_name',
|
||||||
'operator_name',
|
'operator_name',
|
||||||
'items',
|
'items',
|
||||||
|
'allocated_quantity',
|
||||||
|
'progress',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_allocated_quantity(self, obj: business_models.PreSalesOrder) -> str:
|
||||||
|
"""计算整单已配货总量"""
|
||||||
|
total = Decimal('0')
|
||||||
|
for item in obj.items.all():
|
||||||
|
result = item.allocation_records.filter(
|
||||||
|
status=business_models.AllocationRecordStatusEnum.ACTIVE
|
||||||
|
).aggregate(total=Sum('quantity'))
|
||||||
|
total += result.get('total') or Decimal('0')
|
||||||
|
return str(total)
|
||||||
|
|
||||||
|
def get_progress(self, obj: business_models.PreSalesOrder) -> str:
|
||||||
|
"""计算整单配货进度(已配货总量/总需求量)"""
|
||||||
|
required_total = Decimal('0')
|
||||||
|
allocated_total = Decimal('0')
|
||||||
|
for item in obj.items.all():
|
||||||
|
required_total += item.quantity or Decimal('0')
|
||||||
|
result = item.allocation_records.filter(
|
||||||
|
status=business_models.AllocationRecordStatusEnum.ACTIVE
|
||||||
|
).aggregate(total=Sum('quantity'))
|
||||||
|
allocated_total += result.get('total') or Decimal('0')
|
||||||
|
if required_total <= 0:
|
||||||
|
return '0'
|
||||||
|
return str((allocated_total / required_total).quantize(Decimal('0.0001')))
|
||||||
|
|
||||||
|
|
||||||
class PreSalesOrderDetailSerializer(PreSalesOrderSerializer):
|
class PreSalesOrderDetailSerializer(PreSalesOrderSerializer):
|
||||||
items = PreSalesOrderItemDetailSerializer(many=True, read_only=True)
|
items = PreSalesOrderItemDetailSerializer(many=True, read_only=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user