2025-12-05 22:17:50 +01:00
|
|
|
import { WearPart, MaintenanceHistory } from '@prisma/client'
|
|
|
|
|
|
|
|
|
|
export function calculateServiceStatus(
|
2025-12-05 22:36:58 +01:00
|
|
|
part: WearPart & { maintenanceHistory: MaintenanceHistory[] },
|
|
|
|
|
bikeCurrentMileage?: number
|
2025-12-05 22:17:50 +01:00
|
|
|
): {
|
2025-12-05 22:36:58 +01:00
|
|
|
status: 'OK' | 'WARNING' | 'CRITICAL' | 'OVERDUE'
|
2025-12-05 22:17:50 +01:00
|
|
|
remainingKm: number
|
|
|
|
|
percentageUsed: number
|
2025-12-05 22:36:58 +01:00
|
|
|
isOverdue: boolean
|
2025-12-05 22:17:50 +01:00
|
|
|
} {
|
2025-12-05 22:36:58 +01:00
|
|
|
// Use bike's current mileage if provided, otherwise use latest maintenance mileage
|
2025-12-05 22:17:50 +01:00
|
|
|
const latestMaintenance = part.maintenanceHistory[0]
|
2025-12-05 22:36:58 +01:00
|
|
|
const currentMileage = bikeCurrentMileage ?? (latestMaintenance?.mileage ?? part.installMileage)
|
2025-12-05 22:17:50 +01:00
|
|
|
const kmSinceInstall = currentMileage - part.installMileage
|
|
|
|
|
const remainingKm = part.serviceInterval - kmSinceInstall
|
|
|
|
|
const percentageUsed = (kmSinceInstall / part.serviceInterval) * 100
|
2025-12-05 22:36:58 +01:00
|
|
|
const isOverdue = remainingKm < 0
|
2025-12-05 22:17:50 +01:00
|
|
|
|
2025-12-05 22:36:58 +01:00
|
|
|
let status: 'OK' | 'WARNING' | 'CRITICAL' | 'OVERDUE' = 'OK'
|
|
|
|
|
if (isOverdue) {
|
|
|
|
|
status = 'OVERDUE'
|
|
|
|
|
} else if (percentageUsed >= 90) {
|
2025-12-05 22:17:50 +01:00
|
|
|
status = 'CRITICAL'
|
|
|
|
|
} else if (percentageUsed >= 75) {
|
|
|
|
|
status = 'WARNING'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
status,
|
|
|
|
|
remainingKm: Math.max(0, remainingKm),
|
2025-12-05 22:36:58 +01:00
|
|
|
percentageUsed: Math.min(100, Math.max(0, percentageUsed)),
|
|
|
|
|
isOverdue,
|
2025-12-05 22:17:50 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function calculateTotalCosts(
|
|
|
|
|
parts: (WearPart & { maintenanceHistory: MaintenanceHistory[] })[]
|
|
|
|
|
): number {
|
|
|
|
|
return parts.reduce((total, part) => {
|
|
|
|
|
const partCost = part.cost ?? 0
|
|
|
|
|
const maintenanceCost = part.maintenanceHistory.reduce(
|
|
|
|
|
(sum, m) => sum + (m.cost ?? 0),
|
|
|
|
|
0
|
|
|
|
|
)
|
|
|
|
|
return total + partCost + maintenanceCost
|
|
|
|
|
}, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function calculateAverageLifespan(
|
|
|
|
|
parts: (WearPart & { maintenanceHistory: MaintenanceHistory[] })[]
|
|
|
|
|
): number | null {
|
|
|
|
|
const replacedParts = parts.filter(
|
|
|
|
|
(p) => p.status === 'REPLACED' && p.maintenanceHistory.length > 0
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (replacedParts.length === 0) return null
|
|
|
|
|
|
|
|
|
|
const totalKm = replacedParts.reduce((sum, part) => {
|
|
|
|
|
const installHistory = part.maintenanceHistory.find(
|
|
|
|
|
(h) => h.action === 'INSTALL'
|
|
|
|
|
)
|
|
|
|
|
const replaceHistory = part.maintenanceHistory.find(
|
|
|
|
|
(h) => h.action === 'REPLACE'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (installHistory && replaceHistory) {
|
|
|
|
|
return sum + (replaceHistory.mileage - installHistory.mileage)
|
|
|
|
|
}
|
|
|
|
|
return sum
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
return totalKm / replacedParts.length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatDate(date: Date | string): string {
|
|
|
|
|
const d = typeof date === 'string' ? new Date(date) : date
|
|
|
|
|
return d.toLocaleDateString('de-DE', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatCurrency(amount: number | null | undefined): string {
|
|
|
|
|
if (amount === null || amount === undefined) return '0,00 €'
|
|
|
|
|
return new Intl.NumberFormat('de-DE', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'EUR',
|
|
|
|
|
}).format(amount)
|
|
|
|
|
}
|
|
|
|
|
|