Skip to main content

domain/daycare/
mod.rs

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