backbone_inventory/domain/entity/
stock_move_line.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::MoveState;
8use super::AuditMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct StockMoveLineId(pub Uuid);
14
15impl StockMoveLineId {
16 pub fn new(id: Uuid) -> Self { Self(id) }
17 pub fn generate() -> Self { Self(Uuid::new_v4()) }
18 pub fn into_inner(self) -> Uuid { self.0 }
19}
20
21impl std::fmt::Display for StockMoveLineId {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}", self.0)
24 }
25}
26
27impl std::str::FromStr for StockMoveLineId {
28 type Err = uuid::Error;
29 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 Ok(Self(Uuid::parse_str(s)?))
31 }
32}
33
34impl From<Uuid> for StockMoveLineId {
35 fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<StockMoveLineId> for Uuid {
39 fn from(id: StockMoveLineId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for StockMoveLineId {
43 fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for StockMoveLineId {
47 type Target = Uuid;
48 fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct StockMoveLine {
53 pub id: Uuid,
54 pub quantity: Decimal,
55 pub quantity_product_uom_id: Option<Uuid>,
56 pub picked: bool,
57 pub lot_id: Option<Uuid>,
58 pub package_id: Option<Uuid>,
59 pub result_package_id: Option<Uuid>,
60 pub owner_id: Option<Uuid>,
61 pub state: MoveState,
62 pub date: DateTime<Utc>,
63 pub move_id: Uuid,
64 pub picking_id: Option<Uuid>,
65 pub location_id: Uuid,
66 pub location_dest_id: Uuid,
67 pub item_id: Uuid,
68 #[serde(default)]
69 #[sqlx(json)]
70 pub metadata: AuditMetadata,
71}
72
73impl StockMoveLine {
74 pub fn builder() -> StockMoveLineBuilder {
76 <StockMoveLineBuilder as Default>::default()
77 }
78
79 pub fn new(quantity: Decimal, picked: bool, state: MoveState, date: DateTime<Utc>, move_id: Uuid, location_id: Uuid, location_dest_id: Uuid, item_id: Uuid) -> Self {
81 Self {
82 id: Uuid::new_v4(),
83 quantity,
84 quantity_product_uom_id: None,
85 picked,
86 lot_id: None,
87 package_id: None,
88 result_package_id: None,
89 owner_id: None,
90 state,
91 date,
92 move_id,
93 picking_id: None,
94 location_id,
95 location_dest_id,
96 item_id,
97 metadata: AuditMetadata::default(),
98 }
99 }
100
101 pub fn id(&self) -> &Uuid {
103 &self.id
104 }
105
106 pub fn typed_id(&self) -> StockMoveLineId {
108 StockMoveLineId(self.id)
109 }
110
111 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
113 self.metadata.created_at.as_ref()
114 }
115
116 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
118 self.metadata.updated_at.as_ref()
119 }
120
121 pub fn is_deleted(&self) -> bool {
123 self.metadata.deleted_at.is_some()
124 }
125
126 pub fn is_active(&self) -> bool {
128 self.metadata.deleted_at.is_none()
129 }
130
131 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
133 self.metadata.deleted_at.as_ref()
134 }
135
136 pub fn created_by(&self) -> Option<&Uuid> {
138 self.metadata.created_by.as_ref()
139 }
140
141 pub fn updated_by(&self) -> Option<&Uuid> {
143 self.metadata.updated_by.as_ref()
144 }
145
146 pub fn deleted_by(&self) -> Option<&Uuid> {
148 self.metadata.deleted_by.as_ref()
149 }
150
151
152 pub fn with_quantity_product_uom_id(mut self, value: Uuid) -> Self {
158 self.quantity_product_uom_id = Some(value);
159 self
160 }
161
162 pub fn with_lot_id(mut self, value: Uuid) -> Self {
164 self.lot_id = Some(value);
165 self
166 }
167
168 pub fn with_package_id(mut self, value: Uuid) -> Self {
170 self.package_id = Some(value);
171 self
172 }
173
174 pub fn with_result_package_id(mut self, value: Uuid) -> Self {
176 self.result_package_id = Some(value);
177 self
178 }
179
180 pub fn with_owner_id(mut self, value: Uuid) -> Self {
182 self.owner_id = Some(value);
183 self
184 }
185
186 pub fn with_picking_id(mut self, value: Uuid) -> Self {
188 self.picking_id = Some(value);
189 self
190 }
191
192 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
198 for (key, value) in fields {
199 match key.as_str() {
200 "quantity" => {
201 if let Ok(v) = serde_json::from_value(value) { self.quantity = v; }
202 }
203 "quantity_product_uom_id" => {
204 if let Ok(v) = serde_json::from_value(value) { self.quantity_product_uom_id = v; }
205 }
206 "picked" => {
207 if let Ok(v) = serde_json::from_value(value) { self.picked = v; }
208 }
209 "lot_id" => {
210 if let Ok(v) = serde_json::from_value(value) { self.lot_id = v; }
211 }
212 "package_id" => {
213 if let Ok(v) = serde_json::from_value(value) { self.package_id = v; }
214 }
215 "result_package_id" => {
216 if let Ok(v) = serde_json::from_value(value) { self.result_package_id = v; }
217 }
218 "owner_id" => {
219 if let Ok(v) = serde_json::from_value(value) { self.owner_id = v; }
220 }
221 "state" => {
222 if let Ok(v) = serde_json::from_value(value) { self.state = v; }
223 }
224 "date" => {
225 if let Ok(v) = serde_json::from_value(value) { self.date = v; }
226 }
227 "move_id" => {
228 if let Ok(v) = serde_json::from_value(value) { self.move_id = v; }
229 }
230 "picking_id" => {
231 if let Ok(v) = serde_json::from_value(value) { self.picking_id = v; }
232 }
233 "location_id" => {
234 if let Ok(v) = serde_json::from_value(value) { self.location_id = v; }
235 }
236 "location_dest_id" => {
237 if let Ok(v) = serde_json::from_value(value) { self.location_dest_id = v; }
238 }
239 "item_id" => {
240 if let Ok(v) = serde_json::from_value(value) { self.item_id = v; }
241 }
242 _ => {} }
244 }
245 }
246
247}
248
249impl super::Entity for StockMoveLine {
250 type Id = Uuid;
251
252 fn entity_id(&self) -> &Self::Id {
253 &self.id
254 }
255
256 fn entity_type() -> &'static str {
257 "StockMoveLine"
258 }
259}
260
261impl backbone_core::PersistentEntity for StockMoveLine {
262 fn write_protected_fields() -> &'static [&'static str] {
263 &["state"]
264 }
265 fn entity_id(&self) -> String {
266 self.id.to_string()
267 }
268 fn set_entity_id(&mut self, id: String) {
269 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
270 self.id = uuid;
271 }
272 }
273 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
274 self.metadata.created_at
275 }
276 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
277 self.metadata.created_at = Some(ts);
278 }
279 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
280 self.metadata.updated_at
281 }
282 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
283 self.metadata.updated_at = Some(ts);
284 }
285 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
286 self.metadata.deleted_at
287 }
288 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
289 self.metadata.deleted_at = ts;
290 }
291}
292
293impl backbone_orm::EntityRepoMeta for StockMoveLine {
294 fn column_types() -> std::collections::HashMap<String, String> {
295 let mut m = std::collections::HashMap::new();
296 m.insert("id".to_string(), "uuid".to_string());
297 m.insert("quantity_product_uom_id".to_string(), "uuid".to_string());
298 m.insert("lot_id".to_string(), "uuid".to_string());
299 m.insert("package_id".to_string(), "uuid".to_string());
300 m.insert("result_package_id".to_string(), "uuid".to_string());
301 m.insert("owner_id".to_string(), "uuid".to_string());
302 m.insert("move_id".to_string(), "uuid".to_string());
303 m.insert("picking_id".to_string(), "uuid".to_string());
304 m.insert("location_id".to_string(), "uuid".to_string());
305 m.insert("location_dest_id".to_string(), "uuid".to_string());
306 m.insert("item_id".to_string(), "uuid".to_string());
307 m.insert("state".to_string(), "move_state".to_string());
308 m.insert("date".to_string(), "timestamptz".to_string());
309 m
310 }
311 fn search_fields() -> &'static [&'static str] {
312 &[]
313 }
314 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
315 &[("move", "stock_moves", "moveId"), ("picking", "transfers", "pickingId"), ("lot", "lots", "lotId"), ("package", "packages", "packageId"), ("resultPackage", "packages", "resultPackageId"), ("location", "locations", "locationId"), ("locationDest", "locations", "locationDestId")]
316 }
317}
318
319#[derive(Debug, Clone, Default)]
324pub struct StockMoveLineBuilder {
325 quantity: Option<Decimal>,
326 quantity_product_uom_id: Option<Uuid>,
327 picked: Option<bool>,
328 lot_id: Option<Uuid>,
329 package_id: Option<Uuid>,
330 result_package_id: Option<Uuid>,
331 owner_id: Option<Uuid>,
332 state: Option<MoveState>,
333 date: Option<DateTime<Utc>>,
334 move_id: Option<Uuid>,
335 picking_id: Option<Uuid>,
336 location_id: Option<Uuid>,
337 location_dest_id: Option<Uuid>,
338 item_id: Option<Uuid>,
339}
340
341impl StockMoveLineBuilder {
342 pub fn quantity(mut self, value: Decimal) -> Self {
344 self.quantity = Some(value);
345 self
346 }
347
348 pub fn quantity_product_uom_id(mut self, value: Uuid) -> Self {
350 self.quantity_product_uom_id = Some(value);
351 self
352 }
353
354 pub fn picked(mut self, value: bool) -> Self {
356 self.picked = Some(value);
357 self
358 }
359
360 pub fn lot_id(mut self, value: Uuid) -> Self {
362 self.lot_id = Some(value);
363 self
364 }
365
366 pub fn package_id(mut self, value: Uuid) -> Self {
368 self.package_id = Some(value);
369 self
370 }
371
372 pub fn result_package_id(mut self, value: Uuid) -> Self {
374 self.result_package_id = Some(value);
375 self
376 }
377
378 pub fn owner_id(mut self, value: Uuid) -> Self {
380 self.owner_id = Some(value);
381 self
382 }
383
384 pub fn state(mut self, value: MoveState) -> Self {
386 self.state = Some(value);
387 self
388 }
389
390 pub fn date(mut self, value: DateTime<Utc>) -> Self {
392 self.date = Some(value);
393 self
394 }
395
396 pub fn move_id(mut self, value: Uuid) -> Self {
398 self.move_id = Some(value);
399 self
400 }
401
402 pub fn picking_id(mut self, value: Uuid) -> Self {
404 self.picking_id = Some(value);
405 self
406 }
407
408 pub fn location_id(mut self, value: Uuid) -> Self {
410 self.location_id = Some(value);
411 self
412 }
413
414 pub fn location_dest_id(mut self, value: Uuid) -> Self {
416 self.location_dest_id = Some(value);
417 self
418 }
419
420 pub fn item_id(mut self, value: Uuid) -> Self {
422 self.item_id = Some(value);
423 self
424 }
425
426 pub fn build(self) -> Result<StockMoveLine, String> {
430 let move_id = self.move_id.ok_or_else(|| "move_id is required".to_string())?;
431 let location_id = self.location_id.ok_or_else(|| "location_id is required".to_string())?;
432 let location_dest_id = self.location_dest_id.ok_or_else(|| "location_dest_id is required".to_string())?;
433 let item_id = self.item_id.ok_or_else(|| "item_id is required".to_string())?;
434
435 Ok(StockMoveLine {
436 id: Uuid::new_v4(),
437 quantity: self.quantity.unwrap_or(Decimal::from(0)),
438 quantity_product_uom_id: self.quantity_product_uom_id,
439 picked: self.picked.unwrap_or(false),
440 lot_id: self.lot_id,
441 package_id: self.package_id,
442 result_package_id: self.result_package_id,
443 owner_id: self.owner_id,
444 state: self.state.unwrap_or_default(),
445 date: self.date.unwrap_or(Utc::now()),
446 move_id,
447 picking_id: self.picking_id,
448 location_id,
449 location_dest_id,
450 item_id,
451 metadata: AuditMetadata::default(),
452 })
453 }
454}