Compare commits
No commits in common. "1660da90a7a743b69bf420c00970900add9ee992" and "364763fc350117611787d346d405192e536434d6" have entirely different histories.
1660da90a7
...
364763fc35
18 changed files with 24 additions and 800 deletions
|
@ -1,94 +0,0 @@
|
||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "SELECT \n product_name,\n product_id,\n line_item_id,\n quantity_minor_unit,\n quantity_minor_number,\n quantity_major_unit,\n quantity_major_number,\n created_time,\n bill_id,\n price_per_unit_minor,\n price_per_unit_major,\n price_per_unit_currency,\n deleted\n FROM cqrs_billing_line_item_query\n WHERE\n bill_id = $1;",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "product_name",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "product_id",
|
|
||||||
"type_info": "Uuid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "line_item_id",
|
|
||||||
"type_info": "Uuid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "quantity_minor_unit",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "quantity_minor_number",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "quantity_major_unit",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "quantity_major_number",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "created_time",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "bill_id",
|
|
||||||
"type_info": "Uuid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "price_per_unit_minor",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "price_per_unit_major",
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "price_per_unit_currency",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 12,
|
|
||||||
"name": "deleted",
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "538d43c832702b03da4a51e0b0794785adfb14b4b8ff0ed7c4a7079e711b8ce7"
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use super::BillingDBPostgresAdapter;
|
|
||||||
use crate::billing::{
|
|
||||||
application::port::output::db::{errors::*, get_line_items_for_bill_id::*},
|
|
||||||
domain::line_item_aggregate::LineItem,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl GetLineItemsForBillIDDBPort for BillingDBPostgresAdapter {
|
|
||||||
async fn get_line_items_for_bill_id(&self, bill_id: Uuid) -> BillingDBResult<Vec<LineItem>> {
|
|
||||||
let mut res = sqlx::query_as!(
|
|
||||||
super::line_item_view::LineItemView,
|
|
||||||
"SELECT
|
|
||||||
product_name,
|
|
||||||
product_id,
|
|
||||||
line_item_id,
|
|
||||||
quantity_minor_unit,
|
|
||||||
quantity_minor_number,
|
|
||||||
quantity_major_unit,
|
|
||||||
quantity_major_number,
|
|
||||||
created_time,
|
|
||||||
bill_id,
|
|
||||||
price_per_unit_minor,
|
|
||||||
price_per_unit_major,
|
|
||||||
price_per_unit_currency,
|
|
||||||
deleted
|
|
||||||
FROM cqrs_billing_line_item_query
|
|
||||||
WHERE
|
|
||||||
bill_id = $1;",
|
|
||||||
bill_id
|
|
||||||
)
|
|
||||||
.fetch_all(&self.pool)
|
|
||||||
.await?;
|
|
||||||
println!("Got len: {}", res.len());
|
|
||||||
let mut output = Vec::with_capacity(res.len());
|
|
||||||
res.drain(0..).for_each(|r| output.push(r.into()));
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod tests {
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
// use crate::billing::domain::add_product_command::tests::get_customizations;
|
|
||||||
use crate::{
|
|
||||||
billing::{
|
|
||||||
adapters::output::db::postgres::line_item_view::LineItemView, domain::bill_aggregate::*,
|
|
||||||
},
|
|
||||||
types::currency::*,
|
|
||||||
types::quantity::*,
|
|
||||||
utils::uuid::{tests::*, *},
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn create_dummy_line_item(
|
|
||||||
db: &BillingDBPostgresAdapter,
|
|
||||||
bill_id: Uuid,
|
|
||||||
line_item_id: Uuid,
|
|
||||||
) {
|
|
||||||
let view = LineItemView::default();
|
|
||||||
let version = 0;
|
|
||||||
sqlx::query!(
|
|
||||||
"INSERT INTO cqrs_billing_line_item_query (
|
|
||||||
version,
|
|
||||||
product_name,
|
|
||||||
product_id,
|
|
||||||
line_item_id,
|
|
||||||
quantity_minor_unit,
|
|
||||||
quantity_minor_number,
|
|
||||||
quantity_major_unit,
|
|
||||||
quantity_major_number,
|
|
||||||
created_time,
|
|
||||||
bill_id,
|
|
||||||
price_per_unit_minor,
|
|
||||||
price_per_unit_major,
|
|
||||||
price_per_unit_currency,
|
|
||||||
deleted
|
|
||||||
) VALUES (
|
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14
|
|
||||||
);",
|
|
||||||
version,
|
|
||||||
view.product_name,
|
|
||||||
view.product_id,
|
|
||||||
line_item_id,
|
|
||||||
QuantityUnit::DiscreteNumber.to_string(),
|
|
||||||
view.quantity_minor_number,
|
|
||||||
QuantityUnit::DiscreteNumber.to_string(),
|
|
||||||
view.quantity_major_number,
|
|
||||||
view.created_time,
|
|
||||||
bill_id,
|
|
||||||
view.price_per_unit_minor,
|
|
||||||
view.price_per_unit_major,
|
|
||||||
Currency::INR.to_string(),
|
|
||||||
view.deleted,
|
|
||||||
)
|
|
||||||
.execute(&db.pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_postgres_get_line_items_for_bill_id() {
|
|
||||||
let settings = crate::settings::tests::get_settings().await;
|
|
||||||
settings.create_db().await;
|
|
||||||
let db = super::BillingDBPostgresAdapter::new(
|
|
||||||
sqlx::postgres::PgPool::connect(&settings.database.url)
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let bill_id = UUID;
|
|
||||||
|
|
||||||
// state doesn't exist
|
|
||||||
assert!(db
|
|
||||||
.get_line_items_for_bill_id(bill_id)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.is_empty());
|
|
||||||
|
|
||||||
let u = GenerateUUID;
|
|
||||||
let li_id_1 = u.get_uuid();
|
|
||||||
let li_id_2 = u.get_uuid();
|
|
||||||
create_dummy_line_item(&db, bill_id, li_id_1).await;
|
|
||||||
create_dummy_line_item(&db, bill_id, li_id_2).await;
|
|
||||||
|
|
||||||
// state exists
|
|
||||||
let res = db.get_line_items_for_bill_id(bill_id).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(res.len(), 2);
|
|
||||||
assert!(res
|
|
||||||
.iter()
|
|
||||||
.any(|li| *li.bill_id() == bill_id && *li.line_item_id() == li_id_1));
|
|
||||||
assert!(res
|
|
||||||
.iter()
|
|
||||||
.any(|li| *li.bill_id() == bill_id && *li.line_item_id() == li_id_2));
|
|
||||||
|
|
||||||
settings.drop_db().await;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,23 +25,23 @@ pub const NEW_LINE_ITEM_NON_UUID: &str = "new_line_item_non_uuid-asdfa-billing";
|
||||||
// be designed to reflect the response dto that will be returned to a user.
|
// be designed to reflect the response dto that will be returned to a user.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LineItemView {
|
pub struct LineItemView {
|
||||||
pub product_name: String,
|
product_name: String,
|
||||||
pub product_id: Uuid,
|
product_id: Uuid,
|
||||||
pub bill_id: Uuid,
|
bill_id: Uuid,
|
||||||
pub created_time: OffsetDateTime,
|
created_time: OffsetDateTime,
|
||||||
|
|
||||||
pub line_item_id: Uuid,
|
line_item_id: Uuid,
|
||||||
|
|
||||||
pub quantity_major_number: i32,
|
quantity_major_number: i32,
|
||||||
pub quantity_minor_number: i32,
|
quantity_minor_number: i32,
|
||||||
pub quantity_major_unit: String,
|
quantity_major_unit: String,
|
||||||
pub quantity_minor_unit: String,
|
quantity_minor_unit: String,
|
||||||
|
|
||||||
pub price_per_unit_major: i32,
|
price_per_unit_major: i32,
|
||||||
pub price_per_unit_minor: i32,
|
price_per_unit_minor: i32,
|
||||||
pub price_per_unit_currency: String,
|
price_per_unit_currency: String,
|
||||||
|
|
||||||
pub deleted: bool,
|
deleted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineItemView {
|
impl Default for LineItemView {
|
||||||
|
|
|
@ -10,7 +10,6 @@ use crate::db::{migrate::RunMigrations, sqlx_postgres::Postgres};
|
||||||
mod bill_id_exists;
|
mod bill_id_exists;
|
||||||
mod bill_view;
|
mod bill_view;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod get_line_items_for_bill_id;
|
|
||||||
mod line_item_id_exists;
|
mod line_item_id_exists;
|
||||||
mod line_item_view;
|
mod line_item_view;
|
||||||
mod next_token_id;
|
mod next_token_id;
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
// 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,7 +3,6 @@
|
||||||
// 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;
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
// 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,13 +12,11 @@ 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 {
|
||||||
|
@ -30,9 +28,6 @@ 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)]
|
||||||
|
@ -45,8 +40,6 @@ 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 {
|
||||||
|
@ -75,9 +68,4 @@ 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,13 +100,6 @@ 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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,9 +108,6 @@ 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(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -129,8 +119,6 @@ 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;
|
||||||
|
@ -138,8 +126,6 @@ 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;
|
||||||
|
@ -147,7 +133,6 @@ 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>;
|
||||||
|
@ -188,27 +173,6 @@ 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();
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
// 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,11 +7,9 @@ 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,
|
add_store_command::AddStoreCommand, delete_bill_command::DeleteBillCommand,
|
||||||
compute_bill_total_price_command::ComputeBillTotalPriceBillCommand,
|
delete_line_item_command::DeleteLineItemCommand, update_bill_command::UpdateBillCommand,
|
||||||
delete_bill_command::DeleteBillCommand, delete_line_item_command::DeleteLineItemCommand,
|
update_line_item_command::UpdateLineItemCommand, update_store_command::UpdateStoreCommand,
|
||||||
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)]
|
||||||
|
@ -22,7 +20,6 @@ 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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
// 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,7 +7,6 @@ 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,
|
||||||
|
@ -21,7 +20,6 @@ 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),
|
||||||
}
|
}
|
||||||
|
@ -36,10 +34,9 @@ impl DomainEvent for BillingEvent {
|
||||||
BillingEvent::LineItemAdded { .. } => "BillingLineItemAdded",
|
BillingEvent::LineItemAdded { .. } => "BillingLineItemAdded",
|
||||||
BillingEvent::LineItemUpdated { .. } => "BillingLineItemUpdated",
|
BillingEvent::LineItemUpdated { .. } => "BillingLineItemUpdated",
|
||||||
BillingEvent::LineItemDeleted { .. } => "BillingLineItemDeleted",
|
BillingEvent::LineItemDeleted { .. } => "BillingLineItemDeleted",
|
||||||
BillingEvent::BillAdded { .. } => "BillingBillAdded",
|
BillingEvent::BillAdded { .. } => "BillingBilAdded",
|
||||||
BillingEvent::BillUpdated { .. } => "BillingBillUpdated",
|
BillingEvent::BillUpdated { .. } => "BillingBilUpdated",
|
||||||
BillingEvent::BillDeleted { .. } => "BillingBillDeleted",
|
BillingEvent::BillDeleted { .. } => "BillingBilDeleted",
|
||||||
BillingEvent::BillTotalPriceComputed { .. } => "BillingBillTotalPriceComputed",
|
|
||||||
BillingEvent::StoreAdded { .. } => "BillingStoreAdded",
|
BillingEvent::StoreAdded { .. } => "BillingStoreAdded",
|
||||||
BillingEvent::StoreUpdated { .. } => "BillingStoreUpdated",
|
BillingEvent::StoreUpdated { .. } => "BillingStoreUpdated",
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,15 +49,9 @@ impl Default for LineItem {
|
||||||
|
|
||||||
impl LineItem {
|
impl LineItem {
|
||||||
pub fn total_price(&self) -> Price {
|
pub fn total_price(&self) -> Price {
|
||||||
let price_per_unit_as_minor =
|
let total_price_as_minor = (self.quantity().major_as_minor().unwrap() // TODO: handle err
|
||||||
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())
|
||||||
* price_per_unit_as_minor
|
* (self.price_per_unit().major_as_minor() + self.price_per_unit().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,7 +12,6 @@ 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;
|
||||||
|
@ -22,7 +21,6 @@ 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,8 +29,6 @@ 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::*;
|
||||||
|
@ -41,7 +39,6 @@ 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)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use std::{ops::Add, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cqrs_es::Aggregate;
|
use cqrs_es::Aggregate;
|
||||||
|
@ -53,12 +53,7 @@ impl Price {
|
||||||
|
|
||||||
pub fn from_minor(minor_only: usize, currency: Currency) -> Price {
|
pub fn from_minor(minor_only: usize, currency: Currency) -> Price {
|
||||||
let minor = minor_only % 100;
|
let minor = minor_only % 100;
|
||||||
let major_only = minor_only - minor;
|
let major = (minor_only - minor) / 100;
|
||||||
let major = if major_only == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
(minor_only - minor) / 100
|
|
||||||
};
|
|
||||||
Price {
|
Price {
|
||||||
minor,
|
minor,
|
||||||
major,
|
major,
|
||||||
|
@ -67,16 +62,6 @@ impl Price {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for Price {
|
|
||||||
type Output = Self;
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
let self_minor = self.major_as_minor() + self.minor;
|
|
||||||
let rhs_minor = rhs.major_as_minor() + rhs.minor;
|
|
||||||
|
|
||||||
Self::from_minor(self_minor + rhs_minor, self.currency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -125,70 +110,4 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_price_add() {
|
|
||||||
let a = Price {
|
|
||||||
minor: 10,
|
|
||||||
major: 100,
|
|
||||||
currency: Currency::INR,
|
|
||||||
};
|
|
||||||
|
|
||||||
let b = Price {
|
|
||||||
minor: 1,
|
|
||||||
major: 200,
|
|
||||||
currency: Currency::INR,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
a + b,
|
|
||||||
Price {
|
|
||||||
minor: 11,
|
|
||||||
major: 300,
|
|
||||||
currency: Currency::INR
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_price_add_zero() {
|
|
||||||
let a = Price {
|
|
||||||
minor: 0,
|
|
||||||
major: 0,
|
|
||||||
currency: Currency::INR,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
a.clone() + a,
|
|
||||||
Price {
|
|
||||||
minor: 0,
|
|
||||||
major: 0,
|
|
||||||
currency: Currency::INR
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_price_add_overflow() {
|
|
||||||
let a = Price {
|
|
||||||
minor: 80,
|
|
||||||
major: 100,
|
|
||||||
currency: Currency::INR,
|
|
||||||
};
|
|
||||||
|
|
||||||
let b = Price {
|
|
||||||
minor: 80,
|
|
||||||
major: 200,
|
|
||||||
currency: Currency::INR,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
a + b,
|
|
||||||
Price {
|
|
||||||
minor: 60,
|
|
||||||
major: 301,
|
|
||||||
currency: Currency::INR
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl FromStr for QuantityUnit {
|
||||||
MILLI_LITER => Ok(Self::MilliLiter),
|
MILLI_LITER => Ok(Self::MilliLiter),
|
||||||
LITER => Ok(Self::Liter),
|
LITER => Ok(Self::Liter),
|
||||||
MILLI_GRAM => Ok(Self::Milligram),
|
MILLI_GRAM => Ok(Self::Milligram),
|
||||||
_ => Err(format!("Quantity unsupported: {s}")),
|
_ => Err("Currency unsupported".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,15 +74,6 @@ impl QuantityPart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dividable(&self) -> bool {
|
|
||||||
match self.unit {
|
|
||||||
QuantityUnit::Kilogram | QuantityUnit::Liter | QuantityUnit::Gram => true,
|
|
||||||
QuantityUnit::Milligram | QuantityUnit::MilliLiter | QuantityUnit::DiscreteNumber => {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
Loading…
Reference in a new issue