bazzar/actors/database_manager/src/orders.rs

552 lines
14 KiB
Rust
Raw Normal View History

2022-05-06 11:47:18 +02:00
use model::*;
2022-04-18 22:07:52 +02:00
use super::Result;
2022-05-06 11:47:18 +02:00
use crate::{
2022-05-06 16:02:38 +02:00
create_order_item, db_async_handler, shopping_cart_set_state, CreateOrderItem,
2022-05-06 11:47:18 +02:00
ShoppingCartSetState,
2022-04-20 16:09:09 +02:00
};
2022-06-04 16:55:29 +02:00
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, thiserror::Error)]
2022-05-22 14:19:11 +02:00
#[serde(rename_all = "kebab-case", tag = "account-order")]
pub enum Error {
#[error("Can't create account order")]
CantCreate,
#[error("Can't find account order does to lack of identity")]
NoIdentity,
#[error("Account order does not exists")]
NotExists,
#[error("Failed to load all account orders")]
All,
}
#[derive(actix::Message)]
#[rtype(result = "Result<Vec<Order>>")]
2022-06-06 16:07:49 +02:00
pub struct AllOrders;
2022-06-06 16:07:49 +02:00
db_async_handler!(AllOrders, all_orders, Vec<Order>, inner_all_orders);
2022-06-06 16:07:49 +02:00
pub(crate) async fn all_orders(
_msg: AllOrders,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Order>> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
FROM orders
2022-05-01 18:25:27 +02:00
ORDER BY id DESC
"#,
)
2022-06-06 16:07:49 +02:00
.fetch_all(t)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::AccountOrder(Error::All)
})
}
2022-04-20 16:09:09 +02:00
pub mod create_order {
2022-05-06 11:47:18 +02:00
use model::{ProductId, Quantity, QuantityUnit};
2022-04-20 16:09:09 +02:00
pub struct OrderItem {
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
}
#[derive(actix::Message)]
#[rtype(result = "Result<Order>")]
2022-06-06 16:07:49 +02:00
pub struct CreateOrder {
pub buyer_id: AccountId,
2022-04-20 16:09:09 +02:00
pub items: Vec<create_order::OrderItem>,
2022-06-06 16:07:49 +02:00
pub shopping_cart_id: Option<ShoppingCartId>,
2022-05-19 14:03:18 +02:00
pub checkout_notes: Option<String>,
2022-06-06 16:07:49 +02:00
pub delivery_address_id: OrderAddressId,
}
2022-06-06 16:07:49 +02:00
db_async_handler!(CreateOrder, create_order, Order, inner_create_order);
2022-06-06 16:07:49 +02:00
pub(crate) async fn create_order(
msg: CreateOrder,
2022-05-04 22:26:10 +02:00
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
let order: Order = match sqlx::query_as(
r#"
2022-06-06 16:07:49 +02:00
INSERT INTO orders (buyer_id, status, checkout_notes, address_id)
VALUES ($1, $2, $3, $4)
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
"#,
)
.bind(msg.buyer_id)
2022-04-20 16:09:09 +02:00
.bind(OrderStatus::Confirmed)
2022-06-06 16:07:49 +02:00
.bind(msg.checkout_notes.as_deref())
.bind(msg.delivery_address_id)
2022-05-04 22:26:10 +02:00
.fetch_one(&mut *t)
2022-04-18 08:22:51 +02:00
.await
2022-04-20 16:09:09 +02:00
{
Ok(order) => order,
Err(e) => {
tracing::error!("{e:?}");
2022-06-06 16:07:49 +02:00
dbg!(e);
2022-04-20 16:09:09 +02:00
return Err(super::Error::AccountOrder(Error::CantCreate));
}
};
for item in msg.items {
if let Err(e) = create_order_item(
CreateOrderItem {
product_id: item.product_id,
order_id: order.id,
quantity: item.quantity,
quantity_unit: item.quantity_unit,
},
2022-05-04 22:26:10 +02:00
&mut *t,
2022-04-20 16:09:09 +02:00
)
.await
{
2022-06-06 16:07:49 +02:00
dbg!(e);
tracing::error!("{e:?}");
2022-04-20 16:09:09 +02:00
return Err(super::Error::AccountOrder(Error::CantCreate));
}
}
2022-06-06 16:07:49 +02:00
if let Some(shopping_cart_id) = msg.shopping_cart_id {
if let Err(e) = shopping_cart_set_state(
ShoppingCartSetState {
id: shopping_cart_id,
state: ShoppingCartState::Closed,
checkout_notes: msg.checkout_notes,
},
t,
)
.await
{
dbg!(e);
tracing::error!("{e:?}");
2022-04-20 16:09:09 +02:00
2022-06-06 16:07:49 +02:00
return Err(super::Error::AccountOrder(Error::CantCreate));
};
}
2022-04-20 16:09:09 +02:00
Ok(order)
2022-04-18 08:22:51 +02:00
}
#[derive(actix::Message)]
#[rtype(result = "Result<Order>")]
2022-06-06 16:07:49 +02:00
pub struct UpdateOrder {
pub id: OrderId,
2022-04-18 08:22:51 +02:00
pub buyer_id: AccountId,
pub status: OrderStatus,
pub order_id: Option<ExtOrderId>,
2022-04-18 08:22:51 +02:00
}
2022-06-06 16:07:49 +02:00
db_async_handler!(UpdateOrder, update_order, Order, inner_update_order);
2022-04-18 08:22:51 +02:00
2022-06-06 16:07:49 +02:00
pub(crate) async fn update_order(
msg: UpdateOrder,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
2022-04-18 08:22:51 +02:00
sqlx::query_as(
r#"
UPDATE orders
2022-04-18 08:22:51 +02:00
SET buyer_id = $2 AND status = $3 AND order_id = $4
WHERE id = $1
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
2022-04-18 08:22:51 +02:00
"#,
)
.bind(msg.id)
.bind(msg.buyer_id)
.bind(msg.status)
.bind(msg.order_id)
2022-06-06 16:07:49 +02:00
.fetch_one(t)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::AccountOrder(Error::CantCreate)
})
}
2022-04-29 17:10:04 +02:00
#[derive(actix::Message)]
#[rtype(result = "Result<Order>")]
2022-06-06 16:07:49 +02:00
pub struct UpdateOrderByExt {
2022-04-29 17:10:04 +02:00
pub order_ext_id: String,
pub status: OrderStatus,
}
2022-06-06 16:07:49 +02:00
db_async_handler!(
UpdateOrderByExt,
update_order_by_ext,
Order,
inner_update_order_by_ext
);
2022-04-29 17:10:04 +02:00
2022-06-06 16:07:49 +02:00
pub(crate) async fn update_order_by_ext(
msg: UpdateOrderByExt,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
2022-04-29 17:10:04 +02:00
sqlx::query_as(
r#"
UPDATE orders
2022-04-29 17:10:04 +02:00
SET status = $2
WHERE order_ext_id = $1 :: UUID
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
2022-04-29 17:10:04 +02:00
"#,
)
.bind(msg.order_ext_id)
.bind(msg.status)
2022-06-06 16:07:49 +02:00
.fetch_one(t)
2022-04-29 17:10:04 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
dbg!(e);
2022-04-29 17:10:04 +02:00
super::Error::AccountOrder(Error::CantCreate)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<Order>")]
2022-06-06 16:07:49 +02:00
pub struct FindOrder {
pub id: OrderId,
}
2022-06-06 16:07:49 +02:00
db_async_handler!(
FindOrder,
find_account_order,
Order,
inner_find_account_order
);
2022-06-06 16:07:49 +02:00
pub(crate) async fn find_account_order(
msg: FindOrder,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
FROM orders
WHERE id = $1
"#,
)
.bind(msg.id)
2022-06-06 16:07:49 +02:00
.fetch_one(t)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::AccountOrder(Error::NotExists)
})
}
2022-05-04 21:34:26 +02:00
#[derive(actix::Message)]
#[rtype(result = "Result<Order>")]
2022-05-04 21:34:26 +02:00
pub struct SetOrderServiceId {
2022-06-10 15:15:12 +02:00
/// Internal order id
pub id: OrderId,
2022-06-10 15:15:12 +02:00
/// Payment external service id
2022-05-04 21:34:26 +02:00
pub service_order_id: String,
}
2022-06-06 16:07:49 +02:00
db_async_handler!(
SetOrderServiceId,
set_order_service_id,
Order,
inner_set_order_service_id
);
2022-05-04 21:34:26 +02:00
2022-06-06 16:07:49 +02:00
pub(crate) async fn set_order_service_id(
msg: SetOrderServiceId,
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Order> {
2022-05-04 21:34:26 +02:00
sqlx::query_as(
r#"
UPDATE orders
2022-05-04 21:34:26 +02:00
SET service_order_id = $2
WHERE id = $1
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
2022-05-04 21:34:26 +02:00
"#,
)
.bind(msg.id)
.bind(msg.service_order_id)
2022-06-06 16:07:49 +02:00
.fetch_one(t)
2022-05-04 21:34:26 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-05-04 21:34:26 +02:00
super::Error::AccountOrder(Error::NotExists)
})
}
2022-06-06 16:07:49 +02:00
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use fake::Fake;
use model::*;
use uuid::Uuid;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
use crate::*;
async fn test_order_address(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> model::OrderAddress {
create_order_address(
CreateOrderAddress {
name: Default::default(),
email: Default::default(),
street: Default::default(),
city: Default::default(),
country: Default::default(),
zip: Default::default(),
phone: Default::default(),
2022-06-06 16:07:49 +02:00
},
t,
)
.await
.unwrap()
}
async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product {
create_product(
CreateProduct {
name: ProductName::new(format!("a{}", Uuid::new_v4())),
2022-06-06 16:07:49 +02:00
short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
category: None,
price: Price::from_u32(4687),
deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]),
},
t,
)
.await
.unwrap()
}
async fn test_shopping_cart(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
buyer_id: Option<AccountId>,
state: ShoppingCartState,
) -> ShoppingCart {
let buyer_id = match buyer_id {
Some(id) => id,
_ => test_account(&mut *t).await.id,
};
sqlx::query(r#" UPDATE shopping_carts SET state = 'closed' WHERE buyer_id = $1 "#)
.bind(buyer_id)
.execute(&mut *t)
.await
.unwrap();
2022-06-06 16:07:49 +02:00
let cart = create_shopping_cart(
CreateShoppingCart {
buyer_id,
payment_method: PaymentMethod::PaymentOnTheSpot,
},
&mut *t,
)
.await
.unwrap();
update_shopping_cart(
UpdateShoppingCart {
id: cart.id,
buyer_id: cart.buyer_id,
payment_method: cart.payment_method,
state,
checkout_notes: None,
},
&mut *t,
)
.await
.unwrap()
}
async fn test_account(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> FullAccount {
use fake::faker::internet::en;
let login: String = en::Username().fake();
let email: String = en::FreeEmail().fake();
let hash: String = en::Password(10..20).fake();
create_account(
CreateAccount {
email: Email::new(email),
login: Login::new(login),
pass_hash: PassHash::new(hash),
role: Role::Admin,
},
t,
)
.await
.unwrap()
}
async fn test_empty_order_without_cart(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
buyer_id: Option<AccountId>,
address_id: OrderAddressId,
) -> Order {
let buyer_id = match buyer_id {
Some(id) => id,
_ => test_account(t).await.id,
};
super::create_order(
CreateOrder {
buyer_id,
items: vec![],
shopping_cart_id: None,
checkout_notes: None,
delivery_address_id: address_id,
},
t,
)
.await
.unwrap()
}
#[actix::test]
async fn empty_order_without_cart() {
testx::db_t_ref!(t);
2022-06-06 16:07:49 +02:00
let address_id = test_order_address(&mut t).await.id;
test_empty_order_without_cart(&mut t, None, address_id).await;
testx::db_rollback!(t);
}
#[actix::test]
async fn empty_order_with_cart() {
testx::db_t_ref!(t);
2022-06-06 16:07:49 +02:00
let buyer_id = test_account(&mut t).await.id;
let address_id = test_order_address(&mut t).await.id;
let cart_id = test_shopping_cart(&mut t, Some(buyer_id), ShoppingCartState::Active)
.await
.id;
super::create_order(
CreateOrder {
buyer_id,
items: vec![],
shopping_cart_id: Some(cart_id),
checkout_notes: None,
delivery_address_id: address_id,
},
&mut t,
)
.await
.unwrap();
testx::db_rollback!(t);
}
#[actix::test]
async fn non_empty_order_with_cart() {
testx::db_t_ref!(t);
2022-06-06 16:07:49 +02:00
let buyer_id = test_account(&mut t).await.id;
let address_id = test_order_address(&mut t).await.id;
let cart_id = test_shopping_cart(&mut t, Some(buyer_id), ShoppingCartState::Active)
.await
.id;
super::create_order(
CreateOrder {
buyer_id,
items: vec![
create_order::OrderItem {
product_id: test_product(&mut t).await.id,
quantity: Default::default(),
quantity_unit: QuantityUnit::Gram,
},
create_order::OrderItem {
product_id: test_product(&mut t).await.id,
quantity: Default::default(),
quantity_unit: QuantityUnit::Gram,
},
],
shopping_cart_id: Some(cart_id),
checkout_notes: None,
delivery_address_id: address_id,
},
&mut t,
)
.await
.unwrap();
testx::db_rollback!(t);
}
#[actix::test]
async fn non_empty_order_without_cart() {
testx::db_t_ref!(t);
2022-06-06 16:07:49 +02:00
let buyer_id = test_account(&mut t).await.id;
let address_id = test_order_address(&mut t).await.id;
super::create_order(
CreateOrder {
buyer_id,
items: vec![
create_order::OrderItem {
product_id: test_product(&mut t).await.id,
quantity: Default::default(),
quantity_unit: QuantityUnit::Gram,
},
create_order::OrderItem {
product_id: test_product(&mut t).await.id,
quantity: Default::default(),
quantity_unit: QuantityUnit::Gram,
},
2022-06-10 15:02:28 +02:00
create_order::OrderItem {
product_id: test_product(&mut t).await.id,
quantity: Default::default(),
quantity_unit: QuantityUnit::Gram,
},
2022-06-06 16:07:49 +02:00
],
shopping_cart_id: None,
checkout_notes: None,
delivery_address_id: address_id,
},
&mut t,
)
.await
.unwrap();
testx::db_rollback!(t);
}
#[actix::test]
async fn update_by_ext() {
testx::db_t_ref!(t);
let address_id = test_order_address(&mut t).await.id;
let original = test_empty_order_without_cart(&mut t, None, address_id).await;
let updated = super::update_order_by_ext(
UpdateOrderByExt {
order_ext_id: original.order_ext_id.clone().to_string(),
status: OrderStatus::Payed,
},
&mut t,
)
.await
.unwrap();
testx::db_rollback!(t);
assert_ne!(updated, original);
assert_eq!(
updated,
Order {
id: original.id,
buyer_id: original.buyer_id,
status: OrderStatus::Payed,
order_ext_id: original.order_ext_id,
service_order_id: None,
checkout_notes: None,
address_id: original.address_id
}
);
}
2022-06-06 16:07:49 +02:00
}