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