// // VehicleDto.swift // AutoCatCore // // Created by Selim Mustafaev on 12.06.2024. // Copyright © 2024 Selim Mustafaev. All rights reserved. // import Foundation public struct VehicleDto: Sendable { public var brand: VehicleBrandDto? public var model: VehicleModelDto? public var color: String? public var year: Int = 0 public var category: String? public var engine: VehicleEngineDto? public var number: String = "" public var currentNumber: String? public var vin1: String? public var vin2: String? public var sts: String? public var pts: String? public var isRightWheel: Bool? public var isJapanese: Bool? public var addedDate: TimeInterval = 0 public var updatedDate: TimeInterval = 0 public var addedBy: String = "" public var photos: [VehiclePhotoDto] = [] public var ownershipPeriods: [VehicleOwnershipPeriodDto] = [] public var events: [VehicleEventDto] = [] public var osagoContracts: [OsagoDto] = [] public var ads: [VehicleAdDto] = [] public var notes: [VehicleNoteDto] = [] public var debugInfo: DebugInfoDto? public var synchronized: Bool = true public init() { } } extension VehicleDto: Identifiable { public var id: String { number } } extension VehicleDto: Decodable { enum CodingKeys: String, CodingKey { case brand case model case color case year case category case engine case number case currentNumber case vin1 case vin2 case sts case pts case isRightWheel case isJapanese case addedDate case updatedDate case addedBy case photos case ownershipPeriods case events case osagoContracts case ads case notes case debugInfo } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.brand = try container.decodeIfPresent(VehicleBrandDto.self, forKey: .brand) self.model = try container.decodeIfPresent(VehicleModelDto.self, forKey: .model) self.color = try container.decodeIfPresent(String.self, forKey: .color) self.year = try container.decodeIfPresent(Int.self, forKey: .year) ?? 0 self.category = try container.decodeIfPresent(String.self, forKey: .category) self.number = try container.decode(String.self, forKey: .number) self.engine = try container.decodeIfPresent(VehicleEngineDto.self, forKey: .engine) self.currentNumber = try container.decodeIfPresent(String.self, forKey: .currentNumber) self.vin1 = try container.decodeIfPresent(String.self, forKey: .vin1) self.vin2 = try container.decodeIfPresent(String.self, forKey: .vin2) self.sts = try container.decodeIfPresent(String.self, forKey: .sts) self.pts = try container.decodeIfPresent(String.self, forKey: .pts) self.isRightWheel = try container.decodeIfPresent(Bool.self, forKey: .isRightWheel) self.isJapanese = try container.decodeIfPresent(Bool.self, forKey: .isJapanese) self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000 self.addedBy = try container.decode(String.self, forKey: .addedBy) self.debugInfo = try container.decodeIfPresent(DebugInfoDto.self, forKey: .debugInfo) self.updatedDate = (try container.decode(TimeInterval.self, forKey: .updatedDate))/1000 self.photos = try container.decodeIfPresent([VehiclePhotoDto].self, forKey: .photos) ?? [] self.ownershipPeriods = try container.decodeIfPresent([VehicleOwnershipPeriodDto].self, forKey: .ownershipPeriods) ?? [] self.osagoContracts = try container.decodeIfPresent([OsagoDto].self, forKey: .osagoContracts) ?? [] self.ads = try container.decodeIfPresent([VehicleAdDto].self, forKey: .ads) ?? [] self.notes = try container.decodeIfPresent([VehicleNoteDto].self, forKey: .notes) ?? [] if let events = try container.decodeIfPresent([VehicleEventDto].self, forKey: .events) { self.events = events.sorted { $0.date > $1.date } } // All vehicles received from API are synchronized by definition self.synchronized = true } } extension VehicleDto: Exportable { public static var csvHeader: String { return "Plate Number, Model, Color, Year, Power (HP), Events, Owners, VIN, STS, PTS, Engine number, Added Date, Updated date, Locations, Notes" } public var csvLine: String { let model = self.brand?.name?.original ?? "" let added = Formatters.standard.string(from: Date(timeIntervalSince1970: self.addedDate)) let updated = Formatters.standard.string(from: Date(timeIntervalSince1970: self.updatedDate)) var eventsString = "" for event in events { let location = event.address ?? "lat: \(event.latitude), lon: \(event.longitude)" let date = Formatters.standard.string(from: Date(timeIntervalSince1970: event.date)) eventsString.append(location + "; " + date + "\r\n") } let notesString = self.notes.reduce("") { partialResult, note in partialResult + note.text + "\r\n" } let number = self.currentNumber == nil ? self.number : "\(self.number) (\(self.currentNumber ?? ""))" return String(format: "%@, \"%@\", %@, %d, %f, %d, %d, %@, %@, %@, %@, \"%@\", \"%@\", \"%@\", \"%@\"", number, model, self.color ?? "", self.year, self.engine?.powerHp ?? 0.0, self.events.count, self.ownershipPeriods.count, self.vin1 ?? "", self.sts ?? "", self.pts ?? "", self.engine?.number ?? "", added, updated, eventsString, notesString) } } extension VehicleDto { public func getNumber() -> String { return self.number } public var unrecognized: Bool { return self.brand == nil } public var outdated: Bool { if let current = self.currentNumber { return current != self.number } else { return false } } }