koprogo_api/application/dto/
payment_reminder_dto.rs

1use crate::domain::entities::{DeliveryMethod, PaymentReminder, ReminderLevel, ReminderStatus};
2use serde::{Deserialize, Serialize};
3use validator::Validate;
4
5/// DTO for creating a new payment reminder
6#[derive(Debug, Deserialize, Validate, Clone)]
7pub struct CreatePaymentReminderDto {
8    pub organization_id: String,
9    pub expense_id: String,
10    pub owner_id: String,
11    pub level: ReminderLevel,
12
13    #[validate(range(min = 0.01))]
14    pub amount_owed: f64,
15
16    pub due_date: String, // ISO 8601 format
17
18    #[validate(range(min = 0))]
19    pub days_overdue: i64,
20}
21
22/// DTO for payment reminder response
23#[derive(Debug, Serialize, Clone)]
24pub struct PaymentReminderResponseDto {
25    pub id: String,
26    pub organization_id: String,
27    pub expense_id: String,
28    pub owner_id: String,
29    pub owner_name: Option<String>,  // Full name of owner for display
30    pub owner_email: Option<String>, // Owner email for contact
31    pub level: ReminderLevel,
32    pub status: ReminderStatus,
33    pub amount_owed: f64,
34    pub penalty_amount: f64,
35    pub total_amount: f64,
36    pub due_date: String,
37    pub days_overdue: i64,
38    pub delivery_method: DeliveryMethod,
39    pub sent_date: Option<String>,
40    pub opened_date: Option<String>,
41    pub pdf_path: Option<String>,
42    pub tracking_number: Option<String>,
43    pub notes: Option<String>,
44    pub created_at: String,
45    pub updated_at: String,
46}
47
48impl From<PaymentReminder> for PaymentReminderResponseDto {
49    fn from(reminder: PaymentReminder) -> Self {
50        Self {
51            id: reminder.id.to_string(),
52            organization_id: reminder.organization_id.to_string(),
53            expense_id: reminder.expense_id.to_string(),
54            owner_id: reminder.owner_id.to_string(),
55            owner_name: None,  // Will be enriched by use case
56            owner_email: None, // Will be enriched by use case
57            level: reminder.level,
58            status: reminder.status,
59            amount_owed: reminder.amount_owed,
60            penalty_amount: reminder.penalty_amount,
61            total_amount: reminder.total_amount,
62            due_date: reminder.due_date.to_rfc3339(),
63            days_overdue: reminder.days_overdue,
64            delivery_method: reminder.delivery_method,
65            sent_date: reminder.sent_date.map(|d| d.to_rfc3339()),
66            opened_date: reminder.opened_date.map(|d| d.to_rfc3339()),
67            pdf_path: reminder.pdf_path,
68            tracking_number: reminder.tracking_number,
69            notes: reminder.notes,
70            created_at: reminder.created_at.to_rfc3339(),
71            updated_at: reminder.updated_at.to_rfc3339(),
72        }
73    }
74}
75
76/// DTO for marking reminder as sent
77#[derive(Debug, Deserialize, Validate, Clone)]
78pub struct MarkReminderSentDto {
79    pub pdf_path: Option<String>,
80}
81
82/// DTO for escalating a reminder
83#[derive(Debug, Deserialize, Clone)]
84pub struct EscalateReminderDto {
85    pub reason: Option<String>,
86}
87
88/// DTO for cancelling a reminder
89#[derive(Debug, Deserialize, Validate, Clone)]
90pub struct CancelReminderDto {
91    #[validate(length(min = 1))]
92    pub reason: String,
93}
94
95/// DTO for adding tracking number
96#[derive(Debug, Deserialize, Validate, Clone)]
97pub struct AddTrackingNumberDto {
98    #[validate(length(min = 1))]
99    pub tracking_number: String,
100}
101
102/// DTO for payment recovery dashboard statistics
103#[derive(Debug, Serialize, Clone)]
104pub struct PaymentRecoveryStatsDto {
105    pub total_owed: f64,
106    pub total_penalties: f64,
107    pub reminder_counts: Vec<ReminderLevelCountDto>,
108    pub status_counts: Vec<ReminderStatusCountDto>,
109}
110
111#[derive(Debug, Serialize, Clone)]
112pub struct ReminderLevelCountDto {
113    pub level: ReminderLevel,
114    pub count: i64,
115}
116
117#[derive(Debug, Serialize, Clone)]
118pub struct ReminderStatusCountDto {
119    pub status: ReminderStatus,
120    pub count: i64,
121}
122
123/// DTO for overdue expense without reminder (for automated detection)
124#[derive(Debug, Serialize, Clone)]
125pub struct OverdueExpenseDto {
126    pub expense_id: String,
127    pub owner_id: String,
128    pub days_overdue: i64,
129    pub amount: f64,
130    pub recommended_level: ReminderLevel,
131}
132
133impl OverdueExpenseDto {
134    /// Create DTO with automatically determined reminder level
135    pub fn new(expense_id: String, owner_id: String, days_overdue: i64, amount: f64) -> Self {
136        let recommended_level = if days_overdue >= 60 {
137            ReminderLevel::FormalNotice
138        } else if days_overdue >= 30 {
139            ReminderLevel::SecondReminder
140        } else {
141            ReminderLevel::FirstReminder
142        };
143
144        Self {
145            expense_id,
146            owner_id,
147            days_overdue,
148            amount,
149            recommended_level,
150        }
151    }
152}
153
154/// DTO for bulk reminder creation
155#[derive(Debug, Deserialize, Validate, Clone)]
156pub struct BulkCreateRemindersDto {
157    #[serde(default)]
158    pub organization_id: String,
159
160    #[validate(range(min = 15))]
161    pub min_days_overdue: i64,
162}
163
164/// DTO for bulk reminder creation response
165#[derive(Debug, Serialize, Clone)]
166pub struct BulkCreateRemindersResponseDto {
167    pub created_count: i32,
168    pub skipped_count: i32,
169    pub errors: Vec<String>,
170    pub created_reminders: Vec<PaymentReminderResponseDto>,
171}