use std::io;
use std::io::{BufReader, Read, BufRead, Error, ErrorKind};
use std::str::FromStr;
use chrono::prelude::*;
use bigdecimal::BigDecimal;
use encoding::all::ISO_8859_1;
use encoding::{Encoding, DecoderTrap};
use GenericTransaction;
use csv;
pub struct Argenta;
enum ArgentaAccountType {
GiroPlus,
}
impl ArgentaAccountType {
fn from_string(s: &str) -> io::Result<ArgentaAccountType> {
Ok(match s.trim() {
"Giro +" => ArgentaAccountType::GiroPlus,
_ => return Err(Error::new(ErrorKind::InvalidData, "Unrecognised Argenta account type"))
})
}
}
struct ArgentaTransactionIterator<'a, R: Read + 'a> {
origin: ::Iban,
iter: csv::ByteRecords<'a, R>,
}
fn parse_belgian_date(s: &str) -> Date<Utc> {
let v = s.split("-").map(|x| x.parse().unwrap()).collect::<Vec<i32>>();
Utc.ymd(v[2], v[1] as u32, v[0] as u32)
}
impl<'a, R: Read + 'a> ArgentaTransactionIterator<'a, R> {
fn transaction_from_line(&self, s: Vec<Vec<u8>>) -> io::Result<GenericTransaction> {
let s = s.into_iter()
.map(|x| {
ISO_8859_1.decode(&x, DecoderTrap::Strict)
.unwrap()
}).collect::<Vec<_>>();
debug!("Parsing {:?}", s);
Ok(GenericTransaction {
settlement_date: parse_belgian_date(&s[0]),
id: s[1].clone(),
description: s[2].clone(),
amount: BigDecimal::from_str(&s[3].replace('.',"").replace(',',".")).unwrap(),
currency: s[4].clone(),
date: parse_belgian_date(&s[5]),
origin_account: self.origin.clone(),
counterparty_account: s[6].parse()?,
counterparty_name: s[7].clone(),
message_1: s[8].clone(),
message_2: s[9].clone(),
})
}
}
impl<'a, R: Read> Iterator for ArgentaTransactionIterator<'a, R> {
type Item = Box<::Transaction>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().and_then(|record| {
let record = record.unwrap();
self.transaction_from_line(record).map(|a| Box::new(a) as Box<::Transaction>).ok()
})
}
}
pub struct ArgentaTransactions<R> {
iban: ::Iban,
account_type: ArgentaAccountType,
reader: csv::Reader<R>,
}
impl<R: Read, Rt: Read> ::Transactions<R> for ArgentaTransactions<Rt> {
fn get_origin_account(&self) -> Option<::Iban> {
Some(self.iban.clone())
}
fn read_transactions<'a>(&'a mut self) -> Box<Iterator<Item=Box<::Transaction>> + 'a> {
Box::new(ArgentaTransactionIterator {
origin: self.iban.clone(),
iter: self.reader.byte_records(),
})
}
}
impl Argenta {
fn recognise_transaction_file_impl<R: Read + 'static>(reader: &mut BufReader<R>) -> io::Result<(::Iban, ArgentaAccountType)> {
let mut line = String::new();
reader.read_line(&mut line).and_then(|amount| {
if amount == 0 || !line.starts_with("Nr v/d rekening :;") {
Err(Error::new(ErrorKind::InvalidInput, "Not an Argenta CSV"))
} else {
Ok(line.split(';').skip(1))
}
}).and_then(|mut first_line| {
match first_line.next() {
Some(e) => e.parse::<::Iban>().and_then(|iban| {Ok((first_line, iban))}),
None => Err(Error::new(ErrorKind::InvalidInput, "File does not contain Argenta IBAN"))
}
}).and_then(|(mut first_line, iban)| {
match first_line.next() {
Some(e) => ArgentaAccountType::from_string(e).and_then(|acc_t| {
Ok((iban, acc_t))
}),
None => Err(Error::new(ErrorKind::InvalidInput, "File does not contain a valid Argenta account type"))
}
}).and_then(|(iban, account_type)| {
match reader.read_line(&mut line) {
Err(e) => Err(e),
Ok(0) => Err(Error::new(ErrorKind::UnexpectedEof, "File is not valid Argenta data")),
Ok(_) => Ok((iban, account_type))
}
})
}
}
impl ::Bank for Argenta {
fn get_name() -> &'static str {
"Argenta Spaarbank"
}
fn recognise_transaction_file<R: Read + 'static>(reader: &mut BufReader<R>) -> bool {
Self::recognise_transaction_file_impl(reader).is_ok()
}
fn read_transaction_file<R: Read + 'static>(mut reader: BufReader<R>) -> io::Result<Box<::Transactions<R>>> {
Self::recognise_transaction_file_impl(&mut reader).and_then(
|(iban, account_type)| {
let reader = csv::Reader::from_reader(reader)
.has_headers(false)
.delimiter(b';');
Ok(Box::new(ArgentaTransactions {
iban: iban,
account_type: account_type,
reader: reader,
}) as Box<::Transactions<R>>)
})
}
}
#[test]
fn recognise_argenta() {
use std::fs::File;
use Bank;
use std::io::{Seek, SeekFrom};
let f = File::open("tests/test_argenta.csv").unwrap();
let mut reader = BufReader::new(f);
assert!(Argenta::recognise_transaction_file(&mut reader));
reader.seek(SeekFrom::Start(0)).unwrap();
let mut transactions = Argenta::read_transaction_file(reader).unwrap();
let transactions = transactions.read_transactions().collect::<Vec<_>>();
assert_eq!(transactions.len(), 2);
assert_eq!(transactions[0].settlement_date(), Utc.ymd(2017, 03, 22));
assert_eq!(*transactions[0].amount(), BigDecimal::from_str("-80.280000").unwrap());
}
#[test]
fn read_argenta() {
use std::fs::File;
use Bank;
use std::io::Seek;
let f = File::open("tests/test_argenta.csv").unwrap();
let mut reader = BufReader::new(f);
let mut transactions = Argenta::read_transaction_file(reader).unwrap();
let transactions = transactions.read_transactions().collect::<Vec<_>>();
assert_eq!(transactions.len(), 2);
assert_eq!(transactions[0].settlement_date(), Utc.ymd(2017, 03, 22));
assert_eq!(*transactions[0].amount(), BigDecimal::from_str("-80.280000").unwrap());
}
#[test]
fn test_generic_reader() {
use std::fs::File;
use Bank;
use std::io::Seek;
let f = File::open("tests/test_argenta.csv").unwrap();
let mut reader = BufReader::new(f);
let mut transactions = ::read_transaction_file(reader).unwrap();
let transactions = transactions.read_transactions().collect::<Vec<_>>();
assert_eq!(transactions.len(), 2);
assert_eq!(transactions[0].settlement_date(), Utc.ymd(2017, 03, 22));
assert_eq!(*transactions[0].amount(), BigDecimal::from_str("-80.280000").unwrap());
}