Emergency fixes on prod

This commit is contained in:
James Pattinson
2026-06-28 08:35:39 +00:00
parent 5e12561fb2
commit 870bc0649b
4 changed files with 74 additions and 17 deletions
+3 -1
View File
@@ -187,7 +187,7 @@ class CRUDLocalFlight:
current_time = timestamp if timestamp is not None else datetime.utcnow() current_time = timestamp if timestamp is not None else datetime.utcnow()
if status == LocalFlightStatus.GROUND: if status == LocalFlightStatus.GROUND:
db_obj.contact_dt = current_time db_obj.contact_dt = current_time
elif status == LocalFlightStatus.DEPARTED: elif status == LocalFlightStatus.DEPARTED and not db_obj.departed_dt:
db_obj.departed_dt = current_time db_obj.departed_dt = current_time
elif status == LocalFlightStatus.LANDED and not db_obj.landed_dt: elif status == LocalFlightStatus.LANDED and not db_obj.landed_dt:
db_obj.landed_dt = current_time db_obj.landed_dt = current_time
@@ -200,6 +200,8 @@ class CRUDLocalFlight:
# Takeoff: happens once when transitioning away from GROUND # Takeoff: happens once when transitioning away from GROUND
if old_status == LocalFlightStatus.GROUND and status in (LocalFlightStatus.DEPARTED, LocalFlightStatus.LOCAL, LocalFlightStatus.CIRCUIT) and not db_obj.takeoff_dt: if old_status == LocalFlightStatus.GROUND and status in (LocalFlightStatus.DEPARTED, LocalFlightStatus.LOCAL, LocalFlightStatus.CIRCUIT) and not db_obj.takeoff_dt:
db_obj.takeoff_dt = current_time db_obj.takeoff_dt = current_time
if not db_obj.departed_dt:
db_obj.departed_dt = current_time
db.add(db_obj) db.add(db_obj)
db.commit() db.commit()
+35
View File
@@ -194,6 +194,7 @@ def test_local_flight_lifecycle_special_lists_and_not_found_paths(auth_client, d
assert departed_response.status_code == 200 assert departed_response.status_code == 200
assert departed_response.json()["takeoff_dt"] == "2026-06-20T10:05:00" assert departed_response.json()["takeoff_dt"] == "2026-06-20T10:05:00"
assert departed_response.json()["departed_dt"] == "2026-06-20T10:05:00"
assert landed_response.status_code == 200 assert landed_response.status_code == 200
assert landed_response.json()["landed_dt"] == "2026-06-20T10:45:00" assert landed_response.json()["landed_dt"] == "2026-06-20T10:45:00"
@@ -222,6 +223,40 @@ def test_local_flight_lifecycle_special_lists_and_not_found_paths(auth_client, d
assert auth_client.delete("/api/v1/local-flights/404").status_code == 404 assert auth_client.delete("/api/v1/local-flights/404").status_code == 404
def test_local_flight_takeoff_to_local_sets_departed_dt(auth_client):
create_response = auth_client.post(
"/api/v1/local-flights/",
json={
"registration": "g-air",
"type": "PA28",
"pob": 2,
"flight_type": "LOCAL",
"duration": 30,
"etd": "2026-06-20T09:00:00",
},
)
assert create_response.status_code == 200
takeoff_response = auth_client.patch(
f"/api/v1/local-flights/{create_response.json()['id']}/status",
json={"status": "LOCAL", "timestamp": "2026-06-20T09:05:00"},
)
assert takeoff_response.status_code == 200
assert takeoff_response.json()["status"] == "LOCAL"
assert takeoff_response.json()["takeoff_dt"] == "2026-06-20T09:05:00"
assert takeoff_response.json()["departed_dt"] == "2026-06-20T09:05:00"
landing_response = auth_client.patch(
f"/api/v1/local-flights/{create_response.json()['id']}/status",
json={"status": "LANDED", "timestamp": "2026-06-20T09:35:00"},
)
assert landing_response.status_code == 200
assert landing_response.json()["status"] == "LANDED"
assert landing_response.json()["landed_dt"] == "2026-06-20T09:35:00"
def test_overflight_lifecycle_special_lists_and_not_found_paths(auth_client, db): def test_overflight_lifecycle_special_lists_and_not_found_paths(auth_client, db):
payload = { payload = {
"registration": "g-ovr", "registration": "g-ovr",
+9 -2
View File
@@ -631,8 +631,15 @@
if (!utcDateTimeString) return ''; if (!utcDateTimeString) return '';
try { try {
// Parse the ISO datetime string // API datetimes are UTC; normalize naive strings before local display.
const date = new Date(utcDateTimeString); let utcDateStr = utcDateTimeString;
if (!utcDateStr.includes('T')) {
utcDateStr = utcDateStr.replace(' ', 'T');
}
if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(utcDateStr)) {
utcDateStr += 'Z';
}
const date = new Date(utcDateStr);
// Check if valid date // Check if valid date
if (isNaN(date.getTime())) { if (isNaN(date.getTime())) {
+27 -14
View File
@@ -619,6 +619,21 @@
return date.toISOString().slice(0, 10) + ' ' + date.toISOString().slice(11, 16); return date.toISOString().slice(0, 10) + ' ' + date.toISOString().slice(11, 16);
} }
function normalizeUtcDateString(dateStr) {
let utcDateStr = dateStr;
if (!utcDateStr.includes('T')) {
utcDateStr = utcDateStr.replace(' ', 'T');
}
if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(utcDateStr)) {
utcDateStr += 'Z';
}
return utcDateStr;
}
function utcInputToIso(dateStr, timeStr) {
return `${dateStr}T${timeStr}:00Z`;
}
// Modal functions // Modal functions
function openNewPPRModal() { function openNewPPRModal() {
isNewPPR = true; isNewPPR = true;
@@ -755,13 +770,7 @@
if (key === 'eta' || key === 'etd') { if (key === 'eta' || key === 'etd') {
if (ppr[key]) { if (ppr[key]) {
// ppr[key] is UTC datetime string from API (naive, assume UTC) // ppr[key] is UTC datetime string from API (naive, assume UTC)
let utcDateStr = ppr[key]; const utcDateStr = normalizeUtcDateString(ppr[key]);
if (!utcDateStr.includes('T')) {
utcDateStr = utcDateStr.replace(' ', 'T');
}
if (!utcDateStr.includes('Z')) {
utcDateStr += 'Z';
}
const date = new Date(utcDateStr); // Now correctly parsed as UTC const date = new Date(utcDateStr); // Now correctly parsed as UTC
// Split into date and time components for separate inputs // Split into date and time components for separate inputs
@@ -770,15 +779,15 @@
if (dateField && timeField) { if (dateField && timeField) {
// Format date // Format date
const year = date.getFullYear(); const year = date.getUTCFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0');
const dateValue = `${year}-${month}-${day}`; const dateValue = `${year}-${month}-${day}`;
dateField.value = dateValue; dateField.value = dateValue;
// Format time (round to nearest 15-minute interval) // Format time (round to nearest 15-minute interval)
const hours = String(date.getHours()).padStart(2, '0'); const hours = String(date.getUTCHours()).padStart(2, '0');
const rawMinutes = date.getMinutes(); const rawMinutes = date.getUTCMinutes();
const roundedMinutes = Math.round(rawMinutes / 15) * 15 % 60; const roundedMinutes = Math.round(rawMinutes / 15) * 15 % 60;
const minutes = String(roundedMinutes).padStart(2, '0'); const minutes = String(roundedMinutes).padStart(2, '0');
const timeValue = `${hours}:${minutes}`; const timeValue = `${hours}:${minutes}`;
@@ -1089,12 +1098,16 @@
// Combine date and time for ETA // Combine date and time for ETA
const dateStr = formData.get('eta-date'); const dateStr = formData.get('eta-date');
const timeStr = formData.get('eta-time'); const timeStr = formData.get('eta-time');
pprData.eta = new Date(`${dateStr}T${timeStr}`).toISOString(); pprData.eta = isNewPPR
? new Date(`${dateStr}T${timeStr}`).toISOString()
: utcInputToIso(dateStr, timeStr);
} else if (key === 'etd-date' && formData.get('etd-time')) { } else if (key === 'etd-date' && formData.get('etd-time')) {
// Combine date and time for ETD // Combine date and time for ETD
const dateStr = formData.get('etd-date'); const dateStr = formData.get('etd-date');
const timeStr = formData.get('etd-time'); const timeStr = formData.get('etd-time');
pprData.etd = new Date(`${dateStr}T${timeStr}`).toISOString(); pprData.etd = isNewPPR
? new Date(`${dateStr}T${timeStr}`).toISOString()
: utcInputToIso(dateStr, timeStr);
} else if (key !== 'eta-time' && key !== 'etd-time') { } else if (key !== 'eta-time' && key !== 'etd-time') {
// Skip the time fields as they're handled above // Skip the time fields as they're handled above
pprData[key] = value; pprData[key] = value;