koprogo_api/application/ports/journal_entry_repository.rs
1// Application Port: Journal Entry Repository
2//
3// CREDITS & ATTRIBUTION:
4// This implementation is inspired by the Noalyss project (https://gitlab.com/noalyss/noalyss)
5// Noalyss is a free accounting software for Belgian and French accounting
6// License: GPL-2.0-or-later (GNU General Public License version 2 or later)
7// Copyright: (C) 1989, 1991 Free Software Foundation, Inc.
8// Copyright: Dany De Bontridder <dany@alchimerys.eu>
9
10use crate::domain::entities::{JournalEntry, JournalEntryLine};
11use async_trait::async_trait;
12use chrono::{DateTime, Utc};
13use std::collections::HashMap;
14use uuid::Uuid;
15
16/// Repository port for journal entries (double-entry bookkeeping)
17///
18/// This trait defines operations for managing accounting journal entries
19/// inspired by Noalyss' jrn/jrnx table structure.
20#[async_trait]
21pub trait JournalEntryRepository: Send + Sync {
22 /// Create a new journal entry with its lines
23 ///
24 /// # Arguments
25 /// - `entry`: The journal entry to create (must be balanced)
26 ///
27 /// # Returns
28 /// - `Ok(JournalEntry)` with generated IDs and timestamps
29 /// - `Err(String)` if validation fails or database error
30 ///
31 /// # Database Constraints
32 /// - Triggers validate that total debits = total credits
33 /// - Foreign keys validate account codes exist
34 async fn create(&self, entry: &JournalEntry) -> Result<JournalEntry, String>;
35
36 /// Find all journal entries for an organization
37 ///
38 /// Returns entries ordered by entry_date DESC.
39 async fn find_by_organization(
40 &self,
41 organization_id: Uuid,
42 ) -> Result<Vec<JournalEntry>, String>;
43
44 /// Find journal entries linked to a specific expense
45 ///
46 /// Returns all entries that were auto-generated from this expense.
47 async fn find_by_expense(&self, expense_id: Uuid) -> Result<Vec<JournalEntry>, String>;
48
49 /// Find journal entries for a date range
50 ///
51 /// Useful for generating period reports (income statement).
52 async fn find_by_date_range(
53 &self,
54 organization_id: Uuid,
55 start_date: DateTime<Utc>,
56 end_date: DateTime<Utc>,
57 ) -> Result<Vec<JournalEntry>, String>;
58
59 /// Calculate account balances from journal entry lines
60 ///
61 /// This replaces the old method of calculating balances directly from expenses.
62 ///
63 /// # Arguments
64 /// - `organization_id`: Organization to calculate for
65 ///
66 /// # Returns
67 /// - `HashMap<account_code, balance>` where:
68 /// - Assets/Expenses: balance = debits - credits
69 /// - Liabilities/Revenue: balance = credits - debits
70 ///
71 /// # Example
72 /// ```ignore
73 /// {
74 /// "6100": 5000.0, // Utilities expense
75 /// "4110": 1050.0, // VAT recoverable
76 /// "4400": -6050.0, // Suppliers payable (negative = liability)
77 /// "5500": 6050.0 // Bank (after payment)
78 /// }
79 /// ```
80 async fn calculate_account_balances(
81 &self,
82 organization_id: Uuid,
83 ) -> Result<HashMap<String, f64>, String>;
84
85 /// Calculate account balances for a specific period
86 ///
87 /// Same as `calculate_account_balances` but filtered by entry_date.
88 async fn calculate_account_balances_for_period(
89 &self,
90 organization_id: Uuid,
91 start_date: DateTime<Utc>,
92 end_date: DateTime<Utc>,
93 ) -> Result<HashMap<String, f64>, String>;
94
95 /// Get all journal entry lines for an account
96 ///
97 /// Useful for displaying account ledgers (grand-livre).
98 async fn find_lines_by_account(
99 &self,
100 organization_id: Uuid,
101 account_code: &str,
102 ) -> Result<Vec<JournalEntryLine>, String>;
103
104 /// Validate that an entry is balanced (debits = credits)
105 ///
106 /// This is a safety check before persisting. Database triggers also enforce this.
107 async fn validate_balance(&self, entry_id: Uuid) -> Result<bool, String>;
108
109 /// Calculate account balances for a specific building
110 ///
111 /// Filters journal entries by those linked to expenses/contributions for the building.
112 async fn calculate_account_balances_for_building(
113 &self,
114 organization_id: Uuid,
115 building_id: Uuid,
116 ) -> Result<HashMap<String, f64>, String>;
117
118 /// Calculate account balances for a specific building and period
119 async fn calculate_account_balances_for_building_and_period(
120 &self,
121 organization_id: Uuid,
122 building_id: Uuid,
123 start_date: DateTime<Utc>,
124 end_date: DateTime<Utc>,
125 ) -> Result<HashMap<String, f64>, String>;
126
127 /// Create a manual journal entry with multiple lines
128 async fn create_manual_entry(
129 &self,
130 entry: &JournalEntry,
131 lines: &[JournalEntryLine],
132 ) -> Result<(), String>;
133
134 /// List journal entries with filters
135 #[allow(clippy::too_many_arguments)]
136 async fn list_entries(
137 &self,
138 organization_id: Uuid,
139 building_id: Option<Uuid>,
140 journal_type: Option<String>,
141 start_date: Option<DateTime<Utc>>,
142 end_date: Option<DateTime<Utc>>,
143 limit: i64,
144 offset: i64,
145 ) -> Result<Vec<JournalEntry>, String>;
146
147 /// Find a journal entry by ID
148 async fn find_by_id(
149 &self,
150 entry_id: Uuid,
151 organization_id: Uuid,
152 ) -> Result<JournalEntry, String>;
153
154 /// Find all lines for a journal entry
155 async fn find_lines_by_entry(
156 &self,
157 entry_id: Uuid,
158 organization_id: Uuid,
159 ) -> Result<Vec<JournalEntryLine>, String>;
160
161 /// Delete a journal entry and its lines
162 async fn delete_entry(&self, entry_id: Uuid, organization_id: Uuid) -> Result<(), String>;
163}