Roadbound-BRR/lib/models/operations/trip.dart

206 lines
6.6 KiB
Dart

import "package:bus_running_record/models/operations/duty.dart";
import "package:bus_running_record/models/operations/scheduled_stop.dart";
import "package:bus_running_record/models/operations/stop.dart";
class Trip {
final String scheduledTime; // "15:31" - default departure time
final String tripNumber; // "112"
final Duty duty;
final String tripType; // "", "N", "R", "F"
final bool isFinishing;
final List<ScheduledStop> scheduledStops;
final String direction; // "outbound" or "inbound"
String? actualDepartureTime; // "15:33" (nullable - user input)
String? actualFleetNumber; // "33523" (nullable - user input)
Trip({
required this.scheduledTime,
required this.tripNumber,
String? dutyNumber,
String? busWorkNumber,
Duty? duty,
this.tripType = "",
this.isFinishing = false,
Map<String, String> stationTimes = const {},
List<String> stationOrder = const [],
List<ScheduledStop>? scheduledStops,
this.direction = "outbound",
this.actualDepartureTime,
this.actualFleetNumber,
}) : duty =
duty ??
Duty(
dutyNumber: dutyNumber ?? "",
busWorkNumber: busWorkNumber ?? "",
),
scheduledStops =
scheduledStops ?? _buildScheduledStops(stationTimes, stationOrder);
bool get isComplete =>
actualDepartureTime != null && actualFleetNumber != null;
String get dutyNumber => duty.dutyNumber;
String get busWorkNumber => duty.busWorkNumber;
List<String> get stationOrder =>
scheduledStops.map((stop) => stop.name).toList(growable: false);
Map<String, String> get stationTimes {
final result = <String, String>{};
for (final stop in scheduledStops) {
final time = (stop.scheduledTime ?? "").trim();
if (time.isEmpty) continue;
result[stop.name] = time;
}
return result;
}
Trip copyWith({
String? scheduledTime,
String? tripNumber,
String? dutyNumber,
String? busWorkNumber,
Duty? duty,
String? tripType,
bool? isFinishing,
Map<String, String>? stationTimes,
List<String>? stationOrder,
List<ScheduledStop>? scheduledStops,
String? direction,
String? actualDepartureTime,
String? actualFleetNumber,
}) {
return Trip(
scheduledTime: scheduledTime ?? this.scheduledTime,
tripNumber: tripNumber ?? this.tripNumber,
duty:
duty ??
this.duty.copyWith(
dutyNumber: dutyNumber,
busWorkNumber: busWorkNumber,
),
tripType: tripType ?? this.tripType,
isFinishing: isFinishing ?? this.isFinishing,
scheduledStops:
scheduledStops ??
(stationTimes != null || stationOrder != null
? _buildScheduledStops(
stationTimes ?? this.stationTimes,
stationOrder ?? this.stationOrder,
)
: this.scheduledStops),
direction: direction ?? this.direction,
actualDepartureTime: actualDepartureTime ?? this.actualDepartureTime,
actualFleetNumber: actualFleetNumber ?? this.actualFleetNumber,
);
}
Trip withStopAliases(
Map<String, String> aliasesByRawName, {
String aliasSource = "ai",
}) {
if (aliasesByRawName.isEmpty) return this;
final aliasesByNormalizedName = <String, String>{};
for (final entry in aliasesByRawName.entries) {
aliasesByNormalizedName[Stop.normalizeName(entry.key)] = entry.value;
}
return copyWith(
scheduledStops: scheduledStops
.map(
(stop) => stop.copyWith(
alias: aliasesByNormalizedName[Stop.normalizeName(stop.name)],
aliasSource:
aliasesByNormalizedName[Stop.normalizeName(stop.name)] == null
? stop.aliasSource
: aliasSource,
),
)
.toList(growable: false),
);
}
Map<String, dynamic> toJson() {
return {
"scheduledTime": scheduledTime,
"tripNumber": tripNumber,
"duty": duty.toJson(),
"dutyNumber": dutyNumber,
"busWorkNumber": busWorkNumber,
"tripType": tripType,
"isFinishing": isFinishing,
"scheduledStops": scheduledStops.map((stop) => stop.toJson()).toList(),
"stationTimes": stationTimes,
"stationOrder": stationOrder,
"direction": direction,
"actualDepartureTime": actualDepartureTime,
"actualFleetNumber": actualFleetNumber,
};
}
factory Trip.fromJson(Map<String, dynamic> json) {
final dutyJson = json["duty"];
final duty = dutyJson is Map<String, dynamic>
? Duty.fromJson(dutyJson)
: Duty(
dutyNumber: (json["dutyNumber"] ?? "").toString(),
busWorkNumber:
(json["busWorkNumber"] ?? json["runningNumber"] ?? "")
.toString(),
);
final scheduledStopsJson = json["scheduledStops"];
final scheduledStops = scheduledStopsJson is List
? scheduledStopsJson
.whereType<Map>()
.map(
(stopJson) =>
ScheduledStop.fromJson(Map<String, dynamic>.from(stopJson)),
)
.toList()
: null;
return Trip(
scheduledTime: json["scheduledTime"] as String,
tripNumber: json["tripNumber"] as String,
duty: duty,
tripType: json["tripType"] as String? ?? "",
isFinishing: json["isFinishing"] as bool? ?? false,
scheduledStops: scheduledStops,
stationTimes: Map<String, String>.from(
json["stationTimes"] as Map? ?? {},
),
stationOrder: List<String>.from(json["stationOrder"] as List? ?? []),
direction: json["direction"] as String? ?? "outbound",
actualDepartureTime: json["actualDepartureTime"] as String?,
actualFleetNumber: json["actualFleetNumber"] as String?,
);
}
static List<ScheduledStop> _buildScheduledStops(
Map<String, String> stationTimes,
List<String> stationOrder,
) {
if (stationOrder.isEmpty) {
var index = 0;
return stationTimes.entries
.map(
(entry) => ScheduledStop(
name: entry.key,
sequence: ++index,
scheduledTime: entry.value.trim().isEmpty ? null : entry.value,
),
)
.toList(growable: false);
}
return List<ScheduledStop>.generate(stationOrder.length, (i) {
final stopName = stationOrder[i];
final time = (stationTimes[stopName] ?? "").trim();
return ScheduledStop(
name: stopName,
sequence: i + 1,
scheduledTime: time.isEmpty ? null : time,
);
}, growable: false);
}
}