You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
2.7 KiB
95 lines
2.7 KiB
use crate::Io;
|
|
use chrono::{NaiveDate, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{fs::File, io::Read, path::Path, string::ToString};
|
|
|
|
/// A specification to build invoice IDs with
|
|
#[derive(Serialize, Deserialize)]
|
|
pub enum InvoiceId {
|
|
YearMonthId(u16, u8, usize),
|
|
}
|
|
|
|
impl InvoiceId {
|
|
pub(crate) fn date() -> Self {
|
|
let now = Utc::now().naive_local().date();
|
|
let year = now.format("%Y").to_string();
|
|
let month = now.format("%m").to_string();
|
|
|
|
Self::YearMonthId(
|
|
str::parse(&year).unwrap_or_else(|_| {
|
|
fatal!(
|
|
"Current year doesn't fit into u16. WHEN ARE YOU USING THIS CODE???????? (have you abolished capitalism yet? uwu)"
|
|
)
|
|
}),
|
|
str::parse(&month)
|
|
.unwrap_or_else(|_| fatal!("Invalid month value (doesn't fit in u8)")),
|
|
0,
|
|
)
|
|
}
|
|
|
|
/// Utility to find the next invoice ID in a sequence
|
|
///
|
|
/// Start with an InvoiceID that only has the correct date
|
|
/// (`date()`), then call this function for each existing invoice
|
|
/// in your collection. If the current ID is lower or equal than
|
|
/// the one given, take the given ID and increment it by one.
|
|
///
|
|
/// Once this has been done for all invoices in the collection,
|
|
/// you are guaranteed to have the latest invoice ID.
|
|
pub(crate) fn find_next(mut self, i: &Invoice) -> Self {
|
|
if self.numeric() <= i.id.numeric() {
|
|
self.update_numeric(i.id.numeric() + 1);
|
|
}
|
|
self
|
|
}
|
|
|
|
fn update_numeric(&mut self, id: usize) {
|
|
match self {
|
|
Self::YearMonthId(_, _, ref mut _id) => {
|
|
*_id = id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A valid invoice ID needs to have a numeric element
|
|
pub(crate) fn numeric(&self) -> usize {
|
|
match self {
|
|
Self::YearMonthId(_, _, id) => *id,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToString for InvoiceId {
|
|
fn to_string(&self) -> String {
|
|
match self {
|
|
Self::YearMonthId(yr, mo, id) => format!("#{}-{:02}-{:04}", yr, mo, id),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An invoice for a specific project
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Invoice {
|
|
id: InvoiceId,
|
|
client: String,
|
|
project: String,
|
|
date: NaiveDate,
|
|
amount: usize,
|
|
currency: String,
|
|
vat: u8,
|
|
}
|
|
|
|
impl Invoice {
|
|
pub fn load(path: &Path) -> Option<Self> {
|
|
let mut buf = String::new();
|
|
let mut f = File::open(path).ok()?;
|
|
f.read_to_string(&mut buf).ok()?;
|
|
Some(Self::from_yaml(buf))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invoice_id_fmt() {
|
|
let inv_id = InvoiceId::YearMonthId(2020, 06, 0055);
|
|
assert_eq!(inv_id.to_string(), "#2020-06-0055".to_string());
|
|
}
|
|
|