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}