Skip to main content

app/
checkout_completion.rs

1use chrono::{DateTime, Utc};
2use domain::{entities, policy, source};
3use nutype::nutype;
4use serde::{Deserialize, Serialize};
5
6#[nutype(
7    sanitize(trim),
8    validate(not_empty, len_char_max = 1200),
9    derive(
10        Debug,
11        Clone,
12        PartialEq,
13        Eq,
14        PartialOrd,
15        Ord,
16        Hash,
17        Serialize,
18        Deserialize
19    )
20)]
21pub struct CareSummary(String);
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24/// Decision taxonomy for belongings status in the checkout completion workflow; each value carries operational meaning for source-grounded routing and review.
25pub enum BelongingsStatus {
26    /// Labels work as returned to customer for queueing, review, and downstream agent context.
27    ReturnedToCustomer,
28    /// Labels work as needs staff follow up for queueing, review, and downstream agent context.
29    NeedsStaffFollowUp,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
33/// Decision taxonomy for departure notes review in the checkout completion workflow; each value carries operational meaning for source-grounded routing and review.
34pub enum DepartureNotesReview {
35    /// Represents staff reviewed in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
36    StaffReviewed,
37    /// Represents manager review required in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
38    ManagerReviewRequired,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
42/// Decision taxonomy for completion status in the checkout completion workflow; each value carries operational meaning for source-grounded routing and review.
43pub enum CompletionStatus {
44    /// Labels work as staff verified checkout for queueing, review, and downstream agent context.
45    StaffVerifiedCheckout,
46    /// Labels work as needs staff handoff review for queueing, review, and downstream agent context.
47    NeedsStaffHandoffReview,
48    /// Labels work as source not checked out for queueing, review, and downstream agent context.
49    SourceNotCheckedOut,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
53/// Review-safe agent tasks allowed to save staff time without crossing mutation or send boundaries.
54pub enum SafeAgentAction {
55    /// Allows agents to summarize checkout evidence for staff review without mutating records or contacting customers.
56    SummarizeCheckoutEvidence,
57    /// Allows agents to create internal handoff task for staff review without mutating records or contacting customers.
58    CreateInternalHandoffTask,
59    /// Allows agents to draft retention follow up for review for staff review without mutating records or contacting customers.
60    DraftRetentionFollowUpForReview,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
64/// Actions the agent must never perform without a human/operator system of record.
65pub enum BlockedAction {
66    /// Blocks agents from suggest checked out status until staff or the system of record performs the action.
67    SuggestCheckedOutStatus,
68    /// Blocks agents from send customer message until staff or the system of record performs the action.
69    SendCustomerMessage,
70    /// Blocks agents from mutate provider or pms record until staff or the system of record performs the action.
71    MutateProviderOrPmsRecord,
72    /// Blocks agents from move refund discount or payment until staff or the system of record performs the action.
73    MoveRefundDiscountOrPayment,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
77/// Decision taxonomy for audit event draft in the checkout completion workflow; each value carries operational meaning for source-grounded routing and review.
78pub enum AuditEventDraft {
79    /// Represents source checkout observed in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
80    SourceCheckoutObserved,
81    /// Records the staff-submitted handoff payload as received, even when the source status prevents
82    /// treating it as checkout-completion evidence.
83    StaffHandoffRecorded,
84    /// Represents staff handoff review requested in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
85    StaffHandoffReviewRequested,
86    /// Represents checkout completion suggested in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
87    CheckoutCompletionSuggested,
88    /// Represents customer message approval requested in the checkout completion decision model so the app can choose the correct evidence, review, or draft path without taking live action.
89    CustomerMessageApprovalRequested,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
93/// Staff handoff carried by the checkout completion workflow; it keeps checkout tasks, payment exceptions, and handoff notes explicit for staff review.
94pub struct StaffHandoff {
95    completed_by: entities::ActorRef,
96    completed_at: DateTime<Utc>,
97    belongings_status: BelongingsStatus,
98    care_summary: CareSummary,
99    departure_notes_review: DepartureNotesReview,
100}
101
102impl StaffHandoff {
103    /// Returns the completed by source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
104    pub const fn completed_by(&self) -> &entities::ActorRef {
105        &self.completed_by
106    }
107
108    /// Returns the completed at source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
109    pub const fn completed_at(&self) -> DateTime<Utc> {
110        self.completed_at
111    }
112
113    /// Returns the belongings status source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
114    pub const fn belongings_status(&self) -> BelongingsStatus {
115        self.belongings_status
116    }
117
118    /// Returns the care summary source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
119    pub const fn care_summary(&self) -> &CareSummary {
120        &self.care_summary
121    }
122
123    /// Returns the departure notes review source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
124    pub const fn departure_notes_review(&self) -> DepartureNotesReview {
125        self.departure_notes_review
126    }
127
128    const fn is_resolved_for_checkout_completion(&self) -> bool {
129        matches!(self.belongings_status, BelongingsStatus::ReturnedToCustomer)
130            && matches!(
131                self.departure_notes_review,
132                DepartureNotesReview::StaffReviewed
133            )
134    }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
138/// Input contract for building the workflow packet from source-grounded records.
139pub struct Request {
140    reservation_id: entities::reservation::Id,
141    source_provenance: source::Provenance,
142    observed_source_status: source::reservation::Status,
143    staff_handoff: StaffHandoff,
144}
145
146impl Request {
147    /// Returns the reservation id source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
148    pub const fn reservation_id(&self) -> entities::reservation::Id {
149        self.reservation_id
150    }
151
152    /// Returns the source provenance source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
153    pub const fn source_provenance(&self) -> &source::Provenance {
154        &self.source_provenance
155    }
156
157    /// Returns the observed source status source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
158    pub fn observed_source_status(&self) -> source::reservation::Status {
159        self.observed_source_status.clone()
160    }
161
162    /// Returns the staff handoff source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
163    pub const fn staff_handoff(&self) -> &StaffHandoff {
164        &self.staff_handoff
165    }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169/// Reviewable packet handed to staff or agents with deterministic gates already applied.
170pub struct Packet {
171    reservation_id: entities::reservation::Id,
172    provenance: source::Provenance,
173    staff_handoff: StaffHandoff,
174    completion_status: CompletionStatus,
175    suggested_reservation_status: Option<entities::reservation::Status>,
176    required_review_gates: Vec<policy::ReviewGate>,
177    safe_agent_actions: Vec<SafeAgentAction>,
178    blocked_actions: Vec<BlockedAction>,
179    audit_event_drafts: Vec<AuditEventDraft>,
180}
181
182impl Packet {
183    /// Returns the reservation id source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
184    pub const fn reservation_id(&self) -> entities::reservation::Id {
185        self.reservation_id
186    }
187
188    /// Returns the provenance source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
189    pub const fn provenance(&self) -> &source::Provenance {
190        &self.provenance
191    }
192
193    /// Returns the staff handoff source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
194    pub const fn staff_handoff(&self) -> &StaffHandoff {
195        &self.staff_handoff
196    }
197
198    /// Returns the completion status source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
199    pub const fn completion_status(&self) -> CompletionStatus {
200        self.completion_status
201    }
202
203    /// Returns the suggested reservation status source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
204    pub fn suggested_reservation_status(&self) -> Option<entities::reservation::Status> {
205        self.suggested_reservation_status.clone()
206    }
207
208    /// Returns the required review gates source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
209    pub fn required_review_gates(&self) -> &[policy::ReviewGate] {
210        &self.required_review_gates
211    }
212
213    /// Returns the safe agent actions source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
214    pub fn safe_agent_actions(&self) -> &[SafeAgentAction] {
215        &self.safe_agent_actions
216    }
217
218    /// Returns the blocked actions source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
219    pub fn blocked_actions(&self) -> &[BlockedAction] {
220        &self.blocked_actions
221    }
222
223    /// Returns the audit event drafts source evidence carried by this checkout completion workflow artifact without changing provider, customer, payment, or schedule state.
224    pub fn audit_event_drafts(&self) -> &[AuditEventDraft] {
225        &self.audit_event_drafts
226    }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230/// Workflow carried by the checkout completion workflow; it keeps checkout tasks, payment exceptions, and handoff notes explicit for staff review.
231pub struct Workflow;
232
233impl Workflow {
234    /// Builds evaluate for the checkout completion workflow contract from validated source facts while preserving review gates and draft-only side-effect boundaries.
235    pub fn evaluate(request: Request) -> Packet {
236        let completion_status = completion_status_for(&request);
237        let suggested_reservation_status = match completion_status {
238            CompletionStatus::StaffVerifiedCheckout => {
239                Some(entities::reservation::Status::CheckedOut)
240            }
241            CompletionStatus::NeedsStaffHandoffReview | CompletionStatus::SourceNotCheckedOut => {
242                None
243            }
244        };
245        let required_review_gates = required_review_gates_for(completion_status);
246        let safe_agent_actions = safe_agent_actions_for(completion_status);
247        let blocked_actions = blocked_actions_for(completion_status);
248        let audit_event_drafts = audit_event_drafts_for(completion_status);
249
250        Packet {
251            reservation_id: request.reservation_id,
252            provenance: request.source_provenance,
253            staff_handoff: request.staff_handoff,
254            completion_status,
255            suggested_reservation_status,
256            required_review_gates,
257            safe_agent_actions,
258            blocked_actions,
259            audit_event_drafts,
260        }
261    }
262}
263
264fn completion_status_for(request: &Request) -> CompletionStatus {
265    if !matches!(
266        request.observed_source_status,
267        source::reservation::Status::CheckedOut
268    ) {
269        return CompletionStatus::SourceNotCheckedOut;
270    }
271
272    if request.staff_handoff.is_resolved_for_checkout_completion() {
273        CompletionStatus::StaffVerifiedCheckout
274    } else {
275        CompletionStatus::NeedsStaffHandoffReview
276    }
277}
278
279fn required_review_gates_for(completion_status: CompletionStatus) -> Vec<policy::ReviewGate> {
280    match completion_status {
281        CompletionStatus::StaffVerifiedCheckout => {
282            vec![policy::ReviewGate::CustomerMessageApproval]
283        }
284        CompletionStatus::NeedsStaffHandoffReview | CompletionStatus::SourceNotCheckedOut => {
285            vec![policy::ReviewGate::ManagerApproval]
286        }
287    }
288}
289
290fn safe_agent_actions_for(completion_status: CompletionStatus) -> Vec<SafeAgentAction> {
291    let mut actions = vec![
292        SafeAgentAction::SummarizeCheckoutEvidence,
293        SafeAgentAction::CreateInternalHandoffTask,
294    ];
295    if matches!(completion_status, CompletionStatus::StaffVerifiedCheckout) {
296        actions.push(SafeAgentAction::DraftRetentionFollowUpForReview);
297    }
298    actions
299}
300
301fn blocked_actions_for(completion_status: CompletionStatus) -> Vec<BlockedAction> {
302    let mut blocked_actions = vec![
303        BlockedAction::SendCustomerMessage,
304        BlockedAction::MutateProviderOrPmsRecord,
305        BlockedAction::MoveRefundDiscountOrPayment,
306    ];
307    if !matches!(completion_status, CompletionStatus::StaffVerifiedCheckout) {
308        blocked_actions.push(BlockedAction::SuggestCheckedOutStatus);
309    }
310    blocked_actions.sort_unstable();
311    blocked_actions.dedup();
312    blocked_actions
313}
314
315fn audit_event_drafts_for(completion_status: CompletionStatus) -> Vec<AuditEventDraft> {
316    let mut drafts = vec![AuditEventDraft::StaffHandoffRecorded];
317    match completion_status {
318        CompletionStatus::StaffVerifiedCheckout => {
319            drafts.push(AuditEventDraft::SourceCheckoutObserved);
320            drafts.push(AuditEventDraft::CheckoutCompletionSuggested);
321            drafts.push(AuditEventDraft::CustomerMessageApprovalRequested);
322        }
323        CompletionStatus::NeedsStaffHandoffReview => {
324            drafts.push(AuditEventDraft::SourceCheckoutObserved);
325            drafts.push(AuditEventDraft::StaffHandoffReviewRequested);
326        }
327        CompletionStatus::SourceNotCheckedOut => {
328            drafts.push(AuditEventDraft::StaffHandoffReviewRequested);
329        }
330    }
331    drafts.sort_unstable();
332    drafts.dedup();
333    drafts
334}