admin felület különválasztva
This commit is contained in:
453
frontend_app/src/stores/vehicle.ts
Normal file
453
frontend_app/src/stores/vehicle.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import api from '../api/axios'
|
||||
import type { AssetFinancials, VehicleInsurancePolicy, VehicleTaxObligation } from '../types/vehicle'
|
||||
|
||||
/**
|
||||
* Vehicle data shape returned by GET /assets/vehicles
|
||||
* Based on the AssetResponse Pydantic schema from the backend.
|
||||
*/
|
||||
export interface Vehicle {
|
||||
id: string
|
||||
vin: string | null
|
||||
license_plate: string | null
|
||||
name: string | null
|
||||
catalog_id: number | null
|
||||
|
||||
// Classification
|
||||
vehicle_class: string | null
|
||||
brand: string | null
|
||||
model: string | null
|
||||
trim_level: string | null
|
||||
|
||||
// Technical specs
|
||||
fuel_type: string | null
|
||||
engine_capacity: number | null
|
||||
power_kw: number | null
|
||||
torque_nm: number | null
|
||||
cylinder_layout: string | null
|
||||
transmission_type: string | null
|
||||
drive_type: string | null
|
||||
euro_classification: string | null
|
||||
curb_weight: number | null
|
||||
max_weight: number | null
|
||||
door_count: number | null
|
||||
seat_count: number | null
|
||||
|
||||
// Status
|
||||
current_mileage: number
|
||||
condition_score: number
|
||||
status: string
|
||||
is_verified: boolean
|
||||
|
||||
// Timeline
|
||||
year_of_manufacture: number | null
|
||||
created_at: string
|
||||
updated_at: string | null
|
||||
|
||||
// Warranty
|
||||
is_under_warranty: boolean
|
||||
warranty_expiry_date: string | null
|
||||
|
||||
// Registration Documents
|
||||
registration_certificate_number?: string | null
|
||||
registration_certificate_validity?: string | null
|
||||
vehicle_registration_document_number?: string | null
|
||||
|
||||
// ── New Admin Fields ────────────────────────────────────────────────
|
||||
registration_country?: string | null
|
||||
first_domestic_registration_date?: string | null
|
||||
import_country?: string | null
|
||||
title_document_number?: string | null
|
||||
engine_number?: string | null
|
||||
number_of_previous_owners?: number | null
|
||||
|
||||
// Sales
|
||||
is_for_sale: boolean
|
||||
price: number | null
|
||||
currency: string
|
||||
|
||||
// Primary vehicle flag (from backend individual_equipment JSONB)
|
||||
is_primary: boolean
|
||||
|
||||
// Organization association (from backend AssetResponse)
|
||||
current_organization_id: number | null
|
||||
owner_organization_id: number | null
|
||||
|
||||
// ── Finance / Leasing ───────────────────────────────────────────────
|
||||
asset_financials?: AssetFinancials | null
|
||||
insurance_policies?: VehicleInsurancePolicy[] | null
|
||||
tax_obligations?: VehicleTaxObligation[] | null
|
||||
|
||||
// JSONB individual_equipment from backend (car_specs, ev_specs, dates, etc.)
|
||||
individual_equipment?: {
|
||||
car_specs?: {
|
||||
body_type?: string
|
||||
ac_type?: string
|
||||
cylinder_layout?: string
|
||||
features?: string[]
|
||||
}
|
||||
ev_specs?: {
|
||||
battery_capacity_kwh?: number
|
||||
battery_soh?: number
|
||||
ac_connector?: string
|
||||
ac_power_kw?: number
|
||||
dc_connector?: string
|
||||
dc_power_kw?: number
|
||||
range_wltp?: number
|
||||
range_highway?: number
|
||||
range_winter?: number
|
||||
fast_charge_support?: boolean
|
||||
green_plate_eligible?: boolean
|
||||
}
|
||||
dates?: {
|
||||
mot_expiry?: string
|
||||
document_expiry?: string
|
||||
}
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
export const useVehicleStore = defineStore('vehicle', () => {
|
||||
// ── State ──────────────────────────────────────────────────────────
|
||||
const vehicles = ref<Vehicle[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// ── Getters ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Sorted vehicles: primary/favorite vehicles first, then alphabetical by brand/model.
|
||||
* Used across all vehicle list displays (dashboard card, manager, etc.).
|
||||
*/
|
||||
const sortedVehicles = computed<Vehicle[]>(() => {
|
||||
return [...vehicles.value].sort((a, b) => {
|
||||
// Primary vehicles first
|
||||
if (a.is_primary && !b.is_primary) return -1
|
||||
if (!a.is_primary && b.is_primary) return 1
|
||||
// Then alphabetical by brand + model
|
||||
const nameA = `${a.brand || ''} ${a.model || ''} ${a.license_plate || ''}`.trim().toLowerCase()
|
||||
const nameB = `${b.brand || ''} ${b.model || ''} ${b.license_plate || ''}`.trim().toLowerCase()
|
||||
return nameA.localeCompare(nameB)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Actions ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetch all vehicles/assets for the current user or their organization.
|
||||
* GET /assets/vehicles
|
||||
*/
|
||||
async function fetchVehicles() {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.get('/assets/vehicles')
|
||||
vehicles.value = res.data as Vehicle[]
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Nem sikerült betölteni a járműveket. Ellenőrizd a kapcsolatot.'
|
||||
error.value = message
|
||||
console.error('[VehicleStore] fetchVehicles error:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single vehicle by its ID.
|
||||
* GET /assets/vehicles/{id}
|
||||
* Used to refresh vehicle data after odometer updates or other mutations.
|
||||
*/
|
||||
async function fetchVehicleById(id: string): Promise<Vehicle | null> {
|
||||
try {
|
||||
const res = await api.get(`/assets/vehicles/${id}`)
|
||||
const updated = res.data as Vehicle
|
||||
|
||||
// Update the vehicle in local state
|
||||
const index = vehicles.value.findIndex(v => v.id === id)
|
||||
if (index !== -1) {
|
||||
vehicles.value[index] = updated
|
||||
}
|
||||
|
||||
return updated
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] fetchVehicleById error:', err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new vehicle via POST /assets/vehicles.
|
||||
* On success, adds the new vehicle to the local state and refreshes the list.
|
||||
*/
|
||||
async function createVehicle(vehicleData: Record<string, any>): Promise<Vehicle | null> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.post('/assets/vehicles', vehicleData)
|
||||
const newVehicle = res.data as Vehicle
|
||||
|
||||
// Add the new vehicle to local state
|
||||
vehicles.value.unshift(newVehicle)
|
||||
|
||||
return newVehicle
|
||||
} catch (err: any) {
|
||||
// Log full error response for debugging (422 Validation Error details, etc.)
|
||||
if (err.response) {
|
||||
console.error('[VehicleStore] createVehicle error response:', {
|
||||
status: err.response.status,
|
||||
data: err.response.data,
|
||||
headers: err.response.headers,
|
||||
})
|
||||
} else {
|
||||
console.error('[VehicleStore] createVehicle network error:', err.message)
|
||||
}
|
||||
const detail = err.response?.data?.detail
|
||||
const message = Array.isArray(detail)
|
||||
? detail.map((d: any) => `${d.loc?.join('.')}: ${d.msg}`).join('; ')
|
||||
: detail || err.response?.data?.message || 'Nem sikerült létrehozni a járművet.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing vehicle via PUT /assets/vehicles/{id}.
|
||||
* On success, updates the vehicle in the local state.
|
||||
*/
|
||||
async function updateVehicle(id: string, vehicleData: Record<string, any>): Promise<Vehicle | null> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.put(`/assets/vehicles/${id}`, vehicleData)
|
||||
const updatedVehicle = res.data as Vehicle
|
||||
|
||||
// Update the vehicle in local state
|
||||
const index = vehicles.value.findIndex(v => v.id === id)
|
||||
if (index !== -1) {
|
||||
vehicles.value[index] = updatedVehicle
|
||||
}
|
||||
|
||||
return updatedVehicle
|
||||
} catch (err: any) {
|
||||
// Log full error response for debugging (422 Validation Error details, etc.)
|
||||
if (err.response) {
|
||||
console.error('[VehicleStore] updateVehicle error response:', {
|
||||
status: err.response.status,
|
||||
data: err.response.data,
|
||||
headers: err.response.headers,
|
||||
})
|
||||
} else {
|
||||
console.error('[VehicleStore] updateVehicle network error:', err.message)
|
||||
}
|
||||
const detail = err.response?.data?.detail
|
||||
const message = Array.isArray(detail)
|
||||
? detail.map((d: any) => `${d.loc?.join('.')}: ${d.msg}`).join('; ')
|
||||
: detail || err.response?.data?.message || 'Nem sikerült frissíteni a járművet.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimistically set a vehicle as the primary vehicle.
|
||||
* Unsets any other primary vehicle in the local state.
|
||||
*/
|
||||
function setPrimaryVehicle(vehicleId: string | number) {
|
||||
vehicles.value = vehicles.value.map(v => ({
|
||||
...v,
|
||||
is_primary: v.id === vehicleId,
|
||||
}))
|
||||
console.log('[VehicleStore] setPrimaryVehicle:', vehicleId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive (soft delete) a vehicle via POST /assets/vehicles/{id}/archive.
|
||||
* On success, removes the vehicle from the local state.
|
||||
*/
|
||||
async function archiveVehicle(id: string, finalMileage: number, archiveReason: string): Promise<boolean> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
await api.post(`/assets/vehicles/${id}/archive`, {
|
||||
final_mileage: finalMileage,
|
||||
archive_reason: archiveReason,
|
||||
})
|
||||
|
||||
// Remove the vehicle from local state
|
||||
vehicles.value = vehicles.value.filter(v => v.id !== id)
|
||||
|
||||
return true
|
||||
} catch (err: any) {
|
||||
if (err.response) {
|
||||
console.error('[VehicleStore] archiveVehicle error response:', {
|
||||
status: err.response.status,
|
||||
data: err.response.data,
|
||||
})
|
||||
} else {
|
||||
console.error('[VehicleStore] archiveVehicle network error:', err.message)
|
||||
}
|
||||
const detail = err.response?.data?.detail
|
||||
const message = Array.isArray(detail)
|
||||
? detail.map((d: any) => `${d.loc?.join('.')}: ${d.msg}`).join('; ')
|
||||
: detail || err.response?.data?.message || 'Nem sikerült eltávolítani a járművet.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Finance Query Methods ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetch asset financials for a vehicle.
|
||||
* GET /assets/vehicles/{id}/financials
|
||||
*/
|
||||
async function fetchAssetFinancials(assetId: string): Promise<AssetFinancials | null> {
|
||||
try {
|
||||
const res = await api.get(`/assets/vehicles/${assetId}/financials`)
|
||||
return res.data as AssetFinancials
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] fetchAssetFinancials error:', err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save asset financials for a vehicle (create or update).
|
||||
* PUT /assets/vehicles/{id}/financials
|
||||
*/
|
||||
async function saveAssetFinancials(assetId: string, data: Partial<AssetFinancials>): Promise<AssetFinancials | null> {
|
||||
try {
|
||||
const res = await api.put(`/assets/vehicles/${assetId}/financials`, data)
|
||||
return res.data as AssetFinancials
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] saveAssetFinancials error:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch insurance policies for a vehicle.
|
||||
* GET /assets/vehicles/{id}/insurance
|
||||
*/
|
||||
async function fetchInsurancePolicies(assetId: string): Promise<VehicleInsurancePolicy[]> {
|
||||
try {
|
||||
const res = await api.get(`/assets/vehicles/${assetId}/insurance`)
|
||||
return res.data as VehicleInsurancePolicy[]
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] fetchInsurancePolicies error:', err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new insurance policy for a vehicle.
|
||||
* POST /assets/vehicles/{id}/insurance
|
||||
*/
|
||||
async function createInsurancePolicy(assetId: string, data: Partial<VehicleInsurancePolicy>): Promise<VehicleInsurancePolicy | null> {
|
||||
try {
|
||||
const res = await api.post(`/assets/vehicles/${assetId}/insurance`, data)
|
||||
return res.data as VehicleInsurancePolicy
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] createInsurancePolicy error:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an insurance policy.
|
||||
* DELETE /assets/vehicles/{assetId}/insurance/{policyId}
|
||||
*/
|
||||
async function deleteInsurancePolicy(assetId: string, policyId: number): Promise<boolean> {
|
||||
try {
|
||||
await api.delete(`/assets/vehicles/${assetId}/insurance/${policyId}`)
|
||||
return true
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] deleteInsurancePolicy error:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tax obligations for a vehicle.
|
||||
* GET /assets/vehicles/{id}/taxes
|
||||
*/
|
||||
async function fetchTaxObligations(assetId: string): Promise<VehicleTaxObligation[]> {
|
||||
try {
|
||||
const res = await api.get(`/assets/vehicles/${assetId}/taxes`)
|
||||
return res.data as VehicleTaxObligation[]
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] fetchTaxObligations error:', err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tax obligation for a vehicle.
|
||||
* POST /assets/vehicles/{id}/taxes
|
||||
*/
|
||||
async function createTaxObligation(assetId: string, data: Partial<VehicleTaxObligation>): Promise<VehicleTaxObligation | null> {
|
||||
try {
|
||||
const res = await api.post(`/assets/vehicles/${assetId}/taxes`, data)
|
||||
return res.data as VehicleTaxObligation
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] createTaxObligation error:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tax obligation.
|
||||
* DELETE /assets/vehicles/{assetId}/taxes/{taxId}
|
||||
*/
|
||||
async function deleteTaxObligation(assetId: string, taxId: number): Promise<boolean> {
|
||||
try {
|
||||
await api.delete(`/assets/vehicles/${assetId}/taxes/${taxId}`)
|
||||
return true
|
||||
} catch (err: any) {
|
||||
console.error('[VehicleStore] deleteTaxObligation error:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
vehicles,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Getters
|
||||
sortedVehicles,
|
||||
|
||||
// Actions
|
||||
fetchVehicles,
|
||||
fetchVehicleById,
|
||||
createVehicle,
|
||||
updateVehicle,
|
||||
setPrimaryVehicle,
|
||||
archiveVehicle,
|
||||
|
||||
// Finance Actions
|
||||
fetchAssetFinancials,
|
||||
saveAssetFinancials,
|
||||
fetchInsurancePolicies,
|
||||
createInsurancePolicy,
|
||||
deleteInsurancePolicy,
|
||||
fetchTaxObligations,
|
||||
createTaxObligation,
|
||||
deleteTaxObligation,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user