Skip to main content

backbone_accounting/domain/entity/
journal.rs

1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::JournalType;
8use super::JournalSource;
9use super::JournalStatus;
10use super::AuditMetadata;
11
12/// Strongly-typed ID for Journal
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct JournalId(pub Uuid);
16
17impl JournalId {
18    pub fn new(id: Uuid) -> Self { Self(id) }
19    pub fn generate() -> Self { Self(Uuid::new_v4()) }
20    pub fn into_inner(self) -> Uuid { self.0 }
21}
22
23impl std::fmt::Display for JournalId {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29impl std::str::FromStr for JournalId {
30    type Err = uuid::Error;
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        Ok(Self(Uuid::parse_str(s)?))
33    }
34}
35
36impl From<Uuid> for JournalId {
37    fn from(id: Uuid) -> Self { Self(id) }
38}
39
40impl From<JournalId> for Uuid {
41    fn from(id: JournalId) -> Self { id.0 }
42}
43
44impl AsRef<Uuid> for JournalId {
45    fn as_ref(&self) -> &Uuid { &self.0 }
46}
47
48impl std::ops::Deref for JournalId {
49    type Target = Uuid;
50    fn deref(&self) -> &Self::Target { &self.0 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
54pub struct Journal {
55    pub id: Uuid,
56    pub journal_number: String,
57    pub reference_number: Option<String>,
58    pub journal_type: JournalType,
59    pub branch_id: Option<Uuid>,
60    pub transaction_date: NaiveDate,
61    pub posting_date: Option<NaiveDate>,
62    pub fiscal_period_id: Option<Uuid>,
63    pub fiscal_year: Option<i32>,
64    pub fiscal_month: Option<i32>,
65    pub description: String,
66    pub notes: Option<String>,
67    pub currency: String,
68    pub total_debit: Decimal,
69    pub total_credit: Decimal,
70    pub line_count: i32,
71    pub source: JournalSource,
72    pub source_type: Option<String>,
73    pub source_id: Option<Uuid>,
74    pub source_reference: Option<String>,
75    pub is_reversed: bool,
76    pub reversed_by_id: Option<Uuid>,
77    pub reversed_at: Option<DateTime<Utc>>,
78    pub reversal_reason: Option<String>,
79    pub reverses_id: Option<Uuid>,
80    pub is_reversing: bool,
81    pub auto_reverse: bool,
82    pub auto_reverse_date: Option<NaiveDate>,
83    pub status: JournalStatus,
84    pub requires_approval: bool,
85    pub approval_threshold: Option<Decimal>,
86    pub submitted_at: Option<DateTime<Utc>>,
87    pub submitted_by: Option<Uuid>,
88    pub approved_at: Option<DateTime<Utc>>,
89    pub approved_by: Option<Uuid>,
90    pub rejected_at: Option<DateTime<Utc>>,
91    pub rejected_by: Option<Uuid>,
92    pub rejection_reason: Option<String>,
93    pub posted_at: Option<DateTime<Utc>>,
94    pub posted_by: Option<Uuid>,
95    pub is_voided: bool,
96    pub voided_at: Option<DateTime<Utc>>,
97    pub voided_by: Option<Uuid>,
98    pub void_reason: Option<String>,
99    pub attachments: serde_json::Value,
100    #[serde(default)]
101    #[sqlx(json)]
102    pub metadata: AuditMetadata,
103}
104
105impl Journal {
106    /// Create a builder for Journal
107    pub fn builder() -> JournalBuilder {
108        <JournalBuilder as Default>::default()
109    }
110
111    /// Create a new Journal with required fields
112    pub fn new(journal_number: String, journal_type: JournalType, transaction_date: NaiveDate, description: String, currency: String, total_debit: Decimal, total_credit: Decimal, line_count: i32, source: JournalSource, is_reversed: bool, is_reversing: bool, auto_reverse: bool, status: JournalStatus, requires_approval: bool, is_voided: bool, attachments: serde_json::Value) -> Self {
113        Self {
114            id: Uuid::new_v4(),
115            journal_number,
116            reference_number: None,
117            journal_type,
118            branch_id: None,
119            transaction_date,
120            posting_date: None,
121            fiscal_period_id: None,
122            fiscal_year: None,
123            fiscal_month: None,
124            description,
125            notes: None,
126            currency,
127            total_debit,
128            total_credit,
129            line_count,
130            source,
131            source_type: None,
132            source_id: None,
133            source_reference: None,
134            is_reversed,
135            reversed_by_id: None,
136            reversed_at: None,
137            reversal_reason: None,
138            reverses_id: None,
139            is_reversing,
140            auto_reverse,
141            auto_reverse_date: None,
142            status,
143            requires_approval,
144            approval_threshold: None,
145            submitted_at: None,
146            submitted_by: None,
147            approved_at: None,
148            approved_by: None,
149            rejected_at: None,
150            rejected_by: None,
151            rejection_reason: None,
152            posted_at: None,
153            posted_by: None,
154            is_voided,
155            voided_at: None,
156            voided_by: None,
157            void_reason: None,
158            attachments,
159            metadata: AuditMetadata::default(),
160        }
161    }
162
163    /// Get the entity's unique identifier
164    pub fn id(&self) -> &Uuid {
165        &self.id
166    }
167
168    /// Get a strongly-typed ID for this entity
169    pub fn typed_id(&self) -> JournalId {
170        JournalId(self.id)
171    }
172
173    /// Get when this entity was created
174    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
175        self.metadata.created_at.as_ref()
176    }
177
178    /// Get when this entity was last updated
179    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
180        self.metadata.updated_at.as_ref()
181    }
182
183    /// Check if this entity is soft deleted
184    pub fn is_deleted(&self) -> bool {
185        self.metadata.deleted_at.is_some()
186    }
187
188    /// Check if this entity is active (not deleted)
189    pub fn is_active(&self) -> bool {
190        self.metadata.deleted_at.is_none()
191    }
192
193    /// Get when this entity was deleted
194    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
195        self.metadata.deleted_at.as_ref()
196    }
197
198    /// Get who created this entity
199    pub fn created_by(&self) -> Option<&Uuid> {
200        self.metadata.created_by.as_ref()
201    }
202
203    /// Get who last updated this entity
204    pub fn updated_by(&self) -> Option<&Uuid> {
205        self.metadata.updated_by.as_ref()
206    }
207
208    /// Get who deleted this entity
209    pub fn deleted_by(&self) -> Option<&Uuid> {
210        self.metadata.deleted_by.as_ref()
211    }
212
213    /// Get the current status
214    pub fn status(&self) -> &JournalStatus {
215        &self.status
216    }
217
218
219    // ==========================================================
220    // Fluent Setters (with_* for optional fields)
221    // ==========================================================
222
223    /// Set the reference_number field (chainable)
224    pub fn with_reference_number(mut self, value: String) -> Self {
225        self.reference_number = Some(value);
226        self
227    }
228
229    /// Set the branch_id field (chainable)
230    pub fn with_branch_id(mut self, value: Uuid) -> Self {
231        self.branch_id = Some(value);
232        self
233    }
234
235    /// Set the posting_date field (chainable)
236    pub fn with_posting_date(mut self, value: NaiveDate) -> Self {
237        self.posting_date = Some(value);
238        self
239    }
240
241    /// Set the fiscal_period_id field (chainable)
242    pub fn with_fiscal_period_id(mut self, value: Uuid) -> Self {
243        self.fiscal_period_id = Some(value);
244        self
245    }
246
247    /// Set the fiscal_year field (chainable)
248    pub fn with_fiscal_year(mut self, value: i32) -> Self {
249        self.fiscal_year = Some(value);
250        self
251    }
252
253    /// Set the fiscal_month field (chainable)
254    pub fn with_fiscal_month(mut self, value: i32) -> Self {
255        self.fiscal_month = Some(value);
256        self
257    }
258
259    /// Set the notes field (chainable)
260    pub fn with_notes(mut self, value: String) -> Self {
261        self.notes = Some(value);
262        self
263    }
264
265    /// Set the source_type field (chainable)
266    pub fn with_source_type(mut self, value: String) -> Self {
267        self.source_type = Some(value);
268        self
269    }
270
271    /// Set the source_id field (chainable)
272    pub fn with_source_id(mut self, value: Uuid) -> Self {
273        self.source_id = Some(value);
274        self
275    }
276
277    /// Set the source_reference field (chainable)
278    pub fn with_source_reference(mut self, value: String) -> Self {
279        self.source_reference = Some(value);
280        self
281    }
282
283    /// Set the reversed_by_id field (chainable)
284    pub fn with_reversed_by_id(mut self, value: Uuid) -> Self {
285        self.reversed_by_id = Some(value);
286        self
287    }
288
289    /// Set the reversed_at field (chainable)
290    pub fn with_reversed_at(mut self, value: DateTime<Utc>) -> Self {
291        self.reversed_at = Some(value);
292        self
293    }
294
295    /// Set the reversal_reason field (chainable)
296    pub fn with_reversal_reason(mut self, value: String) -> Self {
297        self.reversal_reason = Some(value);
298        self
299    }
300
301    /// Set the reverses_id field (chainable)
302    pub fn with_reverses_id(mut self, value: Uuid) -> Self {
303        self.reverses_id = Some(value);
304        self
305    }
306
307    /// Set the auto_reverse_date field (chainable)
308    pub fn with_auto_reverse_date(mut self, value: NaiveDate) -> Self {
309        self.auto_reverse_date = Some(value);
310        self
311    }
312
313    /// Set the approval_threshold field (chainable)
314    pub fn with_approval_threshold(mut self, value: Decimal) -> Self {
315        self.approval_threshold = Some(value);
316        self
317    }
318
319    /// Set the submitted_at field (chainable)
320    pub fn with_submitted_at(mut self, value: DateTime<Utc>) -> Self {
321        self.submitted_at = Some(value);
322        self
323    }
324
325    /// Set the submitted_by field (chainable)
326    pub fn with_submitted_by(mut self, value: Uuid) -> Self {
327        self.submitted_by = Some(value);
328        self
329    }
330
331    /// Set the approved_at field (chainable)
332    pub fn with_approved_at(mut self, value: DateTime<Utc>) -> Self {
333        self.approved_at = Some(value);
334        self
335    }
336
337    /// Set the approved_by field (chainable)
338    pub fn with_approved_by(mut self, value: Uuid) -> Self {
339        self.approved_by = Some(value);
340        self
341    }
342
343    /// Set the rejected_at field (chainable)
344    pub fn with_rejected_at(mut self, value: DateTime<Utc>) -> Self {
345        self.rejected_at = Some(value);
346        self
347    }
348
349    /// Set the rejected_by field (chainable)
350    pub fn with_rejected_by(mut self, value: Uuid) -> Self {
351        self.rejected_by = Some(value);
352        self
353    }
354
355    /// Set the rejection_reason field (chainable)
356    pub fn with_rejection_reason(mut self, value: String) -> Self {
357        self.rejection_reason = Some(value);
358        self
359    }
360
361    /// Set the posted_at field (chainable)
362    pub fn with_posted_at(mut self, value: DateTime<Utc>) -> Self {
363        self.posted_at = Some(value);
364        self
365    }
366
367    /// Set the posted_by field (chainable)
368    pub fn with_posted_by(mut self, value: Uuid) -> Self {
369        self.posted_by = Some(value);
370        self
371    }
372
373    /// Set the voided_at field (chainable)
374    pub fn with_voided_at(mut self, value: DateTime<Utc>) -> Self {
375        self.voided_at = Some(value);
376        self
377    }
378
379    /// Set the voided_by field (chainable)
380    pub fn with_voided_by(mut self, value: Uuid) -> Self {
381        self.voided_by = Some(value);
382        self
383    }
384
385    /// Set the void_reason field (chainable)
386    pub fn with_void_reason(mut self, value: String) -> Self {
387        self.void_reason = Some(value);
388        self
389    }
390
391    // ==========================================================
392    // Partial Update
393    // ==========================================================
394
395    /// Apply partial updates from a map of field name to JSON value
396    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
397        for (key, value) in fields {
398            match key.as_str() {
399                "journal_number" => {
400                    if let Ok(v) = serde_json::from_value(value) { self.journal_number = v; }
401                }
402                "reference_number" => {
403                    if let Ok(v) = serde_json::from_value(value) { self.reference_number = v; }
404                }
405                "journal_type" => {
406                    if let Ok(v) = serde_json::from_value(value) { self.journal_type = v; }
407                }
408                "branch_id" => {
409                    if let Ok(v) = serde_json::from_value(value) { self.branch_id = v; }
410                }
411                "transaction_date" => {
412                    if let Ok(v) = serde_json::from_value(value) { self.transaction_date = v; }
413                }
414                "posting_date" => {
415                    if let Ok(v) = serde_json::from_value(value) { self.posting_date = v; }
416                }
417                "fiscal_period_id" => {
418                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_period_id = v; }
419                }
420                "fiscal_year" => {
421                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_year = v; }
422                }
423                "fiscal_month" => {
424                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_month = v; }
425                }
426                "description" => {
427                    if let Ok(v) = serde_json::from_value(value) { self.description = v; }
428                }
429                "notes" => {
430                    if let Ok(v) = serde_json::from_value(value) { self.notes = v; }
431                }
432                "currency" => {
433                    if let Ok(v) = serde_json::from_value(value) { self.currency = v; }
434                }
435                "total_debit" => {
436                    if let Ok(v) = serde_json::from_value(value) { self.total_debit = v; }
437                }
438                "total_credit" => {
439                    if let Ok(v) = serde_json::from_value(value) { self.total_credit = v; }
440                }
441                "line_count" => {
442                    if let Ok(v) = serde_json::from_value(value) { self.line_count = v; }
443                }
444                "source" => {
445                    if let Ok(v) = serde_json::from_value(value) { self.source = v; }
446                }
447                "source_type" => {
448                    if let Ok(v) = serde_json::from_value(value) { self.source_type = v; }
449                }
450                "source_id" => {
451                    if let Ok(v) = serde_json::from_value(value) { self.source_id = v; }
452                }
453                "source_reference" => {
454                    if let Ok(v) = serde_json::from_value(value) { self.source_reference = v; }
455                }
456                "is_reversed" => {
457                    if let Ok(v) = serde_json::from_value(value) { self.is_reversed = v; }
458                }
459                "reversed_by_id" => {
460                    if let Ok(v) = serde_json::from_value(value) { self.reversed_by_id = v; }
461                }
462                "reversed_at" => {
463                    if let Ok(v) = serde_json::from_value(value) { self.reversed_at = v; }
464                }
465                "reversal_reason" => {
466                    if let Ok(v) = serde_json::from_value(value) { self.reversal_reason = v; }
467                }
468                "reverses_id" => {
469                    if let Ok(v) = serde_json::from_value(value) { self.reverses_id = v; }
470                }
471                "is_reversing" => {
472                    if let Ok(v) = serde_json::from_value(value) { self.is_reversing = v; }
473                }
474                "auto_reverse" => {
475                    if let Ok(v) = serde_json::from_value(value) { self.auto_reverse = v; }
476                }
477                "auto_reverse_date" => {
478                    if let Ok(v) = serde_json::from_value(value) { self.auto_reverse_date = v; }
479                }
480                "status" => {
481                    if let Ok(v) = serde_json::from_value(value) { self.status = v; }
482                }
483                "requires_approval" => {
484                    if let Ok(v) = serde_json::from_value(value) { self.requires_approval = v; }
485                }
486                "approval_threshold" => {
487                    if let Ok(v) = serde_json::from_value(value) { self.approval_threshold = v; }
488                }
489                "submitted_at" => {
490                    if let Ok(v) = serde_json::from_value(value) { self.submitted_at = v; }
491                }
492                "submitted_by" => {
493                    if let Ok(v) = serde_json::from_value(value) { self.submitted_by = v; }
494                }
495                "approved_at" => {
496                    if let Ok(v) = serde_json::from_value(value) { self.approved_at = v; }
497                }
498                "approved_by" => {
499                    if let Ok(v) = serde_json::from_value(value) { self.approved_by = v; }
500                }
501                "rejected_at" => {
502                    if let Ok(v) = serde_json::from_value(value) { self.rejected_at = v; }
503                }
504                "rejected_by" => {
505                    if let Ok(v) = serde_json::from_value(value) { self.rejected_by = v; }
506                }
507                "rejection_reason" => {
508                    if let Ok(v) = serde_json::from_value(value) { self.rejection_reason = v; }
509                }
510                "posted_at" => {
511                    if let Ok(v) = serde_json::from_value(value) { self.posted_at = v; }
512                }
513                "posted_by" => {
514                    if let Ok(v) = serde_json::from_value(value) { self.posted_by = v; }
515                }
516                "is_voided" => {
517                    if let Ok(v) = serde_json::from_value(value) { self.is_voided = v; }
518                }
519                "voided_at" => {
520                    if let Ok(v) = serde_json::from_value(value) { self.voided_at = v; }
521                }
522                "voided_by" => {
523                    if let Ok(v) = serde_json::from_value(value) { self.voided_by = v; }
524                }
525                "void_reason" => {
526                    if let Ok(v) = serde_json::from_value(value) { self.void_reason = v; }
527                }
528                "attachments" => {
529                    if let Ok(v) = serde_json::from_value(value) { self.attachments = v; }
530                }
531                _ => {} // ignore unknown fields
532            }
533        }
534    }
535
536}
537
538impl super::Entity for Journal {
539    type Id = Uuid;
540
541    fn entity_id(&self) -> &Self::Id {
542        &self.id
543    }
544
545    fn entity_type() -> &'static str {
546        "Journal"
547    }
548}
549
550impl backbone_core::PersistentEntity for Journal {
551    fn entity_id(&self) -> String {
552        self.id.to_string()
553    }
554    fn set_entity_id(&mut self, id: String) {
555        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
556            self.id = uuid;
557        }
558    }
559    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
560        self.metadata.created_at
561    }
562    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
563        self.metadata.created_at = Some(ts);
564    }
565    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
566        self.metadata.updated_at
567    }
568    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
569        self.metadata.updated_at = Some(ts);
570    }
571    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
572        self.metadata.deleted_at
573    }
574    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
575        self.metadata.deleted_at = ts;
576    }
577}
578
579impl backbone_orm::EntityRepoMeta for Journal {
580    fn column_types() -> std::collections::HashMap<String, String> {
581        let mut m = std::collections::HashMap::new();
582        m.insert("id".to_string(), "uuid".to_string());
583        m.insert("branch_id".to_string(), "uuid".to_string());
584        m.insert("fiscal_period_id".to_string(), "uuid".to_string());
585        m.insert("source_id".to_string(), "uuid".to_string());
586        m.insert("reversed_by_id".to_string(), "uuid".to_string());
587        m.insert("reverses_id".to_string(), "uuid".to_string());
588        m.insert("journal_type".to_string(), "journal_type".to_string());
589        m.insert("source".to_string(), "journal_source".to_string());
590        m.insert("status".to_string(), "journal_status".to_string());
591        m.insert("transaction_date".to_string(), "date".to_string());
592        m.insert("posting_date".to_string(), "date".to_string());
593        m.insert("reversed_at".to_string(), "timestamptz".to_string());
594        m.insert("auto_reverse_date".to_string(), "date".to_string());
595        m.insert("submitted_at".to_string(), "timestamptz".to_string());
596        m.insert("approved_at".to_string(), "timestamptz".to_string());
597        m.insert("rejected_at".to_string(), "timestamptz".to_string());
598        m.insert("posted_at".to_string(), "timestamptz".to_string());
599        m.insert("voided_at".to_string(), "timestamptz".to_string());
600        m
601    }
602    fn search_fields() -> &'static [&'static str] {
603        &["journal_number", "description", "currency"]
604    }
605    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
606        &[("fiscalPeriod", "fiscal_periods", "fiscalPeriodId"), ("reversedBy", "journals", "reversedById"), ("reverses", "journals", "reversesId")]
607    }
608}
609
610/// Builder for Journal entity
611///
612/// Provides a fluent API for constructing Journal instances.
613/// System fields (id, metadata, timestamps) are auto-initialized.
614#[derive(Debug, Clone, Default)]
615pub struct JournalBuilder {
616    journal_number: Option<String>,
617    reference_number: Option<String>,
618    journal_type: Option<JournalType>,
619    branch_id: Option<Uuid>,
620    transaction_date: Option<NaiveDate>,
621    posting_date: Option<NaiveDate>,
622    fiscal_period_id: Option<Uuid>,
623    fiscal_year: Option<i32>,
624    fiscal_month: Option<i32>,
625    description: Option<String>,
626    notes: Option<String>,
627    currency: Option<String>,
628    total_debit: Option<Decimal>,
629    total_credit: Option<Decimal>,
630    line_count: Option<i32>,
631    source: Option<JournalSource>,
632    source_type: Option<String>,
633    source_id: Option<Uuid>,
634    source_reference: Option<String>,
635    is_reversed: Option<bool>,
636    reversed_by_id: Option<Uuid>,
637    reversed_at: Option<DateTime<Utc>>,
638    reversal_reason: Option<String>,
639    reverses_id: Option<Uuid>,
640    is_reversing: Option<bool>,
641    auto_reverse: Option<bool>,
642    auto_reverse_date: Option<NaiveDate>,
643    status: Option<JournalStatus>,
644    requires_approval: Option<bool>,
645    approval_threshold: Option<Decimal>,
646    submitted_at: Option<DateTime<Utc>>,
647    submitted_by: Option<Uuid>,
648    approved_at: Option<DateTime<Utc>>,
649    approved_by: Option<Uuid>,
650    rejected_at: Option<DateTime<Utc>>,
651    rejected_by: Option<Uuid>,
652    rejection_reason: Option<String>,
653    posted_at: Option<DateTime<Utc>>,
654    posted_by: Option<Uuid>,
655    is_voided: Option<bool>,
656    voided_at: Option<DateTime<Utc>>,
657    voided_by: Option<Uuid>,
658    void_reason: Option<String>,
659    attachments: Option<serde_json::Value>,
660}
661
662impl JournalBuilder {
663    /// Set the journal_number field (required)
664    pub fn journal_number(mut self, value: String) -> Self {
665        self.journal_number = Some(value);
666        self
667    }
668
669    /// Set the reference_number field (optional)
670    pub fn reference_number(mut self, value: String) -> Self {
671        self.reference_number = Some(value);
672        self
673    }
674
675    /// Set the journal_type field (default: `JournalType::default()`)
676    pub fn journal_type(mut self, value: JournalType) -> Self {
677        self.journal_type = Some(value);
678        self
679    }
680
681    /// Set the branch_id field (optional)
682    pub fn branch_id(mut self, value: Uuid) -> Self {
683        self.branch_id = Some(value);
684        self
685    }
686
687    /// Set the transaction_date field (required)
688    pub fn transaction_date(mut self, value: NaiveDate) -> Self {
689        self.transaction_date = Some(value);
690        self
691    }
692
693    /// Set the posting_date field (optional)
694    pub fn posting_date(mut self, value: NaiveDate) -> Self {
695        self.posting_date = Some(value);
696        self
697    }
698
699    /// Set the fiscal_period_id field (optional)
700    pub fn fiscal_period_id(mut self, value: Uuid) -> Self {
701        self.fiscal_period_id = Some(value);
702        self
703    }
704
705    /// Set the fiscal_year field (optional)
706    pub fn fiscal_year(mut self, value: i32) -> Self {
707        self.fiscal_year = Some(value);
708        self
709    }
710
711    /// Set the fiscal_month field (optional)
712    pub fn fiscal_month(mut self, value: i32) -> Self {
713        self.fiscal_month = Some(value);
714        self
715    }
716
717    /// Set the description field (required)
718    pub fn description(mut self, value: String) -> Self {
719        self.description = Some(value);
720        self
721    }
722
723    /// Set the notes field (optional)
724    pub fn notes(mut self, value: String) -> Self {
725        self.notes = Some(value);
726        self
727    }
728
729    /// Set the currency field (default: `"IDR".to_string()`)
730    pub fn currency(mut self, value: String) -> Self {
731        self.currency = Some(value);
732        self
733    }
734
735    /// Set the total_debit field (default: `Decimal::from(0)`)
736    pub fn total_debit(mut self, value: Decimal) -> Self {
737        self.total_debit = Some(value);
738        self
739    }
740
741    /// Set the total_credit field (default: `Decimal::from(0)`)
742    pub fn total_credit(mut self, value: Decimal) -> Self {
743        self.total_credit = Some(value);
744        self
745    }
746
747    /// Set the line_count field (default: `0`)
748    pub fn line_count(mut self, value: i32) -> Self {
749        self.line_count = Some(value);
750        self
751    }
752
753    /// Set the source field (default: `JournalSource::default()`)
754    pub fn source(mut self, value: JournalSource) -> Self {
755        self.source = Some(value);
756        self
757    }
758
759    /// Set the source_type field (optional)
760    pub fn source_type(mut self, value: String) -> Self {
761        self.source_type = Some(value);
762        self
763    }
764
765    /// Set the source_id field (optional)
766    pub fn source_id(mut self, value: Uuid) -> Self {
767        self.source_id = Some(value);
768        self
769    }
770
771    /// Set the source_reference field (optional)
772    pub fn source_reference(mut self, value: String) -> Self {
773        self.source_reference = Some(value);
774        self
775    }
776
777    /// Set the is_reversed field (default: `false`)
778    pub fn is_reversed(mut self, value: bool) -> Self {
779        self.is_reversed = Some(value);
780        self
781    }
782
783    /// Set the reversed_by_id field (optional)
784    pub fn reversed_by_id(mut self, value: Uuid) -> Self {
785        self.reversed_by_id = Some(value);
786        self
787    }
788
789    /// Set the reversed_at field (optional)
790    pub fn reversed_at(mut self, value: DateTime<Utc>) -> Self {
791        self.reversed_at = Some(value);
792        self
793    }
794
795    /// Set the reversal_reason field (optional)
796    pub fn reversal_reason(mut self, value: String) -> Self {
797        self.reversal_reason = Some(value);
798        self
799    }
800
801    /// Set the reverses_id field (optional)
802    pub fn reverses_id(mut self, value: Uuid) -> Self {
803        self.reverses_id = Some(value);
804        self
805    }
806
807    /// Set the is_reversing field (default: `false`)
808    pub fn is_reversing(mut self, value: bool) -> Self {
809        self.is_reversing = Some(value);
810        self
811    }
812
813    /// Set the auto_reverse field (default: `false`)
814    pub fn auto_reverse(mut self, value: bool) -> Self {
815        self.auto_reverse = Some(value);
816        self
817    }
818
819    /// Set the auto_reverse_date field (optional)
820    pub fn auto_reverse_date(mut self, value: NaiveDate) -> Self {
821        self.auto_reverse_date = Some(value);
822        self
823    }
824
825    /// Set the status field (default: `JournalStatus::default()`)
826    pub fn status(mut self, value: JournalStatus) -> Self {
827        self.status = Some(value);
828        self
829    }
830
831    /// Set the requires_approval field (default: `false`)
832    pub fn requires_approval(mut self, value: bool) -> Self {
833        self.requires_approval = Some(value);
834        self
835    }
836
837    /// Set the approval_threshold field (optional)
838    pub fn approval_threshold(mut self, value: Decimal) -> Self {
839        self.approval_threshold = Some(value);
840        self
841    }
842
843    /// Set the submitted_at field (optional)
844    pub fn submitted_at(mut self, value: DateTime<Utc>) -> Self {
845        self.submitted_at = Some(value);
846        self
847    }
848
849    /// Set the submitted_by field (optional)
850    pub fn submitted_by(mut self, value: Uuid) -> Self {
851        self.submitted_by = Some(value);
852        self
853    }
854
855    /// Set the approved_at field (optional)
856    pub fn approved_at(mut self, value: DateTime<Utc>) -> Self {
857        self.approved_at = Some(value);
858        self
859    }
860
861    /// Set the approved_by field (optional)
862    pub fn approved_by(mut self, value: Uuid) -> Self {
863        self.approved_by = Some(value);
864        self
865    }
866
867    /// Set the rejected_at field (optional)
868    pub fn rejected_at(mut self, value: DateTime<Utc>) -> Self {
869        self.rejected_at = Some(value);
870        self
871    }
872
873    /// Set the rejected_by field (optional)
874    pub fn rejected_by(mut self, value: Uuid) -> Self {
875        self.rejected_by = Some(value);
876        self
877    }
878
879    /// Set the rejection_reason field (optional)
880    pub fn rejection_reason(mut self, value: String) -> Self {
881        self.rejection_reason = Some(value);
882        self
883    }
884
885    /// Set the posted_at field (optional)
886    pub fn posted_at(mut self, value: DateTime<Utc>) -> Self {
887        self.posted_at = Some(value);
888        self
889    }
890
891    /// Set the posted_by field (optional)
892    pub fn posted_by(mut self, value: Uuid) -> Self {
893        self.posted_by = Some(value);
894        self
895    }
896
897    /// Set the is_voided field (default: `false`)
898    pub fn is_voided(mut self, value: bool) -> Self {
899        self.is_voided = Some(value);
900        self
901    }
902
903    /// Set the voided_at field (optional)
904    pub fn voided_at(mut self, value: DateTime<Utc>) -> Self {
905        self.voided_at = Some(value);
906        self
907    }
908
909    /// Set the voided_by field (optional)
910    pub fn voided_by(mut self, value: Uuid) -> Self {
911        self.voided_by = Some(value);
912        self
913    }
914
915    /// Set the void_reason field (optional)
916    pub fn void_reason(mut self, value: String) -> Self {
917        self.void_reason = Some(value);
918        self
919    }
920
921    /// Set the attachments field (default: `serde_json::json!([])`)
922    pub fn attachments(mut self, value: serde_json::Value) -> Self {
923        self.attachments = Some(value);
924        self
925    }
926
927    /// Build the Journal entity
928    ///
929    /// Returns Err if any required field without a default is missing.
930    pub fn build(self) -> Result<Journal, String> {
931        let journal_number = self.journal_number.ok_or_else(|| "journal_number is required".to_string())?;
932        let transaction_date = self.transaction_date.ok_or_else(|| "transaction_date is required".to_string())?;
933        let description = self.description.ok_or_else(|| "description is required".to_string())?;
934
935        Ok(Journal {
936            id: Uuid::new_v4(),
937            journal_number,
938            reference_number: self.reference_number,
939            journal_type: self.journal_type.unwrap_or_default(),
940            branch_id: self.branch_id,
941            transaction_date,
942            posting_date: self.posting_date,
943            fiscal_period_id: self.fiscal_period_id,
944            fiscal_year: self.fiscal_year,
945            fiscal_month: self.fiscal_month,
946            description,
947            notes: self.notes,
948            currency: self.currency.unwrap_or("IDR".to_string()),
949            total_debit: self.total_debit.unwrap_or(Decimal::from(0)),
950            total_credit: self.total_credit.unwrap_or(Decimal::from(0)),
951            line_count: self.line_count.unwrap_or(0),
952            source: self.source.unwrap_or_default(),
953            source_type: self.source_type,
954            source_id: self.source_id,
955            source_reference: self.source_reference,
956            is_reversed: self.is_reversed.unwrap_or(false),
957            reversed_by_id: self.reversed_by_id,
958            reversed_at: self.reversed_at,
959            reversal_reason: self.reversal_reason,
960            reverses_id: self.reverses_id,
961            is_reversing: self.is_reversing.unwrap_or(false),
962            auto_reverse: self.auto_reverse.unwrap_or(false),
963            auto_reverse_date: self.auto_reverse_date,
964            status: self.status.unwrap_or_default(),
965            requires_approval: self.requires_approval.unwrap_or(false),
966            approval_threshold: self.approval_threshold,
967            submitted_at: self.submitted_at,
968            submitted_by: self.submitted_by,
969            approved_at: self.approved_at,
970            approved_by: self.approved_by,
971            rejected_at: self.rejected_at,
972            rejected_by: self.rejected_by,
973            rejection_reason: self.rejection_reason,
974            posted_at: self.posted_at,
975            posted_by: self.posted_by,
976            is_voided: self.is_voided.unwrap_or(false),
977            voided_at: self.voided_at,
978            voided_by: self.voided_by,
979            void_reason: self.void_reason,
980            attachments: self.attachments.unwrap_or(serde_json::json!([])),
981            metadata: AuditMetadata::default(),
982        })
983    }
984}