def _parse_main_table(self, transaction: MultiTransaction) -> Decimal: accounts = { 1000: 'income:salary', 3011: 'income:salary:holiday allowance', # Vacantiegeld 4461: 'expenses:taxes:retirement insurance', 4466: 'expenses:taxes:social', # WGA Aanvullend 4467: 'expenses:taxes:social', # WIA bodem 5150: 'income:salary', # Netto thuiswerkvergoeding 5216: 'income:salary', # Representatievergoeding 7380: 'expenses:taxes:social', # PAWW unemployment insurance 7100: 'expenses:taxes:income', # Loonheffing Tabel 7101: 'expenses:taxes:income', # Loonheffing BT } net_total: Optional[Decimal] = None for item in self.main_table: if item.is_total(): continue if item.code == 9900: net_total = item.uitbetaling continue assert item.code is not None account = accounts[item.code] if item.tabel is not None: amount = -item.tabel elif item.inhouding is not None: amount = item.inhouding elif item.uitbetaling is not None: amount = -item.uitbetaling else: raise ThermoFisherPdfParserError(f'Missing amount in {item}.') p = Posting(account, amount, comment=item.omschrijving) transaction.add_posting(p) assert net_total is not None return net_total
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_payment_table(self, transaction: MultiTransaction) -> Decimal: payment_total = Decimal('0.00') for item in self.payment_table: description = ' '.join(item.description.split()) p = Posting('assets:receivable:salary', item.amount, comment=description) transaction.add_posting(p) payment_total += item.amount return payment_total
def _parse_balance(self, description: str, transaction_date: date, value_date: date, amount: Decimal) -> MultiTransaction: t = MultiTransaction(description, transaction_date, metadata={'type': 'Zinsen/Kontoführung'}) t.add_posting(Posting(self.account, amount, posting_date=value_date)) m = re.search( 'R E C H N U N G S A B S C H L U S S' ' *\(siehe Rückseite\)\n', self.pdf_pages[1]) assert m is not None, 'Start of interests and fees not found.' start = m.end() m = re.search('Summe Zinsen/Kontoführung +EUR +(\d[.\d]*,\d\d[+-])', self.pdf_pages[1][start:]) assert m is not None, 'Sum of interests and fees not found.' assert (self.parse_amount(m.group(1)) == amount) end = start + m.start() for m in re.finditer( r' *(.* Habenzinsen) +(\d+ ZZ|) +' r'(\d[.\d]*,\d\d[+-])\n', self.pdf_pages[1][start:end]): t.add_posting( Posting(None, -self.parse_amount(m.group(3)), comment=m.group(1))) start = start + m.end() for m in re.finditer(' *(.+?) +EUR +(\d[.\d]*,\d\d[+-])\n', self.pdf_pages[1][start:end]): t.add_posting( Posting(None, -self.parse_amount(m.group(2)), comment=m.group(1))) assert (t.is_balanced()) return t
def _parse_banking_fees( self, description: list[str], *, bookdate: date, value_date: date, amount: Decimal, ) -> MultiTransaction: t = MultiTransaction(description=description[0].rstrip() \ + ' | Banking fees', transaction_date=bookdate) t.add_posting( Posting( account=self.account, amount=amount, currency=self.currency, posting_date=value_date, )) for line in description[1:]: m = re.match(r'(.+?) +(\d+,\d{2})', line) assert m is not None t.add_posting( Posting(account='expenses:banking', amount=parse_amount(m.group(2)), currency=self.currency, posting_date=value_date, comment=m.group(1))) return t
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(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 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])