use anyhow::Context;
use chrono::{Duration, Utc};
use planter_core::{
money::{Currency, Money},
person::Person,
project::{Project, TimeRelationship},
resources::{Purchase, Resource},
stakeholders::Stakeholder,
task::Task,
};
use uuid::Uuid;
#[test]
fn test_project() -> anyhow::Result<()> {
let start_date = Utc::now();
let mut project = Project::builder()
.name("World domination")
.description("My second attempt to conquer the world with a crowbar and a stimpack")
.start_date(start_date)
.build();
let crowbar_id = project.add_task(Task::new("Find a crowbar"));
let stimpack_id = project.add_task(Task::new("Find a stimpack"));
let software_id = project.add_task(Task::new("Open a proprietary software house"));
let prey_id = project.add_task(Task::new("Prey on free software projects"));
let profit_id = project.add_task(Task::new("Profit"));
assert_eq!(project.tasks().count(), 5);
project
.add_subtask(stimpack_id, software_id)
.context("Failed to add subtask")?;
project
.add_subtask(stimpack_id, prey_id)
.context("Failed to add subtask")?;
assert_eq!(project.subtasks(stimpack_id).count(), 2);
assert_eq!(project.task_parent(software_id), Some(stimpack_id));
assert!(project.task_parent(crowbar_id).is_none());
project.remove_subtask(software_id)?;
assert!(project.task_parent(software_id).is_none());
assert_eq!(project.subtasks(stimpack_id).count(), 1);
project.add_subtask(stimpack_id, software_id)?;
assert_eq!(project.subtasks(stimpack_id).count(), 2);
project
.add_time_relationship(crowbar_id, stimpack_id, TimeRelationship::StartToFinish)
.context("Tasks don't exist or circular dependencies detected")?;
project
.add_time_relationship(crowbar_id, profit_id, TimeRelationship::StartToFinish)
.context("Tasks don't exist or circular dependencies detected")?;
let sibling = project.add_sibling_before(Task::new("Sibling before"), profit_id)?;
let order: Vec<_> = project.tasks().map(|t| t.id()).collect();
assert!(
order.iter().position(|&id| id == sibling).unwrap()
< order.iter().position(|&id| id == profit_id).unwrap()
);
project.move_task_after(profit_id, crowbar_id)?;
let order: Vec<_> = project.tasks().map(|t| t.id()).collect();
assert_eq!(
order.iter().position(|&id| id == profit_id).unwrap(),
order.iter().position(|&id| id == crowbar_id).unwrap() + 1
);
let crowbar_resource_id = project.add_resource(Resource::new("Crobwar".parse()?));
project
.add_purchase(
crowbar_resource_id,
Purchase::builder()
.quantity(5)
.unit_price(Money::from_minor_units(150, Currency::EUR))
.build(),
)
.context("Crowbar resource not found")?;
project
.resource_mut(crowbar_resource_id)
.context("Crowbar resource not found")?
.set_title("Crowbar".parse()?);
let crowbar_res = project
.resource(crowbar_resource_id)
.context("Crowbar resource not found")?;
assert_eq!(crowbar_res.title(), "Crowbar");
assert_eq!(crowbar_res.purchases().count(), 1);
assert_eq!(
crowbar_res.purchases().next().unwrap().total(),
Money::from_minor_units(750, Currency::EUR),
);
let stimpack_res_id = project.add_resource(Resource::new("Stimpack".parse()?));
project.add_resource(Resource::new("Sebastiano Giordano".parse()?).with_contact(
Stakeholder::individual(
Person::new("Sebastiano", "Giordano").context("Failed to parse a name.")?,
None,
),
));
assert_eq!(project.resources().count(), 3);
let _ = project.rm_resource(stimpack_res_id);
assert_eq!(project.resources().count(), 2);
let server_id = project.add_resource(
Resource::new("Server".parse()?)
.at_hourly_rate(Money::from_minor_units(2_000, Currency::EUR)),
);
let interns_id = project.add_resource(
Resource::new("Interns".parse()?)
.at_hourly_rate(Money::from_minor_units(1_500, Currency::EUR)),
);
let consultant_id = project.add_resource(
Resource::new("Mastro Geppetto".parse()?)
.with_contact(Stakeholder::individual(
Person::new("Geppetto", "Mastro").context("Failed to parse a name.")?,
Some("Invoices in USD".to_owned()),
))
.at_hourly_rate(Money::from_minor_units(10_000, Currency::USD)),
);
project.edit_task_duration(crowbar_id, Duration::hours(2).try_into().unwrap())?;
project
.assign_resource(crowbar_id, server_id)
.context("Failed to assign the server")?;
project.edit_task_duration(profit_id, Duration::hours(3).try_into().unwrap())?;
project
.assign_resource_units(profit_id, interns_id, std::num::NonZeroU32::new(2).unwrap())
.context("Failed to assign interns")?;
project
.assign_resource(profit_id, consultant_id)
.context("Failed to assign the consultant")?;
assert!(project.assign_resource(crowbar_id, Uuid::new_v4()).is_err());
let expected_cost = Money::from_minor_units(750 + 4_000 + 9_000, Currency::EUR)
+ Money::from_minor_units(30_000, Currency::USD);
assert_eq!(project.total_cost(), expected_cost);
let end_date = Utc::now();
project.set_end_date(end_date);
assert_eq!(project.end_date(), Some(end_date));
let person = Person::new("Margherita", "Hack").context("Failed to parse a name")?;
project.add_stakeholder(Stakeholder::Individual {
person,
description: Some("She could try to stop me".to_owned()),
});
project.add_stakeholder(Stakeholder::Organization {
name: "Acme".to_owned(),
description: Some("They might decide to buy me more stimpacks".to_owned()),
});
assert_eq!(project.stakeholders().len(), 2);
let now = Utc::now();
project.edit_task_start(prey_id, now)?;
assert_eq!(
project
.task(stimpack_id)
.context("Stimpack task not found")?
.start(),
Some(now),
);
Ok(())
}