Smartphone photos often have EXIF orientation metadata (tags 6, 8, 3)
that rotates the display but doesn't transform the pixel data. When
PDFs/DOCX embedded images without applying this metadata, they appear
rotated 90/180/270 degrees.
Use ImageOps.exif_transpose(img) before resize/convert in both
_compress_image_for_pdf() and _compress_image_for_docx() to read EXIF
orientation and transpose the actual pixel data accordingly. This is
a standard Pillow function and is a no-op for images without EXIF.
Add regression test test_compress_image_for_docx_handles_exif_orientation
to verify images with EXIF tag 0x0112 (orientation=6) are transposed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previous limit of 8 MP was rejecting standard smartphone photos (12-20 MP),
causing service record PDFs/DOCX documents to be generated without photos.
This was a regression from the image timeout fix: the megapixel guard was
designed to prevent processing of pathologically large files (preventing
worker timeouts), but the threshold was set too aggressively.
Increase limit to 30 MP to allow standard device cameras while still
rejecting extreme outliers that would cause timeout/OOM.
Add regression test test_compress_image_for_docx_keeps_standard_phone_photos
to verify 12 MP photos are accepted.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause was WORKER TIMEOUT, not OOM:
- Gunicorn sends SIGABRT to workers that exceed 60s timeout
- SIGABRT handler calls sys.exit(1) raising SystemExit(BaseException)
- except Exception does NOT catch SystemExit, so the worker crashes
Two fixes:
1. except BaseException — catches SystemExit so the worker survives
and gracefully skips the image (returns None) instead of dying
2. Image.BILINEAR instead of LANCZOS — orders of magnitude faster
for large images, ensures processing completes well within 60s
3. Reduce max_width 1600->800, megapixel limit 20->8 MP, optimize=True removed
(these reduce processing time further)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previous draft()+convert()+thumbnail() ordering still caused OOM for
non-JPEG formats (PNG/HEIC) because draft() is a no-op for those formats,
and convert('RGB') forces a full pixel decode regardless.
Replace with thumbnail()-first ordering:
- thumbnail() internally calls draft() for JPEG before decoding
- thumbnail() performs in-place resize without allocating a second
full-resolution buffer
- convert('RGB') is then called on the already-small image (safe for
any format)
Add _COMPRESS_IMAGE_MAX_MEGAPIXELS guard (20 MP): read image dimensions
from headers only (no pixel decode) and return None for images that
exceed the limit. This prevents OOM even for pathologically large files
where draft() provides no benefit (e.g. PNG, TIFF, HEIC).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PIL img.resize() on high-resolution photos was exhausting worker RAM,
causing Gunicorn to SIGKILL the worker mid-request (production OOM crash).
Replace the explicit resize() path in both _compress_image_for_pdf() and
_compress_image_for_docx() with:
- Image.draft() — hints the JPEG decoder to decode at a lower resolution
- Image.thumbnail() — in-place resize that avoids allocating a second
full-resolution buffer
This keeps peak memory proportional to the output size instead of the
original file size, preventing the worker from being killed when
processing multi-megapixel service-record photos.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevent service-record inference from pulling tasks from other work orders
that share the same vehicle. This keeps task-service-context, service
reports, and related work-order widgets isolated to the selected nalog.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure admin/API deletes and updates of service records, photos, and
attachments invalidate the cached service-records PDF. Also harden cached
PDF lookup/download paths so a missing file is treated as stale cache
instead of a 500.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevent 500 errors on travel-expenses-table when work-hour entries mix
naive and timezone-aware datetimes. Normalize all candidate datetimes to
a consistent timezone before min/max comparisons.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
apply_patch previously duplicated the install event listener, causing a
ServiceWorker script evaluation error on load. Deduplicate and hoist
precacheShell() before the install listener.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BaseModel defines id with uuid4 default, help_text, and verbose_name on
created_at/updated_at/is_active. Migration 0036 omitted these, causing
Django to detect pending model changes and refuse to migrate on the server.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce a dedicated travel-expenses table for work orders so Broj sati, Količina dnevnica, and Iznos dnevnice can be reviewed and edited before PDF generation. The PDF now uses the stored travel-expenses values, with default HR rate and cache invalidation on updates.
Aggregate task work-hour rows into travel-cost calculations and monthly servicer reporting, and keep the frontend calendar aligned with the task work-hours source.
Use prefix path matching so nested putni-nalozi routes highlight the correct nav item, load current user data in Navbar when auth token exists, and group PDF/DOCX work-order download buttons in one action block.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expose task details from the service-context task list and restore visible crane data editing within the task flow.
Add crane working-hours and mileage editing to the task creation modal when a service context is selected, and improve the task details modal action so servicers can update crane data directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark stale pending fleet archives as failed after a timeout window and create a fresh request so users receive completion notifications and can download ZIP files again.
Add regression coverage for replacing stale pending service-task archive requests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Group 'Preuzmi mjesečni izvještaj' and 'Preuzmi ZIP' buttons together in the calendar toolbar
- Close the ZIP modal immediately on action click so the user is no longer blocked in the sidebar
- Backend: include generated_archive_id in the 'requested' notification so the frontend can track the specific archive lifecycle
- Frontend: refresh notifications immediately after archive request and start targeted polling (every 10s) until a completed/failed notification arrives for that archive ID
- Add previous/next pagination to the service records table (page mode) when no service context is selected, showing all records 10 per page (matching the work orders table pattern)
- Tests: assert generated_archive_id is present in both requested and completed notifications
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Invalidate cached monthly service-task ZIP archives so exports are rebuilt with the latest task/work-order/vehicle header data. Keep stale detection for work-order archives and add regression tests for per-task header isolation and stale cache replacement.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring back the calendar widget controls for triggering monthly ZIP archive generation for service tasks and work orders, including the modal actions and loading states.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the monthly fleet archive task and routes so generated ZIP downloads work again. Also populate service report DOCX headers from the selected task/work-order context and cover the direct and archived export flows with regression tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure /putni-nalozi/racuni reads service-report context from the selected task's own work order instead of reusing the parent page work-order data for every task.
- task-service-context now returns task_work_order metadata per task
- work hours modal reads location and travel data from task_work_order
- tasks without a linked work order fall back to None
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid HTTP/2 streaming failures on large archive downloads by returning the stored ZIP as a buffered HttpResponse with explicit Content-Length.
Also update archive download tests to support both buffered and streaming response objects.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
Enable generating service-record documents for a single task on /putni-nalozi/racuni while preserving existing full work-order export.
- Backend service-records PDF/DOCX endpoints now accept optional task_id and validate the task belongs to the selected work order.
- Document builders support scoped task lists, and per-task downloads use task-specific filenames.
- Frontend task cards now include per-task PDF/DOCX actions, while top-level buttons are clarified as exporting all service records.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Backend:
- Added service_report_note TextField to Task model (migration 0012)
- Exposed service_report_note in TaskSerializer
- task_service_context: fixed service record filter from
work_order.vehicle_id -> task.vehicle_id (bug: cross-crane work orders
returned wrong records for non-primary crane)
- task_service_context: include service_report_note in each task payload
- _build_work_order_service_records_docx_bytes: use per-task
service_report_note (fallback: work_order.notes); fix service records
query to use task vehicle ids for cross-crane support
- PDF generator: same per-task notes and cross-crane vehicle fix
Frontend:
- WorkOrderInvoicesPdfPage: removed global 'Uredi tablicu radnih sati' /
'Uredi tekst napomene' buttons that always opened tasks[0]; replaced
with per-task buttons inside each task article
- WorkOrderInvoicesPdfPage: separate editingTaskNote state / handler
that calls updateTaskServiceReportNote (PATCH task.service_report_note)
- WorkOrderServiceNotesModal: now receives task prop instead of workOrder;
reads/writes task.service_report_note; shows task title in header
- TaskWorkHoursTableModal: added workOrder prop; renders read-only amber
info block with travel dates, mileage, and servicer vehicle data
- fleetDashboardStore: added updateTaskServiceReportNote() function
- WorkOrderInvoicesPdfPage: show saved service_report_note inline on
each task card for quick reference
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The dropdown was anchored to right-0 of the <details> element.
On mobile, the trigger button sits near the left side of the viewport
so right-0 caused the panel to extend leftward off-screen.
Changing to left-0 opens the panel rightward from the button, keeping
it fully visible.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Navbar mobile dropdown
- Fixed potential clipping: added z-50 and top-full to dropdown panel
- Added overflow-visible to nav wrapper so dropdown is not cut off
- Wider panel (min-w-52), larger tap targets (py-2.5 per link)
- Active route indicated by colored dot + bold text
- Trigger button now shows hamburger icon + page name
2. KPI cards (section-dashboard)
- grid-cols-2 on mobile (was single column), sm:3, xl:5
- Reduced padding px-3 py-2.5 (was p-4) and font text-xl (was text-2xl)
- flex-col gap-0.5 layout for tighter vertical rhythm
3. Test Toast removed
- Deleted ToastTrigger.jsx component entirely
- Removed import and conditional render from Layout.astro
- Removed unused isDev variable from Layout.astro
4. AnimatedDataTable improvements
- Replaced plain-text loading placeholder with animated skeleton rows
(pulse animation with variable-width grey bars per column)
- Added horizontal scroll fade indicators: left/right gradient overlays
appear automatically via ResizeObserver + scroll event listener
- scroll-smooth touch scrolling on mobile (WebkitOverflowScrolling, scrollbarWidth)
- Added scope=col on th elements and aria-label / aria-busy props
- Slightly snappier spring config (tension 260, friction 28)
- leave animation reduced to 4px shift (was 8px) for less jarring removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace 4 static KPI cards with 5 interactive cards
- 'Otvoreni nalozi' and 'Servisi danas' are now clickable links
- Added context-aware subtotals (u kontekstu) when crane context is set
- Replaced 'Upozorenja' (mileage-based, irrelevant for cranes) with 'Bez putnog naloga'
- 'Dovrseni nalozi' now shows completion percentage
- Added new card 'Moji zadaci danas' (tasks assigned to current user, today)
- Added crane context filter toggle on the tasks table (Sve dizalice / Odabrani kontekst)
- Service records page now shows last 10 records as fallback when no crane context is selected
- Moved SyncLogPanel into a collapsible <details> element
- Removed dead code: PAGE_MODE_TO_SECTION, activeSection/setActiveSection state,
navbar:navigate event handler, and DashboardTopbar nav tab block (showSectionNav
was always false; navigation is handled by Navbar.jsx)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the linked work order visible in task details and force-refresh tasks immediately after work order changes so the dashboard table updates without a hard refresh.
Widget Bilješke sada omogućuje odabir više članova tima (ne samo
servisera), a backend validacija i testovi su usklađeni.
Dodatno, nakon realtime service_note notifikacije sada se osvježava
service-notes store, te create flow više ne briše lokalne bilješke kada
odgovor vrati praznu listu.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uz naziv dizalice (make model) u zagradi prikazuje ime klijenta/tvrtke
za svaki redak (sharedCranes i fallback slučaj).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Umjesto odvajanja s ' • ' u istom retku, svaka dizalica i njen SN
prikazuju se u zasebnom <span> retku unutar flex-col containera.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Kada putni nalog dijele taskovi više dizalica, stupci DIZALICA i SN
u tablici Putni nalozi dizalica prikazuju sve uključene dizalice
odvojene s ' • ' umjesto samo one za koju je WO kreiran.
- Dodana workOrderSharedCranes mapa (workOrderId -> {vehicleId -> crane})
izgrađena iz task.work_order + task.vehicle veza
- Render stupaca DIZALICA i SN koristi mapu ako postoje shared cranes,
inače fallback na craneInfo s originalnog WO-a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- scopedWorkOrders: u context modu uključi i putne naloge koji su dodijeljeni
taskovima kontekstne dizalice (cross-crane shared WO), a ne samo one čiji
vehicle === hydratedSelectedVehicleId
- selectedTaskWorkOrders: prikaži samo otvorene naloge (status != closed)
u dropdownu za dodjelu putnog naloga tasku
- Ažuriran emptyMessage tablice da sugerira prebacivanje na 'svi nalozi'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Problem: serviser nije mogao odabrati putni nalog kreiran za drugu dizalicu
jer je frontend filtrirao naloge po aktivnom crane kontekstu, a backend
serializer je bacao ValidationError ako WO.vehicle != task.vehicle.
Promjene:
- TaskSerializer: uklonjena cross-crane validacija - WO moze pripadati
drugoj dizalici od one na tasku (cross-crane sharing)
- FleetDashboardShell: serviceRecordWorkOrders i selectedTaskWorkOrders
vise ne filtriraju po vehicle ID-u, prikazuju sve otvorene naloge
- TaskServiceRecordsModal: gumb i modal za izmjenu podataka dizalice
(radni sati, kilometraza) premjesten iz WorkOrderDetailModal
- WorkOrderDetailModal: uklonjen gumb 'Izmijeni podatke dizalice'
- Dodana 3 nova backend testa za cross-crane WO dodjelu (15/15 prolaze)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- TaskCreateModal: auto-pretselektira jedini otvoreni nalog za danas pri otvaranju modala
- Dropdown labele prikazuju datum naloga i oznacavaju danasnji nalog sa zvjezdicom (★)
- Dodana napomena da vise taskova moze dijeliti isti putni nalog
- TaskServiceRecordsModal: danasnji nalozi sortirani na vrh liste
- Datum prikazan u dropdownu i u prikazu trenutno odabranog naloga
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generirani servisni DOCX preuzimao je document/write zaštitu iz predloška pa je Word datoteka bila neurediva.\n\nDodano je uklanjanje documentProtection/writeProtection/readOnlyRecommended postavki pri kreiranju DOCX dokumenta te regresijski test koji provjerava da zaštita nije prisutna u word/settings.xml.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
Dodaje uredski unos pocetka i kraja rada u sidebaru mjesecnog izvjestaja servisera i automatski racuna redovan rad iz unesenog raspona. Zadrzava postojece predpopunjavanje za rad u uredu i validira neispravan vremenski raspon.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uklanja ovisnost o workOrders.invoices i dohvaća račune direktno kroz API filtriran po mjesecu. Dodaje i display code putnog naloga u serializer kako bi prikaz u izvještaju ostao potpun.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Dodan model MonthlyServicerDayEntry s automatskom popunom prema tipu:
office (08:00-16:00, Zagreb ured), vacation i sick_leave (8h bez vremena)
- Migracija 0034_monthlyservicerdayentry za novu tablicu s UniqueConstraint
- Novi ViewSet + serializer + endpoint fleet/monthly-servicer-day-entries/
(upsert: POST za novi dan ili update za isti dan ako vec postoji)
- Backend DOCX report (_build_monthly_servicer_report_rows) sad generira
redak za SVAKI dan u mjesecu: taskovi > rucni unos > prazan red
- TaskCalendarWidget potpuno prepisan bez mojibake znakova
(zamjena UTF-8 specijalnih znakova ASCII ekvivalentima u string literalima)
- Tablica izvjestaja servisera: klikom na red bez naloga otvara se overlay
s tri opcije (Rad u uredu / Bolovanje / Godisnji)
- TaskCalendarPortal: dodan calendarWorkOrders computed (svi statusi)
kako bi report imao potpune podatke o radnom vremenu
- fetchMonthlyServicerEntries i upsertMonthlyServicerEntry u store-u
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>