+TransactionLine

This commit is contained in:
Erik Zscheile 2019-12-22 22:18:45 +01:00
parent cf4dadd151
commit 04ec2f320e
3 changed files with 306 additions and 2 deletions

111
Cargo.lock generated
View file

@ -1,5 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "bstr"
version = "0.2.8"
@ -18,6 +24,17 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
[[package]]
name = "chrono"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
dependencies = [
"num-integer",
"num-traits",
"time",
]
[[package]]
name = "csv"
version = "1.1.1"
@ -115,6 +132,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "fixed"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1343b84a06a392d5475cb9884bf21bd461b7af544bee131f4d954b0fbe56e2a2"
dependencies = [
"typenum",
]
[[package]]
name = "itoa"
version = "0.4.4"
@ -152,6 +178,46 @@ dependencies = [
"winapi",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30"
[[package]]
name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
dependencies = [
"autocfg",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "0.4.30"
@ -174,9 +240,12 @@ dependencies = [
name = "rdcsv"
version = "0.1.0"
dependencies = [
"chrono",
"csv",
"encoding",
"fixed",
"readfilez",
"string_cache",
]
[[package]]
@ -189,6 +258,12 @@ dependencies = [
"memmap",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "regex-automata"
version = "0.1.8"
@ -210,6 +285,25 @@ version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
[[package]]
name = "siphasher"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83da420ee8d1a89e640d0948c646c1c088758d3a3c538f943bfa97bdac17929d"
[[package]]
name = "string_cache"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
"serde",
]
[[package]]
name = "syn"
version = "0.15.44"
@ -221,6 +315,23 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
dependencies = [
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "typenum"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
[[package]]
name = "unicode-xid"
version = "0.1.0"

View file

@ -6,5 +6,8 @@ edition = "2018"
[dependencies]
csv = "1.1"
chrono = "0.4"
encoding = "0.2"
fixed = "0.5"
readfilez = "0.2"
string_cache = "0.8"

View file

@ -1,3 +1,154 @@
use chrono::naive::NaiveDate;
use string_cache::DefaultAtom;
use std::{collections::HashSet, fmt};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum KontoDaten {
None,
Old {
knr: u64,
blz: u32,
},
New {
iban: String,
bic: String,
},
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Waehrung {
EUR,
USD,
}
impl fmt::Display for Waehrung {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", match self {
Waehrung::EUR => "EUR",
Waehrung::USD => "USD",
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct WaehrungParseError;
impl std::error::Error for WaehrungParseError {}
impl fmt::Display for WaehrungParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid currency")
}
}
impl std::str::FromStr for Waehrung {
type Err = WaehrungParseError;
fn from_str(s: &str) -> Result<Self, WaehrungParseError> {
Ok(match s {
"EUR" => Waehrung::EUR,
"USD" => Waehrung::USD,
_ => return Err(WaehrungParseError),
})
}
}
type TransactionValue = fixed::types::U21F11;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum TransactionDirection {
Haben, // H = Gutschrift
Soll, // S = Belastung
}
impl fmt::Display for TransactionDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", match self {
TransactionDirection::Haben => "H",
TransactionDirection::Soll => "S",
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct TransactionDirectionParseError;
impl std::error::Error for TransactionDirectionParseError {}
impl fmt::Display for TransactionDirectionParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid H/S")
}
}
impl std::str::FromStr for TransactionDirection {
type Err = TransactionDirectionParseError;
fn from_str(s: &str) -> Result<Self, TransactionDirectionParseError> {
Ok(match s {
"H" => TransactionDirection::Haben,
"S" => TransactionDirection::Soll,
_ => return Err(TransactionDirectionParseError),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct TransactionLine {
d_buchungs: NaiveDate,
d_valuta: NaiveDate,
// ignore 3. column = "Auftraggeber/Zahlungsempfänger",
// because that should be nearly equivalent for every row
p_other: DefaultAtom,
konto_data: KontoDaten,
verwendungszw: String,
kref: String,
waehrung: Waehrung,
umsatz: TransactionValue,
direction: TransactionDirection,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct Transactions {
pothc: HashSet<DefaultAtom>,
elems: Vec<TransactionLine>,
}
impl std::ops::Deref for Transactions {
type Target = [TransactionLine];
#[inline]
fn deref(&self) -> &[TransactionLine] {
&self.elems[..]
}
}
impl Transactions {
#[inline]
pub fn new() -> Self {
Default::default()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.elems.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.elems.len()
}
pub fn push(&mut self, mut tl: TransactionLine) {
if let Some(x) = self.pothc.get(&tl.p_other) {
tl.p_other = x.clone();
} else {
self.pothc.insert(tl.p_other.clone());
}
self.elems.push(tl);
}
}
fn main() {
use encoding::types::Encoding;
@ -10,15 +161,54 @@ fn main() {
.decode(&*fh, encoding::types::DecoderTrap::Replace)
.expect("got invalid latin-1 data");
std::mem::drop(fh);
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.flexible(true)
.has_headers(false)
.from_reader(dat.as_bytes());
for result in rdr.records() {
let mut recsit = rdr.records().skip(8);
assert_eq!(recsit.next().unwrap().unwrap(), vec!["Buchungstag", "Valuta", "Auftraggeber/Zahlungsempfänger", "Empfänger/Zahlungspflichtiger", "Konto-Nr.", "IBAN", "BLZ", "BIC", "Vorgang/Verwendungszweck", "Kundenreferenz", "Währung", "Umsatz", " "]);
println!("\nData:");
for result in recsit {
let record = result.expect("got invalid line");
println!("{:?}", record);
assert_eq!(record.len(), 13);
if record[1].is_empty() {
// finalizer line -> ignore
break;
}
// decode KontoDaten
let r_e: &[u8] = &[record[4].is_empty() as u8, record[5].is_empty() as u8, record[6].is_empty() as u8, record[7].is_empty() as u8];
let konto_data = match &*r_e {
&[0, 1, 0, 1] => KontoDaten::Old {
knr: record[4].parse().expect("invalid Konto-Nr."),
blz: record[6].parse().expect("invalid BLZ"),
},
&[1, 0, 1, 0] => KontoDaten::New {
iban: record[5].to_string(),
bic: record[7].to_string(),
},
&[1, 1, 1, 1] => KontoDaten::None,
_ => unimplemented!("unsupported KontoData encoding: {:?} :in: {:?}", &r_e, &record),
};
let tl = TransactionLine {
d_buchungs: NaiveDate::parse_from_str(&record[0], "%d.%m.%Y").expect("invalid date format"),
d_valuta: NaiveDate::parse_from_str(&record[1], "%d.%m.%Y").expect("invalid date format"),
p_other: DefaultAtom::from(&record[3]),
konto_data,
verwendungszw: record[8].to_string(),
kref: record[9].to_string(),
waehrung: record[10].parse().unwrap(),
umsatz: record[11].replace('.', "").replace(',', ".").parse().expect("invalid Umsatz"),
direction: record[12].parse().unwrap(),
};
println!("{:?}", tl);
}
}
}