Skip to main content

stateset_embedded/
credit.rs

1//! Credit Management operations
2//!
3//! Comprehensive credit management supporting:
4//! - Customer credit limits and accounts
5//! - Credit checks for orders
6//! - Credit holds and releases
7//! - Credit applications and approvals
8//!
9//! # Example
10//!
11//! ```ignore
12//! use stateset_embedded::{Commerce, CreateCreditAccount, CustomerId};
13//! use rust_decimal_macros::dec;
14//!
15//! let commerce = Commerce::new("./store.db")?;
16//!
17//! // Create a credit account for a customer
18//! let account = commerce.credit().create_credit_account(CreateCreditAccount {
19//!     customer_id: CustomerId::new(),
20//!     credit_limit: dec!(10000.00),
21//!     payment_terms: Some("Net 30".into()),
22//!     ..Default::default()
23//! })?;
24//!
25//! println!("Credit limit: ${}", account.credit_limit);
26//! # Ok::<(), stateset_embedded::CommerceError>(())
27//! ```
28
29use rust_decimal::Decimal;
30use stateset_core::{
31    CreateCreditAccount, CreditAccount, CreditAccountFilter, CreditAgingBucket, CreditApplication,
32    CreditApplicationFilter, CreditCheckResult, CreditHold, CreditHoldFilter, CreditId,
33    CreditTransaction, CreditTransactionFilter, CustomerCreditSummary, CustomerId, OrderId,
34    PlaceCreditHold, RecordCreditTransaction, ReleaseCreditHold, Result, ReviewCreditApplication,
35    SubmitCreditApplication, UpdateCreditAccount,
36};
37use stateset_db::Database;
38use std::sync::Arc;
39use uuid::Uuid;
40
41/// Credit Management interface.
42pub struct Credit {
43    db: Arc<dyn Database>,
44}
45
46impl std::fmt::Debug for Credit {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("Credit").finish_non_exhaustive()
49    }
50}
51
52impl Credit {
53    pub(crate) fn new(db: Arc<dyn Database>) -> Self {
54        Self { db }
55    }
56
57    // ========================================================================
58    // Credit Account Operations
59    // ========================================================================
60
61    /// Create a credit account for a customer.
62    ///
63    /// # Example
64    ///
65    /// ```rust,no_run
66    /// use stateset_embedded::{Commerce, CreateCreditAccount, CustomerId, RiskRating};
67    /// use rust_decimal_macros::dec;
68    ///
69    /// let commerce = Commerce::new(":memory:")?;
70    ///
71    /// let account = commerce.credit().create_credit_account(CreateCreditAccount {
72    ///     customer_id: CustomerId::new(),
73    ///     credit_limit: dec!(25000.00),
74    ///     payment_terms: Some("Net 45".into()),
75    ///     risk_rating: Some(RiskRating::Low),
76    ///     notes: Some("Established customer since 2020".into()),
77    ///     ..Default::default()
78    /// })?;
79    /// # Ok::<(), stateset_embedded::CommerceError>(())
80    /// ```
81    pub fn create_credit_account(&self, input: CreateCreditAccount) -> Result<CreditAccount> {
82        self.db.credit().create_credit_account(input)
83    }
84
85    /// Get a credit account by ID.
86    pub fn get_credit_account(&self, id: CreditId) -> Result<Option<CreditAccount>> {
87        self.db.credit().get_credit_account(id)
88    }
89
90    /// Get a credit account by customer ID.
91    pub fn get_credit_account_by_customer(
92        &self,
93        customer_id: CustomerId,
94    ) -> Result<Option<CreditAccount>> {
95        self.db.credit().get_credit_account_by_customer(customer_id)
96    }
97
98    /// Update a credit account.
99    pub fn update_credit_account(
100        &self,
101        id: CreditId,
102        input: UpdateCreditAccount,
103    ) -> Result<CreditAccount> {
104        self.db.credit().update_credit_account(id, input)
105    }
106
107    /// List credit accounts with optional filtering.
108    pub fn list_credit_accounts(&self, filter: CreditAccountFilter) -> Result<Vec<CreditAccount>> {
109        self.db.credit().list_credit_accounts(filter)
110    }
111
112    /// Adjust a customer's credit limit.
113    ///
114    /// # Example
115    ///
116    /// ```rust,no_run
117    /// use stateset_embedded::{Commerce, CustomerId};
118    /// use rust_decimal_macros::dec;
119    ///
120    /// let commerce = Commerce::new(":memory:")?;
121    ///
122    /// let account = commerce.credit().adjust_credit_limit(
123    ///     CustomerId::new(),
124    ///     dec!(50000.00),
125    ///     "Annual review - increased based on payment history"
126    /// )?;
127    /// # Ok::<(), stateset_embedded::CommerceError>(())
128    /// ```
129    pub fn adjust_credit_limit(
130        &self,
131        customer_id: CustomerId,
132        new_limit: Decimal,
133        reason: &str,
134    ) -> Result<CreditAccount> {
135        self.db.credit().adjust_credit_limit(customer_id, new_limit, reason)
136    }
137
138    /// Suspend a credit account.
139    pub fn suspend_credit_account(
140        &self,
141        customer_id: CustomerId,
142        reason: &str,
143    ) -> Result<CreditAccount> {
144        self.db.credit().suspend_credit_account(customer_id, reason)
145    }
146
147    /// Reactivate a suspended credit account.
148    pub fn reactivate_credit_account(&self, customer_id: CustomerId) -> Result<CreditAccount> {
149        self.db.credit().reactivate_credit_account(customer_id)
150    }
151
152    // ========================================================================
153    // Credit Check Operations
154    // ========================================================================
155
156    /// Check credit availability for an order.
157    ///
158    /// # Example
159    ///
160    /// ```rust,no_run
161    /// use stateset_embedded::{Commerce, CustomerId};
162    /// use rust_decimal_macros::dec;
163    ///
164    /// let commerce = Commerce::new(":memory:")?;
165    ///
166    /// let result = commerce.credit().check_credit(
167    ///     CustomerId::new(), // customer ID
168    ///     dec!(5000.00),     // order amount
169    /// )?;
170    ///
171    /// if result.approved {
172    ///     println!("Credit approved");
173    /// } else {
174    ///     println!("Credit denied: {:?}", result.reason);
175    ///     if result.requires_approval {
176    ///         println!("Can be approved by credit manager");
177    ///     }
178    /// }
179    /// # Ok::<(), stateset_embedded::CommerceError>(())
180    /// ```
181    pub fn check_credit(
182        &self,
183        customer_id: CustomerId,
184        order_amount: Decimal,
185    ) -> Result<CreditCheckResult> {
186        self.db.credit().check_credit(customer_id, order_amount)
187    }
188
189    /// Reserve credit for an order.
190    ///
191    /// Reduces available credit until the order is invoiced or cancelled.
192    pub fn reserve_credit(
193        &self,
194        customer_id: CustomerId,
195        order_id: OrderId,
196        amount: Decimal,
197    ) -> Result<CreditAccount> {
198        self.db.credit().reserve_credit(customer_id, order_id, amount)
199    }
200
201    /// Release a credit reservation (e.g., order cancelled).
202    pub fn release_credit_reservation(
203        &self,
204        customer_id: CustomerId,
205        order_id: OrderId,
206    ) -> Result<CreditAccount> {
207        self.db.credit().release_credit_reservation(customer_id, order_id)
208    }
209
210    /// Charge credit (convert reservation to balance when order is invoiced).
211    pub fn charge_credit(
212        &self,
213        customer_id: CustomerId,
214        order_id: OrderId,
215        amount: Decimal,
216    ) -> Result<CreditAccount> {
217        self.db.credit().charge_credit(customer_id, order_id, amount)
218    }
219
220    // ========================================================================
221    // Credit Hold Operations
222    // ========================================================================
223
224    /// Place a credit hold on a customer or order.
225    ///
226    /// # Example
227    ///
228    /// ```rust,no_run
229    /// use stateset_embedded::{Commerce, PlaceCreditHold, CreditHoldType, CustomerId, OrderId};
230    /// use rust_decimal_macros::dec;
231    ///
232    /// let commerce = Commerce::new(":memory:")?;
233    ///
234    /// let hold = commerce.credit().place_hold(PlaceCreditHold {
235    ///     customer_id: CustomerId::new(),
236    ///     order_id: Some(OrderId::new()),
237    ///     hold_type: CreditHoldType::OverLimit,
238    ///     hold_amount: dec!(2500.00),
239    ///     reason: "Order exceeds available credit".into(),
240    ///     placed_by: Some("credit_system".into()),
241    /// })?;
242    ///
243    /// println!("Hold placed: {:?}", hold.id);
244    /// # Ok::<(), stateset_embedded::CommerceError>(())
245    /// ```
246    pub fn place_hold(&self, input: PlaceCreditHold) -> Result<CreditHold> {
247        self.db.credit().place_hold(input)
248    }
249
250    /// Get a credit hold by ID.
251    pub fn get_hold(&self, id: Uuid) -> Result<Option<CreditHold>> {
252        self.db.credit().get_hold(id)
253    }
254
255    /// List credit holds with optional filtering.
256    pub fn list_holds(&self, filter: CreditHoldFilter) -> Result<Vec<CreditHold>> {
257        self.db.credit().list_holds(filter)
258    }
259
260    /// Release a credit hold.
261    pub fn release_hold(&self, input: ReleaseCreditHold) -> Result<CreditHold> {
262        self.db.credit().release_hold(input)
263    }
264
265    /// Get all active holds for a customer.
266    pub fn get_active_holds(&self, customer_id: CustomerId) -> Result<Vec<CreditHold>> {
267        self.db.credit().get_active_holds(customer_id)
268    }
269
270    /// Get all holds for an order.
271    pub fn get_holds_for_order(&self, order_id: OrderId) -> Result<Vec<CreditHold>> {
272        self.db.credit().get_holds_for_order(order_id)
273    }
274
275    // ========================================================================
276    // Credit Application Operations
277    // ========================================================================
278
279    /// Submit a credit application.
280    ///
281    /// # Example
282    ///
283    /// ```rust,no_run
284    /// use stateset_embedded::{Commerce, CustomerId, SubmitCreditApplication};
285    /// use rust_decimal_macros::dec;
286    ///
287    /// let commerce = Commerce::new(":memory:")?;
288    ///
289    /// let app = commerce.credit().submit_application(SubmitCreditApplication {
290    ///     customer_id: CustomerId::new(),
291    ///     requested_limit: dec!(50000.00),
292    ///     business_name: Some("Acme Corp".into()),
293    ///     years_in_business: Some(10),
294    ///     annual_revenue: Some(dec!(5000000.00)),
295    ///     bank_reference: Some("First National Bank".into()),
296    ///     ..Default::default()
297    /// })?;
298    ///
299    /// println!("Application {} submitted", app.application_number);
300    /// # Ok::<(), stateset_embedded::CommerceError>(())
301    /// ```
302    pub fn submit_application(&self, input: SubmitCreditApplication) -> Result<CreditApplication> {
303        self.db.credit().submit_application(input)
304    }
305
306    /// Get a credit application by ID.
307    pub fn get_application(&self, id: Uuid) -> Result<Option<CreditApplication>> {
308        self.db.credit().get_application(id)
309    }
310
311    /// List credit applications with optional filtering.
312    pub fn list_applications(
313        &self,
314        filter: CreditApplicationFilter,
315    ) -> Result<Vec<CreditApplication>> {
316        self.db.credit().list_applications(filter)
317    }
318
319    /// Review and approve/deny a credit application.
320    pub fn review_application(&self, input: ReviewCreditApplication) -> Result<CreditApplication> {
321        self.db.credit().review_application(input)
322    }
323
324    /// Withdraw a credit application.
325    pub fn withdraw_application(&self, id: Uuid) -> Result<CreditApplication> {
326        self.db.credit().withdraw_application(id)
327    }
328
329    // ========================================================================
330    // Transaction Operations
331    // ========================================================================
332
333    /// Record a credit transaction.
334    pub fn record_transaction(&self, input: RecordCreditTransaction) -> Result<CreditTransaction> {
335        self.db.credit().record_transaction(input)
336    }
337
338    /// List credit transactions with optional filtering.
339    pub fn list_transactions(
340        &self,
341        filter: CreditTransactionFilter,
342    ) -> Result<Vec<CreditTransaction>> {
343        self.db.credit().list_transactions(filter)
344    }
345
346    /// Apply a payment to reduce customer balance.
347    pub fn apply_payment(
348        &self,
349        customer_id: CustomerId,
350        amount: Decimal,
351        reference_id: Option<Uuid>,
352    ) -> Result<CreditAccount> {
353        self.db.credit().apply_payment(customer_id, amount, reference_id)
354    }
355
356    // ========================================================================
357    // Analytics
358    // ========================================================================
359
360    /// Get credit summary for a customer.
361    pub fn get_customer_summary(
362        &self,
363        customer_id: CustomerId,
364    ) -> Result<Option<CustomerCreditSummary>> {
365        self.db.credit().get_customer_summary(customer_id)
366    }
367
368    /// Get credit aging report.
369    pub fn get_aging_report(&self) -> Result<Vec<(CustomerId, CreditAgingBucket)>> {
370        self.db.credit().get_aging_report()
371    }
372
373    /// Get all customers over their credit limit.
374    pub fn get_over_limit_customers(&self) -> Result<Vec<CreditAccount>> {
375        self.db.credit().get_over_limit_customers()
376    }
377}