2011-09-13 10:46:53 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2009-11-13 05:41:16 +00:00
#
2008-11-06 07:29:50 +00:00
# OpenERP, Open Source Management Solution
2010-01-12 09:18:39 +00:00
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is free software: you can redistribute it and/or modify
2009-10-14 11:15:34 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2009-10-14 11:15:34 +00:00
# GNU Affero General Public License for more details.
2006-12-07 13:41:40 +00:00
#
2009-10-14 11:15:34 +00:00
# You should have received a copy of the GNU Affero General Public License
2009-11-13 05:41:16 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
2014-07-06 14:44:26 +00:00
import itertools
2010-10-08 11:28:58 +00:00
from lxml import etree
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
from openerp import models , fields , api , _
from openerp . exceptions import except_orm , Warning , RedirectWarning
2014-09-26 14:55:41 +00:00
from openerp . tools import float_compare
2014-07-06 14:44:26 +00:00
import openerp . addons . decimal_precision as dp
2013-05-23 12:28:55 +00:00
2014-07-06 14:44:26 +00:00
# mapping invoice type to journal type
TYPE2JOURNAL = {
' out_invoice ' : ' sale ' ,
' in_invoice ' : ' purchase ' ,
' out_refund ' : ' sale_refund ' ,
' in_refund ' : ' purchase_refund ' ,
}
2008-09-12 07:21:22 +00:00
2014-07-06 14:44:26 +00:00
# mapping invoice type to refund type
TYPE2REFUND = {
' out_invoice ' : ' out_refund ' , # Customer Invoice
' in_invoice ' : ' in_refund ' , # Supplier Invoice
' out_refund ' : ' out_invoice ' , # Customer Refund
' in_refund ' : ' in_invoice ' , # Supplier Refund
}
2008-09-18 13:32:58 +00:00
2014-07-06 14:44:26 +00:00
MAGIC_COLUMNS = ( ' id ' , ' create_uid ' , ' create_date ' , ' write_uid ' , ' write_date ' )
2008-09-12 07:21:22 +00:00
2009-01-16 09:44:05 +00:00
2014-07-06 14:44:26 +00:00
class account_invoice ( models . Model ) :
2008-07-22 15:11:28 +00:00
_name = " account.invoice "
2012-04-03 10:58:01 +00:00
_inherit = [ ' mail.thread ' ]
2014-07-06 14:44:26 +00:00
_description = " Invoice "
2014-05-30 16:47:50 +00:00
_order = " number desc, id desc "
2012-12-18 22:06:17 +00:00
_track = {
2012-12-19 12:15:20 +00:00
' type ' : {
} ,
2012-12-18 22:06:17 +00:00
' state ' : {
2013-06-27 14:46:47 +00:00
' account.mt_invoice_paid ' : lambda self , cr , uid , obj , ctx = None : obj . state == ' paid ' and obj . type in ( ' out_invoice ' , ' out_refund ' ) ,
' account.mt_invoice_validated ' : lambda self , cr , uid , obj , ctx = None : obj . state == ' open ' and obj . type in ( ' out_invoice ' , ' out_refund ' ) ,
2012-12-18 22:06:17 +00:00
} ,
}
2014-07-06 14:44:26 +00:00
@api.one
@api.depends ( ' invoice_line.price_subtotal ' , ' tax_line.amount ' )
def _compute_amount ( self ) :
self . amount_untaxed = sum ( line . price_subtotal for line in self . invoice_line )
self . amount_tax = sum ( line . amount for line in self . tax_line )
self . amount_total = self . amount_untaxed + self . amount_tax
@api.model
def _default_journal ( self ) :
inv_type = self . _context . get ( ' type ' , ' out_invoice ' )
inv_types = inv_type if isinstance ( inv_type , list ) else [ inv_type ]
company_id = self . _context . get ( ' company_id ' , self . env . user . company_id . id )
domain = [
( ' type ' , ' in ' , filter ( None , map ( TYPE2JOURNAL . get , inv_types ) ) ) ,
( ' company_id ' , ' = ' , company_id ) ,
]
return self . env [ ' account.journal ' ] . search ( domain , limit = 1 )
@api.model
def _default_currency ( self ) :
journal = self . _default_journal ( )
return journal . currency or journal . company_id . currency_id
@api.model
2014-08-21 16:54:44 +00:00
@api.returns ( ' account.analytic.journal ' , lambda r : r . id )
2014-07-06 14:44:26 +00:00
def _get_journal_analytic ( self , inv_type ) :
""" Return the analytic journal corresponding to the given invoice type. """
2014-12-01 14:28:00 +00:00
type2journal = { ' out_invoice ' : ' sale ' , ' in_invoice ' : ' purchase ' , ' out_refund ' : ' sale ' , ' in_refund ' : ' purchase ' }
journal_type = type2journal . get ( inv_type , ' sale ' )
2014-07-06 14:44:26 +00:00
journal = self . env [ ' account.analytic.journal ' ] . search ( [ ( ' type ' , ' = ' , journal_type ) ] , limit = 1 )
if not journal :
raise except_orm ( _ ( ' No Analytic Journal! ' ) ,
_ ( " You must define an analytic journal of type ' %s ' ! " ) % ( journal_type , ) )
2014-08-21 16:54:44 +00:00
return journal [ 0 ]
2014-07-06 14:44:26 +00:00
@api.one
@api.depends ( ' account_id ' , ' move_id.line_id.account_id ' , ' move_id.line_id.reconcile_id ' )
def _compute_reconciled ( self ) :
self . reconciled = self . test_paid ( )
@api.model
def _get_reference_type ( self ) :
return [ ( ' none ' , _ ( ' Free Reference ' ) ) ]
@api.one
@api.depends (
' state ' , ' currency_id ' , ' invoice_line.price_subtotal ' ,
' move_id.line_id.account_id.type ' ,
' move_id.line_id.amount_residual ' ,
2014-11-03 10:32:53 +00:00
# Fixes the fact that move_id.line_id.amount_residual, being not stored and old API, doesn't trigger recomputation
' move_id.line_id.reconcile_id ' ,
2014-07-06 14:44:26 +00:00
' move_id.line_id.amount_residual_currency ' ,
' move_id.line_id.currency_id ' ,
' move_id.line_id.reconcile_partial_id.line_partial_ids.invoice.type ' ,
)
2014-11-03 13:06:52 +00:00
# An invoice's residual amount is the sum of its unreconciled move lines and,
# for partially reconciled move lines, their residual amount divided by the
# number of times this reconciliation is used in an invoice (so we split
# the residual amount between all invoice)
2014-07-06 14:44:26 +00:00
def _compute_residual ( self ) :
self . residual = 0.0
2014-11-03 13:06:52 +00:00
# Each partial reconciliation is considered only once for each invoice it appears into,
# and its residual amount is divided by this number of invoices
partial_reconciliations_done = [ ]
2014-08-13 18:46:47 +00:00
for line in self . sudo ( ) . move_id . line_id :
2014-11-03 13:06:52 +00:00
if line . account_id . type not in ( ' receivable ' , ' payable ' ) :
continue
if line . reconcile_partial_id and line . reconcile_partial_id . id in partial_reconciliations_done :
continue
# Get the correct line residual amount
if line . currency_id == self . currency_id :
2015-05-05 16:01:30 +00:00
line_amount = line . amount_residual_currency if line . currency_id else line . amount_residual
2014-11-03 13:06:52 +00:00
else :
from_currency = line . company_id . currency_id . with_context ( date = line . date )
line_amount = from_currency . compute ( line . amount_residual , self . currency_id )
# For partially reconciled lines, split the residual amount
if line . reconcile_partial_id :
partial_reconciliation_invoices = set ( )
2014-07-06 14:44:26 +00:00
for pline in line . reconcile_partial_id . line_partial_ids :
if pline . invoice and self . type == pline . invoice . type :
2014-11-03 13:06:52 +00:00
partial_reconciliation_invoices . update ( [ pline . invoice . id ] )
line_amount = self . currency_id . round ( line_amount / len ( partial_reconciliation_invoices ) )
partial_reconciliations_done . append ( line . reconcile_partial_id . id )
self . residual + = line_amount
2014-07-09 21:22:21 +00:00
self . residual = max ( self . residual , 0.0 )
2014-07-06 14:44:26 +00:00
@api.one
@api.depends (
' move_id.line_id.account_id ' ,
' move_id.line_id.reconcile_id.line_id ' ,
' move_id.line_id.reconcile_partial_id.line_partial_ids ' ,
)
def _compute_move_lines ( self ) :
# Give Journal Items related to the payment reconciled to this invoice.
# Return partial and total payments related to the selected invoice.
self . move_lines = self . env [ ' account.move.line ' ]
if not self . move_id :
return
data_lines = self . move_id . line_id . filtered ( lambda l : l . account_id == self . account_id )
partial_lines = self . env [ ' account.move.line ' ]
for data_line in data_lines :
if data_line . reconcile_id :
lines = data_line . reconcile_id . line_id
elif data_line . reconcile_partial_id :
lines = data_line . reconcile_partial_id . line_partial_ids
else :
2014-09-23 12:26:47 +00:00
lines = self . env [ ' account.move.line ' ]
2014-07-06 14:44:26 +00:00
partial_lines + = data_line
self . move_lines = lines - partial_lines
@api.one
@api.depends (
' move_id.line_id.reconcile_id.line_id ' ,
' move_id.line_id.reconcile_partial_id.line_partial_ids ' ,
)
def _compute_payments ( self ) :
partial_lines = lines = self . env [ ' account.move.line ' ]
for line in self . move_id . line_id :
2014-08-11 13:52:15 +00:00
if line . account_id != self . account_id :
continue
2014-07-06 14:44:26 +00:00
if line . reconcile_id :
lines | = line . reconcile_id . line_id
elif line . reconcile_partial_id :
lines | = line . reconcile_partial_id . line_partial_ids
partial_lines + = line
self . payment_ids = ( lines - partial_lines ) . sorted ( )
name = fields . Char ( string = ' Reference/Description ' , index = True ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
origin = fields . Char ( string = ' Source Document ' ,
help = " Reference of the document that produced this invoice. " ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
supplier_invoice_number = fields . Char ( string = ' Supplier Invoice Number ' ,
help = " The reference of this invoice as provided by the supplier. " ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
type = fields . Selection ( [
2008-07-22 15:11:28 +00:00
( ' out_invoice ' , ' Customer Invoice ' ) ,
( ' in_invoice ' , ' Supplier Invoice ' ) ,
( ' out_refund ' , ' Customer Refund ' ) ,
( ' in_refund ' , ' Supplier Refund ' ) ,
2014-07-06 14:44:26 +00:00
] , string = ' Type ' , readonly = True , index = True , change_default = True ,
default = lambda self : self . _context . get ( ' type ' , ' out_invoice ' ) ,
track_visibility = ' always ' )
number = fields . Char ( related = ' move_id.name ' , store = True , readonly = True , copy = False )
internal_number = fields . Char ( string = ' Invoice Number ' , readonly = True ,
default = False , copy = False ,
help = " Unique number of the invoice, computed automatically when the invoice is created. " )
reference = fields . Char ( string = ' Invoice Reference ' ,
help = " The partner reference of this invoice. " )
reference_type = fields . Selection ( ' _get_reference_type ' , string = ' Payment Reference ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = ' none ' )
comment = fields . Text ( ' Additional Information ' )
state = fields . Selection ( [
2008-07-22 15:11:28 +00:00
( ' draft ' , ' Draft ' ) ,
( ' proforma ' , ' Pro-forma ' ) ,
2008-10-07 10:02:21 +00:00
( ' proforma2 ' , ' Pro-forma ' ) ,
2008-07-22 15:11:28 +00:00
( ' open ' , ' Open ' ) ,
2012-05-22 11:19:07 +00:00
( ' paid ' , ' Paid ' ) ,
2012-05-22 11:06:00 +00:00
( ' cancel ' , ' Cancelled ' ) ,
2014-07-06 14:44:26 +00:00
] , string = ' Status ' , index = True , readonly = True , default = ' draft ' ,
track_visibility = ' onchange ' , copy = False ,
help = " * The ' Draft ' status is used when a user is encoding a new and unconfirmed Invoice. \n "
" * The ' Pro-forma ' when invoice is in Pro-forma status,invoice does not have an invoice number. \n "
" * The ' Open ' status is used when user create invoice,a invoice number is generated.Its in open status till user does not pay invoice. \n "
" * The ' Paid ' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \n "
" * The ' Cancelled ' status is used when user cancel invoice. " )
sent = fields . Boolean ( readonly = True , default = False , copy = False ,
help = " It indicates that the invoice has been sent. " )
date_invoice = fields . Date ( string = ' Invoice Date ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , index = True ,
help = " Keep empty to use the current date " , copy = False )
date_due = fields . Date ( string = ' Due Date ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , index = True , copy = False ,
help = " If you use payment terms, the due date will be computed automatically at the generation "
" of accounting entries. The payment term may compute several due dates, for example 50 % "
" now and 50 % i n one month, but if you want to force a due date, make sure that the payment "
" term is not set on the invoice. If you keep the payment term and the due date empty, it "
" means direct payment. " )
partner_id = fields . Many2one ( ' res.partner ' , string = ' Partner ' , change_default = True ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
track_visibility = ' always ' )
payment_term = fields . Many2one ( ' account.payment.term ' , string = ' Payment Terms ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " If you use payment terms, the due date will be computed automatically at the generation "
" of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "
" The payment term may compute several due dates, for example 50 % now, 50 % i n one month. " )
period_id = fields . Many2one ( ' account.period ' , string = ' Force Period ' ,
domain = [ ( ' state ' , ' != ' , ' done ' ) ] , copy = False ,
help = " Keep empty to use the period of the validation(invoice) date. " ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
account_id = fields . Many2one ( ' account.account ' , string = ' Account ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
help = " The partner account used for this invoice. " )
invoice_line = fields . One2many ( ' account.invoice.line ' , ' invoice_id ' , string = ' Invoice Lines ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , copy = True )
tax_line = fields . One2many ( ' account.invoice.tax ' , ' invoice_id ' , string = ' Tax Lines ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , copy = True )
move_id = fields . Many2one ( ' account.move ' , string = ' Journal Entry ' ,
readonly = True , index = True , ondelete = ' restrict ' , copy = False ,
help = " Link to the automatically generated Journal Items. " )
amount_untaxed = fields . Float ( string = ' Subtotal ' , digits = dp . get_precision ( ' Account ' ) ,
store = True , readonly = True , compute = ' _compute_amount ' , track_visibility = ' always ' )
amount_tax = fields . Float ( string = ' Tax ' , digits = dp . get_precision ( ' Account ' ) ,
store = True , readonly = True , compute = ' _compute_amount ' )
amount_total = fields . Float ( string = ' Total ' , digits = dp . get_precision ( ' Account ' ) ,
store = True , readonly = True , compute = ' _compute_amount ' )
currency_id = fields . Many2one ( ' res.currency ' , string = ' Currency ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = _default_currency , track_visibility = ' always ' )
journal_id = fields . Many2one ( ' account.journal ' , string = ' Journal ' ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = _default_journal ,
domain = " [( ' type ' , ' in ' , { ' out_invoice ' : [ ' sale ' ], ' out_refund ' : [ ' sale_refund ' ], ' in_refund ' : [ ' purchase_refund ' ], ' in_invoice ' : [ ' purchase ' ]}.get(type, [])), ( ' company_id ' , ' = ' , company_id)] " )
company_id = fields . Many2one ( ' res.company ' , string = ' Company ' , change_default = True ,
required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = lambda self : self . env [ ' res.company ' ] . _company_default_get ( ' account.invoice ' ) )
check_total = fields . Float ( string = ' Verification Total ' , digits = dp . get_precision ( ' Account ' ) ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , default = 0.0 )
reconciled = fields . Boolean ( string = ' Paid/Reconciled ' ,
store = True , readonly = True , compute = ' _compute_reconciled ' ,
help = " It indicates that the invoice has been paid and the journal entry of the invoice has been reconciled with one or several journal entries of payment. " )
partner_bank_id = fields . Many2one ( ' res.partner.bank ' , string = ' Bank Account ' ,
help = ' Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Supplier Refund, otherwise a Partner bank account number. ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
move_lines = fields . Many2many ( ' account.move.line ' , string = ' Entry Lines ' ,
compute = ' _compute_move_lines ' )
residual = fields . Float ( string = ' Balance ' , digits = dp . get_precision ( ' Account ' ) ,
compute = ' _compute_residual ' , store = True ,
help = " Remaining amount due. " )
payment_ids = fields . Many2many ( ' account.move.line ' , string = ' Payments ' ,
compute = ' _compute_payments ' )
move_name = fields . Char ( string = ' Journal Entry ' , readonly = True ,
states = { ' draft ' : [ ( ' readonly ' , False ) ] } , copy = False )
user_id = fields . Many2one ( ' res.users ' , string = ' Salesperson ' , track_visibility = ' onchange ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ,
default = lambda self : self . env . user )
fiscal_position = fields . Many2one ( ' account.fiscal.position ' , string = ' Fiscal Position ' ,
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } )
commercial_partner_id = fields . Many2one ( ' res.partner ' , string = ' Commercial Entity ' ,
related = ' partner_id.commercial_partner_id ' , store = True , readonly = True ,
help = " The commercial entity that will be used on Journal Entries for this invoice " )
2011-11-09 06:30:48 +00:00
_sql_constraints = [
2014-07-06 14:44:26 +00:00
( ' number_uniq ' , ' unique(number, company_id, journal_id, type) ' ,
' Invoice Number must be unique per Company! ' ) ,
2011-11-09 06:30:48 +00:00
]
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.model
def fields_view_get ( self , view_id = None , view_type = False , toolbar = False , submenu = False ) :
context = self . _context
2013-03-05 16:20:15 +00:00
2014-10-29 18:33:02 +00:00
def get_view_id ( xid , name ) :
try :
return self . env [ ' ir.model.data ' ] . xmlid_to_res_id ( ' account. ' + xid , raise_if_not_found = True )
except ValueError :
try :
return self . env [ ' ir.ui.view ' ] . search ( [ ( ' name ' , ' = ' , name ) ] , limit = 1 ) . id
except Exception :
return False # view not found
2012-11-30 16:35:28 +00:00
2014-07-06 14:44:26 +00:00
if context . get ( ' active_model ' ) == ' res.partner ' and context . get ( ' active_ids ' ) :
partner = self . env [ ' res.partner ' ] . browse ( context [ ' active_ids ' ] ) [ 0 ]
2010-06-23 10:31:14 +00:00
if not view_type :
2014-10-29 18:33:02 +00:00
view_id = get_view_id ( ' invoice_tree ' , ' account.invoice.tree ' )
2010-06-23 10:31:14 +00:00
view_type = ' tree '
2014-07-06 14:44:26 +00:00
elif view_type == ' form ' :
if partner . supplier and not partner . customer :
2014-10-29 18:33:02 +00:00
view_id = get_view_id ( ' invoice_supplier_form ' , ' account.invoice.supplier.form ' )
2014-07-06 14:44:26 +00:00
elif partner . customer and not partner . supplier :
2014-10-29 18:33:02 +00:00
view_id = get_view_id ( ' invoice_form ' , ' account.invoice.form ' )
2014-07-06 14:44:26 +00:00
res = super ( account_invoice , self ) . fields_view_get ( view_id = view_id , view_type = view_type , toolbar = toolbar , submenu = submenu )
# adapt selection of field journal_id
2010-09-21 13:06:42 +00:00
for field in res [ ' fields ' ] :
2015-05-29 15:30:36 +00:00
if field == ' journal_id ' and context . get ( ' journal_type ' ) :
journal_select = self . env [ ' account.journal ' ] . _name_search ( ' ' , [ ( ' type ' , ' = ' , context [ ' journal_type ' ] ) ] , name_get_uid = 1 )
2010-09-21 13:06:42 +00:00
res [ ' fields ' ] [ field ] [ ' selection ' ] = journal_select
2010-10-08 11:28:58 +00:00
2011-04-29 05:01:18 +00:00
doc = etree . XML ( res [ ' arch ' ] )
2012-08-06 15:44:10 +00:00
2014-07-06 14:44:26 +00:00
if context . get ( ' type ' ) :
2012-01-18 10:14:28 +00:00
for node in doc . xpath ( " //field[@name= ' partner_bank_id ' ] " ) :
if context [ ' type ' ] == ' in_refund ' :
node . set ( ' domain ' , " [( ' partner_id.ref_companies ' , ' in ' , [company_id])] " )
2012-01-19 13:21:53 +00:00
elif context [ ' type ' ] == ' out_refund ' :
2012-01-19 05:38:02 +00:00
node . set ( ' domain ' , " [( ' partner_id ' , ' = ' , partner_id)] " )
2012-08-06 15:44:10 +00:00
2011-04-29 05:01:18 +00:00
if view_type == ' search ' :
2014-07-06 14:44:26 +00:00
if context . get ( ' type ' ) in ( ' out_invoice ' , ' out_refund ' ) :
2011-04-29 05:01:18 +00:00
for node in doc . xpath ( " //group[@name= ' extended filter ' ] " ) :
doc . remove ( node )
2010-10-08 11:28:58 +00:00
if view_type == ' tree ' :
2010-10-17 17:30:00 +00:00
partner_string = _ ( ' Customer ' )
2014-07-06 14:44:26 +00:00
if context . get ( ' type ' ) in ( ' in_invoice ' , ' in_refund ' ) :
2010-10-09 16:01:43 +00:00
partner_string = _ ( ' Supplier ' )
2011-04-29 05:01:18 +00:00
for node in doc . xpath ( " //field[@name= ' reference ' ] " ) :
2011-04-29 08:53:55 +00:00
node . set ( ' invisible ' , ' 0 ' )
2011-04-29 05:01:18 +00:00
for node in doc . xpath ( " //field[@name= ' partner_id ' ] " ) :
2010-10-08 11:28:58 +00:00
node . set ( ' string ' , partner_string )
2010-06-23 10:31:14 +00:00
2014-07-06 14:44:26 +00:00
res [ ' arch ' ] = etree . tostring ( doc )
return res
2012-02-29 10:10:45 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def invoice_print ( self ) :
""" Print the invoice and mark it as sent, so that we can see more
easily the next step of the workflow
"""
assert len ( self ) == 1 , ' This option should only be used for a single id at a time. '
self . sent = True
return self . env [ ' report ' ] . get_action ( self , ' account.report_invoice ' )
@api.multi
def action_invoice_sent ( self ) :
""" Open a window to compose an email, with the edi invoice template
message loaded by default
"""
assert len ( self ) == 1 , ' This option should only be used for a single id at a time. '
template = self . env . ref ( ' account.email_template_edi_invoice ' , False )
compose_form = self . env . ref ( ' mail.email_compose_message_wizard_form ' , False )
2014-08-01 12:24:07 +00:00
ctx = dict (
2014-07-06 14:44:26 +00:00
default_model = ' account.invoice ' ,
default_res_id = self . id ,
default_use_template = bool ( template ) ,
default_template_id = template . id ,
default_composition_mode = ' comment ' ,
mark_invoice_as_sent = True ,
)
2012-02-29 06:47:05 +00:00
return {
2013-03-21 05:38:55 +00:00
' name ' : _ ( ' Compose Email ' ) ,
2012-10-23 11:56:28 +00:00
' type ' : ' ir.actions.act_window ' ,
2012-09-11 14:16:50 +00:00
' view_type ' : ' form ' ,
' view_mode ' : ' form ' ,
' res_model ' : ' mail.compose.message ' ,
2014-07-06 14:44:26 +00:00
' views ' : [ ( compose_form . id , ' form ' ) ] ,
' view_id ' : compose_form . id ,
2012-09-11 14:16:50 +00:00
' target ' : ' new ' ,
' context ' : ctx ,
2012-02-29 06:47:05 +00:00
}
2010-05-17 13:34:31 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def confirm_paid ( self ) :
return self . write ( { ' state ' : ' paid ' } )
@api.multi
def unlink ( self ) :
for invoice in self :
if invoice . state not in ( ' draft ' , ' cancel ' ) :
raise Warning ( _ ( ' You cannot delete an invoice which is not draft or cancelled. You should refund it instead. ' ) )
elif invoice . internal_number :
raise Warning ( _ ( ' You cannot delete an invoice after it has been validated (and received a number). You can set it back to " Draft " state and modify its content, then re-confirm it. ' ) )
return super ( account_invoice , self ) . unlink ( )
@api.multi
def onchange_partner_id ( self , type , partner_id , date_invoice = False ,
payment_term = False , partner_bank_id = False , company_id = False ) :
account_id = False
payment_term_id = False
2009-01-19 13:59:11 +00:00
fiscal_position = False
2014-07-06 14:44:26 +00:00
bank_id = False
2008-07-22 15:11:28 +00:00
if partner_id :
2014-07-06 14:44:26 +00:00
p = self . env [ ' res.partner ' ] . browse ( partner_id )
rec_account = p . property_account_receivable
pay_account = p . property_account_payable
2009-11-10 12:49:20 +00:00
if company_id :
2014-07-06 14:44:26 +00:00
if p . property_account_receivable . company_id and \
p . property_account_receivable . company_id . id != company_id and \
p . property_account_payable . company_id and \
p . property_account_payable . company_id . id != company_id :
prop = self . env [ ' ir.property ' ]
rec_dom = [ ( ' name ' , ' = ' , ' property_account_receivable ' ) , ( ' company_id ' , ' = ' , company_id ) ]
pay_dom = [ ( ' name ' , ' = ' , ' property_account_payable ' ) , ( ' company_id ' , ' = ' , company_id ) ]
res_dom = [ ( ' res_id ' , ' = ' , ' res.partner, %s ' % partner_id ) ]
rec_prop = prop . search ( rec_dom + res_dom ) or prop . search ( rec_dom )
pay_prop = prop . search ( pay_dom + res_dom ) or prop . search ( pay_dom )
rec_account = rec_prop . get_by_record ( rec_prop )
pay_account = pay_prop . get_by_record ( pay_prop )
if not rec_account and not pay_account :
action = self . env . ref ( ' account.action_account_config ' )
2014-06-25 06:09:36 +00:00
msg = _ ( ' Cannot find a chart of accounts for this company, You should configure it. \n Please go to Account Configuration. ' )
2014-07-06 14:44:26 +00:00
raise RedirectWarning ( msg , action . id , _ ( ' Go to the configuration panel ' ) )
2009-11-10 12:49:20 +00:00
2008-07-22 15:11:28 +00:00
if type in ( ' out_invoice ' , ' out_refund ' ) :
2014-07-06 14:44:26 +00:00
account_id = rec_account . id
payment_term_id = p . property_payment_term . id
2008-07-22 15:11:28 +00:00
else :
2014-07-06 14:44:26 +00:00
account_id = pay_account . id
payment_term_id = p . property_supplier_payment_term . id
fiscal_position = p . property_account_position . id
2014-09-16 11:36:36 +00:00
bank_id = p . bank_ids and p . bank_ids [ 0 ] . id or False
2008-07-22 15:11:28 +00:00
result = { ' value ' : {
2014-07-06 14:44:26 +00:00
' account_id ' : account_id ,
' payment_term ' : payment_term_id ,
' fiscal_position ' : fiscal_position ,
} }
2008-07-22 15:11:28 +00:00
if type in ( ' in_invoice ' , ' in_refund ' ) :
2010-07-19 13:47:09 +00:00
result [ ' value ' ] [ ' partner_bank_id ' ] = bank_id
2009-12-24 08:43:23 +00:00
2014-07-06 14:44:26 +00:00
if payment_term != payment_term_id :
if payment_term_id :
to_update = self . onchange_payment_term_date_invoice ( payment_term_id , date_invoice )
result [ ' value ' ] . update ( to_update . get ( ' value ' , { } ) )
2009-11-10 12:49:20 +00:00
else :
result [ ' value ' ] [ ' date_due ' ] = False
2008-07-22 15:11:28 +00:00
2010-07-20 04:20:34 +00:00
if partner_bank_id != bank_id :
2014-07-06 14:44:26 +00:00
to_update = self . onchange_partner_bank ( bank_id )
result [ ' value ' ] . update ( to_update . get ( ' value ' , { } ) )
2008-07-22 15:11:28 +00:00
return result
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_journal_id ( self , journal_id = False ) :
2010-09-17 06:51:33 +00:00
if journal_id :
2014-07-06 14:44:26 +00:00
journal = self . env [ ' account.journal ' ] . browse ( journal_id )
return {
' value ' : {
' currency_id ' : journal . currency . id or journal . company_id . currency_id . id ,
' company_id ' : journal . company_id . id ,
2010-09-17 06:10:44 +00:00
}
2014-07-06 14:44:26 +00:00
}
return { }
2010-09-17 06:10:44 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_payment_term_date_invoice ( self , payment_term_id , date_invoice ) :
2010-08-30 06:28:31 +00:00
if not date_invoice :
2014-08-28 14:42:17 +00:00
date_invoice = fields . Date . context_today ( self )
2012-11-15 08:16:34 +00:00
if not payment_term_id :
2014-07-06 14:44:26 +00:00
# To make sure the invoice due date should contain due date which is
# entered by user when there is no payment term defined
return { ' value ' : { ' date_due ' : self . date_due or date_invoice } }
pterm = self . env [ ' account.payment.term ' ] . browse ( payment_term_id )
pterm_list = pterm . compute ( value = 1 , date_ref = date_invoice ) [ 0 ]
2008-07-22 15:11:28 +00:00
if pterm_list :
2014-07-06 14:44:26 +00:00
return { ' value ' : { ' date_due ' : max ( line [ 0 ] for line in pterm_list ) } }
2009-03-17 13:28:02 +00:00
else :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' Insufficient Data! ' ) ,
_ ( ' The payment term of supplier does not have a payment term line. ' ) )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_invoice_line ( self , lines ) :
2008-07-22 15:11:28 +00:00
return { }
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_partner_bank ( self , partner_bank_id = False ) :
2008-07-22 15:11:28 +00:00
return { ' value ' : { } }
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_company_id ( self , company_id , part_id , type , invoice_line , currency_id ) :
# TODO: add the missing context parameter when forward-porting in trunk
# so we can remove this hack!
self = self . with_context ( self . env [ ' res.users ' ] . context_get ( ) )
2013-06-07 17:59:49 +00:00
2014-07-06 14:44:26 +00:00
values = { }
domain = { }
2013-02-14 17:31:56 +00:00
2009-11-10 12:49:20 +00:00
if company_id and part_id and type :
2014-07-06 14:44:26 +00:00
p = self . env [ ' res.partner ' ] . browse ( part_id )
if p . property_account_payable and p . property_account_receivable and \
p . property_account_payable . company_id . id != company_id and \
p . property_account_receivable . company_id . id != company_id :
prop = self . env [ ' ir.property ' ]
rec_dom = [ ( ' name ' , ' = ' , ' property_account_receivable ' ) , ( ' company_id ' , ' = ' , company_id ) ]
pay_dom = [ ( ' name ' , ' = ' , ' property_account_payable ' ) , ( ' company_id ' , ' = ' , company_id ) ]
res_dom = [ ( ' res_id ' , ' = ' , ' res.partner, %s ' % part_id ) ]
rec_prop = prop . search ( rec_dom + res_dom ) or prop . search ( rec_dom )
pay_prop = prop . search ( pay_dom + res_dom ) or prop . search ( pay_dom )
rec_account = rec_prop . get_by_record ( rec_prop )
pay_account = pay_prop . get_by_record ( pay_prop )
if not rec_account and not pay_account :
action = self . env . ref ( ' account.action_account_config ' )
msg = _ ( ' Cannot find a chart of accounts for this company, You should configure it. \n Please go to Account Configuration. ' )
raise RedirectWarning ( msg , action . id , _ ( ' Go to the configuration panel ' ) )
if type in ( ' out_invoice ' , ' out_refund ' ) :
acc_id = rec_account . id
else :
acc_id = pay_account . id
values = { ' account_id ' : acc_id }
2013-02-14 17:31:56 +00:00
2014-07-06 14:44:26 +00:00
if self :
2009-11-10 12:49:20 +00:00
if company_id :
2014-07-06 14:44:26 +00:00
for line in self . invoice_line :
if not line . account_id :
2009-11-10 12:49:20 +00:00
continue
2014-07-06 14:44:26 +00:00
if line . account_id . company_id . id == company_id :
continue
accounts = self . env [ ' account.account ' ] . search ( [ ( ' name ' , ' = ' , line . account_id . name ) , ( ' company_id ' , ' = ' , company_id ) ] )
if not accounts :
action = self . env . ref ( ' account.action_account_config ' )
msg = _ ( ' Cannot find a chart of accounts for this company, You should configure it. \n Please go to Account Configuration. ' )
raise RedirectWarning ( msg , action . id , _ ( ' Go to the configuration panel ' ) )
line . write ( { ' account_id ' : accounts [ - 1 ] . id } )
else :
for line_cmd in invoice_line or [ ] :
if len ( line_cmd ) > = 3 and isinstance ( line_cmd [ 2 ] , dict ) :
line = self . env [ ' account.account ' ] . browse ( line_cmd [ 2 ] [ ' account_id ' ] )
if line . company_id . id != company_id :
raise except_orm (
_ ( ' Configuration Error! ' ) ,
_ ( " Invoice line account ' s company and invoice ' s company does not match. " )
)
2009-11-10 12:49:20 +00:00
2014-07-06 14:44:26 +00:00
if company_id and type :
journal_type = TYPE2JOURNAL [ type ]
journals = self . env [ ' account.journal ' ] . search ( [ ( ' type ' , ' = ' , journal_type ) , ( ' company_id ' , ' = ' , company_id ) ] )
if journals :
values [ ' journal_id ' ] = journals [ 0 ] . id
journal_defaults = self . env [ ' ir.values ' ] . get_defaults_dict ( ' account.invoice ' , ' type= %s ' % type )
if ' journal_id ' in journal_defaults :
values [ ' journal_id ' ] = journal_defaults [ ' journal_id ' ]
if not values . get ( ' journal_id ' ) :
2014-07-28 10:40:46 +00:00
field_desc = journals . fields_get ( [ ' type ' ] )
type_label = next ( t for t , label in field_desc [ ' type ' ] [ ' selection ' ] if t == journal_type )
2014-07-06 14:44:26 +00:00
action = self . env . ref ( ' account.action_account_journal_form ' )
msg = _ ( ' Cannot find any account journal of type " %s " for this company, You should create one. \n Please go to Journal Configuration ' ) % type_label
raise RedirectWarning ( msg , action . id , _ ( ' Go to the configuration panel ' ) )
domain = { ' journal_id ' : [ ( ' id ' , ' in ' , journals . ids ) ] }
return { ' value ' : values , ' domain ' : domain }
@api.multi
def action_cancel_draft ( self ) :
# go from canceled state to draft state
self . write ( { ' state ' : ' draft ' } )
self . delete_workflow ( )
self . create_workflow ( )
2008-07-22 15:11:28 +00:00
return True
2014-07-06 14:44:26 +00:00
@api.one
@api.returns ( ' ir.ui.view ' )
def get_formview_id ( self ) :
2013-04-26 14:40:19 +00:00
""" Update form view id of action to open the invoice """
2014-07-06 14:44:26 +00:00
if self . type == ' in_invoice ' :
return self . env . ref ( ' account.invoice_supplier_form ' )
2013-04-26 14:40:19 +00:00
else :
2014-07-06 14:44:26 +00:00
return self . env . ref ( ' account.invoice_form ' )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def move_line_id_payment_get ( self ) :
# return the move line ids with the same account as the invoice self
if not self . id :
return [ ]
query = """ SELECT l.id
FROM account_move_line l , account_invoice i
WHERE i . id = % s AND l . move_id = i . move_id AND l . account_id = i . account_id
"""
self . _cr . execute ( query , ( self . id , ) )
return [ row [ 0 ] for row in self . _cr . fetchall ( ) ]
@api.multi
def test_paid ( self ) :
# check whether all corresponding account move lines are reconciled
line_ids = self . move_line_id_payment_get ( )
if not line_ids :
2008-07-22 15:11:28 +00:00
return False
2016-05-12 09:41:13 +00:00
query = " SELECT count(*) FROM account_move_line WHERE reconcile_id IS NULL AND id IN %s "
2014-07-06 14:44:26 +00:00
self . _cr . execute ( query , ( tuple ( line_ids ) , ) )
2016-05-12 09:41:13 +00:00
return self . _cr . fetchone ( ) [ 0 ] == 0
2014-07-06 14:44:26 +00:00
@api.multi
def button_reset_taxes ( self ) :
account_invoice_tax = self . env [ ' account.invoice.tax ' ]
ctx = dict ( self . _context )
for invoice in self :
self . _cr . execute ( " DELETE FROM account_invoice_tax WHERE invoice_id= %s AND manual is False " , ( invoice . id , ) )
self . invalidate_cache ( )
partner = invoice . partner_id
2009-09-11 15:31:07 +00:00
if partner . lang :
2014-07-06 14:44:26 +00:00
ctx [ ' lang ' ] = partner . lang
2015-02-16 09:20:51 +00:00
for taxe in account_invoice_tax . compute ( invoice . with_context ( ctx ) ) . values ( ) :
2014-07-06 14:44:26 +00:00
account_invoice_tax . create ( taxe )
# dummy write on self to trigger recomputations
return self . with_context ( ctx ) . write ( { ' invoice_line ' : [ ] } )
@api.multi
def button_compute ( self , set_total = False ) :
self . button_reset_taxes ( )
for invoice in self :
2008-07-22 15:11:28 +00:00
if set_total :
2014-07-06 14:44:26 +00:00
invoice . check_total = invoice . amount_total
2008-07-22 15:11:28 +00:00
return True
2014-08-21 19:22:12 +00:00
@api.multi
2014-07-06 14:44:26 +00:00
def _get_analytic_lines ( self ) :
""" Return a list of dict for creating analytic lines for self[0] """
company_currency = self . company_id . currency_id
sign = 1 if self . type in ( ' out_invoice ' , ' in_refund ' ) else - 1
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
iml = self . env [ ' account.invoice.line ' ] . move_line_get ( self . id )
2008-07-22 15:11:28 +00:00
for il in iml :
if il [ ' account_analytic_id ' ] :
2014-07-06 14:44:26 +00:00
if self . type in ( ' in_invoice ' , ' in_refund ' ) :
ref = self . reference
2008-07-22 15:11:28 +00:00
else :
2014-09-04 09:32:16 +00:00
ref = self . number
2014-07-06 14:44:26 +00:00
if not self . journal_id . analytic_journal_id :
raise except_orm ( _ ( ' No Analytic Journal! ' ) ,
_ ( " You have to define an analytic journal on the ' %s ' journal! " ) % ( self . journal_id . name , ) )
currency = self . currency_id . with_context ( date = self . date_invoice )
2008-07-22 15:11:28 +00:00
il [ ' analytic_lines ' ] = [ ( 0 , 0 , {
' name ' : il [ ' name ' ] ,
2014-07-06 14:44:26 +00:00
' date ' : self . date_invoice ,
2008-07-22 15:11:28 +00:00
' account_id ' : il [ ' account_analytic_id ' ] ,
' unit_amount ' : il [ ' quantity ' ] ,
2014-07-06 14:44:26 +00:00
' amount ' : currency . compute ( il [ ' price ' ] , company_currency ) * sign ,
2008-07-22 15:11:28 +00:00
' product_id ' : il [ ' product_id ' ] ,
' product_uom_id ' : il [ ' uos_id ' ] ,
' general_account_id ' : il [ ' account_id ' ] ,
2014-07-06 14:44:26 +00:00
' journal_id ' : self . journal_id . analytic_journal_id . id ,
2008-07-22 15:11:28 +00:00
' ref ' : ref ,
} ) ]
return iml
2014-07-06 14:44:26 +00:00
@api.multi
def action_date_assign ( self ) :
for inv in self :
res = inv . onchange_payment_term_date_invoice ( inv . payment_term . id , inv . date_invoice )
if res and res . get ( ' value ' ) :
inv . write ( res [ ' value ' ] )
2009-03-15 16:29:55 +00:00
return True
2010-05-17 13:34:31 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def finalize_invoice_move_lines ( self , move_lines ) :
""" finalize_invoice_move_lines(move_lines) -> move_lines
Hook method to be overridden in additional modules to verify and
possibly alter the move lines to be created by an invoice , for
special cases .
: param move_lines : list of dictionaries with the account . move . lines ( as for create ( ) )
: return : the ( possibly updated ) final move_lines to create for this invoice
2010-05-10 12:27:32 +00:00
"""
return move_lines
2010-02-24 08:47:01 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def check_tax_lines ( self , compute_taxes ) :
account_invoice_tax = self . env [ ' account.invoice.tax ' ]
company_currency = self . company_id . currency_id
if not self . tax_line :
2010-02-22 05:35:21 +00:00
for tax in compute_taxes . values ( ) :
2014-07-06 14:44:26 +00:00
account_invoice_tax . create ( tax )
2010-02-22 05:35:21 +00:00
else :
tax_key = [ ]
2014-10-14 13:13:14 +00:00
precision = self . env [ ' decimal.precision ' ] . precision_get ( ' Account ' )
2014-07-06 14:44:26 +00:00
for tax in self . tax_line :
2010-02-22 05:35:21 +00:00
if tax . manual :
continue
2014-06-06 14:51:09 +00:00
key = ( tax . tax_code_id . id , tax . base_code_id . id , tax . account_id . id )
2010-02-22 05:35:21 +00:00
tax_key . append ( key )
2014-07-06 14:44:26 +00:00
if key not in compute_taxes :
raise except_orm ( _ ( ' Warning! ' ) , _ ( ' Global taxes defined, but they are not in invoice lines ! ' ) )
2010-02-22 05:35:21 +00:00
base = compute_taxes [ key ] [ ' base ' ]
2014-10-07 15:25:56 +00:00
if float_compare ( abs ( base - tax . base ) , company_currency . rounding , precision_digits = precision ) == 1 :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' Warning! ' ) , _ ( ' Tax base different! \n Click on compute to update the tax base. ' ) )
2010-02-22 05:35:21 +00:00
for key in compute_taxes :
2014-07-06 14:44:26 +00:00
if key not in tax_key :
raise except_orm ( _ ( ' Warning! ' ) , _ ( ' Taxes are missing! \n Click on compute button. ' ) )
2010-02-22 05:35:21 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def compute_invoice_totals ( self , company_currency , ref , invoice_move_lines ) :
2010-02-22 05:35:21 +00:00
total = 0
total_currency = 0
2014-07-06 14:44:26 +00:00
for line in invoice_move_lines :
if self . currency_id != company_currency :
2014-08-28 14:42:17 +00:00
currency = self . currency_id . with_context ( date = self . date_invoice or fields . Date . context_today ( self ) )
2014-07-06 14:44:26 +00:00
line [ ' currency_id ' ] = currency . id
2015-08-24 16:16:05 +00:00
line [ ' amount_currency ' ] = currency . round ( line [ ' price ' ] )
2014-07-06 14:44:26 +00:00
line [ ' price ' ] = currency . compute ( line [ ' price ' ] , company_currency )
2010-02-22 05:35:21 +00:00
else :
2014-07-06 14:44:26 +00:00
line [ ' currency_id ' ] = False
line [ ' amount_currency ' ] = False
2015-08-19 16:20:21 +00:00
line [ ' price ' ] = self . currency_id . round ( line [ ' price ' ] )
2014-07-06 14:44:26 +00:00
line [ ' ref ' ] = ref
if self . type in ( ' out_invoice ' , ' in_refund ' ) :
total + = line [ ' price ' ]
total_currency + = line [ ' amount_currency ' ] or line [ ' price ' ]
line [ ' price ' ] = - line [ ' price ' ]
2010-02-22 05:35:21 +00:00
else :
2014-07-06 14:44:26 +00:00
total - = line [ ' price ' ]
total_currency - = line [ ' amount_currency ' ] or line [ ' price ' ]
2010-02-22 05:35:21 +00:00
return total , total_currency , invoice_move_lines
2010-02-24 08:47:01 +00:00
2014-07-06 14:44:26 +00:00
def inv_line_characteristic_hashcode ( self , invoice_line ) :
2010-02-22 05:35:21 +00:00
""" Overridable hashcode generation for invoice lines. Lines having the same hashcode
will be grouped together if the journal has the ' group line ' option . Of course a module
can add fields to invoice lines that would need to be tested too before merging lines
or not . """
2014-07-06 14:44:26 +00:00
return " %s - %s - %s - %s - %s " % (
2010-02-24 08:48:52 +00:00
invoice_line [ ' account_id ' ] ,
2014-07-06 14:44:26 +00:00
invoice_line . get ( ' tax_code_id ' , ' False ' ) ,
invoice_line . get ( ' product_id ' , ' False ' ) ,
invoice_line . get ( ' analytic_account_id ' , ' False ' ) ,
invoice_line . get ( ' date_maturity ' , ' False ' ) ,
)
2010-02-24 08:47:01 +00:00
2014-07-06 14:44:26 +00:00
def group_lines ( self , iml , line ) :
2010-02-23 05:38:30 +00:00
""" Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals """
2014-07-06 14:44:26 +00:00
if self . journal_id . group_invoice_lines :
2010-02-22 05:35:21 +00:00
line2 = { }
for x , y , l in line :
2014-07-06 14:44:26 +00:00
tmp = self . inv_line_characteristic_hashcode ( l )
2010-02-22 05:35:21 +00:00
if tmp in line2 :
am = line2 [ tmp ] [ ' debit ' ] - line2 [ tmp ] [ ' credit ' ] + ( l [ ' debit ' ] - l [ ' credit ' ] )
line2 [ tmp ] [ ' debit ' ] = ( am > 0 ) and am or 0.0
line2 [ tmp ] [ ' credit ' ] = ( am < 0 ) and - am or 0.0
line2 [ tmp ] [ ' tax_amount ' ] + = l [ ' tax_amount ' ]
2016-01-08 17:08:49 +00:00
line2 [ tmp ] [ ' amount_currency ' ] + = l [ ' amount_currency ' ]
2010-02-22 05:35:21 +00:00
line2 [ tmp ] [ ' analytic_lines ' ] + = l [ ' analytic_lines ' ]
2016-06-17 08:27:53 +00:00
qty = l . get ( ' quantity ' )
if qty :
line2 [ tmp ] [ ' quantity ' ] = line2 [ tmp ] . get ( ' quantity ' , 0.0 ) + qty
2010-02-22 05:35:21 +00:00
else :
line2 [ tmp ] = l
line = [ ]
for key , val in line2 . items ( ) :
line . append ( ( 0 , 0 , val ) )
return line
2009-03-15 16:29:55 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def action_move_create ( self ) :
""" Creates invoice related analytics and financial move lines """
account_invoice_tax = self . env [ ' account.invoice.tax ' ]
account_move = self . env [ ' account.move ' ]
for inv in self :
2010-08-14 13:58:01 +00:00
if not inv . journal_id . sequence_id :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' Error! ' ) , _ ( ' Please define sequence on the journal related to this invoice. ' ) )
2010-06-18 07:18:07 +00:00
if not inv . invoice_line :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' No Invoice Lines! ' ) , _ ( ' Please create some invoice lines. ' ) )
2008-07-22 15:11:28 +00:00
if inv . move_id :
continue
2012-08-06 15:44:10 +00:00
2014-07-06 14:44:26 +00:00
ctx = dict ( self . _context , lang = inv . partner_id . lang )
2014-07-31 12:16:52 +00:00
if not inv . date_invoice :
inv . with_context ( ctx ) . write ( { ' date_invoice ' : fields . Date . context_today ( self ) } )
date_invoice = inv . date_invoice
2014-07-06 14:44:26 +00:00
company_currency = inv . company_id . currency_id
# create the analytical lines, one move line per invoice line
iml = inv . _get_analytic_lines ( )
2008-07-22 15:11:28 +00:00
# check if taxes are all computed
2015-04-11 07:38:28 +00:00
compute_taxes = account_invoice_tax . compute ( inv . with_context ( lang = inv . partner_id . lang ) )
2014-07-06 14:44:26 +00:00
inv . check_tax_lines ( compute_taxes )
2008-07-22 15:11:28 +00:00
2012-01-04 06:38:07 +00:00
# I disabled the check_total feature
2016-01-12 14:13:17 +00:00
if self . env . user . has_group ( ' account.group_supplier_inv_check_total ' ) :
2014-07-06 14:44:26 +00:00
if inv . type in ( ' in_invoice ' , ' in_refund ' ) and abs ( inv . check_total - inv . amount_total ) > = ( inv . currency_id . rounding / 2.0 ) :
raise except_orm ( _ ( ' Bad Total! ' ) , _ ( ' Please verify the price of the invoice! \n The encoded total does not match the computed total. ' ) )
2009-01-27 10:06:08 +00:00
2010-09-07 03:43:06 +00:00
if inv . payment_term :
total_fixed = total_percent = 0
for line in inv . payment_term . line_ids :
if line . value == ' fixed ' :
total_fixed + = line . value_amount
if line . value == ' procent ' :
total_percent + = line . value_amount
2010-10-20 13:58:03 +00:00
total_fixed = ( total_fixed * 100 ) / ( inv . amount_total or 1.0 )
2010-09-07 03:43:06 +00:00
if ( total_fixed + total_percent ) > 100 :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' Error! ' ) , _ ( " Cannot create the invoice. \n The related payment term is probably misconfigured as it gives a computed amount greater than the total invoiced amount. In order to avoid rounding issues, the latest line of your payment term must be of type ' balance ' . " ) )
2010-09-07 03:43:06 +00:00
2008-07-22 15:11:28 +00:00
# one move line per tax line
2014-07-06 14:44:26 +00:00
iml + = account_invoice_tax . move_line_get ( inv . id )
2010-07-02 05:16:21 +00:00
2008-07-22 15:11:28 +00:00
if inv . type in ( ' in_invoice ' , ' in_refund ' ) :
ref = inv . reference
else :
2014-09-04 09:32:16 +00:00
ref = inv . number
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
diff_currency = inv . currency_id != company_currency
2008-07-22 15:11:28 +00:00
# create one move line for the total and possibly adjust the other lines amount
2014-07-06 14:44:26 +00:00
total , total_currency , iml = inv . with_context ( ctx ) . compute_invoice_totals ( company_currency , ref , iml )
2008-07-22 15:11:28 +00:00
2015-01-20 07:43:41 +00:00
name = inv . supplier_invoice_number or inv . name or ' / '
2014-07-06 14:44:26 +00:00
totlines = [ ]
2008-07-22 15:11:28 +00:00
if inv . payment_term :
2014-07-06 14:44:26 +00:00
totlines = inv . with_context ( ctx ) . payment_term . compute ( total , date_invoice ) [ 0 ]
2008-07-22 15:11:28 +00:00
if totlines :
res_amount_currency = total_currency
2014-07-06 14:44:26 +00:00
ctx [ ' date ' ] = date_invoice
for i , t in enumerate ( totlines ) :
if inv . currency_id != company_currency :
amount_currency = company_currency . with_context ( ctx ) . compute ( t [ 1 ] , inv . currency_id )
2008-07-22 15:11:28 +00:00
else :
amount_currency = False
2014-07-06 14:44:26 +00:00
# last line: add the diff
2008-07-22 15:11:28 +00:00
res_amount_currency - = amount_currency or 0
2014-07-06 14:44:26 +00:00
if i + 1 == len ( totlines ) :
2008-07-22 15:11:28 +00:00
amount_currency + = res_amount_currency
iml . append ( {
' type ' : ' dest ' ,
' name ' : name ,
' price ' : t [ 1 ] ,
2014-07-06 14:44:26 +00:00
' account_id ' : inv . account_id . id ,
2008-07-22 15:11:28 +00:00
' date_maturity ' : t [ 0 ] ,
2014-07-06 14:44:26 +00:00
' amount_currency ' : diff_currency and amount_currency ,
' currency_id ' : diff_currency and inv . currency_id . id ,
2008-07-22 15:11:28 +00:00
' ref ' : ref ,
} )
else :
iml . append ( {
' type ' : ' dest ' ,
' name ' : name ,
' price ' : total ,
2014-07-06 14:44:26 +00:00
' account_id ' : inv . account_id . id ,
' date_maturity ' : inv . date_due ,
' amount_currency ' : diff_currency and total_currency ,
' currency_id ' : diff_currency and inv . currency_id . id ,
2008-07-22 15:11:28 +00:00
' ref ' : ref
2014-07-06 14:44:26 +00:00
} )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
date = date_invoice
2008-12-26 18:11:02 +00:00
2014-07-06 14:44:26 +00:00
part = self . env [ ' res.partner ' ] . _find_accounting_partner ( inv . partner_id )
2012-11-26 09:26:58 +00:00
2014-07-06 14:44:26 +00:00
line = [ ( 0 , 0 , self . line_get_convert ( l , part . id , date ) ) for l in iml ]
line = inv . group_lines ( iml , line )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
journal = inv . journal_id . with_context ( ctx )
2008-07-22 15:11:28 +00:00
if journal . centralisation :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' User Error! ' ) ,
2012-07-25 07:33:57 +00:00
_ ( ' You cannot create an invoice on a centralized journal. Uncheck the centralized counterpart box in the related journal from the configuration menu. ' ) )
2010-04-30 11:25:55 +00:00
2014-07-06 14:44:26 +00:00
line = inv . finalize_invoice_move_lines ( line )
2010-04-30 11:25:55 +00:00
2014-07-06 14:44:26 +00:00
move_vals = {
' ref ' : inv . reference or inv . name ,
2010-08-14 05:05:55 +00:00
' line_id ' : line ,
2014-07-06 14:44:26 +00:00
' journal_id ' : journal . id ,
2014-08-06 22:37:09 +00:00
' date ' : inv . date_invoice ,
2013-05-16 08:52:54 +00:00
' narration ' : inv . comment ,
' company_id ' : inv . company_id . id ,
2010-07-27 14:33:07 +00:00
}
2014-07-06 14:44:26 +00:00
ctx [ ' company_id ' ] = inv . company_id . id
period = inv . period_id
if not period :
period = period . with_context ( ctx ) . find ( date_invoice ) [ : 1 ]
if period :
move_vals [ ' period_id ' ] = period . id
2008-07-22 15:11:28 +00:00
for i in line :
2014-07-06 14:44:26 +00:00
i [ 2 ] [ ' period_id ' ] = period . id
2008-12-15 10:41:24 +00:00
2014-07-06 14:44:26 +00:00
ctx [ ' invoice ' ] = inv
2015-06-30 11:33:35 +00:00
ctx_nolang = ctx . copy ( )
ctx_nolang . pop ( ' lang ' , None )
move = account_move . with_context ( ctx_nolang ) . create ( move_vals )
2008-12-15 10:41:24 +00:00
2008-07-22 15:11:28 +00:00
# make the invoice point to that move
2014-07-06 14:44:26 +00:00
vals = {
' move_id ' : move . id ,
' period_id ' : period . id ,
' move_name ' : move . name ,
}
inv . with_context ( ctx ) . write ( vals )
2010-07-21 18:37:19 +00:00
# Pass invoice in context in method post: used if you want to get the same
# account move reference when creating the same invoice after a cancelled one:
2014-07-06 14:44:26 +00:00
move . post ( )
self . _log_event ( )
2008-07-22 15:11:28 +00:00
return True
2012-08-06 15:44:10 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def invoice_validate ( self ) :
return self . write ( { ' state ' : ' open ' } )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.model
def line_get_convert ( self , line , part , date ) :
2008-07-22 15:11:28 +00:00
return {
2014-07-06 14:44:26 +00:00
' date_maturity ' : line . get ( ' date_maturity ' , False ) ,
2010-12-27 07:37:46 +00:00
' partner_id ' : part ,
2014-07-06 14:44:26 +00:00
' name ' : line [ ' name ' ] [ : 64 ] ,
2009-01-28 18:33:27 +00:00
' date ' : date ,
2014-07-06 14:44:26 +00:00
' debit ' : line [ ' price ' ] > 0 and line [ ' price ' ] ,
' credit ' : line [ ' price ' ] < 0 and - line [ ' price ' ] ,
' account_id ' : line [ ' account_id ' ] ,
' analytic_lines ' : line . get ( ' analytic_lines ' , [ ] ) ,
' amount_currency ' : line [ ' price ' ] > 0 and abs ( line . get ( ' amount_currency ' , False ) ) or - abs ( line . get ( ' amount_currency ' , False ) ) ,
' currency_id ' : line . get ( ' currency_id ' , False ) ,
' tax_code_id ' : line . get ( ' tax_code_id ' , False ) ,
' tax_amount ' : line . get ( ' tax_amount ' , False ) ,
' ref ' : line . get ( ' ref ' , False ) ,
' quantity ' : line . get ( ' quantity ' , 1.00 ) ,
' product_id ' : line . get ( ' product_id ' , False ) ,
' product_uom_id ' : line . get ( ' uos_id ' , False ) ,
' analytic_account_id ' : line . get ( ' account_analytic_id ' , False ) ,
2008-07-22 15:11:28 +00:00
}
2014-07-06 14:44:26 +00:00
@api.multi
def action_number ( self ) :
#TODO: not correct fix but required a fresh values before reading it.
self . write ( { } )
2010-08-14 13:58:01 +00:00
2014-07-06 14:44:26 +00:00
for inv in self :
self . write ( { ' internal_number ' : inv . number } )
2010-08-17 06:15:57 +00:00
2014-07-06 14:44:26 +00:00
if inv . type in ( ' in_invoice ' , ' in_refund ' ) :
if not inv . reference :
2014-09-04 09:32:16 +00:00
ref = inv . number
2010-10-21 06:58:57 +00:00
else :
2014-07-06 14:44:26 +00:00
ref = inv . reference
2010-08-14 13:58:01 +00:00
else :
2014-09-04 09:32:16 +00:00
ref = inv . number
2014-07-06 14:44:26 +00:00
self . _cr . execute ( """ UPDATE account_move SET ref= %s
WHERE id = % s AND ( ref IS NULL OR ref = ' ' ) """ ,
( ref , inv . move_id . id ) )
self . _cr . execute ( """ UPDATE account_move_line SET ref= %s
WHERE move_id = % s AND ( ref IS NULL OR ref = ' ' ) """ ,
( ref , inv . move_id . id ) )
self . _cr . execute ( """ UPDATE account_analytic_line SET ref= %s
FROM account_move_line
WHERE account_move_line . move_id = % s AND
account_analytic_line . move_id = account_move_line . id """ ,
( ref , inv . move_id . id ) )
self . invalidate_cache ( )
2008-07-22 15:11:28 +00:00
return True
2014-07-06 14:44:26 +00:00
@api.multi
def action_cancel ( self ) :
moves = self . env [ ' account.move ' ]
for inv in self :
if inv . move_id :
moves + = inv . move_id
if inv . payment_ids :
for move_line in inv . payment_ids :
if move_line . reconcile_partial_id . line_partial_ids :
raise except_orm ( _ ( ' Error! ' ) , _ ( ' You cannot cancel an invoice which is partially paid. You need to unreconcile related payment entries first. ' ) )
2010-05-12 10:41:58 +00:00
2011-01-05 12:08:11 +00:00
# First, set the invoices as cancelled and detach the move ids
2014-07-06 14:44:26 +00:00
self . write ( { ' state ' : ' cancel ' , ' move_id ' : False } )
if moves :
2011-01-05 12:08:11 +00:00
# second, invalidate the move(s)
2014-07-06 14:44:26 +00:00
moves . button_cancel ( )
2011-01-05 12:08:11 +00:00
# delete the move this invoice was pointing to
# Note that the corresponding move_lines and move_reconciles
# will be automatically deleted too
2014-07-06 14:44:26 +00:00
moves . unlink ( )
self . _log_event ( - 1.0 , ' Cancel Invoice ' )
2008-07-22 15:11:28 +00:00
return True
###################
2014-07-06 14:44:26 +00:00
@api.multi
def _log_event ( self , factor = 1.0 , name = ' Open Invoice ' ) :
2010-05-27 07:08:59 +00:00
#TODO: implement messages system
return True
2008-07-22 15:11:28 +00:00
[FIX] models: display_name and name_get mismatch
- display_name uses name_get and not the other way around:
name_get should not call _compute_display_name, _compute_display_name should call name_get.
The previous behaviour was not backward-compatible with the old api.
All the models redefining name_get would have 2 different behaviors between name_get and display_name.
- Do not set an inverse function to display_name:
In most cases, writing on display_name writes on _rec_name (if any, not mandatory).
If the display_name computation is redefined, we need to redefine as well the inverse method to avoid unexpected behaviour
This required to also modify tests in base_import as readonly fields are avoided.
- Remove search method on display_name:
For the same reason as for the first point, it could be good that searching on display_name use name_search (and not the other way around).
However doing this would be very inefficiant (need to do the search, without limit, extract the ids of the name_get result just to generate
a subdomain ('id', 'in', [...]). As in most cases it would anyway mean to search on the _rec_name it's better to directly do so.
- Changing label to avoid mismatch:
In view displaying the list of fields or when a match is made on the label of a field (e.g. when importing csv file,
matching is made on both label and technical name), the fact that display_name field has '
Calling it 'Display Name' will avoid most errors.
- remove display_name definition from website_forum_doc,ir_model:
These fields are doing the same thing as the display_name of the new api, we can remove them.
We need to keep the one for res.partner as it's a stored field.
2014-07-16 08:35:52 +00:00
@api.multi
def name_get ( self ) :
2014-07-06 14:44:26 +00:00
TYPES = {
' out_invoice ' : _ ( ' Invoice ' ) ,
' in_invoice ' : _ ( ' Supplier Invoice ' ) ,
' out_refund ' : _ ( ' Refund ' ) ,
' in_refund ' : _ ( ' Supplier Refund ' ) ,
}
[FIX] models: display_name and name_get mismatch
- display_name uses name_get and not the other way around:
name_get should not call _compute_display_name, _compute_display_name should call name_get.
The previous behaviour was not backward-compatible with the old api.
All the models redefining name_get would have 2 different behaviors between name_get and display_name.
- Do not set an inverse function to display_name:
In most cases, writing on display_name writes on _rec_name (if any, not mandatory).
If the display_name computation is redefined, we need to redefine as well the inverse method to avoid unexpected behaviour
This required to also modify tests in base_import as readonly fields are avoided.
- Remove search method on display_name:
For the same reason as for the first point, it could be good that searching on display_name use name_search (and not the other way around).
However doing this would be very inefficiant (need to do the search, without limit, extract the ids of the name_get result just to generate
a subdomain ('id', 'in', [...]). As in most cases it would anyway mean to search on the _rec_name it's better to directly do so.
- Changing label to avoid mismatch:
In view displaying the list of fields or when a match is made on the label of a field (e.g. when importing csv file,
matching is made on both label and technical name), the fact that display_name field has '
Calling it 'Display Name' will avoid most errors.
- remove display_name definition from website_forum_doc,ir_model:
These fields are doing the same thing as the display_name of the new api, we can remove them.
We need to keep the one for res.partner as it's a stored field.
2014-07-16 08:35:52 +00:00
result = [ ]
for inv in self :
result . append ( ( inv . id , " %s %s " % ( inv . number or TYPES [ inv . type ] , inv . name or ' ' ) ) )
return result
2014-07-06 14:44:26 +00:00
@api.model
def name_search ( self , name , args = None , operator = ' ilike ' , limit = 100 ) :
args = args or [ ]
recs = self . browse ( )
2008-07-22 15:11:28 +00:00
if name :
2014-07-06 14:44:26 +00:00
recs = self . search ( [ ( ' number ' , ' = ' , name ) ] + args , limit = limit )
if not recs :
recs = self . search ( [ ( ' name ' , operator , name ) ] + args , limit = limit )
return recs . name_get ( )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.model
def _refund_cleanup_lines ( self , lines ) :
""" Convert records to dict of values suitable for one2many line creation
2013-02-15 09:27:27 +00:00
2014-07-06 14:44:26 +00:00
: param recordset lines : records to convert
2013-02-15 09:27:27 +00:00
: return : list of command tuple for one2many line creation [ ( 0 , 0 , dict of valueis ) , . . . ]
"""
2014-07-06 14:44:26 +00:00
result = [ ]
2008-07-22 15:11:28 +00:00
for line in lines :
2014-07-06 14:44:26 +00:00
values = { }
for name , field in line . _fields . iteritems ( ) :
if name in MAGIC_COLUMNS :
continue
elif field . type == ' many2one ' :
values [ name ] = line [ name ] . id
elif field . type not in [ ' many2many ' , ' one2many ' ] :
values [ name ] = line [ name ]
elif name == ' invoice_line_tax_id ' :
values [ name ] = [ ( 6 , 0 , line [ name ] . ids ) ]
result . append ( ( 0 , 0 , values ) )
return result
@api.model
def _prepare_refund ( self , invoice , date = None , period_id = None , description = None , journal_id = None ) :
""" Prepare the dict of values to create the new refund from the invoice.
2012-10-10 15:37:24 +00:00
This method may be overridden to implement custom
refund generation ( making sure to call super ( ) to establish
a clean extension chain ) .
2014-07-06 14:44:26 +00:00
: param record invoice : invoice to refund
2012-10-12 12:24:06 +00:00
: param string date : refund creation date from the wizard
2012-10-10 15:37:24 +00:00
: param integer period_id : force account . period from the wizard
2012-10-12 12:24:06 +00:00
: param string description : description of the refund from the wizard
2012-10-10 15:37:24 +00:00
: param integer journal_id : account . journal from the wizard
: return : dict of value to create ( ) the refund
"""
2014-07-06 14:44:26 +00:00
values = { }
2012-10-12 12:24:06 +00:00
for field in [ ' name ' , ' reference ' , ' comment ' , ' date_due ' , ' partner_id ' , ' company_id ' ,
2012-12-18 17:42:25 +00:00
' account_id ' , ' currency_id ' , ' payment_term ' , ' user_id ' , ' fiscal_position ' ] :
2014-07-06 14:44:26 +00:00
if invoice . _fields [ field ] . type == ' many2one ' :
values [ field ] = invoice [ field ] . id
2010-06-18 09:33:36 +00:00
else :
2014-07-06 14:44:26 +00:00
values [ field ] = invoice [ field ] or False
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
values [ ' invoice_line ' ] = self . _refund_cleanup_lines ( invoice . invoice_line )
tax_lines = filter ( lambda l : l . manual , invoice . tax_line )
values [ ' tax_line ' ] = self . _refund_cleanup_lines ( tax_lines )
2010-08-14 05:05:55 +00:00
2012-08-29 08:39:33 +00:00
if journal_id :
2014-07-06 14:44:26 +00:00
journal = self . env [ ' account.journal ' ] . browse ( journal_id )
2012-08-29 08:39:33 +00:00
elif invoice [ ' type ' ] == ' in_invoice ' :
2014-07-06 14:44:26 +00:00
journal = self . env [ ' account.journal ' ] . search ( [ ( ' type ' , ' = ' , ' purchase_refund ' ) ] , limit = 1 )
2012-08-29 08:39:33 +00:00
else :
2014-07-06 14:44:26 +00:00
journal = self . env [ ' account.journal ' ] . search ( [ ( ' type ' , ' = ' , ' sale_refund ' ) ] , limit = 1 )
values [ ' journal_id ' ] = journal . id
values [ ' type ' ] = TYPE2REFUND [ invoice [ ' type ' ] ]
2014-08-28 14:42:17 +00:00
values [ ' date_invoice ' ] = date or fields . Date . context_today ( invoice )
2014-07-06 14:44:26 +00:00
values [ ' state ' ] = ' draft '
values [ ' number ' ] = False
2015-02-23 12:38:44 +00:00
values [ ' origin ' ] = invoice . number
2014-07-06 14:44:26 +00:00
2012-08-29 08:39:33 +00:00
if period_id :
2014-07-06 14:44:26 +00:00
values [ ' period_id ' ] = period_id
2012-08-29 08:39:33 +00:00
if description :
2014-07-06 14:44:26 +00:00
values [ ' name ' ] = description
return values
@api.multi
@api.returns ( ' self ' )
def refund ( self , date = None , period_id = None , description = None , journal_id = None ) :
new_invoices = self . browse ( )
for invoice in self :
2008-07-22 15:11:28 +00:00
# create the new invoice
2014-07-06 14:44:26 +00:00
values = self . _prepare_refund ( invoice , date = date , period_id = period_id ,
description = description , journal_id = journal_id )
new_invoices + = self . create ( values )
return new_invoices
@api.v8
def pay_and_reconcile ( self , pay_amount , pay_account_id , period_id , pay_journal_id ,
writeoff_acc_id , writeoff_period_id , writeoff_journal_id , name = ' ' ) :
# TODO check if we can use different period for payment and the writeoff line
assert len ( self ) == 1 , " Can only pay one invoice at a time. "
2008-08-25 15:41:01 +00:00
# Take the seq as name for move
2014-07-06 14:44:26 +00:00
SIGN = { ' out_invoice ' : - 1 , ' in_invoice ' : 1 , ' out_refund ' : 1 , ' in_refund ' : - 1 }
direction = SIGN [ self . type ]
# take the chosen date
2014-08-28 14:42:17 +00:00
date = self . _context . get ( ' date_p ' ) or fields . Date . context_today ( self )
2009-11-25 09:31:44 +00:00
2009-11-02 08:52:13 +00:00
# Take the amount in currency and the currency of the payment
2014-07-06 14:44:26 +00:00
if self . _context . get ( ' amount_currency ' ) and self . _context . get ( ' currency_id ' ) :
amount_currency = self . _context [ ' amount_currency ' ]
currency_id = self . _context [ ' currency_id ' ]
2009-11-02 08:52:13 +00:00
else :
amount_currency = False
currency_id = False
2010-06-18 07:34:50 +00:00
2014-07-06 14:44:26 +00:00
pay_journal = self . env [ ' account.journal ' ] . browse ( pay_journal_id )
if self . type in ( ' in_invoice ' , ' in_refund ' ) :
ref = self . reference
2009-12-10 06:15:40 +00:00
else :
2014-09-04 09:32:16 +00:00
ref = self . number
2014-07-06 14:44:26 +00:00
partner = self . partner_id . _find_accounting_partner ( self . partner_id )
2015-03-28 19:51:58 +00:00
name = name or self . invoice_line [ 0 ] . name or self . number
2009-11-02 08:52:13 +00:00
# Pay attention to the sign for both debit/credit AND amount_currency
2008-07-22 15:11:28 +00:00
l1 = {
2014-07-06 14:44:26 +00:00
' name ' : name ,
' debit ' : direction * pay_amount > 0 and direction * pay_amount ,
' credit ' : direction * pay_amount < 0 and - direction * pay_amount ,
' account_id ' : self . account_id . id ,
2012-11-28 11:12:52 +00:00
' partner_id ' : partner . id ,
2014-07-06 14:44:26 +00:00
' ref ' : ref ,
2009-06-25 13:34:42 +00:00
' date ' : date ,
2014-07-06 14:44:26 +00:00
' currency_id ' : currency_id ,
' amount_currency ' : direction * ( amount_currency or 0.0 ) ,
' company_id ' : self . company_id . id ,
2008-07-22 15:11:28 +00:00
}
l2 = {
2014-07-06 14:44:26 +00:00
' name ' : name ,
' debit ' : direction * pay_amount < 0 and - direction * pay_amount ,
' credit ' : direction * pay_amount > 0 and direction * pay_amount ,
2008-07-22 15:11:28 +00:00
' account_id ' : pay_account_id ,
2012-11-28 11:12:52 +00:00
' partner_id ' : partner . id ,
2014-07-06 14:44:26 +00:00
' ref ' : ref ,
2009-06-25 13:34:42 +00:00
' date ' : date ,
2014-07-06 14:44:26 +00:00
' currency_id ' : currency_id ,
' amount_currency ' : - direction * ( amount_currency or 0.0 ) ,
' company_id ' : self . company_id . id ,
2008-07-22 15:11:28 +00:00
}
2014-07-06 14:44:26 +00:00
move = self . env [ ' account.move ' ] . create ( {
' ref ' : ref ,
' line_id ' : [ ( 0 , 0 , l1 ) , ( 0 , 0 , l2 ) ] ,
' journal_id ' : pay_journal_id ,
' period_id ' : period_id ,
' date ' : date ,
} )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
move_ids = ( move | self . move_id ) . ids
self . _cr . execute ( " SELECT id FROM account_move_line WHERE move_id IN %s " ,
( tuple ( move_ids ) , ) )
lines = self . env [ ' account.move.line ' ] . browse ( [ r [ 0 ] for r in self . _cr . fetchall ( ) ] )
lines2rec = lines . browse ( )
2008-07-22 15:11:28 +00:00
total = 0.0
2014-07-06 14:44:26 +00:00
for line in itertools . chain ( lines , self . payment_ids ) :
if line . account_id == self . account_id :
lines2rec + = line
total + = ( line . debit or 0.0 ) - ( line . credit or 0.0 )
inv_id , name = self . name_get ( ) [ 0 ]
if not round ( total , self . env [ ' decimal.precision ' ] . precision_get ( ' Account ' ) ) or writeoff_acc_id :
lines2rec . reconcile ( ' manual ' , writeoff_acc_id , writeoff_period_id , writeoff_journal_id )
2008-07-22 15:11:28 +00:00
else :
2014-07-06 14:44:26 +00:00
code = self . currency_id . symbol
2010-10-17 17:30:00 +00:00
# TODO: use currency's formatting function
2012-12-19 13:00:24 +00:00
msg = _ ( " Invoice partially paid: %s %s of %s %s ( %s %s remaining). " ) % \
2014-07-06 14:44:26 +00:00
( pay_amount , code , self . amount_total , code , total , code )
self . message_post ( body = msg )
lines2rec . reconcile_partial ( ' manual ' )
2008-12-20 04:57:05 +00:00
2009-01-02 09:03:46 +00:00
# Update the stored value (fields.function), so we write to trigger recompute
2014-07-06 14:44:26 +00:00
return self . write ( { } )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
@api.v7
def pay_and_reconcile ( self , cr , uid , ids , pay_amount , pay_account_id , period_id , pay_journal_id ,
writeoff_acc_id , writeoff_period_id , writeoff_journal_id , context = None , name = ' ' ) :
recs = self . browse ( cr , uid , ids , context )
[FIX] addons: fix usage of decorators `api.v7`/`api.v8`
Specifically, when one API implementation calls the other one, it has to call
the method *from the same class*. Otherwise, overriding the method may result
in an infinite recursion. Consider:
class A(Model):
_name = 'stuff'
@api.v8
def foo(self):
return 42
@api.v7
def foo(self, cr, uid, context=None):
return self.browse(cr, uid, [], context).foo()
class B(Model):
_inherit = 'stuff'
def foo(self, cr, uid, context=None):
return super(B, self).foo(cr, uid, context=context) + 1
and now call: `env['stuff'].foo()`. This invokes `B.foo` (new-API), which
calls `B.foo` (old-API), which calls `A.foo` (old-API), which calls `B.foo`
(new-API) instead of `A.foo`!
This issue would not be present if old-API `A.foo` was defined as:
@api.v7
def foo(self, cr, uid, context=None):
return A.foo(self.browse(cr, uid, [], context))
2016-02-09 09:01:34 +00:00
return account_invoice . pay_and_reconcile ( recs , pay_amount , pay_account_id , period_id , pay_journal_id ,
2014-07-06 14:44:26 +00:00
writeoff_acc_id , writeoff_period_id , writeoff_journal_id , name = name )
2008-07-22 15:11:28 +00:00
2014-07-06 14:44:26 +00:00
class account_invoice_line ( models . Model ) :
2008-07-22 15:11:28 +00:00
_name = " account.invoice.line "
2010-05-19 18:32:32 +00:00
_description = " Invoice Line "
2013-09-03 16:01:08 +00:00
_order = " invoice_id,sequence,id "
2012-08-08 07:06:12 +00:00
2014-07-06 14:44:26 +00:00
@api.one
@api.depends ( ' price_unit ' , ' discount ' , ' invoice_line_tax_id ' , ' quantity ' ,
' product_id ' , ' invoice_id.partner_id ' , ' invoice_id.currency_id ' )
def _compute_price ( self ) :
price = self . price_unit * ( 1 - ( self . discount or 0.0 ) / 100.0 )
taxes = self . invoice_line_tax_id . compute_all ( price , self . quantity , product = self . product_id , partner = self . invoice_id . partner_id )
self . price_subtotal = taxes [ ' total ' ]
if self . invoice_id :
self . price_subtotal = self . invoice_id . currency_id . round ( self . price_subtotal )
@api.model
def _default_price_unit ( self ) :
if not self . _context . get ( ' check_total ' ) :
return 0
total = self . _context [ ' check_total ' ]
for l in self . _context . get ( ' invoice_line ' , [ ] ) :
if isinstance ( l , ( list , tuple ) ) and len ( l ) > = 3 and l [ 2 ] :
vals = l [ 2 ]
price = vals . get ( ' price_unit ' , 0 ) * ( 1 - vals . get ( ' discount ' , 0 ) / 100.0 )
total = total - ( price * vals . get ( ' quantity ' ) )
taxes = vals . get ( ' invoice_line_tax_id ' )
if taxes and len ( taxes [ 0 ] ) > = 3 and taxes [ 0 ] [ 2 ] :
taxes = self . env [ ' account.tax ' ] . browse ( taxes [ 0 ] [ 2 ] )
tax_res = taxes . compute_all ( price , vals . get ( ' quantity ' ) ,
product = vals . get ( ' product_id ' ) , partner = self . _context . get ( ' partner_id ' ) )
for tax in tax_res [ ' taxes ' ] :
total = total - tax [ ' amount ' ]
return total
@api.model
def _default_account ( self ) :
2012-09-09 12:42:59 +00:00
# XXX this gets the default account for the user's company,
# it should get the default account for the invoice's company
# however, the invoice's company does not reach this point
2014-07-06 14:44:26 +00:00
if self . _context . get ( ' type ' ) in ( ' out_invoice ' , ' out_refund ' ) :
return self . env [ ' ir.property ' ] . get ( ' property_account_income_categ ' , ' product.category ' )
2012-12-21 09:41:47 +00:00
else :
2014-07-06 14:44:26 +00:00
return self . env [ ' ir.property ' ] . get ( ' property_account_expense_categ ' , ' product.category ' )
name = fields . Text ( string = ' Description ' , required = True )
origin = fields . Char ( string = ' Source Document ' ,
help = " Reference of the document that produced this invoice. " )
sequence = fields . Integer ( string = ' Sequence ' , default = 10 ,
help = " Gives the sequence of this line when displaying the invoice. " )
invoice_id = fields . Many2one ( ' account.invoice ' , string = ' Invoice Reference ' ,
ondelete = ' cascade ' , index = True )
uos_id = fields . Many2one ( ' product.uom ' , string = ' Unit of Measure ' ,
ondelete = ' set null ' , index = True )
product_id = fields . Many2one ( ' product.product ' , string = ' Product ' ,
2014-12-10 10:21:17 +00:00
ondelete = ' restrict ' , index = True )
2014-07-06 14:44:26 +00:00
account_id = fields . Many2one ( ' account.account ' , string = ' Account ' ,
required = True , domain = [ ( ' type ' , ' not in ' , [ ' view ' , ' closed ' ] ) ] ,
default = _default_account ,
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 ' ) ,
default = _default_price_unit )
price_subtotal = fields . Float ( string = ' Amount ' , digits = dp . get_precision ( ' Account ' ) ,
store = True , readonly = True , compute = ' _compute_price ' )
quantity = fields . Float ( string = ' Quantity ' , digits = dp . get_precision ( ' Product Unit of Measure ' ) ,
required = True , default = 1 )
discount = fields . Float ( string = ' Discount ( % ) ' , digits = dp . get_precision ( ' Discount ' ) ,
default = 0.0 )
invoice_line_tax_id = fields . Many2many ( ' account.tax ' ,
' account_invoice_line_tax ' , ' invoice_line_id ' , ' tax_id ' ,
[FIX] account,*: preserve deactivated taxes
By default, when reading a m2m field, entries that are
deactivated in the destination table are not included.
This behavior is desirable in some cases (e.g. for
"tags" or "categories", but not for entries that
significantly impact other field values in the parent
record, such as taxes.
The problem is rather obvious: when displaying a
paid invoice that used taxes that are now deactivated,
the taxes are hidden while they still affect the
computed amount. And after cancelling + resetting
to draft, the tax is not taken into account anymore,
while still being linked.
Forcing the field-level (python) domain to include
both active and inactive entries solves the problem:
- when reading, displaying and recomputing values,
deactivated taxes will be included.
- when trying to pick a tax, deactivated entries
will still be ignored, as expected.
This commit applies the technique to all m2m
fields that refer to taxes.
Fixes #12066
opw-677751
2016-05-27 09:38:20 +00:00
string = ' Taxes ' , domain = [ ( ' parent_id ' , ' = ' , False ) , ' | ' , ( ' active ' , ' = ' , False ) , ( ' active ' , ' = ' , True ) ] )
2014-07-06 14:44:26 +00:00
account_analytic_id = fields . Many2one ( ' account.analytic.account ' ,
string = ' Analytic Account ' )
company_id = fields . Many2one ( ' res.company ' , string = ' Company ' ,
related = ' invoice_id.company_id ' , store = True , readonly = True )
partner_id = fields . Many2one ( ' res.partner ' , string = ' Partner ' ,
related = ' invoice_id.partner_id ' , store = True , readonly = True )
@api.model
def fields_view_get ( self , view_id = None , view_type = ' form ' , toolbar = False , submenu = False ) :
res = super ( account_invoice_line , self ) . fields_view_get (
view_id = view_id , view_type = view_type , toolbar = toolbar , submenu = submenu )
if self . _context . get ( ' type ' ) :
2011-03-09 05:10:47 +00:00
doc = etree . XML ( res [ ' arch ' ] )
for node in doc . xpath ( " //field[@name= ' product_id ' ] " ) :
2014-07-06 14:44:26 +00:00
if self . _context [ ' type ' ] in ( ' in_invoice ' , ' in_refund ' ) :
2016-08-24 08:59:58 +00:00
# Hack to fix the stable version 8.0 -> saas-12
# purchase_ok will be moved from purchase to product in master #13271
if ' purchase_ok ' in self . env [ ' product.template ' ] . _fields :
node . set ( ' domain ' , " [( ' purchase_ok ' , ' = ' , True)] " )
2011-03-09 05:10:47 +00:00
else :
node . set ( ' domain ' , " [( ' sale_ok ' , ' = ' , True)] " )
res [ ' arch ' ] = etree . tostring ( doc )
return res
2014-07-06 14:44:26 +00:00
@api.multi
def product_id_change ( self , product , uom_id , qty = 0 , name = ' ' , type = ' out_invoice ' ,
partner_id = False , fposition_id = False , price_unit = False , currency_id = False ,
2014-09-09 09:42:50 +00:00
company_id = None ) :
context = self . _context
2014-07-06 14:44:26 +00:00
company_id = company_id if company_id is not None else context . get ( ' company_id ' , False )
self = self . with_context ( company_id = company_id , force_company = company_id )
2008-09-03 13:47:19 +00:00
if not partner_id :
2014-07-06 14:44:26 +00:00
raise except_orm ( _ ( ' No Partner Defined! ' ) , _ ( " You must first select a partner! " ) )
2008-07-22 15:11:28 +00:00
if not product :
if type in ( ' in_invoice ' , ' in_refund ' ) :
2015-06-18 17:33:19 +00:00
return { ' value ' : { } , ' domain ' : { ' uos_id ' : [ ] } }
2008-07-22 15:11:28 +00:00
else :
2015-06-18 17:33:19 +00:00
return { ' value ' : { ' price_unit ' : 0.0 } , ' domain ' : { ' uos_id ' : [ ] } }
2014-07-06 14:44:26 +00:00
values = { }
part = self . env [ ' res.partner ' ] . browse ( partner_id )
fpos = self . env [ ' account.fiscal.position ' ] . browse ( fposition_id )
2009-01-19 16:49:29 +00:00
2010-05-11 13:04:20 +00:00
if part . lang :
2014-07-06 14:44:26 +00:00
self = self . with_context ( lang = part . lang )
product = self . env [ ' product.product ' ] . browse ( product )
values [ ' name ' ] = product . partner_ref
if type in ( ' out_invoice ' , ' out_refund ' ) :
account = product . property_account_income or product . categ_id . property_account_income_categ
2008-07-22 15:11:28 +00:00
else :
2014-07-06 14:44:26 +00:00
account = product . property_account_expense or product . categ_id . property_account_expense_categ
account = fpos . map_account ( account )
if account :
values [ ' account_id ' ] = account . id
2008-07-22 15:11:28 +00:00
2009-01-26 15:28:25 +00:00
if type in ( ' out_invoice ' , ' out_refund ' ) :
2014-07-06 14:44:26 +00:00
taxes = product . taxes_id or account . tax_ids
if product . description_sale :
values [ ' name ' ] + = ' \n ' + product . description_sale
2009-01-26 15:28:25 +00:00
else :
2014-07-06 14:44:26 +00:00
taxes = product . supplier_taxes_id or account . tax_ids
if product . description_purchase :
values [ ' name ' ] + = ' \n ' + product . description_purchase
2013-01-18 11:33:09 +00:00
2015-12-02 09:26:08 +00:00
fp_taxes = fpos . map_tax ( taxes )
values [ ' invoice_line_tax_id ' ] = fp_taxes . ids
2010-12-10 12:13:27 +00:00
2009-01-26 15:28:25 +00:00
if type in ( ' in_invoice ' , ' in_refund ' ) :
2015-12-02 09:26:08 +00:00
if price_unit and price_unit != product . standard_price :
values [ ' price_unit ' ] = price_unit
else :
values [ ' price_unit ' ] = self . env [ ' account.tax ' ] . _fix_tax_included_price ( product . standard_price , taxes , fp_taxes . ids )
2009-01-26 15:28:25 +00:00
else :
2015-12-02 09:26:08 +00:00
values [ ' price_unit ' ] = self . env [ ' account.tax ' ] . _fix_tax_included_price ( product . lst_price , taxes , fp_taxes . ids )
2009-01-26 15:28:25 +00:00
2015-06-11 11:00:49 +00:00
values [ ' uos_id ' ] = product . uom_id . id
2015-06-01 12:02:40 +00:00
if uom_id :
2015-06-11 11:00:49 +00:00
uom = self . env [ ' product.uom ' ] . browse ( uom_id )
if product . uom_id . category_id . id == uom . category_id . id :
values [ ' uos_id ' ] = uom_id
2014-07-06 14:44:26 +00:00
domain = { ' uos_id ' : [ ( ' category_id ' , ' = ' , product . uom_id . category_id . id ) ] }
2012-12-05 09:40:45 +00:00
2014-07-06 14:44:26 +00:00
company = self . env [ ' res.company ' ] . browse ( company_id )
currency = self . env [ ' res.currency ' ] . browse ( currency_id )
2009-12-24 08:43:23 +00:00
2014-07-06 14:44:26 +00:00
if company and currency :
if company . currency_id != currency :
values [ ' price_unit ' ] = values [ ' price_unit ' ] * currency . rate
2009-12-24 08:43:23 +00:00
2014-07-06 14:44:26 +00:00
if values [ ' uos_id ' ] and values [ ' uos_id ' ] != product . uom_id . id :
values [ ' price_unit ' ] = self . env [ ' product.uom ' ] . _compute_price (
product . uom_id . id , values [ ' price_unit ' ] , values [ ' uos_id ' ] )
2009-12-24 08:43:23 +00:00
2014-07-06 14:44:26 +00:00
return { ' value ' : values , ' domain ' : domain }
2010-02-09 08:31:46 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def uos_id_change ( self , product , uom , qty = 0 , name = ' ' , type = ' out_invoice ' , partner_id = False ,
2014-09-09 09:42:50 +00:00
fposition_id = False , price_unit = False , currency_id = False , company_id = None ) :
context = self . _context
2014-07-06 14:44:26 +00:00
company_id = company_id if company_id != None else context . get ( ' company_id ' , False )
self = self . with_context ( company_id = company_id )
result = self . product_id_change (
product , uom , qty , name , type , partner_id , fposition_id , price_unit ,
2014-09-09 09:42:50 +00:00
currency_id , company_id = company_id ,
2014-07-06 14:44:26 +00:00
)
2010-12-17 08:39:47 +00:00
warning = { }
2010-06-08 09:14:47 +00:00
if not uom :
2014-07-06 14:44:26 +00:00
result [ ' value ' ] [ ' price_unit ' ] = 0.0
2010-12-17 10:36:58 +00:00
if product and uom :
2014-07-06 14:44:26 +00:00
prod = self . env [ ' product.product ' ] . browse ( product )
prod_uom = self . env [ ' product.uom ' ] . browse ( uom )
if prod . uom_id . category_id != prod_uom . category_id :
2012-08-17 05:51:34 +00:00
warning = {
2010-12-17 10:18:21 +00:00
' title ' : _ ( ' Warning! ' ) ,
2014-07-06 14:44:26 +00:00
' message ' : _ ( ' The selected unit of measure is not compatible with the unit of measure of the product. ' ) ,
2012-08-17 05:51:34 +00:00
}
2014-07-06 14:44:26 +00:00
result [ ' value ' ] [ ' uos_id ' ] = prod . uom_id . id
if warning :
result [ ' warning ' ] = warning
return result
@api.model
def move_line_get ( self , invoice_id ) :
inv = self . env [ ' account.invoice ' ] . browse ( invoice_id )
currency = inv . currency_id . with_context ( date = inv . date_invoice )
company_currency = inv . company_id . currency_id
2010-06-08 11:44:24 +00:00
2008-07-22 15:11:28 +00:00
res = [ ]
for line in inv . invoice_line :
2014-07-06 14:44:26 +00:00
mres = self . move_line_get_item ( line )
2014-06-21 06:18:42 +00:00
mres [ ' invl_id ' ] = line . id
2008-07-22 15:11:28 +00:00
res . append ( mres )
2014-07-06 14:44:26 +00:00
tax_code_found = False
taxes = line . invoice_line_tax_id . compute_all (
( line . price_unit * ( 1.0 - ( line . discount or 0.0 ) / 100.0 ) ) ,
line . quantity , line . product_id , inv . partner_id ) [ ' taxes ' ]
for tax in taxes :
2008-07-22 15:11:28 +00:00
if inv . type in ( ' out_invoice ' , ' in_invoice ' ) :
tax_code_id = tax [ ' base_code_id ' ]
2015-03-25 12:22:31 +00:00
tax_amount = tax [ ' price_unit ' ] * line . quantity * tax [ ' base_sign ' ]
2008-07-22 15:11:28 +00:00
else :
tax_code_id = tax [ ' ref_base_code_id ' ]
2015-03-25 12:22:31 +00:00
tax_amount = tax [ ' price_unit ' ] * line . quantity * tax [ ' ref_base_sign ' ]
2008-07-22 15:11:28 +00:00
if tax_code_found :
if not tax_code_id :
continue
2014-07-06 14:44:26 +00:00
res . append ( dict ( mres ) )
2008-07-22 15:11:28 +00:00
res [ - 1 ] [ ' price ' ] = 0.0
res [ - 1 ] [ ' account_analytic_id ' ] = False
elif not tax_code_id :
continue
tax_code_found = True
res [ - 1 ] [ ' tax_code_id ' ] = tax_code_id
2014-07-06 14:44:26 +00:00
res [ - 1 ] [ ' tax_amount ' ] = currency . compute ( tax_amount , company_currency )
2008-07-22 15:11:28 +00:00
return res
2014-07-06 14:44:26 +00:00
@api.model
def move_line_get_item ( self , line ) :
2008-07-22 15:11:28 +00:00
return {
2014-07-06 14:44:26 +00:00
' type ' : ' src ' ,
2012-07-14 20:36:23 +00:00
' name ' : line . name . split ( ' \n ' ) [ 0 ] [ : 64 ] ,
2014-07-06 14:44:26 +00:00
' price_unit ' : line . price_unit ,
' quantity ' : line . quantity ,
' price ' : line . price_subtotal ,
' account_id ' : line . account_id . id ,
' product_id ' : line . product_id . id ,
' uos_id ' : line . uos_id . id ,
' account_analytic_id ' : line . account_analytic_id . id ,
' taxes ' : line . invoice_line_tax_id ,
2008-07-22 15:11:28 +00:00
}
2014-07-06 14:44:26 +00:00
2008-07-22 15:11:28 +00:00
#
2009-01-26 15:28:25 +00:00
# Set the tax field according to the account and the fiscal position
2008-07-22 15:11:28 +00:00
#
2014-07-06 14:44:26 +00:00
@api.multi
def onchange_account_id ( self , product_id , partner_id , inv_type , fposition_id , account_id ) :
2009-01-26 15:28:25 +00:00
if not account_id :
2008-07-22 15:11:28 +00:00
return { }
2012-09-30 10:50:06 +00:00
unique_tax_ids = [ ]
2014-07-06 14:44:26 +00:00
account = self . env [ ' account.account ' ] . browse ( account_id )
2012-09-30 10:50:06 +00:00
if not product_id :
2014-07-06 14:44:26 +00:00
fpos = self . env [ ' account.fiscal.position ' ] . browse ( fposition_id )
unique_tax_ids = fpos . map_tax ( account . tax_ids ) . ids
2012-09-30 10:50:06 +00:00
else :
2014-07-06 14:44:26 +00:00
product_change_result = self . product_id_change ( product_id , False , type = inv_type ,
partner_id = partner_id , fposition_id = fposition_id , company_id = account . company_id . id )
if ' invoice_line_tax_id ' in product_change_result . get ( ' value ' , { } ) :
2012-09-30 10:50:06 +00:00
unique_tax_ids = product_change_result [ ' value ' ] [ ' invoice_line_tax_id ' ]
2014-07-06 14:44:26 +00:00
return { ' value ' : { ' invoice_line_tax_id ' : unique_tax_ids } }
2010-10-20 07:00:04 +00:00
2006-12-07 13:41:40 +00:00
2014-07-06 14:44:26 +00:00
class account_invoice_tax ( models . Model ) :
2008-07-22 15:11:28 +00:00
_name = " account.invoice.tax "
_description = " Invoice Tax "
2014-07-06 14:44:26 +00:00
_order = ' sequence '
2010-09-16 13:51:02 +00:00
2014-07-06 14:44:26 +00:00
@api.one
@api.depends ( ' base ' , ' base_amount ' , ' amount ' , ' tax_amount ' )
def _compute_factors ( self ) :
self . factor_base = self . base_amount / self . base if self . base else 1.0
self . factor_tax = self . tax_amount / self . amount if self . amount else 1.0
invoice_id = fields . Many2one ( ' account.invoice ' , string = ' Invoice Line ' ,
ondelete = ' cascade ' , index = True )
name = fields . Char ( string = ' Tax Description ' ,
required = True )
account_id = fields . Many2one ( ' account.account ' , string = ' Tax Account ' ,
required = True , domain = [ ( ' type ' , ' not in ' , [ ' view ' , ' income ' , ' closed ' ] ) ] )
account_analytic_id = fields . Many2one ( ' account.analytic.account ' , string = ' Analytic account ' )
base = fields . Float ( string = ' Base ' , digits = dp . get_precision ( ' Account ' ) )
amount = fields . Float ( string = ' Amount ' , digits = dp . get_precision ( ' Account ' ) )
manual = fields . Boolean ( string = ' Manual ' , default = True )
sequence = fields . Integer ( string = ' Sequence ' ,
help = " Gives the sequence order when displaying a list of invoice tax. " )
base_code_id = fields . Many2one ( ' account.tax.code ' , string = ' Base Code ' ,
help = " The account basis of the tax declaration. " )
base_amount = fields . Float ( string = ' Base Code Amount ' , digits = dp . get_precision ( ' Account ' ) ,
default = 0.0 )
tax_code_id = fields . Many2one ( ' account.tax.code ' , string = ' Tax Code ' ,
help = " The tax basis of the tax declaration. " )
tax_amount = fields . Float ( string = ' Tax Code Amount ' , digits = dp . get_precision ( ' Account ' ) ,
default = 0.0 )
company_id = fields . Many2one ( ' res.company ' , string = ' Company ' ,
related = ' account_id.company_id ' , store = True , readonly = True )
factor_base = fields . Float ( string = ' Multipication factor for Base code ' ,
compute = ' _compute_factors ' )
factor_tax = fields . Float ( string = ' Multipication factor Tax code ' ,
compute = ' _compute_factors ' )
@api.multi
def base_change ( self , base , currency_id = False , company_id = False , date_invoice = False ) :
factor = self . factor_base if self else 1
company = self . env [ ' res.company ' ] . browse ( company_id )
if currency_id and company . currency_id :
currency = self . env [ ' res.currency ' ] . browse ( currency_id )
2014-08-28 14:42:17 +00:00
currency = currency . with_context ( date = date_invoice or fields . Date . context_today ( self ) )
2014-07-06 14:44:26 +00:00
base = currency . compute ( base * factor , company . currency_id , round = False )
return { ' value ' : { ' base_amount ' : base } }
@api.multi
def amount_change ( self , amount , currency_id = False , company_id = False , date_invoice = False ) :
company = self . env [ ' res.company ' ] . browse ( company_id )
if currency_id and company . currency_id :
currency = self . env [ ' res.currency ' ] . browse ( currency_id )
2014-08-28 14:42:17 +00:00
currency = currency . with_context ( date = date_invoice or fields . Date . context_today ( self ) )
2015-04-01 17:13:25 +00:00
amount = currency . compute ( amount , company . currency_id , round = False )
2015-08-05 15:42:47 +00:00
tax_sign = ( self . tax_amount / self . amount ) if self . amount else 1
2015-08-03 10:23:52 +00:00
return { ' value ' : { ' tax_amount ' : amount * tax_sign } }
2009-11-13 05:41:16 +00:00
2014-07-06 14:44:26 +00:00
@api.v8
def compute ( self , invoice ) :
2008-07-22 15:11:28 +00:00
tax_grouped = { }
2014-08-28 14:42:17 +00:00
currency = invoice . currency_id . with_context ( date = invoice . date_invoice or fields . Date . context_today ( invoice ) )
2014-07-06 14:44:26 +00:00
company_currency = invoice . company_id . currency_id
for line in invoice . invoice_line :
taxes = line . invoice_line_tax_id . compute_all (
( line . price_unit * ( 1 - ( line . discount or 0.0 ) / 100.0 ) ) ,
line . quantity , line . product_id , invoice . partner_id ) [ ' taxes ' ]
for tax in taxes :
val = {
' invoice_id ' : invoice . id ,
' name ' : tax [ ' name ' ] ,
' amount ' : tax [ ' amount ' ] ,
' manual ' : False ,
' sequence ' : tax [ ' sequence ' ] ,
' base ' : currency . round ( tax [ ' price_unit ' ] * line [ ' quantity ' ] ) ,
}
if invoice . type in ( ' out_invoice ' , ' in_invoice ' ) :
2008-07-22 15:11:28 +00:00
val [ ' base_code_id ' ] = tax [ ' base_code_id ' ]
val [ ' tax_code_id ' ] = tax [ ' tax_code_id ' ]
2014-07-06 14:44:26 +00:00
val [ ' base_amount ' ] = currency . compute ( val [ ' base ' ] * tax [ ' base_sign ' ] , company_currency , round = False )
val [ ' tax_amount ' ] = currency . compute ( val [ ' amount ' ] * tax [ ' tax_sign ' ] , company_currency , round = False )
2008-07-22 15:11:28 +00:00
val [ ' account_id ' ] = tax [ ' account_collected_id ' ] or line . account_id . id
2012-07-10 21:21:15 +00:00
val [ ' account_analytic_id ' ] = tax [ ' account_analytic_collected_id ' ]
2008-07-22 15:11:28 +00:00
else :
val [ ' base_code_id ' ] = tax [ ' ref_base_code_id ' ]
val [ ' tax_code_id ' ] = tax [ ' ref_tax_code_id ' ]
2014-07-06 14:44:26 +00:00
val [ ' base_amount ' ] = currency . compute ( val [ ' base ' ] * tax [ ' ref_base_sign ' ] , company_currency , round = False )
val [ ' tax_amount ' ] = currency . compute ( val [ ' amount ' ] * tax [ ' ref_tax_sign ' ] , company_currency , round = False )
2008-07-22 15:11:28 +00:00
val [ ' account_id ' ] = tax [ ' account_paid_id ' ] or line . account_id . id
2012-07-10 21:21:15 +00:00
val [ ' account_analytic_id ' ] = tax [ ' account_analytic_paid_id ' ]
2008-07-22 15:11:28 +00:00
2014-06-23 15:23:32 +00:00
# If the taxes generate moves on the same financial account as the invoice line
# and no default analytic account is defined at the tax level, propagate the
# analytic account from the invoice line to the tax line. This is necessary
# in situations were (part of) the taxes cannot be reclaimed,
# to ensure the tax move is allocated to the proper analytic account.
if not val . get ( ' account_analytic_id ' ) and line . account_analytic_id and val [ ' account_id ' ] == line . account_id . id :
val [ ' account_analytic_id ' ] = line . account_analytic_id . id
2014-06-06 14:51:09 +00:00
key = ( val [ ' tax_code_id ' ] , val [ ' base_code_id ' ] , val [ ' account_id ' ] )
2008-07-22 15:11:28 +00:00
if not key in tax_grouped :
tax_grouped [ key ] = val
else :
tax_grouped [ key ] [ ' base ' ] + = val [ ' base ' ]
2014-07-06 14:44:26 +00:00
tax_grouped [ key ] [ ' amount ' ] + = val [ ' amount ' ]
2008-07-22 15:11:28 +00:00
tax_grouped [ key ] [ ' base_amount ' ] + = val [ ' base_amount ' ]
tax_grouped [ key ] [ ' tax_amount ' ] + = val [ ' tax_amount ' ]
2008-11-20 19:46:20 +00:00
for t in tax_grouped . values ( ) :
2014-07-06 14:44:26 +00:00
t [ ' base ' ] = currency . round ( t [ ' base ' ] )
t [ ' amount ' ] = currency . round ( t [ ' amount ' ] )
t [ ' base_amount ' ] = currency . round ( t [ ' base_amount ' ] )
t [ ' tax_amount ' ] = currency . round ( t [ ' tax_amount ' ] )
2008-07-22 15:11:28 +00:00
return tax_grouped
2014-07-06 14:44:26 +00:00
@api.v7
def compute ( self , cr , uid , invoice_id , context = None ) :
recs = self . browse ( cr , uid , [ ] , context )
invoice = recs . env [ ' account.invoice ' ] . browse ( invoice_id )
[FIX] addons: fix usage of decorators `api.v7`/`api.v8`
Specifically, when one API implementation calls the other one, it has to call
the method *from the same class*. Otherwise, overriding the method may result
in an infinite recursion. Consider:
class A(Model):
_name = 'stuff'
@api.v8
def foo(self):
return 42
@api.v7
def foo(self, cr, uid, context=None):
return self.browse(cr, uid, [], context).foo()
class B(Model):
_inherit = 'stuff'
def foo(self, cr, uid, context=None):
return super(B, self).foo(cr, uid, context=context) + 1
and now call: `env['stuff'].foo()`. This invokes `B.foo` (new-API), which
calls `B.foo` (old-API), which calls `A.foo` (old-API), which calls `B.foo`
(new-API) instead of `A.foo`!
This issue would not be present if old-API `A.foo` was defined as:
@api.v7
def foo(self, cr, uid, context=None):
return A.foo(self.browse(cr, uid, [], context))
2016-02-09 09:01:34 +00:00
return account_invoice_tax . compute ( recs , invoice )
2014-07-06 14:44:26 +00:00
@api.model
def move_line_get ( self , invoice_id ) :
2008-07-22 15:11:28 +00:00
res = [ ]
2014-07-06 14:44:26 +00:00
self . _cr . execute (
' SELECT * FROM account_invoice_tax WHERE invoice_id = %s ' ,
( invoice_id , )
)
for row in self . _cr . dictfetchall ( ) :
if not ( row [ ' amount ' ] or row [ ' tax_code_id ' ] or row [ ' tax_amount ' ] ) :
2008-07-22 15:11:28 +00:00
continue
res . append ( {
2014-07-06 14:44:26 +00:00
' type ' : ' tax ' ,
' name ' : row [ ' name ' ] ,
' price_unit ' : row [ ' amount ' ] ,
2008-07-22 15:11:28 +00:00
' quantity ' : 1 ,
2014-07-06 14:44:26 +00:00
' price ' : row [ ' amount ' ] or 0.0 ,
' account_id ' : row [ ' account_id ' ] ,
' tax_code_id ' : row [ ' tax_code_id ' ] ,
' tax_amount ' : row [ ' tax_amount ' ] ,
' account_analytic_id ' : row [ ' account_analytic_id ' ] ,
2008-07-22 15:11:28 +00:00
} )
return res
2010-12-27 07:37:46 +00:00
2010-05-14 04:48:36 +00:00
2014-07-06 14:44:26 +00:00
class res_partner ( models . Model ) :
# Inherits partner and adds invoice information in the partner form
2010-05-14 04:48:36 +00:00
_inherit = ' res.partner '
2014-07-06 14:44:26 +00:00
invoice_ids = fields . One2many ( ' account.invoice ' , ' partner_id ' , string = ' Invoices ' ,
2014-12-01 14:42:51 +00:00
readonly = True , copy = False )
2011-04-29 05:01:18 +00:00
2013-04-07 21:23:33 +00:00
def _find_accounting_partner ( self , partner ) :
2013-03-05 16:20:15 +00:00
'''
Find the partner for which the accounting entries will be created
'''
2013-04-22 15:34:49 +00:00
return partner . commercial_partner_id
2013-03-05 16:20:15 +00:00
2014-07-06 14:44:26 +00:00
class mail_compose_message ( models . Model ) :
2012-11-21 10:21:37 +00:00
_inherit = ' mail.compose.message '
2012-03-27 11:44:44 +00:00
2014-07-06 14:44:26 +00:00
@api.multi
def send_mail ( self ) :
context = self . _context
if context . get ( ' default_model ' ) == ' account.invoice ' and \
context . get ( ' default_res_id ' ) and context . get ( ' mark_invoice_as_sent ' ) :
invoice = self . env [ ' account.invoice ' ] . browse ( context [ ' default_res_id ' ] )
invoice = invoice . with_context ( mail_post_autofollow = True )
2014-07-09 21:22:21 +00:00
invoice . write ( { ' sent ' : True } )
invoice . message_post ( body = _ ( " Invoice sent " ) )
2014-07-06 14:44:26 +00:00
return super ( mail_compose_message , self ) . send_mail ( )
2012-03-27 11:44:44 +00:00
2010-07-08 14:25:59 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: