use stateset_core::{
CreateReturn, CustomerId, OrderId, Result, Return, ReturnFilter, ReturnId, ReturnStatus,
UpdateReturn,
};
use stateset_db::Database;
use stateset_observability::Metrics;
use std::sync::Arc;
#[cfg(feature = "events")]
use crate::events::EventSystem;
#[cfg(feature = "events")]
use rust_decimal::Decimal;
#[cfg(feature = "events")]
use stateset_core::CommerceEvent;
pub struct Returns {
db: Arc<dyn Database>,
metrics: Metrics,
#[cfg(feature = "events")]
event_system: Arc<EventSystem>,
}
impl std::fmt::Debug for Returns {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Returns").finish_non_exhaustive()
}
}
impl Returns {
#[cfg(feature = "events")]
pub(crate) fn new(
db: Arc<dyn Database>,
event_system: Arc<EventSystem>,
metrics: Metrics,
) -> Self {
Self { db, metrics, event_system }
}
#[cfg(not(feature = "events"))]
pub(crate) fn new(db: Arc<dyn Database>, metrics: Metrics) -> Self {
Self { db, metrics }
}
#[cfg(feature = "events")]
fn emit(&self, event: CommerceEvent) {
self.event_system.emit(event);
}
#[cfg(feature = "events")]
fn emit_status_change(&self, previous: &Return, updated: &Return) {
if previous.status != updated.status {
self.emit(CommerceEvent::ReturnStatusChanged {
return_id: updated.id,
from_status: previous.status,
to_status: updated.status,
timestamp: updated.updated_at,
});
}
}
pub fn create(&self, input: CreateReturn) -> Result<Return> {
let ret = self.db.returns().create(input)?;
self.metrics.record_return_requested(&ret.id.to_string());
#[cfg(feature = "events")]
{
self.emit(CommerceEvent::ReturnRequested {
return_id: ret.id,
order_id: ret.order_id,
customer_id: ret.customer_id,
reason: ret.reason,
item_count: ret.items.len(),
timestamp: ret.created_at,
});
}
Ok(ret)
}
pub fn get(&self, id: ReturnId) -> Result<Option<Return>> {
self.db.returns().get(id)
}
pub fn update(&self, id: ReturnId, input: UpdateReturn) -> Result<Return> {
#[cfg(feature = "events")]
let previous = self.db.returns().get(id)?;
let updated = self.db.returns().update(id, input)?;
#[cfg(feature = "events")]
if let Some(previous) = previous {
if previous.status != updated.status {
self.emit_status_change(&previous, &updated);
match updated.status {
ReturnStatus::Approved => {
self.emit(CommerceEvent::ReturnApproved {
return_id: updated.id,
order_id: updated.order_id,
timestamp: updated.updated_at,
});
}
ReturnStatus::Rejected => {
self.emit(CommerceEvent::ReturnRejected {
return_id: updated.id,
order_id: updated.order_id,
reason: updated.notes.clone().unwrap_or_default(),
timestamp: updated.updated_at,
});
}
ReturnStatus::Completed => {
let refund_amount = updated.refund_amount.unwrap_or(Decimal::ZERO);
self.emit(CommerceEvent::ReturnCompleted {
return_id: updated.id,
order_id: updated.order_id,
refund_amount,
timestamp: updated.updated_at,
});
if let (Some(amount), Some(method)) =
(updated.refund_amount, updated.refund_method.clone())
{
self.emit(CommerceEvent::RefundIssued {
return_id: updated.id,
order_id: updated.order_id,
amount,
method,
timestamp: updated.updated_at,
});
}
}
_ => {}
}
}
}
Ok(updated)
}
pub fn list(&self, filter: ReturnFilter) -> Result<Vec<Return>> {
self.db.returns().list(filter)
}
pub fn list_for_order(&self, order_id: OrderId) -> Result<Vec<Return>> {
self.db.returns().list(ReturnFilter { order_id: Some(order_id), ..Default::default() })
}
pub fn list_for_customer(&self, customer_id: CustomerId) -> Result<Vec<Return>> {
self.db
.returns()
.list(ReturnFilter { customer_id: Some(customer_id), ..Default::default() })
}
pub fn approve(&self, id: ReturnId) -> Result<Return> {
#[cfg(feature = "events")]
let previous = self.db.returns().get(id)?;
let ret = self.db.returns().approve(id)?;
#[cfg(feature = "events")]
{
if let Some(previous) = previous {
self.emit_status_change(&previous, &ret);
}
self.emit(CommerceEvent::ReturnApproved {
return_id: ret.id,
order_id: ret.order_id,
timestamp: ret.updated_at,
});
}
Ok(ret)
}
pub fn reject(&self, id: ReturnId, reason: &str) -> Result<Return> {
#[cfg(feature = "events")]
let previous = self.db.returns().get(id)?;
let ret = self.db.returns().reject(id, reason)?;
#[cfg(feature = "events")]
{
if let Some(previous) = previous {
self.emit_status_change(&previous, &ret);
}
self.emit(CommerceEvent::ReturnRejected {
return_id: ret.id,
order_id: ret.order_id,
reason: reason.to_string(),
timestamp: ret.updated_at,
});
}
Ok(ret)
}
pub fn mark_received(&self, id: ReturnId) -> Result<Return> {
self.update(id, UpdateReturn { status: Some(ReturnStatus::Received), ..Default::default() })
}
pub fn complete(&self, id: ReturnId) -> Result<Return> {
#[cfg(feature = "events")]
let previous = self.db.returns().get(id)?;
let ret = self.db.returns().complete(id)?;
#[cfg(feature = "events")]
{
if let Some(previous) = previous {
self.emit_status_change(&previous, &ret);
}
let refund_amount = ret.refund_amount.unwrap_or(Decimal::ZERO);
self.emit(CommerceEvent::ReturnCompleted {
return_id: ret.id,
order_id: ret.order_id,
refund_amount,
timestamp: ret.updated_at,
});
if let (Some(amount), Some(method)) = (ret.refund_amount, ret.refund_method.clone()) {
self.emit(CommerceEvent::RefundIssued {
return_id: ret.id,
order_id: ret.order_id,
amount,
method,
timestamp: ret.updated_at,
});
}
}
Ok(ret)
}
pub fn cancel(&self, id: ReturnId) -> Result<Return> {
#[cfg(feature = "events")]
let previous = self.db.returns().get(id)?;
let ret = self.db.returns().cancel(id)?;
#[cfg(feature = "events")]
if let Some(previous) = previous {
self.emit_status_change(&previous, &ret);
}
Ok(ret)
}
pub fn count(&self, filter: ReturnFilter) -> Result<u64> {
self.db.returns().count(filter)
}
pub fn add_tracking(&self, id: ReturnId, tracking_number: &str) -> Result<Return> {
self.update(
id,
UpdateReturn {
tracking_number: Some(tracking_number.to_string()),
status: Some(ReturnStatus::InTransit),
..Default::default()
},
)
}
pub fn list_pending(&self) -> Result<Vec<Return>> {
self.db
.returns()
.list(ReturnFilter { status: Some(ReturnStatus::Requested), ..Default::default() })
}
}