diff --git a/backend/modules/fleet/serializers.py b/backend/modules/fleet/serializers.py index b8297b0..dfd8f4b 100644 --- a/backend/modules/fleet/serializers.py +++ b/backend/modules/fleet/serializers.py @@ -379,12 +379,14 @@ class WorkOrderSerializer(serializers.ModelSerializer): class WorkOrderInvoiceSerializer(serializers.ModelSerializer): image_url = serializers.SerializerMethodField() image_content_type = serializers.SerializerMethodField() + work_order_display_code = serializers.SerializerMethodField() class Meta: model = WorkOrderInvoice fields = [ 'id', 'work_order', + 'work_order_display_code', 'naziv_racuna', 'lokacija', 'datum', @@ -427,6 +429,15 @@ class WorkOrderInvoiceSerializer(serializers.ModelSerializer): return 'image/gif' return 'application/octet-stream' + def get_work_order_display_code(self, obj): + work_order = getattr(obj, 'work_order', None) + if work_order is None: + return None + display_code = str(getattr(work_order, 'display_code', '') or '').strip() + if display_code: + return display_code + return str(getattr(work_order, 'id', '') or '').strip() or None + def validate_image(self, value): if not value: return value diff --git a/backend/modules/fleet/views.py b/backend/modules/fleet/views.py index 63bebf1..31d1555 100644 --- a/backend/modules/fleet/views.py +++ b/backend/modules/fleet/views.py @@ -3126,16 +3126,30 @@ class WorkOrderInvoiceViewSet(viewsets.ModelViewSet): def get_queryset(self): user = self.request.user work_order_id = self.request.query_params.get('work_order_id') + year = self.request.query_params.get('year') + month = self.request.query_params.get('month') qs = WorkOrderInvoice.objects.select_related('work_order', 'work_order__vehicle', 'created_by').filter(is_active=True) + if year and month: + try: + year_value = int(year) + month_value = int(month) + except (TypeError, ValueError): + year_value = None + month_value = None + if year_value and month_value and 1 <= month_value <= 12: + qs = qs.filter( + work_order__work_order_tasks__scheduled_date__year=year_value, + work_order__work_order_tasks__scheduled_date__month=month_value, + ) if user.is_staff: if work_order_id: qs = qs.filter(work_order_id=work_order_id) - return qs + return qs.distinct() allowed_orders = _work_orders_queryset_for_user(user).values('id') qs = qs.filter(work_order_id__in=allowed_orders) if work_order_id: qs = qs.filter(work_order_id=work_order_id) - return qs + return qs.distinct() def get_serializer_context(self): context = super().get_serializer_context() diff --git a/frontend/src/components/dashboard/TaskCalendarWidget.jsx b/frontend/src/components/dashboard/TaskCalendarWidget.jsx index 729a1f2..cb2e5c6 100644 --- a/frontend/src/components/dashboard/TaskCalendarWidget.jsx +++ b/frontend/src/components/dashboard/TaskCalendarWidget.jsx @@ -3,6 +3,7 @@ import { getStatusLabel } from '../../stores/taskStore'; import { downloadMonthlyCostsReport, downloadMonthlyServiserReport, + fetchMonthlyCostInvoices, fetchMonthlyServicerEntries, upsertMonthlyServicerEntry, } from '../../stores/fleetDashboardStore'; @@ -65,7 +66,9 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders const [reportType, setReportType] = useState(null); const [downloading, setDownloading] = useState(false); const [manualEntries, setManualEntries] = useState([]); + const [costInvoices, setCostInvoices] = useState([]); const [loadingManualEntries, setLoadingManualEntries] = useState(false); + const [loadingCostInvoices, setLoadingCostInvoices] = useState(false); const [manualEntryTarget, setManualEntryTarget] = useState(null); const [savingManualEntry, setSavingManualEntry] = useState(false); @@ -148,6 +151,28 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders }; }, [reportOpen, reportType, viewYear, viewMonth]); + useEffect(() => { + if (!reportOpen || reportType !== 'costs') return undefined; + let active = true; + setLoadingCostInvoices(true); + fetchMonthlyCostInvoices(viewYear, viewMonth + 1) + .then((rows) => { + if (!active) return; + setCostInvoices(Array.isArray(rows) ? rows : []); + }) + .catch(() => { + if (!active) return; + setCostInvoices([]); + }) + .finally(() => { + if (!active) return; + setLoadingCostInvoices(false); + }); + return () => { + active = false; + }; + }, [reportOpen, reportType, viewYear, viewMonth]); + const servicerRows = useMemo(() => { if (reportType !== 'servicer') return []; const rows = []; @@ -248,28 +273,14 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders const costsRows = useMemo(() => { if (reportType !== 'costs') return []; - const monthPrefix = `${viewYear}-${String(viewMonth + 1).padStart(2, '0')}`; - const workOrderIds = new Set( - tasks - .filter((task) => { - const key = toDateKey(task.scheduled_date); - return Boolean(key && key.startsWith(monthPrefix) && task.work_order); - }) - .map((task) => String(task.work_order)) - ); - - return workOrders - .filter((workOrder) => workOrderIds.has(String(workOrder.id)) && Array.isArray(workOrder.invoices)) - .flatMap((workOrder) => ( - workOrder.invoices.map((invoice) => ([ - invoice?.datum ? new Date(invoice.datum).toLocaleDateString('hr-HR') : '-', - invoice?.naziv_racuna || '-', - invoice?.lokacija || '-', - invoice?.opis || '-', - workOrder.display_code || '-', - ])) - )); - }, [reportType, tasks, viewYear, viewMonth, workOrders]); + return costInvoices.map((invoice) => ([ + invoice?.datum ? new Date(invoice.datum).toLocaleDateString('hr-HR') : '-', + invoice?.naziv_racuna || '-', + invoice?.lokacija || '-', + invoice?.opis || '-', + invoice?.work_order_display_code || '-', + ])); + }, [costInvoices, reportType]); const selectedDayKey = selectedDay ? `${viewYear}-${String(viewMonth + 1).padStart(2, '0')}-${String(selectedDay).padStart(2, '0')}` @@ -628,7 +639,9 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders )} {reportType === 'costs' && ( - costsRows.length === 0 ? ( + loadingCostInvoices ? ( +
Ucitam troskove...
+ ) : costsRows.length === 0 ? (Nema troskova za {MONTH_NAMES[viewMonth]} {viewYear}.
diff --git a/frontend/src/stores/fleetDashboardStore.js b/frontend/src/stores/fleetDashboardStore.js index 81de8b3..12f729e 100644 --- a/frontend/src/stores/fleetDashboardStore.js +++ b/frontend/src/stores/fleetDashboardStore.js @@ -920,6 +920,13 @@ export async function fetchMonthlyServicerEntries(year, month) { return Array.isArray(payload) ? payload : (payload?.results ?? []); } +export async function fetchMonthlyCostInvoices(year, month) { + const payload = await api.get( + `fleet/work-order-invoices/?year=${year}&month=${month}` + ); + return Array.isArray(payload) ? payload : (payload?.results ?? []); +} + export async function upsertMonthlyServicerEntry(data = {}) { if (!data.entry_date) { throw new Error('Datum dnevnog unosa je obavezan.');