222 lines
6.4 KiB
Rust
222 lines
6.4 KiB
Rust
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<Self, Result<$res>>;
|
|
|
|
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<T> = std::result::Result<T, Error>;
|
|
|
|
pub struct OrderManager {
|
|
db: SharedDatabase,
|
|
config: SharedAppConfig,
|
|
}
|
|
|
|
impl actix::Actor for OrderManager {
|
|
type Context = actix::Context<Self>;
|
|
}
|
|
|
|
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<Order>")]
|
|
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<Order> {
|
|
let cart: ShoppingCart = query_db!(
|
|
db,
|
|
database_manager::EnsureActiveShoppingCart {
|
|
buyer_id: msg.account_id
|
|
},
|
|
Error::ShoppingCart,
|
|
Error::DatabaseInternal
|
|
);
|
|
let items: Vec<ShoppingCartItem> = 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<OrderStatus> {
|
|
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,
|
|
}
|
|
}
|