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
2014-07-06 14:44:26 +00:00
query = " SELECT reconcile_id FROM account_move_line WHERE id IN %s "
self . _cr . execute ( query , ( tuple ( line_ids ) , ) )
return all ( row [ 0 ] for row in self . _cr . fetchall ( ) )
@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
line [ ' amount_currency ' ] = line [ ' price ' ]
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 ' ]
line2 [ tmp ] [ ' analytic_lines ' ] + = l [ ' analytic_lines ' ]
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
2014-08-29 09:36:35 +00:00
if self . env [ ' res.users ' ] . 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 )
return recs . pay_and_reconcile ( pay_amount , pay_account_id , period_id , pay_journal_id ,
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 ' ,
string = ' Taxes ' , domain = [ ( ' parent_id ' , ' = ' , False ) ] )
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 ' ) :
2011-03-09 05:10:47 +00:00
node . set ( ' domain ' , " [( ' purchase_ok ' , ' = ' , True)] " )
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
2014-07-06 14:44:26 +00:00
taxes = fpos . map_tax ( taxes )
values [ ' invoice_line_tax_id ' ] = 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 ' ) :
2014-07-06 14:44:26 +00:00
values [ ' price_unit ' ] = price_unit or product . standard_price
2009-01-26 15:28:25 +00:00
else :
2015-03-06 09:57:16 +00:00
values [ ' price_unit ' ] = product . lst_price
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 :
if type in ( ' in_invoice ' , ' in_refund ' ) :
values [ ' price_unit ' ] = product . standard_price
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 )
return recs . compute ( invoice )
@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: