Refactor CSV-based Importers

korrat
Nov 14, 2023, 11:56 AM
UO34MAAGCLHTUGNRZQUNIS32ZE354N72IMGZ76THUBKR7TDT6L2QC

Dependencies

  • [2] D6LJRTWX Add importers for ebase accounts
  • [3] VXPFYRI2 Include more information in Amazon gift card balance imports
  • [4] EYMZ5JWP Include more information in Apple store balance imports
  • [5] JUYXTWXP Change the name of metadata keys for imported data
  • [6] 4WYI5U7Y Upgrade dependencies
  • [7] TSSWMQVU Change date format for Amazon order history importer
  • [8] I2P2FTLE add basic parser for german decimals
  • [9] RCS5VP3A Add an importer for PayPal account statements
  • [10] PCHAKXNM Add an importer for Fidor account statements
  • [11] 362NCCMX Add importer for Apple Store
  • [12] 5S4MZHL5 pretty print decimals using icu
  • [13] ZVTVMOZQ Upgrade dependencies
  • [14] ONRIF4V7 add basic snapshot test for pretty printer
  • [15] WCCQYDLP Move linking logic from uniondepot importer to runner
  • [16] R7S2CWF7 Add type for account segments
  • [17] 6MR76MLL Replace build script with cargo-px
  • [18] 2JBFREZG enable additional warnings
  • [19] UESS5YZE migrate dependencies into workspace manifest
  • [20] XQHYMSDY Add importer for Union Investment transactions
  • [21] GVEI7KND Add a importer component for CSV files
  • [22] 6A5YLGWV Add an importer for the VR-Bank CSV format
  • [23] TB2QGHXN Upgrade dependencies
  • [24] 24CCPM5O Update dependencies
  • [25] R5K55SCB Move tagging of directives with source to framework runner
  • [26] BDLVPDJZ Add a importer account for BW bank portfolios
  • [27] RI7HQBYA Add generator and parser for ISO20022 messages
  • [28] NQ455YWR Extract balance deduplication logic into shared utility
  • [29] KB7Y4PJI Implement importers for Amazon accounts
  • [30] 2Z4EGCWQ Update dependencies
  • [*] JQJTN6N5 Fix issue with non-sync decimal formatter
  • [*] SLTVZLYX Upgrade dependencies
  • [*] YDK6X6PP add a library of important types for beancount

Change contents

  • edit in importers/vr-bank/src/lib.rs at line 21
    [7.672]
    [7.672]
    use iso_currency::Currency;
  • edit in importers/vr-bank/src/lib.rs at line 24
    [7.729]
    [7.729]
    use rust_decimal::Decimal;
  • edit in importers/vr-bank/src/lib.rs at line 29
    [7.820][7.820:858]()
    use time::macros::format_description;
  • edit in importers/vr-bank/src/lib.rs at line 153
    [7.4095]
    [7.4095]
    type Record<'de> = Record<'de>;
  • replacement in importers/vr-bank/src/lib.rs at line 155
    [7.4096][7.4096:4221]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_transaction_date(record))
    [7.4096]
    [7.4221]
    fn date(&self, record: Record) -> Date {
    Date::max(record.booking_date, record.value_date)
  • replacement in importers/vr-bank/src/lib.rs at line 162
    [7.4292][7.4292:4322]()
    record: &csv::Record,
    [7.4292]
    [7.4322]
    record: Record,
  • replacement in importers/vr-bank/src/lib.rs at line 164
    [7.4369][7.4369:4518]()
    let date = parse_transaction_date(record)?;
    let commodity = Commodity::try_from(&record[12]).map_err(|_err| -> Error { todo!() })?;
    [7.4369]
    [7.4518]
    let commodity =
    Commodity::try_from(record.currency.code()).expect("currencies are valid commodities");
  • replacement in importers/vr-bank/src/lib.rs at line 169
    [7.4582][7.4582:4639]()
    let mut transaction = Transaction::on(date);
    [7.4582]
    [7.4639]
    let mut transaction = Transaction::on(record.value_date);
  • replacement in importers/vr-bank/src/lib.rs at line 171
    [7.4640][7.4640:4714]()
    transaction.set_payee(&record[6]).set_narration(&record[10]);
    [7.4640]
    [7.4714]
    transaction
    .set_payee(record.transaction_partner_name)
    .set_narration(record.purpose);
  • edit in importers/vr-bank/src/lib.rs at line 178
    [7.4826][7.4826:5098]()
    let amount = {
    let amount = &record[11];
    let amount = german_decimal::parse(amount).map_err(|_| -> Error { todo!() })?;
    Amount::new(amount, commodity)
    };
    let opposite_iban = &record[7];
  • replacement in importers/vr-bank/src/lib.rs at line 181
    [7.5191][7.5191:5227]()
    .get(opposite_iban)
    [7.5191]
    [7.5227]
    .get(record.transaction_partner_iban)
  • replacement in importers/vr-bank/src/lib.rs at line 187
    [7.5415][7.5415:5450]()
    opposite_iban,
    [7.5415]
    [7.5450]
    record.transaction_partner_iban,
  • replacement in importers/vr-bank/src/lib.rs at line 190
    [7.5520][7.5520:5568]()
    posting.set_amount(amount);
    [7.5520]
    [7.5568]
    posting.set_amount(Amount::new(record.amount, commodity));
  • edit in importers/vr-bank/src/lib.rs at line 198
    [7.5767][7.5767:6019]()
    let balance = {
    let amount = {
    let amount = &record[13];
    let amount = german_decimal::parse(amount).map_err(|_| -> Error { todo!() })?;
    Amount::new(amount, commodity)
    };
  • replacement in importers/vr-bank/src/lib.rs at line 199
    [7.6020][7.6020:6118]()
    Balance::new(date.next_day().unwrap(), self.config.account.base(), amount)
    };
    [7.6020]
    [7.6118]
    let balance = Balance::new(
    record.value_date.next_day().unwrap(),
    self.config.account.base(),
    Amount::new(record.balance, commodity),
    );
  • edit in importers/vr-bank/src/lib.rs at line 248
    [7.7249]
    [7.7249]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Betrag", with = "german_decimal::serde")]
    amount: Decimal,
    #[serde(rename = "Saldo nach Buchung", with = "german_decimal::serde")]
    balance: Decimal,
  • edit in importers/vr-bank/src/lib.rs at line 257
    [7.7250]
    [7.7250]
    #[serde(rename = "Buchungstag", with = "dmy")]
    booking_date: Date,
    #[serde(rename = "Waehrung")]
    currency: Currency,
    #[serde(rename = "Verwendungszweck")]
    purpose: &'r str,
    #[serde(rename = "IBAN Zahlungsbeteiligter")]
    transaction_partner_iban: &'r str,
    #[serde(rename = "Name Zahlungsbeteiligter")]
    transaction_partner_name: &'r str,
    #[serde(rename = "Valutadatum", with = "dmy")]
    value_date: Date,
    }
  • edit in importers/vr-bank/src/lib.rs at line 304
    [7.7851][7.7851:7852](),[7.9105][7.9105:9349]()
    fn parse_transaction_date(record: &csv::Record) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    const DATE_COLUMN_INDEX: usize = 5;
    const DATE_FORMAT: &[FormatItem] = format_description!("[day].[month].[year]");
  • replacement in importers/vr-bank/src/lib.rs at line 305
    [7.9350][7.9350:9430]()
    Date::parse(&record[DATE_COLUMN_INDEX], DATE_FORMAT).map_err(|_| todo!())
    }
    [7.9350]
    time::serde::format_description!(dmy, Date, "[day].[month].[year]");
  • edit in importers/vr-bank/Cargo.toml at line 20
    [7.10014]
    [7.10014]
    iso_currency.workspace = true
  • edit in importers/vr-bank/Cargo.toml at line 22
    [7.10046]
    [7.10046]
    rust_decimal.workspace = true
  • edit in importers/uniondepot/src/lib.rs at line 21
    [7.705]
    [7.705]
    use rust_decimal::Decimal;
  • edit in importers/uniondepot/src/lib.rs at line 26
    [7.796][7.796:847]()
    use time::format_description::well_known::Iso8601;
  • edit in importers/uniondepot/src/lib.rs at line 166
    [7.4502]
    [7.4502]
    type Record<'de> = Record<'de>;
  • replacement in importers/uniondepot/src/lib.rs at line 168
    [7.4503][7.4503:4622]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_order_date(record))
    [7.4503]
    [7.4622]
    fn date(&self, record: Record) -> Date {
    record.date
  • replacement in importers/uniondepot/src/lib.rs at line 175
    [7.4693][7.4693:4723]()
    record: &csv::Record,
    [7.4693]
    [7.4723]
    record: Record,
  • replacement in importers/uniondepot/src/lib.rs at line 177
    [7.4770][7.4770:5136]()
    const DEPOT_ID_COLUMN_INDEX: usize = 1;
    const PRICE_COLUMN_INDEX: usize = 5;
    const TOTAL_COLUMN_INDEX: usize = 7;
    const TRANSACTION_KIND_COLUMN_INDEX: usize = 3;
    const UNIT_COLUMN_INDEX: usize = 8;
    const UNITS_COLUMN_INDEX: usize = 6;
    let context = TemplateContext::try_from(&record[DEPOT_ID_COLUMN_INDEX])?;
    [7.4770]
    [7.5136]
    let context = TemplateContext::try_from(record.depot_id)?;
  • replacement in importers/uniondepot/src/lib.rs at line 180
    [7.5202][7.5202:5272]()
    let stock = self.lookup_commodity(context.position).unwrap();
    [7.5202]
    [7.5272]
    let stock = self.lookup_commodity(record.depot_id.position).unwrap();
  • replacement in importers/uniondepot/src/lib.rs at line 182
    [7.5273][7.5273:5319]()
    let date = parse_order_date(record)?;
    [7.5273]
    [7.5319]
    let date = record.date;
  • replacement in importers/uniondepot/src/lib.rs at line 184
    [7.5320][7.5320:5713]()
    let currency: Commodity = self.lookup_currency(&record[UNIT_COLUMN_INDEX])?;
    let units = {
    let value: &str = &record[UNITS_COLUMN_INDEX];
    german_decimal::parse(value).map_err(|_| todo!())
    }?;
    let unit_cost = {
    let price = &record[PRICE_COLUMN_INDEX];
    german_decimal::parse(price).map_err(|_| todo!())
    }?;
    [7.5320]
    [7.5713]
    let currency: Commodity = self.lookup_currency(record.currency)?;
    let units = record.shares;
    let unit_cost = record.unit_cost;
  • replacement in importers/uniondepot/src/lib.rs at line 188
    [7.5714][7.5714:5969]()
    let total = {
    let value: &str = &record[TOTAL_COLUMN_INDEX];
    german_decimal::parse(value).map_err(|_| todo!())
    }?;
    let total = Amount {
    amount: total,
    commodity: currency,
    };
    [7.5714]
    [7.5969]
    let total = Amount::new(record.total, currency);
  • replacement in importers/uniondepot/src/lib.rs at line 190
    [7.5970][7.5970:6069]()
    let transaction_kind = TransactionKind::from_str(&record[TRANSACTION_KIND_COLUMN_INDEX])?;
    [7.5970]
    [7.6069]
    let transaction_kind = record.transaction_kind;
  • edit in importers/uniondepot/src/lib.rs at line 334
    [7.10824]
    [7.10824]
    }
    }
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "DATUM")]
    date: Date,
    #[serde(rename = "UNIONDEPOTNUMMER")]
    depot_id: DepotId<'r>,
    #[serde(rename = "TRANSAKTIONSART")]
    transaction_kind: TransactionKind,
    #[serde(rename = "FONDSPREIS", with = "german_decimal::serde")]
    unit_cost: Decimal,
    #[serde(rename = "ANTEILE", with = "german_decimal::serde")]
    shares: Decimal,
    #[serde(rename = "VOLUMEN", with = "german_decimal::serde")]
    total: Decimal,
    #[serde(rename = "EINHEIT")]
    currency: &'r str,
    }
    #[derive(Clone, Copy, Debug, Deserialize)]
    #[serde(try_from = "&str")]
    struct DepotId<'r> {
    depot: &'r str,
    position: &'r str,
    }
    impl<'i> TryFrom<&'i str> for DepotId<'i> {
    type Error = Error;
    fn try_from(value: &'i str) -> Result<Self, Self::Error> {
    let (depot, position) = value.split_once('/').ok_or_else(|| todo!())?;
    Ok(Self { depot, position })
  • replacement in importers/uniondepot/src/lib.rs at line 395
    [7.11225][7.11225:11277]()
    impl<'i> TryFrom<&'i str> for TemplateContext<'i> {
    [7.11225]
    [7.11277]
    impl<'r> TryFrom<DepotId<'r>> for TemplateContext<'r> {
  • replacement in importers/uniondepot/src/lib.rs at line 398
    [7.11302][7.11302:11444]()
    fn try_from(value: &'i str) -> Result<Self, Self::Error> {
    let (depot, position) = value.split_once('/').ok_or_else(|| todo!())?;
    [7.11302]
    [7.11444]
    fn try_from(value: DepotId<'r>) -> Result<Self, Self::Error> {
    let DepotId { depot, position } = value;
  • replacement in importers/uniondepot/src/lib.rs at line 434
    [7.12175][7.12175:12205]()
    #[derive(Clone, Copy, Debug)]
    [7.12175]
    [7.12205]
    #[derive(Clone, Copy, Debug, Deserialize)]
  • edit in importers/uniondepot/src/lib.rs at line 436
    [7.12228]
    [7.12228]
    #[serde(rename = "Verkauf wg. Depotgebühr UID mit Postbox")]
  • edit in importers/uniondepot/src/lib.rs at line 438
    [7.12244]
    [7.12244]
    #[serde(rename = "Ertragsausschüttung")]
  • edit in importers/uniondepot/src/lib.rs at line 441
    [7.12262]
    [7.12262]
    #[serde(rename = "Fremde Gebühren")]
  • edit in importers/uniondepot/src/lib.rs at line 444
    [7.12279]
    [7.12279]
    #[serde(rename = "Kauf")]
  • edit in importers/uniondepot/src/lib.rs at line 447
    [7.12293]
    [7.12293]
    #[serde(rename = "Storno wegen Rücklastschrift")]
  • edit in importers/uniondepot/src/lib.rs at line 450
    [7.12307]
    [7.12307]
    #[serde(rename = "Verkauf")]
  • edit in importers/uniondepot/src/lib.rs at line 484
    [7.13427][7.13427:13632](),[7.13835][7.13835:13837]()
    fn parse_order_date(record: &csv::Record) -> Result<Date, Error> {
    const ORDER_DATE_COLUMN_INDEX: usize = 0;
    Date::parse(&record[ORDER_DATE_COLUMN_INDEX], &Iso8601::PARSING).map_err(|_| todo!())
    }
  • edit in importers/uniondepot/Cargo.toml at line 20
    [7.14421]
    [7.14421]
    rust_decimal.workspace = true
  • edit in importers/paypal/src/lib.rs at line 21
    [7.701][7.701:718]()
    use csv::Record;
  • edit in importers/paypal/src/lib.rs at line 27
    [7.848]
    [7.848]
    use rust_decimal::Decimal;
  • edit in importers/paypal/src/lib.rs at line 33
    [7.985][7.985:1023]()
    use time::macros::format_description;
  • replacement in importers/paypal/src/lib.rs at line 105
    [7.2933][7.2933:3255]()
    fn extract_record(&self, record: &Record) -> Result<(Transaction, Balance), Error> {
    let date = parse_date(record).unwrap();
    let time =
    Time::parse(&record[1], format_description!("[hour]:[minute]:[second]")).unwrap();
    let tz = time_tz::timezones::get_by_name(&record[2]).unwrap();
    [7.2933]
    [7.3255]
    fn extract_record(&self, record: Record) -> Result<(Transaction, Balance), Error> {
    dbg!(record);
  • replacement in importers/paypal/src/lib.rs at line 108
    [7.3256][7.3256:3397]()
    let timestamp = date.with_time(time).assume_timezone(tz).unwrap();
    let commodity = Commodity::try_from(&record[4]).unwrap();
    [7.3256]
    [7.3397]
    let tz = time_tz::timezones::get_by_name(record.timezone).unwrap();
    let timestamp = record
    .date
    .with_time(record.time)
    .assume_timezone(tz)
    .unwrap();
  • replacement in importers/paypal/src/lib.rs at line 116
    [7.3422][7.3422:3467]()
    let currency: &str = &commodity;
    [7.3422]
    [7.3467]
    let currency: &str = &record.currency;
  • replacement in importers/paypal/src/lib.rs at line 122
    [7.3644][7.3644:3701]()
    let mut transaction = Transaction::on(date);
    [7.3644]
    [7.3701]
    let mut transaction = Transaction::on(record.date);
  • replacement in importers/paypal/src/lib.rs at line 124
    [7.3702][7.3702:3823]()
    let kind = TransactionKind::try_from(&record[3])?;
    let (payee, opposite_account) = match kind {
    [7.3702]
    [7.3823]
    let (payee, opposite_account) = match record.description {
  • replacement in importers/paypal/src/lib.rs at line 129
    [7.3972][7.3972:4014]()
    .get(&record[10])
    [7.3972]
    [7.4014]
    .get(record.sender_email)
  • replacement in importers/paypal/src/lib.rs at line 138
    [7.4393][7.4393:4466]()
    self.config.reference_accounts[&record[13]].clone(),
    [7.4393]
    [7.4466]
    self.config.reference_accounts[record.bank_account].clone(),
  • replacement in importers/paypal/src/lib.rs at line 141
    [7.4531][7.4531:4581]()
    non_empty_field(&record[11]),
    [7.4531]
    [7.4581]
    non_empty_field(record.name),
  • replacement in importers/paypal/src/lib.rs at line 144
    [7.4655][7.4655:4697]()
    .get(&record[10])
    [7.4655]
    [7.4697]
    .get(record.sender_email)
  • replacement in importers/paypal/src/lib.rs at line 154
    [7.4941][7.4941:5145]()
    let transaction_id = &record[9];
    if matches!(kind, TransactionKind::Deposit) {
    transaction.add_link(Link::try_from(format!("^paypal.{transaction_id}")).unwrap());
    [7.4941]
    [7.5145]
    if matches!(record.description, TransactionKind::Deposit) {
    transaction.add_link(
    Link::try_from(format!("^paypal.{}", record.transaction_id)).unwrap(),
    );
  • replacement in importers/paypal/src/lib.rs at line 162
    [7.5271][7.5271:5343]()
    .add_meta(common_keys::TRANSACTION_ID, transaction_id);
    [7.5271]
    [7.5343]
    .add_meta(common_keys::TRANSACTION_ID, record.transaction_id);
  • replacement in importers/paypal/src/lib.rs at line 164
    [7.5344][7.5344:5413]()
    if let Some(invoice_id) = non_empty_field(&record[16]) {
    [7.5344]
    [7.5413]
    if let Some(invoice_id) = record.invoice_id {
  • replacement in importers/paypal/src/lib.rs at line 168
    [7.5524][7.5524:5605]()
    if let Some(related_transaction_id) = non_empty_field(&record[17]) {
    [7.5524]
    [7.5605]
    if let Some(related_transaction_id) = record.related_transaction_id {
  • replacement in importers/paypal/src/lib.rs at line 175
    [7.5799][7.5799:5892]()
    let amount = Amount::new(german_decimal::parse(&record[5]).unwrap(), commodity);
    [7.5799]
    [7.5892]
    let amount = Amount::new(record.gross, record.currency);
  • edit in importers/paypal/src/lib.rs at line 187
    [7.6196]
    [7.6196]
    let date = record.date.next_day().unwrap();
  • replacement in importers/paypal/src/lib.rs at line 189
    [7.6268][7.6268:6395]()
    let amount = german_decimal::parse(&record[8]).unwrap();
    let amount = Amount::new(amount, commodity);
    [7.6268]
    [7.6395]
    let amount = Amount::new(record.balance, record.currency);
  • edit in importers/paypal/src/lib.rs at line 191
    [7.6396][7.6396:6446]()
    let date = date.next_day().unwrap();
  • edit in importers/paypal/src/lib.rs at line 192
    [7.4607][7.4607:4608]()
  • edit in importers/paypal/src/lib.rs at line 193
    [7.4699][7.4699:4700]()
  • edit in importers/paypal/src/lib.rs at line 263
    [7.9588]
    [7.9588]
    type Record<'de> = Record<'de>;
  • replacement in importers/paypal/src/lib.rs at line 265
    [7.9589][7.9589:9702]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_date(record))
    [7.9589]
    [7.9702]
    fn date(&self, record: Record) -> Date {
    record.date
  • replacement in importers/paypal/src/lib.rs at line 272
    [7.9773][7.9773:9803]()
    record: &csv::Record,
    [7.9773]
    [7.9803]
    record: Record,
  • edit in importers/paypal/src/lib.rs at line 363
    [7.12238]
    [7.12238]
    }
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Datum", with = "dmy")]
    date: Date,
    #[serde(rename = "Uhrzeit", with = "hms")]
    time: Time,
    #[serde(rename = "Zeitzone")]
    timezone: &'r str,
    #[serde(rename = "Beschreibung")]
    description: TransactionKind,
    #[serde(rename = "Währung")]
    currency: Commodity,
    #[serde(rename = "Brutto", with = "german_decimal::serde")]
    gross: Decimal,
    #[serde(rename = "Guthaben", with = "german_decimal::serde")]
    balance: Decimal,
    #[serde(rename = "Transaktionscode")]
    transaction_id: &'r str,
    #[serde(rename = "Absender E-Mail-Adresse")]
    sender_email: &'r str,
    #[serde(rename = "Name")]
    name: &'r str,
    #[serde(rename = "Bankkonto")]
    bank_account: &'r str,
    #[serde(rename = "Rechnungsnummer")]
    invoice_id: Option<&'r str>,
    #[serde(borrow, rename = "Zugehöriger Transaktionscode")]
    related_transaction_id: Option<&'r str>,
  • replacement in importers/paypal/src/lib.rs at line 459
    [7.13456][7.13456:13501]()
    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    [7.13456]
    [7.13501]
    #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
  • edit in importers/paypal/src/lib.rs at line 461
    [7.13524]
    [7.13524]
    #[serde(rename = "Rückbuchung")]
  • edit in importers/paypal/src/lib.rs at line 463
    [7.13540]
    [7.13540]
    #[serde(rename = "Allgemeine Währungsumrechnung")]
  • edit in importers/paypal/src/lib.rs at line 466
    [7.13564]
    [7.13564]
    #[serde(
    alias = "Allgemeine Abbuchung – Bankkonto",
    rename = "Bankgutschrift auf PayPal-Konto"
    )]
  • edit in importers/paypal/src/lib.rs at line 472
    [7.13577]
    [7.13577]
    #[serde(
    alias = "Allgemeine Zahlung",
    alias = "Handyzahlung",
    alias = "PayPal Express-Zahlung",
    alias = "Rückzahlung",
    alias = "Spendenzahlung",
    alias = "Website-Zahlung",
    rename = "Zahlung im Einzugsverfahren mit Zahlungsrechnung"
    )]
  • replacement in importers/paypal/src/lib.rs at line 513
    [7.14400][7.14400:14708]()
    fn parse_date(record: &Record) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    const DATE_COLUMN_INDEX: usize = 0;
    const DATE_FORMAT: &[FormatItem] = format_description!("[day].[month].[year]");
    Date::parse(&record[DATE_COLUMN_INDEX], DATE_FORMAT).map_err(|_| todo!())
    }
    [7.14400]
    [7.14708]
    time::serde::format_description!(dmy, Date, "[day].[month].[year]");
    time::serde::format_description!(hms, Time, "[hour]:[minute]:[second]");
  • edit in importers/fidor/src/lib.rs at line 32
    [7.906][7.906:944]()
    use time::macros::format_description;
  • replacement in importers/fidor/src/lib.rs at line 108
    [7.2813][7.2813:2843]()
    record: &csv::Record,
    [7.2813]
    [7.2843]
    record: Record,
  • replacement in importers/fidor/src/lib.rs at line 112
    [7.2912][7.2912:2950]()
    let (payee, iban) = record[2]
    [7.2912]
    [7.2950]
    let (payee, iban) = record
    .description
  • edit in importers/fidor/src/lib.rs at line 179
    [7.4886]
    [7.4886]
    type Record<'de> = Record<'de>;
  • replacement in importers/fidor/src/lib.rs at line 181
    [7.4887][7.4887:5012]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_transaction_date(record))
    [7.4887]
    [7.5012]
    fn date(&self, record: Record) -> Date {
    record.date
  • replacement in importers/fidor/src/lib.rs at line 188
    [7.5083][7.5083:5113]()
    record: &csv::Record,
    [7.5083]
    [7.5113]
    record: Record,
  • replacement in importers/fidor/src/lib.rs at line 190
    [7.5160][7.5160:5260]()
    let date = parse_transaction_date(record)?;
    let commodity = self.config.commodity;
    [7.5160]
    [7.5260]
    let date = record.date;
  • replacement in importers/fidor/src/lib.rs at line 197
    [7.5418][7.5418:5489]()
    let transaction_kind = TransactionKind::try_from(&record[1])?;
    [7.5418]
    [7.5489]
    let transaction_kind = record.transaction_kind;
  • replacement in importers/fidor/src/lib.rs at line 199
    [7.5490][7.5490:5696]()
    let amount = {
    let amount = &record[3];
    let amount = german_decimal::parse(amount).map_err(|_| -> Error { todo!() })?;
    Amount::new(amount, commodity)
    };
    [7.5490]
    [7.5696]
    let amount = Amount::new(record.amount, self.config.commodity);
  • edit in importers/fidor/src/lib.rs at line 360
    [7.11174]
    [7.11174]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Datum", with = "dmy")]
    date: Date,
    #[serde(rename = "Beschreibung")]
    transaction_kind: TransactionKind<'r>,
    #[serde(rename = "Beschreibung2")]
    description: &'r str,
    #[serde(rename = "Wert", with = "german_decimal::serde")]
    amount: Decimal,
    }
  • replacement in importers/fidor/src/lib.rs at line 404
    [7.11776][7.11776:11806]()
    #[derive(Clone, Copy, Debug)]
    [7.11776]
    [7.11806]
    #[derive(Clone, Copy, Debug, Deserialize)]
    #[serde(try_from = "&str")]
  • replacement in importers/fidor/src/lib.rs at line 487
    [7.14646][7.14646:14971]()
    fn parse_transaction_date(record: &csv::Record) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    const DATE_COLUMN_INDEX: usize = 0;
    const DATE_FORMAT: &[FormatItem] = format_description!("[day].[month].[year]");
    Date::parse(&record[DATE_COLUMN_INDEX], DATE_FORMAT).map_err(|_| todo!())
    }
    [7.14646]
    time::serde::format_description!(dmy, Date, "[day].[month].[year]");
  • edit in importers/ebase/src/transactions.rs at line 1
    [2.37]
    [2.38]
    use core::fmt::Write;
  • edit in importers/ebase/src/transactions.rs at line 18
    [2.550][2.550:567]()
    use csv::Record;
  • edit in importers/ebase/src/transactions.rs at line 27
    [2.781][2.781:822]()
    use tap::Tap as _;
    use tap::TapFallible;
  • edit in importers/ebase/src/transactions.rs at line 28
    [2.860][2.860:887]()
    use time::parsing::Parsed;
  • edit in importers/ebase/src/transactions.rs at line 29
    [2.903]
    [2.903]
    mod dmy_short {
    pub fn deserialize<'de, D>(deserializer: D) -> Result<time::Date, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
    impl serde::de::Visitor<'_> for Visitor {
    type Value = time::Date;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("a date in dd.mm.yy format")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    const BASE_YEAR: i32 = 2000;
    const DATE_FORMAT: &[time::format_description::FormatItem] =
    time::macros::format_description!("[day].[month].[year repr:last_two]");
    let mut parsed = time::parsing::Parsed::new();
    parsed.parse_items(v.as_bytes(), DATE_FORMAT).unwrap();
    let year_last_two = i32::from(parsed.year_last_two().unwrap());
    parsed.set_year(BASE_YEAR + year_last_two);
    parsed.try_into().map_err(E::custom)
    }
    }
  • edit in importers/ebase/src/transactions.rs at line 62
    [2.904]
    [2.904]
    deserializer.deserialize_str(Visitor)
    }
    }
  • edit in importers/ebase/src/transactions.rs at line 226
    [2.5422]
    [2.5422]
    type Record<'de> = Record<'de>;
  • replacement in importers/ebase/src/transactions.rs at line 228
    [2.5423][2.5423:5548]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_transaction_date(record))
    [2.5423]
    [2.5548]
    fn date(&self, record: Record) -> Date {
    record.booking_date
  • replacement in importers/ebase/src/transactions.rs at line 235
    [2.5619][2.5619:5644]()
    record: &Record,
    [2.5619]
    [2.5644]
    record: Record,
  • edit in importers/ebase/src/transactions.rs at line 237
    [2.5691][2.5691:6359]()
    const DEPOT_ID_COLUMN_INDEX: usize = 0;
    const DEPOT_POSITION_COLUMN_INDEX: usize = 1;
    const FUND_CURRENCY_COLUMN_INDEX: usize = 12;
    const FUND_NAME_COLUMN_INDEX: usize = 6;
    const ISIN_COLUMN_INDEX: usize = 7;
    const REFERENCE_COLUMN_INDEX: usize = 2;
    const SHARES_COLUMN_INDEX: usize = 10;
    const PRICE_DATE_COLUMN_INDEX: usize = 13;
    const PAYMENT_AMOUNT_COLUMN_INDEX: usize = 8;
    const PAYMENT_CURRENY_COLUMN_INDEX: usize = 9;
    const TRANSACTION_KIND_COLUMN_INDEX: usize = 4;
    const SHARE_PRICE_COLUMN_INDEX: usize = 11;
    const EXCHANGE_RATE_COLUMN_INDEX: usize = 14;
  • replacement in importers/ebase/src/transactions.rs at line 239
    [2.6399][2.6399:6486](),[2.6486][2.6486:6487](),[2.6487][2.6487:6620]()
    let kind = TransactionKind::try_from(&record[TRANSACTION_KIND_COLUMN_INDEX])?;
    let transaction_id = &record[REFERENCE_COLUMN_INDEX];
    let (reference, ..) = transaction_id.split_once('/').unwrap();
    [2.6399]
    [2.6620]
    let reference = record.reference.transaction_id;
  • replacement in importers/ebase/src/transactions.rs at line 241
    [2.6621][2.6621:6708](),[2.6708][2.6708:6709](),[2.6709][2.6709:6825]()
    let fund_commodity = Commodity::try_from(&record[ISIN_COLUMN_INDEX]).unwrap();
    let depot_id = &record[DEPOT_ID_COLUMN_INDEX];
    let position = &record[DEPOT_POSITION_COLUMN_INDEX];
    [2.6621]
    [2.6825]
    let depot_id = record.depot_id;
    let position = record.depot_position;
  • edit in importers/ebase/src/transactions.rs at line 246
    [2.6983][2.6983:6995](),[2.6995][2.6995:7246](),[2.7246][2.7246:7247](),[2.7247][2.7247:7444](),[2.7444][2.7444:7445](),[2.7445][2.7445:7584](),[2.7584][2.7584:7585](),[2.7585][2.7585:7764](),[2.7764][2.7764:7765](),[2.7765][2.7765:7815]()
    };
    let fund_currency = Commodity::try_from(&record[FUND_CURRENCY_COLUMN_INDEX]).unwrap();
    let share_price = Amount::new(
    german_decimal::parse(&record[SHARE_PRICE_COLUMN_INDEX]).unwrap(),
    fund_currency,
    );
    let shares_amount = Amount::new(
    german_decimal::parse(&record[SHARES_COLUMN_INDEX])
    .map_err(|_| -> Error { todo!() })?,
    fund_commodity,
    );
    let payment_currency = Commodity::try_from(&record[PAYMENT_CURRENY_COLUMN_INDEX])
    .map_err(|_| -> Error { todo!() })?;
    let payment_amount = {
    let amount = parse_decimal_with_precision(&record[PAYMENT_AMOUNT_COLUMN_INDEX], 2)
    .map_err(|_| -> Error { todo!() })?;
    Amount::new(amount, payment_currency)
  • replacement in importers/ebase/src/transactions.rs at line 248
    [2.7827][2.7827:8058]()
    let investment_amount_payment = {
    let investment_amount = &record[15];
    let investment_amount = parse_decimal_with_precision(investment_amount, 2)
    .map_err(|_| -> Error { todo!() })?;
    [2.7827]
    [2.8058]
    let fund_currency = record.fund_currency;
    let share_price = Amount::new(record.share_price, fund_currency);
  • replacement in importers/ebase/src/transactions.rs at line 251
    [2.8059][2.8059:8131]()
    Amount::new(investment_amount, payment_currency)
    };
    [2.8059]
    [2.8131]
    let shares_amount = Amount::new(record.shares, record.isin);
  • replacement in importers/ebase/src/transactions.rs at line 253
    [2.8132][2.8132:8422]()
    let investment_amount_fund = {
    let exchange_rate = Some(&record[EXCHANGE_RATE_COLUMN_INDEX])
    .filter(|value| !value.is_empty())
    .map(german_decimal::parse)
    .transpose()
    .map_err(|_| -> Error { todo!() })?;
    [2.8132]
    [2.8422]
    let payment_amount = Amount::new(record.payment_amount, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 255
    [2.8423][2.8423:8648]()
    exchange_rate.map(|rate| {
    let mut amount = investment_amount_payment.amount * rate;
    amount.rescale(2);
    Amount::new(amount, fund_currency)
    })
    };
    [2.8423]
    [2.8648]
    let investment_amount_payment =
    Amount::new(record.investment_amount, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 258
    [2.8649][2.8649:8808]()
    let fees = german_decimal::parse(&record[26])
    .unwrap()
    .tap_mut(|amount| {
    amount.rescale(2);
    });
    [2.8649]
    [2.8808]
    let investment_amount_fund = record.exchange_rate.map(|rate| {
    let mut amount = investment_amount_payment.amount * rate;
    amount.rescale(2);
    Amount::new(amount, fund_currency)
    });
  • replacement in importers/ebase/src/transactions.rs at line 264
    [2.8809][2.8809:8889]()
    let mut transaction = Transaction::on(parse_transaction_date(record)?);
    [2.8809]
    [2.8889]
    let mut transaction = Transaction::on(record.booking_date);
  • replacement in importers/ebase/src/transactions.rs at line 267
    [2.8952][2.8952:9103]()
    .set_narration(kind.format_narration(&record[FUND_NAME_COLUMN_INDEX]))
    .add_meta(common_keys::TRANSACTION_ID, transaction_id);
    [2.8952]
    [2.9103]
    .set_narration(record.transaction_kind.format_narration(record.fund_name))
    .add_meta(common_keys::TRANSACTION_ID, record.reference.to_string());
  • replacement in importers/ebase/src/transactions.rs at line 270
    [2.9104][2.9104:9125]()
    match kind {
    [2.9104]
    [2.9125]
    match record.transaction_kind {
  • replacement in importers/ebase/src/transactions.rs at line 280
    [2.9591][2.9591:9696]()
    if !fees.is_zero() {
    let fees = Amount::new(fees, payment_currency);
    [2.9591]
    [2.9696]
    if !record.fees.is_zero() {
    let fees = Amount::new(record.fees, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 305
    [2.10573][2.10573:10653]()
    let currency: &str = &payment_currency;
    [2.10573]
    [2.10653]
    let currency: &str = &record.payment_curreny;
  • replacement in importers/ebase/src/transactions.rs at line 343
    [2.12421][2.12421:12506]()
    posting.set_amount(Amount::new(fees, payment_currency));
    [2.12421]
    [2.12506]
    posting.set_amount(Amount::new(record.fees, record.payment_curreny));
  • replacement in importers/ebase/src/transactions.rs at line 362
    [2.13131][2.13131:13236]()
    if !fees.is_zero() {
    let fees = Amount::new(fees, payment_currency);
    [2.13131]
    [2.13236]
    if !record.fees.is_zero() {
    let fees = Amount::new(record.fees, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 379
    [2.13866][2.13866:13929]()
    if payment_currency != fund_currency {
    [2.13866]
    [2.13929]
    if record.payment_curreny != fund_currency {
  • replacement in importers/ebase/src/transactions.rs at line 381
    [2.14038][2.14038:14131]()
    let exchange_rate = german_decimal::parse(&record[14]).unwrap();
    [2.14038]
    [2.14131]
    let exchange_rate = record
    .exchange_rate
    .expect("exchange rate is set when currencies differ");
  • replacement in importers/ebase/src/transactions.rs at line 400
    [2.14703][2.14703:14808]()
    if !fees.is_zero() {
    let fees = Amount::new(fees, payment_currency);
    [2.14703]
    [2.14808]
    if !record.fees.is_zero() {
    let fees = Amount::new(record.fees, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 417
    [2.15433][2.15433:15496]()
    if payment_currency != fund_currency {
    [2.15433]
    [2.15496]
    if record.payment_curreny != fund_currency {
  • replacement in importers/ebase/src/transactions.rs at line 419
    [2.15605][2.15605:15698]()
    let exchange_rate = german_decimal::parse(&record[14]).unwrap();
    [2.15605]
    [2.15698]
    let exchange_rate = record
    .exchange_rate
    .expect("exchange rate is set when currencies differ");
  • replacement in importers/ebase/src/transactions.rs at line 438
    [2.16269][2.16269:16374]()
    if !fees.is_zero() {
    let fees = Amount::new(fees, payment_currency);
    [2.16269]
    [2.16374]
    if !record.fees.is_zero() {
    let fees = Amount::new(record.fees, record.payment_curreny);
  • replacement in importers/ebase/src/transactions.rs at line 463
    [2.17251][2.17251:17331]()
    let currency: &str = &payment_currency;
    [2.17251]
    [2.17331]
    let currency: &str = &record.payment_curreny;
  • replacement in importers/ebase/src/transactions.rs at line 491
    [2.18526][2.18526:18654]()
    let date = parse_date(&record[PRICE_DATE_COLUMN_INDEX])?;
    Price::new(date, fund_commodity, share_price)
    [2.18526]
    [2.18654]
    let date = record.price_date;
    Price::new(date, record.isin, share_price)
  • edit in importers/ebase/src/transactions.rs at line 559
    [2.20955]
    [2.20955]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Buchungsdatum", with = "dmy_short")]
    booking_date: Date,
    #[serde(rename = "Depotnummer")]
    depot_id: &'r str,
    #[serde(rename = "Depotposition")]
    depot_position: &'r str,
    #[serde(rename = "Devisenkurs  (ZW/FW)", with = "german_decimal::serde::opt")]
    exchange_rate: Option<Decimal>,
    #[serde(
    rename = "Entgelt in ZW",
    with = "german_decimal::serde::with_precision::<2>"
    )]
    fees: Decimal,
  • edit in importers/ebase/src/transactions.rs at line 580
    [2.20956]
    [2.20956]
    #[serde(rename = "Fondswährung (FW)")]
    fund_currency: Commodity,
    #[serde(rename = "Fonds")]
    fund_name: &'r str,
    #[serde(
    rename = "Anlagebetrag in ZW",
    with = "german_decimal::serde::with_precision::<2>"
    )]
    investment_amount: Decimal,
    #[serde(rename = "ISIN")]
    isin: Commodity,
    #[serde(
    rename = "Zahlungsbetrag in ZW",
    with = "german_decimal::serde::with_precision::<2>"
    )]
    payment_amount: Decimal,
    #[serde(rename = "Zahlungswährung (ZW)")]
    payment_curreny: Commodity,
    #[serde(rename = "Kursdatum", with = "dmy_short")]
    price_date: Date,
    #[serde(rename = "Ref. Nr.")]
    reference: TransactionReference<'r>,
    #[serde(rename = "Abrechnungskurs in FW", with = "german_decimal::serde")]
    share_price: Decimal,
    #[serde(rename = "Anteile", with = "german_decimal::serde")]
    shares: Decimal,
    #[serde(rename = "Umsatzart")]
    transaction_kind: TransactionKind,
    }
  • replacement in importers/ebase/src/transactions.rs at line 680
    [2.22401][2.22401:22418]()
    #[derive(Debug)]
    [2.22401]
    [2.22418]
    #[derive(Clone, Copy, Debug, Deserialize)]
  • edit in importers/ebase/src/transactions.rs at line 682
    [2.22441]
    [2.22441]
    #[serde(rename = "Entgeltbelastung Verkauf")]
  • edit in importers/ebase/src/transactions.rs at line 684
    [2.22459]
    [2.22459]
    #[serde(rename = "Kauf")]
  • edit in importers/ebase/src/transactions.rs at line 686
    [2.22473]
    [2.22473]
    #[serde(rename = "Wiederanlage Fondsertrag")]
  • edit in importers/ebase/src/transactions.rs at line 688
    [2.22487]
    [2.22487]
    #[serde(rename = "Ansparplan")]
  • edit in importers/ebase/src/transactions.rs at line 690
    [2.22504]
    [2.22504]
    #[serde(rename = "Neuabrechnung Ansparplan")]
  • edit in importers/ebase/src/transactions.rs at line 692
    [2.22530]
    [2.22530]
    #[serde(rename = "Stornierung Ansparplan")]
  • replacement in importers/ebase/src/transactions.rs at line 728
    [2.23804][2.23804:23928](),[2.23928][2.23928:23929](),[2.23929][2.23929:23984]()
    pub fn parse_transaction_date(record: &Record) -> Result<Date, Error> {
    const TRANSACTION_DATE_COLUMN_INDEX: usize = 3;
    parse_date(&record[TRANSACTION_DATE_COLUMN_INDEX])
    [2.23804]
    [2.23984]
    #[derive(Clone, Copy, Debug)]
    struct TransactionReference<'r> {
    order_date: Date,
    transaction_id: &'r str,
  • replacement in importers/ebase/src/transactions.rs at line 734
    [2.23987][2.23987:24207]()
    pub fn parse_date(date: &str) -> Result<Date, Error> {
    const BASE_YEAR: i32 = 2000;
    const DATE_FORMAT: &[time::format_description::FormatItem] =
    format_description!("[day].[month].[year repr:last_two]");
    [2.23987]
    [2.24207]
    impl<'de, 'r> serde::Deserialize<'de> for TransactionReference<'r>
    where
    'de: 'r,
    {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = TransactionReference<'de>;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an ebase transaction reference number")
    }
    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    let (transaction_id, order_date) = v
    .split_once('/')
    .ok_or(E::custom("unexpected format for transaction reference"))?;
  • replacement in importers/ebase/src/transactions.rs at line 759
    [2.24208][2.24208:24307]()
    let mut parsed = Parsed::new();
    parsed.parse_items(date.as_bytes(), DATE_FORMAT).unwrap();
    [2.24208]
    [2.24307]
    let order_date = Date::parse(
    order_date,
    time::macros::format_description!("[day][month][year]"),
    )
    .map_err(E::custom)?;
  • replacement in importers/ebase/src/transactions.rs at line 765
    [2.24308][2.24308:24424]()
    let year_last_two = i32::from(parsed.year_last_two().unwrap());
    parsed.set_year(BASE_YEAR + year_last_two);
    [2.24308]
    [2.24424]
    Ok(TransactionReference {
    order_date,
    transaction_id,
    })
    }
    }
  • replacement in importers/ebase/src/transactions.rs at line 772
    [2.24425][2.24425:24468]()
    parsed.try_into().map_err(Error::from)
    [2.24425]
    [2.24468]
    deserializer.deserialize_str(Visitor)
    }
  • replacement in importers/ebase/src/transactions.rs at line 776
    [2.24471][2.24471:24686]()
    fn parse_decimal_with_precision(
    decimal: &str,
    precision: u32,
    ) -> Result<Decimal, rust_decimal::Error> {
    german_decimal::parse(decimal).tap_ok_mut(|amount| {
    amount.rescale(precision);
    })
    [2.24471]
    [2.24686]
    impl std::fmt::Display for TransactionReference<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let mut buffer: &mut [u8] = &mut [0u8; 8];
    f.write_str(self.transaction_id)?;
    f.write_char('/')?;
    self.order_date
    .format_into(&mut buffer, format_description!("[day][month][year]"))
    .expect("valid formatting");
    f.write_str(core::str::from_utf8(buffer).expect("valid UTF-8"))
    }
  • edit in importers/ebase/src/balances.rs at line 14
    [2.25220][2.25220:25237]()
    use csv::Record;
  • edit in importers/ebase/src/balances.rs at line 18
    [2.25343]
    [2.25343]
    use rust_decimal::Decimal;
  • edit in importers/ebase/src/balances.rs at line 22
    [2.25416][2.25416:25443]()
    use snafu::ResultExt as _;
  • edit in importers/ebase/src/balances.rs at line 23
    [2.25461][2.25461:25499]()
    use time::macros::format_description;
  • edit in importers/ebase/src/balances.rs at line 178
    [2.29330]
    [2.29330]
    type Record<'de> = Record<'de>;
  • replacement in importers/ebase/src/balances.rs at line 180
    [2.29331][2.29331:29450]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_price_date(record))
    [2.29331]
    [2.29450]
    fn date(&self, record: Record) -> Date {
    record.share_price_date
  • replacement in importers/ebase/src/balances.rs at line 187
    [2.29521][2.29521:29546]()
    record: &Record,
    [2.29521]
    [2.29546]
    record: Record,
  • replacement in importers/ebase/src/balances.rs at line 189
    [2.29593][2.29593:30219]()
    const CURRENCY_COLUMN_INDEX: usize = 7;
    const DEPOT_ID_COLUMN_INDEX: usize = 0;
    const DEPOT_POSITION_COLUMN_INDEX: usize = 1;
    const ISIN_COLUMN_INDEX: usize = 3;
    const SHARE_PRICE_COLUMN_INDEX: usize = 6;
    const SHARES_COLUMN_INDEX: usize = 5;
    let date = parse_price_date(record)?;
    let depot_id = &record[DEPOT_ID_COLUMN_INDEX];
    let position = {
    let position = &record[DEPOT_POSITION_COLUMN_INDEX];
    let position = usize::from_str(position).context(PositionSnafu { position })?;
    format!("{:02}", position)
    };
    [2.29593]
    [2.30219]
    let position = format!("{:02}", record.depot_position);
  • replacement in importers/ebase/src/balances.rs at line 192
    [2.30260][2.30260:30319]()
    depot_id: <&Seg>::try_from(depot_id).unwrap(),
    [2.30260]
    [2.30319]
    depot_id: <&Seg>::try_from(record.depot_id).unwrap(),
  • replacement in importers/ebase/src/balances.rs at line 196
    [2.30392][2.30392:30464]()
    let isin = {
    let isin = &record[ISIN_COLUMN_INDEX];
    [2.30392]
    [2.30464]
    let isin = record.isin;
    let total_shares = Amount::new(record.shares, isin);
  • edit in importers/ebase/src/balances.rs at line 199
    [2.30465][2.30465:30767]()
    Commodity::try_from(isin).context(IsinSnafu { isin })?
    };
    let total_shares = {
    let shares = &record[SHARES_COLUMN_INDEX];
    let shares = german_decimal::parse(shares).context(SharesSnafu { shares })?;
    Amount::new(shares, isin)
    };
  • replacement in importers/ebase/src/balances.rs at line 200
    [2.30803][2.30803:30821]()
    date,
    [2.30803]
    [2.30821]
    record.share_price_date,
  • replacement in importers/ebase/src/balances.rs at line 205
    [2.30917][2.30917:31285]()
    let share_price = {
    let share_price = &record[SHARE_PRICE_COLUMN_INDEX];
    let amount =
    german_decimal::parse(share_price).context(SharePriceSnafu { share_price })?;
    let currency = &record[CURRENCY_COLUMN_INDEX];
    let commodity = Commodity::try_from(currency).context(CurrencySnafu { currency })?;
    [2.30917]
    [2.31285]
    let share_price = Amount::new(record.share_price, record.share_price_currency);
    let price = Price::new(record.share_price_date, isin, share_price);
  • edit in importers/ebase/src/balances.rs at line 208
    [2.31286][2.31286:31399]()
    Amount::new(amount, commodity)
    };
    let price = Price::new(date, isin, share_price);
  • edit in importers/ebase/src/balances.rs at line 228
    [2.31907]
    [2.31907]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Depotnummer")]
    depot_id: &'r str,
    #[serde(rename = "Position")]
    depot_position: usize,
    #[serde(rename = "ISIN")]
    isin: Commodity,
    #[serde(rename = "Anteile", with = "german_decimal::serde")]
    shares: Decimal,
    #[serde(rename = "Anteilswert", with = "german_decimal::serde")]
    share_price: Decimal,
    #[serde(rename = "Währung (Anteilswert)")]
    share_price_currency: Commodity,
    #[serde(rename = "Kursdatum", with = "dmy")]
    share_price_date: Date,
    }
  • edit in importers/ebase/src/balances.rs at line 294
    [2.32963][2.32963:33128]()
    }
    pub fn parse_price_date(record: &Record) -> Result<Date, Error> {
    const PRICE_DATE_COLUMN_INDEX: usize = 8;
    parse_date(&record[PRICE_DATE_COLUMN_INDEX])
  • edit in importers/ebase/src/balances.rs at line 295
    [2.33130][2.33130:33317]()
    pub fn parse_date(date: &str) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    const DATE_FORMAT: &[FormatItem] = format_description!("[day].[month].[year]");
  • replacement in importers/ebase/src/balances.rs at line 296
    [2.33318][2.33318:33428]()
    Date::parse(date, DATE_FORMAT)
    .map_err(time::Error::from)
    .context(DateFormatSnafu {})
    }
    [2.33318]
    time::serde::format_description!(dmy, Date, "[day].[month].[year]");
  • edit in importers/csv/src/lib.rs at line 12
    [7.313]
    [7.313]
    use serde::Deserialize;
  • edit in importers/csv/src/lib.rs at line 17
    [7.397][7.397:419]()
    use tap::TapFallible;
  • replacement in importers/csv/src/lib.rs at line 24
    [7.541][7.541:627]()
    #[snafu(display("error(s) while extracting data from records"))]
    Extracting {
    [7.541]
    [7.627]
    #[snafu(display("error(s) while processing CSV records"))]
    Processing {
  • replacement in importers/csv/src/lib.rs at line 32
    [7.736][7.736:792]()
    #[snafu(display("error while parsing CSV record"))]
    [7.736]
    [7.792]
    #[snafu(display("error while parsing CSV file"))]
  • replacement in importers/csv/src/lib.rs at line 66
    [7.1501][7.1501:1854]()
    self.fold(
    file,
    None,
    |record| inner.date(record).transpose(),
    |date, extracted| {
    if let Some(new_date) = extracted {
    let date = date.get_or_insert(new_date);
    *date = Date::max(*date, new_date);
    };
    },
    )
    [7.1501]
    [7.1854]
    (|| {
    let mut date = None;
    let data = std::fs::read_to_string(file).context(ReadingSnafu { file })?;
    let mut reader = self.builder.from_reader(data.as_bytes());
    let headers = reader.headers().context(ParsingSnafu {})?.clone();
    let mut errors = Vec::new();
    let mut record = Record::new();
    while reader.read_record(&mut record).context(ParsingSnafu {})? {
    let position = record.position().expect("position is set by csv::Reader");
    let offset = SourceOffset::from((position.byte() + 1) as usize);
    let new_date = record
    .deserialize(Some(&headers))
    .map(|record| inner.date(record))
    .context(DeserializationSnafu { offset });
    match new_date {
    Ok(new_date) if errors.is_empty() => {
    let date = date.get_or_insert(new_date);
    *date = Date::max(*date, new_date);
    }
    // we can skip updating our state when we already have at least one error
    Ok(_) => {}
    Err(error) => {
    errors.push(error);
    }
    }
    }
    if errors.is_empty() {
    Ok(date)
    } else {
    ProcessingSnafu { errors, data }.fail()
    }
    })()
  • replacement in importers/csv/src/lib.rs at line 117
    [7.2087][7.2087:2396](),[7.2396][5.34:127](),[7.117][7.2588:3438](),[5.127][7.2588:3438](),[7.2588][7.2588:3438]()
    self.fold(
    file,
    Vec::new(),
    |record| {
    let position = record.position().expect("position is set by csv::Reader");
    inner.extract(existing, record).tap_ok_mut(|records| {
    records.iter_mut().for_each(|directive| {
    directive.add_meta(common_keys::IMPORTED_RECORD, position.record());
    });
    })
    },
    |directives, extracted| {
    directives.extend(extracted);
    },
    )
    }
    pub fn identify(&self, file: &Utf8Path, expected_headers: &[&str]) -> Result<bool, csv::Error> {
    if !matches!(file.extension(), Some(ext) if ext.eq_ignore_ascii_case("csv")) {
    return Ok(false);
    }
    let mut reader = self.builder.from_path(file)?;
    let headers = reader.headers()?;
    Ok(headers == expected_headers)
    }
    }
    impl Importer {
    fn fold<State, Err>(
    &self,
    file: &Utf8Path,
    mut state: State,
    extractor: impl Fn(&Record) -> Result<State, Err>,
    folder: impl Fn(&mut State, State),
    ) -> Result<State, Error<Err>>
    where
    Err: Diagnostic + Send + Sync,
    {
    [7.2087]
    [7.3438]
    let mut directives = Vec::new();
  • edit in importers/csv/src/lib.rs at line 121
    [7.3589]
    [7.3589]
    let headers = reader.headers().context(ParsingSnafu {})?.clone();
  • replacement in importers/csv/src/lib.rs at line 125
    [7.3628][7.3628:3932]()
    {
    let mut record = Record::new();
    while reader.read_record(&mut record).context(ParsingSnafu {})? {
    let position = record.position().expect("position is set by csv::Reader");
    let offset = SourceOffset::from((position.byte() + 1) as usize);
    [7.3628]
    [7.3932]
    let mut record = Record::new();
    while reader.read_record(&mut record).context(ParsingSnafu {})? {
    let position = record.position().expect("position is set by csv::Reader");
    let offset = SourceOffset::from((position.byte() + 1) as usize);
  • replacement in importers/csv/src/lib.rs at line 130
    [7.3933][7.3933:4145]()
    match extractor(&record).context(RecordSnafu { offset }) {
    Ok(new_state) if errors.is_empty() => {
    folder(&mut state, new_state);
    }
    [7.3933]
    [7.4145]
    let extracted = record
    .deserialize(Some(&headers))
    .context(DeserializationSnafu { offset })
    .and_then(|record| {
    inner
    .extract(existing, record)
    .context(ExtractionSnafu { offset })
    });
    match extracted {
    Ok(extracted) if errors.is_empty() => {
    directives.extend(extracted.into_iter().map(|mut directive| {
    directive.add_meta(common_keys::IMPORTED_RECORD, position.record());
    directive
    }));
    }
  • replacement in importers/csv/src/lib.rs at line 146
    [7.4146][7.4146:4272]()
    // we can skip updating our state when we already have at least one error
    Ok(_) => {}
    [7.4146]
    [7.4272]
    // we can skip updating our state when we already have at least one error
    Ok(_) => {}
  • replacement in importers/csv/src/lib.rs at line 149
    [7.4273][7.4273:4375]()
    Err(error) => {
    errors.push(error);
    }
    [7.4273]
    [7.4375]
    Err(error) => {
    errors.push(error);
  • replacement in importers/csv/src/lib.rs at line 156
    [7.4449][7.4449:4471]()
    Ok(state)
    [7.4449]
    [7.4471]
    Ok(directives)
  • replacement in importers/csv/src/lib.rs at line 158
    [7.4488][7.4488:4540]()
    ExtractingSnafu { errors, data }.fail()
    [7.4488]
    [7.4540]
    ProcessingSnafu { errors, data }.fail()
    }
    }
    pub fn identify(&self, file: &Utf8Path, expected_headers: &[&str]) -> Result<bool, csv::Error> {
    if !matches!(file.extension(), Some(ext) if ext.eq_ignore_ascii_case("csv")) {
    return Ok(false);
  • edit in importers/csv/src/lib.rs at line 166
    [7.4550]
    [7.4550]
    let mut reader = self.builder.from_path(file)?;
    let headers = reader.headers()?;
    Ok(headers == expected_headers)
  • edit in importers/csv/src/lib.rs at line 173
    [7.4558]
    [7.4558]
    impl Importer {}
  • replacement in importers/csv/src/lib.rs at line 177
    [7.4595][7.4595:4684]()
    #[snafu(display("encountered error while extracting record"))]
    pub struct RecordError<E>
    [7.4595]
    [7.4684]
    pub enum RecordError<E>
  • replacement in importers/csv/src/lib.rs at line 181
    [7.4721][7.4721:4761]()
    #[diagnostic_source]
    source: E,
    [7.4721]
    [7.4761]
    #[snafu(display("error while deserializing CSV record"))]
    Deserialization {
    source: csv::Error,
    #[label("in this record")]
    offset: SourceSpan,
    },
    #[snafu(display("error while extracting record"))]
    Extraction {
    #[diagnostic_source]
    source: E,
  • replacement in importers/csv/src/lib.rs at line 194
    [7.4762][7.4762:4817]()
    #[label("in this record")]
    offset: SourceSpan,
    [7.4762]
    [7.4817]
    #[label("in this record")]
    offset: SourceSpan,
    },
  • edit in importers/csv/src/lib.rs at line 201
    [7.4899]
    [7.4899]
    type Record<'de>: Deserialize<'de>;
  • replacement in importers/csv/src/lib.rs at line 203
    [7.4900][7.4900:4995]()
    fn date(&self, _record: &Record) -> Option<Result<Date, Self::Error>> {
    None
    }
    [7.4900]
    [7.4995]
    fn date(&self, _record: Self::Record<'_>) -> Date;
  • replacement in importers/csv/src/lib.rs at line 208
    [7.5059][7.5059:5084]()
    record: &Record,
    [7.5059]
    [7.5084]
    record: Self::Record<'_>,
  • edit in importers/csv/src/lib.rs at line 217
    [7.5221]
    [7.5221]
    type Record<'de> = R::Record<'de>;
  • replacement in importers/csv/src/lib.rs at line 221
    [7.5259][7.5259:5342]()
    fn date(&self, _record: &Record) -> Option<Result<Date, Self::Error>>;
    [7.5259]
    [7.5342]
    fn date(&self, record: Self::Record<'_>) -> Date;
  • replacement in importers/csv/src/lib.rs at line 225
    [7.5429][7.5429:5462]()
    record: &Record,
    [7.5429]
    [7.5462]
    record: Self::Record<'_>,
  • edit in importers/bw-bank/src/portfolio.rs at line 18
    [7.589]
    [7.589]
    use isin::ISIN;
  • edit in importers/bw-bank/src/portfolio.rs at line 21
    [7.641]
    [7.641]
    use rust_decimal::Decimal;
  • edit in importers/bw-bank/src/portfolio.rs at line 27
    [7.751][7.751:789]()
    use time::macros::format_description;
  • edit in importers/bw-bank/src/portfolio.rs at line 126
    [7.3301]
    [7.3301]
    type Record<'de> = Record<'de>;
  • replacement in importers/bw-bank/src/portfolio.rs at line 128
    [7.3302][7.3302:3421]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_order_date(record))
    [7.3302]
    [7.3421]
    fn date(&self, record: Record) -> Date {
    record.date
  • replacement in importers/bw-bank/src/portfolio.rs at line 135
    [7.3492][7.3492:3522]()
    record: &csv::Record,
    [7.3492]
    [7.3522]
    record: Record,
  • replacement in importers/bw-bank/src/portfolio.rs at line 137
    [7.3569][7.3569:4090]()
    const ORDER_ID_COLUMN_INDEX: usize = 3;
    let isin = Commodity::try_from(&record[0]).map_err(|_| todo!())?;
    let security_name = &record[1];
    let date = parse_order_date(record)?;
    let order_id = &record[ORDER_ID_COLUMN_INDEX];
    let transaction_kind = TransactionKind::from_str(&record[4])?;
    let units = german_decimal::parse(&record[5]).map_err(|_| todo!())?;
    let amount = Amount::new(units, isin);
    let security_price = parse_amount(&record[7])?;
    [7.3569]
    [7.4090]
    let isin =
    Commodity::try_from(record.isin.to_string()).expect("ISINs are valid commodities");
  • replacement in importers/bw-bank/src/portfolio.rs at line 140
    [7.4091][7.4091:4138]()
    let total = parse_amount(&record[9])?;
    [7.4091]
    [7.4138]
    let transaction_kind = record.transaction_kind;
    let security_name = record.security_name;
    let order_id = record.order_id;
  • replacement in importers/bw-bank/src/portfolio.rs at line 145
    [7.4213][7.4213:4404]()
    TransactionKind::Buy => (amount, CostBasis::PerUnit(security_price), None, -total),
    TransactionKind::Sell => (-amount, CostBasis::Empty, Some(security_price), total),
    [7.4213]
    [7.4404]
    TransactionKind::Buy => (
    Amount::new(record.shares, isin),
    CostBasis::PerUnit(record.unit_cost),
    None,
    -record.total_cost,
    ),
    TransactionKind::Sell => (
    -Amount::new(record.shares, isin),
    CostBasis::Empty,
    Some(record.unit_cost),
    record.total_cost,
    ),
  • replacement in importers/bw-bank/src/portfolio.rs at line 159
    [7.4416][7.4416:4488]()
    let transaction = Transaction::on(date).tap_mut(|transaction| {
    [7.4416]
    [7.4488]
    let transaction = Transaction::on(record.date).tap_mut(|transaction| {
  • replacement in importers/bw-bank/src/portfolio.rs at line 171
    [7.5003][7.5003:5068]()
    .add_meta(common_keys::TRANSACTION_ID, order_id)
    [7.5003]
    [7.5068]
    .add_meta(common_keys::TRANSACTION_ID, record.order_id)
  • replacement in importers/bw-bank/src/portfolio.rs at line 187
    [7.5663][7.5663:5723]()
    let price = Price::new(date, isin, security_price);
    [7.5663]
    [7.5723]
    let price = Price::new(record.date, isin, record.unit_cost);
  • edit in importers/bw-bank/src/portfolio.rs at line 226
    [7.6985]
    [7.6985]
    }
    #[derive(Clone, Debug, Deserialize)]
    // TODO derive Copy once ISIN changes to support that
    pub struct Record<'r> {
    #[serde(rename = "Datum", with = "dmy")]
    date: Date,
    #[serde(deserialize_with = "deserialize_isin", rename = "Isin")]
    isin: ISIN,
    #[serde(rename = "Ordernummer")]
    order_id: &'r str,
    #[serde(rename = "Wertpapierbezeichnung")]
    security_name: &'r str,
    #[serde(rename = "Stück", with = "german_decimal::serde")]
    shares: Decimal,
    #[serde(deserialize_with = "deserialize_german_amount", rename = "Kurswert")]
    total_cost: Amount,
    #[serde(rename = "Geschäftsart")]
    transaction_kind: TransactionKind,
    #[serde(deserialize_with = "deserialize_german_amount", rename = "Kurs")]
    unit_cost: Amount,
  • replacement in importers/bw-bank/src/portfolio.rs at line 295
    [7.7827][7.7827:7857]()
    #[derive(Clone, Copy, Debug)]
    [7.7827]
    [7.7857]
    #[derive(Clone, Copy, Debug, Deserialize)]
  • edit in importers/bw-bank/src/portfolio.rs at line 297
    [7.7880]
    [7.7880]
    #[serde(rename = "Kauf")]
  • edit in importers/bw-bank/src/portfolio.rs at line 299
    [7.7889]
    [7.7889]
    #[serde(rename = "Verkauf")]
  • replacement in importers/bw-bank/src/portfolio.rs at line 315
    [7.8155][7.8155:8286]()
    fn parse_amount(cost: &str) -> Result<Amount, Error> {
    let (amount, commodity) = cost.split_once(' ').ok_or_else(|| todo!())?;
    [7.8155]
    [7.8286]
    time::serde::format_description!(dmy, Date, "[day].[month].[year]");
    fn deserialize_isin<'de, D>(deserializer: D) -> Result<ISIN, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = ISIN;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an ISIN")
    }
  • replacement in importers/bw-bank/src/portfolio.rs at line 330
    [7.8287][7.8287:8431]()
    let amount = german_decimal::parse(amount).map_err(|_| todo!())?;
    let commodity = Commodity::try_from(commodity).map_err(|_| todo!())?;
    [7.8287]
    [7.8431]
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    isin::parse(v).map_err(E::custom)
    }
    }
  • replacement in importers/bw-bank/src/portfolio.rs at line 338
    [7.8432][7.8432:8471]()
    Ok(Amount::new(amount, commodity))
    [7.8432]
    [7.8471]
    deserializer.deserialize_str(Visitor)
  • edit in importers/bw-bank/src/portfolio.rs at line 340
    [7.8473]
    [7.8473]
    fn deserialize_german_amount<'de, D>(deserializer: D) -> Result<Amount, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
  • replacement in importers/bw-bank/src/portfolio.rs at line 347
    [7.8474][7.8474:8587]()
    fn parse_order_date(record: &csv::Record) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    [7.8474]
    [7.8587]
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = Amount;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an ISIN")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    let Some((amount, currency)) = v.split_once(' ') else {
    return Err(E::custom("unexpected format"));
    };
  • replacement in importers/bw-bank/src/portfolio.rs at line 362
    [7.8588][7.8588:8712]()
    const DATE_COLUMN_INDEX: usize = 2;
    const DATE_FORMAT: &[FormatItem] = format_description!("[day].[month].[year]");
    [7.8588]
    [7.8712]
    let amount = german_decimal::parse(amount).map_err(E::custom)?;
    let currency = Commodity::try_from(currency).map_err(E::custom)?;
    Ok(Amount::new(amount, currency))
    }
    }
  • replacement in importers/bw-bank/src/portfolio.rs at line 368
    [7.8713][7.8713:8791]()
    Date::parse(&record[DATE_COLUMN_INDEX], DATE_FORMAT).map_err(|_| todo!())
    [7.8713]
    [7.8791]
    deserializer.deserialize_str(Visitor)
  • edit in importers/bw-bank/Cargo.toml at line 21
    [7.9463]
    [7.9463]
    isin.workspace = true
  • edit in importers/apple/src/transaction_history.rs at line 13
    [7.438]
    [7.438]
    use beancount_types::MetadataKey;
  • edit in importers/apple/src/transaction_history.rs at line 18
    [7.549]
    [7.549]
    use iso_currency::Currency;
  • edit in importers/apple/src/transaction_history.rs at line 21
    [7.606]
    [7.606]
    use rust_decimal::Decimal;
  • edit in importers/apple/src/transaction_history.rs at line 27
    [7.711][7.711:762]()
    use time::format_description::well_known::Iso8601;
  • edit in importers/apple/src/transaction_history.rs at line 28
    [7.813]
    [7.813]
    use time::Date;
  • edit in importers/apple/src/transaction_history.rs at line 30
    [7.839][7.839:876]()
    use time_tz::OffsetDateTimeExt as _;
  • edit in importers/apple/src/transaction_history.rs at line 144
    [7.3858]
    [7.3858]
    type Record<'de> = Record<'de>;
  • replacement in importers/apple/src/transaction_history.rs at line 146
    [7.3859][7.3859:4015]()
    fn date(&self, record: &csv::Record) -> Option<Result<time::Date, Self::Error>> {
    Some(parse_order_timestamp(record).map(OffsetDateTime::date))
    [7.3859]
    [7.4015]
    fn date(&self, record: Record) -> Date {
    Date::max(
    record.order_time.date(),
    record.invoice_date.unwrap_or(Date::MIN),
    )
  • replacement in importers/apple/src/transaction_history.rs at line 156
    [7.4086][7.4086:4116]()
    record: &csv::Record,
    [7.4086]
    [7.4116]
    record: Record,
  • replacement in importers/apple/src/transaction_history.rs at line 158
    [7.4163][7.4163:4892]()
    const CURRENCY_COLUMN_INDEX: usize = 22;
    const ORDER_ID_COLUMN_INDEX: usize = 19;
    const PRODUCT_NAME_COLUMN_INDEX: usize = 4;
    const ITEM_PRICE_COLUMN_INDEX: usize = 8;
    let order_id = Some(&record[ORDER_ID_COLUMN_INDEX]).filter(|order_id| !order_id.is_empty());
    let timestamp = parse_order_timestamp(record)?;
    let commodity = Commodity::try_from(&record[CURRENCY_COLUMN_INDEX]).unwrap();
    let amount = {
    let amount = &record[ITEM_PRICE_COLUMN_INDEX];
    let (amount, _) = amount.split_once(' ').ok_or_else(|| todo!())?;
    let amount = german_decimal::parse(amount).map_err(|_| todo!())?;
    Amount::new(-amount, commodity)
    [7.4163]
    [7.4892]
    let Some(order_id) = record.order_id else {
    return Ok(vec![]);
  • replacement in importers/apple/src/transaction_history.rs at line 162
    [7.4904][7.4904:4967]()
    let product_name = &record[PRODUCT_NAME_COLUMN_INDEX];
    [7.4904]
    [7.4967]
    let commodity = Commodity::try_from(record.currency.code()).unwrap();
    let amount = Amount::new(-record.item_total, commodity);
  • replacement in importers/apple/src/transaction_history.rs at line 165
    [7.4968][7.4968:5052]()
    let transaction = Transaction::on(timestamp.date()).tap_mut(|transaction| {
    [7.4968]
    [7.5052]
    let transaction = Transaction::on(record.order_time.date()).tap_mut(|transaction| {
  • replacement in importers/apple/src/transaction_history.rs at line 174
    [7.5378][7.5378:5471]()
    .set_payee(&self.config.payee)
    .set_narration(product_name);
    [7.5378]
    [7.5471]
    .set_payee(record.seller)
    .set_narration(record.item_description)
    .add_link(Link::try_from(format!("^apple.{order_id}")).expect("valid link"))
    .add_meta(common_keys::TRANSACTION_ID, order_id);
  • replacement in importers/apple/src/transaction_history.rs at line 179
    [7.5472][7.5472:5519]()
    if let Some(order_id) = order_id {
    [7.5472]
    [7.5519]
    if let Some((invoice_date, invoice_id)) =
    record.invoice_date.zip(record.document_number)
    {
  • replacement in importers/apple/src/transaction_history.rs at line 183
    [7.5547][7.5547:5729]()
    .add_link(Link::try_from(format!("^apple.{order_id}")).expect("valid link"))
    .add_meta(common_keys::TRANSACTION_ID, order_id);
    };
    [7.5547]
    [7.5729]
    .add_meta(
    MetadataKey::try_from("invoice-date").expect("valid key"),
    invoice_date.to_string(),
    )
    .add_meta(
    MetadataKey::try_from("invoice-id").expect("valid key"),
    invoice_id,
    );
    }
  • replacement in importers/apple/src/transaction_history.rs at line 194
    [7.5754][7.5754:5841]()
    .add_meta(common_keys::TIMESTAMP, timestamp.format(&Rfc3339).unwrap())
    [7.5754]
    [7.5841]
    .add_meta(
    common_keys::TIMESTAMP,
    record.order_time.format(&Rfc3339).unwrap(),
    )
  • edit in importers/apple/src/transaction_history.rs at line 235
    [7.6982]
    [7.6982]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Currency")]
    currency: Currency,
    #[serde(rename = "Document Number")]
    document_number: Option<&'r str>,
    #[serde(rename = "Invoice Date", with = "mdy::option")]
    invoice_date: Option<Date>,
    #[serde(rename = "Item Description")]
    item_description: &'r str,
    #[serde(
    deserialize_with = "deserialize_german_euro_amount",
    rename = "Invoice Item Total"
    )]
    item_total: Decimal,
    #[serde(rename = "Order Number")]
    order_id: Option<&'r str>,
    #[serde(rename = "Item Purchased Date", with = "time::serde::iso8601")]
    order_time: OffsetDateTime,
    #[serde(rename = "Seller")]
    seller: &'r str,
    }
  • edit in importers/apple/src/transaction_history.rs at line 305
    [7.7931]
    [7.7931]
    time::serde::format_description!(mdy, Date, "[month]/[day]/[year]");
  • replacement in importers/apple/src/transaction_history.rs at line 308
    [7.7932][7.7932:8065]()
    fn parse_order_timestamp(record: &csv::Record) -> Result<OffsetDateTime, Error> {
    const ORDER_TIMESTAMP_COLUMN_INDEX: usize = 1;
    [7.7932]
    [7.8065]
    fn deserialize_german_euro_amount<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
  • replacement in importers/apple/src/transaction_history.rs at line 314
    [7.8066][7.8066:8127]()
    parse_timestamp(&record[ORDER_TIMESTAMP_COLUMN_INDEX])
    }
    [7.8066]
    [7.8127]
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = Decimal;
  • replacement in importers/apple/src/transaction_history.rs at line 317
    [7.8128][7.8128:8290]()
    fn parse_timestamp(input: &str) -> Result<OffsetDateTime, Error> {
    let utc_timestamp = OffsetDateTime::parse(input, &Iso8601::PARSING).map_err(|_| todo!())?;
    [7.8128]
    [7.8290]
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an amount with trailing € symbol")
    }
  • replacement in importers/apple/src/transaction_history.rs at line 321
    [7.8291][7.8291:8426]()
    let timezone = time_tz::system::get_timezone().expect("system timezone");
    let timestamp = utc_timestamp.to_timezone(timezone);
    [7.8291]
    [7.8426]
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    let v = v
    .strip_suffix(" €")
    .ok_or_else(|| E::custom("expected symbol suffix not found"))?
    .trim();
  • replacement in importers/apple/src/transaction_history.rs at line 330
    [7.8427][7.8427:8445]()
    Ok(timestamp)
    [7.8427]
    [7.8445]
    german_decimal::parse(v).map_err(E::custom)
    }
    }
    deserializer.deserialize_str(Visitor)
  • edit in importers/apple/src/store_balance.rs at line 17
    [7.9008]
    [7.9008]
    use iso_currency::Country;
  • edit in importers/apple/src/store_balance.rs at line 29
    [7.9327][7.9327:9412]()
    use time::PrimitiveDateTime;
    use time_tz::system;
    use time_tz::PrimitiveDateTimeExt;
  • edit in importers/apple/src/store_balance.rs at line 123
    [7.11708]
    [7.11708]
    type Record<'de> = Record<'de>;
  • replacement in importers/apple/src/store_balance.rs at line 125
    [7.11709][7.11709:11860]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_record_timestamp(record).map(OffsetDateTime::date))
    [7.11709]
    [7.11860]
    fn date(&self, record: Record) -> Date {
    record.transaction_date.date()
  • replacement in importers/apple/src/store_balance.rs at line 132
    [7.11931][7.11931:11961]()
    record: &csv::Record,
    [7.11931]
    [7.11961]
    record: Record,
  • replacement in importers/apple/src/store_balance.rs at line 134
    [7.12008][7.12008:12414]()
    const AMOUNT_COLUMN_INDEX: usize = 3;
    const COUNTRY_COLUMN_INDEX: usize = 1;
    const ORDER_ID_COLUMN_INDEX: usize = 2;
    const TRANSACTION_KIND_COLUMN_INDEX: usize = 4;
    let timestamp = parse_record_timestamp(record)?;
    let country = &record[COUNTRY_COLUMN_INDEX];
    let commodity = match country {
    "DE" => Commodity::try_from("EUR").unwrap(),
    [7.12008]
    [7.12414]
    let commodity = match record.country {
    Country::DE => Commodity::try_from("EUR").unwrap(),
  • replacement in importers/apple/src/store_balance.rs at line 139
    [7.12452][4.52:113]()
    let transaction_id = &record[ORDER_ID_COLUMN_INDEX];
    [7.12452]
    [7.12507]
    let transaction_id = record.order_number;
  • replacement in importers/apple/src/store_balance.rs at line 141
    [7.12508][7.12508:12801]()
    let kind = TransactionKind::try_from(&record[TRANSACTION_KIND_COLUMN_INDEX])?;
    let amount = {
    let amount = &record[AMOUNT_COLUMN_INDEX];
    let amount = Decimal::from_str(amount).map_err(|_| todo!())?;
    Amount::new(amount, commodity)
    };
    [7.12508]
    [7.12801]
    let kind = TransactionKind::try_from(record.transaction_type)?;
    let amount = Amount::new(record.amount, commodity);
  • replacement in importers/apple/src/store_balance.rs at line 153
    [7.13339][7.13339:13412]()
    let mut transaction = Transaction::on(timestamp.date());
    [7.13166]
    [7.13412]
    let mut transaction = Transaction::on(record.transaction_date.date());
  • replacement in importers/apple/src/store_balance.rs at line 169
    [7.13778][7.13778:13852]()
    timestamp.format(&Rfc3339).map_err(|_| todo!())?,
    [7.13778]
    [7.13852]
    record
    .transaction_date
    .format(&Rfc3339)
    .map_err(|_| todo!())?,
  • replacement in importers/apple/src/store_balance.rs at line 184
    [7.14287][7.14287:14360]()
    let mut transaction = Transaction::on(timestamp.date());
    [7.14287]
    [7.14360]
    let mut transaction = Transaction::on(record.transaction_date.date());
  • replacement in importers/apple/src/store_balance.rs at line 191
    [7.14592][7.14592:14666]()
    timestamp.format(&Rfc3339).map_err(|_| todo!())?,
    [7.14592]
    [7.14666]
    record
    .transaction_date
    .format(&Rfc3339)
    .map_err(|_| todo!())?,
  • edit in importers/apple/src/store_balance.rs at line 245
    [7.16225]
    [7.16225]
    }
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Amount")]
    amount: Decimal,
    #[serde(rename = "Country")]
    country: Country,
    #[serde(rename = "Order number")]
    order_number: &'r str,
    #[serde(
    rename = "Transaction date",
    deserialize_with = "deserialize_local_offset_date_time"
    )]
    transaction_date: OffsetDateTime,
    #[serde(rename = "Transaction type")]
    transaction_type: &'r str,
  • replacement in importers/apple/src/store_balance.rs at line 327
    [7.17562][7.17562:17685]()
    fn parse_record_timestamp(record: &csv::Record) -> Result<OffsetDateTime, Error> {
    const DATE_COLUMN_INDEX: usize = 0;
    [7.17562]
    [7.17685]
    fn deserialize_local_offset_date_time<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    use serde::de::Error as _;
    use time_tz::system;
    use time_tz::PrimitiveDateTimeExt as _;
    use time_tz::TimeZone as _;
  • replacement in importers/apple/src/store_balance.rs at line 336
    [7.17686][7.17686:17863]()
    let timestamp = PrimitiveDateTime::parse(&record[DATE_COLUMN_INDEX], &Iso8601::PARSING)
    .map_err(|error| {
    dbg!(error);
    todo!()
    })?;
    [7.17686]
    [7.17863]
    let timestamp = iso_primitivedatetime::deserialize(deserializer)?;
  • replacement in importers/apple/src/store_balance.rs at line 338
    [7.17864][7.17864:18020]()
    let tz = system::get_timezone().map_err(|_| todo!())?;
    timestamp
    .assume_timezone(tz)
    .take_first()
    .ok_or_else(|| todo!())
    [7.17864]
    [7.18020]
    let tz = system::get_timezone().map_err(D::Error::custom)?;
    timestamp.assume_timezone(tz).take_first().ok_or_else(|| {
    D::Error::custom(format!(
    "invalid datetime {timestamp} in timezone {}",
    tz.name()
    ))
    })
  • edit in importers/apple/src/store_balance.rs at line 346
    [7.18022]
    const PRIMITIVE_DATE_TIME_FORMAT: Iso8601<0> =
    time::format_description::well_known::Iso8601::PARSING;
    time::serde::format_description!(
    iso_primitivedatetime,
    PrimitiveDateTime,
    PRIMITIVE_DATE_TIME_FORMAT
    );
  • edit in importers/apple/Cargo.toml at line 20
    [7.18695]
    [7.18695]
    iso_country.workspace = true
    iso_currency.workspace = true
  • edit in importers/amazon/src/order_history.rs at line 27
    [7.852]
    [7.890]
    use time::Date;
  • edit in importers/amazon/src/order_history.rs at line 29
    [7.916][7.916:982]()
    use time::PrimitiveDateTime;
    use time_tz::OffsetDateTimeExt as _;
  • edit in importers/amazon/src/order_history.rs at line 141
    [7.3893]
    [7.3893]
    type Record<'de> = Record<'de>;
  • replacement in importers/amazon/src/order_history.rs at line 143
    [7.3894][7.3894:4050]()
    fn date(&self, record: &csv::Record) -> Option<Result<time::Date, Self::Error>> {
    Some(parse_order_timestamp(record).map(OffsetDateTime::date))
    [7.3894]
    [7.4050]
    fn date(&self, record: Record) -> Date {
    record.order_timestamp.date()
  • replacement in importers/amazon/src/order_history.rs at line 150
    [7.4121][7.4121:4151]()
    record: &csv::Record,
    [7.4121]
    [7.4151]
    record: Record,
  • replacement in importers/amazon/src/order_history.rs at line 152
    [7.4198][7.4198:4442]()
    const ASIN_COLUMN_INDEX: usize = 12;
    const CURRENCY_COLUMN_INDEX: usize = 4;
    const ORDER_ID_COLUMN_INDEX: usize = 1;
    const PRODUCT_NAME_COLUMN_INDEX: usize = 23;
    const TOTAL_OWED_COLUMN_INDEX: usize = 9;
    [7.4198]
    [7.4442]
    dbg!(record);
  • replacement in importers/amazon/src/order_history.rs at line 154
    [7.4443][7.4443:4555]()
    let order_id = &record[ORDER_ID_COLUMN_INDEX];
    let timestamp = parse_order_timestamp(record)?;
    [7.4443]
    [7.4555]
    let order_id = record.order_id;
  • edit in importers/amazon/src/order_history.rs at line 156
    [7.4556][7.4556:4642]()
    let commodity = Commodity::try_from(&record[CURRENCY_COLUMN_INDEX]).unwrap();
  • replacement in importers/amazon/src/order_history.rs at line 157
    [7.4665][7.4665:4865]()
    let amount = Decimal::from_str(&record[TOTAL_OWED_COLUMN_INDEX])
    .unwrap()
    .tap_mut(|amount| amount.rescale(2));
    Amount::new(amount, commodity)
    [7.4665]
    [7.4865]
    Amount::new(
    record.total_owed.tap_mut(|amount| amount.rescale(2)),
    record.currency,
    )
  • replacement in importers/amazon/src/order_history.rs at line 163
    [7.4877][7.4877:4987]()
    let asin = &record[ASIN_COLUMN_INDEX];
    let product_name = &record[PRODUCT_NAME_COLUMN_INDEX];
    [7.4877]
    [7.4987]
    let asin = record.asin;
    let product_name = record.product_name;
  • replacement in importers/amazon/src/order_history.rs at line 166
    [7.4988][7.4988:5072]()
    let transaction = Transaction::on(timestamp.date()).tap_mut(|transaction| {
    [7.4988]
    [7.5072]
    let transaction = Transaction::on(record.order_timestamp.date()).tap_mut(|transaction| {
  • replacement in importers/amazon/src/order_history.rs at line 168
    [7.5100][7.5100:5149]()
    let currency: &str = &commodity;
    [7.5100]
    [7.5149]
    let currency: &str = &record.currency;
  • replacement in importers/amazon/src/order_history.rs at line 179
    [7.5689][7.5689:5776]()
    .add_meta(common_keys::TIMESTAMP, timestamp.format(&Rfc3339).unwrap())
    [7.5689]
    [7.5776]
    .add_meta(
    common_keys::TIMESTAMP,
    record.order_timestamp.format(&Rfc3339).unwrap(),
    )
  • edit in importers/amazon/src/order_history.rs at line 214
    [7.6752]
    [7.6752]
    #[derive(Clone, Copy, Debug, Deserialize)]
    pub struct Record<'r> {
    #[serde(rename = "Order ID")]
    order_id: &'r str,
    #[serde(rename = "Order Date", with = "time::serde::iso8601")]
    order_timestamp: OffsetDateTime,
    #[serde(rename = "Currency")]
    currency: Commodity,
    #[serde(rename = "Total Owed")]
    total_owed: Decimal,
  • edit in importers/amazon/src/order_history.rs at line 229
    [7.6753]
    [7.6753]
    #[serde(rename = "ASIN")]
    asin: &'r str,
    #[serde(rename = "Product Name")]
    product_name: &'r str,
    }
  • edit in importers/amazon/src/order_history.rs at line 275
    [7.7721][7.7721:7907]()
    }
    fn parse_order_timestamp(record: &csv::Record) -> Result<OffsetDateTime, Error> {
    const ORDER_DATE_COLUMN_INDEX: usize = 2;
    parse_timestamp(&record[ORDER_DATE_COLUMN_INDEX])
  • edit in importers/amazon/src/order_history.rs at line 276
    [7.7909][7.7909:7977](),[7.7977][7.78:144](),[7.144][7.8206:8403](),[7.8206][7.8206:8403]()
    fn parse_timestamp(input: &str) -> Result<OffsetDateTime, Error> {
    let utc_timestamp = PrimitiveDateTime::parse(input, &Rfc3339)
    .unwrap()
    .assume_utc();
    let timezone = time_tz::system::get_timezone().expect("system timezone");
    let timestamp = utc_timestamp.to_timezone(timezone);
    Ok(timestamp)
    }
  • edit in importers/amazon/src/gift_card_balance.rs at line 26
    [7.9278][7.9278:9316]()
    use time::macros::format_description;
  • edit in importers/amazon/src/gift_card_balance.rs at line 156
    [7.13148]
    [7.13148]
    type Record<'de> = Record<'de>;
  • replacement in importers/amazon/src/gift_card_balance.rs at line 158
    [7.13149][7.13149:13269]()
    fn date(&self, record: &csv::Record) -> Option<Result<Date, Self::Error>> {
    Some(parse_record_date(record))
    [7.13149]
    [7.13269]
    fn date(&self, record: Record) -> Date {
    record.date
  • replacement in importers/amazon/src/gift_card_balance.rs at line 165
    [7.13340][7.13340:13370]()
    record: &csv::Record,
    [7.13340]
    [7.13370]
    record: Record,
  • replacement in importers/amazon/src/gift_card_balance.rs at line 167
    [7.13417][7.13417:13464]()
    let date = parse_record_date(record)?;
    [7.13417]
    [7.13464]
    let date = record.date;
  • edit in importers/amazon/src/gift_card_balance.rs at line 169
    [7.13465][3.59:165]()
    let description = &record[1];
    let kind = TransactionKind::try_from(description).unwrap();
  • replacement in importers/amazon/src/gift_card_balance.rs at line 170
    [7.13555][7.13555:13592]()
    let amount = &record[2];
    [7.13555]
    [7.13592]
    let amount = record.amount;
  • replacement in importers/amazon/src/gift_card_balance.rs at line 187
    [3.238][3.238:290]()
    transaction.set_narration(description);
    [3.238]
    [3.290]
    transaction.set_narration(record.kind.to_string());
  • replacement in importers/amazon/src/gift_card_balance.rs at line 189
    [3.291][3.291:316]()
    match kind {
    [3.291]
    [3.316]
    match record.kind {
  • replacement in importers/amazon/src/gift_card_balance.rs at line 238
    [7.15655][7.15655:15891]()
    fn parse_record_date(record: &csv::Record) -> Result<Date, Error> {
    const DATE_COLUMN_INDEX: usize = 0;
    const DATE_FORMAT: &[time::format_description::FormatItem] =
    format_description!("[day] [month repr:long] [year]");
    [7.15655]
    [7.15891]
    #[derive(Clone, Copy, Debug, Deserialize)]
    #[serde(rename_all = "PascalCase")]
    pub struct Record<'r> {
    #[serde(with = "dm_long_y")]
    date: Date,
    #[serde(rename = "Description")]
    kind: TransactionKind<'r>,
  • replacement in importers/amazon/src/gift_card_balance.rs at line 247
    [7.15892][7.15892:15970]()
    Date::parse(&record[DATE_COLUMN_INDEX], DATE_FORMAT).map_err(|_| todo!())
    [7.15892]
    [7.15970]
    amount: &'r str,
  • edit in importers/amazon/src/gift_card_balance.rs at line 249
    [7.15972]
    [7.15972]
    time::serde::format_description!(dm_long_y, Date, "[day] [month repr:long] [year]");
  • replacement in importers/amazon/src/gift_card_balance.rs at line 293
    [7.16911][7.16911:16941]()
    #[derive(Clone, Copy, Debug)]
    [7.16911]
    [7.16941]
    #[derive(Clone, Copy, Debug, Deserialize)]
    #[serde(try_from = "&str")]
  • replacement in importers/amazon/src/gift_card_balance.rs at line 304
    [7.17207][7.17207:17431]()
    TransactionKind::Claim { .. } => f.write_str("Gift Card Claim"),
    TransactionKind::Payment { .. } => f.write_str("Payment"),
    TransactionKind::TopUp { .. } => f.write_str("Balance Top Up"),
    [7.17207]
    [7.17431]
    TransactionKind::Claim { claim_code } => write!(f, "Claim gift card {claim_code}"),
    TransactionKind::Payment { order_id } => write!(f, "Payment towards order {order_id}"),
    TransactionKind::TopUp { order_id } => write!(f, "Balance top up in order {order_id}"),
  • replacement in importers/amazon/src/gift_card_balance.rs at line 312
    [7.17502][7.17502:17531]()
    type Error = (); // TODO
    [7.17502]
    [7.17531]
    type Error = &'static str; // TODO
  • edit in importers/amazon/src/digital_items.rs at line 25
    [7.19235][7.19235:19273]()
    use time::macros::format_description;
  • edit in importers/amazon/src/digital_items.rs at line 159
    [7.22976]
    [7.22976]
    type Record<'de> = Record<'de>;
  • replacement in importers/amazon/src/digital_items.rs at line 161
    [7.22977][7.22977:23102]()
    fn date(&self, record: &csv::Record) -> Option<Result<time::Date, Self::Error>> {
    Some(parse_order_date(record))
    [7.22977]
    [7.23102]
    fn date(&self, record: Record) -> Date {
    Date::max(
    record.order_date,
    record.fulfilled_date.unwrap_or(Date::MIN),
    )
  • replacement in importers/amazon/src/digital_items.rs at line 171
    [7.23173][7.23173:23203]()
    record: &csv::Record,
    [7.23173]
    [7.23203]
    record: Record,
  • replacement in importers/amazon/src/digital_items.rs at line 173
    [7.23250][7.23250:23541]()
    const ASIN_COLUMN_INDEX: usize = 0;
    const CURRENCY_COLUMN_INDEX: usize = 14;
    const ORDER_ID_COLUMN_INDEX: usize = 2;
    const PRODUCT_NAME_COLUMN_INDEX: usize = 1;
    const TOTAL_OWED_COLUMN_INDEX: usize = 13;
    let date = parse_order_date(record)?;
    [7.23250]
    [7.23541]
    let date = record.order_date;
  • replacement in importers/amazon/src/digital_items.rs at line 175
    [7.23542][7.23542:23707]()
    let asin = &record[ASIN_COLUMN_INDEX];
    let product_name = &record[PRODUCT_NAME_COLUMN_INDEX];
    let order_id = &record[ORDER_ID_COLUMN_INDEX];
    [7.23542]
    [7.23707]
    let asin = record.asin;
    let product_name = record.title;
    let order_id = record.order_id;
  • replacement in importers/amazon/src/digital_items.rs at line 179
    [7.23708][7.23708:24028]()
    let commodity = Commodity::try_from(&record[CURRENCY_COLUMN_INDEX]).unwrap();
    let amount = {
    let amount = Decimal::from_str(&record[TOTAL_OWED_COLUMN_INDEX])
    .unwrap()
    .tap_mut(|amount| amount.rescale(2));
    Amount::new(amount, commodity)
    };
    [7.23708]
    [7.24028]
    let amount = Amount::new(
    record.our_price_tax.tap_mut(|amount| amount.rescale(2)),
    record.our_price_tax_currency_code,
    );
  • replacement in importers/amazon/src/digital_items.rs at line 186
    [7.24129][7.24129:24178]()
    let currency: &str = &commodity;
    [7.24129]
    [7.24178]
    let currency: &str = &record.our_price_tax_currency_code;
  • edit in importers/amazon/src/digital_items.rs at line 193
    [7.24427]
    [7.24427]
    .set_payee(record.seller_of_record)
  • edit in importers/amazon/src/digital_items.rs at line 229
    [7.25695]
    [7.25695]
    #[derive(Clone, Copy, Debug, Deserialize)]
    #[serde(rename_all = "PascalCase")]
    pub struct Record<'r> {
    #[serde(rename = "ASIN")]
    asin: &'r str,
  • edit in importers/amazon/src/digital_items.rs at line 236
    [7.25696]
    [7.25696]
    fulfilled_date: Option<Date>,
    order_date: Date,
    order_id: &'r str,
    our_price_tax: Decimal,
    our_price_tax_currency_code: Commodity,
    seller_of_record: &'r str,
    #[serde(rename = "Title")]
    title: &'r str,
    }
  • edit in importers/amazon/src/digital_items.rs at line 291
    [7.26664][7.26664:26830]()
    }
    fn parse_order_date(record: &csv::Record) -> Result<Date, Error> {
    const ORDER_DATE_COLUMN_INDEX: usize = 9;
    parse_date(&record[ORDER_DATE_COLUMN_INDEX])
  • edit in importers/amazon/src/digital_items.rs at line 292
    [7.26832][7.26832:27088]()
    fn parse_date(input: &str) -> Result<Date, Error> {
    use time::format_description::FormatItem;
    const ORDER_DATE_FORMAT: &[FormatItem] = format_description!("[year]-[month]-[day]");
    Date::parse(input, ORDER_DATE_FORMAT).map_err(|_| todo!())
    }
  • file addition: serde.rs (---r------)
    [7.1856]
    use rust_decimal::Decimal;
    pub mod opt {
    use rust_decimal::Decimal;
    #[allow(non_camel_case_types)]
    pub struct with_precision<const PRECISION: u32>;
    impl<const PRECISION: u32> with_precision<PRECISION> {
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor<const PRECISION: u32>;
    impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {
    type Value = Option<Decimal>;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an optional decimal number in german format")
    }
    fn visit_none<E>(self) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    Ok(None)
    }
    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    super::with_precision::<PRECISION>::deserialize(deserializer).map(Some)
    }
    }
    deserializer.deserialize_option(Visitor::<PRECISION>)
    }
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = Option<Decimal>;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("an optional decimal number in german format")
    }
    fn visit_none<E>(self) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    Ok(None)
    }
    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    super::deserialize(deserializer).map(Some)
    }
    }
    deserializer.deserialize_option(Visitor)
    }
    }
    #[allow(non_camel_case_types)]
    pub struct with_precision<const PRECISION: u32>;
    impl<const PRECISION: u32> with_precision<PRECISION> {
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor<const PRECISION: u32>;
    impl<'de, const PRECISION: u32> serde::de::Visitor<'de> for Visitor<PRECISION> {
    type Value = Decimal;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("a decimal number in german format")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    let mut amount = crate::parse(v).map_err(E::custom)?;
    amount.rescale(PRECISION);
    Ok(amount)
    }
    }
    deserializer.deserialize_str(Visitor::<PRECISION>)
    }
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
    where
    D: serde::Deserializer<'de>,
    {
    struct Visitor;
    impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = Decimal;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    formatter.write_str("a decimal number in german format")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
    E: serde::de::Error,
    {
    crate::parse(v).map_err(E::custom)
    }
    }
    deserializer.deserialize_str(Visitor)
    }
  • edit in common/german-decimal/src/lib.rs at line 5
    [7.2041]
    [7.2041]
    pub mod serde;
  • replacement in common/german-decimal/Cargo.toml at line 4
    [7.3259][7.3259:3309]()
    edition.workspace = true
    publish.workspace = true
    [7.3259]
    [7.3309]
    edition.workspace = true
    publish.workspace = true
  • replacement in common/german-decimal/Cargo.toml at line 7
    [7.3339][7.3339:3364]()
    version.workspace = true
    [7.3339]
    [7.9704]
    version.workspace = true
  • edit in common/german-decimal/Cargo.toml at line 12
    [7.55]
    [7.9744]
    serde.workspace = true
  • replacement in common/german-decimal/Cargo.toml at line 16
    [7.81][7.81:107]()
    proptest.workspace = true
    [7.81]
    [7.107]
    proptest.workspace = true
  • edit in Cargo.toml at line 47
    [7.261][6.315:346]()
    iso_currency = "0.4.4"
  • edit in Cargo.toml at line 102
    [32.236]
    [32.236]
    [workspace.dependencies.iso_country]
    features = ["serde"]
    version = "0.1.4"
  • edit in Cargo.toml at line 107
    [32.237]
    [7.1584]
    [workspace.dependencies.iso_currency]
    features = ["with-serde"]
    version = "0.4.4"
  • replacement in Cargo.toml at line 116
    [7.2453][7.24983:25046](),[7.25046][6.1083:1103]()
    features = ["formatting", "local-offset", "macros", "parsing"]
    version = "0.3.28"
    [7.2453]
    [7.17973]
    features = [
    "formatting",
    "local-offset",
    "macros",
    "parsing",
    "serde-human-readable",
    ]
    version = "0.3.28"
  • edit in Cargo.lock at line 135
    [33.707]
    [33.707]
    "iso_country",
    "iso_currency",
  • edit in Cargo.lock at line 482
    [7.10088]
    [6.3027]
    "isin",
  • edit in Cargo.lock at line 928
    [6.5517]
    [6.5517]
    "serde",
  • edit in Cargo.lock at line 1361
    [7.10522]
    [34.29095]
    "serde",
  • edit in Cargo.lock at line 2104
    [7.91796818]
    [7.91796818]
    "serde",
  • edit in Cargo.lock at line 2114
    [7.91797049]
    [7.91797049]
    "serde",
  • edit in Cargo.lock at line 4128
    [7.15537]
    [7.15537]
    "rust_decimal",
  • edit in Cargo.lock at line 4223
    [6.24872]
    [7.12988]
    "iso_currency",
  • edit in Cargo.lock at line 4225
    [7.12999]
    [7.12999]
    "rust_decimal",