forked from erp-dev/erp
fix: rebuild stock change record
This commit is contained in:
426
stock/tests.py
426
stock/tests.py
@@ -1,3 +1,427 @@
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from decimal import Decimal
|
||||
from unittest.mock import patch, MagicMock
|
||||
import logging
|
||||
|
||||
# Create your tests here.
|
||||
from basic_info.models import Product, WareHouse, Supplier, ProductUnitEnum, ProductCategory
|
||||
from . import models, services
|
||||
|
||||
|
||||
class StockServicesTestCase(TestCase):
|
||||
"""库存服务层测试"""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""设置测试数据(类级别,所有测试方法共享)"""
|
||||
# 产品单位(使用枚举值)
|
||||
cls.unit_meter = ProductUnitEnum.METER
|
||||
cls.unit_kg = ProductUnitEnum.KG
|
||||
|
||||
# 创建产品类别
|
||||
cls.fabric_category = ProductCategory.objects.create(
|
||||
name="布料类",
|
||||
description="各种布料产品",
|
||||
product_prefix="FAB"
|
||||
)
|
||||
|
||||
# 创建仓库
|
||||
cls.warehouse_main = WareHouse.objects.create(
|
||||
name="主仓库",
|
||||
location="主仓库地址"
|
||||
)
|
||||
cls.warehouse_backup = WareHouse.objects.create(
|
||||
name="备用仓库",
|
||||
location="备用仓库地址"
|
||||
)
|
||||
|
||||
# 创建供应商
|
||||
cls.supplier = Supplier.objects.create(
|
||||
name="测试供应商",
|
||||
contact="张三",
|
||||
mobile="13800138000"
|
||||
)
|
||||
|
||||
# 创建产品
|
||||
cls.product_fabric_a = Product.objects.create(
|
||||
category=cls.fabric_category,
|
||||
name="布料A",
|
||||
human_id="FABRIC-A-001",
|
||||
color="红色",
|
||||
spec="规格A",
|
||||
single_price_in=Decimal('100.00'),
|
||||
single_price_out=Decimal('120.00'),
|
||||
unit=cls.unit_meter
|
||||
)
|
||||
cls.product_fabric_b = Product.objects.create(
|
||||
category=cls.fabric_category,
|
||||
name="布料B",
|
||||
human_id="FABRIC-B-001",
|
||||
color="蓝色",
|
||||
spec="规格B",
|
||||
single_price_in=Decimal('150.00'),
|
||||
single_price_out=Decimal('180.00'),
|
||||
unit=cls.unit_kg
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
"""每个测试方法执行前的设置"""
|
||||
# 创建采购单
|
||||
self.purchase_order = models.PurchaseOrder.objects.create(
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('1000.00'),
|
||||
remarks="测试采购单"
|
||||
)
|
||||
|
||||
# 创建库存变动记录(入库)
|
||||
self.stock_change_in = models.StockChangeRecord.objects.create(
|
||||
type=models.StockChangeTypeEnum.ADD,
|
||||
source_type=models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
warehouse=self.warehouse_main,
|
||||
is_finished=False
|
||||
)
|
||||
|
||||
# 创建库存变动记录(出库)
|
||||
self.stock_change_out = models.StockChangeRecord.objects.create(
|
||||
type=models.StockChangeTypeEnum.REMOVE,
|
||||
source_type=models.StockChangeSourceEnum.SALES,
|
||||
source_id=1,
|
||||
warehouse=self.warehouse_main,
|
||||
is_finished=False
|
||||
)
|
||||
|
||||
# 创建库存变动明细
|
||||
self.stock_detail_in = models.StockChangeDetail.objects.create(
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_a,
|
||||
quantity=Decimal('100'),
|
||||
unit=self.unit_meter
|
||||
)
|
||||
|
||||
self.stock_detail_out = models.StockChangeDetail.objects.create(
|
||||
stock_change_record=self.stock_change_out,
|
||||
product=self.product_fabric_a,
|
||||
quantity=Decimal('50'),
|
||||
unit=self.unit_meter
|
||||
)
|
||||
|
||||
|
||||
class FindInventoryTestCase(StockServicesTestCase):
|
||||
"""测试 find_inventory 函数"""
|
||||
|
||||
def test_find_existing_inventory(self):
|
||||
"""测试查找已存在的库存记录"""
|
||||
# 先创建一个库存记录
|
||||
inventory = models.Inventory.objects.create(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
num_of_rolls=5
|
||||
)
|
||||
|
||||
# 测试查找
|
||||
found_inventory = services.find_inventory(
|
||||
product_id=self.product_fabric_a.id,
|
||||
warehouse_id=self.warehouse_main.id
|
||||
)
|
||||
|
||||
self.assertIsNotNone(found_inventory)
|
||||
self.assertEqual(found_inventory.id, inventory.id)
|
||||
self.assertEqual(found_inventory.quantity, 50)
|
||||
self.assertEqual(found_inventory.num_of_rolls, 5)
|
||||
|
||||
def test_find_nonexistent_inventory(self):
|
||||
"""测试查找不存在的库存记录"""
|
||||
result = services.find_inventory(
|
||||
product_id=self.product_fabric_a.id,
|
||||
warehouse_id=self.warehouse_main.id
|
||||
)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_find_inventory_wrong_warehouse(self):
|
||||
"""测试在错误的仓库中查找库存"""
|
||||
# 在主仓库创建库存
|
||||
models.Inventory.objects.create(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
num_of_rolls=5
|
||||
)
|
||||
|
||||
# 在备用仓库查找
|
||||
result = services.find_inventory(
|
||||
product_id=self.product_fabric_a.id,
|
||||
warehouse_id=self.warehouse_backup.id
|
||||
)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class CreateStockSnapshotTestCase(StockServicesTestCase):
|
||||
"""测试 create_stock_snapshot 函数"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# 创建库存记录
|
||||
self.inventory = models.Inventory.objects.create(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=150,
|
||||
num_of_rolls=15
|
||||
)
|
||||
|
||||
def test_create_snapshot_for_incoming_stock(self):
|
||||
"""测试为入库记录创建快照"""
|
||||
with patch.object(services.logger, 'info') as mock_logger:
|
||||
snapshot = services.create_stock_snapshot(
|
||||
self.stock_detail_in,
|
||||
self.inventory
|
||||
)
|
||||
|
||||
self.assertIsNotNone(snapshot)
|
||||
self.assertEqual(snapshot.product_id, self.product_fabric_a.id)
|
||||
self.assertEqual(snapshot.warehouse_id, self.warehouse_main.id)
|
||||
self.assertEqual(snapshot.delta, 100) # 入库数量为正
|
||||
self.assertEqual(snapshot.quantity_before, 50) # 150 - 100
|
||||
self.assertEqual(snapshot.quantity_after, 150)
|
||||
self.assertEqual(snapshot.stock_change_record_id, self.stock_change_in.id)
|
||||
|
||||
# 验证日志调用
|
||||
mock_logger.assert_called_once()
|
||||
|
||||
def test_create_snapshot_for_outgoing_stock(self):
|
||||
"""测试为出库记录创建快照"""
|
||||
snapshot = services.create_stock_snapshot(
|
||||
self.stock_detail_out,
|
||||
self.inventory
|
||||
)
|
||||
|
||||
self.assertIsNotNone(snapshot)
|
||||
self.assertEqual(snapshot.delta, -50) # 出库数量为负
|
||||
self.assertEqual(snapshot.quantity_before, 200) # 150 - (-50)
|
||||
self.assertEqual(snapshot.quantity_after, 150)
|
||||
|
||||
def test_create_snapshot_with_invalid_detail(self):
|
||||
"""测试使用无效明细记录创建快照"""
|
||||
# 测试 None 参数
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
services.create_stock_snapshot(None, self.inventory)
|
||||
self.assertIn("无效的库存变动明细记录", str(cm.exception))
|
||||
|
||||
# 测试未保存的明细记录
|
||||
unsaved_detail = models.StockChangeDetail(
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_a,
|
||||
quantity=50
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
services.create_stock_snapshot(unsaved_detail, self.inventory)
|
||||
|
||||
|
||||
class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
||||
"""测试 make_stock_change_completed 函数"""
|
||||
|
||||
def test_complete_invalid_record(self):
|
||||
"""测试完成无效的库存变动记录"""
|
||||
result = services.make_stock_change_completed(None)
|
||||
self.assertFalse(result)
|
||||
|
||||
# 测试未保存的记录
|
||||
unsaved_record = models.StockChangeRecord(
|
||||
type=models.StockChangeTypeEnum.ADD,
|
||||
warehouse=self.warehouse_main
|
||||
)
|
||||
result = services.make_stock_change_completed(unsaved_record)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_complete_already_finished_record(self):
|
||||
"""测试完成已经完成的记录"""
|
||||
self.stock_change_in.is_finished = True
|
||||
self.stock_change_in.save()
|
||||
|
||||
result = services.make_stock_change_completed(self.stock_change_in)
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('stock.services.find_inventory')
|
||||
@patch('stock.services.create_stock_snapshot')
|
||||
def test_complete_inbound_record_new_inventory(self, mock_snapshot, mock_find):
|
||||
"""测试完成入库记录(新建库存)"""
|
||||
# 模拟没有找到现有库存
|
||||
mock_find.return_value = None
|
||||
mock_snapshot.return_value = MagicMock()
|
||||
|
||||
result = services.make_stock_change_completed(self.stock_change_in)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(self.stock_change_in.is_finished)
|
||||
self.assertIsNotNone(self.stock_change_in.finished_at)
|
||||
|
||||
# 验证创建了新的库存记录
|
||||
new_inventory = models.Inventory.objects.get(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main
|
||||
)
|
||||
self.assertEqual(new_inventory.quantity, 100)
|
||||
self.assertEqual(new_inventory.num_of_rolls, 1)
|
||||
|
||||
# 验证调用了相关函数
|
||||
mock_find.assert_called_once_with(
|
||||
product_id=self.product_fabric_a.id,
|
||||
warehouse_id=self.warehouse_main.id
|
||||
)
|
||||
mock_snapshot.assert_called_once()
|
||||
|
||||
def test_complete_inbound_record_existing_inventory(self):
|
||||
"""测试完成入库记录(更新现有库存)"""
|
||||
# 创建现有库存
|
||||
existing_inventory = models.Inventory.objects.create(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
num_of_rolls=5
|
||||
)
|
||||
|
||||
with patch('stock.services.create_stock_snapshot') as mock_snapshot:
|
||||
mock_snapshot.return_value = MagicMock()
|
||||
|
||||
result = services.make_stock_change_completed(self.stock_change_in)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
# 检查库存更新
|
||||
existing_inventory.refresh_from_db()
|
||||
self.assertEqual(existing_inventory.quantity, 150) # 50 + 100
|
||||
self.assertEqual(existing_inventory.num_of_rolls, 6) # 5 + 1
|
||||
|
||||
# 验证记录状态
|
||||
self.stock_change_in.refresh_from_db()
|
||||
self.assertTrue(self.stock_change_in.is_finished)
|
||||
|
||||
def test_complete_outbound_record(self):
|
||||
"""测试完成出库记录"""
|
||||
# 创建现有库存
|
||||
existing_inventory = models.Inventory.objects.create(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=100,
|
||||
num_of_rolls=10
|
||||
)
|
||||
|
||||
with patch('stock.services.create_stock_snapshot') as mock_snapshot:
|
||||
mock_snapshot.return_value = MagicMock()
|
||||
|
||||
result = services.make_stock_change_completed(self.stock_change_out)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
# 检查库存减少
|
||||
existing_inventory.refresh_from_db()
|
||||
self.assertEqual(existing_inventory.quantity, 50) # 100 - 50
|
||||
self.assertEqual(existing_inventory.num_of_rolls, 9) # 10 - 1
|
||||
|
||||
def test_complete_record_with_multiple_details(self):
|
||||
"""测试完成包含多个明细的记录"""
|
||||
# 添加另一个明细
|
||||
models.StockChangeDetail.objects.create(
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_b,
|
||||
quantity=Decimal('200'),
|
||||
unit=self.unit_kg
|
||||
)
|
||||
|
||||
with patch('stock.services.create_stock_snapshot') as mock_snapshot:
|
||||
mock_snapshot.return_value = MagicMock()
|
||||
|
||||
result = services.make_stock_change_completed(self.stock_change_in)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
# 检查为两个产品都创建了库存记录
|
||||
inventory_a = models.Inventory.objects.get(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main
|
||||
)
|
||||
inventory_b = models.Inventory.objects.get(
|
||||
product=self.product_fabric_b,
|
||||
warehouse=self.warehouse_main
|
||||
)
|
||||
|
||||
self.assertEqual(inventory_a.quantity, 100)
|
||||
self.assertEqual(inventory_b.quantity, 200)
|
||||
|
||||
# 验证快照创建了两次
|
||||
self.assertEqual(mock_snapshot.call_count, 2)
|
||||
|
||||
@patch.object(services.logger, 'info')
|
||||
def test_logging_during_completion(self, mock_logger):
|
||||
"""测试完成过程中的日志记录"""
|
||||
services.make_stock_change_completed(self.stock_change_in)
|
||||
|
||||
# 验证日志调用
|
||||
self.assertGreater(mock_logger.call_count, 0)
|
||||
|
||||
# 检查最后一条日志消息
|
||||
last_call_args = mock_logger.call_args_list[-1][0]
|
||||
self.assertIn('标记为已完成', last_call_args[0])
|
||||
|
||||
|
||||
class StockServicesIntegrationTestCase(StockServicesTestCase):
|
||||
"""集成测试:测试服务函数之间的协作"""
|
||||
|
||||
def test_complete_workflow(self):
|
||||
"""测试完整的库存变动工作流"""
|
||||
# 1. 确保开始时没有库存
|
||||
self.assertEqual(
|
||||
models.Inventory.objects.filter(
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main
|
||||
).count(),
|
||||
0
|
||||
)
|
||||
|
||||
# 2. 完成入库操作
|
||||
result = services.make_stock_change_completed(self.stock_change_in)
|
||||
self.assertTrue(result)
|
||||
|
||||
# 3. 验证库存被创建
|
||||
inventory = services.find_inventory(
|
||||
self.product_fabric_a.id,
|
||||
self.warehouse_main.id
|
||||
)
|
||||
self.assertIsNotNone(inventory)
|
||||
self.assertEqual(inventory.quantity, 100)
|
||||
|
||||
# 4. 验证快照被创建
|
||||
snapshots = models.StockSnapshot.objects.filter(
|
||||
stock_change_record=self.stock_change_in
|
||||
)
|
||||
self.assertEqual(snapshots.count(), 1)
|
||||
snapshot = snapshots.first()
|
||||
self.assertEqual(snapshot.delta, 100)
|
||||
self.assertEqual(snapshot.quantity_before, 0)
|
||||
self.assertEqual(snapshot.quantity_after, 100)
|
||||
|
||||
# 5. 完成出库操作
|
||||
result = services.make_stock_change_completed(self.stock_change_out)
|
||||
self.assertTrue(result)
|
||||
|
||||
# 6. 验证库存被更新
|
||||
inventory.refresh_from_db()
|
||||
self.assertEqual(inventory.quantity, 50) # 100 - 50
|
||||
|
||||
# 7. 验证出库快照
|
||||
out_snapshots = models.StockSnapshot.objects.filter(
|
||||
stock_change_record=self.stock_change_out
|
||||
)
|
||||
self.assertEqual(out_snapshots.count(), 1)
|
||||
out_snapshot = out_snapshots.first()
|
||||
self.assertEqual(out_snapshot.delta, -50)
|
||||
self.assertEqual(out_snapshot.quantity_before, 100)
|
||||
self.assertEqual(out_snapshot.quantity_after, 50)
|
||||
|
||||
|
||||
# 禁用测试期间的日志输出
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
Reference in New Issue
Block a user