use chrono::NaiveDate;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use turnframe_core::event::ExternalStatus;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Payer {
Traveler,
Company,
Airline,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NewExtra {
pub description: String,
pub quantity: u32,
pub unit_price_cents: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Extra {
pub extra_id: Uuid,
pub description: String,
pub quantity: u32,
pub unit_price_cents: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payer: Option<Payer>,
}
impl Extra {
#[must_use]
pub fn total_cents(&self) -> i64 {
i64::from(self.quantity).saturating_mul(self.unit_price_cents)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum LegStatus {
#[default]
OnTime,
Delayed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Leg {
pub number: u32,
pub flight: String,
pub from: String,
pub to: String,
pub departs: String,
pub status: LegStatus,
#[serde(default)]
pub protected: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Offer {
pub leg: u32,
pub flight: String,
pub departs: String,
pub fare_difference_cents: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TripTraveler {
pub traveler_id: Uuid,
pub display_name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TripStatus {
#[default]
Draft,
AwaitingRebookingConfirmation,
Rebooking,
Ticketed,
Refused,
Notified,
NotNotified,
Withdrawn,
}
impl TripStatus {
#[must_use]
pub fn is_editable(self) -> bool {
matches!(
self,
Self::Draft | Self::AwaitingRebookingConfirmation | Self::Refused
)
}
#[must_use]
pub fn is_sent(self) -> bool {
matches!(
self,
Self::Rebooking | Self::Ticketed | Self::Notified | Self::NotNotified
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TripState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub traveler: Option<TripTraveler>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub travel_date: Option<NaiveDate>,
#[serde(default)]
pub legs: Vec<Leg>,
#[serde(default)]
pub extras: Vec<Extra>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offer: Option<Offer>,
pub status: TripStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_status: Option<ExternalStatus>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ticket_number: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refusal_code: Option<String>,
}
impl TripState {
#[must_use]
pub fn open_obligations(&self) -> Vec<TripObligation> {
if !self.status.is_editable() {
return Vec::new();
}
let mut obligations = Vec::new();
if self.traveler.is_none() {
obligations.push(TripObligation::SelectTraveler);
}
for extra in &self.extras {
if extra.payer.is_none() {
obligations.push(TripObligation::AssignPayer {
extra_id: extra.extra_id,
});
}
}
if self.name.is_none() {
obligations.push(TripObligation::SetName);
}
if self.travel_date.is_none() {
obligations.push(TripObligation::SetTravelDate);
}
obligations
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.status.is_editable() && self.open_obligations().is_empty()
}
#[must_use]
pub fn extras_total_cents(&self) -> i64 {
self.extras.iter().fold(0_i64, |total, extra| {
total.saturating_add(extra.total_cents())
})
}
#[must_use]
pub fn extra(&self, extra_id: Uuid) -> Option<&Extra> {
self.extras.iter().find(|extra| extra.extra_id == extra_id)
}
#[must_use]
pub fn leg(&self, number: u32) -> Option<&Leg> {
self.legs.iter().find(|leg| leg.number == number)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TripPhase {
PreDraft,
Collecting,
AwaitingRebookingConfirmation,
Dispatching,
Refused,
Ticketed,
Notified,
NotNotified,
Withdrawn,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TripObligation {
SelectTraveler,
AssignPayer {
extra_id: Uuid,
},
SetName,
SetTravelDate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TripOutcome {
Withdrawn,
Notified,
NotNotified,
}