Skip to main content

backbone_accounting/domain/entity/
journal_line.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::PartyType;
8
9/// Strongly-typed ID for JournalLine
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct JournalLineId(pub Uuid);
13
14impl JournalLineId {
15    pub fn new(id: Uuid) -> Self { Self(id) }
16    pub fn generate() -> Self { Self(Uuid::new_v4()) }
17    pub fn into_inner(self) -> Uuid { self.0 }
18}
19
20impl std::fmt::Display for JournalLineId {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{}", self.0)
23    }
24}
25
26impl std::str::FromStr for JournalLineId {
27    type Err = uuid::Error;
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        Ok(Self(Uuid::parse_str(s)?))
30    }
31}
32
33impl From<Uuid> for JournalLineId {
34    fn from(id: Uuid) -> Self { Self(id) }
35}
36
37impl From<JournalLineId> for Uuid {
38    fn from(id: JournalLineId) -> Self { id.0 }
39}
40
41impl AsRef<Uuid> for JournalLineId {
42    fn as_ref(&self) -> &Uuid { &self.0 }
43}
44
45impl std::ops::Deref for JournalLineId {
46    type Target = Uuid;
47    fn deref(&self) -> &Self::Target { &self.0 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
51pub struct JournalLine {
52    pub id: Uuid,
53    pub journal_id: Uuid,
54    pub branch_id: Option<Uuid>,
55    pub party_type: Option<PartyType>,
56    pub party_id: Option<Uuid>,
57    pub line_number: i32,
58    pub account_id: Uuid,
59    pub account_number: String,
60    pub account_name: String,
61    pub debit_amount: Decimal,
62    pub credit_amount: Decimal,
63    pub currency: String,
64    pub exchange_rate: Decimal,
65    pub base_debit_amount: Decimal,
66    pub base_credit_amount: Decimal,
67    pub description: Option<String>,
68    pub cost_center_id: Option<Uuid>,
69    pub project_id: Option<Uuid>,
70    pub department_id: Option<Uuid>,
71    pub dimensions: Option<serde_json::Value>,
72    pub source_type: Option<String>,
73    pub source_id: Option<Uuid>,
74    pub source_reference: Option<String>,
75    pub is_tax_line: bool,
76    pub tax_rate: Option<Decimal>,
77    pub tax_base_amount: Option<Decimal>,
78    pub related_line_id: Option<Uuid>,
79    pub has_quantity: bool,
80    pub quantity: Option<Decimal>,
81    pub unit: Option<String>,
82    pub unit_price: Option<Decimal>,
83    pub is_reconciled: bool,
84    pub reconciliation_id: Option<Uuid>,
85    pub reconciled_at: Option<DateTime<Utc>>,
86    pub full_reconcile_id: Option<Uuid>,
87    pub is_posted: bool,
88    pub ledger_id: Option<Uuid>,
89    pub posted_at: Option<DateTime<Utc>>,
90    pub tags: serde_json::Value,
91    pub data: serde_json::Value,
92}
93
94impl JournalLine {
95    /// Create a builder for JournalLine
96    pub fn builder() -> JournalLineBuilder {
97        <JournalLineBuilder as Default>::default()
98    }
99
100    /// Create a new JournalLine with required fields
101    pub fn new(journal_id: Uuid, line_number: i32, account_id: Uuid, account_number: String, account_name: String, debit_amount: Decimal, credit_amount: Decimal, currency: String, exchange_rate: Decimal, base_debit_amount: Decimal, base_credit_amount: Decimal, is_tax_line: bool, has_quantity: bool, is_reconciled: bool, is_posted: bool, tags: serde_json::Value, data: serde_json::Value) -> Self {
102        Self {
103            id: Uuid::new_v4(),
104            journal_id,
105            branch_id: None,
106            party_type: None,
107            party_id: None,
108            line_number,
109            account_id,
110            account_number,
111            account_name,
112            debit_amount,
113            credit_amount,
114            currency,
115            exchange_rate,
116            base_debit_amount,
117            base_credit_amount,
118            description: None,
119            cost_center_id: None,
120            project_id: None,
121            department_id: None,
122            dimensions: None,
123            source_type: None,
124            source_id: None,
125            source_reference: None,
126            is_tax_line,
127            tax_rate: None,
128            tax_base_amount: None,
129            related_line_id: None,
130            has_quantity,
131            quantity: None,
132            unit: None,
133            unit_price: None,
134            is_reconciled,
135            reconciliation_id: None,
136            reconciled_at: None,
137            full_reconcile_id: None,
138            is_posted,
139            ledger_id: None,
140            posted_at: None,
141            tags,
142            data,
143        }
144    }
145
146    /// Get the entity's unique identifier
147    pub fn id(&self) -> &Uuid {
148        &self.id
149    }
150
151    /// Get a strongly-typed ID for this entity
152    pub fn typed_id(&self) -> JournalLineId {
153        JournalLineId(self.id)
154    }
155
156
157    // ==========================================================
158    // Fluent Setters (with_* for optional fields)
159    // ==========================================================
160
161    /// Set the branch_id field (chainable)
162    pub fn with_branch_id(mut self, value: Uuid) -> Self {
163        self.branch_id = Some(value);
164        self
165    }
166
167    /// Set the party_type field (chainable)
168    pub fn with_party_type(mut self, value: PartyType) -> Self {
169        self.party_type = Some(value);
170        self
171    }
172
173    /// Set the party_id field (chainable)
174    pub fn with_party_id(mut self, value: Uuid) -> Self {
175        self.party_id = Some(value);
176        self
177    }
178
179    /// Set the description field (chainable)
180    pub fn with_description(mut self, value: String) -> Self {
181        self.description = Some(value);
182        self
183    }
184
185    /// Set the cost_center_id field (chainable)
186    pub fn with_cost_center_id(mut self, value: Uuid) -> Self {
187        self.cost_center_id = Some(value);
188        self
189    }
190
191    /// Set the project_id field (chainable)
192    pub fn with_project_id(mut self, value: Uuid) -> Self {
193        self.project_id = Some(value);
194        self
195    }
196
197    /// Set the department_id field (chainable)
198    pub fn with_department_id(mut self, value: Uuid) -> Self {
199        self.department_id = Some(value);
200        self
201    }
202
203    /// Set the dimensions field (chainable)
204    pub fn with_dimensions(mut self, value: serde_json::Value) -> Self {
205        self.dimensions = Some(value);
206        self
207    }
208
209    /// Set the source_type field (chainable)
210    pub fn with_source_type(mut self, value: String) -> Self {
211        self.source_type = Some(value);
212        self
213    }
214
215    /// Set the source_id field (chainable)
216    pub fn with_source_id(mut self, value: Uuid) -> Self {
217        self.source_id = Some(value);
218        self
219    }
220
221    /// Set the source_reference field (chainable)
222    pub fn with_source_reference(mut self, value: String) -> Self {
223        self.source_reference = Some(value);
224        self
225    }
226
227    /// Set the tax_rate field (chainable)
228    pub fn with_tax_rate(mut self, value: Decimal) -> Self {
229        self.tax_rate = Some(value);
230        self
231    }
232
233    /// Set the tax_base_amount field (chainable)
234    pub fn with_tax_base_amount(mut self, value: Decimal) -> Self {
235        self.tax_base_amount = Some(value);
236        self
237    }
238
239    /// Set the related_line_id field (chainable)
240    pub fn with_related_line_id(mut self, value: Uuid) -> Self {
241        self.related_line_id = Some(value);
242        self
243    }
244
245    /// Set the quantity field (chainable)
246    pub fn with_quantity(mut self, value: Decimal) -> Self {
247        self.quantity = Some(value);
248        self
249    }
250
251    /// Set the unit field (chainable)
252    pub fn with_unit(mut self, value: String) -> Self {
253        self.unit = Some(value);
254        self
255    }
256
257    /// Set the unit_price field (chainable)
258    pub fn with_unit_price(mut self, value: Decimal) -> Self {
259        self.unit_price = Some(value);
260        self
261    }
262
263    /// Set the reconciliation_id field (chainable)
264    pub fn with_reconciliation_id(mut self, value: Uuid) -> Self {
265        self.reconciliation_id = Some(value);
266        self
267    }
268
269    /// Set the reconciled_at field (chainable)
270    pub fn with_reconciled_at(mut self, value: DateTime<Utc>) -> Self {
271        self.reconciled_at = Some(value);
272        self
273    }
274
275    /// Set the full_reconcile_id field (chainable)
276    pub fn with_full_reconcile_id(mut self, value: Uuid) -> Self {
277        self.full_reconcile_id = Some(value);
278        self
279    }
280
281    /// Set the ledger_id field (chainable)
282    pub fn with_ledger_id(mut self, value: Uuid) -> Self {
283        self.ledger_id = Some(value);
284        self
285    }
286
287    /// Set the posted_at field (chainable)
288    pub fn with_posted_at(mut self, value: DateTime<Utc>) -> Self {
289        self.posted_at = Some(value);
290        self
291    }
292
293    // ==========================================================
294    // Partial Update
295    // ==========================================================
296
297    /// Apply partial updates from a map of field name to JSON value
298    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
299        for (key, value) in fields {
300            match key.as_str() {
301                "journal_id" => {
302                    if let Ok(v) = serde_json::from_value(value) { self.journal_id = v; }
303                }
304                "branch_id" => {
305                    if let Ok(v) = serde_json::from_value(value) { self.branch_id = v; }
306                }
307                "party_type" => {
308                    if let Ok(v) = serde_json::from_value(value) { self.party_type = v; }
309                }
310                "party_id" => {
311                    if let Ok(v) = serde_json::from_value(value) { self.party_id = v; }
312                }
313                "line_number" => {
314                    if let Ok(v) = serde_json::from_value(value) { self.line_number = v; }
315                }
316                "account_id" => {
317                    if let Ok(v) = serde_json::from_value(value) { self.account_id = v; }
318                }
319                "account_number" => {
320                    if let Ok(v) = serde_json::from_value(value) { self.account_number = v; }
321                }
322                "account_name" => {
323                    if let Ok(v) = serde_json::from_value(value) { self.account_name = v; }
324                }
325                "debit_amount" => {
326                    if let Ok(v) = serde_json::from_value(value) { self.debit_amount = v; }
327                }
328                "credit_amount" => {
329                    if let Ok(v) = serde_json::from_value(value) { self.credit_amount = v; }
330                }
331                "currency" => {
332                    if let Ok(v) = serde_json::from_value(value) { self.currency = v; }
333                }
334                "exchange_rate" => {
335                    if let Ok(v) = serde_json::from_value(value) { self.exchange_rate = v; }
336                }
337                "base_debit_amount" => {
338                    if let Ok(v) = serde_json::from_value(value) { self.base_debit_amount = v; }
339                }
340                "base_credit_amount" => {
341                    if let Ok(v) = serde_json::from_value(value) { self.base_credit_amount = v; }
342                }
343                "description" => {
344                    if let Ok(v) = serde_json::from_value(value) { self.description = v; }
345                }
346                "cost_center_id" => {
347                    if let Ok(v) = serde_json::from_value(value) { self.cost_center_id = v; }
348                }
349                "project_id" => {
350                    if let Ok(v) = serde_json::from_value(value) { self.project_id = v; }
351                }
352                "department_id" => {
353                    if let Ok(v) = serde_json::from_value(value) { self.department_id = v; }
354                }
355                "dimensions" => {
356                    if let Ok(v) = serde_json::from_value(value) { self.dimensions = v; }
357                }
358                "source_type" => {
359                    if let Ok(v) = serde_json::from_value(value) { self.source_type = v; }
360                }
361                "source_id" => {
362                    if let Ok(v) = serde_json::from_value(value) { self.source_id = v; }
363                }
364                "source_reference" => {
365                    if let Ok(v) = serde_json::from_value(value) { self.source_reference = v; }
366                }
367                "is_tax_line" => {
368                    if let Ok(v) = serde_json::from_value(value) { self.is_tax_line = v; }
369                }
370                "tax_rate" => {
371                    if let Ok(v) = serde_json::from_value(value) { self.tax_rate = v; }
372                }
373                "tax_base_amount" => {
374                    if let Ok(v) = serde_json::from_value(value) { self.tax_base_amount = v; }
375                }
376                "related_line_id" => {
377                    if let Ok(v) = serde_json::from_value(value) { self.related_line_id = v; }
378                }
379                "has_quantity" => {
380                    if let Ok(v) = serde_json::from_value(value) { self.has_quantity = v; }
381                }
382                "quantity" => {
383                    if let Ok(v) = serde_json::from_value(value) { self.quantity = v; }
384                }
385                "unit" => {
386                    if let Ok(v) = serde_json::from_value(value) { self.unit = v; }
387                }
388                "unit_price" => {
389                    if let Ok(v) = serde_json::from_value(value) { self.unit_price = v; }
390                }
391                "is_reconciled" => {
392                    if let Ok(v) = serde_json::from_value(value) { self.is_reconciled = v; }
393                }
394                "reconciliation_id" => {
395                    if let Ok(v) = serde_json::from_value(value) { self.reconciliation_id = v; }
396                }
397                "reconciled_at" => {
398                    if let Ok(v) = serde_json::from_value(value) { self.reconciled_at = v; }
399                }
400                "full_reconcile_id" => {
401                    if let Ok(v) = serde_json::from_value(value) { self.full_reconcile_id = v; }
402                }
403                "is_posted" => {
404                    if let Ok(v) = serde_json::from_value(value) { self.is_posted = v; }
405                }
406                "ledger_id" => {
407                    if let Ok(v) = serde_json::from_value(value) { self.ledger_id = v; }
408                }
409                "posted_at" => {
410                    if let Ok(v) = serde_json::from_value(value) { self.posted_at = v; }
411                }
412                "tags" => {
413                    if let Ok(v) = serde_json::from_value(value) { self.tags = v; }
414                }
415                "data" => {
416                    if let Ok(v) = serde_json::from_value(value) { self.data = v; }
417                }
418                _ => {} // ignore unknown fields
419            }
420        }
421    }
422
423}
424
425impl super::Entity for JournalLine {
426    type Id = Uuid;
427
428    fn entity_id(&self) -> &Self::Id {
429        &self.id
430    }
431
432    fn entity_type() -> &'static str {
433        "JournalLine"
434    }
435}
436
437impl backbone_core::PersistentEntity for JournalLine {
438    fn entity_id(&self) -> String {
439        self.id.to_string()
440    }
441    fn set_entity_id(&mut self, id: String) {
442        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
443            self.id = uuid;
444        }
445    }
446    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
447        None
448    }
449    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
450        let _ = ts;
451    }
452    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
453        None
454    }
455    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
456        let _ = ts;
457    }
458    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
459        None
460    }
461    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
462        let _ = ts;
463    }
464}
465
466impl backbone_orm::EntityRepoMeta for JournalLine {
467    fn column_types() -> std::collections::HashMap<String, String> {
468        let mut m = std::collections::HashMap::new();
469        m.insert("id".to_string(), "uuid".to_string());
470        m.insert("journal_id".to_string(), "uuid".to_string());
471        m.insert("branch_id".to_string(), "uuid".to_string());
472        m.insert("party_id".to_string(), "uuid".to_string());
473        m.insert("account_id".to_string(), "uuid".to_string());
474        m.insert("cost_center_id".to_string(), "uuid".to_string());
475        m.insert("project_id".to_string(), "uuid".to_string());
476        m.insert("department_id".to_string(), "uuid".to_string());
477        m.insert("source_id".to_string(), "uuid".to_string());
478        m.insert("related_line_id".to_string(), "uuid".to_string());
479        m.insert("reconciliation_id".to_string(), "uuid".to_string());
480        m.insert("full_reconcile_id".to_string(), "uuid".to_string());
481        m.insert("ledger_id".to_string(), "uuid".to_string());
482        m.insert("party_type".to_string(), "party_type".to_string());
483        m.insert("reconciled_at".to_string(), "timestamptz".to_string());
484        m.insert("posted_at".to_string(), "timestamptz".to_string());
485        m
486    }
487    fn search_fields() -> &'static [&'static str] {
488        &["account_number", "account_name", "currency"]
489    }
490    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
491        &[("journal", "journals", "journalId"), ("account", "accounts", "accountId"), ("relatedLine", "journal_lines", "relatedLineId"), ("reconciliation", "reconciliations", "reconciliationId"), ("fullReconcile", "full_reconciles", "fullReconcileId"), ("ledger", "ledgers", "ledgerId"), ("costCenter", "cost_centers", "costCenterId")]
492    }
493}
494
495/// Builder for JournalLine entity
496///
497/// Provides a fluent API for constructing JournalLine instances.
498/// System fields (id, metadata, timestamps) are auto-initialized.
499#[derive(Debug, Clone, Default)]
500pub struct JournalLineBuilder {
501    journal_id: Option<Uuid>,
502    branch_id: Option<Uuid>,
503    party_type: Option<PartyType>,
504    party_id: Option<Uuid>,
505    line_number: Option<i32>,
506    account_id: Option<Uuid>,
507    account_number: Option<String>,
508    account_name: Option<String>,
509    debit_amount: Option<Decimal>,
510    credit_amount: Option<Decimal>,
511    currency: Option<String>,
512    exchange_rate: Option<Decimal>,
513    base_debit_amount: Option<Decimal>,
514    base_credit_amount: Option<Decimal>,
515    description: Option<String>,
516    cost_center_id: Option<Uuid>,
517    project_id: Option<Uuid>,
518    department_id: Option<Uuid>,
519    dimensions: Option<serde_json::Value>,
520    source_type: Option<String>,
521    source_id: Option<Uuid>,
522    source_reference: Option<String>,
523    is_tax_line: Option<bool>,
524    tax_rate: Option<Decimal>,
525    tax_base_amount: Option<Decimal>,
526    related_line_id: Option<Uuid>,
527    has_quantity: Option<bool>,
528    quantity: Option<Decimal>,
529    unit: Option<String>,
530    unit_price: Option<Decimal>,
531    is_reconciled: Option<bool>,
532    reconciliation_id: Option<Uuid>,
533    reconciled_at: Option<DateTime<Utc>>,
534    full_reconcile_id: Option<Uuid>,
535    is_posted: Option<bool>,
536    ledger_id: Option<Uuid>,
537    posted_at: Option<DateTime<Utc>>,
538    tags: Option<serde_json::Value>,
539    data: Option<serde_json::Value>,
540}
541
542impl JournalLineBuilder {
543    /// Set the journal_id field (required)
544    pub fn journal_id(mut self, value: Uuid) -> Self {
545        self.journal_id = Some(value);
546        self
547    }
548
549    /// Set the branch_id field (optional)
550    pub fn branch_id(mut self, value: Uuid) -> Self {
551        self.branch_id = Some(value);
552        self
553    }
554
555    /// Set the party_type field (optional)
556    pub fn party_type(mut self, value: PartyType) -> Self {
557        self.party_type = Some(value);
558        self
559    }
560
561    /// Set the party_id field (optional)
562    pub fn party_id(mut self, value: Uuid) -> Self {
563        self.party_id = Some(value);
564        self
565    }
566
567    /// Set the line_number field (required)
568    pub fn line_number(mut self, value: i32) -> Self {
569        self.line_number = Some(value);
570        self
571    }
572
573    /// Set the account_id field (required)
574    pub fn account_id(mut self, value: Uuid) -> Self {
575        self.account_id = Some(value);
576        self
577    }
578
579    /// Set the account_number field (required)
580    pub fn account_number(mut self, value: String) -> Self {
581        self.account_number = Some(value);
582        self
583    }
584
585    /// Set the account_name field (required)
586    pub fn account_name(mut self, value: String) -> Self {
587        self.account_name = Some(value);
588        self
589    }
590
591    /// Set the debit_amount field (default: `Decimal::from(0)`)
592    pub fn debit_amount(mut self, value: Decimal) -> Self {
593        self.debit_amount = Some(value);
594        self
595    }
596
597    /// Set the credit_amount field (default: `Decimal::from(0)`)
598    pub fn credit_amount(mut self, value: Decimal) -> Self {
599        self.credit_amount = Some(value);
600        self
601    }
602
603    /// Set the currency field (default: `"IDR".to_string()`)
604    pub fn currency(mut self, value: String) -> Self {
605        self.currency = Some(value);
606        self
607    }
608
609    /// Set the exchange_rate field (default: `Decimal::from(1)`)
610    pub fn exchange_rate(mut self, value: Decimal) -> Self {
611        self.exchange_rate = Some(value);
612        self
613    }
614
615    /// Set the base_debit_amount field (default: `Decimal::from(0)`)
616    pub fn base_debit_amount(mut self, value: Decimal) -> Self {
617        self.base_debit_amount = Some(value);
618        self
619    }
620
621    /// Set the base_credit_amount field (default: `Decimal::from(0)`)
622    pub fn base_credit_amount(mut self, value: Decimal) -> Self {
623        self.base_credit_amount = Some(value);
624        self
625    }
626
627    /// Set the description field (optional)
628    pub fn description(mut self, value: String) -> Self {
629        self.description = Some(value);
630        self
631    }
632
633    /// Set the cost_center_id field (optional)
634    pub fn cost_center_id(mut self, value: Uuid) -> Self {
635        self.cost_center_id = Some(value);
636        self
637    }
638
639    /// Set the project_id field (optional)
640    pub fn project_id(mut self, value: Uuid) -> Self {
641        self.project_id = Some(value);
642        self
643    }
644
645    /// Set the department_id field (optional)
646    pub fn department_id(mut self, value: Uuid) -> Self {
647        self.department_id = Some(value);
648        self
649    }
650
651    /// Set the dimensions field (optional)
652    pub fn dimensions(mut self, value: serde_json::Value) -> Self {
653        self.dimensions = Some(value);
654        self
655    }
656
657    /// Set the source_type field (optional)
658    pub fn source_type(mut self, value: String) -> Self {
659        self.source_type = Some(value);
660        self
661    }
662
663    /// Set the source_id field (optional)
664    pub fn source_id(mut self, value: Uuid) -> Self {
665        self.source_id = Some(value);
666        self
667    }
668
669    /// Set the source_reference field (optional)
670    pub fn source_reference(mut self, value: String) -> Self {
671        self.source_reference = Some(value);
672        self
673    }
674
675    /// Set the is_tax_line field (default: `false`)
676    pub fn is_tax_line(mut self, value: bool) -> Self {
677        self.is_tax_line = Some(value);
678        self
679    }
680
681    /// Set the tax_rate field (optional)
682    pub fn tax_rate(mut self, value: Decimal) -> Self {
683        self.tax_rate = Some(value);
684        self
685    }
686
687    /// Set the tax_base_amount field (optional)
688    pub fn tax_base_amount(mut self, value: Decimal) -> Self {
689        self.tax_base_amount = Some(value);
690        self
691    }
692
693    /// Set the related_line_id field (optional)
694    pub fn related_line_id(mut self, value: Uuid) -> Self {
695        self.related_line_id = Some(value);
696        self
697    }
698
699    /// Set the has_quantity field (default: `false`)
700    pub fn has_quantity(mut self, value: bool) -> Self {
701        self.has_quantity = Some(value);
702        self
703    }
704
705    /// Set the quantity field (optional)
706    pub fn quantity(mut self, value: Decimal) -> Self {
707        self.quantity = Some(value);
708        self
709    }
710
711    /// Set the unit field (optional)
712    pub fn unit(mut self, value: String) -> Self {
713        self.unit = Some(value);
714        self
715    }
716
717    /// Set the unit_price field (optional)
718    pub fn unit_price(mut self, value: Decimal) -> Self {
719        self.unit_price = Some(value);
720        self
721    }
722
723    /// Set the is_reconciled field (default: `false`)
724    pub fn is_reconciled(mut self, value: bool) -> Self {
725        self.is_reconciled = Some(value);
726        self
727    }
728
729    /// Set the reconciliation_id field (optional)
730    pub fn reconciliation_id(mut self, value: Uuid) -> Self {
731        self.reconciliation_id = Some(value);
732        self
733    }
734
735    /// Set the reconciled_at field (optional)
736    pub fn reconciled_at(mut self, value: DateTime<Utc>) -> Self {
737        self.reconciled_at = Some(value);
738        self
739    }
740
741    /// Set the full_reconcile_id field (optional)
742    pub fn full_reconcile_id(mut self, value: Uuid) -> Self {
743        self.full_reconcile_id = Some(value);
744        self
745    }
746
747    /// Set the is_posted field (default: `false`)
748    pub fn is_posted(mut self, value: bool) -> Self {
749        self.is_posted = Some(value);
750        self
751    }
752
753    /// Set the ledger_id field (optional)
754    pub fn ledger_id(mut self, value: Uuid) -> Self {
755        self.ledger_id = Some(value);
756        self
757    }
758
759    /// Set the posted_at field (optional)
760    pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
761        self.posted_at = Some(value);
762        self
763    }
764
765    /// Set the tags field (default: `serde_json::json!([])`)
766    pub fn tags(mut self, value: serde_json::Value) -> Self {
767        self.tags = Some(value);
768        self
769    }
770
771    /// Set the data field (default: `serde_json::json!({})`)
772    pub fn data(mut self, value: serde_json::Value) -> Self {
773        self.data = Some(value);
774        self
775    }
776
777    /// Build the JournalLine entity
778    ///
779    /// Returns Err if any required field without a default is missing.
780    pub fn build(self) -> Result<JournalLine, String> {
781        let journal_id = self.journal_id.ok_or_else(|| "journal_id is required".to_string())?;
782        let line_number = self.line_number.ok_or_else(|| "line_number is required".to_string())?;
783        let account_id = self.account_id.ok_or_else(|| "account_id is required".to_string())?;
784        let account_number = self.account_number.ok_or_else(|| "account_number is required".to_string())?;
785        let account_name = self.account_name.ok_or_else(|| "account_name is required".to_string())?;
786
787        Ok(JournalLine {
788            id: Uuid::new_v4(),
789            journal_id,
790            branch_id: self.branch_id,
791            party_type: self.party_type,
792            party_id: self.party_id,
793            line_number,
794            account_id,
795            account_number,
796            account_name,
797            debit_amount: self.debit_amount.unwrap_or(Decimal::from(0)),
798            credit_amount: self.credit_amount.unwrap_or(Decimal::from(0)),
799            currency: self.currency.unwrap_or("IDR".to_string()),
800            exchange_rate: self.exchange_rate.unwrap_or(Decimal::from(1)),
801            base_debit_amount: self.base_debit_amount.unwrap_or(Decimal::from(0)),
802            base_credit_amount: self.base_credit_amount.unwrap_or(Decimal::from(0)),
803            description: self.description,
804            cost_center_id: self.cost_center_id,
805            project_id: self.project_id,
806            department_id: self.department_id,
807            dimensions: self.dimensions,
808            source_type: self.source_type,
809            source_id: self.source_id,
810            source_reference: self.source_reference,
811            is_tax_line: self.is_tax_line.unwrap_or(false),
812            tax_rate: self.tax_rate,
813            tax_base_amount: self.tax_base_amount,
814            related_line_id: self.related_line_id,
815            has_quantity: self.has_quantity.unwrap_or(false),
816            quantity: self.quantity,
817            unit: self.unit,
818            unit_price: self.unit_price,
819            is_reconciled: self.is_reconciled.unwrap_or(false),
820            reconciliation_id: self.reconciliation_id,
821            reconciled_at: self.reconciled_at,
822            full_reconcile_id: self.full_reconcile_id,
823            is_posted: self.is_posted.unwrap_or(false),
824            ledger_id: self.ledger_id,
825            posted_at: self.posted_at,
826            tags: self.tags.unwrap_or(serde_json::json!([])),
827            data: self.data.unwrap_or(serde_json::json!({})),
828        })
829    }
830}