backbone_maintenance/domain/entity/
maintenance_visit.rs1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::MaintenanceType;
8use super::VisitStatus;
9use super::AuditMetadata;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct MaintenanceVisitId(pub Uuid);
15
16impl MaintenanceVisitId {
17 pub fn new(id: Uuid) -> Self { Self(id) }
18 pub fn generate() -> Self { Self(Uuid::new_v4()) }
19 pub fn into_inner(self) -> Uuid { self.0 }
20}
21
22impl std::fmt::Display for MaintenanceVisitId {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(f, "{}", self.0)
25 }
26}
27
28impl std::str::FromStr for MaintenanceVisitId {
29 type Err = uuid::Error;
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 Ok(Self(Uuid::parse_str(s)?))
32 }
33}
34
35impl From<Uuid> for MaintenanceVisitId {
36 fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<MaintenanceVisitId> for Uuid {
40 fn from(id: MaintenanceVisitId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for MaintenanceVisitId {
44 fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for MaintenanceVisitId {
48 type Target = Uuid;
49 fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct MaintenanceVisit {
54 pub id: Uuid,
55 pub asset_id: Uuid,
56 pub schedule_id: Option<Uuid>,
57 pub maintenance_type: MaintenanceType,
58 pub status: VisitStatus,
59 pub warehouse_id: Option<Uuid>,
60 pub warranty_claim_id: Option<Uuid>,
61 pub scheduled_date: NaiveDate,
62 pub performed_date: Option<NaiveDate>,
63 pub labor_cost: Decimal,
64 pub parts_cost: Decimal,
65 pub total_cost: Decimal,
66 pub maintenance_expense_account_id: Option<Uuid>,
67 pub parts_inventory_account_id: Option<Uuid>,
68 pub labor_payable_account_id: Option<Uuid>,
69 pub journal_id: Option<Uuid>,
70 pub accounting_post_id: Option<Uuid>,
71 pub notes: Option<String>,
72 #[serde(default)]
73 #[sqlx(json)]
74 pub metadata: AuditMetadata,
75}
76
77impl MaintenanceVisit {
78 pub fn builder() -> MaintenanceVisitBuilder {
80 <MaintenanceVisitBuilder as Default>::default()
81 }
82
83 pub fn new(asset_id: Uuid, maintenance_type: MaintenanceType, status: VisitStatus, scheduled_date: NaiveDate, labor_cost: Decimal, parts_cost: Decimal, total_cost: Decimal) -> Self {
85 Self {
86 id: Uuid::new_v4(),
87 asset_id,
88 schedule_id: None,
89 maintenance_type,
90 status,
91 warehouse_id: None,
92 warranty_claim_id: None,
93 scheduled_date,
94 performed_date: None,
95 labor_cost,
96 parts_cost,
97 total_cost,
98 maintenance_expense_account_id: None,
99 parts_inventory_account_id: None,
100 labor_payable_account_id: None,
101 journal_id: None,
102 accounting_post_id: None,
103 notes: None,
104 metadata: AuditMetadata::default(),
105 }
106 }
107
108 pub fn id(&self) -> &Uuid {
110 &self.id
111 }
112
113 pub fn typed_id(&self) -> MaintenanceVisitId {
115 MaintenanceVisitId(self.id)
116 }
117
118 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
120 self.metadata.created_at.as_ref()
121 }
122
123 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
125 self.metadata.updated_at.as_ref()
126 }
127
128 pub fn is_deleted(&self) -> bool {
130 self.metadata.deleted_at.is_some()
131 }
132
133 pub fn is_active(&self) -> bool {
135 self.metadata.deleted_at.is_none()
136 }
137
138 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
140 self.metadata.deleted_at.as_ref()
141 }
142
143 pub fn created_by(&self) -> Option<&Uuid> {
145 self.metadata.created_by.as_ref()
146 }
147
148 pub fn updated_by(&self) -> Option<&Uuid> {
150 self.metadata.updated_by.as_ref()
151 }
152
153 pub fn deleted_by(&self) -> Option<&Uuid> {
155 self.metadata.deleted_by.as_ref()
156 }
157
158 pub fn status(&self) -> &VisitStatus {
160 &self.status
161 }
162
163
164 pub fn with_schedule_id(mut self, value: Uuid) -> Self {
170 self.schedule_id = Some(value);
171 self
172 }
173
174 pub fn with_warehouse_id(mut self, value: Uuid) -> Self {
176 self.warehouse_id = Some(value);
177 self
178 }
179
180 pub fn with_warranty_claim_id(mut self, value: Uuid) -> Self {
182 self.warranty_claim_id = Some(value);
183 self
184 }
185
186 pub fn with_performed_date(mut self, value: NaiveDate) -> Self {
188 self.performed_date = Some(value);
189 self
190 }
191
192 pub fn with_maintenance_expense_account_id(mut self, value: Uuid) -> Self {
194 self.maintenance_expense_account_id = Some(value);
195 self
196 }
197
198 pub fn with_parts_inventory_account_id(mut self, value: Uuid) -> Self {
200 self.parts_inventory_account_id = Some(value);
201 self
202 }
203
204 pub fn with_labor_payable_account_id(mut self, value: Uuid) -> Self {
206 self.labor_payable_account_id = Some(value);
207 self
208 }
209
210 pub fn with_journal_id(mut self, value: Uuid) -> Self {
212 self.journal_id = Some(value);
213 self
214 }
215
216 pub fn with_accounting_post_id(mut self, value: Uuid) -> Self {
218 self.accounting_post_id = Some(value);
219 self
220 }
221
222 pub fn with_notes(mut self, value: String) -> Self {
224 self.notes = Some(value);
225 self
226 }
227
228 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
234 for (key, value) in fields {
235 match key.as_str() {
236 "asset_id" => {
237 if let Ok(v) = serde_json::from_value(value) { self.asset_id = v; }
238 }
239 "schedule_id" => {
240 if let Ok(v) = serde_json::from_value(value) { self.schedule_id = v; }
241 }
242 "maintenance_type" => {
243 if let Ok(v) = serde_json::from_value(value) { self.maintenance_type = v; }
244 }
245 "status" => {
246 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
247 }
248 "warehouse_id" => {
249 if let Ok(v) = serde_json::from_value(value) { self.warehouse_id = v; }
250 }
251 "warranty_claim_id" => {
252 if let Ok(v) = serde_json::from_value(value) { self.warranty_claim_id = v; }
253 }
254 "scheduled_date" => {
255 if let Ok(v) = serde_json::from_value(value) { self.scheduled_date = v; }
256 }
257 "performed_date" => {
258 if let Ok(v) = serde_json::from_value(value) { self.performed_date = v; }
259 }
260 "labor_cost" => {
261 if let Ok(v) = serde_json::from_value(value) { self.labor_cost = v; }
262 }
263 "parts_cost" => {
264 if let Ok(v) = serde_json::from_value(value) { self.parts_cost = v; }
265 }
266 "total_cost" => {
267 if let Ok(v) = serde_json::from_value(value) { self.total_cost = v; }
268 }
269 "maintenance_expense_account_id" => {
270 if let Ok(v) = serde_json::from_value(value) { self.maintenance_expense_account_id = v; }
271 }
272 "parts_inventory_account_id" => {
273 if let Ok(v) = serde_json::from_value(value) { self.parts_inventory_account_id = v; }
274 }
275 "labor_payable_account_id" => {
276 if let Ok(v) = serde_json::from_value(value) { self.labor_payable_account_id = v; }
277 }
278 "journal_id" => {
279 if let Ok(v) = serde_json::from_value(value) { self.journal_id = v; }
280 }
281 "accounting_post_id" => {
282 if let Ok(v) = serde_json::from_value(value) { self.accounting_post_id = v; }
283 }
284 "notes" => {
285 if let Ok(v) = serde_json::from_value(value) { self.notes = v; }
286 }
287 _ => {} }
289 }
290 }
291
292 }
295
296impl super::Entity for MaintenanceVisit {
297 type Id = Uuid;
298
299 fn entity_id(&self) -> &Self::Id {
300 &self.id
301 }
302
303 fn entity_type() -> &'static str {
304 "MaintenanceVisit"
305 }
306}
307
308impl backbone_core::PersistentEntity for MaintenanceVisit {
309 fn entity_id(&self) -> String {
310 self.id.to_string()
311 }
312 fn set_entity_id(&mut self, id: String) {
313 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
314 self.id = uuid;
315 }
316 }
317 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
318 self.metadata.created_at
319 }
320 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
321 self.metadata.created_at = Some(ts);
322 }
323 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
324 self.metadata.updated_at
325 }
326 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
327 self.metadata.updated_at = Some(ts);
328 }
329 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
330 self.metadata.deleted_at
331 }
332 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
333 self.metadata.deleted_at = ts;
334 }
335}
336
337impl backbone_orm::EntityRepoMeta for MaintenanceVisit {
338 fn column_types() -> std::collections::HashMap<String, String> {
339 let mut m = std::collections::HashMap::new();
340 m.insert("id".to_string(), "uuid".to_string());
341 m.insert("asset_id".to_string(), "uuid".to_string());
342 m.insert("schedule_id".to_string(), "uuid".to_string());
343 m.insert("warehouse_id".to_string(), "uuid".to_string());
344 m.insert("warranty_claim_id".to_string(), "uuid".to_string());
345 m.insert("maintenance_expense_account_id".to_string(), "uuid".to_string());
346 m.insert("parts_inventory_account_id".to_string(), "uuid".to_string());
347 m.insert("labor_payable_account_id".to_string(), "uuid".to_string());
348 m.insert("journal_id".to_string(), "uuid".to_string());
349 m.insert("accounting_post_id".to_string(), "uuid".to_string());
350 m.insert("maintenance_type".to_string(), "maintenance_type".to_string());
351 m.insert("status".to_string(), "visit_status".to_string());
352 m.insert("scheduled_date".to_string(), "date".to_string());
353 m.insert("performed_date".to_string(), "date".to_string());
354 m
355 }
356 fn search_fields() -> &'static [&'static str] {
357 &[]
358 }
359}
360
361#[derive(Debug, Clone, Default)]
366pub struct MaintenanceVisitBuilder {
367 asset_id: Option<Uuid>,
368 schedule_id: Option<Uuid>,
369 maintenance_type: Option<MaintenanceType>,
370 status: Option<VisitStatus>,
371 warehouse_id: Option<Uuid>,
372 warranty_claim_id: Option<Uuid>,
373 scheduled_date: Option<NaiveDate>,
374 performed_date: Option<NaiveDate>,
375 labor_cost: Option<Decimal>,
376 parts_cost: Option<Decimal>,
377 total_cost: Option<Decimal>,
378 maintenance_expense_account_id: Option<Uuid>,
379 parts_inventory_account_id: Option<Uuid>,
380 labor_payable_account_id: Option<Uuid>,
381 journal_id: Option<Uuid>,
382 accounting_post_id: Option<Uuid>,
383 notes: Option<String>,
384}
385
386impl MaintenanceVisitBuilder {
387 pub fn asset_id(mut self, value: Uuid) -> Self {
389 self.asset_id = Some(value);
390 self
391 }
392
393 pub fn schedule_id(mut self, value: Uuid) -> Self {
395 self.schedule_id = Some(value);
396 self
397 }
398
399 pub fn maintenance_type(mut self, value: MaintenanceType) -> Self {
401 self.maintenance_type = Some(value);
402 self
403 }
404
405 pub fn status(mut self, value: VisitStatus) -> Self {
407 self.status = Some(value);
408 self
409 }
410
411 pub fn warehouse_id(mut self, value: Uuid) -> Self {
413 self.warehouse_id = Some(value);
414 self
415 }
416
417 pub fn warranty_claim_id(mut self, value: Uuid) -> Self {
419 self.warranty_claim_id = Some(value);
420 self
421 }
422
423 pub fn scheduled_date(mut self, value: NaiveDate) -> Self {
425 self.scheduled_date = Some(value);
426 self
427 }
428
429 pub fn performed_date(mut self, value: NaiveDate) -> Self {
431 self.performed_date = Some(value);
432 self
433 }
434
435 pub fn labor_cost(mut self, value: Decimal) -> Self {
437 self.labor_cost = Some(value);
438 self
439 }
440
441 pub fn parts_cost(mut self, value: Decimal) -> Self {
443 self.parts_cost = Some(value);
444 self
445 }
446
447 pub fn total_cost(mut self, value: Decimal) -> Self {
449 self.total_cost = Some(value);
450 self
451 }
452
453 pub fn maintenance_expense_account_id(mut self, value: Uuid) -> Self {
455 self.maintenance_expense_account_id = Some(value);
456 self
457 }
458
459 pub fn parts_inventory_account_id(mut self, value: Uuid) -> Self {
461 self.parts_inventory_account_id = Some(value);
462 self
463 }
464
465 pub fn labor_payable_account_id(mut self, value: Uuid) -> Self {
467 self.labor_payable_account_id = Some(value);
468 self
469 }
470
471 pub fn journal_id(mut self, value: Uuid) -> Self {
473 self.journal_id = Some(value);
474 self
475 }
476
477 pub fn accounting_post_id(mut self, value: Uuid) -> Self {
479 self.accounting_post_id = Some(value);
480 self
481 }
482
483 pub fn notes(mut self, value: String) -> Self {
485 self.notes = Some(value);
486 self
487 }
488
489 pub fn build(self) -> Result<MaintenanceVisit, String> {
493 let asset_id = self.asset_id.ok_or_else(|| "asset_id is required".to_string())?;
494 let scheduled_date = self.scheduled_date.ok_or_else(|| "scheduled_date is required".to_string())?;
495
496 Ok(MaintenanceVisit {
497 id: Uuid::new_v4(),
498 asset_id,
499 schedule_id: self.schedule_id,
500 maintenance_type: self.maintenance_type.unwrap_or_default(),
501 status: self.status.unwrap_or_default(),
502 warehouse_id: self.warehouse_id,
503 warranty_claim_id: self.warranty_claim_id,
504 scheduled_date,
505 performed_date: self.performed_date,
506 labor_cost: self.labor_cost.unwrap_or(Decimal::from(0)),
507 parts_cost: self.parts_cost.unwrap_or(Decimal::from(0)),
508 total_cost: self.total_cost.unwrap_or(Decimal::from(0)),
509 maintenance_expense_account_id: self.maintenance_expense_account_id,
510 parts_inventory_account_id: self.parts_inventory_account_id,
511 labor_payable_account_id: self.labor_payable_account_id,
512 journal_id: self.journal_id,
513 accounting_post_id: self.accounting_post_id,
514 notes: self.notes,
515 metadata: AuditMetadata::default(),
516 })
517 }
518}