Exemple #1
0
class SplitCreateForm(WTForm):
    account = TypeaheadField(u"Konto", validators=[DataRequired()])
    account_id = HiddenField(
        validators=[DataRequired(message=gettext("Missing account."))])
    amount = MoneyField(
        u"Wert", validators=[DataRequired(message=gettext("Invalid value."))])

    def validate_amount(self, field):
        cents = field.data.shift(2)
        if cents == 0 or cents != int(cents):
            raise ValidationError(gettext("Invalid value."))
Exemple #2
0
def check_transaction_on_save(mapper, connection, target):
    """
    Check transaction constraints.

    Transaction must be balanced, an account mustn't be referenced by more than
    one split and it must consist of at least two splits.
    :raises: IllegalTransactionError if transaction contains errors
    """
    if not target.is_balanced:
        raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
    if len(target.splits) < 2:
        raise IllegalTransactionError(gettext(u"Transaction must consist "
                                              u"of at least two splits."))
Exemple #3
0
def check_transaction_on_save(mapper, connection, target):
    """
    Check transaction constraints.

    Transaction must be balanced, an account mustn't be referenced by more than
    one split and it must consist of at least two splits.
    :raises: IllegalTransactionError if transaction contains errors
    """
    if not target.is_balanced:
        raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
    if len(target.splits) < 2:
        raise IllegalTransactionError(
            gettext(u"Transaction must consist "
                    u"of at least two splits."))
Exemple #4
0
 def __init__(self, message, cause=None):
     if cause is not None:
         message = gettext(u"{0}\nCaused by:\n{1}").format(
             message, cause
         )
     self.cause = cause
     super(CSVImportError, self).__init__(message)
Exemple #5
0
 def _check_subnet_valid(self, address, subnet):
     if address is None or subnet is None:
         return
     if address not in subnet:
         message = gettext('IP address {} is not contained in its subnet {}'
                           .format(address, subnet))
         raise ValueError(message)
Exemple #6
0
 def __init__(self, message, cause=None):
     if cause is not None:
         message = gettext(u"{0}\nCaused by:\n{1}").format(
             message, cause
         )
     self.cause = cause
     super(CSVImportError, self).__init__(message)
Exemple #7
0
 def _check_subnet_valid(self, address, subnet):
     if address is None or subnet is None:
         return
     if address not in subnet:
         message = gettext(
             'IP address {} is not contained in its subnet {}'.format(
                 address, subnet))
         raise ValueError(message)
Exemple #8
0
def transaction_type_filter(credit_debit_type):
    def replacer(types):
        return types and tuple(sub(r'[A-Z]+_(?=ASSET)', r'', t) for t in types)

    types = {
        ("ASSET", "LIABILITY"): gettext("Balance sheet extension"),
        ("LIABILITY", "ASSET"): gettext("Balance sheet contraction"),
        ("ASSET", "REVENUE"): gettext("Revenue"),
        ("REVENUE", "ASSET"): gettext("Correcting entry (Revenue)"),
        ("EXPENSE", "ASSET"): gettext("Expense"),
        ("ASSET", "EXPENSE"): gettext("Correcting entry (Expense)"),
        ("ASSET", "ASSET"): gettext("Asset exchange"),
        ("LIABILITY", "LIABILITY"): gettext("Liability exchange")
    }
    return types.get(replacer(credit_debit_type), gettext("Unknown"))
Exemple #9
0
def transaction_type_filter(credit_debit_type):
    def replacer(types):
        return types and tuple(sub(r'[A-Z]+_(?=ASSET)', r'', t) for t in types)

    types = {
        ("ASSET", "LIABILITY"): gettext("Balance sheet extension"),
        ("LIABILITY", "ASSET"): gettext("Balance sheet contraction"),
        ("ASSET", "REVENUE"): gettext("Revenue"),
        ("REVENUE", "ASSET"): gettext("Correcting entry (Revenue)"),
        ("EXPENSE", "ASSET"): gettext("Expense"),
        ("ASSET", "EXPENSE"): gettext("Correcting entry (Expense)"),
        ("ASSET", "ASSET"): gettext("Asset exchange"),
        ("LIABILITY", "LIABILITY"): gettext("Liability exchange")
    }
    return types.get(replacer(credit_debit_type), gettext("Unknown"))
Exemple #10
0
def account_type_filter(account_type):
    types = {
        "USER_ASSET": gettext("User account (asset)"),
        "BANK_ASSET": gettext("Bank account (asset)"),
        "ASSET": gettext("Asset account"),
        "LIABILITY": gettext("Liability account"),
        "REVENUE": gettext("Revenue account"),
        "EXPENSE": gettext("Expense account"),
        "LEGACY": gettext("Legacy account"),
    }

    return types.get(account_type)
Exemple #11
0
    def errorpage(e):
        """Handle errors according to their error code

        :param e: The error from the errorhandler
        """
        # We need this path hard-coding because the global app errorhandlers have higher
        # precedence than anything registered to a blueprint.
        # A clean solution would be flask supporting nested blueprints (see flask #539)
        if request.path.startswith('/api/'):
            return api.errorpage(e)

        if not hasattr(e, 'code'):
            code = 500
        else:
            code = e.code
        if code == 500:
            message = str(e)
        elif code == 403:
            message = gettext(u"You are not allowed to access this page.")
        elif code == 404:
            message = gettext(u"Page not found.")
        else:
            raise AssertionError()
        return render_template('error.html', error=message), code
Exemple #12
0
def process_record(index, record, imported_at):
    if record.currency != u"EUR":
        message = gettext(u"Unsupported currency {0}. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(message.format(record.currency, index, raw_record))
    try:
        bank_account = BankAccount.q.filter_by(
            account_number=record.our_account_number
        ).one()
    except NoResultFound as e:
        message = gettext(u"No bank account with account number {0}. "
                          u"Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(
            message.format(record.our_account_number, index, raw_record), e)

    try:
        valid_on = datetime.strptime(record.valid_on, u"%d.%m.%y").date()
        posted_on = datetime.strptime(record.posted_on, u"%d.%m.%y").date()
    except ValueError as e:
        message = gettext(u"Illegal date format. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(message.format(index, raw_record), e)

    try:
        amount = Decimal(record.amount.replace(u",", u"."))
    except ValueError as e:
        message = gettext(u"Illegal value format {0}. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(
            message.format(record.amount, index, raw_record), e)

    return (amount, bank_account.id, cleanup_description(record.reference),
            record.reference, record.other_account_number,
            record.other_routing_number, record.other_name, imported_at,
            posted_on, valid_on)
Exemple #13
0
def process_record(index, record, imported_at):
    if record.currency != u"EUR":
        message = gettext(u"Unsupported currency {0}. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(message.format(record.currency, index, raw_record))
    try:
        bank_account = BankAccount.q.filter_by(
            account_number=record.our_account_number
        ).one()
    except NoResultFound as e:
        message = gettext(u"No bank account with account number {0}. "
                          u"Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(
            message.format(record.our_account_number, index, raw_record), e)

    try:
        valid_on = datetime.strptime(record.valid_on, u"%d.%m.%y").date()
        posted_on = datetime.strptime(record.posted_on, u"%d.%m.%y").date()
    except ValueError as e:
        message = gettext(u"Illegal date format. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(message.format(index, raw_record), e)

    try:
        amount = Decimal(record.amount.replace(u",", u"."))
    except ValueError as e:
        message = gettext(u"Illegal value format {0}. Record {1}: {2}")
        raw_record = restore_record(record)
        raise CSVImportError(
            message.format(record.amount, index, raw_record), e)

    return (amount, bank_account.id, cleanup_description(record.reference),
            record.reference, record.other_account_number,
            record.other_routing_number, record.other_name, imported_at,
            posted_on, valid_on)
Exemple #14
0
def account_type_filter(account_type):
    types = {
        "USER_ASSET": gettext("User account (asset)"),
        "BANK_ASSET": gettext("Bank account (asset)"),
        "ASSET": gettext("Asset account"),
        "LIABILITY": gettext("Liability account"),
        "REVENUE": gettext("Revenue account"),
        "EXPENSE": gettext("Expense account"),
    }

    return types.get(account_type)
Exemple #15
0
def room_edit(room_id):
    room = Room.q.get(room_id)

    if not room:
        flash("Raum mit ID {} nicht gefunden!".format(room_id), "error")
        return redirect(url_for('.overview'))

    form = EditRoomForm(building=room.building.short_name,
                        level=room.level,
                        number=room.number,
                        inhabitable=room.inhabitable,
                        vo_suchname=room.swdd_vo_suchname)

    if form.validate_on_submit():
        try:
            with session.session.no_autoflush:
                address = get_or_create_address(**form.address_kwargs)
                edit_room(room,
                          form.number.data,
                          form.inhabitable.data,
                          form.vo_suchname.data,
                          address=address,
                          processor=current_user)

            session.session.commit()

            flash(
                "Der Raum {} wurde erfolgreich bearbeitet.".format(
                    room.short_name), "success")

            return redirect(url_for('.room_show', room_id=room.id))
        except RoomAlreadyExistsException:
            form.number.errors.append(
                "Ein Raum mit diesem Namen existiert bereits in dieser Etage!")

    old_addr = room.address
    if not form.is_submitted():
        form.address_street.data = old_addr.street
        form.address_number.data = old_addr.number
        form.address_addition.data = old_addr.addition
        form.address_zip_code.data = old_addr.zip_code
        form.address_city.data = old_addr.city
        form.address_state.data = old_addr.state
        form.address_country.data = old_addr.country

    if room.users_sharing_address:
        flash(
            gettext(
                "Dieser Raum hat {} bewohner ({}), die die Adresse des Raums teilen."
                " Ihre Adresse wird beim Ändern automatisch angepasst.").
            format(len(room.users_sharing_address),
                   ', '.join(u.name for u in room.users_sharing_address)),
            'info')

    form_args = {
        'form': form,
        'cancel_to': url_for('.room_show', room_id=room.id)
    }

    return render_template('generic_form.html',
                           page_title="Raum bearbeiten",
                           form_args=form_args)
Exemple #16
0
def import_bank_account_activities_csv(csv_file, expected_balance,
                                       imported_at=None):
    """
    Import bank account activities from a MT940 CSV file into the database.

    The new activities are merged with the activities that are already saved to
    the database.
    :param csv_file:
    :param expected_balance:
    :param imported_at:
    :return:
    """

    if imported_at is None:
        imported_at = session.utcnow()

    # Convert to MT940Record and enumerate
    reader = csv.reader(csv_file, dialect=MT940Dialect)
    records = enumerate((MT940Record._make(r) for r in reader), 1)
    try:
        # Skip first record (header)
        next(records)
        activities = tuple(
            process_record(index, record, imported_at=imported_at)
            for index, record in records)
    except StopIteration:
        raise CSVImportError(gettext(u"No data present."))
    except csv.Error as e:
        raise CSVImportError(gettext(u"Could not read CSV."), e)
    if not activities:
        raise CSVImportError(gettext(u"No data present."))
    if not is_ordered((a[8] for a in activities), operator.ge):
        raise CSVImportError(gettext(
            u"Transaction are not sorted according to transaction date in "
            u"descending order."))
    first_posted_on = activities[-1][8]
    balance = session.session.query(
        func.coalesce(func.sum(BankAccountActivity.amount), 0)
    ).filter(
        BankAccountActivity.posted_on < first_posted_on
    ).scalar()
    a = tuple(session.session.query(
        BankAccountActivity.amount, BankAccountActivity.bank_account_id,
        BankAccountActivity.reference, BankAccountActivity.reference,
        BankAccountActivity.other_account_number,
        BankAccountActivity.other_routing_number,
        BankAccountActivity.other_name, BankAccountActivity.imported_at,
        BankAccountActivity.posted_on, BankAccountActivity.valid_on
    ).filter(
        BankAccountActivity.posted_on >= first_posted_on)
    )
    b = tuple(reversed(activities))
    matcher = difflib.SequenceMatcher(a=a, b=b)
    for tag, i1, i2, j1, j2 in matcher.get_opcodes():
        if 'equal' == tag:
            continue
        elif 'insert' == tag:
            balance += sum(a[0] for a in islice(activities, j1, j2))
            session.session.add_all(
                BankAccountActivity(
                    amount=e[0], bank_account_id=e[1], reference=e[3],
                    other_account_number=e[4],
                    other_routing_number=e[5], other_name=e[6],
                    imported_at=e[7], posted_on=e[8], valid_on=e[9]
                ) for e in islice(activities, j1, j2)
            )
        elif 'delete' == tag:
            continue
        elif 'replace' == tag:
            raise CSVImportError(
                gettext(u"Import conflict:\n"
                        u"Database bank account activities:\n{0}\n"
                        u"File bank account activities:\n{1}").format(
                    u'\n'.join(str(x) for x in islice(activities, i1, i2)),
                    u'\n'.join(str(x) for x in islice(activities, j1, j2))))
        else:
            raise AssertionError()
    if balance != expected_balance:
        message = gettext(u"Balance after does not equal expected balance: "
                          u"{0} != {1}.")
        raise CSVImportError(message.format(balance, expected_balance))
Exemple #17
0
def import_bank_account_activities_csv(csv_file, expected_balance,
                                       imported_at=None):
    """
    Import bank account activities from a MT940 CSV file into the database.

    The new activities are merged with the activities that are already saved to
    the database.
    :param csv_file:
    :param expected_balance:
    :param imported_at:
    :return:
    """

    if imported_at is None:
        imported_at = session.utcnow()

    # Convert to MT940Record and enumerate
    reader = csv.reader(csv_file, dialect=MT940Dialect)
    records = enumerate((MT940Record._make(r) for r in reader), 1)
    try:
        # Skip first record (header)
        next(records)
        activities = tuple(
            process_record(index, record, imported_at=imported_at)
            for index, record in records)
    except StopIteration:
        raise CSVImportError(gettext(u"No data present."))
    except csv.Error as e:
        raise CSVImportError(gettext(u"Could not read CSV."), e)
    if not activities:
        raise CSVImportError(gettext(u"No data present."))
    if not is_ordered((a[8] for a in activities), operator.ge):
        raise CSVImportError(gettext(
            u"Transaction are not sorted according to transaction date in "
            u"descending order."))
    first_posted_on = activities[-1][8]
    balance = session.session.query(
        func.coalesce(func.sum(BankAccountActivity.amount), 0)
    ).filter(
        BankAccountActivity.posted_on < first_posted_on
    ).scalar()
    a = tuple(session.session.query(
        BankAccountActivity.amount, BankAccountActivity.bank_account_id,
        BankAccountActivity.reference, BankAccountActivity.original_reference,
        BankAccountActivity.other_account_number,
        BankAccountActivity.other_routing_number,
        BankAccountActivity.other_name, BankAccountActivity.imported_at,
        BankAccountActivity.posted_on, BankAccountActivity.valid_on
    ).filter(
        BankAccountActivity.posted_on >= first_posted_on)
    )
    b = tuple(reversed(activities))
    matcher = difflib.SequenceMatcher(a=a, b=b)
    for tag, i1, i2, j1, j2 in matcher.get_opcodes():
        if 'equal' == tag:
            continue
        elif 'insert' == tag:
            balance += sum(a[0] for a in islice(activities, j1, j2))
            session.session.add_all(
                BankAccountActivity(
                    amount=e[0], bank_account_id=e[1], reference=e[2],
                    original_reference=e[3], other_account_number=e[4],
                    other_routing_number=e[5], other_name=e[6],
                    imported_at=e[7], posted_on=e[8], valid_on=e[9]
                ) for e in islice(activities, j1, j2)
            )
        elif 'delete' == tag:
            continue
        elif 'replace' == tag:
            raise CSVImportError(
                gettext(u"Import conflict:\n"
                        u"Database bank account activities:\n{0}\n"
                        u"File bank account activities:\n{1}").format(
                    u'\n'.join(str(x) for x in islice(activities, i1, i2)),
                    u'\n'.join(str(x) for x in islice(activities, j1, j2))))
        else:
            raise AssertionError()
    if balance != expected_balance:
        message = gettext(u"Balance after does not equal expected balance: "
                          u"{0} != {1}.")
        raise CSVImportError(message.format(balance, expected_balance))
Exemple #18
0
def check_split_on_update(mapper, connection, target):
    if not target.transaction.is_balanced:
        raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
Exemple #19
0
 def validate_amount(self, field):
     cents = field.data.shift(2)
     if cents == 0 or cents != int(cents):
         raise ValidationError(gettext("Invalid value."))
Exemple #20
0
def check_split_on_update(mapper, connection, target):
    if not target.transaction.is_balanced:
        raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
Exemple #21
0
 def validate_amount(self, field):
     cents = field.data.shift(2)
     if cents == 0 or cents != int(cents):
         raise ValidationError(gettext("Invalid value."))