FEAT: correccion IVA y Boletas, filtro categorias, vista previa evidencia y proveedor de servicios
This commit is contained in:
+12
-5
@@ -137,6 +137,7 @@ class ExpenseForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['project'].queryset = Project.objects.filter(parent__parent__isnull=False)
|
||||
self.fields['category'].queryset = Category.objects.filter(children__isnull=True)
|
||||
if self.instance and self.instance.pk:
|
||||
try:
|
||||
vlog = self.instance.vehicle_log
|
||||
@@ -218,10 +219,11 @@ class ExpenseForm(forms.ModelForm):
|
||||
raise forms.ValidationError("Si desea registrar un nuevo proveedor, debe ingresar tanto el RUT como la Razón Social.")
|
||||
|
||||
# Recalculate taxes via SIIService
|
||||
amount = cleaned_data.get('amount_clp')
|
||||
amount_clp = cleaned_data.get('amount_clp')
|
||||
tax_type = cleaned_data.get('tax_type')
|
||||
if amount and tax_type:
|
||||
tax_res = SIIService.calculate_taxes(amount, tax_type)
|
||||
if amount_clp and amount_clp > 0 and tax_type:
|
||||
tax_res = SIIService.calculate_taxes(amount_clp, tax_type)
|
||||
cleaned_data['amount_clp'] = tax_res['net_amount']
|
||||
cleaned_data['tax_amount_clp'] = tax_res['tax_amount']
|
||||
cleaned_data['total_amount_clp'] = tax_res['total_amount']
|
||||
|
||||
@@ -229,7 +231,7 @@ class ExpenseForm(forms.ModelForm):
|
||||
vendor = cleaned_data.get('vendor')
|
||||
doc_num = cleaned_data.get('document_number')
|
||||
date_val = cleaned_data.get('date')
|
||||
if amount and (vendor or doc_num):
|
||||
if amount_clp and (vendor or doc_num):
|
||||
duplicates = SIIService.check_duplicate_expense(
|
||||
expense_model=Expense,
|
||||
vendor_id=vendor.id if vendor else None,
|
||||
@@ -352,6 +354,10 @@ class ProjectForm(forms.ModelForm):
|
||||
'payment_method': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Ej: 50% Anticipo / 50% Hito Final'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['category'].queryset = Category.objects.filter(children__isnull=True)
|
||||
|
||||
|
||||
class ProjectPhaseForm(forms.ModelForm):
|
||||
due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), required=False, label="Fecha Pactada")
|
||||
@@ -381,10 +387,11 @@ class CategoryForm(forms.ModelForm):
|
||||
class VendorForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Vendor
|
||||
fields = ['rut', 'name', 'business_line', 'bank', 'account_type', 'account_number', 'email', 'payment_terms']
|
||||
fields = ['rut', 'name', 'provider_type', 'business_line', 'bank', 'account_type', 'account_number', 'email', 'payment_terms']
|
||||
widgets = {
|
||||
'rut': forms.TextInput(attrs={'class': 'form-control', 'required': 'required', 'pattern': '^[0-9]+-[0-9kK]$', 'title': 'Formato válido: 12345678-9'}),
|
||||
'name': forms.TextInput(attrs={'class': 'form-control', 'required': 'required'}),
|
||||
'provider_type': forms.Select(attrs={'class': 'form-control'}),
|
||||
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.1 on 2026-09-09 17:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('expenses', '0012_alter_category_parent_alter_expense_project_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='vendor',
|
||||
name='provider_type',
|
||||
field=models.CharField(choices=[('BIENES', 'Proveedor de Bienes'), ('SERVICIOS', 'Proveedor de Servicios'), ('AMBOS', 'Bienes y Servicios')], default='BIENES', max_length=20, verbose_name='Clasificación'),
|
||||
),
|
||||
]
|
||||
@@ -137,8 +137,14 @@ class Category(CatalogModel):
|
||||
|
||||
|
||||
class Vendor(TimeStampedModel):
|
||||
PROVIDER_TYPE_CHOICES = [
|
||||
('BIENES', 'Proveedor de Bienes'),
|
||||
('SERVICIOS', 'Proveedor de Servicios'),
|
||||
('AMBOS', 'Bienes y Servicios'),
|
||||
]
|
||||
rut = models.CharField(max_length=15, unique=True, verbose_name="RUT Proveedor")
|
||||
name = models.CharField(max_length=200, verbose_name="Razón Social / Nombre")
|
||||
provider_type = models.CharField(max_length=20, choices=PROVIDER_TYPE_CHOICES, default='BIENES', verbose_name="Clasificación")
|
||||
business_line = models.CharField(max_length=200, blank=True, null=True, verbose_name="Giro Comercial")
|
||||
|
||||
bank = models.CharField(max_length=100, blank=True, null=True, verbose_name="Banco")
|
||||
|
||||
@@ -186,17 +186,19 @@ class SIIService:
|
||||
'description': f'Retención Honorarios ({tasa_porcentaje}%) - Vía API Externa'
|
||||
}
|
||||
|
||||
# 2. Fallback local (Bruto = Neto ingresado)
|
||||
# Retencion = Bruto * 0.1525 (año 2026)
|
||||
# 2. Fallback local (Neto ingresado = Líquido)
|
||||
# Bruto = Líquido / (1 - 0.1525)
|
||||
# Retencion = Bruto - Líquido
|
||||
tasa = Decimal('0.1525')
|
||||
tax_amount = (net_amount_clp * tasa).quantize(Decimal('1'), rounding=ROUND_HALF_UP)
|
||||
liquid_amount = net_amount_clp - tax_amount
|
||||
liquid_amount = net_amount_clp
|
||||
bruto_amount = (liquid_amount / (Decimal('1') - tasa)).quantize(Decimal('1'), rounding=ROUND_HALF_UP)
|
||||
tax_amount = bruto_amount - liquid_amount
|
||||
return {
|
||||
'tax_type': 'HONORARIOS_13_75',
|
||||
'net_amount': liquid_amount,
|
||||
'tax_rate_percent': Decimal('15.25'),
|
||||
'tax_amount': tax_amount,
|
||||
'total_amount': net_amount_clp,
|
||||
'total_amount': bruto_amount,
|
||||
'description': 'Retención Boleta de Honorarios 15.25%'
|
||||
}
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user