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
2018-01-16 11:34:37 +01:00
from flectra import fields , models , api , _
from flectra . addons import decimal_precision as dp
from flectra . exceptions import UserError
2018-01-16 06:58:15 +01:00
class AccountVoucher ( models . Model ) :
_name = ' account.voucher '
_description = ' Accounting Voucher '
_inherit = [ ' mail.thread ' ]
_order = " date desc, id desc "
@api.model
def _default_journal ( self ) :
voucher_type = self . _context . get ( ' voucher_type ' , ' sale ' )
company_id = self . _context . get ( ' company_id ' , self . env . user . company_id . id )
domain = [
( ' type ' , ' = ' , voucher_type ) ,
( ' company_id ' , ' = ' , company_id ) ,
]
return self . env [ ' account.journal ' ] . search ( domain , limit = 1 )
voucher_type = fields . Selection ( [
( ' sale ' , ' Sale ' ) ,
( ' purchase ' , ' Purchase ' )
] , string = ' Type ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , oldname = " type " )
name = fields . Char ( ' Payment Reference ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = ' ' )
date = fields . Date ( " Bill Date " , readonly = True ,
index = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
copy = False , default = fields . Date . context_today )
account_date = fields . Date ( " Accounting Date " ,
readonly = True , index = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " Effective date for accounting entries " , copy = False , default = fields . Date . context_today )
journal_id = fields . Many2one ( ' account.journal ' , ' Journal ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = _default_journal )
account_id = fields . Many2one ( ' account.account ' , ' Account ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
domain = " [( ' deprecated ' , ' = ' , False), ( ' internal_type ' , ' = ' , (pay_now == ' pay_now ' and ' liquidity ' or voucher_type == ' purchase ' and ' payable ' or ' receivable ' ))] " )
line_ids = fields . One2many ( ' account.voucher.line ' , ' voucher_id ' , ' Voucher Lines ' ,
readonly = True , copy = True ,
states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
narration = fields . Text ( ' Notes ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
currency_id = fields . Many2one ( ' res.currency ' , compute = ' _get_journal_currency ' ,
string = ' Currency ' , readonly = True , required = True , default = lambda self : self . _get_currency ( ) )
company_id = fields . Many2one ( ' res.company ' , ' Company ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
related = ' journal_id.company_id ' , default = lambda self : self . _get_company ( ) )
state = fields . Selection ( [
( ' draft ' , ' Draft ' ) ,
( ' cancel ' , ' Cancelled ' ) ,
( ' proforma ' , ' Pro-forma ' ) ,
( ' posted ' , ' Posted ' )
] , ' Status ' , readonly = True , track_visibility = ' onchange ' , copy = False , default = ' draft ' ,
help = " * The ' Draft ' status is used when a user is encoding a new and unconfirmed Voucher. \n "
" * The ' Pro-forma ' status is used when the voucher does not have a voucher number. \n "
" * The ' Posted ' status is used when user create voucher,a voucher number is generated and voucher entries are created in account. \n "
" * The ' Cancelled ' status is used when user cancel voucher. " )
reference = fields . Char ( ' Bill Reference ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " The partner reference of this document. " , copy = False )
amount = fields . Monetary ( string = ' Total ' , store = True , readonly = True , compute = ' _compute_total ' )
tax_amount = fields . Monetary ( readonly = True , store = True , compute = ' _compute_total ' )
tax_correction = fields . Monetary ( readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = ' In case we have a rounding problem in the tax, use this field to correct it ' )
number = fields . Char ( readonly = True , copy = False )
move_id = fields . Many2one ( ' account.move ' , ' Journal Entry ' , copy = False )
partner_id = fields . Many2one ( ' res.partner ' , ' Partner ' , change_default = 1 , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
paid = fields . Boolean ( compute = ' _check_paid ' , help = " The Voucher has been totally paid. " )
pay_now = fields . Selection ( [
( ' pay_now ' , ' Pay Directly ' ) ,
( ' pay_later ' , ' Pay Later ' ) ,
] , ' Payment ' , index = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = ' pay_later ' )
date_due = fields . Date ( ' Due Date ' , readonly = True , index = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
@api.one
@api.depends ( ' move_id.line_ids.reconciled ' , ' move_id.line_ids.account_id.internal_type ' )
def _check_paid ( self ) :
self . paid = any ( [ ( ( line . account_id . internal_type , ' in ' , ( ' receivable ' , ' payable ' ) ) and line . reconciled ) for line in self . move_id . line_ids ] )
@api.model
def _get_currency ( self ) :
journal = self . env [ ' account.journal ' ] . browse ( self . env . context . get ( ' default_journal_id ' , False ) )
if journal . currency_id :
return journal . currency_id . id
return self . env . user . company_id . currency_id . id
@api.model
def _get_company ( self ) :
return self . _context . get ( ' company_id ' , self . env . user . company_id . id )
@api.multi
@api.depends ( ' name ' , ' number ' )
def name_get ( self ) :
return [ ( r . id , ( r . number or _ ( ' Voucher ' ) ) ) for r in self ]
@api.one
@api.depends ( ' journal_id ' , ' company_id ' )
def _get_journal_currency ( self ) :
self . currency_id = self . journal_id . currency_id . id or self . company_id . currency_id . id
@api.multi
@api.depends ( ' tax_correction ' , ' line_ids.price_subtotal ' )
def _compute_total ( self ) :
for voucher in self :
total = 0
tax_amount = 0
for line in voucher . line_ids :
tax_info = line . tax_ids . compute_all ( line . price_unit , voucher . currency_id , line . quantity , line . product_id , voucher . partner_id )
total + = tax_info . get ( ' total_included ' , 0.0 )
tax_amount + = sum ( [ t . get ( ' amount ' , 0.0 ) for t in tax_info . get ( ' taxes ' , False ) ] )
voucher . amount = total + voucher . tax_correction
voucher . tax_amount = tax_amount
@api.one
@api.depends ( ' account_pay_now_id ' , ' account_pay_later_id ' , ' pay_now ' )
def _get_account ( self ) :
self . account_id = self . account_pay_now_id if self . pay_now == ' pay_now ' else self . account_pay_later_id
@api.onchange ( ' date ' )
def onchange_date ( self ) :
self . account_date = self . date
@api.onchange ( ' partner_id ' , ' pay_now ' )
def onchange_partner_id ( self ) :
if self . pay_now == ' pay_now ' :
liq_journal = self . env [ ' account.journal ' ] . search ( [ ( ' type ' , ' in ' , ( ' bank ' , ' cash ' ) ) ] , limit = 1 )
self . account_id = liq_journal . default_debit_account_id \
if self . voucher_type == ' sale ' else liq_journal . default_credit_account_id
else :
if self . partner_id :
self . account_id = self . partner_id . property_account_receivable_id \
if self . voucher_type == ' sale ' else self . partner_id . property_account_payable_id
else :
account_type = self . voucher_type == ' purchase ' and ' payable ' or ' receivable '
domain = [ ( ' deprecated ' , ' = ' , False ) , ( ' internal_type ' , ' = ' , account_type ) ]
self . account_id = self . env [ ' account.account ' ] . search ( domain , limit = 1 )
@api.multi
def proforma_voucher ( self ) :
self . action_move_line_create ( )
@api.multi
def action_cancel_draft ( self ) :
self . write ( { ' state ' : ' draft ' } )
@api.multi
def cancel_voucher ( self ) :
for voucher in self :
voucher . move_id . button_cancel ( )
voucher . move_id . unlink ( )
self . write ( { ' state ' : ' cancel ' , ' move_id ' : False } )
@api.multi
def unlink ( self ) :
for voucher in self :
if voucher . state not in ( ' draft ' , ' cancel ' ) :
raise UserError ( _ ( ' Cannot delete voucher(s) which are already opened or paid. ' ) )
return super ( AccountVoucher , self ) . unlink ( )
@api.multi
def first_move_line_get ( self , move_id , company_currency , current_currency ) :
debit = credit = 0.0
if self . voucher_type == ' purchase ' :
credit = self . _convert_amount ( self . amount )
elif self . voucher_type == ' sale ' :
debit = self . _convert_amount ( self . amount )
if debit < 0.0 : debit = 0.0
if credit < 0.0 : credit = 0.0
sign = debit - credit < 0 and - 1 or 1
#set the first line of the voucher
move_line = {
' name ' : self . name or ' / ' ,
' debit ' : debit ,
' credit ' : credit ,
' account_id ' : self . account_id . id ,
' move_id ' : move_id ,
' journal_id ' : self . journal_id . id ,
' partner_id ' : self . partner_id . id ,
' currency_id ' : company_currency != current_currency and current_currency or False ,
' amount_currency ' : ( sign * abs ( self . amount ) # amount < 0 for refunds
if company_currency != current_currency else 0.0 ) ,
' date ' : self . account_date ,
' date_maturity ' : self . date_due ,
' payment_id ' : self . _context . get ( ' payment_id ' ) ,
}
return move_line
@api.multi
def account_move_get ( self ) :
if self . number :
name = self . number
elif self . journal_id . sequence_id :
if not self . journal_id . sequence_id . active :
raise UserError ( _ ( ' Please activate the sequence of selected journal ! ' ) )
name = self . journal_id . sequence_id . with_context ( ir_sequence_date = self . date ) . next_by_id ( )
else :
raise UserError ( _ ( ' Please define a sequence on the journal. ' ) )
move = {
' name ' : name ,
' journal_id ' : self . journal_id . id ,
' narration ' : self . narration ,
' date ' : self . account_date ,
' ref ' : self . reference ,
}
return move
@api.multi
def _convert_amount ( self , amount ) :
'''
This function convert the amount given in company currency . It takes either the rate in the voucher ( if the
payment_rate_currency_id is relevant ) either the rate encoded in the system .
: param amount : float . The amount to convert
: param voucher : id of the voucher on which we want the conversion
: param context : to context to use for the conversion . It may contain the key ' date ' set to the voucher date
field in order to select the good rate to use .
: return : the amount in the currency of the voucher ' s company
: rtype : float
'''
for voucher in self :
return voucher . currency_id . compute ( amount , voucher . company_id . currency_id )
@api.multi
def voucher_pay_now_payment_create ( self ) :
payment_methods = self . journal_id . outbound_payment_method_ids
return {
' payment_type ' : ' outbound ' ,
' payment_method_id ' : payment_methods and payment_methods [ 0 ] . id or False ,
' partner_type ' : ' supplier ' ,
' partner_id ' : self . partner_id . id ,
' amount ' : self . amount ,
' currency_id ' : self . currency_id . id ,
' payment_date ' : self . date ,
' journal_id ' : self . journal_id . id ,
' company_id ' : self . company_id . id ,
' communication ' : self . name ,
' name ' : self . name ,
' state ' : ' reconciled ' ,
}
@api.multi
def voucher_move_line_create ( self , line_total , move_id , company_currency , current_currency ) :
'''
Create one account move line , on the given account move , per voucher line where amount is not 0.0 .
It returns Tuple with tot_line what is total of difference between debit and credit and
a list of lists with ids to be reconciled with this format ( total_deb_cred , list_of_lists ) .
: param voucher_id : Voucher id what we are working with
: param line_total : Amount of the first line , which correspond to the amount we should totally split among all voucher lines .
: param move_id : Account move wher those lines will be joined .
: param company_currency : id of currency of the company to which the voucher belong
: param current_currency : id of currency of the voucher
: return : Tuple build as ( remaining amount not allocated on voucher lines , list of account_move_line created in this method )
: rtype : tuple ( float , list of int )
'''
for line in self . line_ids :
#create one move line per voucher line where amount is not 0.0
if not line . price_subtotal :
continue
# convert the amount set on the voucher line into the currency of the voucher's company
# this calls res_curreny.compute() with the right context,
# so that it will take either the rate on the voucher if it is relevant or will use the default behaviour
amount = self . _convert_amount ( line . price_unit * line . quantity )
move_line = {
' journal_id ' : self . journal_id . id ,
' name ' : line . name or ' / ' ,
' account_id ' : line . account_id . id ,
' move_id ' : move_id ,
' partner_id ' : self . partner_id . id ,
' analytic_account_id ' : line . account_analytic_id and line . account_analytic_id . id or False ,
' quantity ' : 1 ,
' credit ' : abs ( amount ) if self . voucher_type == ' sale ' else 0.0 ,
' debit ' : abs ( amount ) if self . voucher_type == ' purchase ' else 0.0 ,
' date ' : self . account_date ,
' tax_ids ' : [ ( 4 , t . id ) for t in line . tax_ids ] ,
' amount_currency ' : line . price_subtotal if current_currency != company_currency else 0.0 ,
' currency_id ' : company_currency != current_currency and current_currency or False ,
' payment_id ' : self . _context . get ( ' payment_id ' ) ,
}
self . env [ ' account.move.line ' ] . with_context ( apply_taxes = True ) . create ( move_line )
return line_total
@api.multi
def action_move_line_create ( self ) :
'''
Confirm the vouchers given in ids and create the journal entries for each of them
'''
for voucher in self :
local_context = dict ( self . _context , force_company = voucher . journal_id . company_id . id )
if voucher . move_id :
continue
company_currency = voucher . journal_id . company_id . currency_id . id
current_currency = voucher . currency_id . id or company_currency
# we select the context to use accordingly if it's a multicurrency case or not
# But for the operations made by _convert_amount, we always need to give the date in the context
ctx = local_context . copy ( )
ctx [ ' date ' ] = voucher . account_date
ctx [ ' check_move_validity ' ] = False
# Create a payment to allow the reconciliation when pay_now = 'pay_now'.
if self . pay_now == ' pay_now ' and self . amount > 0 :
ctx [ ' payment_id ' ] = self . env [ ' account.payment ' ] . create ( self . voucher_pay_now_payment_create ( ) ) . id
# Create the account move record.
move = self . env [ ' account.move ' ] . create ( voucher . account_move_get ( ) )
# Get the name of the account_move just created
# Create the first line of the voucher
move_line = self . env [ ' account.move.line ' ] . with_context ( ctx ) . create ( voucher . with_context ( ctx ) . first_move_line_get ( move . id , company_currency , current_currency ) )
line_total = move_line . debit - move_line . credit
if voucher . voucher_type == ' sale ' :
line_total = line_total - voucher . _convert_amount ( voucher . tax_amount )
elif voucher . voucher_type == ' purchase ' :
line_total = line_total + voucher . _convert_amount ( voucher . tax_amount )
# Create one move line per voucher line where amount is not 0.0
line_total = voucher . with_context ( ctx ) . voucher_move_line_create ( line_total , move . id , company_currency , current_currency )
# Add tax correction to move line if any tax correction specified
if voucher . tax_correction != 0.0 :
tax_move_line = self . env [ ' account.move.line ' ] . search ( [ ( ' move_id ' , ' = ' , move . id ) , ( ' tax_line_id ' , ' != ' , False ) ] , limit = 1 )
if len ( tax_move_line ) :
tax_move_line . write ( { ' debit ' : tax_move_line . debit + voucher . tax_correction if tax_move_line . debit > 0 else 0 ,
' credit ' : tax_move_line . credit + voucher . tax_correction if tax_move_line . credit > 0 else 0 } )
# We post the voucher.
voucher . write ( {
' move_id ' : move . id ,
' state ' : ' posted ' ,
' number ' : move . name
} )
move . post ( )
return True
@api.multi
def _track_subtype ( self , init_values ) :
if ' state ' in init_values :
return ' account_voucher.mt_voucher_state_change '
return super ( AccountVoucher , self ) . _track_subtype ( init_values )
class AccountVoucherLine ( models . Model ) :
_name = ' account.voucher.line '
_description = ' Voucher Lines '
name = fields . Text ( string = ' Description ' , required = True )
sequence = fields . Integer ( default = 10 ,
help = " Gives the sequence of this line when displaying the voucher. " )
voucher_id = fields . Many2one ( ' account.voucher ' , ' Voucher ' , required = 1 , ondelete = ' cascade ' )
product_id = fields . Many2one ( ' product.product ' , string = ' Product ' ,
ondelete = ' set null ' , index = True )
account_id = fields . Many2one ( ' account.account ' , string = ' Account ' ,
required = True , domain = [ ( ' deprecated ' , ' = ' , False ) ] ,
help = " The income or expense account related to the selected product. " )
price_unit = fields . Float ( string = ' Unit Price ' , required = True , digits = dp . get_precision ( ' Product Price ' ) , oldname = ' amount ' )
price_subtotal = fields . Monetary ( string = ' Amount ' ,
store = True , readonly = True , compute = ' _compute_subtotal ' )
quantity = fields . Float ( digits = dp . get_precision ( ' Product Unit of Measure ' ) ,
required = True , default = 1 )
account_analytic_id = fields . Many2one ( ' account.analytic.account ' , ' Analytic Account ' )
company_id = fields . Many2one ( ' res.company ' , related = ' voucher_id.company_id ' , string = ' Company ' , store = True , readonly = True )
tax_ids = fields . Many2many ( ' account.tax ' , string = ' Tax ' , help = " Only for tax excluded from price " )
currency_id = fields . Many2one ( ' res.currency ' , related = ' voucher_id.currency_id ' )
@api.one
@api.depends ( ' price_unit ' , ' tax_ids ' , ' quantity ' , ' product_id ' , ' voucher_id.currency_id ' )
def _compute_subtotal ( self ) :
self . price_subtotal = self . quantity * self . price_unit
if self . tax_ids :
taxes = self . tax_ids . compute_all ( self . price_unit , self . voucher_id . currency_id , self . quantity , product = self . product_id , partner = self . voucher_id . partner_id )
self . price_subtotal = taxes [ ' total_excluded ' ]
@api.onchange ( ' product_id ' , ' voucher_id ' , ' price_unit ' , ' company_id ' )
def _onchange_line_details ( self ) :
if not self . voucher_id or not self . product_id or not self . voucher_id . partner_id :
return
onchange_res = self . product_id_change (
self . product_id . id ,
self . voucher_id . partner_id . id ,
self . price_unit ,
self . company_id . id ,
self . voucher_id . currency_id . id ,
self . voucher_id . voucher_type )
for fname , fvalue in onchange_res [ ' value ' ] . items ( ) :
setattr ( self , fname , fvalue )
def _get_account ( self , product , fpos , type ) :
accounts = product . product_tmpl_id . get_product_accounts ( fpos )
if type == ' sale ' :
return accounts [ ' income ' ]
return accounts [ ' expense ' ]
@api.multi
def product_id_change ( self , product_id , partner_id = False , price_unit = False , company_id = None , currency_id = None , type = None ) :
# TDE note: mix of old and new onchange badly written in 9, multi but does not use record set
context = self . _context
company_id = company_id if company_id is not None else context . get ( ' company_id ' , False )
company = self . env [ ' res.company ' ] . browse ( company_id )
currency = self . env [ ' res.currency ' ] . browse ( currency_id )
if not partner_id :
raise UserError ( _ ( " You must first select a partner! " ) )
part = self . env [ ' res.partner ' ] . browse ( partner_id )
if part . lang :
self = self . with_context ( lang = part . lang )
product = self . env [ ' product.product ' ] . browse ( product_id )
fpos = part . property_account_position_id
account = self . _get_account ( product , fpos , type )
values = {
' name ' : product . partner_ref ,
' account_id ' : account . id ,
}
if type == ' purchase ' :
values [ ' price_unit ' ] = price_unit or product . standard_price
taxes = product . supplier_taxes_id or account . tax_ids
if product . description_purchase :
values [ ' name ' ] + = ' \n ' + product . description_purchase
else :
values [ ' price_unit ' ] = price_unit or product . lst_price
taxes = product . taxes_id or account . tax_ids
if product . description_sale :
values [ ' name ' ] + = ' \n ' + product . description_sale
values [ ' tax_ids ' ] = taxes . ids
if company and currency :
if company . currency_id != currency :
if type == ' purchase ' :
values [ ' price_unit ' ] = price_unit or product . standard_price
values [ ' price_unit ' ] = values [ ' price_unit ' ] * currency . rate
return { ' value ' : values , ' domain ' : { } }