Skip to main content

turnframe_test/workflows/trip/
command.rs

1//! Typed trip commands, events and model-facing argument shapes.
2
3use chrono::NaiveDate;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use turnframe_core::event::ExternalStatus;
7use turnframe_core::operation::Money;
8use uuid::Uuid;
9
10use crate::workflows::trip::state::{NewExtra, Payer, TripTraveler};
11
12/// Keys of the operations the interpreter may propose.
13pub mod operations {
14    /// Open the disruption case.
15    pub const OPEN: &str = "trip.open";
16    /// Name the trip.
17    pub const SET_NAME: &str = "trip.set_name";
18    /// Set the day the traveler would rather fly.
19    pub const SET_TRAVEL_DATE: &str = "trip.set_travel_date";
20    /// Add an extra.
21    pub const ADD_EXTRA: &str = "trip.add_extra";
22    /// Say who pays for an extra.
23    pub const ASSIGN_PAYER: &str = "trip.assign_payer";
24    /// Change an extra's description, quantity or unit price.
25    pub const CHANGE_EXTRA: &str = "trip.change_extra";
26    /// Choose the traveler of a case that has none yet.
27    pub const SET_TRAVELER: &str = "trip.set_traveler";
28    /// Put another traveler on the case.
29    pub const CHANGE_TRAVELER: &str = "trip.change_traveler";
30    /// Keep a leg as it is.
31    pub const PROTECT_LEG: &str = "trip.protect_leg";
32    /// Ask for the rebooking card of the quoted offer.
33    pub const REQUEST_REBOOKING: &str = "trip.request_rebooking";
34    /// Send the rebooking to the airline.
35    pub const REBOOK: &str = "trip.rebook";
36    /// Withdraw the case before a rebooking is sent.
37    pub const WITHDRAW: &str = "trip.withdraw";
38    /// Acknowledges the card on screen; only the card may address it.
39    pub const ACKNOWLEDGE_CARD: &str = "trip.acknowledge_card";
40}
41
42/// One typed trip command.
43///
44/// Every command names what it does: policy, validation and receipts all key off
45/// the variant. `Requote` and `RecordAirlineOutcome` come from the airline, never
46/// from the interpreter, so no operation compiles to them.
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49#[non_exhaustive]
50pub enum TripCommand {
51    /// Bring the case into existence, on the sample booking.
52    Open,
53    /// Bring the case into existence, for its traveler.
54    OpenFor {
55        /// The traveler.
56        traveler: TripTraveler,
57    },
58    /// Name the trip.
59    SetName {
60        /// The name.
61        value: String,
62    },
63    /// Set the day the traveler would rather fly.
64    SetTravelDate {
65        /// The day.
66        value: NaiveDate,
67    },
68    /// Add an extra.
69    AddExtra {
70        /// The extra.
71        extra: NewExtra,
72    },
73    /// Say who pays for an extra.
74    AssignPayer {
75        /// The extra.
76        extra_id: Uuid,
77        /// Who pays.
78        payer: Payer,
79    },
80    /// Change an extra already on the case; what is not given stays as it was.
81    ChangeExtra {
82        /// The extra.
83        extra_id: Uuid,
84        /// Its new description.
85        #[serde(default, skip_serializing_if = "Option::is_none")]
86        description: Option<String>,
87        /// Its new quantity.
88        #[serde(default, skip_serializing_if = "Option::is_none")]
89        quantity: Option<u32>,
90        /// Its new unit price in cents.
91        #[serde(default, skip_serializing_if = "Option::is_none")]
92        unit_price_cents: Option<i64>,
93    },
94    /// Put another traveler on the case. Carries the server-authored label as well
95    /// as the identifier so cards never have to read it again.
96    ChangeTraveler {
97        /// The new traveler.
98        traveler: TripTraveler,
99    },
100    /// Keep a leg as it is: nothing may change it from now on.
101    ProtectLeg {
102        /// The leg's number.
103        leg: u32,
104    },
105    /// The airline quoted, or quoted again, the rebooking of a leg.
106    Requote {
107        /// The leg it replaces.
108        leg: u32,
109        /// The new flight.
110        flight: String,
111        /// When it leaves.
112        departs: String,
113        /// What it costs beyond the ticket already paid, in cents.
114        fare_difference_cents: i64,
115    },
116    /// Move to the rebooking confirmation phase for a leg.
117    RequestRebooking {
118        /// The leg's number.
119        leg: u32,
120    },
121    /// Send the rebooking to the airline.
122    Rebook,
123    /// Withdraw the case before a rebooking is sent.
124    Withdraw,
125    /// Record what the airline said about the rebooking.
126    RecordAirlineOutcome {
127        /// Where the rebooking now stands.
128        status: ExternalStatus,
129        /// The new ticket's number, when the airline issued one.
130        #[serde(default, skip_serializing_if = "Option::is_none")]
131        ticket_number: Option<String>,
132        /// Stable reason code of a refusal, never free text.
133        #[serde(default, skip_serializing_if = "Option::is_none")]
134        reason_code: Option<String>,
135    },
136}
137
138/// One committed trip event: the claim ledger of the sample.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "snake_case")]
141#[non_exhaustive]
142pub enum TripEvent {
143    /// The case came into existence.
144    Opened,
145    /// The trip was named.
146    NameSet {
147        /// The new name.
148        value: String,
149    },
150    /// The travel date changed.
151    TravelDateSet {
152        /// The new date.
153        value: NaiveDate,
154    },
155    /// An extra was added.
156    ExtraAdded {
157        /// Identifier of the new extra.
158        extra_id: Uuid,
159        /// Its description.
160        description: String,
161        /// Its quantity.
162        quantity: u32,
163        /// Its unit price in cents.
164        unit_price_cents: i64,
165    },
166    /// An extra was given its payer.
167    PayerAssigned {
168        /// The extra.
169        extra_id: Uuid,
170        /// Who pays.
171        payer: Payer,
172    },
173    /// An extra was changed; each field given is its new value.
174    ExtraChanged {
175        /// The extra.
176        extra_id: Uuid,
177        /// Its new description.
178        #[serde(default, skip_serializing_if = "Option::is_none")]
179        description: Option<String>,
180        /// Its new quantity.
181        #[serde(default, skip_serializing_if = "Option::is_none")]
182        quantity: Option<u32>,
183        /// Its new unit price in cents.
184        #[serde(default, skip_serializing_if = "Option::is_none")]
185        unit_price_cents: Option<i64>,
186    },
187    /// Another traveler was put on the case.
188    TravelerChanged {
189        /// The previous traveler, when there was one.
190        #[serde(default, skip_serializing_if = "Option::is_none")]
191        previous_traveler_id: Option<Uuid>,
192        /// The new traveler.
193        traveler_id: Uuid,
194        /// The new traveler's name.
195        #[serde(default)]
196        display_name: String,
197    },
198    /// A leg is kept as it is.
199    LegProtected {
200        /// The leg's number.
201        leg: u32,
202    },
203    /// The airline quoted a rebooking.
204    OfferQuoted {
205        /// The leg it replaces.
206        leg: u32,
207        /// The new flight.
208        flight: String,
209        /// When it leaves.
210        departs: String,
211        /// What it costs beyond the ticket already paid, in cents.
212        fare_difference_cents: i64,
213    },
214    /// The rebooking confirmation was requested.
215    RebookingRequested {
216        /// The leg.
217        leg: u32,
218    },
219    /// The rebooking was sent to the airline.
220    RebookingSent,
221    /// The case was withdrawn.
222    Withdrawn,
223    /// The airline reported where the rebooking stands.
224    AirlineOutcomeRecorded {
225        /// Where it stands.
226        status: ExternalStatus,
227        /// The new ticket's number, when issued.
228        #[serde(default, skip_serializing_if = "Option::is_none")]
229        ticket_number: Option<String>,
230        /// Stable reason code of a refusal.
231        #[serde(default, skip_serializing_if = "Option::is_none")]
232        reason_code: Option<String>,
233    },
234}
235
236impl TripEvent {
237    /// The stable event type label stored on the committed event.
238    #[must_use]
239    pub fn event_type(&self) -> &'static str {
240        match self {
241            Self::Opened => "trip.opened",
242            Self::NameSet { .. } => "trip.name_set",
243            Self::TravelDateSet { .. } => "trip.travel_date_set",
244            Self::ExtraAdded { .. } => "trip.extra_added",
245            Self::PayerAssigned { .. } => "trip.payer_assigned",
246            Self::ExtraChanged { .. } => "trip.extra_changed",
247            Self::TravelerChanged { .. } => "trip.traveler_changed",
248            Self::LegProtected { .. } => "trip.leg_protected",
249            Self::OfferQuoted { .. } => "trip.offer_quoted",
250            Self::RebookingRequested { .. } => "trip.rebooking_requested",
251            Self::RebookingSent => "trip.rebooking_sent",
252            Self::Withdrawn => "trip.withdrawn",
253            Self::AirlineOutcomeRecorded { .. } => "trip.airline_outcome_recorded",
254        }
255    }
256}
257
258/// Arguments of [`operations::SET_NAME`].
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
260#[serde(deny_unknown_fields)]
261pub struct SetNameArgs {
262    /// What the traveler calls the trip.
263    pub value: String,
264}
265
266/// Arguments of [`operations::SET_TRAVEL_DATE`].
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
268#[serde(deny_unknown_fields)]
269pub struct SetTravelDateArgs {
270    /// The day the traveler would rather fly.
271    pub value: NaiveDate,
272}
273
274/// Arguments of [`operations::ADD_EXTRA`].
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
276#[serde(deny_unknown_fields)]
277pub struct AddExtraArgs {
278    /// What the extra is.
279    pub description: String,
280    /// How many.
281    pub quantity: u32,
282    /// Price of one.
283    pub unit_price: Money,
284}
285
286/// Arguments of [`operations::ASSIGN_PAYER`].
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
288#[serde(deny_unknown_fields)]
289pub struct AssignPayerArgs {
290    /// The extra's number, as the record lists it, from 1.
291    pub extra: u32,
292    /// Who pays.
293    pub payer: Payer,
294}
295
296/// Arguments of [`operations::CHANGE_EXTRA`]: the extra, and what changes on it.
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
298#[serde(deny_unknown_fields)]
299pub struct ChangeExtraArgs {
300    /// The extra's number, as the record lists it, from 1.
301    pub extra: u32,
302    /// Its new description.
303    #[serde(default)]
304    pub description: Option<String>,
305    /// Its new quantity.
306    #[serde(default)]
307    pub quantity: Option<u32>,
308    /// Its new unit price.
309    #[serde(default)]
310    pub unit_price: Option<Money>,
311}
312
313/// Arguments of [`operations::OPEN`]: the traveler, when the user named one.
314#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
315#[serde(deny_unknown_fields)]
316pub struct OpenArgs {
317    /// The traveler record.
318    #[serde(default)]
319    pub traveler: Option<TravelerRecord>,
320}
321
322/// Arguments of [`operations::SET_TRAVELER`].
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
324#[serde(deny_unknown_fields)]
325pub struct SetTravelerArgs {
326    /// The traveler record.
327    pub traveler: TravelerRecord,
328}
329
330/// A traveler record, as a record argument reaches the workflow.
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
332pub struct TravelerRecord {
333    /// Its identifier.
334    pub case_id: String,
335    /// Its name, when it was in view.
336    #[serde(default)]
337    pub label: Option<String>,
338}
339
340/// Arguments of [`operations::CHANGE_TRAVELER`].
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
342#[serde(deny_unknown_fields)]
343pub struct ChangeTravelerArgs {
344    /// Who travels instead, as the user names them.
345    pub traveler: String,
346}
347
348/// Arguments of [`operations::PROTECT_LEG`] and [`operations::REQUEST_REBOOKING`].
349#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
350#[serde(deny_unknown_fields)]
351pub struct LegArgs {
352    /// The leg's number, as the record lists it, from 1.
353    pub leg: u32,
354}