Skip to main content

jobberdb/
job_list.rs

1//! An indexed list of jobs which have been extracted from the [Jobs] database
2
3use super::prelude::*;
4
5/// Adds an index to a [Job] reference which stores the original position within the database.
6pub type IndexedJob<'a> = (usize, &'a Job);
7
8/// Selection of jobs from a database.
9#[derive(Debug, Clone)]
10pub struct JobList<'a> {
11    /// References to jobs within a database (including original indexes).
12    jobs: Vec<IndexedJob<'a>>,
13    /// Copy of the configuration of the original [Jobs] database.
14    pub configuration: &'a Configuration,
15}
16
17impl<'a> IntoIterator for JobList<'a> {
18    type Item = IndexedJob<'a>;
19    type IntoIter = std::vec::IntoIter<Self::Item>;
20
21    fn into_iter(self) -> Self::IntoIter {
22        self.jobs.into_iter()
23    }
24}
25
26impl<'a> From<&'a JobListOwned> for JobList<'a> {
27    fn from(list: &'a JobListOwned) -> Self {
28        Self {
29            configuration: &list.configuration,
30            jobs: list.iter().map(|(n, j)| (*n, j)).collect(),
31        }
32    }
33}
34
35impl<'a> std::fmt::Display for JobList<'a> {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        writeln!(f)?;
38        let mut count = 0;
39        for (pos, job) in self.iter() {
40            writeln!(f, "    Pos: {}", pos + 1)?;
41            job.writeln(f, self.configuration.get(&job.tags))?;
42            writeln!(f)?;
43            count += 1;
44        }
45        let pay = {
46            if let Some(pay) = self.pay_overall() {
47                format!(" = ${}", format::pay_pure(pay))
48            } else {
49                String::new()
50            }
51        };
52        if count > 1 {
53            writeln!(
54                f,
55                "Total: {} job(s), {} hours{}",
56                self.len(),
57                format::hours_pure(self.hours_overall()),
58                pay,
59            )?;
60        }
61        Ok(())
62    }
63}
64
65impl<'a> JobList<'a> {
66    /// Create job list on base of the given database but does not copy the jobs themselves (but it's configuration).
67    pub fn new(jobs: Vec<(usize, &'a Job)>, configuration: &'a Configuration) -> Self {
68        Self {
69            jobs,
70            configuration,
71        }
72    }
73    /// Create job list on base of the given database but does not copy the jobs themselves (but it's configuration).
74    pub fn new_from(jobs: &'a Jobs) -> Self {
75        Self {
76            jobs: Vec::new(),
77            configuration: &jobs.configuration,
78        }
79    }
80    /// Add a new job.
81    pub fn push(&mut self, pos: usize, job: &'a Job) {
82        self.jobs.push((pos, job))
83    }
84    /// Get read-only iterator over included jobs.
85    pub fn iter(&self) -> core::slice::Iter<'_, IndexedJob> {
86        self.jobs.iter()
87    }
88    /// Return `true` if list is empty.
89    pub fn is_empty(&self) -> bool {
90        self.jobs.is_empty()
91    }
92    /// Return the length of the list.
93    pub fn len(&self) -> usize {
94        self.jobs.len()
95    }
96    /// Drain all but the last `count` jobs from the list.
97    pub fn drain(&mut self, count: usize) -> Result<(), Error> {
98        if count > self.jobs.len() {
99            return Err(Error::ToFewJobs(count, self.jobs.len()));
100        }
101        self.jobs.drain(0..(self.jobs.len() - count));
102        Ok(())
103    }
104    /// Collect all tags which are in use in this list.
105    pub fn tags(&self) -> TagSet {
106        let mut tags = TagSet::new();
107        for (_, job) in &self.jobs {
108            tags.insert_many(job.tags.clone());
109        }
110        tags
111    }
112    /// Return a list of all positions (indexes) of the jobs in this list.
113    pub fn positions(&self) -> Positions {
114        Positions::from_iter(self.jobs.iter().map(|(n, _)| *n))
115    }
116    /// Get the configuration that belong to the given list of tags or the base configuration.
117    pub fn get_configuration(&self, tags: &TagSet) -> &Properties {
118        for tag in &tags.0 {
119            if let Some(properties) = self.configuration.tags.get(tag) {
120                return properties;
121            }
122        }
123        &self.configuration.base
124    }
125    /// Calculate the overall hours that were spent within this job list (considers resolutions).
126    pub fn hours_overall(&self) -> f64 {
127        let mut hours = 0.0;
128        for (_, job) in &self.jobs {
129            hours += job.hours(self.get_configuration(&job.tags))
130        }
131        hours
132    }
133    /// Calculate the overall costs of the jobs in this list.
134    pub fn pay_overall(&self) -> Option<f64> {
135        let mut pay_sum = 0.0;
136        let mut has_payment = false;
137        for (_, job) in &self.jobs {
138            let properties = self.get_configuration(&job.tags);
139            if let Some(rate) = properties.rate {
140                pay_sum += rate * job.hours(properties);
141                has_payment = true;
142            }
143        }
144        if has_payment {
145            Some(pay_sum)
146        } else {
147            None
148        }
149    }
150}