feat: compute total price for bill #108
12 changed files with 455 additions and 8 deletions
|
@ -0,0 +1,61 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::billing::domain::line_item_aggregate::LineItem;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use tests::*;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait GetLineItemsForBillIDDBPort: Send + Sync {
|
||||||
|
async fn get_line_items_for_bill_id(&self, bill_id: Uuid) -> BillingDBResult<Vec<LineItem>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type GetLineItemsForBillIDDBPortObj = std::sync::Arc<dyn GetLineItemsForBillIDDBPort>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub fn mock_get_line_items_for_bill_id_db_port_no_line_items(
|
||||||
|
times: Option<usize>,
|
||||||
|
) -> GetLineItemsForBillIDDBPortObj {
|
||||||
|
let mut m = MockGetLineItemsForBillIDDBPort::new();
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_get_line_items_for_bill_id()
|
||||||
|
.times(times)
|
||||||
|
.returning(|_| Ok(Vec::default()));
|
||||||
|
} else {
|
||||||
|
m.expect_get_line_items_for_bill_id()
|
||||||
|
.returning(|_| Ok(Vec::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mock_get_line_items_for_bill_id_db_port_true(
|
||||||
|
times: Option<usize>,
|
||||||
|
) -> GetLineItemsForBillIDDBPortObj {
|
||||||
|
let mut m = MockGetLineItemsForBillIDDBPort::new();
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_get_line_items_for_bill_id()
|
||||||
|
.times(times)
|
||||||
|
.returning(|_| Ok(vec![LineItem::default()]));
|
||||||
|
} else {
|
||||||
|
m.expect_get_line_items_for_bill_id()
|
||||||
|
.returning(|_| Ok(vec![LineItem::default()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
pub mod bill_id_exists;
|
pub mod bill_id_exists;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
pub mod get_line_items_for_bill_id;
|
||||||
pub mod line_item_id_exists;
|
pub mod line_item_id_exists;
|
||||||
pub mod next_token_id;
|
pub mod next_token_id;
|
||||||
pub mod store_id_exists;
|
pub mod store_id_exists;
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
use mockall::*;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
use crate::{
|
||||||
|
billing::{
|
||||||
|
application::port::output::db::bill_id_exists::*,
|
||||||
|
application::port::output::db::get_line_items_for_bill_id::*,
|
||||||
|
domain::{
|
||||||
|
bill_aggregate::*, bill_total_price_computed_event::*,
|
||||||
|
compute_bill_total_price_command::*,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
types::currency::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait ComputeBillTotalPriceBillUseCase: Send + Sync {
|
||||||
|
async fn compute_total_price_for_bill(
|
||||||
|
&self,
|
||||||
|
cmd: ComputeBillTotalPriceBillCommand,
|
||||||
|
) -> BillingResult<BillTotalPriceComputedEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ComputeBillTotalPriceBillServiceObj = Arc<dyn ComputeBillTotalPriceBillUseCase>;
|
||||||
|
|
||||||
|
#[derive(Clone, Builder)]
|
||||||
|
pub struct ComputeBillTotalPriceBillService {
|
||||||
|
db_bill_id_exists: BillIDExistsDBPortObj,
|
||||||
|
db_get_line_items_for_bill_id: GetLineItemsForBillIDDBPortObj,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ComputeBillTotalPriceBillUseCase for ComputeBillTotalPriceBillService {
|
||||||
|
async fn compute_total_price_for_bill(
|
||||||
|
&self,
|
||||||
|
cmd: ComputeBillTotalPriceBillCommand,
|
||||||
|
) -> BillingResult<BillTotalPriceComputedEvent> {
|
||||||
|
if !self.db_bill_id_exists.bill_id_exists(cmd.bill_id()).await? {
|
||||||
|
return Err(BillingError::BillIDNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let line_items = self
|
||||||
|
.db_get_line_items_for_bill_id
|
||||||
|
.get_line_items_for_bill_id(*cmd.bill_id())
|
||||||
|
.await?;
|
||||||
|
if line_items.is_empty() {
|
||||||
|
todo!("Can't compute bill");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_price = PriceBuilder::default()
|
||||||
|
.major(0)
|
||||||
|
.minor(0)
|
||||||
|
.currency(Currency::INR)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for li in line_items.iter() {
|
||||||
|
total_price = total_price.clone() + li.total_price();
|
||||||
|
}
|
||||||
|
// TODO: 3. Tax?
|
||||||
|
|
||||||
|
Ok(BillTotalPriceComputedEventBuilder::default()
|
||||||
|
.added_by_user(*cmd.adding_by())
|
||||||
|
.bill_id(*cmd.bill_id())
|
||||||
|
.total_price(total_price)
|
||||||
|
.build()
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::billing::domain::bill_total_price_computed_event::tests::get_bill_total_computed_event_from_command;
|
||||||
|
use crate::billing::domain::line_item_aggregate::LineItemBuilder;
|
||||||
|
use crate::tests::bdd::*;
|
||||||
|
use crate::types::quantity::*;
|
||||||
|
use crate::utils::uuid::tests::*;
|
||||||
|
|
||||||
|
pub fn mock_compute_bill_total_price_service(
|
||||||
|
times: Option<usize>,
|
||||||
|
cmd: ComputeBillTotalPriceBillCommand,
|
||||||
|
) -> ComputeBillTotalPriceBillServiceObj {
|
||||||
|
let mut m = MockComputeBillTotalPriceBillUseCase::new();
|
||||||
|
|
||||||
|
let res = get_bill_total_computed_event_from_command(&cmd);
|
||||||
|
|
||||||
|
if let Some(times) = times {
|
||||||
|
m.expect_compute_total_price_for_bill()
|
||||||
|
.times(times)
|
||||||
|
.returning(move |_| Ok(res.clone()));
|
||||||
|
} else {
|
||||||
|
m.expect_compute_total_price_for_bill()
|
||||||
|
.returning(move |_| Ok(res.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service() {
|
||||||
|
let cmd = ComputeBillTotalPriceBillCommand::get_cmd();
|
||||||
|
|
||||||
|
let s = ComputeBillTotalPriceBillServiceBuilder::default()
|
||||||
|
.db_bill_id_exists(mock_bill_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
|
.db_get_line_items_for_bill_id(mock_get_line_items_for_bill_id_db_port_true(
|
||||||
|
IS_CALLED_ONLY_ONCE,
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = s.compute_total_price_for_bill(cmd.clone()).await.unwrap();
|
||||||
|
assert_eq!(res.total_price().clone(), Price::default());
|
||||||
|
assert_eq!(res.bill_id(), cmd.bill_id());
|
||||||
|
assert_eq!(res.added_by_user(), cmd.adding_by());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_sum() {
|
||||||
|
let cmd = ComputeBillTotalPriceBillCommand::get_cmd();
|
||||||
|
|
||||||
|
let li = LineItemBuilder::default()
|
||||||
|
.created_time(datetime!(1970-01-01 0:00 UTC))
|
||||||
|
.product_name("test_product".into())
|
||||||
|
.product_id(UUID)
|
||||||
|
.quantity(
|
||||||
|
QuantityBuilder::default()
|
||||||
|
.major(
|
||||||
|
QuantityPartBuilder::default()
|
||||||
|
.number(2)
|
||||||
|
.unit(QuantityUnit::DiscreteNumber)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.minor(
|
||||||
|
QuantityPartBuilder::default()
|
||||||
|
.number(1)
|
||||||
|
.unit(QuantityUnit::DiscreteNumber)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.bill_id(UUID)
|
||||||
|
.price_per_unit(
|
||||||
|
PriceBuilder::default()
|
||||||
|
.major(100)
|
||||||
|
.minor(20)
|
||||||
|
.currency(Currency::INR)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.line_item_id(UUID)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut m = MockGetLineItemsForBillIDDBPort::new();
|
||||||
|
m.expect_get_line_items_for_bill_id()
|
||||||
|
.times(IS_CALLED_ONLY_ONCE.unwrap())
|
||||||
|
.returning(move |_| Ok(vec![li.clone(), li.clone()]));
|
||||||
|
|
||||||
|
let s = ComputeBillTotalPriceBillServiceBuilder::default()
|
||||||
|
.db_bill_id_exists(mock_bill_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
|
.db_get_line_items_for_bill_id(std::sync::Arc::new(m))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = s.compute_total_price_for_bill(cmd.clone()).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res.total_price().clone(),
|
||||||
|
PriceBuilder::default()
|
||||||
|
.major(100 * 2 * 2)
|
||||||
|
.minor(20 * 2 * 2)
|
||||||
|
.currency(Currency::INR)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_bill_id_doesnt_exist() {
|
||||||
|
let cmd = ComputeBillTotalPriceBillCommand::get_cmd();
|
||||||
|
|
||||||
|
let s = ComputeBillTotalPriceBillServiceBuilder::default()
|
||||||
|
.db_bill_id_exists(mock_bill_id_exists_db_port_false(IS_CALLED_ONLY_ONCE))
|
||||||
|
.db_get_line_items_for_bill_id(mock_get_line_items_for_bill_id_db_port_true(
|
||||||
|
IS_NEVER_CALLED,
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
s.compute_total_price_for_bill(cmd.clone()).await,
|
||||||
|
Err(BillingError::BillIDNotFound)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,11 +12,13 @@ pub mod errors;
|
||||||
pub mod add_bill_service;
|
pub mod add_bill_service;
|
||||||
pub mod add_line_item_service;
|
pub mod add_line_item_service;
|
||||||
pub mod add_store_service;
|
pub mod add_store_service;
|
||||||
|
pub mod compute_bill_total_price_service;
|
||||||
pub mod delete_bill_service;
|
pub mod delete_bill_service;
|
||||||
pub mod delete_line_item_service;
|
pub mod delete_line_item_service;
|
||||||
pub mod update_bill_service;
|
pub mod update_bill_service;
|
||||||
pub mod update_line_item_service;
|
pub mod update_line_item_service;
|
||||||
pub mod update_store_service;
|
pub mod update_store_service;
|
||||||
|
// TODO: 2. reset token number for store_id cronjob
|
||||||
|
|
||||||
#[automock]
|
#[automock]
|
||||||
pub trait BillingServicesInterface: Send + Sync {
|
pub trait BillingServicesInterface: Send + Sync {
|
||||||
|
@ -28,6 +30,9 @@ pub trait BillingServicesInterface: Send + Sync {
|
||||||
fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj;
|
fn add_line_item(&self) -> add_line_item_service::AddLineItemServiceObj;
|
||||||
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj;
|
fn update_line_item(&self) -> update_line_item_service::UpdateLineItemServiceObj;
|
||||||
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj;
|
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj;
|
||||||
|
fn compute_total_price_for_bill(
|
||||||
|
&self,
|
||||||
|
) -> compute_bill_total_price_service::ComputeBillTotalPriceBillServiceObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Builder)]
|
#[derive(Clone, Builder)]
|
||||||
|
@ -40,6 +45,8 @@ pub struct BillingServices {
|
||||||
delete_line_item: delete_line_item_service::DeleteLineItemServiceObj,
|
delete_line_item: delete_line_item_service::DeleteLineItemServiceObj,
|
||||||
update_bill: update_bill_service::UpdateBillServiceObj,
|
update_bill: update_bill_service::UpdateBillServiceObj,
|
||||||
delete_bill: delete_bill_service::DeleteBillServiceObj,
|
delete_bill: delete_bill_service::DeleteBillServiceObj,
|
||||||
|
compute_total_price_for_bill:
|
||||||
|
compute_bill_total_price_service::ComputeBillTotalPriceBillServiceObj,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BillingServicesInterface for BillingServices {
|
impl BillingServicesInterface for BillingServices {
|
||||||
|
@ -68,4 +75,9 @@ impl BillingServicesInterface for BillingServices {
|
||||||
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj {
|
fn delete_line_item(&self) -> delete_line_item_service::DeleteLineItemServiceObj {
|
||||||
self.delete_line_item.clone()
|
self.delete_line_item.clone()
|
||||||
}
|
}
|
||||||
|
fn compute_total_price_for_bill(
|
||||||
|
&self,
|
||||||
|
) -> compute_bill_total_price_service::ComputeBillTotalPriceBillServiceObj {
|
||||||
|
self.compute_total_price_for_bill.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,13 @@ impl Aggregate for Bill {
|
||||||
let res = services.delete_bill().delete_bill(cmd).await?;
|
let res = services.delete_bill().delete_bill(cmd).await?;
|
||||||
Ok(vec![BillingEvent::BillDeleted(res)])
|
Ok(vec![BillingEvent::BillDeleted(res)])
|
||||||
}
|
}
|
||||||
|
BillingCommand::ComputeBillTotalPriceBill(cmd) => {
|
||||||
|
let res = services
|
||||||
|
.compute_total_price_for_bill()
|
||||||
|
.compute_total_price_for_bill(cmd)
|
||||||
|
.await?;
|
||||||
|
Ok(vec![BillingEvent::BillTotalPriceComputed(res)])
|
||||||
|
}
|
||||||
_ => Ok(Vec::default()),
|
_ => Ok(Vec::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,6 +115,9 @@ impl Aggregate for Bill {
|
||||||
match event {
|
match event {
|
||||||
BillingEvent::BillAdded(e) => *self = e.bill().clone(),
|
BillingEvent::BillAdded(e) => *self = e.bill().clone(),
|
||||||
BillingEvent::BillUpdated(e) => *self = e.new_bill().clone(),
|
BillingEvent::BillUpdated(e) => *self = e.new_bill().clone(),
|
||||||
|
BillingEvent::BillTotalPriceComputed(e) => {
|
||||||
|
self.total_price = Some(e.total_price().clone());
|
||||||
|
}
|
||||||
BillingEvent::BillDeleted(e) => *self = e.bill().clone(),
|
BillingEvent::BillDeleted(e) => *self = e.bill().clone(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -119,6 +129,8 @@ mod aggregate_tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use add_bill_service::tests::mock_add_bill_service;
|
use add_bill_service::tests::mock_add_bill_service;
|
||||||
|
use compute_bill_total_price_service::tests::mock_compute_bill_total_price_service;
|
||||||
|
use compute_bill_total_price_service::*;
|
||||||
use cqrs_es::test::TestFramework;
|
use cqrs_es::test::TestFramework;
|
||||||
use delete_bill_service::tests::mock_delete_bill_service;
|
use delete_bill_service::tests::mock_delete_bill_service;
|
||||||
use update_bill_service::tests::mock_update_bill_service;
|
use update_bill_service::tests::mock_update_bill_service;
|
||||||
|
@ -126,6 +138,8 @@ mod aggregate_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::billing::domain::bill_deleted_event::tests::get_deleted_bill_event_from_command;
|
use crate::billing::domain::bill_deleted_event::tests::get_deleted_bill_event_from_command;
|
||||||
|
use crate::billing::domain::bill_total_price_computed_event::tests::get_bill_total_computed_event_from_command;
|
||||||
|
use crate::billing::domain::bill_total_price_computed_event::BillTotalPriceComputedEvent;
|
||||||
use crate::billing::domain::bill_updated_event::tests::get_updated_bill_event_from_command;
|
use crate::billing::domain::bill_updated_event::tests::get_updated_bill_event_from_command;
|
||||||
use crate::billing::domain::delete_bill_command::DeleteBillCommand;
|
use crate::billing::domain::delete_bill_command::DeleteBillCommand;
|
||||||
use crate::billing::domain::update_bill_command::UpdateBillCommand;
|
use crate::billing::domain::update_bill_command::UpdateBillCommand;
|
||||||
|
@ -133,6 +147,7 @@ mod aggregate_tests {
|
||||||
|
|
||||||
use crate::billing::domain::{
|
use crate::billing::domain::{
|
||||||
add_bill_command::*, bill_added_event::tests::get_added_bill_event_from_command,
|
add_bill_command::*, bill_added_event::tests::get_added_bill_event_from_command,
|
||||||
|
bill_total_price_computed_event::tests::*, compute_bill_total_price_command::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
type BillTestFramework = TestFramework<Bill>;
|
type BillTestFramework = TestFramework<Bill>;
|
||||||
|
@ -173,6 +188,27 @@ mod aggregate_tests {
|
||||||
.then_expect_events(vec![expected]);
|
.then_expect_events(vec![expected]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bill_total_price_computed() {
|
||||||
|
let cmd = ComputeBillTotalPriceBillCommand::get_cmd();
|
||||||
|
let expected = get_bill_total_computed_event_from_command(&cmd);
|
||||||
|
let expected = BillingEvent::BillTotalPriceComputed(expected);
|
||||||
|
|
||||||
|
let mut services = MockBillingServicesInterface::new();
|
||||||
|
services
|
||||||
|
.expect_compute_total_price_for_bill()
|
||||||
|
.times(IS_CALLED_ONLY_ONCE.unwrap())
|
||||||
|
.return_const(mock_compute_bill_total_price_service(
|
||||||
|
IS_CALLED_ONLY_ONCE,
|
||||||
|
cmd.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
BillTestFramework::with(Arc::new(services))
|
||||||
|
.given_no_previous_events()
|
||||||
|
.when(BillingCommand::ComputeBillTotalPriceBill(cmd))
|
||||||
|
.then_expect_events(vec![expected]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_bill() {
|
fn test_delete_bill() {
|
||||||
let cmd = DeleteBillCommand::get_cmd();
|
let cmd = DeleteBillCommand::get_cmd();
|
||||||
|
|
44
src/billing/domain/bill_total_price_computed_event.rs
Normal file
44
src/billing/domain/bill_total_price_computed_event.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use derive_getters::Getters;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::bill_aggregate::*;
|
||||||
|
use crate::types::currency::*;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Builder, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd,
|
||||||
|
)]
|
||||||
|
pub struct BillTotalPriceComputedEvent {
|
||||||
|
added_by_user: Uuid,
|
||||||
|
bill_id: Uuid,
|
||||||
|
|
||||||
|
total_price: Price,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use crate::billing::domain::compute_bill_total_price_command::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn get_bill_total_computed_event_from_command(
|
||||||
|
cmd: &ComputeBillTotalPriceBillCommand,
|
||||||
|
) -> BillTotalPriceComputedEvent {
|
||||||
|
BillTotalPriceComputedEventBuilder::default()
|
||||||
|
.added_by_user(cmd.adding_by().clone())
|
||||||
|
.bill_id(*cmd.bill_id())
|
||||||
|
.total_price(Price::default())
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_event() {
|
||||||
|
get_bill_total_computed_event_from_command(&ComputeBillTotalPriceBillCommand::get_cmd());
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
add_bill_command::AddBillCommand, add_line_item_command::AddLineItemCommand,
|
add_bill_command::AddBillCommand, add_line_item_command::AddLineItemCommand,
|
||||||
add_store_command::AddStoreCommand, delete_bill_command::DeleteBillCommand,
|
add_store_command::AddStoreCommand,
|
||||||
delete_line_item_command::DeleteLineItemCommand, update_bill_command::UpdateBillCommand,
|
compute_bill_total_price_command::ComputeBillTotalPriceBillCommand,
|
||||||
update_line_item_command::UpdateLineItemCommand, update_store_command::UpdateStoreCommand,
|
delete_bill_command::DeleteBillCommand, delete_line_item_command::DeleteLineItemCommand,
|
||||||
|
update_bill_command::UpdateBillCommand, update_line_item_command::UpdateLineItemCommand,
|
||||||
|
update_store_command::UpdateStoreCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
@ -20,6 +22,7 @@ pub enum BillingCommand {
|
||||||
AddBill(AddBillCommand),
|
AddBill(AddBillCommand),
|
||||||
UpdateBill(UpdateBillCommand),
|
UpdateBill(UpdateBillCommand),
|
||||||
DeleteBill(DeleteBillCommand),
|
DeleteBill(DeleteBillCommand),
|
||||||
|
ComputeBillTotalPriceBill(ComputeBillTotalPriceBillCommand),
|
||||||
AddStore(AddStoreCommand),
|
AddStore(AddStoreCommand),
|
||||||
UpdateStore(UpdateStoreCommand),
|
UpdateStore(UpdateStoreCommand),
|
||||||
}
|
}
|
||||||
|
|
66
src/billing/domain/compute_bill_total_price_command.rs
Normal file
66
src/billing/domain/compute_bill_total_price_command.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use derive_getters::Getters;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::bill_aggregate::Bill;
|
||||||
|
use crate::types::currency::*;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
|
||||||
|
)]
|
||||||
|
pub struct ComputeBillTotalPriceBillCommand {
|
||||||
|
adding_by: Uuid,
|
||||||
|
|
||||||
|
#[builder(default = "OffsetDateTime::now_utc()")]
|
||||||
|
created_time: OffsetDateTime,
|
||||||
|
|
||||||
|
bill_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
billing::{self, domain::bill_aggregate::*},
|
||||||
|
utils::uuid::tests::UUID,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl ComputeBillTotalPriceBillCommand {
|
||||||
|
pub fn get_cmd() -> Self {
|
||||||
|
let bill_id = UUID;
|
||||||
|
let adding_by = UUID;
|
||||||
|
|
||||||
|
ComputeBillTotalPriceBillCommandBuilder::default()
|
||||||
|
.adding_by(adding_by)
|
||||||
|
.bill_id(bill_id)
|
||||||
|
.created_time(datetime!(1970-01-01 0:00 UTC))
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmd() {
|
||||||
|
let bill_id = UUID;
|
||||||
|
let adding_by = UUID;
|
||||||
|
|
||||||
|
let cmd = ComputeBillTotalPriceBillCommandBuilder::default()
|
||||||
|
.adding_by(adding_by)
|
||||||
|
.bill_id(bill_id)
|
||||||
|
.created_time(datetime!(1970-01-01 0:00 UTC))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(*cmd.bill_id(), bill_id);
|
||||||
|
assert_eq!(*cmd.adding_by(), adding_by);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
bill_added_event::BillAddedEvent, bill_deleted_event::BillDeletedEvent,
|
bill_added_event::BillAddedEvent, bill_deleted_event::BillDeletedEvent,
|
||||||
|
bill_total_price_computed_event::BillTotalPriceComputedEvent,
|
||||||
bill_updated_event::BillUpdatedEvent, line_item_added_event::LineItemAddedEvent,
|
bill_updated_event::BillUpdatedEvent, line_item_added_event::LineItemAddedEvent,
|
||||||
line_item_deleted_event::LineItemDeletedEvent, line_item_updated_event::LineItemUpdatedEvent,
|
line_item_deleted_event::LineItemDeletedEvent, line_item_updated_event::LineItemUpdatedEvent,
|
||||||
store_added_event::StoreAddedEvent, store_updated_event::StoreUpdatedEvent,
|
store_added_event::StoreAddedEvent, store_updated_event::StoreUpdatedEvent,
|
||||||
|
@ -20,6 +21,7 @@ pub enum BillingEvent {
|
||||||
BillAdded(BillAddedEvent),
|
BillAdded(BillAddedEvent),
|
||||||
BillUpdated(BillUpdatedEvent),
|
BillUpdated(BillUpdatedEvent),
|
||||||
BillDeleted(BillDeletedEvent),
|
BillDeleted(BillDeletedEvent),
|
||||||
|
BillTotalPriceComputed(BillTotalPriceComputedEvent),
|
||||||
StoreAdded(StoreAddedEvent),
|
StoreAdded(StoreAddedEvent),
|
||||||
StoreUpdated(StoreUpdatedEvent),
|
StoreUpdated(StoreUpdatedEvent),
|
||||||
}
|
}
|
||||||
|
@ -34,9 +36,10 @@ impl DomainEvent for BillingEvent {
|
||||||
BillingEvent::LineItemAdded { .. } => "BillingLineItemAdded",
|
BillingEvent::LineItemAdded { .. } => "BillingLineItemAdded",
|
||||||
BillingEvent::LineItemUpdated { .. } => "BillingLineItemUpdated",
|
BillingEvent::LineItemUpdated { .. } => "BillingLineItemUpdated",
|
||||||
BillingEvent::LineItemDeleted { .. } => "BillingLineItemDeleted",
|
BillingEvent::LineItemDeleted { .. } => "BillingLineItemDeleted",
|
||||||
BillingEvent::BillAdded { .. } => "BillingBilAdded",
|
BillingEvent::BillAdded { .. } => "BillingBillAdded",
|
||||||
BillingEvent::BillUpdated { .. } => "BillingBilUpdated",
|
BillingEvent::BillUpdated { .. } => "BillingBillUpdated",
|
||||||
BillingEvent::BillDeleted { .. } => "BillingBilDeleted",
|
BillingEvent::BillDeleted { .. } => "BillingBillDeleted",
|
||||||
|
BillingEvent::BillTotalPriceComputed { .. } => "BillingBillTotalPriceComputed",
|
||||||
BillingEvent::StoreAdded { .. } => "BillingStoreAdded",
|
BillingEvent::StoreAdded { .. } => "BillingStoreAdded",
|
||||||
BillingEvent::StoreUpdated { .. } => "BillingStoreUpdated",
|
BillingEvent::StoreUpdated { .. } => "BillingStoreUpdated",
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,9 +49,15 @@ impl Default for LineItem {
|
||||||
|
|
||||||
impl LineItem {
|
impl LineItem {
|
||||||
pub fn total_price(&self) -> Price {
|
pub fn total_price(&self) -> Price {
|
||||||
let total_price_as_minor = (self.quantity().major_as_minor().unwrap() // TODO: handle err
|
let price_per_unit_as_minor =
|
||||||
|
self.price_per_unit().major_as_minor() + self.price_per_unit().minor();
|
||||||
|
let total_price_as_minor = if self.quantity().major().is_dividable() {
|
||||||
|
(self.quantity().major_as_minor().unwrap() // TODO: handle err
|
||||||
+ self.quantity().minor().number())
|
+ self.quantity().minor().number())
|
||||||
* (self.price_per_unit().major_as_minor() + self.price_per_unit().minor());
|
* price_per_unit_as_minor
|
||||||
|
} else {
|
||||||
|
self.quantity().major().number() * price_per_unit_as_minor
|
||||||
|
};
|
||||||
|
|
||||||
Price::from_minor(
|
Price::from_minor(
|
||||||
total_price_as_minor,
|
total_price_as_minor,
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod add_bill_command;
|
||||||
pub mod add_line_item_command;
|
pub mod add_line_item_command;
|
||||||
pub mod add_store_command;
|
pub mod add_store_command;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
pub mod compute_bill_total_price_command;
|
||||||
pub mod delete_bill_command;
|
pub mod delete_bill_command;
|
||||||
pub mod delete_line_item_command;
|
pub mod delete_line_item_command;
|
||||||
pub mod update_bill_command;
|
pub mod update_bill_command;
|
||||||
|
@ -21,6 +22,7 @@ pub mod update_store_command;
|
||||||
// events;
|
// events;
|
||||||
pub mod bill_added_event;
|
pub mod bill_added_event;
|
||||||
pub mod bill_deleted_event;
|
pub mod bill_deleted_event;
|
||||||
|
pub mod bill_total_price_computed_event;
|
||||||
pub mod bill_updated_event;
|
pub mod bill_updated_event;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod line_item_added_event;
|
pub mod line_item_added_event;
|
||||||
|
|
|
@ -29,6 +29,8 @@ pub struct UpdateBillCommand {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use time::macros::datetime;
|
||||||
|
|
||||||
use crate::{billing::domain::bill_aggregate::*, utils::uuid::tests::UUID};
|
use crate::{billing::domain::bill_aggregate::*, utils::uuid::tests::UUID};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -39,6 +41,7 @@ mod tests {
|
||||||
let adding_by = UUID;
|
let adding_by = UUID;
|
||||||
|
|
||||||
UpdateBillCommandBuilder::default()
|
UpdateBillCommandBuilder::default()
|
||||||
|
.created_time(datetime!(1970-01-01 0:00 UTC))
|
||||||
.adding_by(adding_by)
|
.adding_by(adding_by)
|
||||||
.store_id(store_id)
|
.store_id(store_id)
|
||||||
.total_price(None)
|
.total_price(None)
|
||||||
|
|
Loading…
Reference in a new issue