interaktivna Tablica radnih sati, db
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

This commit is contained in:
mariomitte
2026-07-13 00:36:28 +02:00
parent 70151f1225
commit 3ad536a23e
22 changed files with 1629 additions and 174 deletions

View File

@@ -103,7 +103,9 @@ def _work_order_related_tasks_queryset(work_order):
task_ids = list({*direct_task_ids, *inferred_task_ids})
if not task_ids:
return Task.objects.none()
return Task.objects.filter(id__in=task_ids, is_active=True).select_related('assigned_to', 'vehicle', 'work_order').order_by('-created_at')
return Task.objects.filter(id__in=task_ids, is_active=True).select_related(
'assigned_to', 'vehicle', 'work_order', 'work_hours_table'
).order_by('-created_at')
def _can_access_service_record(user, service_record):
service_record_id = getattr(service_record, 'pk', service_record)
@@ -523,31 +525,6 @@ def _build_work_order_service_records_pdf(work_order):
HEADER_H = 86
FOOTER_H = 72
MARGIN = 28
DAY_MAP = {
0: 'PO',
1: 'UT',
2: 'SR',
3: 'ČET',
4: 'PET',
5: 'SUB',
6: 'NED',
}
def _fmt_date(value):
if not value:
return '-'
return value.strftime('%d.%m.%Y')
def _fmt_time(value):
if not value:
return '-'
return value.astimezone(timezone.get_current_timezone()).strftime('%H:%M')
def _hours_between(start_at, end_at):
if not start_at or not end_at:
return 0.0
delta = end_at - start_at
return max(0.0, round(delta.total_seconds() / 3600.0, 2))
service_rows = list(
VehicleServiceRecord.objects.filter(
@@ -559,8 +536,6 @@ def _build_work_order_service_records_pdf(work_order):
.order_by('service_date', 'created_at')
)
related_tasks = list(_work_order_related_tasks_queryset(work_order))
travel_hours = _hours_between(work_order.travel_start_at, work_order.travel_end_at)
travel_km = work_order.distance if work_order.distance is not None else 0
task_titles = [row.task.title for row in service_rows if getattr(row, 'task', None) and row.task.title]
notes_text = ' | '.join(task_titles[:2]) if task_titles else (work_order.notes or '')
@@ -627,6 +602,58 @@ def _build_work_order_service_records_pdf(work_order):
description_style.fontSize = 8.2
description_style.leading = 10.4
def _normalize_hours_table_row(row):
if not isinstance(row, dict):
return None
return {
'day': str(row.get('day', '') or '').strip() or '-',
'date': str(row.get('date', '') or '').strip() or '-',
'work_time': " - ".join(
[
str(row.get('work_time_from', '') or '').strip() or '-',
str(row.get('work_time_to', '') or '').strip() or '-',
]
),
'travel_time': " - ".join(
[
str(row.get('travel_time_from', '') or '').strip() or '-',
str(row.get('travel_time_to', '') or '').strip() or '-',
]
),
'break_hours': str(row.get('break_hours', '') or '').strip() or '-',
'work_hours': str(row.get('work_hours', '') or '').strip() or '-',
'travel_hours': str(row.get('travel_hours', '') or '').strip() or '-',
'places': "\n".join([
f"Polazak: {str(row.get('departure_place', '') or '').strip() or '-'}",
f"Dolazak: {str(row.get('arrival_place', '') or '').strip() or '-'}",
]),
'vehicle_km': str(row.get('vehicle_km', '') or '').strip() or '-',
}
def _parse_decimal(value):
if value in (None, ''):
return 0.0
try:
return float(str(value).replace(',', '.'))
except (TypeError, ValueError):
return 0.0
def _format_decimal(value):
return f"{float(value):.1f}".replace('.', ',')
normalized_hours_rows = []
for task in related_tasks:
table_data = getattr(getattr(task, 'work_hours_table', None), 'data', None)
if not isinstance(table_data, dict):
continue
rows = table_data.get('rows', [])
if not isinstance(rows, list):
continue
for raw_row in rows:
normalized = _normalize_hours_table_row(raw_row)
if normalized:
normalized_hours_rows.append(normalized)
y = content_top
table1 = [
@@ -683,42 +710,29 @@ def _build_work_order_service_records_pdf(work_order):
"Kilometri\nvozila",
]
table3_rows = [table3_headers]
total_hours_travel = 0.0
total_vehicle_km = 0.0
if work_order.travel_start_at or work_order.travel_end_at:
day_idx = work_order.travel_start_at.weekday() if work_order.travel_start_at else None
table3_rows.append([
DAY_MAP.get(day_idx, '-'),
_fmt_date(work_order.travel_start_at.date() if work_order.travel_start_at else work_order.date),
"-",
f"{_fmt_time(work_order.travel_start_at)} - {_fmt_time(work_order.travel_end_at)}",
"-",
"-",
str(travel_hours).replace('.', ','),
work_order.location or '-',
str(travel_km or '-'),
])
if normalized_hours_rows:
for row in normalized_hours_rows:
table3_rows.append([
row['day'],
row['date'],
row['work_time'],
row['travel_time'],
row['break_hours'],
row['work_hours'],
row['travel_hours'],
row['places'],
row['vehicle_km'],
])
total_hours_travel += _parse_decimal(row['travel_hours'])
total_vehicle_km += _parse_decimal(row['vehicle_km'])
else:
for _ in range(12):
table3_rows.append(["-", "-", "-", "-", "-", "-", "-", "Polazak: -\nDolazak: -", "-"])
for record in service_rows[:6]:
day_idx = record.service_date.weekday() if record.service_date else None
table3_rows.append([
DAY_MAP.get(day_idx, '-'),
_fmt_date(record.service_date),
"-",
"-",
"-",
"-",
"-",
getattr(record.vehicle, 'registration_number', '-') or '-',
str(record.mileage or '-'),
])
if len(table3_rows) == 1:
table3_rows.append(["-", "-", "-", "-", "-", "-", "-", "-", "-"])
total_hours_travel = str(travel_hours).replace('.', ',')
for _ in range(6):
table3_rows.append(["", "", "", "", "", "", "", "", ""])
table3_rows.append(["UKUPNO", "", "", "", "", "", total_hours_travel, "", str(travel_km or 0)])
table3_rows.append(["UKUPNO", "", "", "", "", "", _format_decimal(total_hours_travel), "", str(int(total_vehicle_km) if total_vehicle_km.is_integer() else total_vehicle_km).replace('.', ',')])
y = draw_table(
y,
table3_rows,
@@ -739,6 +753,8 @@ def _build_work_order_service_records_pdf(work_order):
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
('LEFTPADDING', (0, -1), (5, -1), 12),
('VALIGN', (7, 1), (7, -2), 'TOP'),
('ALIGN', (7, 1), (7, -2), 'LEFT'),
]),
)
@@ -1449,6 +1465,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
'description': task.description,
'assigned_to_name': _user_display_name(task.assigned_to),
'vehicle_registration': getattr(task.vehicle, 'registration_number', None),
'work_hours_table': getattr(getattr(task, 'work_hours_table', None), 'data', None),
'service_records': records_payload,
})