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