fix: dodaj fotografije servisnih zapisa u DOCX
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled

DOCX izvjestaj servisnih zapisa sada ugradjuje povezane fotografije nakon kompresije slike za manju velicinu dokumenta.

Dodan je regresijski test koji provjerava da generirani DOCX sadrzi embedded media datoteke.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-31 18:09:16 +02:00
parent 51a65f9b80
commit 16264cb8a9
2 changed files with 70 additions and 0 deletions

View File

@@ -7,9 +7,11 @@ from rest_framework.test import APIClient
import uuid
from io import BytesIO
from datetime import timedelta
import zipfile
from PIL import Image
from modules.fleet.models import Vehicle, WorkOrder, WorkOrderInvoice, VehicleServiceRecord, VehicleServicePhoto, GeneratedWorkOrderPdf
from modules.task_management.models import Task
def create_test_image(filename='test.jpg', size=(40, 40), color='red'):
@@ -55,9 +57,16 @@ class WorkOrderImagesEndpointTests(TestCase):
creator=self.user,
purpose='kontrola',
)
self.task = Task.objects.create(
title='Test servisni zadatak',
assigned_to=self.user,
vehicle=self.vehicle,
work_order=self.work_order,
)
self.service_record = VehicleServiceRecord.objects.create(
vehicle=self.vehicle,
task=self.task,
performed_by=self.user,
description='Servisni zapis za test izolacije fotografija.',
service_title='Test servis',
@@ -240,3 +249,22 @@ class WorkOrderImagesEndpointTests(TestCase):
cached.refresh_from_db()
self.assertFalse(cached.is_active)
self.assertEqual(cached.status, 'failed')
def test_service_records_docx_contains_embedded_service_photos(self):
VehicleServicePhoto.objects.create(
service_record=self.service_record,
image=create_test_image('service-record-docx.jpg', size=(320, 200), color='blue'),
description='DOCX photo',
uploaded_by=self.user,
)
response = self.client.get(f"/api/fleet/work-orders/{self.work_order.pk}/service-records-docx/")
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(
response['Content-Type'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
)
archive = zipfile.ZipFile(BytesIO(response.content))
media_files = [name for name in archive.namelist() if name.startswith('word/media/')]
self.assertTrue(media_files, archive.namelist())

View File

@@ -236,6 +236,31 @@ def _compress_image_for_pdf(image_field, max_width=1280, quality=75):
pass
def _compress_image_for_docx(image_field, max_width=1600, quality=80):
"""
Pripremi sliku za python-docx kao JPEG stream razumne veličine.
"""
try:
image_field.open('rb')
with Image.open(image_field) as src:
img = src.convert('RGB')
if img.width > max_width:
ratio = max_width / float(img.width)
new_h = max(1, int(img.height * ratio))
img = img.resize((max_width, new_h), Image.LANCZOS)
buf = BytesIO()
img.save(buf, format='JPEG', quality=quality, optimize=True)
buf.seek(0)
return buf
except Exception:
return None
finally:
try:
image_field.close()
except Exception:
pass
def _get_cached_pdf(work_order, pdf_type):
now = timezone.now()
expected_filename = _pdf_filename(work_order, pdf_type)
@@ -1759,6 +1784,8 @@ def _build_work_order_docx_bytes(work_order):
def _build_work_order_service_records_docx_bytes(work_order):
from docx.shared import Cm
doc = _create_docx_document(SERVICE_REPORT_DOCX_TEMPLATE_NAME)
vehicle = work_order.vehicle
client_name = getattr(getattr(vehicle, 'client', None), 'name', None) or '-'
@@ -1805,6 +1832,7 @@ def _build_work_order_service_records_docx_bytes(work_order):
vehicle_id=work_order.vehicle_id,
)
.select_related('performed_by', 'task')
.prefetch_related('photos')
.order_by('service_date', 'created_at')
)
records_by_task = {
@@ -1915,6 +1943,20 @@ def _build_work_order_service_records_docx_bytes(work_order):
doc.add_paragraph(f"Korišteni dijelovi: {record.parts or '-'}")
doc.add_paragraph(f"Trošak: {record.cost or '-'} EUR")
doc.add_paragraph(f"Kilometraža: {record.mileage if record.mileage is not None else '-'}")
photos = [photo for photo in record.photos.filter(is_active=True).all() if photo.image]
if photos:
doc.add_paragraph('Fotografije:')
for photo in photos:
image_stream = _compress_image_for_docx(photo.image)
if image_stream is None:
continue
try:
doc.add_picture(image_stream, width=Cm(16))
except Exception:
continue
photo_caption = str(photo.description or '').strip()
if photo_caption:
doc.add_paragraph(photo_caption)
doc.add_paragraph('')
if not has_records:
doc.add_paragraph('Nema povezanih servisnih zapisa za ovaj putni nalog.')