206 lines
6.6 KiB
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);
|
|
}
|
|
}
|