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."))
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."))
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."))
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)
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)
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)
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)
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"))
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"))
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)
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
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)
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)
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)
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)
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))
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))
def check_split_on_update(mapper, connection, target): if not target.transaction.is_balanced: raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
def validate_amount(self, field): cents = field.data.shift(2) if cents == 0 or cents != int(cents): raise ValidationError(gettext("Invalid value."))
def check_split_on_update(mapper, connection, target): if not target.transaction.is_balanced: raise IllegalTransactionError(gettext(u"Transaction is not balanced."))
def validate_amount(self, field): cents = field.data.shift(2) if cents == 0 or cents != int(cents): raise ValidationError(gettext("Invalid value."))