Emergency fixes on prod
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user