koprogo_api/application/dto/
payment_reminder_dto.rs1use crate::domain::entities::{DeliveryMethod, PaymentReminder, ReminderLevel, ReminderStatus};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6#[derive(Debug, Deserialize, Validate, Clone)]
8pub struct CreatePaymentReminderDto {
9 pub organization_id: String,
10 pub expense_id: String,
11 pub owner_id: String,
12 pub level: ReminderLevel,
13
14 #[serde(with = "rust_decimal::serde::float")]
23 pub amount_owed: Decimal,
24
25 pub due_date: String, #[validate(range(min = 0))]
28 pub days_overdue: i64,
29}
30
31#[derive(Debug, Serialize, Clone)]
33pub struct PaymentReminderResponseDto {
34 pub id: String,
35 pub acp_id: String,
37 pub organization_id: String,
39 pub expense_id: String,
40 pub owner_id: String,
41 pub owner_name: Option<String>, pub owner_email: Option<String>, pub level: ReminderLevel,
44 pub status: ReminderStatus,
45 #[serde(with = "rust_decimal::serde::float")]
46 pub amount_owed: Decimal,
47 #[serde(with = "rust_decimal::serde::float")]
48 pub penalty_amount: Decimal,
49 #[serde(with = "rust_decimal::serde::float")]
50 pub total_amount: Decimal,
51 pub due_date: String,
52 pub days_overdue: i64,
53 pub delivery_method: DeliveryMethod,
54 pub sent_date: Option<String>,
55 pub opened_date: Option<String>,
56 pub pdf_path: Option<String>,
57 pub tracking_number: Option<String>,
58 pub notes: Option<String>,
59 pub created_at: String,
60 pub updated_at: String,
61}
62
63impl From<PaymentReminder> for PaymentReminderResponseDto {
64 fn from(reminder: PaymentReminder) -> Self {
65 Self {
66 id: reminder.id.to_string(),
67 acp_id: reminder.acp_id.to_string(),
68 organization_id: reminder.organization_id.to_string(),
69 expense_id: reminder.expense_id.to_string(),
70 owner_id: reminder.owner_id.to_string(),
71 owner_name: None, owner_email: None, level: reminder.level,
74 status: reminder.status,
75 amount_owed: reminder.amount_owed,
76 penalty_amount: reminder.penalty_amount,
77 total_amount: reminder.total_amount,
78 due_date: reminder.due_date.to_rfc3339(),
79 days_overdue: reminder.days_overdue,
80 delivery_method: reminder.delivery_method,
81 sent_date: reminder.sent_date.map(|d| d.to_rfc3339()),
82 opened_date: reminder.opened_date.map(|d| d.to_rfc3339()),
83 pdf_path: reminder.pdf_path,
84 tracking_number: reminder.tracking_number,
85 notes: reminder.notes,
86 created_at: reminder.created_at.to_rfc3339(),
87 updated_at: reminder.updated_at.to_rfc3339(),
88 }
89 }
90}
91
92#[derive(Debug, Deserialize, Validate, Clone)]
94pub struct MarkReminderSentDto {
95 pub pdf_path: Option<String>,
96}
97
98#[derive(Debug, Deserialize, Clone)]
100pub struct EscalateReminderDto {
101 pub reason: Option<String>,
102}
103
104#[derive(Debug, Deserialize, Validate, Clone)]
106pub struct CancelReminderDto {
107 #[validate(length(min = 1))]
108 pub reason: String,
109}
110
111#[derive(Debug, Deserialize, Validate, Clone)]
113pub struct AddTrackingNumberDto {
114 #[validate(length(min = 1))]
115 pub tracking_number: String,
116}
117
118#[derive(Debug, Serialize, Clone)]
120pub struct PaymentRecoveryStatsDto {
121 #[serde(with = "rust_decimal::serde::float")]
122 pub total_owed: Decimal,
123 #[serde(with = "rust_decimal::serde::float")]
124 pub total_penalties: Decimal,
125 pub reminder_counts: Vec<ReminderLevelCountDto>,
126 pub status_counts: Vec<ReminderStatusCountDto>,
127}
128
129#[derive(Debug, Serialize, Clone)]
130pub struct ReminderLevelCountDto {
131 pub level: ReminderLevel,
132 pub count: i64,
133}
134
135#[derive(Debug, Serialize, Clone)]
136pub struct ReminderStatusCountDto {
137 pub status: ReminderStatus,
138 pub count: i64,
139}
140
141#[derive(Debug, Serialize, Clone)]
143pub struct OverdueExpenseDto {
144 pub expense_id: String,
145 pub owner_id: String,
146 pub days_overdue: i64,
147 #[serde(with = "rust_decimal::serde::float")]
148 pub amount: Decimal,
149 pub recommended_level: ReminderLevel,
150}
151
152impl OverdueExpenseDto {
153 pub fn new(expense_id: String, owner_id: String, days_overdue: i64, amount: Decimal) -> Self {
155 let recommended_level = if days_overdue >= 60 {
156 ReminderLevel::FormalNotice
157 } else if days_overdue >= 30 {
158 ReminderLevel::SecondReminder
159 } else {
160 ReminderLevel::FirstReminder
161 };
162
163 Self {
164 expense_id,
165 owner_id,
166 days_overdue,
167 amount,
168 recommended_level,
169 }
170 }
171}
172
173#[derive(Debug, Deserialize, Validate, Clone)]
175pub struct BulkCreateRemindersDto {
176 #[serde(default)]
177 pub organization_id: String,
178
179 #[validate(range(min = 15))]
180 pub min_days_overdue: i64,
181}
182
183#[derive(Debug, Serialize, Clone)]
185pub struct BulkCreateRemindersResponseDto {
186 pub created_count: i32,
187 pub skipped_count: i32,
188 pub errors: Vec<String>,
189 pub created_reminders: Vec<PaymentReminderResponseDto>,
190}