73 lines
2.9 KiB
Python
73 lines
2.9 KiB
Python
from decimal import Decimal
|
|
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
|
|
|
|
class Trip(models.Model):
|
|
class TripType(models.TextChoices):
|
|
BUSINESS = "business", "Business"
|
|
PERSONAL = "personal", "Personal"
|
|
COMMUTING = "commuting", "Commuting"
|
|
REPAIR = "repair", "Repair / Maintenance"
|
|
|
|
class DistanceSource(models.TextChoices):
|
|
ODOMETER = "odometer", "Odometer"
|
|
MAP = "map", "Google Maps"
|
|
|
|
date = models.DateField()
|
|
start_time = models.TimeField()
|
|
end_time = models.TimeField()
|
|
start_location = models.CharField(max_length=255)
|
|
end_location = models.CharField(max_length=255)
|
|
business_purpose = models.TextField()
|
|
trip_type = models.CharField(max_length=20, choices=TripType.choices)
|
|
start_odometer = models.DecimalField(max_digits=10, decimal_places=1, null=True, blank=True)
|
|
end_odometer = models.DecimalField(max_digits=10, decimal_places=1, null=True, blank=True)
|
|
distance_miles = models.DecimalField(max_digits=8, decimal_places=1, null=True, blank=True)
|
|
distance_source = models.CharField(max_length=20, choices=DistanceSource.choices, default=DistanceSource.MAP)
|
|
notes = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(default=timezone.now)
|
|
updated_at = models.DateTimeField(default=timezone.now)
|
|
|
|
class Meta:
|
|
ordering = ["-date", "-start_time", "-created_at"]
|
|
|
|
def __str__(self):
|
|
return f"{self.date:%Y-%m-%d} · {self.start_location} → {self.end_location}"
|
|
|
|
def get_absolute_url(self):
|
|
return reverse("trip_detail", args=[self.pk])
|
|
|
|
@property
|
|
def miles_display(self):
|
|
return self.distance_miles or Decimal("0.0")
|
|
|
|
def clean(self):
|
|
errors = {}
|
|
if self.end_time and self.start_time and self.end_time < self.start_time:
|
|
errors["end_time"] = "End time must be after the start time."
|
|
|
|
if self.start_odometer is not None and self.end_odometer is not None:
|
|
if self.end_odometer < self.start_odometer:
|
|
errors["end_odometer"] = "Ending odometer must be greater than or equal to starting odometer."
|
|
else:
|
|
self.distance_miles = self.end_odometer - self.start_odometer
|
|
self.distance_source = self.DistanceSource.ODOMETER
|
|
elif self.distance_miles is not None:
|
|
if self.distance_miles < 0:
|
|
errors["distance_miles"] = "Distance must be zero or greater."
|
|
else:
|
|
self.distance_source = self.DistanceSource.MAP
|
|
else:
|
|
errors["distance_miles"] = "Add odometer readings or calculate mileage from Google Maps."
|
|
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.full_clean()
|
|
super().save(*args, **kwargs)
|