def test_add(self): self.assertEqual(Amount(D('117.02'), 'CAD'), amount.add(Amount(D('100'), 'CAD'), Amount(D('17.02'), 'CAD'))) with self.assertRaises(ValueError): amount.add(Amount(D('100'), 'USD'), Amount(D('17.02'), 'CAD'))
def compute_stack(stack): for i in range(1, len(stack), 2): if stack[i] == '+': stack[0] = add(stack[0], stack[i + 1]) elif stack[i] == '-': stack[0] = sub(stack[0], stack[i + 1]) return stack[0]
def get_entries(self): for file, import_map in sorted(self.entry_map.items()): if os.path.basename(file) in self.matching_paychecks: continue entry = None for imported_data in import_map: if entry is None: entry = Transaction( meta=None, date=imported_data.date, flag=FLAG_OKAY, payee=None, narration=imported_data.narration, tags=EMPTY_SET, links=EMPTY_SET, postings=[], ) entry.postings.append(imported_data.posting()) # Verify entry completeness entry_sum = Amount(Decimal("0"), "USD") self.source.log_status( f"Verifying Transaction: {entry.date} {entry.narration}") for posting in entry.postings: entry_sum = amount.add(entry_sum, posting.units) self.source.log_status(pformat(posting, indent=2)) self.source.log_status(f"entry_sum = {entry_sum}") if entry_sum: err_msg = ("ERROR: Invalid Transaction posting total: " + f"{entry_sum} from {os.path.basename(file)}") self.source.log_status(err_msg) print(err_msg) else: self.results.add_pending_entry( ImportResult( date=imported_data.date, entries=[entry], info=get_info(imported_data), ))
def extract(self, file): # Open the CSV file and create directives. entries = [] index = 0 for index, row in enumerate(csv.DictReader(open(file.name))): meta = data.new_metadata(file.name, index) date = parse(row['DATE']).date() rtype = row['TYPE'] link = "ut{0[REF #]}".format(row) desc = "({0[TYPE]}) {0[DESCRIPTION]}".format(row) units = amount.Amount(D(row['AMOUNT']), self.currency) fees = amount.Amount(D(row['FEES']), self.currency) other = amount.add(units, fees) if rtype == 'XFER': assert fees.number == ZERO txn = data.Transaction( meta, date, self.FLAG, None, desc, data.EMPTY_SET, {link}, [ data.Posting(self.account_cash, units, None, None, None, None), data.Posting(self.account_external, -other, None, None, None, None), ]) elif rtype == 'DIV': assert fees.number == ZERO # Extract the instrument name from its description. match = re.search(r'~([A-Z]+)$', row['DESCRIPTION']) if not match: logging.error("Missing instrument name in '%s'", row['DESCRIPTION']) continue instrument = match.group(1) account_dividends = self.account_dividends.format(instrument) txn = data.Transaction( meta, date, self.FLAG, None, desc, data.EMPTY_SET, {link}, [ data.Posting(self.account_cash, units, None, None, None, None), data.Posting(account_dividends, -other, None, None, None, None), ]) elif rtype in ('BUY', 'SELL'): # Extract the instrument name, number of units, and price from # the description. That's just what we're provided with (this is # actually realistic of some data from some institutions, you # have to figure out a way in your parser). match = re.search(r'\+([A-Z]+)\b +([0-9.]+)\b +@([0-9.]+)', row['DESCRIPTION']) if not match: logging.error("Missing purchase infos in '%s'", row['DESCRIPTION']) continue instrument = match.group(1) account_inst = account.join(self.account_root, instrument) units_inst = amount.Amount(D(match.group(2)), instrument) rate = D(match.group(3)) if rtype == 'BUY': cost = position.Cost(rate, self.currency, None, None) txn = data.Transaction( meta, date, self.FLAG, None, desc, data.EMPTY_SET, {link}, [ data.Posting(self.account_cash, units, None, None, None, None), data.Posting(self.account_fees, fees, None, None, None, None), data.Posting(account_inst, units_inst, cost, None, None, None), ]) elif rtype == 'SELL': # Extract the lot. In practice this information not be there # and you will have to identify the lots manually by editing # the resulting output. You can leave the cost.number slot # set to None if you like. match = re.search(r'\(LOT ([0-9.]+)\)', row['DESCRIPTION']) if not match: logging.error("Missing cost basis in '%s'", row['DESCRIPTION']) continue cost_number = D(match.group(1)) cost = position.Cost(cost_number, self.currency, None, None) price = amount.Amount(rate, self.currency) account_gains = self.account_gains.format(instrument) txn = data.Transaction( meta, date, self.FLAG, None, desc, data.EMPTY_SET, {link}, [ data.Posting(self.account_cash, units, None, None, None, None), data.Posting(self.account_fees, fees, None, None, None, None), data.Posting(account_inst, units_inst, cost, price, None, None), data.Posting(account_gains, None, None, None, None, None), ]) else: logging.error("Unknown row type: %s; skipping", rtype) continue entries.append(txn) # Insert a final balance check. if index: entries.append( data.Balance(meta, date + datetime.timedelta(days=1), self.account_cash, amount.Amount(D(row['BALANCE']), self.currency), None, None)) return entries