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        /// Who pays for it, when it was said as it was added.
166        #[serde(default, skip_serializing_if = "Option::is_none")]
167        payer: Option<Payer>,
168    },
169    /// An extra was given its payer.
170    PayerAssigned {
171        /// The extra.
172        extra_id: Uuid,
173        /// Who pays.
174        payer: Payer,
175    },
176    /// An extra was changed; each field given is its new value.
177    ExtraChanged {
178        /// The extra.
179        extra_id: Uuid,
180        /// Its new description.
181        #[serde(default, skip_serializing_if = "Option::is_none")]
182        description: Option<String>,
183        /// Its new quantity.
184        #[serde(default, skip_serializing_if = "Option::is_none")]
185        quantity: Option<u32>,
186        /// Its new unit price in cents.
187        #[serde(default, skip_serializing_if = "Option::is_none")]
188        unit_price_cents: Option<i64>,
189    },
190    /// Another traveler was put on the case.
191    TravelerChanged {
192        /// The previous traveler, when there was one.
193        #[serde(default, skip_serializing_if = "Option::is_none")]
194        previous_traveler_id: Option<Uuid>,
195        /// The new traveler.
196        traveler_id: Uuid,
197        /// The new traveler's name.
198        #[serde(default)]
199        display_name: String,
200    },
201    /// A leg is kept as it is.
202    LegProtected {
203        /// The leg's number.
204        leg: u32,
205    },
206    /// The airline quoted a rebooking.
207    OfferQuoted {
208        /// The leg it replaces.
209        leg: u32,
210        /// The new flight.
211        flight: String,
212        /// When it leaves.
213        departs: String,
214        /// What it costs beyond the ticket already paid, in cents.
215        fare_difference_cents: i64,
216    },
217    /// The rebooking confirmation was requested.
218    RebookingRequested {
219        /// The leg.
220        leg: u32,
221    },
222    /// The rebooking was sent to the airline.
223    RebookingSent,
224    /// The case was withdrawn.
225    Withdrawn,
226    /// The airline reported where the rebooking stands.
227    AirlineOutcomeRecorded {
228        /// Where it stands.
229        status: ExternalStatus,
230        /// The new ticket's number, when issued.
231        #[serde(default, skip_serializing_if = "Option::is_none")]
232        ticket_number: Option<String>,
233        /// Stable reason code of a refusal.
234        #[serde(default, skip_serializing_if = "Option::is_none")]
235        reason_code: Option<String>,
236    },
237}
238
239impl TripEvent {
240    /// The stable event type label stored on the committed event.
241    #[must_use]
242    pub fn event_type(&self) -> &'static str {
243        match self {
244            Self::Opened => "trip.opened",
245            Self::NameSet { .. } => "trip.name_set",
246            Self::TravelDateSet { .. } => "trip.travel_date_set",
247            Self::ExtraAdded { .. } => "trip.extra_added",
248            Self::PayerAssigned { .. } => "trip.payer_assigned",
249            Self::ExtraChanged { .. } => "trip.extra_changed",
250            Self::TravelerChanged { .. } => "trip.traveler_changed",
251            Self::LegProtected { .. } => "trip.leg_protected",
252            Self::OfferQuoted { .. } => "trip.offer_quoted",
253            Self::RebookingRequested { .. } => "trip.rebooking_requested",
254            Self::RebookingSent => "trip.rebooking_sent",
255            Self::Withdrawn => "trip.withdrawn",
256            Self::AirlineOutcomeRecorded { .. } => "trip.airline_outcome_recorded",
257        }
258    }
259}
260
261/// Arguments of [`operations::SET_NAME`].
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
263#[serde(deny_unknown_fields)]
264pub struct SetNameArgs {
265    /// What the traveler calls the trip.
266    pub value: String,
267}
268
269/// Arguments of [`operations::SET_TRAVEL_DATE`].
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
271#[serde(deny_unknown_fields)]
272pub struct SetTravelDateArgs {
273    /// The day the traveler would rather fly.
274    pub value: NaiveDate,
275}
276
277/// Arguments of [`operations::ADD_EXTRA`].
278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
279#[serde(deny_unknown_fields)]
280pub struct AddExtraArgs {
281    /// What the extra is.
282    pub description: String,
283    /// How many.
284    pub quantity: u32,
285    /// Price of one.
286    pub unit_price: Money,
287    /// Who pays, when the message says so.
288    #[serde(default)]
289    pub payer: Option<Payer>,
290}
291
292/// Arguments of [`operations::ASSIGN_PAYER`].
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
294#[serde(deny_unknown_fields)]
295pub struct AssignPayerArgs {
296    /// The extra's number, as the record lists it, from 1.
297    pub extra: u32,
298    /// Who pays.
299    pub payer: Payer,
300}
301
302/// Arguments of [`operations::CHANGE_EXTRA`]: the extra, and what changes on it.
303#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
304#[serde(deny_unknown_fields)]
305pub struct ChangeExtraArgs {
306    /// The extra's number, as the record lists it, from 1.
307    pub extra: u32,
308    /// Its new description.
309    #[serde(default)]
310    pub description: Option<String>,
311    /// Its new quantity.
312    #[serde(default)]
313    pub quantity: Option<u32>,
314    /// Its new unit price.
315    #[serde(default)]
316    pub unit_price: Option<Money>,
317}
318
319/// Arguments of [`operations::OPEN`]: the traveler, when the user named one.
320#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
321#[serde(deny_unknown_fields)]
322pub struct OpenArgs {
323    /// The traveler record.
324    #[serde(default)]
325    pub traveler: Option<TravelerRecord>,
326}
327
328/// Arguments of [`operations::SET_TRAVELER`].
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
330#[serde(deny_unknown_fields)]
331pub struct SetTravelerArgs {
332    /// The traveler record.
333    pub traveler: TravelerRecord,
334}
335
336/// A traveler record, as a record argument reaches the workflow.
337#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
338pub struct TravelerRecord {
339    /// Its identifier.
340    pub case_id: String,
341    /// Its name, when it was in view.
342    #[serde(default)]
343    pub label: Option<String>,
344}
345
346/// Arguments of [`operations::CHANGE_TRAVELER`].
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
348#[serde(deny_unknown_fields)]
349pub struct ChangeTravelerArgs {
350    /// Who travels instead, as the user names them.
351    pub traveler: String,
352}
353
354/// Arguments of [`operations::PROTECT_LEG`] and [`operations::REQUEST_REBOOKING`].
355#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
356#[serde(deny_unknown_fields)]
357pub struct LegArgs {
358    /// The leg's number, as the record lists it, from 1.
359    pub leg: u32,
360}