def _compute_l10n_ch_isr_optical_line(self): """ The optical reading line of the ISR looks like this : left>isr_ref+ bank_ref> Where: - left is composed of two ciphers indicating the currency (01 for CHF, 03 for EUR), followed by ten characters containing the total of the invoice (with the dot between units and cents removed, everything being right-aligned and empty places filled with zeros). After the total, left contains a last cipher, which is the result of a recursive modulo 10 function ran over the rest of it. - isr_ref is the ISR reference number - bank_ref is the full postal bank code (aka clearing number) of the bank supporting the ISR (including the zeros). """ for record in self: if record.l10n_ch_isr_number and record.l10n_ch_isr_postal and record.currency_id.name: #Left part currency_code = None if record.currency_id.name == 'CHF': currency_code = '01' elif record.currency_id.name == 'EUR': currency_code = '03' units, cents = float_split_str(record.amount_total, 2) amount_to_display = units + cents amount_ref = amount_to_display.zfill(10) left = currency_code + amount_ref left = mod10r(left) #Final assembly (the space after the '+' is no typo, it stands in the specs.) record.l10n_ch_isr_optical_line = left + '>' + record.l10n_ch_isr_number + '+ ' + record.l10n_ch_isr_postal + '>'
def _compute_l10n_ch_isr_number(self): """ The ISR reference number is 27 characters long. The first 12 of them contain the postal account number of this ISR's issuer, removing the zeros at the beginning and filling the empty places with zeros on the right if it is too short. The next 14 characters contain an internal reference identifying the invoice. For this, we use the invoice sequence number, removing each of its non-digit characters, and pad the unused spaces on the left of this number with zeros. The last character of the ISR number is the result of a recursive modulo 10 on its first 26 characters. """ def _space_isr_number(isr_number): to_treat = isr_number res = '' while to_treat: res = to_treat[-5:] + res to_treat = to_treat[:-5] if to_treat: res = ' ' + res return res for record in self: if record.number and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal: invoice_issuer_ref = re.sub('^0*', '', record.partner_bank_id.l10n_ch_postal) invoice_issuer_ref = invoice_issuer_ref.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0') invoice_ref = re.sub('[^\d]', '', record.number) #We only keep the last digits of the sequence number if it is too long invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:] internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH - 1) # -1 for mod10r check character record.l10n_ch_isr_number = mod10r(invoice_issuer_ref + internal_ref) record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)
def _is_qr_reference(self, reference): """ Checks whether the given reference is a QR-reference, i.e. it is made of 27 digits, the 27th being a mod10r check on the 26 previous ones. """ return reference \ and len(reference) == 27 \ and re.match('\d+$', reference) \ and reference == mod10r(reference[:-1])
def _is_l10n_ch_postal(account_ref): """ Returns True iff the string account_ref is a valid postal account number, i.e. it only contains ciphers and is last cipher is the result of a recursive modulo 10 operation ran over the rest of it. """ if re.match('\d+$', account_ref or ''): account_ref_without_check = account_ref[:-1] return mod10r(account_ref_without_check) == account_ref return False
def _is_l10n_ch_postal(account_ref): """ Returns True if the string account_ref is a valid postal account number, i.e. it only contains ciphers and is last cipher is the result of a recursive modulo 10 operation ran over the rest of it. Shorten form with - is also accepted. """ if _re_postal.match(account_ref or ''): ref_subparts = account_ref.split('-') account_ref = ref_subparts[0] + ref_subparts[1].rjust(6, '0') + ref_subparts[2] if re.match('\d+$', account_ref or ''): account_ref_without_check = account_ref[:-1] return mod10r(account_ref_without_check) == account_ref return False
def _get_l10n_ch_isr_optical_amount(self): """Prepare amount string for ISR optical line""" self.ensure_one() currency_code = None if self.currency_id.name == 'CHF': currency_code = '01' elif self.currency_id.name == 'EUR': currency_code = '03' units, cents = float_split_str(self.amount_residual, 2) amount_to_display = units + cents amount_ref = amount_to_display.zfill(10) optical_amount = currency_code + amount_ref optical_amount = mod10r(optical_amount) return optical_amount
def _has_isr_ref(self): """Check if this invoice has a valid ISR reference (for Switzerland) e.g. 12371 000000000000000000000012371 210000000003139471430009017 21 00000 00003 13947 14300 09017 """ self.ensure_one() ref = self.payment_reference or self.ref if not ref: return False ref = ref.replace(' ', '') if re.match(r'^(\d{2,27})$', ref): return ref == mod10r(ref[:-1]) return False
def _compute_l10n_ch_isr_number(self): """Generates the ISR or QRR reference An ISR references are 27 characters long. QRR is a recycling of ISR for QR-bills. Thus works the same. The invoice sequence number is used, removing each of its non-digit characters, and pad the unused spaces on the left of this number with zeros. The last digit is a checksum (mod10r). There are 2 types of references: * ISR (Postfinance) The reference is free but for the last digit which is a checksum. If shorter than 27 digits, it is filled with zeros on the left. e.g. 120000000000234478943216899 \________________________/| 1 2 (1) 12000000000023447894321689 | reference (2) 9: control digit for identification number and reference * ISR-B (Indirect through a bank, requires a customer ID) In case of ISR-B The firsts digits (usually 6), contain the customer ID at the Bank of this ISR's issuer. The rest (usually 20 digits) is reserved for the reference plus the control digit. If the [customer ID] + [the reference] + [the control digit] is shorter than 27 digits, it is filled with zeros between the customer ID till the start of the reference. e.g. 150001123456789012345678901 \____/\__________________/| 1 2 3 (1) 150001 | id number of the customer (size may vary) (2) 12345678901234567890 | reference (3) 1: control digit for identification number and reference """ for record in self: has_qriban = record.partner_bank_id and record.partner_bank_id._is_qr_iban( ) or False isr_subscription = record.l10n_ch_isr_subscription if (has_qriban or isr_subscription) and record.name: id_number = record._get_isrb_id_number() if id_number: id_number = id_number.zfill(l10n_ch_ISR_ID_NUM_LENGTH) invoice_ref = re.sub('[^\d]', '', record.name) # keep only the last digits if it exceed boundaries full_len = len(id_number) + len(invoice_ref) ref_payload_len = l10n_ch_ISR_NUMBER_LENGTH - 1 extra = full_len - ref_payload_len if extra > 0: invoice_ref = invoice_ref[extra:] internal_ref = invoice_ref.zfill(ref_payload_len - len(id_number)) record.l10n_ch_isr_number = mod10r(id_number + internal_ref) else: record.l10n_ch_isr_number = False