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}