1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc, NaiveDate};
11
12#[cfg(feature = "openapi")]
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16use crate::domain::entity::Location;
17use crate::domain::entity::AuditMetadata;
18use crate::domain::entity::LocationUsage;
19
20#[derive(Debug, Clone, Deserialize)]
29#[cfg_attr(feature = "openapi", derive(ToSchema))]
30#[serde(rename_all = "camelCase")]
31pub struct CreateLocationDto {
32 #[cfg_attr(feature = "openapi", schema(example = "example"))]
33 pub name: String,
34 #[cfg_attr(feature = "openapi", schema(example = "example"))]
35 #[serde(alias = "complete_name")]
36 pub complete_name: String,
37 #[cfg_attr(feature = "openapi", schema(example = true))]
38 pub active: bool,
39 pub usage: LocationUsage,
40 #[serde(default, skip_serializing_if = "Option::is_none", alias = "location_id")]
41 pub location_id: Option<Uuid>,
42 #[cfg_attr(feature = "openapi", schema(example = "example"))]
43 #[serde(alias = "parent_path")]
44 pub parent_path: String,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub barcode: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none", alias = "warehouse_id")]
48 pub warehouse_id: Option<Uuid>,
49 #[cfg_attr(feature = "openapi", schema(example = 42))]
50 #[serde(alias = "cyclic_inventory_frequency")]
51 pub cyclic_inventory_frequency: i32,
52 #[serde(default, skip_serializing_if = "Option::is_none", alias = "last_inventory_date")]
53 pub last_inventory_date: Option<NaiveDate>,
54 #[serde(default, skip_serializing_if = "Option::is_none", alias = "next_inventory_date")]
55 pub next_inventory_date: Option<NaiveDate>,
56 #[serde(default, skip_serializing_if = "Option::is_none", alias = "valuation_account_id")]
57 pub valuation_account_id: Option<Uuid>,
58 #[serde(default, skip_serializing_if = "Option::is_none", alias = "storage_category_id")]
59 pub storage_category_id: Option<Uuid>,
60}
61
62#[derive(Debug, Clone, Deserialize)]
71#[cfg_attr(feature = "openapi", derive(ToSchema))]
72#[serde(rename_all = "camelCase")]
73pub struct UpdateLocationDto {
74 #[cfg_attr(feature = "openapi", schema(example = "example"))]
75 pub name: String,
76 #[cfg_attr(feature = "openapi", schema(example = "example"))]
77 #[serde(alias = "complete_name")]
78 pub complete_name: String,
79 #[cfg_attr(feature = "openapi", schema(example = true))]
80 pub active: bool,
81 pub usage: LocationUsage,
82 #[serde(default, skip_serializing_if = "Option::is_none", alias = "location_id")]
83 pub location_id: Option<Uuid>,
84 #[cfg_attr(feature = "openapi", schema(example = "example"))]
85 #[serde(alias = "parent_path")]
86 pub parent_path: String,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub barcode: Option<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none", alias = "warehouse_id")]
90 pub warehouse_id: Option<Uuid>,
91 #[cfg_attr(feature = "openapi", schema(example = 42))]
92 #[serde(alias = "cyclic_inventory_frequency")]
93 pub cyclic_inventory_frequency: i32,
94 #[serde(default, skip_serializing_if = "Option::is_none", alias = "last_inventory_date")]
95 pub last_inventory_date: Option<NaiveDate>,
96 #[serde(default, skip_serializing_if = "Option::is_none", alias = "next_inventory_date")]
97 pub next_inventory_date: Option<NaiveDate>,
98 #[serde(default, skip_serializing_if = "Option::is_none", alias = "valuation_account_id")]
99 pub valuation_account_id: Option<Uuid>,
100 #[serde(default, skip_serializing_if = "Option::is_none", alias = "storage_category_id")]
101 pub storage_category_id: Option<Uuid>,
102}
103
104#[derive(Debug, Clone, Default, Deserialize)]
113#[cfg_attr(feature = "openapi", derive(ToSchema))]
114#[serde(rename_all = "camelCase")]
115pub struct PatchLocationDto {
116 #[cfg_attr(feature = "openapi", schema(example = "example"))]
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub name: Option<String>,
119 #[cfg_attr(feature = "openapi", schema(example = "example"))]
120 #[serde(skip_serializing_if = "Option::is_none", alias = "complete_name")]
121 pub complete_name: Option<String>,
122 #[cfg_attr(feature = "openapi", schema(example = true))]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub active: Option<bool>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub usage: Option<LocationUsage>,
127 #[serde(skip_serializing_if = "Option::is_none", alias = "location_id")]
128 pub location_id: Option<Uuid>,
129 #[cfg_attr(feature = "openapi", schema(example = "example"))]
130 #[serde(skip_serializing_if = "Option::is_none", alias = "parent_path")]
131 pub parent_path: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub barcode: Option<String>,
134 #[serde(skip_serializing_if = "Option::is_none", alias = "warehouse_id")]
135 pub warehouse_id: Option<Uuid>,
136 #[cfg_attr(feature = "openapi", schema(example = 42))]
137 #[serde(skip_serializing_if = "Option::is_none", alias = "cyclic_inventory_frequency")]
138 pub cyclic_inventory_frequency: Option<i32>,
139 #[serde(skip_serializing_if = "Option::is_none", alias = "last_inventory_date")]
140 pub last_inventory_date: Option<NaiveDate>,
141 #[serde(skip_serializing_if = "Option::is_none", alias = "next_inventory_date")]
142 pub next_inventory_date: Option<NaiveDate>,
143 #[serde(skip_serializing_if = "Option::is_none", alias = "valuation_account_id")]
144 pub valuation_account_id: Option<Uuid>,
145 #[serde(skip_serializing_if = "Option::is_none", alias = "storage_category_id")]
146 pub storage_category_id: Option<Uuid>,
147}
148
149impl PatchLocationDto {
150 pub fn has_changes(&self) -> bool {
152 self.name.is_some() || self.complete_name.is_some() || self.active.is_some() || self.usage.is_some() || self.location_id.is_some() || self.parent_path.is_some() || self.barcode.is_some() || self.warehouse_id.is_some() || self.cyclic_inventory_frequency.is_some() || self.last_inventory_date.is_some() || self.next_inventory_date.is_some() || self.valuation_account_id.is_some() || self.storage_category_id.is_some()
153 }
154}
155
156#[derive(Debug, Clone, Serialize)]
165#[cfg_attr(feature = "openapi", derive(ToSchema))]
166#[serde(rename_all = "camelCase")]
167pub struct LocationResponseDto {
168 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
169 pub id: Uuid,
170 #[cfg_attr(feature = "openapi", schema(example = "example"))]
171 pub name: String,
172 #[cfg_attr(feature = "openapi", schema(example = "example"))]
173 pub complete_name: String,
174 #[cfg_attr(feature = "openapi", schema(example = true))]
175 pub active: bool,
176 pub usage: LocationUsage,
177 pub location_id: Option<Uuid>,
178 #[cfg_attr(feature = "openapi", schema(example = "example"))]
179 pub parent_path: String,
180 pub barcode: Option<String>,
181 pub warehouse_id: Option<Uuid>,
182 #[cfg_attr(feature = "openapi", schema(example = 42))]
183 pub cyclic_inventory_frequency: i32,
184 pub last_inventory_date: Option<NaiveDate>,
185 pub next_inventory_date: Option<NaiveDate>,
186 pub valuation_account_id: Option<Uuid>,
187 pub storage_category_id: Option<Uuid>,
188 pub metadata: AuditMetadata,
189}
190
191#[derive(Debug, Clone, Serialize)]
200#[cfg_attr(feature = "openapi", derive(ToSchema))]
201#[serde(rename_all = "camelCase")]
202pub struct LocationListResponseDto {
203 pub items: Vec<LocationResponseDto>,
205 pub total: u64,
207 pub page: u32,
209 pub per_page: u32,
211 pub total_pages: u32,
213 pub has_next: bool,
215 pub has_prev: bool,
217}
218
219impl LocationListResponseDto {
220 pub fn new(items: Vec<LocationResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
222 let total_pages = if per_page > 0 {
223 ((total as f64) / (per_page as f64)).ceil() as u32
224 } else {
225 0
226 };
227 Self {
228 items,
229 total,
230 page,
231 per_page,
232 total_pages,
233 has_next: page < total_pages,
234 has_prev: page > 1,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Serialize)]
241#[cfg_attr(feature = "openapi", derive(ToSchema))]
242#[serde(rename_all = "camelCase")]
243pub struct LocationSummaryDto {
244 pub id: Uuid,
245 pub name: String,
246 pub complete_name: String,
247 pub active: bool,
248 pub created_at: Option<DateTime<Utc>>,
249}
250
251impl From<Location> for LocationResponseDto {
256 fn from(entity: Location) -> Self {
257 Self {
258 id: entity.id,
259 name: entity.name,
260 complete_name: entity.complete_name,
261 active: entity.active,
262 usage: entity.usage,
263 location_id: entity.location_id,
264 parent_path: entity.parent_path,
265 barcode: entity.barcode,
266 warehouse_id: entity.warehouse_id,
267 cyclic_inventory_frequency: entity.cyclic_inventory_frequency,
268 last_inventory_date: entity.last_inventory_date,
269 next_inventory_date: entity.next_inventory_date,
270 valuation_account_id: entity.valuation_account_id,
271 storage_category_id: entity.storage_category_id,
272 metadata: entity.metadata,
273 }
274 }
275}
276
277impl From<Location> for LocationSummaryDto {
278 fn from(entity: Location) -> Self {
279 let created_at = backbone_core::PersistentEntity::created_at(&entity);
280 Self {
281 id: entity.id,
282 name: entity.name,
283 complete_name: entity.complete_name,
284 active: entity.active,
285 created_at,
286 }
287 }
288}
289
290impl From<CreateLocationDto> for Location {
291 fn from(dto: CreateLocationDto) -> Self {
292 Self {
293 id: Uuid::new_v4(),
294 name: dto.name,
295 complete_name: dto.complete_name,
296 active: dto.active,
297 usage: dto.usage,
298 location_id: dto.location_id,
299 parent_path: dto.parent_path,
300 barcode: dto.barcode,
301 warehouse_id: dto.warehouse_id,
302 cyclic_inventory_frequency: dto.cyclic_inventory_frequency,
303 last_inventory_date: dto.last_inventory_date,
304 next_inventory_date: dto.next_inventory_date,
305 valuation_account_id: dto.valuation_account_id,
306 storage_category_id: dto.storage_category_id,
307 metadata: AuditMetadata::default(),
308 }
309 }
310}
311
312impl From<&Location> for LocationResponseDto {
313 fn from(entity: &Location) -> Self {
314 Self {
315 id: entity.id.clone(),
316 name: entity.name.clone(),
317 complete_name: entity.complete_name.clone(),
318 active: entity.active.clone(),
319 usage: entity.usage.clone(),
320 location_id: entity.location_id.clone(),
321 parent_path: entity.parent_path.clone(),
322 barcode: entity.barcode.clone(),
323 warehouse_id: entity.warehouse_id.clone(),
324 cyclic_inventory_frequency: entity.cyclic_inventory_frequency.clone(),
325 last_inventory_date: entity.last_inventory_date.clone(),
326 next_inventory_date: entity.next_inventory_date.clone(),
327 valuation_account_id: entity.valuation_account_id.clone(),
328 storage_category_id: entity.storage_category_id.clone(),
329 metadata: entity.metadata.clone(),
330 }
331 }
332}
333
334impl backbone_core::FromCreateDto<CreateLocationDto> for Location {
335 fn from_create_dto(dto: CreateLocationDto) -> backbone_core::ServiceResult<Self> {
336 Ok(Location::from(dto))
337 }
338}
339
340impl backbone_core::ApplyUpdateDto<UpdateLocationDto> for Location {
341 fn apply_update(mut self, dto: UpdateLocationDto) -> backbone_core::ServiceResult<Self> {
342 self.name = dto.name;
343 self.complete_name = dto.complete_name;
344 self.active = dto.active;
345 self.usage = dto.usage;
346 self.location_id = dto.location_id;
347 self.parent_path = dto.parent_path;
348 self.barcode = dto.barcode;
349 self.warehouse_id = dto.warehouse_id;
350 self.cyclic_inventory_frequency = dto.cyclic_inventory_frequency;
351 self.last_inventory_date = dto.last_inventory_date;
352 self.next_inventory_date = dto.next_inventory_date;
353 self.valuation_account_id = dto.valuation_account_id;
354 self.storage_category_id = dto.storage_category_id;
355 Ok(self)
356 }
357}
358
359