from django import forms from .models import ItemCatalog, PurchaseRequisition, RequisitionItem, PurchaseOrder, PurchaseOrderItem, GoodsReceipt, ThreeWayMatch from expenses.models import Project, Vendor, Category from decimal import Decimal class ItemCatalogForm(forms.ModelForm): class Meta: model = ItemCatalog fields = ['code', 'name', 'category', 'unit', 'reference_price_clp', 'reference_price_usd', 'sla_days', 'preferred_vendor', 'specifications', 'is_active'] widgets = { 'code': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'CAT-XXX-01', 'required': 'required', 'pattern': 'CAT-[A-Z0-9]+-[0-9]+'}), 'name': forms.TextInput(attrs={'class': 'form-control', 'required': 'required', 'minlength': '3'}), 'category': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'unit': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'reference_price_clp': forms.NumberInput(attrs={'class': 'form-control', 'step': '1000', 'required': 'required', 'min': '0'}), 'reference_price_usd': forms.NumberInput(attrs={'class': 'form-control', 'step': '1', 'min': '0'}), 'sla_days': forms.NumberInput(attrs={'class': 'form-control', 'required': 'required', 'min': '0'}), 'preferred_vendor': forms.Select(attrs={'class': 'form-control'}), 'specifications': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def clean_reference_price_clp(self): val = self.cleaned_data.get('reference_price_clp') if val is not None and val < 0: raise forms.ValidationError("El precio no puede ser negativo.") return val class PurchaseRequisitionForm(forms.ModelForm): required_date = forms.DateField( widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'required': 'required'}), label="Fecha Requerida en Sitio / Faena" ) class Meta: model = PurchaseRequisition fields = ['project', 'purpose', 'priority', 'required_date'] widgets = { 'project': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'purpose': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Describa el objetivo técnico y la necesidad para el proyecto...', 'required': 'required', 'minlength': '10'}), 'priority': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), } class PurchaseOrderForm(forms.ModelForm): issue_date = forms.DateField( widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'required': 'required'}), label="Fecha de Emisión" ) expected_delivery_date = forms.DateField( widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), label="Fecha Estimada de Entrega", required=False ) class Meta: model = PurchaseOrder fields = ['requisition', 'vendor', 'project', 'issue_date', 'expected_delivery_date', 'payment_terms', 'delivery_address', 'terms_and_conditions', 'notes'] widgets = { 'requisition': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'vendor': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'project': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'payment_terms': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Ej: 30 Días Contra Factura', 'required': 'required'}), 'delivery_address': forms.TextInput(attrs={'class': 'form-control', 'id': 'id_delivery_address', 'required': 'required'}), 'terms_and_conditions': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}), } def clean(self): cleaned_data = super().clean() issue = cleaned_data.get('issue_date') expected = cleaned_data.get('expected_delivery_date') if issue and expected and expected < issue: raise forms.ValidationError("La fecha de entrega esperada no puede ser anterior a la fecha de emisión.") return cleaned_data class GoodsReceiptForm(forms.ModelForm): receipt_date = forms.DateField( widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'required': 'required'}), label="Fecha de Recepción" ) class Meta: model = GoodsReceipt fields = ['purchase_order', 'dispatch_guide_number', 'receipt_date', 'technical_conformity', 'notes'] widgets = { 'purchase_order': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'dispatch_guide_number': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'N° Guía de Despacho o Acta Terreno', 'required': 'required'}), 'technical_conformity': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones de estado físico, embalaje o pruebas de funcionamiento...'}), } class ThreeWayMatchForm(forms.ModelForm): dte_invoice_date = forms.DateField( widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control', 'required': 'required'}), label="Fecha Emisión Factura SII" ) class Meta: model = ThreeWayMatch fields = [ 'purchase_order', 'goods_receipt', 'dte_invoice_number', 'dte_invoice_rut', 'dte_invoice_date', 'dte_net_amount', 'dte_tax_amount', 'dte_total_amount', 'resolution_notes' ] widgets = { 'purchase_order': forms.Select(attrs={'class': 'form-control', 'id': 'id_po_select', 'required': 'required'}), 'goods_receipt': forms.Select(attrs={'class': 'form-control', 'required': 'required'}), 'dte_invoice_number': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Ej: 45290', 'required': 'required', 'pattern': '^[0-9A-Za-z\-]+$'}), 'dte_invoice_rut': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Ej: 76.543.210-K', 'required': 'required', 'pattern': '^\d{1,2}\.\d{3}\.\d{3}-[\dkK]$'}), 'dte_net_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '1', 'id': 'id_dte_net', 'required': 'required', 'min': '0'}), 'dte_tax_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '1', 'id': 'id_dte_tax', 'required': 'required', 'min': '0'}), 'dte_total_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '1', 'id': 'id_dte_total', 'required': 'required', 'min': '0'}), 'resolution_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2, 'placeholder': 'Comentarios de auditoría o justificación de diferencias...'}), } def clean_dte_invoice_rut(self): rut = self.cleaned_data.get('dte_invoice_rut') if not rut: raise forms.ValidationError("Este campo es obligatorio.") return rut.upper() def clean(self): cleaned_data = super().clean() net = cleaned_data.get('dte_net_amount') tax = cleaned_data.get('dte_tax_amount') total = cleaned_data.get('dte_total_amount') if net is not None and tax is not None and total is not None: if net + tax != total: raise forms.ValidationError("El Monto Total debe ser igual a la suma del Monto Neto y el Monto IVA.") return cleaned_data