Skip to main content

backbone_inventory/presentation/dto/
location_dto.rs

1//! Location DTOs (Data Transfer Objects)
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! DTOs provide a clean separation between domain entities and API
6//! representations, with OpenAPI documentation support.
7
8use 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// =============================================================================
21// Create DTO
22// =============================================================================
23
24/// Request DTO for creating a new Location
25///
26/// Used in POST requests to create new entities.
27/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
28#[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// =============================================================================
63// Update DTO
64// =============================================================================
65
66/// Request DTO for full update of a Location
67///
68/// Used in PUT requests for full entity replacement.
69/// All fields are required (except auto-generated ones).
70#[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// =============================================================================
105// Patch DTO
106// =============================================================================
107
108/// Request DTO for partial update of a Location
109///
110/// Used in PATCH requests for partial updates.
111/// All fields are optional - only provided fields will be updated.
112#[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    /// Check if any field is set
151    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// =============================================================================
157// Response DTO
158// =============================================================================
159
160/// Response DTO for Location entity
161///
162/// Used in API responses for single entity.
163/// Includes all fields including metadata.
164#[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// =============================================================================
192// List Response DTO
193// =============================================================================
194
195/// Paginated list response for Location entities
196///
197/// Used in API responses for list endpoints.
198/// Includes pagination metadata.
199#[derive(Debug, Clone, Serialize)]
200#[cfg_attr(feature = "openapi", derive(ToSchema))]
201#[serde(rename_all = "camelCase")]
202pub struct LocationListResponseDto {
203    /// List of items
204    pub items: Vec<LocationResponseDto>,
205    /// Total number of items (across all pages)
206    pub total: u64,
207    /// Current page number (1-indexed)
208    pub page: u32,
209    /// Number of items per page
210    pub per_page: u32,
211    /// Total number of pages
212    pub total_pages: u32,
213    /// Whether there is a next page
214    pub has_next: bool,
215    /// Whether there is a previous page
216    pub has_prev: bool,
217}
218
219impl LocationListResponseDto {
220    /// Create a new list response from items and pagination info
221    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/// Summary DTO for list views (compact version)
240#[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
251// =============================================================================
252// Conversions
253// =============================================================================
254
255impl 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// =============================================================================
360// Custom DTOs
361// =============================================================================
362
363// Add custom DTOs specific to Location here.
364// This section will be preserved during regeneration.
365// >>> END CUSTOM DTOs
366