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 _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 QRR or 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. """ for record in self: has_qriban = record.invoice_partner_bank_id and record.invoice_partner_bank_id._is_qr_iban( ) or False isr_subscription = (record.invoice_partner_bank_id.l10n_ch_postal or '').replace( "-", "") # In case the user put the - if (has_qriban or isr_subscription) and record.name: invoice_issuer_ref = (isr_subscription or '').ljust( l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0') invoice_ref = re.sub('[^\d]', '', record.name) #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) else: record.l10n_ch_isr_number = False
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: record.l10n_ch_isr_optical_line = '' if record.l10n_ch_isr_number and record.l10n_ch_isr_subscription 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_residual, 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_subscription + '>'
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_isr_supplier_invoice(self): """Check for payments that a supplier invoice has a bank account that can issue ISR and that the reference is an ISR reference number""" # We consider a structured ref can only be in `reference` whereas in v13 # it can be in 2 different fields ref = self.invoice_payment_ref or self.ref if (ref and self.invoice_partner_bank_id.is_isr_issuer() and re.match(r"^(\d{2,27}|\d{2}( \d{5}){5})$", ref)): ref = ref.replace(" ", "") return ref == mod10r(ref[:-1]) 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 _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. Shorten form with - is also accepted. """ if re.match('^[0-9]{2}-[0-9]{1,6}-[0-9]$', 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, payment_comm): """Check if the communication is a valid ISR reference (for Switzerland) e.g. 12371 000000000000000000000012371 210000000003139471430009017 21 00000 00003 13947 14300 09017 This is used to determine SEPA local instrument """ if not payment_comm: return False if re.match(r'^(\d{2,27}|\d{2}( \d{5}){5})$', payment_comm): ref = payment_comm.replace(' ', '') return ref == mod10r(ref[:-1]) return False
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.invoice_payment_ref 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_ref(self): """Retrieve ISR reference from move line in order to print it Returns False when no ISR reference should be generated. No reference is generated when a transaction reference already exists for the line (likely been generated by a payment service). """ for rec in self: move_line = rec.move_line_id if not rec._can_generate(move_line): continue # We should not use technical id but will keep it for # historical reason move_number = str(move_line.id) ad_number = rec._get_adherent_number() if move_line.invoice_id.number: compound = move_line.invoice_id.number + str(move_line.id) move_number = rec._compile_get_ref.sub('', compound) reference = mod10r(ad_number + move_number.rjust(26 - len(ad_number), '0')) rec.reference = rec._space(reference)
def _compute_scan_line_list(self): """Generate a list containing all element of scan line the element are grouped by char or symbol This will allows the free placment of each element and enable a fine tuning of spacing :return: a list of sting representing the scan bar :rtype: list """ self.ensure_one() line = [] if not self._can_generate(self.move_line_id): return [] justified_amount = '01%s' % ('%.2f' % self.amount_total).replace( '.', '').rjust(10, '0') line += [char for char in mod10r(justified_amount)] line.append('>') line += [char for char in self.reference.replace(" ", "")] line.append('+') line.append(' ') partner_bank = self.move_line_id.invoice_id.partner_bank_id bank = partner_bank.get_account_number() account_components = bank.split('-') if len(account_components) != 3: raise exceptions.UserError( _('Please enter a correct postal number like: ' '01-23456-1')) bank_identifier = "%s%s%s" % ( account_components[0], account_components[1].rjust(6, '0'), account_components[2] ) line += [car for car in bank_identifier] line.append('>') return line
def _create_record(self, line): """Create a v11 record dict :param line: raw v11 line :type line: str :return: current line dict representation :rtype: dict """ amount = self._get_line_amount(line) cost = self._get_line_cost(line) record = { 'reference': line[12:39], 'amount': amount, 'date': time.strftime('%Y-%m-%d', time.strptime(line[65:71], '%y%m%d')), 'cost': cost, } if record['reference'] != mod10r(record['reference'][:-1]): raise exceptions.UserError( _('Recursive mod10 is invalid for reference: %s') % record['reference']) return record
def _compute_l10n_ch_isr_optical_line(self): """ Compute the optical line to print on the bottom of the ISR. This line is read by an OCR. It's format is: amount>reference+ creditor> Where: - amount: currency and invoice amount - reference: ISR structured reference number - in case of ISR-B contains the Customer ID number - it can also contains a partner reference (of the debitor) - creditor: Subscription number of the creditor An optical line can have the 2 following formats: * ISR (Postfinance) 0100003949753>120000000000234478943216899+ 010001628> |/\________/| \________________________/| \_______/ 1 2 3 4 5 6 (1) 01 | currency (2) 0000394975 | amount 3949.75 (3) 4 | control digit for amount (5) 12000000000023447894321689 | reference (6) 9: control digit for identification number and reference (7) 010001628: subscription number (01-162-8) * ISR-B (Indirect through a bank, requires a customer ID) 0100000494004>150001123456789012345678901+ 010234567> |/\________/| \____/\__________________/| \_______/ 1 2 3 4 5 6 7 (1) 01 | currency (2) 0000049400 | amount 494.00 (3) 4 | control digit for amount (4) 150001 | id number of the customer (size may vary, usually 6 chars) (5) 12345678901234567890 | reference (6) 1: control digit for identification number and reference (7) 010234567: subscription number (01-23456-7) """ for record in self: record.l10n_ch_isr_optical_line = '' if record.l10n_ch_isr_number and record.l10n_ch_isr_subscription 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_residual, 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_subscription + '>'
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.invoice_partner_bank_id and record.invoice_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
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.invoice_partner_bank_id and record.invoice_partner_bank_id._is_qr_iban( ) or False isr_subscription = (record.invoice_partner_bank_id.l10n_ch_postal or '').replace( "-", "") # In case the user put the - if (has_qriban or isr_subscription) and record.name: invoice_issuer_ref = (isr_subscription or '').ljust( l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0') invoice_ref = re.sub('[^\d]', '', record.name) #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) else: record.l10n_ch_isr_number = False
this number with zeros. The last character of the ISR number is the result of a recursive modulo 10 on its first 26 characters. """ for record in self: <<<<<<< HEAD if record.name and record.invoice_partner_bank_id and record.invoice_partner_bank_id.l10n_ch_postal: invoice_issuer_ref = record.invoice_partner_bank_id.l10n_ch_postal.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0') ======= if record.name and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal: invoice_issuer_ref = record.partner_bank_id.l10n_ch_postal.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0') >>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8 invoice_ref = re.sub('[^\d]', '', record.name) #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) else: record.l10n_ch_isr_number = False @api.depends('l10n_ch_isr_number') def _compute_l10n_ch_isr_number_spaced(self): 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