backbone_inventory/domain/entity/
location.rs1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5
6use super::LocationUsage;
7use super::AuditMetadata;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct LocationId(pub Uuid);
13
14impl LocationId {
15 pub fn new(id: Uuid) -> Self { Self(id) }
16 pub fn generate() -> Self { Self(Uuid::new_v4()) }
17 pub fn into_inner(self) -> Uuid { self.0 }
18}
19
20impl std::fmt::Display for LocationId {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "{}", self.0)
23 }
24}
25
26impl std::str::FromStr for LocationId {
27 type Err = uuid::Error;
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 Ok(Self(Uuid::parse_str(s)?))
30 }
31}
32
33impl From<Uuid> for LocationId {
34 fn from(id: Uuid) -> Self { Self(id) }
35}
36
37impl From<LocationId> for Uuid {
38 fn from(id: LocationId) -> Self { id.0 }
39}
40
41impl AsRef<Uuid> for LocationId {
42 fn as_ref(&self) -> &Uuid { &self.0 }
43}
44
45impl std::ops::Deref for LocationId {
46 type Target = Uuid;
47 fn deref(&self) -> &Self::Target { &self.0 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
51pub struct Location {
52 pub id: Uuid,
53 pub name: String,
54 pub complete_name: String,
55 pub active: bool,
56 pub usage: LocationUsage,
57 pub location_id: Option<Uuid>,
58 pub parent_path: String,
59 pub barcode: Option<String>,
60 pub warehouse_id: Option<Uuid>,
61 pub cyclic_inventory_frequency: i32,
62 pub last_inventory_date: Option<NaiveDate>,
63 pub next_inventory_date: Option<NaiveDate>,
64 pub valuation_account_id: Option<Uuid>,
65 pub storage_category_id: Option<Uuid>,
66 #[serde(default)]
67 #[sqlx(json)]
68 pub metadata: AuditMetadata,
69}
70
71impl Location {
72 pub fn builder() -> LocationBuilder {
74 <LocationBuilder as Default>::default()
75 }
76
77 pub fn new(name: String, complete_name: String, active: bool, usage: LocationUsage, parent_path: String, cyclic_inventory_frequency: i32) -> Self {
79 Self {
80 id: Uuid::new_v4(),
81 name,
82 complete_name,
83 active,
84 usage,
85 location_id: None,
86 parent_path,
87 barcode: None,
88 warehouse_id: None,
89 cyclic_inventory_frequency,
90 last_inventory_date: None,
91 next_inventory_date: None,
92 valuation_account_id: None,
93 storage_category_id: None,
94 metadata: AuditMetadata::default(),
95 }
96 }
97
98 pub fn id(&self) -> &Uuid {
100 &self.id
101 }
102
103 pub fn typed_id(&self) -> LocationId {
105 LocationId(self.id)
106 }
107
108 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
110 self.metadata.created_at.as_ref()
111 }
112
113 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
115 self.metadata.updated_at.as_ref()
116 }
117
118 pub fn is_deleted(&self) -> bool {
120 self.metadata.deleted_at.is_some()
121 }
122
123 pub fn is_active(&self) -> bool {
125 self.metadata.deleted_at.is_none()
126 }
127
128 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
130 self.metadata.deleted_at.as_ref()
131 }
132
133 pub fn created_by(&self) -> Option<&Uuid> {
135 self.metadata.created_by.as_ref()
136 }
137
138 pub fn updated_by(&self) -> Option<&Uuid> {
140 self.metadata.updated_by.as_ref()
141 }
142
143 pub fn deleted_by(&self) -> Option<&Uuid> {
145 self.metadata.deleted_by.as_ref()
146 }
147
148
149 pub fn with_location_id(mut self, value: Uuid) -> Self {
155 self.location_id = Some(value);
156 self
157 }
158
159 pub fn with_barcode(mut self, value: String) -> Self {
161 self.barcode = Some(value);
162 self
163 }
164
165 pub fn with_warehouse_id(mut self, value: Uuid) -> Self {
167 self.warehouse_id = Some(value);
168 self
169 }
170
171 pub fn with_last_inventory_date(mut self, value: NaiveDate) -> Self {
173 self.last_inventory_date = Some(value);
174 self
175 }
176
177 pub fn with_next_inventory_date(mut self, value: NaiveDate) -> Self {
179 self.next_inventory_date = Some(value);
180 self
181 }
182
183 pub fn with_valuation_account_id(mut self, value: Uuid) -> Self {
185 self.valuation_account_id = Some(value);
186 self
187 }
188
189 pub fn with_storage_category_id(mut self, value: Uuid) -> Self {
191 self.storage_category_id = Some(value);
192 self
193 }
194
195 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
201 for (key, value) in fields {
202 match key.as_str() {
203 "name" => {
204 if let Ok(v) = serde_json::from_value(value) { self.name = v; }
205 }
206 "complete_name" => {
207 if let Ok(v) = serde_json::from_value(value) { self.complete_name = v; }
208 }
209 "active" => {
210 if let Ok(v) = serde_json::from_value(value) { self.active = v; }
211 }
212 "usage" => {
213 if let Ok(v) = serde_json::from_value(value) { self.usage = v; }
214 }
215 "location_id" => {
216 if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
217 }
218 "parent_path" => {
219 if let Ok(v) = serde_json::from_value(value) { self.parent_path = v; }
220 }
221 "barcode" => {
222 if let Ok(v) = serde_json::from_value(value) { self.barcode = v; }
223 }
224 "warehouse_id" => {
225 if let Ok(v) = serde_json::from_value(value) { self.warehouse_id = v; }
226 }
227 "cyclic_inventory_frequency" => {
228 if let Ok(v) = serde_json::from_value(value) { self.cyclic_inventory_frequency = v; }
229 }
230 "last_inventory_date" => {
231 if let Ok(v) = serde_json::from_value(value) { self.last_inventory_date = v; }
232 }
233 "next_inventory_date" => {
234 if let Ok(v) = serde_json::from_value(value) { self.next_inventory_date = v; }
235 }
236 "valuation_account_id" => {
237 if let Ok(v) = serde_json::from_value(value) { self.valuation_account_id = v; }
238 }
239 "storage_category_id" => {
240 if let Ok(v) = serde_json::from_value(value) { self.storage_category_id = v; }
241 }
242 _ => {} }
244 }
245 }
246
247}
248
249impl super::Entity for Location {
250 type Id = Uuid;
251
252 fn entity_id(&self) -> &Self::Id {
253 &self.id
254 }
255
256 fn entity_type() -> &'static str {
257 "Location"
258 }
259}
260
261impl backbone_core::PersistentEntity for Location {
262 fn entity_id(&self) -> String {
263 self.id.to_string()
264 }
265 fn set_entity_id(&mut self, id: String) {
266 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
267 self.id = uuid;
268 }
269 }
270 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
271 self.metadata.created_at
272 }
273 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
274 self.metadata.created_at = Some(ts);
275 }
276 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
277 self.metadata.updated_at
278 }
279 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
280 self.metadata.updated_at = Some(ts);
281 }
282 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
283 self.metadata.deleted_at
284 }
285 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
286 self.metadata.deleted_at = ts;
287 }
288}
289
290impl backbone_orm::EntityRepoMeta for Location {
291 fn column_types() -> std::collections::HashMap<String, String> {
292 let mut m = std::collections::HashMap::new();
293 m.insert("id".to_string(), "uuid".to_string());
294 m.insert("location_id".to_string(), "uuid".to_string());
295 m.insert("warehouse_id".to_string(), "uuid".to_string());
296 m.insert("valuation_account_id".to_string(), "uuid".to_string());
297 m.insert("storage_category_id".to_string(), "uuid".to_string());
298 m.insert("usage".to_string(), "location_usage".to_string());
299 m.insert("last_inventory_date".to_string(), "date".to_string());
300 m.insert("next_inventory_date".to_string(), "date".to_string());
301 m
302 }
303 fn search_fields() -> &'static [&'static str] {
304 &["name", "complete_name", "parent_path"]
305 }
306 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
307 &[("location", "locations", "locationId"), ("warehouse", "warehouses", "warehouseId"), ("storageCategory", "storage_categories", "storageCategoryId")]
308 }
309}
310
311#[derive(Debug, Clone, Default)]
316pub struct LocationBuilder {
317 name: Option<String>,
318 complete_name: Option<String>,
319 active: Option<bool>,
320 usage: Option<LocationUsage>,
321 location_id: Option<Uuid>,
322 parent_path: Option<String>,
323 barcode: Option<String>,
324 warehouse_id: Option<Uuid>,
325 cyclic_inventory_frequency: Option<i32>,
326 last_inventory_date: Option<NaiveDate>,
327 next_inventory_date: Option<NaiveDate>,
328 valuation_account_id: Option<Uuid>,
329 storage_category_id: Option<Uuid>,
330}
331
332impl LocationBuilder {
333 pub fn name(mut self, value: String) -> Self {
335 self.name = Some(value);
336 self
337 }
338
339 pub fn complete_name(mut self, value: String) -> Self {
341 self.complete_name = Some(value);
342 self
343 }
344
345 pub fn active(mut self, value: bool) -> Self {
347 self.active = Some(value);
348 self
349 }
350
351 pub fn usage(mut self, value: LocationUsage) -> Self {
353 self.usage = Some(value);
354 self
355 }
356
357 pub fn location_id(mut self, value: Uuid) -> Self {
359 self.location_id = Some(value);
360 self
361 }
362
363 pub fn parent_path(mut self, value: String) -> Self {
365 self.parent_path = Some(value);
366 self
367 }
368
369 pub fn barcode(mut self, value: String) -> Self {
371 self.barcode = Some(value);
372 self
373 }
374
375 pub fn warehouse_id(mut self, value: Uuid) -> Self {
377 self.warehouse_id = Some(value);
378 self
379 }
380
381 pub fn cyclic_inventory_frequency(mut self, value: i32) -> Self {
383 self.cyclic_inventory_frequency = Some(value);
384 self
385 }
386
387 pub fn last_inventory_date(mut self, value: NaiveDate) -> Self {
389 self.last_inventory_date = Some(value);
390 self
391 }
392
393 pub fn next_inventory_date(mut self, value: NaiveDate) -> Self {
395 self.next_inventory_date = Some(value);
396 self
397 }
398
399 pub fn valuation_account_id(mut self, value: Uuid) -> Self {
401 self.valuation_account_id = Some(value);
402 self
403 }
404
405 pub fn storage_category_id(mut self, value: Uuid) -> Self {
407 self.storage_category_id = Some(value);
408 self
409 }
410
411 pub fn build(self) -> Result<Location, String> {
415 let name = self.name.ok_or_else(|| "name is required".to_string())?;
416 let complete_name = self.complete_name.ok_or_else(|| "complete_name is required".to_string())?;
417 let parent_path = self.parent_path.ok_or_else(|| "parent_path is required".to_string())?;
418
419 Ok(Location {
420 id: Uuid::new_v4(),
421 name,
422 complete_name,
423 active: self.active.unwrap_or(true),
424 usage: self.usage.unwrap_or_default(),
425 location_id: self.location_id,
426 parent_path,
427 barcode: self.barcode,
428 warehouse_id: self.warehouse_id,
429 cyclic_inventory_frequency: self.cyclic_inventory_frequency.unwrap_or(0),
430 last_inventory_date: self.last_inventory_date,
431 next_inventory_date: self.next_inventory_date,
432 valuation_account_id: self.valuation_account_id,
433 storage_category_id: self.storage_category_id,
434 metadata: AuditMetadata::default(),
435 })
436 }
437}