Files
bus-manager/busManager/traveller/models.py
T
st01765 4f5842f21f Added signal for traveller updates
Added contact emails to family inline
2026-03-10 14:44:28 +11:00

327 lines
13 KiB
Python

from datetime import datetime
from django.core.exceptions import ValidationError
from django.db import models
from common.models import Suburb
from locations.models import Location
class School(models.Model):
name = models.CharField(max_length=30, unique=True)
shortName = models.CharField(max_length=10, unique=True)
address = models.CharField(max_length=50)
suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
email = models.CharField(max_length=50, blank=True)
phone = models.CharField(max_length=15, blank=True)
principal_name = models.CharField(max_length=50, blank=True)
principal_phone = models.CharField(max_length=15, blank=True)
notes = models.TextField(blank=True)
def __str__(self):
return self.name
class Traveller(models.Model):
YEAR = [
("PS", "PreSchool"),
("00", "Year 00"),
("01", "Year 01"),
("02", "Year 02"),
("03", "Year 03"),
("04", "Year 04"),
("05", "Year 05"),
("06", "Year 06"),
("07", "Year 07"),
("08", "Year 08"),
("09", "Year 09"),
("10", "Year 10"),
("11", "Year 11"),
("12", "Year 12"),
("AL", "Adult Learner"),
]
ELIGIBILITY_STATUS = [
("1", "Eligible"),
("2", "Ineligible"),
("3", "<4.8 Exemption"),
("4", "Eligible waitlisted"),
("5", "Ineligible waitlisted"),
("6", "Kinder Exemption"),
("7", "Tafe/Post Secondary Exemption"),
("8", "Other Exemption"),
]
school = models.ForeignKey(School, on_delete=models.PROTECT)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
dob = models.DateField(blank=True, null=True)
year_level = models.CharField(max_length=2, choices=YEAR)
bus_stops = models.ManyToManyField("transport.BusStop", through='TravellerRoute', blank=True)
distance_to_school = models.PositiveSmallIntegerField(blank=True, null=True)
address = models.CharField(max_length=100, default="")
travel_start_date = models.DateField(blank=True, null=True)
travel_end_date = models.DateField(blank=True, null=True)
eligibility_status = models.CharField(max_length=1, choices=ELIGIBILITY_STATUS)
assessment_date = models.DateField(blank=True, null=True)
fee_per_term = models.DecimalField(decimal_places=2, max_digits=5, blank=True, null=True)
term_1_paid = models.BooleanField(default=False)
term_2_paid = models.BooleanField(default=False)
term_3_paid = models.BooleanField(default=False)
term_4_paid = models.BooleanField(default=False)
application_form_completed = models.BooleanField()
parent_notified = models.BooleanField()
seat_number = models.CharField(max_length=5, blank=True)
created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
last_edit = models.DateTimeField(auto_now=True, blank=True, null=True)
is_archived = models.BooleanField(default=False, verbose_name="Archived")
is_active = models.BooleanField(default=False, verbose_name='Active')
notes = models.TextField(blank=True, verbose_name='Admin Notes')
shuttle = models.ForeignKey("transport.Shuttle", on_delete=models.SET_NULL, blank=True, null=True)
class Meta:
ordering = ["last_name", "first_name"]
def __str__(self):
return f"{self.first_name} {self.last_name}"
def _is_active(self, date=None):
if date is None:
today = datetime.today()
date = datetime(today.year, today.month, today.day)
if not self.travel_start_date or datetime(self.travel_start_date.year, self.travel_start_date.month, self.travel_start_date.day) > date:
return False
if not self.travel_end_date:
return True
end_date = datetime(self.travel_end_date.year, self.travel_end_date.month, self.travel_end_date.day)
return end_date >= date
def recalculate_travel_dates(self):
routes = TravellerRoute.objects.filter(traveller=self)
new_start = None
new_end = None
for route in routes:
if route.travel_start_date:
if new_start is None or route.travel_start_date < new_start:
new_start = route.travel_start_date
if route.travel_end_date:
if new_end is None or route.travel_end_date > new_end:
new_end = route.travel_end_date
self.travel_start_date = new_start
self.travel_end_date = new_end
self.is_active = self._is_active()
def fare_paying(self):
if self.eligibility_status != "2":
return
cost_setting = 0
if not cost_setting.exists():
return "TERM_TRAVEL_COST not configured"
cost = int(cost_setting.get().value)
stops = 0
for stop in TravellerRoute.objects.filter(traveller=self.id):
stops += stop.active_stops()
if stops > 1:
stops = 1
return f"${round(cost*stops)}"
def update_address_from_families(self):
addresses = {
str(f.location)
for f in self.get_families()
if f.location
}
self.address = " | ".join(sorted(addresses)) if addresses else ""
def get_families(self):
return Family.objects.filter(traveller__id__exact=self.id)
class ContactPerson(models.Model):
first_name = models.CharField(max_length=100, blank=True)
last_name = models.CharField(max_length=100, blank=True)
phone = models.CharField(max_length=15, blank=True)
email = models.EmailField(blank=True)
class Meta:
indexes = [
models.Index(fields=["phone"]),
models.Index(fields=["last_name"]),
]
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Family(models.Model):
RELATIONS = [
("1", "Parent"),
("2", "Step-Parent"),
("3", "Foster Parent"),
("4", "Host Family"),
("5", "Sibling"),
("6", "Grandparent"),
("7", "Aunt/Uncle"),
("8", "Cousin"),
("9", "Carer"),
("10", "Case Worker"),
("11", "Friend/Other"),
]
traveller = models.ForeignKey(Traveller, on_delete=models.CASCADE)
location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True)
postal_location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True, blank=True,
related_name="family_postal_locations")
residential_address = models.CharField(max_length=50, blank=True)
residential_suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT, blank=True, null=True,
related_name='family_residential_suburb')
postal_address = models.CharField(max_length=50, blank=True)
postal_suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT, blank=True, null=True,
related_name='family_postal_suburb')
parent_A_firstname = models.CharField(max_length=50, blank=True)
parent_A_lastname = models.CharField(max_length=50, blank=True)
parent_A_phone = models.CharField(max_length=15, blank=True)
parent_A_email = models.CharField(max_length=50, blank=True)
parent_B_firstname = models.CharField(max_length=50, blank=True)
parent_B_lastname = models.CharField(max_length=50, blank=True)
parent_B_phone = models.CharField(max_length=15, blank=True)
parent_B_email = models.CharField(max_length=50, blank=True)
emergency_contact_A_firstname = models.CharField(max_length=50, blank=True)
emergency_contact_A_lastname = models.CharField(max_length=50, blank=True)
emergency_contact_A_phone = models.CharField(max_length=15, blank=True)
emergency_contact_A_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
emergency_contact_B_firstname = models.CharField(max_length=50, blank=True)
emergency_contact_B_lastname = models.CharField(max_length=50, blank=True)
emergency_contact_B_phone = models.CharField(max_length=15, blank=True)
emergency_contact_B_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
contact_A_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
contact_A = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_contact_A')
contact_B_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
contact_B = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_contact_B')
emergency_contact_A = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_emergency_contact_A')
emergency_contact_B = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_emergency_contact_B')
created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
last_edit = models.DateTimeField(auto_now=True, blank=True, null=True)
class Meta:
verbose_name_plural = "Families"
indexes = [
models.Index(fields=["contact_A"]),
models.Index(fields=["contact_B"]),
models.Index(fields=["emergency_contact_A"]),
models.Index(fields=["emergency_contact_B"]),
]
def __str__(self):
name = self.parent_names()
if name:
return f"{self.traveller}: {name}"
return str(self.traveller)
def clean(self):
valid_numbers, failed_numbers = self.get_parsed_numbers(True, True)
if len(failed_numbers) > 0:
raise ValidationError(f"Phone number {failed_numbers[0]} not valid")
def parent_names(self):
a_name = ""
b_name = ""
if self.contact_A:
a_name = self.contact_A.first_name
if self.contact_B:
b_name = self.contact_B.first_name
if a_name:
if b_name:
return f"{a_name} and {b_name}"
return a_name
elif b_name:
return b_name
return ""
def get_parsed_numbers(self, parents=False, emergency=False):
import phonenumbers
numbers = []
if parents:
for contact in [self.contact_A, self.contact_B]:
if contact and contact.phone:
numbers.append(contact.phone)
if emergency:
for contact in [self.emergency_contact_A, self.emergency_contact_B]:
if contact and contact.phone:
numbers.append(contact.phone)
valid_numbers = []
failed_numbers = []
for number in numbers:
try:
parsed = phonenumbers.parse(number, "AU")
if phonenumbers.is_valid_number(parsed):
valid_numbers.append(f"+{parsed.country_code}{parsed.national_number}")
else:
failed_numbers.append(number)
except:
failed_numbers.append(number)
return valid_numbers, failed_numbers
class TravellerRoute(models.Model):
traveller = models.ForeignKey(Traveller, on_delete=models.CASCADE)
busStop = models.ForeignKey("transport.BusStop", on_delete=models.CASCADE)
travel_start_date = models.DateField(blank=True, null=True)
travel_end_date = models.DateField(blank=True, null=True)
mon_am = models.BooleanField(default=True)
mon_pm = models.BooleanField(default=True)
tue_am = models.BooleanField(default=True)
tue_pm = models.BooleanField(default=True)
wen_am = models.BooleanField(default=True)
wen_pm = models.BooleanField(default=True)
thu_am = models.BooleanField(default=True)
thu_pm = models.BooleanField(default=True)
fri_am = models.BooleanField(default=True)
fri_pm = models.BooleanField(default=True)
notes = models.TextField(blank=True, verbose_name="Driver Notes")
created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
last_edit = models.DateTimeField(auto_now=True, blank=True, null=True)
def __str__(self):
return f"{self.busStop}"
def save(self, *args, **kwargs):
super(TravellerRoute, self).save(*args, **kwargs)
self.traveller.save()
def active_stops(self):
stops = 0
if self.mon_am:
stops += 1
if self.mon_pm:
stops += 1
if self.tue_am:
stops += 1
if self.tue_pm:
stops += 1
if self.wen_am:
stops += 1
if self.wen_pm:
stops += 1
if self.thu_am:
stops += 1
if self.thu_pm:
stops += 1
if self.fri_am:
stops += 1
if self.fri_pm:
stops += 1
return stops * 0.1