use actix::Message; use crate::config::SharedAppConfig; use crate::database::{self, SharedDatabase}; use crate::model::{ AccountId, AccountOrder, OrderStatus, ShoppingCart, ShoppingCartId, ShoppingCartItem, }; #[macro_export] macro_rules! order_async_handler { ($msg: ty, $async: ident, $res: ty) => { impl actix::Handler<$msg> for OrderManager { type Result = actix::ResponseActFuture>; fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result { use actix::WrapFuture; let db = self.db.clone(); let config = self.config.clone(); Box::pin(async { $async(msg, db, config).await }.into_actor(self)) } } }; } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Database actor failed")] DatabaseInternal, #[error("Shopping cart does not exists")] ShoppingCart, #[error("Failed to create account order")] CreateAccountOrder, } pub type Result = std::result::Result; pub struct OrderManager { db: SharedDatabase, config: SharedAppConfig, } impl actix::Actor for OrderManager { type Context = actix::Context; } impl OrderManager { pub fn new(config: SharedAppConfig, db: SharedDatabase) -> Self { Self { db, config } } } #[derive(Message, Debug)] #[rtype(result = "Result")] pub struct CreateOrder { pub account_id: AccountId, pub shopping_cart_id: ShoppingCartId, } order_async_handler!(CreateOrder, create_order, AccountOrder); pub(crate) async fn create_order( msg: CreateOrder, db: SharedDatabase, _config: SharedAppConfig, ) -> Result { let cart: ShoppingCart = match db .send(database::FindShoppingCart { id: msg.shopping_cart_id, }) .await { Ok(Ok(cart)) => cart, Ok(Err(e)) => { log::error!("{e}"); return Err(Error::ShoppingCart); } Err(e) => { log::error!("{e:?}"); return Err(Error::DatabaseInternal); } }; let items: Vec = match db .send(database::AccountShoppingCartItems { account_id: cart.buyer_id, shopping_cart_id: Some(cart.id), }) .await { Ok(Ok(items)) => items, Ok(Err(e)) => { log::error!("{e}"); return Err(Error::ShoppingCart); } Err(e) => { log::error!("{e:?}"); return Err(Error::DatabaseInternal); } }; let order = match db .send(database::CreateAccountOrder { shopping_cart_id: cart.id, buyer_id: msg.account_id, items: items .into_iter() .map(|item| database::create_order::OrderItem { product_id: item.product_id, quantity: item.quantity, quantity_unit: item.quantity_unit, }) .collect(), }) .await { Ok(Ok(order)) => order, Ok(Err(e)) => { log::error!("{e}"); return Err(Error::CreateAccountOrder); } Err(e) => { log::error!("{e:?}"); return Err(Error::DatabaseInternal); } }; Ok(order) } pub fn change(current: OrderStatus, next: OrderStatus) -> Option { match (current, next) { // paying (OrderStatus::Confirmed, OrderStatus::Payed) => Some(OrderStatus::Payed), // delivering (OrderStatus::Confirmed | OrderStatus::Payed, OrderStatus::Delivered) => { Some(OrderStatus::Delivered) } // cancelling (OrderStatus::Confirmed, OrderStatus::Cancelled) => Some(OrderStatus::Cancelled), (OrderStatus::Payed, OrderStatus::Cancelled) => Some(OrderStatus::RequireRefund), (OrderStatus::Payed, OrderStatus::RequireRefund) => Some(OrderStatus::RequireRefund), (OrderStatus::RequireRefund, OrderStatus::Refunded) => Some(OrderStatus::Refunded), _ => None, } }