Skip to main content

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