phytrace_sdk/models/domains/
payload.rs1use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6use super::common::ObjectRef;
7use crate::models::enums::LoadStatus;
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
11pub struct PayloadDomain {
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub load_status: Option<LoadStatus>,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
18 #[validate(range(min = 0.0))]
19 pub total_weight_kg: Option<f64>,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub max_weight_kg: Option<f64>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 #[validate(range(min = 0.0, max = 100.0))]
28 pub weight_utilization_pct: Option<f64>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub items: Option<Vec<PayloadItem>>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub item_count: Option<u32>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub compartments: Option<Vec<Compartment>>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub is_secured: Option<bool>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub center_of_mass_offset_m: Option<[f64; 3]>,
49}
50
51#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
53pub struct PayloadItem {
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub object_ref: Option<ObjectRef>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub item_id: Option<String>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub item_type: Option<String>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 #[validate(range(min = 0.0))]
69 pub weight_kg: Option<f64>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub quantity: Option<u32>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub compartment_id: Option<String>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub pickup_location: Option<String>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub destination: Option<String>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub is_fragile: Option<bool>,
90
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub special_handling: Option<String>,
94}
95
96#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
98pub struct Compartment {
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub compartment_id: Option<String>,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub name: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub status: Option<LoadStatus>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 #[validate(range(min = 0.0))]
114 pub weight_kg: Option<f64>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub max_weight_kg: Option<f64>,
119
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub item_count: Option<u32>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub is_open: Option<bool>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub is_locked: Option<bool>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub temperature_c: Option<f64>,
135}
136
137impl PayloadDomain {
138 pub fn empty() -> Self {
140 Self {
141 load_status: Some(LoadStatus::Empty),
142 total_weight_kg: Some(0.0),
143 item_count: Some(0),
144 ..Default::default()
145 }
146 }
147
148 pub fn with_weight(weight_kg: f64, max_weight_kg: f64) -> Self {
150 let status = if weight_kg == 0.0 {
151 LoadStatus::Empty
152 } else if weight_kg >= max_weight_kg * 0.8 {
153 LoadStatus::FullLoad
154 } else {
155 LoadStatus::PartialLoad
156 };
157
158 Self {
159 load_status: Some(status),
160 total_weight_kg: Some(weight_kg),
161 max_weight_kg: Some(max_weight_kg),
162 weight_utilization_pct: Some((weight_kg / max_weight_kg) * 100.0),
163 ..Default::default()
164 }
165 }
166
167 pub fn with_item(mut self, item: PayloadItem) -> Self {
169 let items = self.items.get_or_insert_with(Vec::new);
170 items.push(item);
171 self.item_count = Some(items.len() as u32);
172 self
173 }
174
175 pub fn with_compartment(mut self, compartment: Compartment) -> Self {
177 let compartments = self.compartments.get_or_insert_with(Vec::new);
178 compartments.push(compartment);
179 self
180 }
181}
182
183impl PayloadItem {
184 pub fn new(item_id: impl Into<String>, item_type: impl Into<String>) -> Self {
186 Self {
187 item_id: Some(item_id.into()),
188 item_type: Some(item_type.into()),
189 quantity: Some(1),
190 ..Default::default()
191 }
192 }
193
194 pub fn with_weight(mut self, weight_kg: f64) -> Self {
196 self.weight_kg = Some(weight_kg);
197 self
198 }
199
200 pub fn with_destination(mut self, dest: impl Into<String>) -> Self {
202 self.destination = Some(dest.into());
203 self
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_payload_empty() {
213 let payload = PayloadDomain::empty();
214 assert_eq!(payload.load_status, Some(LoadStatus::Empty));
215 }
216
217 #[test]
218 fn test_payload_with_weight() {
219 let payload = PayloadDomain::with_weight(50.0, 100.0);
220 assert_eq!(payload.load_status, Some(LoadStatus::PartialLoad));
221 assert_eq!(payload.weight_utilization_pct, Some(50.0));
222 }
223
224 #[test]
225 fn test_payload_item() {
226 let payload =
227 PayloadDomain::empty().with_item(PayloadItem::new("item-001", "box").with_weight(5.0));
228 assert_eq!(payload.item_count, Some(1));
229 }
230}