Skip to main content

domain/daycare/
mod.rs

1//! Daycare service contracts for front-desk throughput, safe play, and package review.
2//!
3//! The module keeps care mode, eligibility, staffing ratios, and package policy explicit so
4//! automated recommendations reduce check-in labor without bypassing staff review:
5//!
6//! ```
7//! use domain::daycare;
8//!
9//! let contract = daycare::Contract::standard_petsuites();
10//! assert!(contract.requires_staff_review_before_group_play());
11//! assert_eq!(
12//!     daycare::ServiceVariant::DayBoarding.care_mode(),
13//!     daycare::CareMode::DogIndividualDayBoarding,
14//! );
15//! ```
16
17use bon::Builder;
18use chrono::NaiveDate;
19use nutype::nutype;
20use serde::{Deserialize, Deserializer, Serialize};
21
22use crate::entities::{CustomerId, PetId};
23
24macro_rules! positive_scalar {
25    ($name:ident, $primitive:ty, $error:ident, $message:literal) => {
26        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
27        /// Positive scalar used by daycare policy where zero would hide real staffing, pet-count, queue, or package volume.
28        pub struct $name($primitive);
29
30        impl $name {
31            /// Promotes boundary input into a validated daycare domain value.
32            pub const fn try_new(value: $primitive) -> std::result::Result<Self, $error> {
33                if value == 0 {
34                    return Err($error::Zero);
35                }
36                Ok(Self(value))
37            }
38
39            /// Exposes the validated scalar for serialization and adapter boundaries.
40            pub const fn get(self) -> $primitive {
41                self.0
42            }
43        }
44
45        impl<'de> Deserialize<'de> for $name {
46            fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
47            where
48                D: Deserializer<'de>,
49            {
50                Self::try_new(<$primitive>::deserialize(deserializer)?)
51                    .map_err(serde::de::Error::custom)
52            }
53        }
54
55        #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
56        /// Validation failure returned when a required positive daycare scalar is zero.
57        pub enum $error {
58            #[error($message)]
59            /// Rejects zero where the pet-resort workflow requires a positive quantity.
60            Zero,
61        }
62    };
63}
64
65positive_scalar!(
66    PackageVisits,
67    u16,
68    PackageVisitsError,
69    "daycare packages require at least one visit"
70);
71positive_scalar!(
72    StaffCount,
73    u16,
74    StaffCountError,
75    "daycare ratio requires at least one staff member"
76);
77positive_scalar!(
78    PetCount,
79    u16,
80    PetCountError,
81    "daycare ratio requires at least one pet"
82);
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85/// Daycare service variant requested by a customer or reservation workflow.
86pub enum ServiceVariant {
87    /// Full-day dog group-play service requiring eligibility and staffing-ratio checks.
88    AllDayPlay,
89    /// Partial-day dog group-play service requiring eligibility and staffing-ratio checks.
90    HalfDayPlay,
91    /// Daytime boarding care with lodging-style supervision.
92    DayBoarding,
93    /// Hybrid daycare offering that combines play with room-based rest or supervision.
94    DayPlayPlusRoom,
95    /// Cat enrichment service that remains separate from dog group-play eligibility rules.
96    CatIndividualPlaytime,
97}
98
99impl ServiceVariant {
100    /// Maps the customer-facing service variant to the care mode used by eligibility and staffing policy.
101    pub const fn care_mode(self) -> CareMode {
102        match self {
103            Self::AllDayPlay | Self::HalfDayPlay => CareMode::DogGroupPlay,
104            Self::DayBoarding => CareMode::DogIndividualDayBoarding,
105            Self::DayPlayPlusRoom => CareMode::DogHybridPlayAndRoom,
106            Self::CatIndividualPlaytime => CareMode::CatIndividualEnrichment,
107        }
108    }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112/// Operational care mode that determines whether group-play, individual care, or cat enrichment rules apply.
113pub enum CareMode {
114    /// Dog group-play mode requiring temperament, vaccine, and staffing-ratio clearance.
115    DogGroupPlay,
116    /// Individual dog supervision mode for pets not suited to group play or needing quieter care.
117    DogIndividualDayBoarding,
118    /// Mixed dog care mode combining group-play eligibility with room-based supervision.
119    DogHybridPlayAndRoom,
120    /// Individual cat enrichment mode outside dog playgroup assignment.
121    CatIndividualEnrichment,
122}
123
124pub mod incident;
125
126pub mod coverage;
127
128pub mod eligibility;
129
130pub mod assignment;
131
132pub mod attendance;
133
134pub mod package_opportunity;
135
136pub mod front_desk;
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
139/// Attendance policy controlling whether daycare check-in is drop-in, reserved, or waitlisted.
140pub enum AttendancePolicy {
141    /// Staff may accept unscheduled daycare arrivals if other gates are clear.
142    DropInAllowed,
143    /// Staff should require a reservation before admitting daycare attendance.
144    ReservationRequired,
145    /// Capacity constraints require staff to route new daycare demand through a waitlist.
146    CapacityManagedWaitlist,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150/// Daycare payment/package model used for collection and recommendation workflows.
151pub enum PackagePolicy {
152    /// Customer pays each visit without a prepaid package or membership covering attendance.
153    PayPerVisit,
154    /// Prepaid visit count available to apply against daycare attendance.
155    PrepaidPasses {
156        /// Visits carried by this variant.
157        visits: PackageVisits,
158    },
159    /// Customer has a membership covering the daycare attendance path.
160    Membership,
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164/// Staff-to-pet ratio used to decide whether group-play coverage is sufficient.
165pub struct StaffPetRatio {
166    staff: StaffCount,
167    pets: PetCount,
168}
169impl StaffPetRatio {
170    /// Creates the daycare value from validated domain parts without trusting raw source primitives.
171    pub const fn new(staff: StaffCount, pets: PetCount) -> Self {
172        Self { staff, pets }
173    }
174    /// Returns the allowed pet count per staff member for coverage checks.
175    pub const fn pets_per_staff(&self) -> PetCount {
176        self.pets
177    }
178    /// Returns the staff side of the ratio used by coverage checks.
179    pub const fn staff(&self) -> StaffCount {
180        self.staff
181    }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
185/// Rule used to choose or restrict daycare group assignment.
186pub enum GroupAssignmentRule {
187    /// Assign pets only to groups matched by temperament and size evidence.
188    TemperamentAndSizeMatched,
189    /// Restrict the pet to individual play instead of group assignment.
190    IndividualPlayOnly,
191    /// Route the pet to a calmer group suited to senior or low-energy needs.
192    SeniorOrLowEnergyGroup,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196/// Evidence requirements that must be satisfied before daycare care modes proceed.
197pub enum EligibilityRequirement {
198    /// Current temperament assessment is required before group-play admission.
199    TemperamentAssessment,
200    /// Vaccine proof must be current before daycare admission.
201    VaccinesCurrent,
202    /// Spay/neuter status must be reviewed for dog group-play eligibility.
203    SpayNeuterForGroupPlay,
204    /// Staffing coverage must satisfy the configured ratio before attendance proceeds.
205    StaffRatioAvailable,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
209/// Daycare service-line contract combining attendance, package, ratio, assignment, incident, and eligibility policy.
210pub struct Contract {
211    /// Attendance gate controlling reservations, drop-ins, and waitlist routing.
212    pub attendance: AttendancePolicy,
213    /// Package or payment model used for front-desk collection and sales opportunities.
214    pub package: PackagePolicy,
215    /// Staff-to-pet ratio that defines safe coverage for daycare operations.
216    pub ratio: StaffPetRatio,
217    /// Assignment rule that protects playgroup fit and care safety.
218    pub group_assignment: GroupAssignmentRule,
219    /// Incident handling policy that can require manager review or customer notice.
220    pub incident: incident::Policy,
221    #[builder(default)]
222    /// Eligibility requirements that must be evidenced before group-play automation proceeds.
223    pub eligibility: Vec<EligibilityRequirement>,
224}
225
226impl Contract {
227    /// Reports whether this contract requires staff review before admitting a pet to group play.
228    pub fn requires_staff_review_before_group_play(&self) -> bool {
229        self.eligibility
230            .contains(&EligibilityRequirement::TemperamentAssessment)
231            || matches!(
232                self.group_assignment,
233                GroupAssignmentRule::TemperamentAndSizeMatched
234            )
235    }
236    /// Builds the baseline PetSuites-style daycare contract used by examples and tests.
237    pub fn standard_petsuites() -> Self {
238        Self::builder()
239            .attendance(AttendancePolicy::ReservationRequired)
240            .package(PackagePolicy::PrepaidPasses {
241                visits: PackageVisits::try_new(5).unwrap(),
242            })
243            .ratio(StaffPetRatio::new(
244                StaffCount::try_new(1).unwrap(),
245                PetCount::try_new(12).unwrap(),
246            ))
247            .group_assignment(GroupAssignmentRule::TemperamentAndSizeMatched)
248            .incident(incident::Policy::ManagerReviewAndCustomerNotice)
249            .eligibility(vec![
250                EligibilityRequirement::TemperamentAssessment,
251                EligibilityRequirement::VaccinesCurrent,
252            ])
253            .build()
254    }
255}