Skip to main content

backbone_accounting/presentation/dto/
journal_line_dto.rs

1//! JournalLine 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};
11use rust_decimal::Decimal;
12
13#[cfg(feature = "openapi")]
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17use crate::domain::entity::JournalLine;
18use crate::domain::entity::PartyType;
19
20// =============================================================================
21// Create DTO
22// =============================================================================
23
24/// Request DTO for creating a new JournalLine
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 CreateJournalLineDto {
32    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
33    #[serde(alias = "journal_id")]
34    pub journal_id: Uuid,
35    #[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
36    pub branch_id: Option<Uuid>,
37    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_type")]
38    pub party_type: Option<PartyType>,
39    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
40    pub party_id: Option<Uuid>,
41    #[cfg_attr(feature = "openapi", schema(example = 42))]
42    #[serde(alias = "line_number")]
43    pub line_number: i32,
44    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
45    #[serde(alias = "account_id")]
46    pub account_id: Uuid,
47    #[cfg_attr(feature = "openapi", schema(example = "example"))]
48    #[serde(alias = "account_number")]
49    pub account_number: String,
50    #[cfg_attr(feature = "openapi", schema(example = "example"))]
51    #[serde(alias = "account_name")]
52    pub account_name: String,
53    #[serde(alias = "debit_amount")]
54    pub debit_amount: Decimal,
55    #[serde(alias = "credit_amount")]
56    pub credit_amount: Decimal,
57    #[cfg_attr(feature = "openapi", schema(example = "example"))]
58    pub currency: String,
59    #[serde(alias = "exchange_rate")]
60    pub exchange_rate: Decimal,
61    #[serde(alias = "base_debit_amount")]
62    pub base_debit_amount: Decimal,
63    #[serde(alias = "base_credit_amount")]
64    pub base_credit_amount: Decimal,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub description: Option<String>,
67    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cost_center_id")]
68    pub cost_center_id: Option<Uuid>,
69    #[serde(default, skip_serializing_if = "Option::is_none", alias = "project_id")]
70    pub project_id: Option<Uuid>,
71    #[serde(default, skip_serializing_if = "Option::is_none", alias = "department_id")]
72    pub department_id: Option<Uuid>,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub dimensions: Option<serde_json::Value>,
75    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_type")]
76    pub source_type: Option<String>,
77    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_id")]
78    pub source_id: Option<Uuid>,
79    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_reference")]
80    pub source_reference: Option<String>,
81    #[cfg_attr(feature = "openapi", schema(example = true))]
82    #[serde(alias = "is_tax_line")]
83    pub is_tax_line: bool,
84    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_rate")]
85    pub tax_rate: Option<Decimal>,
86    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_base_amount")]
87    pub tax_base_amount: Option<Decimal>,
88    #[serde(default, skip_serializing_if = "Option::is_none", alias = "related_line_id")]
89    pub related_line_id: Option<Uuid>,
90    #[cfg_attr(feature = "openapi", schema(example = true))]
91    #[serde(alias = "has_quantity")]
92    pub has_quantity: bool,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub quantity: Option<Decimal>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub unit: Option<String>,
97    #[serde(default, skip_serializing_if = "Option::is_none", alias = "unit_price")]
98    pub unit_price: Option<Decimal>,
99    #[cfg_attr(feature = "openapi", schema(example = true))]
100    #[serde(alias = "is_reconciled")]
101    pub is_reconciled: bool,
102    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reconciliation_id")]
103    pub reconciliation_id: Option<Uuid>,
104    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reconciled_at")]
105    pub reconciled_at: Option<DateTime<Utc>>,
106    #[serde(default, skip_serializing_if = "Option::is_none", alias = "full_reconcile_id")]
107    pub full_reconcile_id: Option<Uuid>,
108    #[cfg_attr(feature = "openapi", schema(example = true))]
109    #[serde(alias = "is_posted")]
110    pub is_posted: bool,
111    #[serde(default, skip_serializing_if = "Option::is_none", alias = "ledger_id")]
112    pub ledger_id: Option<Uuid>,
113    #[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
114    pub posted_at: Option<DateTime<Utc>>,
115    pub tags: serde_json::Value,
116    pub data: serde_json::Value,
117}
118
119// =============================================================================
120// Update DTO
121// =============================================================================
122
123/// Request DTO for full update of a JournalLine
124///
125/// Used in PUT requests for full entity replacement.
126/// All fields are required (except auto-generated ones).
127#[derive(Debug, Clone, Deserialize)]
128#[cfg_attr(feature = "openapi", derive(ToSchema))]
129#[serde(rename_all = "camelCase")]
130pub struct UpdateJournalLineDto {
131    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
132    #[serde(alias = "journal_id")]
133    pub journal_id: Uuid,
134    #[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
135    pub branch_id: Option<Uuid>,
136    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_type")]
137    pub party_type: Option<PartyType>,
138    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
139    pub party_id: Option<Uuid>,
140    #[cfg_attr(feature = "openapi", schema(example = 42))]
141    #[serde(alias = "line_number")]
142    pub line_number: i32,
143    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
144    #[serde(alias = "account_id")]
145    pub account_id: Uuid,
146    #[cfg_attr(feature = "openapi", schema(example = "example"))]
147    #[serde(alias = "account_number")]
148    pub account_number: String,
149    #[cfg_attr(feature = "openapi", schema(example = "example"))]
150    #[serde(alias = "account_name")]
151    pub account_name: String,
152    #[serde(alias = "debit_amount")]
153    pub debit_amount: Decimal,
154    #[serde(alias = "credit_amount")]
155    pub credit_amount: Decimal,
156    #[cfg_attr(feature = "openapi", schema(example = "example"))]
157    pub currency: String,
158    #[serde(alias = "exchange_rate")]
159    pub exchange_rate: Decimal,
160    #[serde(alias = "base_debit_amount")]
161    pub base_debit_amount: Decimal,
162    #[serde(alias = "base_credit_amount")]
163    pub base_credit_amount: Decimal,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub description: Option<String>,
166    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cost_center_id")]
167    pub cost_center_id: Option<Uuid>,
168    #[serde(default, skip_serializing_if = "Option::is_none", alias = "project_id")]
169    pub project_id: Option<Uuid>,
170    #[serde(default, skip_serializing_if = "Option::is_none", alias = "department_id")]
171    pub department_id: Option<Uuid>,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub dimensions: Option<serde_json::Value>,
174    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_type")]
175    pub source_type: Option<String>,
176    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_id")]
177    pub source_id: Option<Uuid>,
178    #[serde(default, skip_serializing_if = "Option::is_none", alias = "source_reference")]
179    pub source_reference: Option<String>,
180    #[cfg_attr(feature = "openapi", schema(example = true))]
181    #[serde(alias = "is_tax_line")]
182    pub is_tax_line: bool,
183    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_rate")]
184    pub tax_rate: Option<Decimal>,
185    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_base_amount")]
186    pub tax_base_amount: Option<Decimal>,
187    #[serde(default, skip_serializing_if = "Option::is_none", alias = "related_line_id")]
188    pub related_line_id: Option<Uuid>,
189    #[cfg_attr(feature = "openapi", schema(example = true))]
190    #[serde(alias = "has_quantity")]
191    pub has_quantity: bool,
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub quantity: Option<Decimal>,
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub unit: Option<String>,
196    #[serde(default, skip_serializing_if = "Option::is_none", alias = "unit_price")]
197    pub unit_price: Option<Decimal>,
198    #[cfg_attr(feature = "openapi", schema(example = true))]
199    #[serde(alias = "is_reconciled")]
200    pub is_reconciled: bool,
201    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reconciliation_id")]
202    pub reconciliation_id: Option<Uuid>,
203    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reconciled_at")]
204    pub reconciled_at: Option<DateTime<Utc>>,
205    #[serde(default, skip_serializing_if = "Option::is_none", alias = "full_reconcile_id")]
206    pub full_reconcile_id: Option<Uuid>,
207    #[cfg_attr(feature = "openapi", schema(example = true))]
208    #[serde(alias = "is_posted")]
209    pub is_posted: bool,
210    #[serde(default, skip_serializing_if = "Option::is_none", alias = "ledger_id")]
211    pub ledger_id: Option<Uuid>,
212    #[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
213    pub posted_at: Option<DateTime<Utc>>,
214    pub tags: serde_json::Value,
215    pub data: serde_json::Value,
216}
217
218// =============================================================================
219// Patch DTO
220// =============================================================================
221
222/// Request DTO for partial update of a JournalLine
223///
224/// Used in PATCH requests for partial updates.
225/// All fields are optional - only provided fields will be updated.
226#[derive(Debug, Clone, Default, Deserialize)]
227#[cfg_attr(feature = "openapi", derive(ToSchema))]
228#[serde(rename_all = "camelCase")]
229pub struct PatchJournalLineDto {
230    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
231    #[serde(skip_serializing_if = "Option::is_none", alias = "journal_id")]
232    pub journal_id: Option<Uuid>,
233    #[serde(skip_serializing_if = "Option::is_none", alias = "branch_id")]
234    pub branch_id: Option<Uuid>,
235    #[serde(skip_serializing_if = "Option::is_none", alias = "party_type")]
236    pub party_type: Option<PartyType>,
237    #[serde(skip_serializing_if = "Option::is_none", alias = "party_id")]
238    pub party_id: Option<Uuid>,
239    #[cfg_attr(feature = "openapi", schema(example = 42))]
240    #[serde(skip_serializing_if = "Option::is_none", alias = "line_number")]
241    pub line_number: Option<i32>,
242    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
243    #[serde(skip_serializing_if = "Option::is_none", alias = "account_id")]
244    pub account_id: Option<Uuid>,
245    #[cfg_attr(feature = "openapi", schema(example = "example"))]
246    #[serde(skip_serializing_if = "Option::is_none", alias = "account_number")]
247    pub account_number: Option<String>,
248    #[cfg_attr(feature = "openapi", schema(example = "example"))]
249    #[serde(skip_serializing_if = "Option::is_none", alias = "account_name")]
250    pub account_name: Option<String>,
251    #[serde(skip_serializing_if = "Option::is_none", alias = "debit_amount")]
252    pub debit_amount: Option<Decimal>,
253    #[serde(skip_serializing_if = "Option::is_none", alias = "credit_amount")]
254    pub credit_amount: Option<Decimal>,
255    #[cfg_attr(feature = "openapi", schema(example = "example"))]
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub currency: Option<String>,
258    #[serde(skip_serializing_if = "Option::is_none", alias = "exchange_rate")]
259    pub exchange_rate: Option<Decimal>,
260    #[serde(skip_serializing_if = "Option::is_none", alias = "base_debit_amount")]
261    pub base_debit_amount: Option<Decimal>,
262    #[serde(skip_serializing_if = "Option::is_none", alias = "base_credit_amount")]
263    pub base_credit_amount: Option<Decimal>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub description: Option<String>,
266    #[serde(skip_serializing_if = "Option::is_none", alias = "cost_center_id")]
267    pub cost_center_id: Option<Uuid>,
268    #[serde(skip_serializing_if = "Option::is_none", alias = "project_id")]
269    pub project_id: Option<Uuid>,
270    #[serde(skip_serializing_if = "Option::is_none", alias = "department_id")]
271    pub department_id: Option<Uuid>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub dimensions: Option<serde_json::Value>,
274    #[serde(skip_serializing_if = "Option::is_none", alias = "source_type")]
275    pub source_type: Option<String>,
276    #[serde(skip_serializing_if = "Option::is_none", alias = "source_id")]
277    pub source_id: Option<Uuid>,
278    #[serde(skip_serializing_if = "Option::is_none", alias = "source_reference")]
279    pub source_reference: Option<String>,
280    #[cfg_attr(feature = "openapi", schema(example = true))]
281    #[serde(skip_serializing_if = "Option::is_none", alias = "is_tax_line")]
282    pub is_tax_line: Option<bool>,
283    #[serde(skip_serializing_if = "Option::is_none", alias = "tax_rate")]
284    pub tax_rate: Option<Decimal>,
285    #[serde(skip_serializing_if = "Option::is_none", alias = "tax_base_amount")]
286    pub tax_base_amount: Option<Decimal>,
287    #[serde(skip_serializing_if = "Option::is_none", alias = "related_line_id")]
288    pub related_line_id: Option<Uuid>,
289    #[cfg_attr(feature = "openapi", schema(example = true))]
290    #[serde(skip_serializing_if = "Option::is_none", alias = "has_quantity")]
291    pub has_quantity: Option<bool>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub quantity: Option<Decimal>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub unit: Option<String>,
296    #[serde(skip_serializing_if = "Option::is_none", alias = "unit_price")]
297    pub unit_price: Option<Decimal>,
298    #[cfg_attr(feature = "openapi", schema(example = true))]
299    #[serde(skip_serializing_if = "Option::is_none", alias = "is_reconciled")]
300    pub is_reconciled: Option<bool>,
301    #[serde(skip_serializing_if = "Option::is_none", alias = "reconciliation_id")]
302    pub reconciliation_id: Option<Uuid>,
303    #[serde(skip_serializing_if = "Option::is_none", alias = "reconciled_at")]
304    pub reconciled_at: Option<DateTime<Utc>>,
305    #[serde(skip_serializing_if = "Option::is_none", alias = "full_reconcile_id")]
306    pub full_reconcile_id: Option<Uuid>,
307    #[cfg_attr(feature = "openapi", schema(example = true))]
308    #[serde(skip_serializing_if = "Option::is_none", alias = "is_posted")]
309    pub is_posted: Option<bool>,
310    #[serde(skip_serializing_if = "Option::is_none", alias = "ledger_id")]
311    pub ledger_id: Option<Uuid>,
312    #[serde(skip_serializing_if = "Option::is_none", alias = "posted_at")]
313    pub posted_at: Option<DateTime<Utc>>,
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub tags: Option<serde_json::Value>,
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub data: Option<serde_json::Value>,
318}
319
320impl PatchJournalLineDto {
321    /// Check if any field is set
322    pub fn has_changes(&self) -> bool {
323        self.journal_id.is_some() || self.branch_id.is_some() || self.party_type.is_some() || self.party_id.is_some() || self.line_number.is_some() || self.account_id.is_some() || self.account_number.is_some() || self.account_name.is_some() || self.debit_amount.is_some() || self.credit_amount.is_some() || self.currency.is_some() || self.exchange_rate.is_some() || self.base_debit_amount.is_some() || self.base_credit_amount.is_some() || self.description.is_some() || self.cost_center_id.is_some() || self.project_id.is_some() || self.department_id.is_some() || self.dimensions.is_some() || self.source_type.is_some() || self.source_id.is_some() || self.source_reference.is_some() || self.is_tax_line.is_some() || self.tax_rate.is_some() || self.tax_base_amount.is_some() || self.related_line_id.is_some() || self.has_quantity.is_some() || self.quantity.is_some() || self.unit.is_some() || self.unit_price.is_some() || self.is_reconciled.is_some() || self.reconciliation_id.is_some() || self.reconciled_at.is_some() || self.full_reconcile_id.is_some() || self.is_posted.is_some() || self.ledger_id.is_some() || self.posted_at.is_some() || self.tags.is_some() || self.data.is_some()
324    }
325}
326
327// =============================================================================
328// Response DTO
329// =============================================================================
330
331/// Response DTO for JournalLine entity
332///
333/// Used in API responses for single entity.
334/// Includes all fields including metadata.
335#[derive(Debug, Clone, Serialize)]
336#[cfg_attr(feature = "openapi", derive(ToSchema))]
337#[serde(rename_all = "camelCase")]
338pub struct JournalLineResponseDto {
339    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
340    pub id: Uuid,
341    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
342    pub journal_id: Uuid,
343    pub branch_id: Option<Uuid>,
344    pub party_type: Option<PartyType>,
345    pub party_id: Option<Uuid>,
346    #[cfg_attr(feature = "openapi", schema(example = 42))]
347    pub line_number: i32,
348    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
349    pub account_id: Uuid,
350    #[cfg_attr(feature = "openapi", schema(example = "example"))]
351    pub account_number: String,
352    #[cfg_attr(feature = "openapi", schema(example = "example"))]
353    pub account_name: String,
354    pub debit_amount: Decimal,
355    pub credit_amount: Decimal,
356    #[cfg_attr(feature = "openapi", schema(example = "example"))]
357    pub currency: String,
358    pub exchange_rate: Decimal,
359    pub base_debit_amount: Decimal,
360    pub base_credit_amount: Decimal,
361    pub description: Option<String>,
362    pub cost_center_id: Option<Uuid>,
363    pub project_id: Option<Uuid>,
364    pub department_id: Option<Uuid>,
365    pub dimensions: Option<serde_json::Value>,
366    pub source_type: Option<String>,
367    pub source_id: Option<Uuid>,
368    pub source_reference: Option<String>,
369    #[cfg_attr(feature = "openapi", schema(example = true))]
370    pub is_tax_line: bool,
371    pub tax_rate: Option<Decimal>,
372    pub tax_base_amount: Option<Decimal>,
373    pub related_line_id: Option<Uuid>,
374    #[cfg_attr(feature = "openapi", schema(example = true))]
375    pub has_quantity: bool,
376    pub quantity: Option<Decimal>,
377    pub unit: Option<String>,
378    pub unit_price: Option<Decimal>,
379    #[cfg_attr(feature = "openapi", schema(example = true))]
380    pub is_reconciled: bool,
381    pub reconciliation_id: Option<Uuid>,
382    pub reconciled_at: Option<DateTime<Utc>>,
383    pub full_reconcile_id: Option<Uuid>,
384    #[cfg_attr(feature = "openapi", schema(example = true))]
385    pub is_posted: bool,
386    pub ledger_id: Option<Uuid>,
387    pub posted_at: Option<DateTime<Utc>>,
388    pub tags: serde_json::Value,
389    pub data: serde_json::Value,
390}
391
392// =============================================================================
393// List Response DTO
394// =============================================================================
395
396/// Paginated list response for JournalLine entities
397///
398/// Used in API responses for list endpoints.
399/// Includes pagination metadata.
400#[derive(Debug, Clone, Serialize)]
401#[cfg_attr(feature = "openapi", derive(ToSchema))]
402#[serde(rename_all = "camelCase")]
403pub struct JournalLineListResponseDto {
404    /// List of items
405    pub items: Vec<JournalLineResponseDto>,
406    /// Total number of items (across all pages)
407    pub total: u64,
408    /// Current page number (1-indexed)
409    pub page: u32,
410    /// Number of items per page
411    pub per_page: u32,
412    /// Total number of pages
413    pub total_pages: u32,
414    /// Whether there is a next page
415    pub has_next: bool,
416    /// Whether there is a previous page
417    pub has_prev: bool,
418}
419
420impl JournalLineListResponseDto {
421    /// Create a new list response from items and pagination info
422    pub fn new(items: Vec<JournalLineResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
423        let total_pages = if per_page > 0 {
424            ((total as f64) / (per_page as f64)).ceil() as u32
425        } else {
426            0
427        };
428        Self {
429            items,
430            total,
431            page,
432            per_page,
433            total_pages,
434            has_next: page < total_pages,
435            has_prev: page > 1,
436        }
437    }
438}
439
440/// Summary DTO for list views (compact version)
441#[derive(Debug, Clone, Serialize)]
442#[cfg_attr(feature = "openapi", derive(ToSchema))]
443#[serde(rename_all = "camelCase")]
444pub struct JournalLineSummaryDto {
445    pub id: Uuid,
446    pub journal_id: Uuid,
447    pub branch_id: Option<Uuid>,
448    pub party_type: Option<PartyType>,
449    pub created_at: Option<DateTime<Utc>>,
450}
451
452// =============================================================================
453// Conversions
454// =============================================================================
455
456impl From<JournalLine> for JournalLineResponseDto {
457    fn from(entity: JournalLine) -> Self {
458        Self {
459            id: entity.id,
460            journal_id: entity.journal_id,
461            branch_id: entity.branch_id,
462            party_type: entity.party_type,
463            party_id: entity.party_id,
464            line_number: entity.line_number,
465            account_id: entity.account_id,
466            account_number: entity.account_number,
467            account_name: entity.account_name,
468            debit_amount: entity.debit_amount,
469            credit_amount: entity.credit_amount,
470            currency: entity.currency,
471            exchange_rate: entity.exchange_rate,
472            base_debit_amount: entity.base_debit_amount,
473            base_credit_amount: entity.base_credit_amount,
474            description: entity.description,
475            cost_center_id: entity.cost_center_id,
476            project_id: entity.project_id,
477            department_id: entity.department_id,
478            dimensions: entity.dimensions,
479            source_type: entity.source_type,
480            source_id: entity.source_id,
481            source_reference: entity.source_reference,
482            is_tax_line: entity.is_tax_line,
483            tax_rate: entity.tax_rate,
484            tax_base_amount: entity.tax_base_amount,
485            related_line_id: entity.related_line_id,
486            has_quantity: entity.has_quantity,
487            quantity: entity.quantity,
488            unit: entity.unit,
489            unit_price: entity.unit_price,
490            is_reconciled: entity.is_reconciled,
491            reconciliation_id: entity.reconciliation_id,
492            reconciled_at: entity.reconciled_at,
493            full_reconcile_id: entity.full_reconcile_id,
494            is_posted: entity.is_posted,
495            ledger_id: entity.ledger_id,
496            posted_at: entity.posted_at,
497            tags: entity.tags,
498            data: entity.data,
499        }
500    }
501}
502
503impl From<JournalLine> for JournalLineSummaryDto {
504    fn from(entity: JournalLine) -> Self {
505        let created_at = backbone_core::PersistentEntity::created_at(&entity);
506        Self {
507            id: entity.id,
508            journal_id: entity.journal_id,
509            branch_id: entity.branch_id,
510            party_type: entity.party_type,
511            created_at,
512        }
513    }
514}
515
516impl From<CreateJournalLineDto> for JournalLine {
517    fn from(dto: CreateJournalLineDto) -> Self {
518        Self {
519            id: Uuid::new_v4(),
520            journal_id: dto.journal_id,
521            branch_id: dto.branch_id,
522            party_type: dto.party_type,
523            party_id: dto.party_id,
524            line_number: dto.line_number,
525            account_id: dto.account_id,
526            account_number: dto.account_number,
527            account_name: dto.account_name,
528            debit_amount: dto.debit_amount,
529            credit_amount: dto.credit_amount,
530            currency: dto.currency,
531            exchange_rate: dto.exchange_rate,
532            base_debit_amount: dto.base_debit_amount,
533            base_credit_amount: dto.base_credit_amount,
534            description: dto.description,
535            cost_center_id: dto.cost_center_id,
536            project_id: dto.project_id,
537            department_id: dto.department_id,
538            dimensions: dto.dimensions,
539            source_type: dto.source_type,
540            source_id: dto.source_id,
541            source_reference: dto.source_reference,
542            is_tax_line: dto.is_tax_line,
543            tax_rate: dto.tax_rate,
544            tax_base_amount: dto.tax_base_amount,
545            related_line_id: dto.related_line_id,
546            has_quantity: dto.has_quantity,
547            quantity: dto.quantity,
548            unit: dto.unit,
549            unit_price: dto.unit_price,
550            is_reconciled: dto.is_reconciled,
551            reconciliation_id: dto.reconciliation_id,
552            reconciled_at: dto.reconciled_at,
553            full_reconcile_id: dto.full_reconcile_id,
554            is_posted: dto.is_posted,
555            ledger_id: dto.ledger_id,
556            posted_at: dto.posted_at,
557            tags: dto.tags,
558            data: dto.data,
559        }
560    }
561}
562
563impl From<&JournalLine> for JournalLineResponseDto {
564    fn from(entity: &JournalLine) -> Self {
565        Self {
566            id: entity.id.clone(),
567            journal_id: entity.journal_id.clone(),
568            branch_id: entity.branch_id.clone(),
569            party_type: entity.party_type.clone(),
570            party_id: entity.party_id.clone(),
571            line_number: entity.line_number.clone(),
572            account_id: entity.account_id.clone(),
573            account_number: entity.account_number.clone(),
574            account_name: entity.account_name.clone(),
575            debit_amount: entity.debit_amount.clone(),
576            credit_amount: entity.credit_amount.clone(),
577            currency: entity.currency.clone(),
578            exchange_rate: entity.exchange_rate.clone(),
579            base_debit_amount: entity.base_debit_amount.clone(),
580            base_credit_amount: entity.base_credit_amount.clone(),
581            description: entity.description.clone(),
582            cost_center_id: entity.cost_center_id.clone(),
583            project_id: entity.project_id.clone(),
584            department_id: entity.department_id.clone(),
585            dimensions: entity.dimensions.clone(),
586            source_type: entity.source_type.clone(),
587            source_id: entity.source_id.clone(),
588            source_reference: entity.source_reference.clone(),
589            is_tax_line: entity.is_tax_line.clone(),
590            tax_rate: entity.tax_rate.clone(),
591            tax_base_amount: entity.tax_base_amount.clone(),
592            related_line_id: entity.related_line_id.clone(),
593            has_quantity: entity.has_quantity.clone(),
594            quantity: entity.quantity.clone(),
595            unit: entity.unit.clone(),
596            unit_price: entity.unit_price.clone(),
597            is_reconciled: entity.is_reconciled.clone(),
598            reconciliation_id: entity.reconciliation_id.clone(),
599            reconciled_at: entity.reconciled_at.clone(),
600            full_reconcile_id: entity.full_reconcile_id.clone(),
601            is_posted: entity.is_posted.clone(),
602            ledger_id: entity.ledger_id.clone(),
603            posted_at: entity.posted_at.clone(),
604            tags: entity.tags.clone(),
605            data: entity.data.clone(),
606        }
607    }
608}
609
610impl backbone_core::FromCreateDto<CreateJournalLineDto> for JournalLine {
611    fn from_create_dto(dto: CreateJournalLineDto) -> backbone_core::ServiceResult<Self> {
612        Ok(JournalLine::from(dto))
613    }
614}
615
616impl backbone_core::ApplyUpdateDto<UpdateJournalLineDto> for JournalLine {
617    fn apply_update(mut self, dto: UpdateJournalLineDto) -> backbone_core::ServiceResult<Self> {
618        self.journal_id = dto.journal_id;
619        self.branch_id = dto.branch_id;
620        self.party_type = dto.party_type;
621        self.party_id = dto.party_id;
622        self.line_number = dto.line_number;
623        self.account_id = dto.account_id;
624        self.account_number = dto.account_number;
625        self.account_name = dto.account_name;
626        self.debit_amount = dto.debit_amount;
627        self.credit_amount = dto.credit_amount;
628        self.currency = dto.currency;
629        self.exchange_rate = dto.exchange_rate;
630        self.base_debit_amount = dto.base_debit_amount;
631        self.base_credit_amount = dto.base_credit_amount;
632        self.description = dto.description;
633        self.cost_center_id = dto.cost_center_id;
634        self.project_id = dto.project_id;
635        self.department_id = dto.department_id;
636        self.dimensions = dto.dimensions;
637        self.source_type = dto.source_type;
638        self.source_id = dto.source_id;
639        self.source_reference = dto.source_reference;
640        self.is_tax_line = dto.is_tax_line;
641        self.tax_rate = dto.tax_rate;
642        self.tax_base_amount = dto.tax_base_amount;
643        self.related_line_id = dto.related_line_id;
644        self.has_quantity = dto.has_quantity;
645        self.quantity = dto.quantity;
646        self.unit = dto.unit;
647        self.unit_price = dto.unit_price;
648        self.is_reconciled = dto.is_reconciled;
649        self.reconciliation_id = dto.reconciliation_id;
650        self.reconciled_at = dto.reconciled_at;
651        self.full_reconcile_id = dto.full_reconcile_id;
652        self.is_posted = dto.is_posted;
653        self.ledger_id = dto.ledger_id;
654        self.posted_at = dto.posted_at;
655        self.tags = dto.tags;
656        self.data = dto.data;
657        Ok(self)
658    }
659}
660
661// =============================================================================
662// Custom DTOs
663// =============================================================================
664
665// Add custom DTOs specific to JournalLine here.
666// This section will be preserved during regeneration.
667// >>> END CUSTOM DTOs
668