Skip to main content

domain/reservation/
mod.rs

1//! Reservation support facts used by booking triage and checkout handoff workflows.
2//!
3//! ## Operator summary
4//!
5//! Staff use reservation facts to decide which booking or checkout queue owns the next review:
6//! routine front-desk collection, vaccine/document follow-up, care or behavior review, payment
7//! review, manager exception, or read-only handoff to checkout/retention workflows. The module
8//! reduces labor by naming reusable policy facts—minimum-age thresholds, add-on labels, and
9//! transition reasons—so application packets can surface the same evidence without staff retyping
10//! provider notes or reconciling free-text labels.
11//!
12//! This module must not book, confirm, cancel, check in/out, hold capacity, change pricing,
13//! move money, mutate Gingr/provider/PMS records, or send customer messages. It is source
14//! vocabulary only. Live authority stays with the provider/PMS ledger, approved location policy,
15//! verified payment/deposit records, customer/pet/reservation source snapshots, and accountable
16//! staff/manager approvals.
17//!
18//! Review gates protect pets, customers, and staff whenever reservation facts touch medical or
19//! vaccine evidence, temperament/incident handling, special-care acceptance, capacity or staffing
20//! exceptions, payment/deposit closeout, customer-sensitive copy, or provider mutation. Unknown,
21//! stale, conflicting, or unmapped source facts should remain review work; they must not become
22//! inferred readiness.
23
24mod error;
25
26use serde::{Deserialize, Deserializer, Serialize};
27
28pub use error::{Error, Result};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
31/// Minimum pet age, in weeks, required before a service can be booked.
32pub struct MinimumAgeWeeks(u8);
33
34impl MinimumAgeWeeks {
35    /// Promotes reservation policy input after enforcing the domain validation rule.
36    pub fn try_new(value: u8) -> Result<Self> {
37        if value == 0 {
38            return Err(Error::EmptyMinimumAge);
39        }
40        Ok(Self(value))
41    }
42
43    /// Returns the minimum age threshold used by booking and policy adapters.
44    pub const fn get(self) -> u8 {
45        self.0
46    }
47}
48
49impl<'de> Deserialize<'de> for MinimumAgeWeeks {
50    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
51    where
52        D: Deserializer<'de>,
53    {
54        Self::try_new(u8::deserialize(deserializer)?).map_err(serde::de::Error::custom)
55    }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59/// Reasons a pet may be blocked by age rules for a service.
60pub enum AgePolicyReason {
61    /// Minimum-age rule for boarding reservations.
62    BoardingMinimum,
63    /// Minimum-age rule for day-play reservations.
64    DayPlayMinimum,
65    /// Minimum-age rule for daycare reservations.
66    DaycareMinimum,
67    /// Minimum-age rule configured for a specific service.
68    ServiceSpecificMinimum,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72/// Age gate applied when determining whether a pet may book a service.
73pub struct AgeThreshold {
74    minimum: MinimumAgeWeeks,
75    reason: AgePolicyReason,
76}
77
78impl AgeThreshold {
79    /// Assembles a reservation policy value from validated age and reason parts.
80    pub const fn new(minimum: MinimumAgeWeeks, reason: AgePolicyReason) -> Self {
81        Self { minimum, reason }
82    }
83
84    /// Returns the reservation minimum used by the policy gate.
85    pub const fn minimum(&self) -> MinimumAgeWeeks {
86        self.minimum
87    }
88
89    /// Returns the reservation reason used by the policy gate.
90    pub const fn reason(&self) -> AgePolicyReason {
91        self.reason
92    }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
96/// Display label for an optional reservation add-on offered to the customer.
97pub struct AddOnLabel(String);
98
99impl AddOnLabel {
100    /// Promotes reservation policy input after enforcing the domain validation rule.
101    pub fn try_new(value: impl Into<String>) -> Result<Self> {
102        let value = value.into().trim().to_string();
103        if value.is_empty() {
104            return Err(Error::EmptyAddOnLabel);
105        }
106        if value.chars().count() > 120 {
107            return Err(Error::AddOnLabelTooLong);
108        }
109        Ok(Self(value))
110    }
111
112    /// Returns the owned inner string for storage or outbound mapping.
113    pub fn into_inner(self) -> String {
114        self.0
115    }
116}
117
118impl<'de> Deserialize<'de> for AddOnLabel {
119    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        Self::try_new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
124    }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128/// Business reasons for moving or rejecting a reservation workflow transition.
129pub enum TransitionReason {
130    /// Transition was initiated by a customer request.
131    CustomerRequested,
132    /// Transition was blocked because the requested capacity is unavailable.
133    CapacityUnavailable,
134    /// Transition is blocked by a non-overridable policy.
135    PolicyHardStop,
136    /// Transition is blocked until required customer or pet details are supplied.
137    MissingRequiredInformation,
138    /// Staff manually approved a workflow transition.
139    StaffOverride,
140}