177 lines
7.9 KiB
Python
177 lines
7.9 KiB
Python
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())
|