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:
mariomitte
2026-07-19 21:23:29 +02:00
parent d35eaeccf4
commit ae35a6041d
14 changed files with 836 additions and 19 deletions

View File

@@ -2,12 +2,14 @@ from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient
import uuid
from io import BytesIO
from datetime import timedelta
from PIL import Image
from modules.fleet.models import Vehicle, WorkOrder, WorkOrderInvoice, VehicleServiceRecord, VehicleServicePhoto
from modules.fleet.models import Vehicle, WorkOrder, WorkOrderInvoice, VehicleServiceRecord, VehicleServicePhoto, GeneratedWorkOrderPdf
def create_test_image(filename='test.jpg', size=(40, 40), color='red'):
@@ -167,3 +169,74 @@ class WorkOrderImagesEndpointTests(TestCase):
file_response = self.client.get(image_url)
self.assertEqual(file_response.status_code, 200)
self.assertEqual(file_response['Content-Type'], 'application/pdf')
def test_additional_costs_table_can_be_saved_and_total_is_computed(self):
url = f"/api/fleet/work-orders/{self.work_order.pk}/additional-costs-table/"
response = self.client.put(
url,
data={
"rows": [
{"naziv": "Cestarina", "broj_racuna": "R-100", "ukupan_iznos": "12,50", "prilog": "racun-1.pdf"},
{"naziv": "Gorivo", "broj_racuna": "R-101", "ukupan_iznos": "7.50", "prilog": "racun-2.jpg"},
]
},
format='json',
)
self.assertEqual(response.status_code, 200, response.content)
payload = response.json()
self.assertEqual(payload.get('total_for_payout'), '20.00')
rows = payload.get('data', {}).get('rows', [])
self.assertEqual(len(rows), 2)
def test_task_service_context_contains_additional_costs_table(self):
table_url = f"/api/fleet/work-orders/{self.work_order.pk}/additional-costs-table/"
save_response = self.client.put(
table_url,
data={"rows": [{"naziv": "Parking", "broj_racuna": "P-1", "ukupan_iznos": "5.00", "prilog": "parking.pdf"}]},
format='json',
)
self.assertEqual(save_response.status_code, 200, save_response.content)
context_response = self.client.get(f"/api/fleet/work-orders/{self.work_order.pk}/task-service-context/")
self.assertEqual(context_response.status_code, 200, context_response.content)
payload = context_response.json()
self.assertIn('additional_costs_table', payload)
self.assertEqual(payload['additional_costs_table']['total_for_payout'], '5.00')
def test_invoice_upload_immediately_appears_in_additional_costs_table(self):
create_response = self.client.post(
reverse('work-order-invoice-list'),
data={
'work_order': str(self.work_order.pk),
'naziv_racuna': 'Test2',
'datum': '2026-07-19',
'image': create_test_image('test2.jpg'),
},
format='multipart',
)
self.assertEqual(create_response.status_code, 201, create_response.content)
table_response = self.client.get(f"/api/fleet/work-orders/{self.work_order.pk}/additional-costs-table/")
self.assertEqual(table_response.status_code, 200, table_response.content)
rows = table_response.json().get('data', {}).get('rows', [])
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0].get('naziv'), 'Test2')
def test_additional_costs_update_invalidates_work_order_pdf_cache(self):
cached = GeneratedWorkOrderPdf.objects.create(
work_order=self.work_order,
requested_by=self.user,
pdf_type='work_order',
status='ready',
filename='MT010101.work-order.pdf',
expires_at=timezone.now() + timedelta(hours=1),
)
response = self.client.put(
f"/api/fleet/work-orders/{self.work_order.pk}/additional-costs-table/",
data={"rows": [{"naziv": "Test", "broj_racuna": "R-1", "ukupan_iznos": "10.00", "prilog": "r1.pdf"}]},
format='json',
)
self.assertEqual(response.status_code, 200, response.content)
cached.refresh_from_db()
self.assertFalse(cached.is_active)
self.assertEqual(cached.status, 'failed')