feat: dodaj dodatne troškove s OCR sinkronizacijom
Dodana je tablica DODATNI TROŠKOVI za putni nalog (backend model + API + frontend modal) s izračunom ukupnog iznosa za isplatu. Upload računa sada odmah upisuje redak u dodatne troškove, a asinkroni OCR (Celery) naknadno pokušava popuniti broj računa i iznos iz slike/PDF-a. Uvedena je invalidacija PDF cache-a nakon promjena računa/dodatnih troškova kako bi generirani PDF uvijek prikazivao najnovije podatke. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
import re
|
||||
from pathlib import Path
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from .models import (
|
||||
@@ -8,6 +9,7 @@ from .models import (
|
||||
Vehicle,
|
||||
VehicleNotification,
|
||||
WorkOrder,
|
||||
WorkOrderAdditionalCostsTable,
|
||||
WorkOrderPhoto,
|
||||
WorkOrderInvoice,
|
||||
VehicleServiceRecord,
|
||||
@@ -268,6 +270,61 @@ class WorkOrderInvoiceSerializer(serializers.ModelSerializer):
|
||||
return value
|
||||
|
||||
|
||||
class WorkOrderAdditionalCostsTableSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = WorkOrderAdditionalCostsTable
|
||||
fields = ['id', 'work_order', 'data', 'total_for_payout', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'total_for_payout', 'created_at', 'updated_at']
|
||||
|
||||
@staticmethod
|
||||
def _parse_decimal(value):
|
||||
if value in (None, ''):
|
||||
return Decimal('0.00')
|
||||
try:
|
||||
normalized = str(value).strip().replace('€', '').replace(' ', '').replace(',', '.')
|
||||
return Decimal(normalized)
|
||||
except (InvalidOperation, ValueError, TypeError):
|
||||
return Decimal('0.00')
|
||||
|
||||
def _normalize_row(self, row):
|
||||
return {
|
||||
'naziv': str(row.get('naziv', '') or '').strip(),
|
||||
'broj_racuna': str(row.get('broj_racuna', '') or '').strip(),
|
||||
'ukupan_iznos': str(row.get('ukupan_iznos', '') or '').strip(),
|
||||
'prilog': str(row.get('prilog', '') or '').strip(),
|
||||
'source_invoice_id': str(row.get('source_invoice_id', '') or '').strip(),
|
||||
}
|
||||
|
||||
def validate_data(self, value):
|
||||
if value is None:
|
||||
return {'rows': []}
|
||||
if not isinstance(value, dict):
|
||||
raise serializers.ValidationError("Podaci tablice moraju biti JSON objekt.")
|
||||
rows = value.get('rows', [])
|
||||
if not isinstance(rows, list):
|
||||
raise serializers.ValidationError({"rows": "Polje 'rows' mora biti lista redaka."})
|
||||
normalized_rows = []
|
||||
for row in rows[:100]:
|
||||
if not isinstance(row, dict):
|
||||
raise serializers.ValidationError({"rows": "Svaki redak mora biti JSON objekt."})
|
||||
normalized_rows.append(self._normalize_row(row))
|
||||
return {'rows': normalized_rows}
|
||||
|
||||
def _calculate_total_for_payout(self, data):
|
||||
rows = data.get('rows', []) if isinstance(data, dict) else []
|
||||
total = sum((self._parse_decimal(row.get('ukupan_iznos')) for row in rows), Decimal('0.00'))
|
||||
return total.quantize(Decimal('0.01'))
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['total_for_payout'] = self._calculate_total_for_payout(validated_data.get('data'))
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
data = validated_data.get('data', instance.data or {'rows': []})
|
||||
validated_data['total_for_payout'] = self._calculate_total_for_payout(data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class WorkOrderPhotoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = WorkOrderPhoto
|
||||
|
||||
Reference in New Issue
Block a user