Files
khatibi/tests/test_inventory.py
T
2026-10-09 20:02:36 +03:30

202 lines
8.7 KiB
Python

"""
تست‌های اطمینان انبار: ریاضی موجودی، تاریخچه گردش کالا و چرخهٔ کامل انتقال بین انبارها.
"""
from django.core.exceptions import PermissionDenied, ValidationError
from django.test import TestCase
from admin_panel.models import (ProductInStore, ProductStockHistory,
StoreTransferRequest)
from admin_panel.services import (approve_transfer_request,
cancel_transfer_request,
create_transfer_request,
get_product_current_stock,
reject_transfer_request)
from .factories import (current_stock, make_product, make_store, make_user,
stock_in)
class ProductInStoreMathTests(TestCase):
def setUp(self):
self.store = make_store('انبار مرکزی')
self.product = make_product('کالای تست', price=500)
def test_initial_in_records_history_and_stock(self):
row = stock_in(self.store, self.product, 10)
row.refresh_from_db()
self.assertEqual(row.current_stock_at_time, 10)
self.assertEqual(current_stock(self.store, self.product), 10)
history = ProductStockHistory.objects.filter(store_room=self.store, product=self.product)
self.assertEqual(history.count(), 1)
self.assertEqual(history.first().quantity_changed, 10)
self.assertEqual(history.first().current_stock, 10)
def test_multiple_movements_accumulate_correctly(self):
stock_in(self.store, self.product, 10)
ProductInStore.objects.create(
StoreRoom=self.store, Product=self.product, s_in=0, s_out=4,
desc='خروج تست', movement_type=ProductInStore.MovementType.DIRECT_OUT,
)
ProductInStore.objects.create(
StoreRoom=self.store, Product=self.product, s_in=7, s_out=0,
desc='ورود تست', movement_type=ProductInStore.MovementType.DIRECT_IN,
)
self.assertEqual(current_stock(self.store, self.product), 13)
self.assertEqual(get_product_current_stock(self.store, self.product), 13)
self.assertEqual(
ProductStockHistory.objects.filter(store_room=self.store, product=self.product).count(),
3,
)
def test_editing_a_row_updates_stock_and_history(self):
row = stock_in(self.store, self.product, 10)
row.s_in = 25
row.save()
row.refresh_from_db()
self.assertEqual(row.current_stock_at_time, 25)
self.assertEqual(current_stock(self.store, self.product), 25)
# آخرین رکورد تاریخچه باید تغییر (+15) را نشان دهد
last = ProductStockHistory.objects.filter(
store_room=self.store, product=self.product
).order_by('-id').first()
self.assertEqual(last.quantity_changed, 15)
self.assertEqual(last.current_stock, 25)
def test_deleting_a_row_creates_history_and_reduces_stock(self):
stock_in(self.store, self.product, 10)
out_row = ProductInStore.objects.create(
StoreRoom=self.store, Product=self.product, s_in=0, s_out=3,
desc='خروج', movement_type=ProductInStore.MovementType.DIRECT_OUT,
)
self.assertEqual(current_stock(self.store, self.product), 7)
out_row.delete()
self.assertEqual(current_stock(self.store, self.product), 10)
last = ProductStockHistory.objects.filter(
store_room=self.store, product=self.product
).order_by('-id').first()
self.assertEqual(last.quantity_changed, 3)
self.assertEqual(last.current_stock, 10)
def test_garbage_numeric_values_are_coerced_to_zero(self):
row = ProductInStore.objects.create(
StoreRoom=self.store, Product=self.product,
s_in='abc', s_out=None, desc='مقدار خراب',
)
row.refresh_from_db()
self.assertEqual(row.s_in, 0)
self.assertEqual(row.s_out, 0)
class TransferWorkflowTests(TestCase):
def setUp(self):
self.boss = make_user('boss', superuser=True)
self.store_a = make_store('مبدا', viewers=[self.boss])
self.store_b = make_store('مقصد', viewers=[self.boss])
self.product = make_product('کالای انتقالی', price=1000)
stock_in(self.store_a, self.product, 10)
def test_create_transfer_request_pending(self):
transfer = create_transfer_request(
source_store=self.store_a,
target_store=self.store_b,
product=self.product,
quantity=4,
requester=self.boss,
)
self.assertEqual(transfer.status, StoreTransferRequest.Status.PENDING)
# تا زمان تایید، موجودی هیچ انباری تغییر نمی‌کند
self.assertEqual(current_stock(self.store_a, self.product), 10)
self.assertEqual(current_stock(self.store_b, self.product), 0)
def test_create_rejects_insufficient_stock(self):
with self.assertRaises(ValidationError):
create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=999, requester=self.boss,
)
def test_create_rejects_same_store(self):
with self.assertRaises(ValidationError):
create_transfer_request(
source_store=self.store_a, target_store=self.store_a,
product=self.product, quantity=1, requester=self.boss,
)
def test_create_rejects_non_positive_quantity(self):
with self.assertRaises(ValidationError):
create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=0, requester=self.boss,
)
def test_approve_moves_stock_and_links_transactions(self):
transfer = create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=4, requester=self.boss,
)
approved = approve_transfer_request(request_id=transfer.id, approver=self.boss)
self.assertEqual(approved.status, StoreTransferRequest.Status.APPROVED)
self.assertIsNotNone(approved.out_transaction_id)
self.assertIsNotNone(approved.in_transaction_id)
self.assertEqual(current_stock(self.store_a, self.product), 6)
self.assertEqual(current_stock(self.store_b, self.product), 4)
def test_double_approval_is_rejected(self):
transfer = create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=4, requester=self.boss,
)
approve_transfer_request(request_id=transfer.id, approver=self.boss)
with self.assertRaises(ValidationError):
approve_transfer_request(request_id=transfer.id, approver=self.boss)
def test_reject_keeps_stock_untouched(self):
transfer = create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=4, requester=self.boss,
)
rejected = reject_transfer_request(request_id=transfer.id, rejector=self.boss, reason='کالا خراب')
self.assertEqual(rejected.status, StoreTransferRequest.Status.REJECTED)
self.assertEqual(rejected.rejection_reason, 'کالا خراب')
self.assertEqual(current_stock(self.store_a, self.product), 10)
self.assertEqual(current_stock(self.store_b, self.product), 0)
def test_cancel_by_requester(self):
transfer = create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=4, requester=self.boss,
)
canceled = cancel_transfer_request(request_id=transfer.id, user=self.boss)
self.assertEqual(canceled.status, StoreTransferRequest.Status.CANCELED)
def test_permission_denied_for_outsider(self):
outsider = make_user('outsider')
with self.assertRaises(PermissionDenied):
create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=1, requester=outsider,
)
def test_approval_permission_denied_for_non_target_member(self):
transfer = create_transfer_request(
source_store=self.store_a, target_store=self.store_b,
product=self.product, quantity=4, requester=self.boss,
)
outsider = make_user('outsider2')
with self.assertRaises(PermissionDenied):
approve_transfer_request(request_id=transfer.id, approver=outsider)