feat: update service report exports and calendar bulk downloads
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

Align service-record PDF/DOCX generation with task-level data and naming.

- Use Task.service_report_note as report note source
- Use Task.scheduled_date for service-record dates
- Rename per-task exports to MT...SN... format
- Add monthly SN/PN ZIP download endpoints with 7-day retention
- Add calendar 'Preuzmi sve' modal with both bulk download actions
- Prefill work-hours day/date from scheduled_date for empty tables
- Remove duplicate note table and clean extra DOCX page breaks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-08-06 10:32:03 +02:00
parent 9a7c7e3163
commit 594881f6cd
7 changed files with 474 additions and 44 deletions

View File

@@ -6,8 +6,10 @@ from django.utils import timezone
from rest_framework.test import APIClient
import uuid
from io import BytesIO
from datetime import timedelta
from datetime import date, timedelta
import zipfile
from pathlib import Path
from django.conf import settings
from PIL import Image
from modules.fleet.models import Vehicle, WorkOrder, WorkOrderInvoice, VehicleServiceRecord, VehicleServicePhoto, GeneratedWorkOrderPdf
@@ -55,13 +57,17 @@ class WorkOrderImagesEndpointTests(TestCase):
self.work_order = WorkOrder.objects.create(
vehicle=self.vehicle,
creator=self.user,
display_code='MT150726',
purpose='kontrola',
notes='Napomena iz putnog naloga',
)
self.task = Task.objects.create(
title='Test servisni zadatak',
assigned_to=self.user,
vehicle=self.vehicle,
work_order=self.work_order,
scheduled_date=date(2033, 12, 24),
service_report_note='Napomena iz taska',
)
self.service_record = VehicleServiceRecord.objects.create(
@@ -271,3 +277,51 @@ class WorkOrderImagesEndpointTests(TestCase):
settings_xml = archive.read('word/settings.xml').decode('utf-8')
self.assertNotIn('documentProtection', settings_xml)
self.assertNotIn('writeProtection', settings_xml)
def test_task_specific_service_records_docx_uses_task_note_and_scheduled_date(self):
response = self.client.get(
f"/api/fleet/work-orders/{self.work_order.pk}/service-records-docx/?task_id={self.task.pk}"
)
self.assertEqual(response.status_code, 200, response.content)
content_disposition = response.get('Content-Disposition', '')
self.assertIn('MT150726.SN-Test_servisni_zadatak.docx', content_disposition)
archive = zipfile.ZipFile(BytesIO(response.content))
document_xml = archive.read('word/document.xml').decode('utf-8')
self.assertIn('Napomena iz taska', document_xml)
self.assertNotIn('Napomena iz putnog naloga', document_xml)
self.assertIn('24.12.2033', document_xml)
self.assertEqual(document_xml.count('w:type="page"'), 1)
def test_monthly_service_tasks_archive_returns_zip_with_task_docx(self):
response = self.client.get('/api/fleet/reports/monthly-service-tasks-archive/?year=2033&month=12')
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'application/zip')
archive = zipfile.ZipFile(BytesIO(response.content))
names = archive.namelist()
self.assertIn('MT150726.SN-Test_servisni_zadatak.docx', names)
def test_monthly_work_orders_archive_contains_work_orders_and_invoices_folder(self):
WorkOrderInvoice.objects.create(
work_order=self.work_order,
naziv_racuna='Prosinac račun',
datum='2033-12-10',
image=create_test_pdf('racun-prosinac.pdf'),
created_by=self.user,
)
before_files = set((Path(settings.MEDIA_ROOT) / 'fleet' / 'generated_archives').glob('*.zip'))
response = self.client.get('/api/fleet/reports/monthly-work-orders-archive/?year=2033&month=12')
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'application/zip')
archive = zipfile.ZipFile(BytesIO(response.content))
names = archive.namelist()
self.assertIn('MT150726.work-order.pdf', names)
self.assertTrue(any(name.startswith('Racuni/MT150726/') for name in names), names)
generated_archives_dir = Path(settings.MEDIA_ROOT) / 'fleet' / 'generated_archives'
self.assertTrue(generated_archives_dir.exists())
after_files = set(generated_archives_dir.glob('*.zip'))
self.assertTrue(after_files - before_files)