Skip to main content

backbone_inventory/presentation/dto/
delivery_note_dto.rs

1//! DeliveryNote 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 validation and OpenAPI documentation support.
7
8use 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::DeliveryNote;
21use crate::domain::entity::AuditMetadata;
22use crate::domain::entity::DocStatus;
23use crate::domain::entity::GlPostingState;
24
25// =============================================================================
26// Create DTO
27// =============================================================================
28
29/// Request DTO for creating a new DeliveryNote
30///
31/// Used in POST requests to create new entities.
32/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
33#[derive(Debug, Clone, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(ToSchema))]
35#[cfg_attr(feature = "validation", derive(Validate))]
36#[serde(rename_all = "camelCase")]
37pub struct CreateDeliveryNoteDto {
38    #[cfg_attr(feature = "validation", validate(length(max = 40)))]
39    #[cfg_attr(feature = "openapi", schema(example = "example"))]
40    #[serde(alias = "delivery_number")]
41    pub delivery_number: String,
42    #[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
43    pub branch_id: Option<Uuid>,
44    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
45    #[serde(alias = "customer_id")]
46    pub customer_id: Uuid,
47    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_so_id")]
48    pub source_so_id: Option<Uuid>,
49    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
50    #[serde(alias = "warehouse_id")]
51    pub warehouse_id: Uuid,
52    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
53    #[serde(alias = "posting_date")]
54    pub posting_date: NaiveDate,
55    #[serde(alias = "total_cogs")]
56    pub total_cogs: Decimal,
57    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
58    #[serde(alias = "cogs_account_id")]
59    pub cogs_account_id: Uuid,
60    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
61    #[serde(alias = "inventory_account_id")]
62    pub inventory_account_id: Uuid,
63    #[serde(default, skip_serializing_if = "Option::is_none", alias = "transfer_id")]
64    pub transfer_id: Option<Uuid>,
65    pub status: DocStatus,
66    #[serde(alias = "posting_state")]
67    pub posting_state: GlPostingState,
68    #[serde(default, skip_serializing_if = "Option::is_none", alias = "journal_id")]
69    pub journal_id: Option<Uuid>,
70    #[serde(default, skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
71    pub accounting_post_id: Option<Uuid>,
72    #[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
73    pub posted_at: Option<DateTime<Utc>>,
74    #[cfg_attr(feature = "validation", validate(length(max = 1000)))]
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub notes: Option<String>,
77}
78
79// =============================================================================
80// Update DTO
81// =============================================================================
82
83/// Request DTO for full update of a DeliveryNote
84///
85/// Used in PUT requests for full entity replacement.
86/// All fields are required (except auto-generated ones).
87#[derive(Debug, Clone, Deserialize)]
88#[cfg_attr(feature = "openapi", derive(ToSchema))]
89#[cfg_attr(feature = "validation", derive(Validate))]
90#[serde(rename_all = "camelCase")]
91pub struct UpdateDeliveryNoteDto {
92    #[cfg_attr(feature = "validation", validate(length(max = 40)))]
93    #[cfg_attr(feature = "openapi", schema(example = "example"))]
94    #[serde(alias = "delivery_number")]
95    pub delivery_number: String,
96    #[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
97    pub branch_id: Option<Uuid>,
98    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
99    #[serde(alias = "customer_id")]
100    pub customer_id: Uuid,
101    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_so_id")]
102    pub source_so_id: Option<Uuid>,
103    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
104    #[serde(alias = "warehouse_id")]
105    pub warehouse_id: Uuid,
106    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
107    #[serde(alias = "posting_date")]
108    pub posting_date: NaiveDate,
109    #[serde(alias = "total_cogs")]
110    pub total_cogs: Decimal,
111    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
112    #[serde(alias = "cogs_account_id")]
113    pub cogs_account_id: Uuid,
114    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
115    #[serde(alias = "inventory_account_id")]
116    pub inventory_account_id: Uuid,
117    #[serde(default, skip_serializing_if = "Option::is_none", alias = "transfer_id")]
118    pub transfer_id: Option<Uuid>,
119    pub status: DocStatus,
120    #[serde(alias = "posting_state")]
121    pub posting_state: GlPostingState,
122    #[serde(default, skip_serializing_if = "Option::is_none", alias = "journal_id")]
123    pub journal_id: Option<Uuid>,
124    #[serde(default, skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
125    pub accounting_post_id: Option<Uuid>,
126    #[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
127    pub posted_at: Option<DateTime<Utc>>,
128    #[cfg_attr(feature = "validation", validate(length(max = 1000)))]
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub notes: Option<String>,
131}
132
133// =============================================================================
134// Patch DTO
135// =============================================================================
136
137/// Request DTO for partial update of a DeliveryNote
138///
139/// Used in PATCH requests for partial updates.
140/// All fields are optional - only provided fields will be updated.
141#[derive(Debug, Clone, Default, Deserialize)]
142#[cfg_attr(feature = "openapi", derive(ToSchema))]
143#[cfg_attr(feature = "validation", derive(Validate))]
144#[serde(rename_all = "camelCase")]
145pub struct PatchDeliveryNoteDto {
146    #[cfg_attr(feature = "validation", validate(length(max = 40)))]
147    #[cfg_attr(feature = "openapi", schema(example = "example"))]
148    #[serde(skip_serializing_if = "Option::is_none", alias = "delivery_number")]
149    pub delivery_number: Option<String>,
150    #[serde(skip_serializing_if = "Option::is_none", alias = "branch_id")]
151    pub branch_id: Option<Uuid>,
152    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
153    #[serde(skip_serializing_if = "Option::is_none", alias = "customer_id")]
154    pub customer_id: Option<Uuid>,
155    #[serde(skip_serializing_if = "Option::is_none", alias = "source_so_id")]
156    pub source_so_id: Option<Uuid>,
157    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
158    #[serde(skip_serializing_if = "Option::is_none", alias = "warehouse_id")]
159    pub warehouse_id: Option<Uuid>,
160    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
161    #[serde(skip_serializing_if = "Option::is_none", alias = "posting_date")]
162    pub posting_date: Option<NaiveDate>,
163    #[serde(skip_serializing_if = "Option::is_none", alias = "total_cogs")]
164    pub total_cogs: Option<Decimal>,
165    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
166    #[serde(skip_serializing_if = "Option::is_none", alias = "cogs_account_id")]
167    pub cogs_account_id: Option<Uuid>,
168    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
169    #[serde(skip_serializing_if = "Option::is_none", alias = "inventory_account_id")]
170    pub inventory_account_id: Option<Uuid>,
171    #[serde(skip_serializing_if = "Option::is_none", alias = "transfer_id")]
172    pub transfer_id: Option<Uuid>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub status: Option<DocStatus>,
175    #[serde(skip_serializing_if = "Option::is_none", alias = "posting_state")]
176    pub posting_state: Option<GlPostingState>,
177    #[serde(skip_serializing_if = "Option::is_none", alias = "journal_id")]
178    pub journal_id: Option<Uuid>,
179    #[serde(skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
180    pub accounting_post_id: Option<Uuid>,
181    #[serde(skip_serializing_if = "Option::is_none", alias = "posted_at")]
182    pub posted_at: Option<DateTime<Utc>>,
183    #[cfg_attr(feature = "validation", validate(length(max = 1000)))]
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub notes: Option<String>,
186}
187
188impl PatchDeliveryNoteDto {
189    /// Check if any field is set
190    pub fn has_changes(&self) -> bool {
191        self.delivery_number.is_some() || self.branch_id.is_some() || self.customer_id.is_some() || self.source_so_id.is_some() || self.warehouse_id.is_some() || self.posting_date.is_some() || self.total_cogs.is_some() || self.cogs_account_id.is_some() || self.inventory_account_id.is_some() || self.transfer_id.is_some() || self.status.is_some() || self.posting_state.is_some() || self.journal_id.is_some() || self.accounting_post_id.is_some() || self.posted_at.is_some() || self.notes.is_some()
192    }
193}
194
195// =============================================================================
196// Response DTO
197// =============================================================================
198
199/// Response DTO for DeliveryNote entity
200///
201/// Used in API responses for single entity.
202/// Includes all fields including metadata.
203#[derive(Debug, Clone, Serialize)]
204#[cfg_attr(feature = "openapi", derive(ToSchema))]
205#[serde(rename_all = "camelCase")]
206pub struct DeliveryNoteResponseDto {
207    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
208    pub id: Uuid,
209    #[cfg_attr(feature = "openapi", schema(example = "example"))]
210    pub delivery_number: String,
211    pub branch_id: Option<Uuid>,
212    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
213    pub customer_id: Uuid,
214    pub source_so_id: Option<Uuid>,
215    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
216    pub warehouse_id: Uuid,
217    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
218    pub posting_date: NaiveDate,
219    pub total_cogs: Decimal,
220    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
221    pub cogs_account_id: Uuid,
222    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
223    pub inventory_account_id: Uuid,
224    pub transfer_id: Option<Uuid>,
225    pub status: DocStatus,
226    pub posting_state: GlPostingState,
227    pub journal_id: Option<Uuid>,
228    pub accounting_post_id: Option<Uuid>,
229    pub posted_at: Option<DateTime<Utc>>,
230    pub notes: Option<String>,
231    pub metadata: AuditMetadata,
232}
233
234// =============================================================================
235// List Response DTO
236// =============================================================================
237
238/// Paginated list response for DeliveryNote entities
239///
240/// Used in API responses for list endpoints.
241/// Includes pagination metadata.
242#[derive(Debug, Clone, Serialize)]
243#[cfg_attr(feature = "openapi", derive(ToSchema))]
244#[serde(rename_all = "camelCase")]
245pub struct DeliveryNoteListResponseDto {
246    /// List of items
247    pub items: Vec<DeliveryNoteResponseDto>,
248    /// Total number of items (across all pages)
249    pub total: u64,
250    /// Current page number (1-indexed)
251    pub page: u32,
252    /// Number of items per page
253    pub per_page: u32,
254    /// Total number of pages
255    pub total_pages: u32,
256    /// Whether there is a next page
257    pub has_next: bool,
258    /// Whether there is a previous page
259    pub has_prev: bool,
260}
261
262impl DeliveryNoteListResponseDto {
263    /// Create a new list response from items and pagination info
264    pub fn new(items: Vec<DeliveryNoteResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
265        let total_pages = if per_page > 0 {
266            ((total as f64) / (per_page as f64)).ceil() as u32
267        } else {
268            0
269        };
270        Self {
271            items,
272            total,
273            page,
274            per_page,
275            total_pages,
276            has_next: page < total_pages,
277            has_prev: page > 1,
278        }
279    }
280}
281
282/// Summary DTO for list views (compact version)
283#[derive(Debug, Clone, Serialize)]
284#[cfg_attr(feature = "openapi", derive(ToSchema))]
285#[serde(rename_all = "camelCase")]
286pub struct DeliveryNoteSummaryDto {
287    pub id: Uuid,
288    pub delivery_number: String,
289    pub branch_id: Option<Uuid>,
290    pub customer_id: Uuid,
291    pub created_at: Option<DateTime<Utc>>,
292}
293
294// =============================================================================
295// Conversions
296// =============================================================================
297
298impl From<DeliveryNote> for DeliveryNoteResponseDto {
299    fn from(entity: DeliveryNote) -> Self {
300        Self {
301            id: entity.id,
302            delivery_number: entity.delivery_number,
303            branch_id: entity.branch_id,
304            customer_id: entity.customer_id,
305            source_so_id: entity.source_so_id,
306            warehouse_id: entity.warehouse_id,
307            posting_date: entity.posting_date,
308            total_cogs: entity.total_cogs,
309            cogs_account_id: entity.cogs_account_id,
310            inventory_account_id: entity.inventory_account_id,
311            transfer_id: entity.transfer_id,
312            status: entity.status,
313            posting_state: entity.posting_state,
314            journal_id: entity.journal_id,
315            accounting_post_id: entity.accounting_post_id,
316            posted_at: entity.posted_at,
317            notes: entity.notes,
318            metadata: entity.metadata,
319        }
320    }
321}
322
323impl From<DeliveryNote> for DeliveryNoteSummaryDto {
324    fn from(entity: DeliveryNote) -> Self {
325        let created_at = backbone_core::PersistentEntity::created_at(&entity);
326        Self {
327            id: entity.id,
328            delivery_number: entity.delivery_number,
329            branch_id: entity.branch_id,
330            customer_id: entity.customer_id,
331            created_at,
332        }
333    }
334}
335
336impl From<CreateDeliveryNoteDto> for DeliveryNote {
337    fn from(dto: CreateDeliveryNoteDto) -> Self {
338        Self {
339            id: Uuid::new_v4(),
340            delivery_number: dto.delivery_number,
341            branch_id: dto.branch_id,
342            customer_id: dto.customer_id,
343            source_so_id: dto.source_so_id,
344            warehouse_id: dto.warehouse_id,
345            posting_date: dto.posting_date,
346            total_cogs: dto.total_cogs,
347            cogs_account_id: dto.cogs_account_id,
348            inventory_account_id: dto.inventory_account_id,
349            transfer_id: dto.transfer_id,
350            status: dto.status,
351            posting_state: dto.posting_state,
352            journal_id: dto.journal_id,
353            accounting_post_id: dto.accounting_post_id,
354            posted_at: dto.posted_at,
355            notes: dto.notes,
356            metadata: AuditMetadata::default(),
357        }
358    }
359}
360
361impl From<&DeliveryNote> for DeliveryNoteResponseDto {
362    fn from(entity: &DeliveryNote) -> Self {
363        Self {
364            id: entity.id.clone(),
365            delivery_number: entity.delivery_number.clone(),
366            branch_id: entity.branch_id.clone(),
367            customer_id: entity.customer_id.clone(),
368            source_so_id: entity.source_so_id.clone(),
369            warehouse_id: entity.warehouse_id.clone(),
370            posting_date: entity.posting_date.clone(),
371            total_cogs: entity.total_cogs.clone(),
372            cogs_account_id: entity.cogs_account_id.clone(),
373            inventory_account_id: entity.inventory_account_id.clone(),
374            transfer_id: entity.transfer_id.clone(),
375            status: entity.status.clone(),
376            posting_state: entity.posting_state.clone(),
377            journal_id: entity.journal_id.clone(),
378            accounting_post_id: entity.accounting_post_id.clone(),
379            posted_at: entity.posted_at.clone(),
380            notes: entity.notes.clone(),
381            metadata: entity.metadata.clone(),
382        }
383    }
384}
385
386impl backbone_core::FromCreateDto<CreateDeliveryNoteDto> for DeliveryNote {
387    fn from_create_dto(dto: CreateDeliveryNoteDto) -> backbone_core::ServiceResult<Self> {
388        Ok(DeliveryNote::from(dto))
389    }
390}
391
392impl backbone_core::ApplyUpdateDto<UpdateDeliveryNoteDto> for DeliveryNote {
393    fn apply_update(mut self, dto: UpdateDeliveryNoteDto) -> backbone_core::ServiceResult<Self> {
394        self.delivery_number = dto.delivery_number;
395        self.branch_id = dto.branch_id;
396        self.customer_id = dto.customer_id;
397        self.source_so_id = dto.source_so_id;
398        self.warehouse_id = dto.warehouse_id;
399        self.posting_date = dto.posting_date;
400        self.total_cogs = dto.total_cogs;
401        self.cogs_account_id = dto.cogs_account_id;
402        self.inventory_account_id = dto.inventory_account_id;
403        self.transfer_id = dto.transfer_id;
404        self.status = dto.status;
405        self.posting_state = dto.posting_state;
406        self.journal_id = dto.journal_id;
407        self.accounting_post_id = dto.accounting_post_id;
408        self.posted_at = dto.posted_at;
409        self.notes = dto.notes;
410        Ok(self)
411    }
412}
413
414// =============================================================================
415// Custom DTOs
416// =============================================================================
417
418// Add custom DTOs specific to DeliveryNote here.
419// This section will be preserved during regeneration.
420// >>> END CUSTOM DTOs
421