from django.test import TestCase, Client from django.contrib.auth.models import User, Group from decimal import Decimal from datetime import date from expenses.models import ( Expense, Project, Category, SystemSetting, Vendor, ProjectPhase, ProjectStatusEnum, TaxTypeEnum, ExpenseTypeEnum, VehicleLog ) from expenses.services.sii import SIIService from talent.models import SpecialistCategoryEnum, SpecialistCandidate from procurement.models import ItemCatalog, Category as ProcurementCategory class ERPRequirementsTestCase(TestCase): def setUp(self): self.client = Client() self.admin = User.objects.create_superuser('admin_user', 'admin@efintel.cl', 'password123') self.pm_group, _ = Group.objects.get_or_create(name='Director de Proyecto') self.pm_user = User.objects.create_user('pm_user', 'pm@efintel.cl', 'password123') self.pm_user.groups.add(self.pm_group) self.setting = SystemSetting.objects.create(id=1, usd_exchange_rate=950.0, iva_rate=19.00) self.category_combustible = Category.objects.create(code='3101', name='Combustibles / Bencina') self.category_office = Category.objects.create(code='2301', name='Arriendo de Oficinas y Espacios de Trabajo') self.category_courses = Category.objects.create(code='4201', name='Cursos, Capacitación y Certificaciones Técnicas para Especialistas') self.category_ai = Category.objects.create(code='7000', name='Software, Datos, Inteligencia Artificial y Cloud') self.project_idi = Project.objects.create( code='CC-IDI-01', name='Centro I+D+i Central', assigned_budget_clp=10000000, payment_method='Mensual', total_income_clp=15000000, type=ExpenseTypeEnum.COSTO_DIRECTO, status=ProjectStatusEnum.ACTIVO, manager=self.pm_user ) def test_req_1_and_5_dynamic_iva_and_settings(self): """Test Requerimiento 1 (No global_budget in dashboard) & Requerimiento 5 (Dynamic IVA).""" self.client.login(username='admin_user', password='password123') response = self.client.get('/') self.assertEqual(response.status_code, 200) self.assertNotIn('PRESUPUESTO GENERAL I+D+i', response.content.decode()) # Test Dynamic IVA (Change to 21%) self.setting.iva_rate = Decimal('21.00') self.setting.save() calc = SIIService.calculate_taxes(Decimal('100000'), 'IVA_19') self.assertEqual(calc['tax_amount'], Decimal('21000')) self.assertEqual(calc['total_amount'], Decimal('121000')) def test_req_2_exact_project_count(self): """Test Requerimiento 2: Exact project count by status.""" Project.objects.create(code='PROY-P1', name='Pausado', status=ProjectStatusEnum.PAUSADA) Project.objects.create(code='PROY-T1', name='Terminado', status=ProjectStatusEnum.TERMINADO) Project.objects.create(code='PROY-D1', name='Borrador', status=ProjectStatusEnum.BORRADOR) self.client.login(username='admin_user', password='password123') res = self.client.get('/proyectos/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['project_count_active'], 1) self.assertEqual(res.context['project_count_paused'], 1) self.assertEqual(res.context['project_count_completed'], 1) self.assertEqual(res.context['project_count_draft'], 1) self.assertEqual(res.context['project_count_total'], 4) def test_req_4_sii_integration(self): """Test Requerimiento 4: SII API service and taxpayer info query.""" is_valid, formatted = SIIService.validate_rut('76543210-3') self.assertTrue(is_valid) self.assertEqual(formatted, '76.543.210-3') info = SIIService.get_taxpayer_info('76.543.210-3') self.assertTrue(info['valid']) self.assertEqual(info['rut'], '76.543.210-3') def test_req_6_vendor_draft_mitigation(self): """Test Requerimiento 6: Vendor registration error mitigation and draft in session.""" self.client.login(username='admin_user', password='password123') # Submit invalid vendor form (RUT invalid - fails Modulo 11 check) response = self.client.post('/gestion/proveedores/nuevo/', { 'rut': '12345678-0', 'name': 'Vendor Test Draft', 'payment_terms': 30 }) self.assertEqual(response.status_code, 200) self.assertIn('vendor_draft', self.client.session) def test_req_7_is_reimbursement_logic(self): """Test Requerimiento 7: Erogación rendición vs centro I+D+i.""" expense = Expense.objects.create( project=self.project_idi, category=self.category_office, is_reimbursement=False, amount_clp=50000, date=date.today(), description='Gasto I+D+i directo', user=self.admin ) self.assertFalse(expense.is_reimbursement) # Reimbursement expense expense_reimb = Expense.objects.create( project=self.project_idi, category=self.category_office, is_reimbursement=True, responsible_person=self.pm_user, amount_clp=30000, date=date.today(), description='Rendición de pasajes', user=self.admin ) self.assertTrue(expense_reimb.is_reimbursement) self.assertEqual(expense_reimb.responsible_person, self.pm_user) def test_req_8_9_10_project_detail_and_phase_balances(self): """Test Requerimiento 8, 9, 10: Project detail, progressive creation, and phase balances (+ / -).""" phase1 = ProjectPhase.objects.create( project=self.project_idi, phase_number=1, name='Fase 1: Prototipado', planned_amount_clp=5000000, income_amount_clp=6000000, expense_amount_clp=2000000 ) self.assertEqual(phase1.balance_clp, Decimal('4000000')) # Positive balance +$4.000.000 phase2 = ProjectPhase.objects.create( project=self.project_idi, phase_number=2, name='Fase 2: Pruebas Terreno', planned_amount_clp=3000000, income_amount_clp=1000000, expense_amount_clp=2500000 ) self.assertEqual(phase2.balance_clp, Decimal('-1500000')) # Negative balance -$1.500.000 self.client.login(username='admin_user', password='password123') res = self.client.get(f'/proyectos/{self.project_idi.id}/') self.assertEqual(res.status_code, 200) self.assertIn('Fases de Pago e Hitos del Proyecto', res.content.decode()) def test_req_11_12_13_categories_taxonomy(self): """Test Requerimiento 11, 12, 13: Talent category 7, Office Rental, and Specialist Courses.""" # Req 11 self.assertEqual(SpecialistCategoryEnum.CAT_7_SOFTWARE_DATOS_IA_CLOUD, '7') self.assertEqual(SpecialistCategoryEnum.CAT_7_SOFTWARE_DATOS_IA_CLOUD.label, '7. Software, Datos, IA y Cloud') # Req 12 self.assertEqual(self.category_office.code, '2301') # Req 13 self.assertEqual(self.category_courses.code, '4201') def test_req_14_vehicle_category_strict_combustible(self): """Test Requerimiento 14: Vehicle log applies ONLY to combustible category.""" exp_combustible = Expense.objects.create( project=self.project_idi, category=self.category_combustible, amount_clp=40000, date=date.today(), description='Carga Combustible Copec', user=self.admin ) self.assertTrue(exp_combustible.is_vehicle_category()) exp_office = Expense.objects.create( project=self.project_idi, category=self.category_office, amount_clp=150000, date=date.today(), description='Pago Arriendo Oficina', user=self.admin ) self.assertFalse(exp_office.is_vehicle_category())