flectra/addons/l10n_ch/models/account_invoice.py
2018-01-16 02:34:37 -08:00

170 lines
9.2 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
import re
from flectra import models, fields, api, _
from flectra.exceptions import ValidationError
from flectra.tools.float_utils import float_split_str
from flectra.tools.misc import mod10r
l10n_ch_ISR_NUMBER_LENGTH = 27
l10n_ch_ISR_NUMBER_ISSUER_LENGTH = 12
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
l10n_ch_isr_postal = fields.Char(compute='_compute_l10n_ch_isr_postal', help='The postal reference identifying the bank managing this ISR.')
l10n_ch_isr_postal_formatted = fields.Char(compute='_compute_l10n_ch_isr_postal', help="Postal reference of the bank, formated with '-' and without the padding zeros, to generate ISR report.")
l10n_ch_isr_number = fields.Char(compute='_compute_l10n_ch_isr_number', store=True, help='The reference number associated with this invoice')
l10n_ch_isr_number_spaced = fields.Char(compute='_compute_l10n_ch_isr_number', help="ISR number split in blocks of 5 characters (right-justified), to generate ISR report.")
l10n_ch_isr_optical_line = fields.Char(compute="_compute_l10n_ch_isr_optical_line", help='Optical reading line, as it will be printed on ISR')
l10n_ch_isr_valid = fields.Boolean(compute='_compute_l10n_ch_isr_valid', help='Boolean value. True iff all the data required to generate the ISR are present')
l10n_ch_isr_sent = fields.Boolean(defaut=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
l10n_ch_currency_name = fields.Char(related='currency_id.name', help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.
@api.depends('partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
def _compute_l10n_ch_isr_postal(self):
""" Computes the postal reference identifying the bank managing this ISR and formats it accordingly"""
def _format_isr_postal(isr_postal):
#format the isr as per specifications
currency_code = isr_postal[:2]
middle_part = isr_postal[2:-1]
trailing_cipher = isr_postal[-1]
middle_part = re.sub('^0*', '', middle_part)
return currency_code + '-' + middle_part + '-' + trailing_cipher
for record in self:
if record.partner_bank_id and record.partner_bank_id.bank_id:
isr_postal = False
if record.currency_id.name == 'EUR':
isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_eur
elif record.currency_id.name == 'CHF':
isr_postal = record.partner_bank_id.bank_id.l10n_ch_postal_chf
else:
#we don't format if in another currency as EUR or CHF
continue
if isr_postal:
record.l10n_ch_isr_postal = isr_postal
record.l10n_ch_isr_postal_formatted = _format_isr_postal(isr_postal)
@api.depends('number', 'partner_bank_id.l10n_ch_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)
@api.depends('currency_id.name', 'amount_total', 'partner_bank_id.bank_id', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
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 + '>'
@api.depends('type', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id', 'currency_id.name', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
def _compute_l10n_ch_isr_valid(self):
"""Returns True if all the data required to generate the ISR are present"""
for record in self:
record.l10n_ch_isr_valid = record.type == 'out_invoice' and\
record.number and \
record.l10n_ch_isr_postal and \
record.partner_bank_id and \
record.partner_bank_id.l10n_ch_postal and \
record.l10n_ch_currency_name in ['EUR', 'CHF']
def split_total_amount(self):
""" Splits the total amount of this invoice in two parts, using the dot as
a separator, and taking two precision digits (always displayed).
These two parts are returned as the two elements of a tuple, as strings
to print in the report.
This function is needed on the model, as it must be called in the report
template, which cannot reference static functions
"""
return float_split_str(self.amount_total, 2)
def isr_print(self):
""" Triggered by the 'Print ISR' button.
"""
self.ensure_one()
if self.l10n_ch_isr_valid:
self.l10n_ch_isr_sent = True
return self.env.ref('l10n_ch.l10n_ch_isr_report').report_action(self)
else:
raise ValidationError(_("""You cannot generate an ISR yet.\n
For this, you need to :\n
- set a valid postal account number (or an IBAN referencing one) for your company\n
- define its bank\n
- associate this bank with a postal reference for the currency used in this invoice\n
- fill the 'bank account' field of the invoice with the postal to be used to receive the related payment. A default account will be automatically set for all invoices created after you defined a postal account for your company."""))
def action_invoice_sent(self):
""" Overridden. Triggered by the 'send by mail' button.
"""
rslt = super(AccountInvoice, self).action_invoice_sent()
if self.l10n_ch_isr_valid:
rslt['context']['l10n_ch_mark_isr_as_sent'] = True
return rslt