use serde::Deserialize;
use uuid::Uuid;
use super::event_error::EventResult;
use crate::infrastructure::persistence::sale_seam_repository::{SaleSeamRepository, SeamOutcome};
use crate::infrastructure::persistence::seat_repository::RegisterCommand;
pub const SALE_SEAM_CONSUMER: &str = "event-sale-seam";
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OrderConfirmed {
pub delivery_id: Option<String>,
pub order_id: Uuid,
pub org_unit_id: Option<Uuid>,
pub customer_id: Option<Uuid>,
pub grand_total: String,
pub currency: Option<String>,
pub registrations: Vec<RegistrationSpec>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RegistrationSpec {
pub event_id: Uuid,
pub event_slot_id: Option<Uuid>,
pub event_ticket_id: Option<Uuid>,
pub name: String,
pub email: String,
pub phone: Option<String>,
pub company_name: Option<String>,
pub partner_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OrderCancelled {
pub delivery_id: Option<String>,
pub order_id: Uuid,
pub org_unit_id: Option<Uuid>,
pub customer_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OrderPaid {
pub delivery_id: Option<String>,
pub order_id: Uuid,
pub line_ids: Vec<Uuid>,
}
pub struct SaleSeamService {
repo: SaleSeamRepository,
consumer: String,
}
impl SaleSeamService {
pub fn new(repo: SaleSeamRepository) -> Self {
Self {
repo,
consumer: SALE_SEAM_CONSUMER.to_string(),
}
}
pub fn with_consumer(mut self, consumer: impl Into<String>) -> Self {
self.consumer = consumer.into();
self
}
pub async fn on_order_confirmed(&self, cmd: OrderConfirmed) -> EventResult<SeamOutcome> {
if cmd.registrations.is_empty() {
return Err(super::event_error::EventError::Validation(
"SalesOrderConfirmed carries no attendee specs — nothing to mint".into(),
));
}
let distinct: Vec<uuid::Uuid> = {
let mut ids: Vec<uuid::Uuid> =
cmd.registrations.iter().map(|r| r.event_id).collect();
ids.sort_unstable();
ids.dedup();
ids
};
let flags = self.repo.multi_slot_flags(&distinct).await?;
for spec in &cmd.registrations {
if flags.get(&spec.event_id).copied().unwrap_or(false)
&& spec.event_slot_id.is_none()
{
return Err(super::event_error::EventError::Validation(format!(
"event {} is multi-slot: a Cart order cannot choose the attendee's slot — \
route registrations for it through the direct intake",
spec.event_id
)));
}
}
let specs: Vec<RegisterCommand> = cmd
.registrations
.iter()
.map(|spec| RegisterCommand {
event_id: spec.event_id,
event_slot_id: spec.event_slot_id,
event_ticket_id: spec.event_ticket_id,
name: spec.name.clone(),
email: spec.email.clone(),
phone: spec.phone.clone(),
company_name: spec.company_name.clone(),
partner_id: spec.partner_id,
actor: None, lead_rule_skip: false,
})
.collect();
self.repo
.on_order_confirmed(
&self.consumer,
cmd.delivery_id.as_deref(),
cmd.order_id,
&cmd.grand_total,
specs,
None,
)
.await
}
pub async fn on_order_cancelled(&self, cmd: OrderCancelled) -> EventResult<SeamOutcome> {
self.repo
.on_order_cancelled(&self.consumer, cmd.delivery_id.as_deref(), cmd.order_id, None)
.await
}
pub async fn on_order_paid(&self, cmd: OrderPaid) -> EventResult<SeamOutcome> {
self.repo
.on_order_paid(
&self.consumer,
cmd.delivery_id.as_deref(),
cmd.order_id,
&cmd.line_ids,
None,
)
.await
}
}