2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
import re
2018-01-16 11:34:37 +01:00
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
2018-01-16 06:58:15 +01:00
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