def parse(self, rules_dir: Optional[Path]) -> BankStatement: m = re.search(r'DATE DE PAIEMENT *(\d\d \S* \d{4})', self.summary_text) if m is None: raise PayfitPdfParserError('Could not find payment date.') payment_date = parse_verbose_date(m.group(1)) transaction = MultiTransaction('Salaire', payment_date) salary_postings, total_gross_salary = self._parse_salary() social_security_postings, social_security_total = \ self._parse_social_security_payments() transportation_postings, transportation_reimbursed \ = self._parse_travel_reimbursement() meal_vouchers = self._parse_meal_vouchers() m = self.net_before_taxes_pattern.search(self.summary_text) if m is None: raise PayfitPdfParserError('Could not find net before taxes.') net_before_taxes = parse_amount(m.group(1)) income_tax = self._parse_tax_deducted_at_source() payment = self._parse_payment() assert (-total_gross_salary - transportation_reimbursed + meal_vouchers.amount + social_security_total + income_tax.amount + payment.amount == 0) transaction.add_posting(payment) for p in salary_postings: transaction.add_posting(p) transaction.add_posting(income_tax) for p in social_security_postings: transaction.add_posting(p) transaction.add_posting(meal_vouchers) for p in transportation_postings: transaction.add_posting(p) return BankStatement(None, [transaction])
def parse_raw(self) -> BankStatement: with self.csv_file.open(encoding='LATIN1', newline='') as f: f.seek(self.csv_start) reader = csv.DictReader(f, fieldnames=self.csv_fieldnames, dialect=IngCsvDialect) transactions: list[BaseTransaction] = [] for row in reader: transaction_type = row['Buchungstext'] destination = row['Auftraggeber/Empfänger'] description = destination + ' | ' + row['Verwendungszweck'] currency = row['Währung'] if currency == 'EUR': currency = '€' transaction = Transaction( account=self.account, description=description, operation_date=parse_date(row['Buchung']), value_date=parse_date(row['Valuta']), amount=parse_amount(row['Betrag']), currency=currency, metadata={ 'type': transaction_type, }, ) transactions.append(transaction) transactions = list(reversed(transactions)) return BankStatement(self.account, transactions)
def parse(self, rules_dir: Optional[Path]) -> BankStatement: metadata = self.parse_metadata() transaction = MultiTransaction(metadata.description, metadata.payment_date) net_total = self._parse_main_table(transaction) payment_total = self._parse_payment_table(transaction) assert net_total == payment_total assert transaction.is_balanced() return BankStatement(None, [transaction])
def parse_raw(self) -> BankStatement: self.transactions_text = self.extract_transactions_table() self.parse_balances() transactions = [ t for t in self.generate_transactions(self.transactions_start, self.transactions_end) ] return BankStatement(self.account, transactions, self.old_balance, self.new_balance)
def parse_raw(self) -> BankStatement: if not self.qif_file.exists(): raise IOError(f'Unknown file: {self.qif_file}') with open(self.qif_file) as f: header = f.readline() if not header == '!Type:Bank\n': raise RuntimeError(f'Unknown QIF account type: {header}') transactions = self._parse_transactions(f) return BankStatement(account=self.account, transactions=transactions)
def parse(self, rules_dir: Optional[Path]) -> BankStatement: m = re.search(r'Date de paiement\s*Période de paie\n' r'\s*BULLETIN DE PAIE' r'\s*(\d\d/\d\d/\d{4})\s*' r's*DU (\d\d/\d\d/\d{4}) AU (\d\d/\d\d/\d{4})', self.pdf_pages[0]) if m is None: raise BouyguesPdfParserError('Could not find payment date.') payment_date = parse_date(m.group(1)) payment_period = (parse_date(m.group(2)), parse_date(m.group(3))) description = f'Salaire du {payment_period[0]} au {payment_period[1]}' transaction = MultiTransaction(description, payment_date) lines = self.iter_main_table() header = next(lines) if header.is_section_header() and header.description == 'ELEMENTS DE REVENU BRUT': salary_postings, total_gross_salary \ = self._parse_gross_income(lines) else: assert header.description == "AUTRES CONTRIBUTIONS DUES PAR L'EMPLOYEUR" salary_postings = [] total_gross_salary = Decimal('0.00') social_security_postings, social_security_total = \ self._parse_social_security_payments(lines) misc_postings, misc_total = self._parse_misc(lines) net_before_taxes, income_tax = self._parse_net_income(lines) payment = self._parse_payment(lines) assert(total_gross_salary + social_security_total + misc_total - income_tax.amount - payment.amount == 0) transaction.add_posting(payment) for p in salary_postings: transaction.add_posting(p) transaction.add_posting(income_tax) for p in social_security_postings: transaction.add_posting(p) for p in misc_postings: transaction.add_posting(p) assert transaction.is_balanced() return BankStatement(None, [transaction])
def travel_history_to_bank_statement(transactions: list[OvTransaction], recharge_account: str) -> BankStatement: def convert_transaction(transaction: OvTransaction) -> Transaction: # TODO: That looks like a nice case for structural pattern matching # once we depend on Python 3.10. if transaction.type == 'recharge': account = recharge_account elif transaction.mode_of_transportation is None: raise RuntimeError( 'Expected mode_of_transportation for transaction' f' {transaction}.') elif transaction.mode_of_transportation.startswith('Trein'): account = 'expenses:transportation:train' elif transaction.mode_of_transportation.startswith('Bus'): account = 'expenses:transportation:bus' else: raise RuntimeError('Unknown mode of transportation' f' "{transaction.mode_of_transportation}" in' f' transaction {transaction}.') description = 'OV-Chipkaart |' if transaction.mode_of_transportation is None: description += ' ' + transaction.type else: description += ' ' + transaction.mode_of_transportation if transaction.place: description += ' | ' + transaction.place return Transaction( account='assets:OV-Chipkaart', description=description, operation_date=transaction.date, value_date=None, amount=transaction.amount, currency='€', external_account=account, ) return BankStatement( account='assets:OV-Chipkaart', transactions=[convert_transaction(t) for t in transactions], )
def _add_interest_details(self, bank_statement: BankStatement) -> None: interests = self.parse_interest_postings() interest_transaction = cast(Transaction, bank_statement.transactions[-1]) assert interest_transaction.type == 'Zinsertrag' interests_sum = sum(i.amount for i in interests) assert interest_transaction.amount + interests_sum == 0 interest_transaction.to_multi_transaction() mt = MultiTransaction( description=interest_transaction.description, transaction_date=interest_transaction.operation_date, metadata=interest_transaction.metadata) mt.add_posting(Posting( interest_transaction.account, interest_transaction.amount, interest_transaction.currency, interest_transaction.value_date)) for posting in interests: mt.add_posting(posting) bank_statement.transactions[-1] = mt
def parse_raw(self) -> BankStatement: transactions = list(reversed(list(self._iter_main_table()))) return BankStatement(self.account, transactions, self.old_balance, self.new_balance)
def parse_raw(self) -> BankStatement: #self.check_transactions_consistency(self.transactions) return BankStatement(self.account, self.transactions)