use actix::Message; use config::SharedAppConfig; use database_manager::{query_db, SharedDatabase}; use model::{AccountId, Order, OrderStatus, ShoppingCart, 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)) } } }; } #[macro_export] macro_rules! query_order { ($order_manager: expr, $msg: expr, $fail: expr) => { $crate::query_order!($order_manager, $msg, $fail, $fail) }; ($order_manager: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => { match $order_manager.send($msg).await { Ok(Ok(r)) => Ok(r), Ok(Err(e)) => { tracing::error!("{e}"); Err($db_fail) } Err(e) => { tracing::error!("{e:?}"); Err($act_fail) } } }; } #[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] #[serde(rename_all = "kebab-case", tag = "order")] pub enum Error { #[error("Database actor failed")] DatabaseInternal, #[error("Shopping cart does not exists")] ShoppingCart, #[error("Failed to create account order")] CreateAccountOrder, #[error("Account does not have address")] NoAddress, #[error("Invalid account address")] InvalidAccountAddress, #[error("Invalid order address")] InvalidOrderAddress, } 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(Debug)] pub struct CreateOrderAddress { pub name: model::Name, pub email: model::Email, pub phone: model::Phone, pub street: model::Street, pub city: model::City, pub country: model::Country, pub zip: model::Zip, } #[derive(Debug)] pub enum OrderAddressInput { Address(CreateOrderAddress), AccountAddress(model::AddressId), DefaultAccountAddress, } #[derive(Message, Debug)] #[rtype(result = "Result")] pub struct CreateAccountOrder { pub account_id: AccountId, pub order_address: OrderAddressInput, } order_async_handler!(CreateAccountOrder, create_account_order, Order); pub(crate) async fn create_account_order( msg: CreateAccountOrder, db: SharedDatabase, _config: SharedAppConfig, ) -> Result { let cart: ShoppingCart = query_db!( db, database_manager::EnsureActiveShoppingCart { buyer_id: msg.account_id }, Error::ShoppingCart, Error::DatabaseInternal ); let items: Vec = query_db!( db, database_manager::AccountShoppingCartItems { account_id: cart.buyer_id, shopping_cart_id: Some(cart.id), }, Error::ShoppingCart, Error::DatabaseInternal ); let address: model::AccountAddress = match msg.order_address { OrderAddressInput::Address(input) => { query_db!( db, database_manager::CreateAccountAddress { name: input.name, email: input.email, phone: input.phone, street: input.street, city: input.city, country: input.country, zip: input.zip, account_id: Some(cart.buyer_id), is_default: true, }, Error::InvalidAccountAddress ) } OrderAddressInput::AccountAddress(address_id) => { query_db!( db, database_manager::FindAccountAddress { address_id, account_id: cart.buyer_id }, Error::NoAddress ) } OrderAddressInput::DefaultAccountAddress => { query_db!( db, database_manager::DefaultAccountAddress { account_id: cart.buyer_id }, Error::NoAddress ) } }; let address: model::OrderAddress = query_db!( db, database_manager::CreateOrderAddress { name: address.name, email: address.email, street: address.street, city: address.city, country: address.country, zip: address.zip, phone: address.phone, }, Error::InvalidOrderAddress ); let order = query_db!( db, database_manager::CreateOrder { shopping_cart_id: Some(cart.id), buyer_id: msg.account_id, items: items .into_iter() .map(|item| database_manager::create_order::OrderItem { product_id: item.product_id, quantity: item.quantity, quantity_unit: item.quantity_unit, }) .collect(), checkout_notes: cart.checkout_notes, delivery_address_id: address.id, }, Error::CreateAccountOrder, 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, } }