1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc, NaiveDate};
11use rust_decimal::Decimal;
12
13#[cfg(feature = "openapi")]
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17#[cfg(feature = "validation")]
18use validator::Validate;
19
20use crate::domain::entity::Quant;
21use crate::domain::entity::AuditMetadata;
22
23#[derive(Debug, Clone, Deserialize)]
32#[cfg_attr(feature = "openapi", derive(ToSchema))]
33#[cfg_attr(feature = "validation", derive(Validate))]
34#[serde(rename_all = "camelCase")]
35pub struct CreateQuantDto {
36 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
37 #[serde(alias = "item_id")]
38 pub item_id: Uuid,
39 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
40 #[serde(alias = "location_id")]
41 pub location_id: Uuid,
42 #[serde(default, skip_serializing_if = "Option::is_none", alias = "lot_id")]
43 pub lot_id: Option<Uuid>,
44 #[serde(default, skip_serializing_if = "Option::is_none", alias = "package_id")]
45 pub package_id: Option<Uuid>,
46 #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_id")]
47 pub owner_id: Option<Uuid>,
48 pub quantity: Decimal,
49 #[serde(alias = "reserved_quantity")]
50 pub reserved_quantity: Decimal,
51 #[serde(alias = "available_quantity")]
52 pub available_quantity: Decimal,
53 #[serde(default, skip_serializing_if = "Option::is_none", alias = "in_date")]
54 pub in_date: Option<DateTime<Utc>>,
55 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_quantity")]
56 pub inventory_quantity: Option<Decimal>,
57 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_diff_quantity")]
58 pub inventory_diff_quantity: Option<Decimal>,
59 #[cfg_attr(feature = "openapi", schema(example = true))]
60 #[serde(alias = "inventory_quantity_set")]
61 pub inventory_quantity_set: bool,
62 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_date")]
63 pub inventory_date: Option<NaiveDate>,
64 #[cfg_attr(feature = "openapi", schema(example = true))]
65 #[serde(alias = "sn_duplicated")]
66 pub sn_duplicated: bool,
67}
68
69#[derive(Debug, Clone, Deserialize)]
78#[cfg_attr(feature = "openapi", derive(ToSchema))]
79#[cfg_attr(feature = "validation", derive(Validate))]
80#[serde(rename_all = "camelCase")]
81pub struct UpdateQuantDto {
82 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
83 #[serde(alias = "item_id")]
84 pub item_id: Uuid,
85 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
86 #[serde(alias = "location_id")]
87 pub location_id: Uuid,
88 #[serde(default, skip_serializing_if = "Option::is_none", alias = "lot_id")]
89 pub lot_id: Option<Uuid>,
90 #[serde(default, skip_serializing_if = "Option::is_none", alias = "package_id")]
91 pub package_id: Option<Uuid>,
92 #[serde(default, skip_serializing_if = "Option::is_none", alias = "owner_id")]
93 pub owner_id: Option<Uuid>,
94 pub quantity: Decimal,
95 #[serde(alias = "reserved_quantity")]
96 pub reserved_quantity: Decimal,
97 #[serde(alias = "available_quantity")]
98 pub available_quantity: Decimal,
99 #[serde(default, skip_serializing_if = "Option::is_none", alias = "in_date")]
100 pub in_date: Option<DateTime<Utc>>,
101 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_quantity")]
102 pub inventory_quantity: Option<Decimal>,
103 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_diff_quantity")]
104 pub inventory_diff_quantity: Option<Decimal>,
105 #[cfg_attr(feature = "openapi", schema(example = true))]
106 #[serde(alias = "inventory_quantity_set")]
107 pub inventory_quantity_set: bool,
108 #[serde(default, skip_serializing_if = "Option::is_none", alias = "inventory_date")]
109 pub inventory_date: Option<NaiveDate>,
110 #[cfg_attr(feature = "openapi", schema(example = true))]
111 #[serde(alias = "sn_duplicated")]
112 pub sn_duplicated: bool,
113}
114
115#[derive(Debug, Clone, Default, Deserialize)]
124#[cfg_attr(feature = "openapi", derive(ToSchema))]
125#[cfg_attr(feature = "validation", derive(Validate))]
126#[serde(rename_all = "camelCase")]
127pub struct PatchQuantDto {
128 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
129 #[serde(skip_serializing_if = "Option::is_none", alias = "item_id")]
130 pub item_id: Option<Uuid>,
131 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
132 #[serde(skip_serializing_if = "Option::is_none", alias = "location_id")]
133 pub location_id: Option<Uuid>,
134 #[serde(skip_serializing_if = "Option::is_none", alias = "lot_id")]
135 pub lot_id: Option<Uuid>,
136 #[serde(skip_serializing_if = "Option::is_none", alias = "package_id")]
137 pub package_id: Option<Uuid>,
138 #[serde(skip_serializing_if = "Option::is_none", alias = "owner_id")]
139 pub owner_id: Option<Uuid>,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub quantity: Option<Decimal>,
142 #[serde(skip_serializing_if = "Option::is_none", alias = "reserved_quantity")]
143 pub reserved_quantity: Option<Decimal>,
144 #[serde(skip_serializing_if = "Option::is_none", alias = "available_quantity")]
145 pub available_quantity: Option<Decimal>,
146 #[serde(skip_serializing_if = "Option::is_none", alias = "in_date")]
147 pub in_date: Option<DateTime<Utc>>,
148 #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_quantity")]
149 pub inventory_quantity: Option<Decimal>,
150 #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_diff_quantity")]
151 pub inventory_diff_quantity: Option<Decimal>,
152 #[cfg_attr(feature = "openapi", schema(example = true))]
153 #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_quantity_set")]
154 pub inventory_quantity_set: Option<bool>,
155 #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_date")]
156 pub inventory_date: Option<NaiveDate>,
157 #[cfg_attr(feature = "openapi", schema(example = true))]
158 #[serde(skip_serializing_if = "Option::is_none", alias = "sn_duplicated")]
159 pub sn_duplicated: Option<bool>,
160}
161
162impl PatchQuantDto {
163 pub fn has_changes(&self) -> bool {
165 self.item_id.is_some() || self.location_id.is_some() || self.lot_id.is_some() || self.package_id.is_some() || self.owner_id.is_some() || self.quantity.is_some() || self.reserved_quantity.is_some() || self.available_quantity.is_some() || self.in_date.is_some() || self.inventory_quantity.is_some() || self.inventory_diff_quantity.is_some() || self.inventory_quantity_set.is_some() || self.inventory_date.is_some() || self.sn_duplicated.is_some()
166 }
167}
168
169#[derive(Debug, Clone, Serialize)]
178#[cfg_attr(feature = "openapi", derive(ToSchema))]
179#[serde(rename_all = "camelCase")]
180pub struct QuantResponseDto {
181 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
182 pub id: Uuid,
183 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
184 pub item_id: Uuid,
185 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
186 pub location_id: Uuid,
187 pub lot_id: Option<Uuid>,
188 pub package_id: Option<Uuid>,
189 pub owner_id: Option<Uuid>,
190 pub quantity: Decimal,
191 pub reserved_quantity: Decimal,
192 pub available_quantity: Decimal,
193 pub in_date: Option<DateTime<Utc>>,
194 pub inventory_quantity: Option<Decimal>,
195 pub inventory_diff_quantity: Option<Decimal>,
196 #[cfg_attr(feature = "openapi", schema(example = true))]
197 pub inventory_quantity_set: bool,
198 pub inventory_date: Option<NaiveDate>,
199 #[cfg_attr(feature = "openapi", schema(example = true))]
200 pub sn_duplicated: bool,
201 pub metadata: AuditMetadata,
202}
203
204#[derive(Debug, Clone, Serialize)]
213#[cfg_attr(feature = "openapi", derive(ToSchema))]
214#[serde(rename_all = "camelCase")]
215pub struct QuantListResponseDto {
216 pub items: Vec<QuantResponseDto>,
218 pub total: u64,
220 pub page: u32,
222 pub per_page: u32,
224 pub total_pages: u32,
226 pub has_next: bool,
228 pub has_prev: bool,
230}
231
232impl QuantListResponseDto {
233 pub fn new(items: Vec<QuantResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
235 let total_pages = if per_page > 0 {
236 ((total as f64) / (per_page as f64)).ceil() as u32
237 } else {
238 0
239 };
240 Self {
241 items,
242 total,
243 page,
244 per_page,
245 total_pages,
246 has_next: page < total_pages,
247 has_prev: page > 1,
248 }
249 }
250}
251
252#[derive(Debug, Clone, Serialize)]
254#[cfg_attr(feature = "openapi", derive(ToSchema))]
255#[serde(rename_all = "camelCase")]
256pub struct QuantSummaryDto {
257 pub id: Uuid,
258 pub item_id: Uuid,
259 pub location_id: Uuid,
260 pub lot_id: Option<Uuid>,
261 pub created_at: Option<DateTime<Utc>>,
262}
263
264impl From<Quant> for QuantResponseDto {
269 fn from(entity: Quant) -> Self {
270 Self {
271 id: entity.id,
272 item_id: entity.item_id,
273 location_id: entity.location_id,
274 lot_id: entity.lot_id,
275 package_id: entity.package_id,
276 owner_id: entity.owner_id,
277 quantity: entity.quantity,
278 reserved_quantity: entity.reserved_quantity,
279 available_quantity: entity.available_quantity,
280 in_date: entity.in_date,
281 inventory_quantity: entity.inventory_quantity,
282 inventory_diff_quantity: entity.inventory_diff_quantity,
283 inventory_quantity_set: entity.inventory_quantity_set,
284 inventory_date: entity.inventory_date,
285 sn_duplicated: entity.sn_duplicated,
286 metadata: entity.metadata,
287 }
288 }
289}
290
291impl From<Quant> for QuantSummaryDto {
292 fn from(entity: Quant) -> Self {
293 let created_at = backbone_core::PersistentEntity::created_at(&entity);
294 Self {
295 id: entity.id,
296 item_id: entity.item_id,
297 location_id: entity.location_id,
298 lot_id: entity.lot_id,
299 created_at,
300 }
301 }
302}
303
304impl From<CreateQuantDto> for Quant {
305 fn from(dto: CreateQuantDto) -> Self {
306 Self {
307 id: Uuid::new_v4(),
308 item_id: dto.item_id,
309 location_id: dto.location_id,
310 lot_id: dto.lot_id,
311 package_id: dto.package_id,
312 owner_id: dto.owner_id,
313 quantity: dto.quantity,
314 reserved_quantity: dto.reserved_quantity,
315 available_quantity: dto.available_quantity,
316 in_date: dto.in_date,
317 inventory_quantity: dto.inventory_quantity,
318 inventory_diff_quantity: dto.inventory_diff_quantity,
319 inventory_quantity_set: dto.inventory_quantity_set,
320 inventory_date: dto.inventory_date,
321 sn_duplicated: dto.sn_duplicated,
322 metadata: AuditMetadata::default(),
323 }
324 }
325}
326
327impl From<&Quant> for QuantResponseDto {
328 fn from(entity: &Quant) -> Self {
329 Self {
330 id: entity.id.clone(),
331 item_id: entity.item_id.clone(),
332 location_id: entity.location_id.clone(),
333 lot_id: entity.lot_id.clone(),
334 package_id: entity.package_id.clone(),
335 owner_id: entity.owner_id.clone(),
336 quantity: entity.quantity.clone(),
337 reserved_quantity: entity.reserved_quantity.clone(),
338 available_quantity: entity.available_quantity.clone(),
339 in_date: entity.in_date.clone(),
340 inventory_quantity: entity.inventory_quantity.clone(),
341 inventory_diff_quantity: entity.inventory_diff_quantity.clone(),
342 inventory_quantity_set: entity.inventory_quantity_set.clone(),
343 inventory_date: entity.inventory_date.clone(),
344 sn_duplicated: entity.sn_duplicated.clone(),
345 metadata: entity.metadata.clone(),
346 }
347 }
348}
349
350impl backbone_core::FromCreateDto<CreateQuantDto> for Quant {
351 fn from_create_dto(dto: CreateQuantDto) -> backbone_core::ServiceResult<Self> {
352 Ok(Quant::from(dto))
353 }
354}
355
356impl backbone_core::ApplyUpdateDto<UpdateQuantDto> for Quant {
357 fn apply_update(mut self, dto: UpdateQuantDto) -> backbone_core::ServiceResult<Self> {
358 self.item_id = dto.item_id;
359 self.location_id = dto.location_id;
360 self.lot_id = dto.lot_id;
361 self.package_id = dto.package_id;
362 self.owner_id = dto.owner_id;
363 self.quantity = dto.quantity;
364 self.reserved_quantity = dto.reserved_quantity;
365 self.available_quantity = dto.available_quantity;
366 self.in_date = dto.in_date;
367 self.inventory_quantity = dto.inventory_quantity;
368 self.inventory_diff_quantity = dto.inventory_diff_quantity;
369 self.inventory_quantity_set = dto.inventory_quantity_set;
370 self.inventory_date = dto.inventory_date;
371 self.sn_duplicated = dto.sn_duplicated;
372 Ok(self)
373 }
374}
375
376