""" Unit tests for Retail Models Tests for retail module models: - Product - Sale Author: Claude """ import pytest from django.test import TestCase from django.contrib.auth import get_user_model from django.utils import timezone from decimal import Decimal from datetime import date from backend.src.core.models.tenant import Tenant from backend.src.core.models.user import User from backend.src.modules.retail.models.product import Product from backend.src.modules.retail.models.sale import Sale, SaleItem User = get_user_model() class ProductModelTest(TestCase): """Test cases for Product model""" def setUp(self): self.tenant = Tenant.objects.create( name='Test Business Sdn Bhd', schema_name='test_business', domain='testbusiness.com', business_type='retail' ) self.user = User.objects.create_user( username='testuser', email='user@test.com', password='test123', tenant=self.tenant ) self.product_data = { 'tenant': self.tenant, 'sku': 'PRD-001', 'name': 'Test Product', 'description': 'A test product for unit testing', 'category': 'electronics', 'brand': 'Test Brand', 'barcode': '1234567890123', 'unit': 'piece', 'current_stock': 100, 'minimum_stock': 10, 'maximum_stock': 500, 'reorder_point': 15, 'purchase_price': Decimal('50.00'), 'selling_price': Decimal('100.00'), 'wholesale_price': Decimal('80.00'), 'tax_rate': 10.0, 'is_taxable': True, 'is_active': True, 'requires_prescription': False, 'is_halal': True, 'msme_certified': True, 'created_by': self.user } def test_create_product(self): """Test creating a new product""" product = Product.objects.create(**self.product_data) self.assertEqual(product.tenant, self.tenant) self.assertEqual(product.sku, self.product_data['sku']) self.assertEqual(product.name, self.product_data['name']) self.assertEqual(product.current_stock, self.product_data['current_stock']) self.assertEqual(product.purchase_price, self.product_data['purchase_price']) self.assertEqual(product.selling_price, self.product_data['selling_price']) self.assertTrue(product.is_active) self.assertTrue(product.is_halal) def test_product_string_representation(self): """Test product string representation""" product = Product.objects.create(**self.product_data) self.assertEqual(str(product), f"{product.name} ({product.sku})") def test_product_is_low_stock(self): """Test product low stock detection""" product = Product.objects.create(**self.product_data) # Normal stock level self.assertFalse(product.is_low_stock) # Low stock level product.current_stock = 5 product.save() self.assertTrue(product.is_low_stock) def test_product_profit_margin(self): """Test product profit margin calculation""" product = Product.objects.create(**self.product_data) expected_margin = ((product.selling_price - product.purchase_price) / product.selling_price) * 100 self.assertAlmostEqual(product.profit_margin, expected_margin) def test_product_category_choices(self): """Test product category validation""" invalid_data = self.product_data.copy() invalid_data['category'] = 'invalid_category' with self.assertRaises(Exception): Product.objects.create(**invalid_data) def test_product_unit_choices(self): """Test product unit validation""" invalid_data = self.product_data.copy() invalid_data['unit'] = 'invalid_unit' with self.assertRaises(Exception): Product.objects.create(**invalid_data) def test_product_barcode_validation(self): """Test product barcode validation""" # Valid barcode product = Product.objects.create(**self.product_data) self.assertEqual(product.barcode, self.product_data['barcode']) # Invalid barcode (too long) invalid_data = self.product_data.copy() invalid_data['barcode'] = '1' * 14 with self.assertRaises(Exception): Product.objects.create(**invalid_data) def test_product_stock_validation(self): """Test product stock validation""" invalid_data = self.product_data.copy() invalid_data['current_stock'] = -1 with self.assertRaises(Exception): Product.objects.create(**invalid_data) invalid_data['current_stock'] = 0 invalid_data['minimum_stock'] = -5 with self.assertRaises(Exception): Product.objects.create(**invalid_data) class SaleModelTest(TestCase): """Test cases for Sale and SaleItem models""" def setUp(self): self.tenant = Tenant.objects.create( name='Test Business Sdn Bhd', schema_name='test_business', domain='testbusiness.com', business_type='retail' ) self.user = User.objects.create_user( username='testuser', email='user@test.com', password='test123', tenant=self.tenant ) self.product1 = Product.objects.create( tenant=self.tenant, sku='PRD-001', name='Product 1', category='electronics', unit='piece', current_stock=100, minimum_stock=10, purchase_price=Decimal('50.00'), selling_price=Decimal('100.00'), tax_rate=10.0, created_by=self.user ) self.product2 = Product.objects.create( tenant=self.tenant, sku='PRD-002', name='Product 2', category='electronics', unit='piece', current_stock=50, minimum_stock=5, purchase_price=Decimal('30.00'), selling_price=Decimal('60.00'), tax_rate=10.0, created_by=self.user ) self.sale_data = { 'tenant': self.tenant, 'invoice_number': 'INV-2024010001', 'customer_name': 'Test Customer', 'customer_email': 'customer@test.com', 'customer_phone': '+60123456789', 'customer_ic': '000101-01-0001', 'sale_date': timezone.now(), 'status': 'completed', 'payment_method': 'cash', 'payment_status': 'paid', 'sales_person': self.user, 'notes': 'Test sale for unit testing' } def test_create_sale(self): """Test creating a new sale""" sale = Sale.objects.create(**self.sale_data) self.assertEqual(sale.tenant, self.tenant) self.assertEqual(sale.invoice_number, self.sale_data['invoice_number']) self.assertEqual(sale.customer_name, self.sale_data['customer_name']) self.assertEqual(sale.status, self.sale_data['status']) self.assertEqual(sale.payment_status, self.sale_data['payment_status']) self.assertEqual(sale.sales_person, self.user) def test_sale_string_representation(self): """Test sale string representation""" sale = Sale.objects.create(**self.sale_data) self.assertEqual(str(sale), f"Invoice #{sale.invoice_number} - {sale.customer_name}") def test_create_sale_item(self): """Test creating a sale item""" sale = Sale.objects.create(**self.sale_data) sale_item_data = { 'sale': sale, 'product': self.product1, 'quantity': 2, 'unit_price': Decimal('100.00'), 'discount_percentage': 0.0, 'tax_rate': 10.0, 'notes': 'Test sale item' } sale_item = SaleItem.objects.create(**sale_item_data) self.assertEqual(sale_item.sale, sale) self.assertEqual(sale_item.product, self.product1) self.assertEqual(sale_item.quantity, 2) self.assertEqual(sale_item.unit_price, Decimal('100.00')) def test_sale_item_subtotal(self): """Test sale item subtotal calculation""" sale = Sale.objects.create(**self.sale_data) sale_item = SaleItem.objects.create( sale=sale, product=self.product1, quantity=2, unit_price=Decimal('100.00'), tax_rate=10.0 ) expected_subtotal = Decimal('200.00') # 2 * 100.00 self.assertEqual(sale_item.subtotal, expected_subtotal) def test_sale_item_tax_amount(self): """Test sale item tax amount calculation""" sale = Sale.objects.create(**self.sale_data) sale_item = SaleItem.objects.create( sale=sale, product=self.product1, quantity=2, unit_price=Decimal('100.00'), tax_rate=10.0 ) expected_tax = Decimal('20.00') # 200.00 * 0.10 self.assertEqual(sale_item.tax_amount, expected_tax) def test_sale_item_total_amount(self): """Test sale item total amount calculation""" sale = Sale.objects.create(**self.sale_data) sale_item = SaleItem.objects.create( sale=sale, product=self.product1, quantity=2, unit_price=Decimal('100.00'), tax_rate=10.0 ) expected_total = Decimal('220.00') # 200.00 + 20.00 self.assertEqual(sale_item.total_amount, expected_total) def test_sale_calculate_totals(self): """Test sale total calculations""" sale = Sale.objects.create(**self.sale_data) # Create multiple sale items SaleItem.objects.create( sale=sale, product=self.product1, quantity=2, unit_price=Decimal('100.00'), tax_rate=10.0 ) SaleItem.objects.create( sale=sale, product=self.product2, quantity=1, unit_price=Decimal('60.00'), tax_rate=10.0 ) # Test the calculate_totals method sale.calculate_totals() expected_subtotal = Decimal('260.00') # 200.00 + 60.00 expected_tax = Decimal('26.00') # 20.00 + 6.00 expected_total = Decimal('286.00') # 260.00 + 26.00 self.assertEqual(sale.subtotal_amount, expected_subtotal) self.assertEqual(sale.tax_amount, expected_tax) self.assertEqual(sale.total_amount, expected_total) def test_sale_status_choices(self): """Test sale status validation""" invalid_data = self.sale_data.copy() invalid_data['status'] = 'invalid_status' with self.assertRaises(Exception): Sale.objects.create(**invalid_data) def test_sale_payment_method_choices(self): """Test sale payment method validation""" invalid_data = self.sale_data.copy() invalid_data['payment_method'] = 'invalid_method' with self.assertRaises(Exception): Sale.objects.create(**invalid_data) def test_malaysian_customer_validation(self): """Test Malaysian customer validation""" # Valid Malaysian IC sale = Sale.objects.create(**self.sale_data) self.assertEqual(sale.customer_ic, self.sale_data['customer_ic']) # Valid Malaysian phone self.assertEqual(sale.customer_phone, self.sale_data['customer_phone']) # Invalid phone number invalid_data = self.sale_data.copy() invalid_data['customer_phone'] = '12345' with self.assertRaises(Exception): Sale.objects.create(**invalid_data)