1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::MoveState;
8use super::GlPostingState;
9use super::Priority;
10use super::ProcureMethod;
11use super::AuditMetadata;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct StockMoveId(pub Uuid);
17
18impl StockMoveId {
19 pub fn new(id: Uuid) -> Self { Self(id) }
20 pub fn generate() -> Self { Self(Uuid::new_v4()) }
21 pub fn into_inner(self) -> Uuid { self.0 }
22}
23
24impl std::fmt::Display for StockMoveId {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{}", self.0)
27 }
28}
29
30impl std::str::FromStr for StockMoveId {
31 type Err = uuid::Error;
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 Ok(Self(Uuid::parse_str(s)?))
34 }
35}
36
37impl From<Uuid> for StockMoveId {
38 fn from(id: Uuid) -> Self { Self(id) }
39}
40
41impl From<StockMoveId> for Uuid {
42 fn from(id: StockMoveId) -> Self { id.0 }
43}
44
45impl AsRef<Uuid> for StockMoveId {
46 fn as_ref(&self) -> &Uuid { &self.0 }
47}
48
49impl std::ops::Deref for StockMoveId {
50 type Target = Uuid;
51 fn deref(&self) -> &Self::Target { &self.0 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
55pub struct StockMove {
56 pub id: Uuid,
57 pub name: String,
58 pub state: MoveState,
59 pub posting_state: GlPostingState,
60 pub priority: Priority,
61 pub create_date: DateTime<Utc>,
62 pub date: DateTime<Utc>,
63 pub item_id: Uuid,
64 pub demand_qty: Decimal,
65 pub quantity: Decimal,
66 pub price_unit: Decimal,
67 pub forced_value: Option<Decimal>,
68 pub procure_method: ProcureMethod,
69 pub picking_id: Option<Uuid>,
70 pub origin: Option<String>,
71 pub location_id: Uuid,
72 pub location_dest_id: Uuid,
73 pub partner_id: Option<Uuid>,
74 pub rule_id: Option<Uuid>,
75 pub warehouse_id: Option<Uuid>,
76 pub orderpoint_id: Option<Uuid>,
77 pub move_orig_ids: Vec<Uuid>,
78 pub move_dest_ids: Vec<Uuid>,
79 pub is_inventory: bool,
80 pub scrapped: bool,
81 pub propagate_cancel: bool,
82 #[serde(default)]
83 #[sqlx(json)]
84 pub metadata: AuditMetadata,
85}
86
87impl StockMove {
88 pub fn builder() -> StockMoveBuilder {
90 <StockMoveBuilder as Default>::default()
91 }
92
93 pub fn new(name: String, state: MoveState, posting_state: GlPostingState, priority: Priority, create_date: DateTime<Utc>, date: DateTime<Utc>, item_id: Uuid, demand_qty: Decimal, quantity: Decimal, price_unit: Decimal, procure_method: ProcureMethod, location_id: Uuid, location_dest_id: Uuid, move_orig_ids: Vec<Uuid>, move_dest_ids: Vec<Uuid>, is_inventory: bool, scrapped: bool, propagate_cancel: bool) -> Self {
95 Self {
96 id: Uuid::new_v4(),
97 name,
98 state,
99 posting_state,
100 priority,
101 create_date,
102 date,
103 item_id,
104 demand_qty,
105 quantity,
106 price_unit,
107 forced_value: None,
108 procure_method,
109 picking_id: None,
110 origin: None,
111 location_id,
112 location_dest_id,
113 partner_id: None,
114 rule_id: None,
115 warehouse_id: None,
116 orderpoint_id: None,
117 move_orig_ids,
118 move_dest_ids,
119 is_inventory,
120 scrapped,
121 propagate_cancel,
122 metadata: AuditMetadata::default(),
123 }
124 }
125
126 pub fn id(&self) -> &Uuid {
128 &self.id
129 }
130
131 pub fn typed_id(&self) -> StockMoveId {
133 StockMoveId(self.id)
134 }
135
136 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
138 self.metadata.created_at.as_ref()
139 }
140
141 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
143 self.metadata.updated_at.as_ref()
144 }
145
146 pub fn is_deleted(&self) -> bool {
148 self.metadata.deleted_at.is_some()
149 }
150
151 pub fn is_active(&self) -> bool {
153 self.metadata.deleted_at.is_none()
154 }
155
156 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
158 self.metadata.deleted_at.as_ref()
159 }
160
161 pub fn created_by(&self) -> Option<&Uuid> {
163 self.metadata.created_by.as_ref()
164 }
165
166 pub fn updated_by(&self) -> Option<&Uuid> {
168 self.metadata.updated_by.as_ref()
169 }
170
171 pub fn deleted_by(&self) -> Option<&Uuid> {
173 self.metadata.deleted_by.as_ref()
174 }
175
176
177 pub fn with_forced_value(mut self, value: Decimal) -> Self {
183 self.forced_value = Some(value);
184 self
185 }
186
187 pub fn with_picking_id(mut self, value: Uuid) -> Self {
189 self.picking_id = Some(value);
190 self
191 }
192
193 pub fn with_origin(mut self, value: String) -> Self {
195 self.origin = Some(value);
196 self
197 }
198
199 pub fn with_partner_id(mut self, value: Uuid) -> Self {
201 self.partner_id = Some(value);
202 self
203 }
204
205 pub fn with_rule_id(mut self, value: Uuid) -> Self {
207 self.rule_id = Some(value);
208 self
209 }
210
211 pub fn with_warehouse_id(mut self, value: Uuid) -> Self {
213 self.warehouse_id = Some(value);
214 self
215 }
216
217 pub fn with_orderpoint_id(mut self, value: Uuid) -> Self {
219 self.orderpoint_id = Some(value);
220 self
221 }
222
223 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
229 for (key, value) in fields {
230 match key.as_str() {
231 "name" => {
232 if let Ok(v) = serde_json::from_value(value) { self.name = v; }
233 }
234 "state" => {
235 if let Ok(v) = serde_json::from_value(value) { self.state = v; }
236 }
237 "posting_state" => {
238 if let Ok(v) = serde_json::from_value(value) { self.posting_state = v; }
239 }
240 "priority" => {
241 if let Ok(v) = serde_json::from_value(value) { self.priority = v; }
242 }
243 "create_date" => {
244 if let Ok(v) = serde_json::from_value(value) { self.create_date = v; }
245 }
246 "date" => {
247 if let Ok(v) = serde_json::from_value(value) { self.date = v; }
248 }
249 "item_id" => {
250 if let Ok(v) = serde_json::from_value(value) { self.item_id = v; }
251 }
252 "demand_qty" => {
253 if let Ok(v) = serde_json::from_value(value) { self.demand_qty = v; }
254 }
255 "quantity" => {
256 if let Ok(v) = serde_json::from_value(value) { self.quantity = v; }
257 }
258 "price_unit" => {
259 if let Ok(v) = serde_json::from_value(value) { self.price_unit = v; }
260 }
261 "forced_value" => {
262 if let Ok(v) = serde_json::from_value(value) { self.forced_value = v; }
263 }
264 "procure_method" => {
265 if let Ok(v) = serde_json::from_value(value) { self.procure_method = v; }
266 }
267 "picking_id" => {
268 if let Ok(v) = serde_json::from_value(value) { self.picking_id = v; }
269 }
270 "origin" => {
271 if let Ok(v) = serde_json::from_value(value) { self.origin = v; }
272 }
273 "location_id" => {
274 if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
275 }
276 "location_dest_id" => {
277 if let Ok(v) = serde_json::from_value(value) { self.location_dest_id = v; }
278 }
279 "partner_id" => {
280 if let Ok(v) = serde_json::from_value(value) { self.partner_id = v; }
281 }
282 "rule_id" => {
283 if let Ok(v) = serde_json::from_value(value) { self.rule_id = v; }
284 }
285 "warehouse_id" => {
286 if let Ok(v) = serde_json::from_value(value) { self.warehouse_id = v; }
287 }
288 "orderpoint_id" => {
289 if let Ok(v) = serde_json::from_value(value) { self.orderpoint_id = v; }
290 }
291 "move_orig_ids" => {
292 if let Ok(v) = serde_json::from_value(value) { self.move_orig_ids = v; }
293 }
294 "move_dest_ids" => {
295 if let Ok(v) = serde_json::from_value(value) { self.move_dest_ids = v; }
296 }
297 "is_inventory" => {
298 if let Ok(v) = serde_json::from_value(value) { self.is_inventory = v; }
299 }
300 "scrapped" => {
301 if let Ok(v) = serde_json::from_value(value) { self.scrapped = v; }
302 }
303 "propagate_cancel" => {
304 if let Ok(v) = serde_json::from_value(value) { self.propagate_cancel = v; }
305 }
306 _ => {} }
308 }
309 }
310
311}
312
313impl super::Entity for StockMove {
314 type Id = Uuid;
315
316 fn entity_id(&self) -> &Self::Id {
317 &self.id
318 }
319
320 fn entity_type() -> &'static str {
321 "StockMove"
322 }
323}
324
325impl backbone_core::PersistentEntity for StockMove {
326 fn write_protected_fields() -> &'static [&'static str] {
327 &["state"]
328 }
329 fn entity_id(&self) -> String {
330 self.id.to_string()
331 }
332 fn set_entity_id(&mut self, id: String) {
333 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
334 self.id = uuid;
335 }
336 }
337 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
338 self.metadata.created_at
339 }
340 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
341 self.metadata.created_at = Some(ts);
342 }
343 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
344 self.metadata.updated_at
345 }
346 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
347 self.metadata.updated_at = Some(ts);
348 }
349 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
350 self.metadata.deleted_at
351 }
352 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
353 self.metadata.deleted_at = ts;
354 }
355}
356
357impl backbone_orm::EntityRepoMeta for StockMove {
358 fn column_types() -> std::collections::HashMap<String, String> {
359 let mut m = std::collections::HashMap::new();
360 m.insert("id".to_string(), "uuid".to_string());
361 m.insert("item_id".to_string(), "uuid".to_string());
362 m.insert("picking_id".to_string(), "uuid".to_string());
363 m.insert("location_id".to_string(), "uuid".to_string());
364 m.insert("location_dest_id".to_string(), "uuid".to_string());
365 m.insert("partner_id".to_string(), "uuid".to_string());
366 m.insert("rule_id".to_string(), "uuid".to_string());
367 m.insert("warehouse_id".to_string(), "uuid".to_string());
368 m.insert("orderpoint_id".to_string(), "uuid".to_string());
369 m.insert("state".to_string(), "move_state".to_string());
370 m.insert("posting_state".to_string(), "gl_posting_state".to_string());
371 m.insert("priority".to_string(), "priority".to_string());
372 m.insert("procure_method".to_string(), "procure_method".to_string());
373 m.insert("create_date".to_string(), "timestamptz".to_string());
374 m.insert("date".to_string(), "timestamptz".to_string());
375 m
376 }
377 fn search_fields() -> &'static [&'static str] {
378 &["name"]
379 }
380 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
381 &[("picking", "transfers", "pickingId"), ("location", "locations", "locationId"), ("locationDest", "locations", "locationDestId"), ("rule", "route_rules", "ruleId"), ("warehouse", "warehouses", "warehouseId"), ("orderpoint", "reordering_rules", "orderpointId")]
382 }
383}
384
385#[derive(Debug, Clone, Default)]
390pub struct StockMoveBuilder {
391 name: Option<String>,
392 state: Option<MoveState>,
393 posting_state: Option<GlPostingState>,
394 priority: Option<Priority>,
395 create_date: Option<DateTime<Utc>>,
396 date: Option<DateTime<Utc>>,
397 item_id: Option<Uuid>,
398 demand_qty: Option<Decimal>,
399 quantity: Option<Decimal>,
400 price_unit: Option<Decimal>,
401 forced_value: Option<Decimal>,
402 procure_method: Option<ProcureMethod>,
403 picking_id: Option<Uuid>,
404 origin: Option<String>,
405 location_id: Option<Uuid>,
406 location_dest_id: Option<Uuid>,
407 partner_id: Option<Uuid>,
408 rule_id: Option<Uuid>,
409 warehouse_id: Option<Uuid>,
410 orderpoint_id: Option<Uuid>,
411 move_orig_ids: Option<Vec<Uuid>>,
412 move_dest_ids: Option<Vec<Uuid>>,
413 is_inventory: Option<bool>,
414 scrapped: Option<bool>,
415 propagate_cancel: Option<bool>,
416}
417
418impl StockMoveBuilder {
419 pub fn name(mut self, value: String) -> Self {
421 self.name = Some(value);
422 self
423 }
424
425 pub fn state(mut self, value: MoveState) -> Self {
427 self.state = Some(value);
428 self
429 }
430
431 pub fn posting_state(mut self, value: GlPostingState) -> Self {
433 self.posting_state = Some(value);
434 self
435 }
436
437 pub fn priority(mut self, value: Priority) -> Self {
439 self.priority = Some(value);
440 self
441 }
442
443 pub fn create_date(mut self, value: DateTime<Utc>) -> Self {
445 self.create_date = Some(value);
446 self
447 }
448
449 pub fn date(mut self, value: DateTime<Utc>) -> Self {
451 self.date = Some(value);
452 self
453 }
454
455 pub fn item_id(mut self, value: Uuid) -> Self {
457 self.item_id = Some(value);
458 self
459 }
460
461 pub fn demand_qty(mut self, value: Decimal) -> Self {
463 self.demand_qty = Some(value);
464 self
465 }
466
467 pub fn quantity(mut self, value: Decimal) -> Self {
469 self.quantity = Some(value);
470 self
471 }
472
473 pub fn price_unit(mut self, value: Decimal) -> Self {
475 self.price_unit = Some(value);
476 self
477 }
478
479 pub fn forced_value(mut self, value: Decimal) -> Self {
481 self.forced_value = Some(value);
482 self
483 }
484
485 pub fn procure_method(mut self, value: ProcureMethod) -> Self {
487 self.procure_method = Some(value);
488 self
489 }
490
491 pub fn picking_id(mut self, value: Uuid) -> Self {
493 self.picking_id = Some(value);
494 self
495 }
496
497 pub fn origin(mut self, value: String) -> Self {
499 self.origin = Some(value);
500 self
501 }
502
503 pub fn location_id(mut self, value: Uuid) -> Self {
505 self.location_id = Some(value);
506 self
507 }
508
509 pub fn location_dest_id(mut self, value: Uuid) -> Self {
511 self.location_dest_id = Some(value);
512 self
513 }
514
515 pub fn partner_id(mut self, value: Uuid) -> Self {
517 self.partner_id = Some(value);
518 self
519 }
520
521 pub fn rule_id(mut self, value: Uuid) -> Self {
523 self.rule_id = Some(value);
524 self
525 }
526
527 pub fn warehouse_id(mut self, value: Uuid) -> Self {
529 self.warehouse_id = Some(value);
530 self
531 }
532
533 pub fn orderpoint_id(mut self, value: Uuid) -> Self {
535 self.orderpoint_id = Some(value);
536 self
537 }
538
539 pub fn move_orig_ids(mut self, value: Vec<Uuid>) -> Self {
541 self.move_orig_ids = Some(value);
542 self
543 }
544
545 pub fn move_dest_ids(mut self, value: Vec<Uuid>) -> Self {
547 self.move_dest_ids = Some(value);
548 self
549 }
550
551 pub fn is_inventory(mut self, value: bool) -> Self {
553 self.is_inventory = Some(value);
554 self
555 }
556
557 pub fn scrapped(mut self, value: bool) -> Self {
559 self.scrapped = Some(value);
560 self
561 }
562
563 pub fn propagate_cancel(mut self, value: bool) -> Self {
565 self.propagate_cancel = Some(value);
566 self
567 }
568
569 pub fn build(self) -> Result<StockMove, String> {
573 let name = self.name.ok_or_else(|| "name is required".to_string())?;
574 let item_id = self.item_id.ok_or_else(|| "item_id is required".to_string())?;
575 let location_id = self.location_id.ok_or_else(|| "location_id is required".to_string())?;
576 let location_dest_id = self.location_dest_id.ok_or_else(|| "location_dest_id is required".to_string())?;
577 let move_orig_ids = self.move_orig_ids.ok_or_else(|| "move_orig_ids is required".to_string())?;
578 let move_dest_ids = self.move_dest_ids.ok_or_else(|| "move_dest_ids is required".to_string())?;
579
580 Ok(StockMove {
581 id: Uuid::new_v4(),
582 name,
583 state: self.state.unwrap_or_default(),
584 posting_state: self.posting_state.unwrap_or_default(),
585 priority: self.priority.unwrap_or_default(),
586 create_date: self.create_date.unwrap_or(Utc::now()),
587 date: self.date.unwrap_or(Utc::now()),
588 item_id,
589 demand_qty: self.demand_qty.unwrap_or(Decimal::from(0)),
590 quantity: self.quantity.unwrap_or(Decimal::from(0)),
591 price_unit: self.price_unit.unwrap_or(Decimal::from(0)),
592 forced_value: self.forced_value,
593 procure_method: self.procure_method.unwrap_or_default(),
594 picking_id: self.picking_id,
595 origin: self.origin,
596 location_id,
597 location_dest_id,
598 partner_id: self.partner_id,
599 rule_id: self.rule_id,
600 warehouse_id: self.warehouse_id,
601 orderpoint_id: self.orderpoint_id,
602 move_orig_ids,
603 move_dest_ids,
604 is_inventory: self.is_inventory.unwrap_or(false),
605 scrapped: self.scrapped.unwrap_or(false),
606 propagate_cancel: self.propagate_cancel.unwrap_or(true),
607 metadata: AuditMetadata::default(),
608 })
609 }
610}