feat: compute total price for bill #108
4 changed files with 252 additions and 13 deletions
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"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"
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// 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.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LineItemView {
|
||||
product_name: String,
|
||||
product_id: Uuid,
|
||||
bill_id: Uuid,
|
||||
created_time: OffsetDateTime,
|
||||
pub product_name: String,
|
||||
pub product_id: Uuid,
|
||||
pub bill_id: Uuid,
|
||||
pub created_time: OffsetDateTime,
|
||||
|
||||
line_item_id: Uuid,
|
||||
pub line_item_id: Uuid,
|
||||
|
||||
quantity_major_number: i32,
|
||||
quantity_minor_number: i32,
|
||||
quantity_major_unit: String,
|
||||
quantity_minor_unit: String,
|
||||
pub quantity_major_number: i32,
|
||||
pub quantity_minor_number: i32,
|
||||
pub quantity_major_unit: String,
|
||||
pub quantity_minor_unit: String,
|
||||
|
||||
price_per_unit_major: i32,
|
||||
price_per_unit_minor: i32,
|
||||
price_per_unit_currency: String,
|
||||
pub price_per_unit_major: i32,
|
||||
pub price_per_unit_minor: i32,
|
||||
pub price_per_unit_currency: String,
|
||||
|
||||
deleted: bool,
|
||||
pub deleted: bool,
|
||||
}
|
||||
|
||||
impl Default for LineItemView {
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::db::{migrate::RunMigrations, sqlx_postgres::Postgres};
|
|||
mod bill_id_exists;
|
||||
mod bill_view;
|
||||
mod errors;
|
||||
mod get_line_items_for_bill_id;
|
||||
mod line_item_id_exists;
|
||||
mod line_item_view;
|
||||
mod next_token_id;
|
||||
|
|
Loading…
Reference in a new issue