예제 #1
0
 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
예제 #2
0
 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])
예제 #3
0
 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
예제 #4
0
 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
예제 #5
0
 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
예제 #6
0
    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
예제 #7
0
    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])
예제 #8
0
    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])