def format_ref_number(bill): if not bill.ref_number: return '' num = bill.ref_number if bill.ref_type == "QRR": return esr.format(num) elif bill.ref_type == "SCOR": return iso11649.format(num) else: return num
def __init__(self, account=None, creditor=None, final_creditor=None, amount=None, currency='CHF', due_date=None, debtor=None, ref_number=None, extra_infos='', alt_procs=(), language='en', top_line=True, payment_line=True): """ Arguments --------- account: str IBAN of the creditor (must start with 'CH' or 'LI') creditor: Address Address (combined or structured) of the creditor final_creditor: Address (for future use) amount: str currency: str two values allowed: 'CHF' and 'EUR' due_date: str (YYYY-MM-DD) debtor: Address Address (combined or structured) of the debtor extra_infos: str Extra information aimed for the bill recipient alt_procs: list of str (max 2) two additional fields for alternative payment schemes language: str language of the output (ISO, 2 letters): 'en', 'de', 'fr' or 'it' top_line: bool print a horizontal line at the top of the bill payment_line: bool print a vertical line between the receipt and the bill itself """ # Account (IBAN) validation if not account: raise ValueError("The account parameter is mandatory") if not iban.is_valid(account): raise ValueError("Sorry, the IBAN is not valid") self.account = iban.validate(account) if self.account[:2] not in IBAN_ALLOWED_COUNTRIES: raise ValueError("IBAN must start with: %s" % ", ".join(IBAN_ALLOWED_COUNTRIES)) iban_iid = int(self.account[4:9]) if QR_IID["start"] <= iban_iid <= QR_IID["end"]: self.account_is_qriban = True else: self.account_is_qriban = False if amount is not None: if isinstance(amount, Decimal): amount = str(amount) elif not isinstance(amount, str): raise ValueError( "Amount can only be specified as str or Decimal.") # remove commonly used thousands separators amount = amount.replace("'", "").strip() # people often don't add .00 for amounts without cents/rappen if "." not in amount: amount = amount + ".00" # support lazy people who write 12.1 instead of 12.10 if amount[-2] == '.': amount = amount + '0' # strip leading zeros amount = amount.lstrip("0") # some people tend to strip the leading zero on amounts below 1 CHF/EUR # and with removing leading zeros, we would have removed the zero before # the decimal delimiter anyway if amount[0] == ".": amount = "0" + amount m = re.match(AMOUNT_REGEX, amount) if not m: raise ValueError( "If provided, the amount must match the pattern '###.##'" " and cannot be larger than 999'999'999.99") self.amount = amount if currency not in self.allowed_currencies: raise ValueError("Currency can only contain: %s" % ", ".join(self.allowed_currencies)) self.currency = currency if due_date: m = re.match(DATE_REGEX, due_date) if not m: raise ValueError( "The date must match the pattern 'YYYY-MM-DD'") due_date = date(*[int(g) for g in m.groups()]) self.due_date = due_date if not creditor: raise ValueError("Creditor information is mandatory") try: self.creditor = Address.create(**creditor) except ValueError as err: raise ValueError("The creditor address is invalid: %s" % err) if final_creditor is not None: # The standard says ultimate creditor is reserved for future use. # The online validator does not properly validate QR-codes where # this is set, saying it must not (yet) be used. raise ValueError( "final creditor is reserved for future use, must not be used") else: self.final_creditor = final_creditor if debtor is not None: try: self.debtor = Address.create(**debtor) except ValueError as err: raise ValueError("The debtor address is invalid: %s" % err) else: self.debtor = debtor if not ref_number: self.ref_type = 'NON' self.ref_number = None elif ref_number.strip()[:2].upper() == "RF": if iso11649.is_valid(ref_number): self.ref_type = 'SCOR' self.ref_number = iso11649.validate(ref_number) else: raise ValueError("The reference number is invalid") elif esr.is_valid(ref_number): self.ref_type = 'QRR' self.ref_number = esr.format(ref_number).replace(" ", "") else: raise ValueError("The reference number is invalid") # A QRR reference number must only be used with a QR-IBAN and # with a QR-IBAN, a QRR reference number must be used if self.account_is_qriban: if self.ref_type != 'QRR': raise ValueError("A QR-IBAN requires a QRR reference number") else: if self.ref_type == 'QRR': raise ValueError( "A QRR reference number is only allowed for a QR-IBAN") if extra_infos and len(extra_infos) > 140: raise ValueError( "Additional information cannot contain more than 140 characters" ) self.extra_infos = extra_infos if len(alt_procs) > 2: raise ValueError( "Only two lines allowed in alternative procedure parameters") if any(len(el) > 100 for el in alt_procs): raise ValueError( "An alternative procedure line cannot be longer than 100 characters" ) self.alt_procs = list(alt_procs) # Meta-information if language not in ['en', 'de', 'fr', 'it']: raise ValueError("Language should be 'en', 'de', 'fr', or 'it'") self.language = language self.top_line = top_line self.payment_line = payment_line