1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from lxml import etree
24 import openerp.addons.decimal_precision as dp
25 import openerp.exceptions
27 from openerp.osv import fields, osv, orm
28 from openerp.tools import float_compare
29 from openerp.tools.translate import _
30 from openerp import SUPERUSER_ID
32 class account_invoice(osv.osv):
33 def _amount_all(self, cr, uid, ids, name, args, context=None):
35 for invoice in self.browse(cr, uid, ids, context=context):
37 'amount_untaxed': 0.0,
41 for line in invoice.invoice_line:
42 res[invoice.id]['amount_untaxed'] += line.price_subtotal
43 for line in invoice.tax_line:
44 res[invoice.id]['amount_tax'] += line.amount
45 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
48 def _get_journal(self, cr, uid, context=None):
51 type_inv = context.get('type', 'out_invoice')
52 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
53 company_id = context.get('company_id', user.company_id.id)
54 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
55 journal_obj = self.pool.get('account.journal')
56 domain = [('company_id', '=', company_id)]
57 if isinstance(type_inv, list):
58 domain.append(('type', 'in', [type2journal.get(type) for type in type_inv if type2journal.get(type)]))
60 domain.append(('type', '=', type2journal.get(type_inv, 'sale')))
61 res = journal_obj.search(cr, uid, domain, limit=1)
62 return res and res[0] or False
64 def _get_currency(self, cr, uid, context=None):
66 journal_id = self._get_journal(cr, uid, context=context)
68 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
69 res = journal.currency and journal.currency.id or journal.company_id.currency_id.id
72 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
73 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
74 tt = type2journal.get(type_inv, 'sale')
75 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
77 raise osv.except_osv(_('No Analytic Journal!'),_("You must define an analytic journal of type '%s'!") % (tt,))
80 def _get_type(self, cr, uid, context=None):
83 return context.get('type', 'out_invoice')
85 def _reconciled(self, cr, uid, ids, name, args, context=None):
87 for inv in self.browse(cr, uid, ids, context=context):
88 res[inv.id] = self.test_paid(cr, uid, [inv.id])
89 if not res[inv.id] and inv.state == 'paid':
90 self.signal_open_test(cr, uid, [inv.id])
93 def _get_reference_type(self, cr, uid, context=None):
94 return [('none', _('Free Reference'))]
96 def _amount_residual(self, cr, uid, ids, name, args, context=None):
97 """Function of the field residua. It computes the residual amount (balance) for each invoice"""
102 currency_obj = self.pool.get('res.currency')
103 for invoice in self.browse(cr, SUPERUSER_ID, ids, context=context):
104 nb_inv_in_partial_rec = max_invoice_id = 0
105 result[invoice.id] = 0.0
107 for aml in invoice.move_id.line_id:
108 if aml.account_id.type in ('receivable','payable'):
109 if aml.currency_id and aml.currency_id.id == invoice.currency_id.id:
110 result[invoice.id] += aml.amount_residual_currency
112 ctx['date'] = aml.date
113 result[invoice.id] += currency_obj.compute(cr, uid, aml.company_id.currency_id.id, invoice.currency_id.id, aml.amount_residual, context=ctx)
115 if aml.reconcile_partial_id.line_partial_ids:
116 #we check if the invoice is partially reconciled and if there are other invoices
117 #involved in this partial reconciliation (and we sum these invoices)
118 for line in aml.reconcile_partial_id.line_partial_ids:
119 if line.invoice and invoice.type == line.invoice.type:
120 nb_inv_in_partial_rec += 1
121 #store the max invoice id as for this invoice we will make a balance instead of a simple division
122 max_invoice_id = max(max_invoice_id, line.invoice.id)
123 if nb_inv_in_partial_rec:
124 #if there are several invoices in a partial reconciliation, we split the residual by the number
125 #of invoice to have a sum of residual amounts that matches the partner balance
126 new_value = currency_obj.round(cr, uid, invoice.currency_id, result[invoice.id] / nb_inv_in_partial_rec)
127 if invoice.id == max_invoice_id:
128 #if it's the last the invoice of the bunch of invoices partially reconciled together, we make a
129 #balance to avoid rounding errors
130 result[invoice.id] = result[invoice.id] - ((nb_inv_in_partial_rec - 1) * new_value)
132 result[invoice.id] = new_value
134 #prevent the residual amount on the invoice to be less than 0
135 result[invoice.id] = max(result[invoice.id], 0.0)
138 # Give Journal Items related to the payment reconciled to this invoice
139 # Return ids of partial and total payments related to the selected invoices
140 def _get_lines(self, cr, uid, ids, name, arg, context=None):
142 for invoice in self.browse(cr, uid, ids, context=context):
145 if not invoice.move_id:
147 data_lines = [x for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
149 for line in data_lines:
151 if line.reconcile_id:
152 ids_line = line.reconcile_id.line_id
153 elif line.reconcile_partial_id:
154 ids_line = line.reconcile_partial_id.line_partial_ids
155 l = map(lambda x: x.id, ids_line)
156 partial_ids.append(line.id)
157 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
160 def _get_invoice_line(self, cr, uid, ids, context=None):
162 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
163 result[line.invoice_id.id] = True
166 def _get_invoice_tax(self, cr, uid, ids, context=None):
168 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
169 result[tax.invoice_id.id] = True
172 def _compute_lines(self, cr, uid, ids, name, args, context=None):
174 for invoice in self.browse(cr, uid, ids, context=context):
178 for m in invoice.move_id.line_id:
179 if m.account_id != invoice.account_id:
183 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
184 elif m.reconcile_partial_id:
185 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
186 lines += [x for x in temp_lines if x not in lines]
189 lines = filter(lambda x: x not in src, lines)
190 result[invoice.id] = lines
193 def _get_invoice_from_line(self, cr, uid, ids, context=None):
195 for line in self.pool.get('account.move.line').browse(cr, uid, ids, context=context):
196 if line.reconcile_partial_id:
197 for line2 in line.reconcile_partial_id.line_partial_ids:
198 move[line2.move_id.id] = True
199 if line.reconcile_id:
200 for line2 in line.reconcile_id.line_id:
201 move[line2.move_id.id] = True
204 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
207 def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
209 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids, context=context):
210 for line in r.line_partial_ids:
211 move[line.move_id.id] = True
212 for line in r.line_id:
213 move[line.move_id.id] = True
217 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
220 _name = "account.invoice"
221 _inherit = ['mail.thread']
222 _description = 'Invoice'
228 'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj.state == 'paid' and obj.type in ('out_invoice', 'out_refund'),
229 'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'open' and obj.type in ('out_invoice', 'out_refund'),
233 'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
234 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
235 'supplier_invoice_number': fields.char('Supplier Invoice Number', size=64, help="The reference of this invoice as provided by the supplier.", readonly=True, states={'draft':[('readonly',False)]}),
236 'type': fields.selection([
237 ('out_invoice','Customer Invoice'),
238 ('in_invoice','Supplier Invoice'),
239 ('out_refund','Customer Refund'),
240 ('in_refund','Supplier Refund'),
241 ],'Type', readonly=True, select=True, change_default=True, track_visibility='always'),
243 'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
244 'internal_number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
245 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
246 'reference_type': fields.selection(_get_reference_type, 'Payment Reference',
247 required=True, readonly=True, states={'draft':[('readonly',False)]}),
248 'comment': fields.text('Additional Information'),
250 'state': fields.selection([
252 ('proforma','Pro-forma'),
253 ('proforma2','Pro-forma'),
256 ('cancel','Cancelled'),
257 ],'Status', select=True, readonly=True, track_visibility='onchange',
258 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Invoice. \
259 \n* The \'Pro-forma\' when invoice is in Pro-forma status,invoice does not have an invoice number. \
260 \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. \
261 \n* The \'Paid\' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \
262 \n* The \'Cancelled\' status is used when user cancel invoice.'),
263 'sent': fields.boolean('Sent', readonly=True, help="It indicates that the invoice has been sent."),
264 'date_invoice': fields.date('Invoice Date', readonly=True, states={'draft':[('readonly',False)]}, select=True, help="Keep empty to use the current date"),
265 'date_due': fields.date('Due Date', readonly=True, states={'draft':[('readonly',False)]}, select=True,
266 help="If you use payment terms, the due date will be computed automatically at the generation "\
267 "of accounting entries. The payment term may compute several due dates, for example 50% now and 50% in 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."),
268 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
269 'payment_term': fields.many2one('account.payment.term', 'Payment Terms',readonly=True, states={'draft':[('readonly',False)]},
270 help="If you use payment terms, the due date will be computed automatically at the generation "\
271 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
272 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
273 'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(invoice) date.", readonly=True, states={'draft':[('readonly',False)]}),
275 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
276 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
277 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
279 'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, select=1, ondelete='restrict', help="Link to the automatically generated Journal Items."),
280 'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Subtotal', track_visibility='always',
282 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
283 'account.invoice.tax': (_get_invoice_tax, None, 20),
284 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
287 'amount_tax': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Tax',
289 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
290 'account.invoice.tax': (_get_invoice_tax, None, 20),
291 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
294 'amount_total': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Total',
296 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
297 'account.invoice.tax': (_get_invoice_tax, None, 20),
298 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
301 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
302 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]},
303 domain="[('type', 'in', {'out_invoice': ['sale'], 'out_refund': ['sale_refund'], 'in_refund': ['purchase_refund'], 'in_invoice': ['purchase']}.get(type, [])), ('company_id', '=', company_id)]"),
304 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
305 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
306 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',
308 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
309 'account.move.line': (_get_invoice_from_line, None, 50),
310 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
311 }, 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."),
312 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
313 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)]}),
314 'move_lines':fields.function(_get_lines, type='many2many', relation='account.move.line', string='Entry Lines'),
315 'residual': fields.function(_amount_residual, digits_compute=dp.get_precision('Account'), string='Balance',
317 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line','move_id'], 50),
318 'account.invoice.tax': (_get_invoice_tax, None, 50),
319 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 50),
320 'account.move.line': (_get_invoice_from_line, None, 50),
321 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
323 help="Remaining amount due."),
324 'payment_ids': fields.function(_compute_lines, relation='account.move.line', type="many2many", string='Payments', groups='base.group_user'),
325 'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
326 'user_id': fields.many2one('res.users', 'Salesperson', readonly=True, track_visibility='onchange', states={'draft':[('readonly',False)]}),
327 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]}),
328 'commercial_partner_id': fields.related('partner_id', 'commercial_partner_id', string='Commercial Entity', type='many2one',
329 relation='res.partner', store=True, readonly=True,
330 help="The commercial entity that will be used on Journal Entries for this invoice")
335 'journal_id': _get_journal,
336 'currency_id': _get_currency,
337 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
338 'reference_type': 'none',
340 'internal_number': False,
341 'user_id': lambda s, cr, u, c: u,
345 ('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
351 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
352 journal_obj = self.pool.get('account.journal')
356 if context.get('active_model', '') in ['res.partner'] and context.get('active_ids', False) and context['active_ids']:
357 partner = self.pool[context['active_model']].read(cr, uid, context['active_ids'], ['supplier','customer'])[0]
360 view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_tree')[1]
362 view_id = self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'account.invoice.tree')], limit=1)
364 if view_type == 'form':
365 if partner['supplier'] and not partner['customer']:
367 view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_supplier_form')[1]
369 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.supplier.form')], limit=1)
370 elif partner['customer'] and not partner['supplier']:
372 view_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'invoice_form')[1]
374 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.form')], limit=1)
375 if view_id and isinstance(view_id, (list, tuple)):
377 res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
379 type = context.get('journal_type', False)
380 for field in res['fields']:
381 if field == 'journal_id' and type:
382 journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
383 res['fields'][field]['selection'] = journal_select
385 doc = etree.XML(res['arch'])
387 if context.get('type', False):
388 for node in doc.xpath("//field[@name='partner_bank_id']"):
389 if context['type'] == 'in_refund':
390 node.set('domain', "[('partner_id.ref_companies', 'in', [company_id])]")
391 elif context['type'] == 'out_refund':
392 node.set('domain', "[('partner_id', '=', partner_id)]")
393 res['arch'] = etree.tostring(doc)
395 if view_type == 'search':
396 if context.get('type', 'in_invoice') in ('out_invoice', 'out_refund'):
397 for node in doc.xpath("//group[@name='extended filter']"):
399 res['arch'] = etree.tostring(doc)
401 if view_type == 'tree':
402 partner_string = _('Customer')
403 if context.get('type', 'out_invoice') in ('in_invoice', 'in_refund'):
404 partner_string = _('Supplier')
405 for node in doc.xpath("//field[@name='reference']"):
406 node.set('invisible', '0')
407 for node in doc.xpath("//field[@name='partner_id']"):
408 node.set('string', partner_string)
409 res['arch'] = etree.tostring(doc)
412 def get_log_context(self, cr, uid, context=None):
415 res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
416 view_id = res and res[1] or False
417 context['view_id'] = view_id
420 def invoice_print(self, cr, uid, ids, context=None):
422 This function prints the invoice and mark it as sent, so that we can see more easily the next step of the workflow
424 assert len(ids) == 1, 'This option should only be used for a single id at a time.'
425 self.write(cr, uid, ids, {'sent': True}, context=context)
428 'model': 'account.invoice',
429 'form': self.read(cr, uid, ids[0], context=context)
432 'type': 'ir.actions.report.xml',
433 'report_name': 'account.invoice',
438 def action_invoice_sent(self, cr, uid, ids, context=None):
440 This function opens a window to compose an email, with the edi invoice template message loaded by default
442 assert len(ids) == 1, 'This option should only be used for a single id at a time.'
443 ir_model_data = self.pool.get('ir.model.data')
445 template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1]
449 compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
451 compose_form_id = False
454 'default_model': 'account.invoice',
455 'default_res_id': ids[0],
456 'default_use_template': bool(template_id),
457 'default_template_id': template_id,
458 'default_composition_mode': 'comment',
459 'mark_invoice_as_sent': True,
462 'name': _('Compose Email'),
463 'type': 'ir.actions.act_window',
466 'res_model': 'mail.compose.message',
467 'views': [(compose_form_id, 'form')],
468 'view_id': compose_form_id,
473 def confirm_paid(self, cr, uid, ids, context=None):
476 self.write(cr, uid, ids, {'state':'paid'}, context=context)
479 def unlink(self, cr, uid, ids, context=None):
482 invoices = self.read(cr, uid, ids, ['state','internal_number'], context=context)
486 if t['state'] not in ('draft', 'cancel'):
487 raise openerp.exceptions.Warning(_('You cannot delete an invoice which is not draft or cancelled. You should refund it instead.'))
488 elif t['internal_number']:
489 raise openerp.exceptions.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.'))
491 unlink_ids.append(t['id'])
493 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
496 def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
497 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
498 partner_payment_term = False
501 fiscal_position = False
503 opt = [('uid', str(uid))]
506 opt.insert(0, ('id', partner_id))
507 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
509 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)):
510 property_obj = self.pool.get('ir.property')
511 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
512 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
514 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
516 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
517 rec_line_data = property_obj.read(cr,uid,rec_pro_id,['name','value_reference','res_id'])
518 pay_line_data = property_obj.read(cr,uid,pay_pro_id,['name','value_reference','res_id'])
519 rec_res_id = rec_line_data and rec_line_data[0].get('value_reference',False) and int(rec_line_data[0]['value_reference'].split(',')[1]) or False
520 pay_res_id = pay_line_data and pay_line_data[0].get('value_reference',False) and int(pay_line_data[0]['value_reference'].split(',')[1]) or False
521 if not rec_res_id and not pay_res_id:
522 raise osv.except_osv(_('Configuration Error!'),
523 _('Cannot find a chart of accounts for this company, you should create one.'))
524 account_obj = self.pool.get('account.account')
525 rec_obj_acc = account_obj.browse(cr, uid, [rec_res_id])
526 pay_obj_acc = account_obj.browse(cr, uid, [pay_res_id])
527 p.property_account_receivable = rec_obj_acc[0]
528 p.property_account_payable = pay_obj_acc[0]
530 if type in ('out_invoice', 'out_refund'):
531 acc_id = p.property_account_receivable.id
532 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
534 acc_id = p.property_account_payable.id
535 partner_payment_term = p.property_supplier_payment_term and p.property_supplier_payment_term.id or False
536 fiscal_position = p.property_account_position and p.property_account_position.id or False
538 bank_id = p.bank_ids[0].id
541 'account_id': acc_id,
542 'payment_term': partner_payment_term,
543 'fiscal_position': fiscal_position
547 if type in ('in_invoice', 'in_refund'):
548 result['value']['partner_bank_id'] = bank_id
550 if payment_term != partner_payment_term:
551 if partner_payment_term:
552 to_update = self.onchange_payment_term_date_invoice(
553 cr, uid, ids, partner_payment_term, date_invoice)
554 result['value'].update(to_update['value'])
556 result['value']['date_due'] = False
558 if partner_bank_id != bank_id:
559 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
560 result['value'].update(to_update['value'])
563 def onchange_journal_id(self, cr, uid, ids, journal_id=False, context=None):
566 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
567 currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id
568 company_id = journal.company_id.id
570 'currency_id': currency_id,
571 'company_id': company_id,
576 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
578 if isinstance(ids, (int, long)):
581 date_invoice = fields.date.context_today(self, cr, uid)
582 if not payment_term_id:
583 inv = self.browse(cr, uid, ids[0])
584 #To make sure the invoice due date should contain due date which is entered by user when there is no payment term defined
585 return {'value':{'date_due': inv.date_due and inv.date_due or date_invoice}}
586 pterm_list = self.pool.get('account.payment.term').compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
588 pterm_list = [line[0] for line in pterm_list]
590 res = {'value':{'date_due': pterm_list[-1]}}
592 raise osv.except_osv(_('Insufficient Data!'), _('The payment term of supplier does not have a payment term line.'))
595 def onchange_invoice_line(self, cr, uid, ids, lines):
598 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id=False):
601 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id, context=None):
602 #TODO: add the missing context parameter when forward-porting in trunk so we can remove
604 context = self.pool['res.users'].context_get(cr, uid)
608 obj_journal = self.pool.get('account.journal')
609 account_obj = self.pool.get('account.account')
610 inv_line_obj = self.pool.get('account.invoice.line')
612 if company_id and part_id and type:
614 partner_obj = self.pool.get('res.partner').browse(cr, uid, part_id, context=context)
616 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
617 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
618 property_obj = self.pool.get('ir.property')
619 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
620 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
623 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
625 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
627 rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value_reference','res_id'])
628 pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value_reference','res_id'])
629 rec_res_id = rec_line_data and rec_line_data[0].get('value_reference',False) and int(rec_line_data[0]['value_reference'].split(',')[1]) or False
630 pay_res_id = pay_line_data and pay_line_data[0].get('value_reference',False) and int(pay_line_data[0]['value_reference'].split(',')[1]) or False
632 if not rec_res_id and not pay_res_id:
633 raise self.pool.get('res.config.settings').get_config_warning(cr, _('Cannot find any chart of account: you can create a new one from %(menu:account.menu_account_config)s.'), context=context)
635 if type in ('out_invoice', 'out_refund'):
640 val= {'account_id': acc_id}
643 inv_obj = self.browse(cr,uid,ids)
644 for line in inv_obj[0].invoice_line:
646 if line.account_id.company_id.id != company_id:
647 result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
649 raise osv.except_osv(_('Configuration Error!'),
650 _('Cannot find a chart of account, you should create one from Settings\Configuration\Accounting menu.'))
651 inv_line_obj.write(cr, uid, [line.id], {'account_id': result_id[-1]})
654 for inv_line in invoice_line:
655 obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
656 if obj_l.company_id.id != company_id:
657 raise osv.except_osv(_('Configuration Error!'),
658 _('Invoice line account\'s company and invoice\'s company does not match.'))
661 if company_id and type:
663 'out_invoice': 'sale',
664 'out_refund': 'sale_refund',
665 'in_refund': 'purchase_refund',
666 'in_invoice': 'purchase',
668 journal_type = journal_mapping[type]
669 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
671 val['journal_id'] = journal_ids[0]
672 ir_values_obj = self.pool.get('ir.values')
673 res_journal_default = ir_values_obj.get(cr, uid, 'default', 'type=%s' % (type), ['account.invoice'])
674 for r in res_journal_default:
675 if r[1] == 'journal_id' and r[2] in journal_ids:
676 val['journal_id'] = r[2]
677 if not val.get('journal_id', False):
678 journal_type_map = dict(obj_journal._columns['type'].selection)
679 journal_type_label = self.pool['ir.translation']._get_source(cr, uid, None, ('code','selection'),
681 journal_type_map.get(journal_type))
682 raise osv.except_osv(_('Configuration Error!'),
683 _('Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Journals\Journals.') % ('"%s"' % journal_type_label))
684 dom = {'journal_id': [('id', 'in', journal_ids)]}
686 journal_ids = obj_journal.search(cr, uid, [])
688 return {'value': val, 'domain': dom}
690 # go from canceled state to draft state
691 def action_cancel_draft(self, cr, uid, ids, *args):
692 self.write(cr, uid, ids, {'state':'draft'})
693 self.delete_workflow(cr, uid, ids)
694 self.create_workflow(cr, uid, ids)
697 # ----------------------------------------
698 # Mail related methods
699 # ----------------------------------------
701 def _get_formview_action(self, cr, uid, id, context=None):
702 """ Update form view id of action to open the invoice """
703 action = super(account_invoice, self)._get_formview_action(cr, uid, id, context=context)
704 obj = self.browse(cr, uid, id, context=context)
705 if obj.type == 'in_invoice':
706 model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
708 'views': [(view_id, 'form')],
711 model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
713 'views': [(view_id, 'form')],
720 # return the ids of the move lines which has the same account than the invoice
722 def move_line_id_payment_get(self, cr, uid, ids, *args):
723 if not ids: return []
724 result = self.move_line_id_payment_gets(cr, uid, ids, *args)
725 return result.get(ids[0], [])
727 def move_line_id_payment_gets(self, cr, uid, ids, *args):
729 if not ids: return res
730 cr.execute('SELECT i.id, l.id '\
731 'FROM account_move_line l '\
732 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
734 'AND l.account_id=i.account_id',
736 for r in cr.fetchall():
737 res.setdefault(r[0], [])
738 res[r[0]].append( r[1] )
741 def copy(self, cr, uid, id, default=None, context=None):
742 default = default or {}
748 'internal_number': False,
752 if 'date_invoice' not in default:
756 if 'date_due' not in default:
760 return super(account_invoice, self).copy(cr, uid, id, default, context)
762 def test_paid(self, cr, uid, ids, *args):
763 res = self.move_line_id_payment_get(cr, uid, ids)
768 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
769 ok = ok and bool(cr.fetchone()[0])
772 def button_reset_taxes(self, cr, uid, ids, context=None):
776 ait_obj = self.pool.get('account.invoice.tax')
778 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s AND manual is False", (id,))
779 partner = self.browse(cr, uid, id, context=ctx).partner_id
781 ctx.update({'lang': partner.lang})
782 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
783 ait_obj.create(cr, uid, taxe)
784 # Update the stored value (fields.function), so we write to trigger recompute
785 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
788 def button_compute(self, cr, uid, ids, context=None, set_total=False):
789 self.button_reset_taxes(cr, uid, ids, context)
790 for inv in self.browse(cr, uid, ids, context=context):
792 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
795 def _convert_ref(self, cr, uid, ref):
796 return (ref or '').replace('/','')
798 def _get_analytic_lines(self, cr, uid, id, context=None):
801 inv = self.browse(cr, uid, id)
802 cur_obj = self.pool.get('res.currency')
804 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
805 if inv.type in ('out_invoice', 'in_refund'):
810 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id, context=context)
812 if il['account_analytic_id']:
813 if inv.type in ('in_invoice', 'in_refund'):
816 ref = self._convert_ref(cr, uid, inv.number)
817 if not inv.journal_id.analytic_journal_id:
818 raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (inv.journal_id.name,))
819 il['analytic_lines'] = [(0,0, {
821 'date': inv['date_invoice'],
822 'account_id': il['account_analytic_id'],
823 'unit_amount': il['quantity'],
824 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
825 'product_id': il['product_id'],
826 'product_uom_id': il['uos_id'],
827 'general_account_id': il['account_id'],
828 'journal_id': inv.journal_id.analytic_journal_id.id,
833 def action_date_assign(self, cr, uid, ids, *args):
834 for inv in self.browse(cr, uid, ids):
835 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
836 if res and res['value']:
837 self.write(cr, uid, [inv.id], res['value'])
840 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
841 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
842 Hook method to be overridden in additional modules to verify and possibly alter the
843 move lines to be created by an invoice, for special cases.
844 :param invoice_browse: browsable record of the invoice that is generating the move lines
845 :param move_lines: list of dictionaries with the account.move.lines (as for create())
846 :return: the (possibly updated) final move_lines to create for this invoice
850 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
851 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id
853 for tax in compute_taxes.values():
854 ait_obj.create(cr, uid, tax)
857 for tax in inv.tax_line:
860 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
862 if not key in compute_taxes:
863 raise osv.except_osv(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
864 base = compute_taxes[key]['base']
865 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
866 if float_compare(abs(base - tax.base), company_currency.rounding, precision_digits=precision) == 1:
867 raise osv.except_osv(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
868 for key in compute_taxes:
869 if not key in tax_key:
870 raise osv.except_osv(_('Warning!'), _('Taxes are missing!\nClick on compute button.'))
872 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None):
877 cur_obj = self.pool.get('res.currency')
878 for i in invoice_move_lines:
879 if inv.currency_id.id != company_currency:
880 context.update({'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)})
881 i['currency_id'] = inv.currency_id.id
882 i['amount_currency'] = i['price']
883 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
884 company_currency, i['price'],
887 i['amount_currency'] = False
888 i['currency_id'] = False
890 if inv.type in ('out_invoice','in_refund'):
892 total_currency += i['amount_currency'] or i['price']
893 i['price'] = - i['price']
896 total_currency -= i['amount_currency'] or i['price']
897 return total, total_currency, invoice_move_lines
899 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
900 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
901 will be grouped together if the journal has the 'group line' option. Of course a module
902 can add fields to invoice lines that would need to be tested too before merging lines
904 return "%s-%s-%s-%s-%s"%(
905 invoice_line['account_id'],
906 invoice_line.get('tax_code_id',"False"),
907 invoice_line.get('product_id',"False"),
908 invoice_line.get('analytic_account_id',"False"),
909 invoice_line.get('date_maturity',"False"))
911 def group_lines(self, cr, uid, iml, line, inv):
912 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
913 if inv.journal_id.group_invoice_lines:
916 tmp = self.inv_line_characteristic_hashcode(inv, l)
919 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
920 line2[tmp]['debit'] = (am > 0) and am or 0.0
921 line2[tmp]['credit'] = (am < 0) and -am or 0.0
922 line2[tmp]['tax_amount'] += l['tax_amount']
923 line2[tmp]['analytic_lines'] += l['analytic_lines']
927 for key, val in line2.items():
928 line.append((0,0,val))
931 def action_move_create(self, cr, uid, ids, context=None):
932 """Creates invoice related analytics and financial move lines"""
933 ait_obj = self.pool.get('account.invoice.tax')
934 cur_obj = self.pool.get('res.currency')
935 period_obj = self.pool.get('account.period')
936 payment_term_obj = self.pool.get('account.payment.term')
937 journal_obj = self.pool.get('account.journal')
938 move_obj = self.pool.get('account.move')
941 for inv in self.browse(cr, uid, ids, context=context):
942 if not inv.journal_id.sequence_id:
943 raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.'))
944 if not inv.invoice_line:
945 raise osv.except_osv(_('No Invoice Lines!'), _('Please create some invoice lines.'))
950 ctx.update({'lang': inv.partner_id.lang})
951 if not inv.date_invoice:
952 self.write(cr, uid, [inv.id], {'date_invoice': fields.date.context_today(self, cr, uid, context=context)}, context=ctx)
954 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
955 # create the analytical lines
956 # one move line per invoice line
957 iml = self._get_analytic_lines(cr, uid, inv.id, context=ctx)
958 # check if taxes are all computed
959 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
960 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
962 # I disabled the check_total feature
963 if self.pool['res.users'].has_group(cr, uid, 'account.group_supplier_inv_check_total'):
964 if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)):
965 raise osv.except_osv(_('Bad Total!'), _('Please verify the price of the invoice!\nThe encoded total does not match the computed total.'))
968 total_fixed = total_percent = 0
969 for line in inv.payment_term.line_ids:
970 if line.value == 'fixed':
971 total_fixed += line.value_amount
972 if line.value == 'procent':
973 total_percent += line.value_amount
974 total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
975 if (total_fixed + total_percent) > 100:
976 raise osv.except_osv(_('Error!'), _("Cannot create the invoice.\nThe 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'."))
978 # one move line per tax line
979 iml += ait_obj.move_line_get(cr, uid, inv.id)
982 if inv.type in ('in_invoice', 'in_refund'):
984 entry_type = 'journal_pur_voucher'
985 if inv.type == 'in_refund':
986 entry_type = 'cont_voucher'
988 ref = self._convert_ref(cr, uid, inv.number)
989 entry_type = 'journal_sale_vou'
990 if inv.type == 'out_refund':
991 entry_type = 'cont_voucher'
993 diff_currency_p = inv.currency_id.id <> company_currency
994 # create one move line for the total and possibly adjust the other lines amount
997 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml, context=ctx)
998 acc_id = inv.account_id.id
1000 name = inv['name'] or inv['supplier_invoice_number'] or '/'
1002 if inv.payment_term:
1003 totlines = payment_term_obj.compute(cr,
1004 uid, inv.payment_term.id, total, inv.date_invoice or False, context=ctx)
1006 res_amount_currency = total_currency
1008 ctx.update({'date': inv.date_invoice})
1010 if inv.currency_id.id != company_currency:
1011 amount_currency = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, t[1], context=ctx)
1013 amount_currency = False
1015 # last line add the diff
1016 res_amount_currency -= amount_currency or 0
1018 if i == len(totlines):
1019 amount_currency += res_amount_currency
1025 'account_id': acc_id,
1026 'date_maturity': t[0],
1027 'amount_currency': diff_currency_p \
1028 and amount_currency or False,
1029 'currency_id': diff_currency_p \
1030 and inv.currency_id.id or False,
1038 'account_id': acc_id,
1039 'date_maturity': inv.date_due or False,
1040 'amount_currency': diff_currency_p \
1041 and total_currency or False,
1042 'currency_id': diff_currency_p \
1043 and inv.currency_id.id or False,
1047 part = self.pool.get("res.partner")._find_accounting_partner(inv.partner_id)
1049 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part.id, inv.date_invoice, context=ctx)),iml)
1051 line = self.group_lines(cr, uid, iml, line, inv)
1053 journal_id = inv.journal_id.id
1054 journal = journal_obj.browse(cr, uid, journal_id, context=ctx)
1055 if journal.centralisation:
1056 raise osv.except_osv(_('User Error!'),
1057 _('You cannot create an invoice on a centralized journal. Uncheck the centralized counterpart box in the related journal from the configuration menu.'))
1059 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
1062 'ref': inv.reference and inv.reference or inv.name,
1064 'journal_id': journal_id,
1065 'date': inv.date_invoice,
1066 'narration': inv.comment,
1067 'company_id': inv.company_id.id,
1069 period_id = inv.period_id and inv.period_id.id or False
1070 ctx.update(company_id=inv.company_id.id)
1072 period_ids = period_obj.find(cr, uid, inv.date_invoice, context=ctx)
1073 period_id = period_ids and period_ids[0] or False
1075 move['period_id'] = period_id
1077 i[2]['period_id'] = period_id
1079 ctx.update(invoice=inv)
1080 move_id = move_obj.create(cr, uid, move, context=ctx)
1081 new_move_name = move_obj.browse(cr, uid, move_id, context=ctx).name
1082 # make the invoice point to that move
1083 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name}, context=ctx)
1084 # Pass invoice in context in method post: used if you want to get the same
1085 # account move reference when creating the same invoice after a cancelled one:
1086 move_obj.post(cr, uid, [move_id], context=ctx)
1087 self._log_event(cr, uid, ids)
1090 def invoice_validate(self, cr, uid, ids, context=None):
1091 self.write(cr, uid, ids, {'state':'open'}, context=context)
1094 def line_get_convert(self, cr, uid, x, part, date, context=None):
1096 'date_maturity': x.get('date_maturity', False),
1098 'name': x['name'][:64],
1100 'debit': x['price']>0 and x['price'],
1101 'credit': x['price']<0 and -x['price'],
1102 'account_id': x['account_id'],
1103 'analytic_lines': x.get('analytic_lines', []),
1104 'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
1105 'currency_id': x.get('currency_id', False),
1106 'tax_code_id': x.get('tax_code_id', False),
1107 'tax_amount': x.get('tax_amount', False),
1108 'ref': x.get('ref', False),
1109 'quantity': x.get('quantity',1.00),
1110 'product_id': x.get('product_id', False),
1111 'product_uom_id': x.get('uos_id', False),
1112 'analytic_account_id': x.get('account_analytic_id', False),
1115 def action_number(self, cr, uid, ids, context=None):
1118 #TODO: not correct fix but required a frech values before reading it.
1119 self.write(cr, uid, ids, {})
1121 for obj_inv in self.browse(cr, uid, ids, context=context):
1122 invtype = obj_inv.type
1123 number = obj_inv.number
1124 move_id = obj_inv.move_id and obj_inv.move_id.id or False
1125 reference = obj_inv.reference or ''
1127 self.write(cr, uid, ids, {'internal_number': number})
1129 if invtype in ('in_invoice', 'in_refund'):
1131 ref = self._convert_ref(cr, uid, number)
1135 ref = self._convert_ref(cr, uid, number)
1137 cr.execute('UPDATE account_move SET ref=%s ' \
1138 'WHERE id=%s AND (ref is null OR ref = \'\')',
1140 cr.execute('UPDATE account_move_line SET ref=%s ' \
1141 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
1143 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
1144 'FROM account_move_line ' \
1145 'WHERE account_move_line.move_id = %s ' \
1146 'AND account_analytic_line.move_id = account_move_line.id',
1150 def action_cancel(self, cr, uid, ids, context=None):
1153 account_move_obj = self.pool.get('account.move')
1154 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
1155 move_ids = [] # ones that we will need to remove
1158 move_ids.append(i['move_id'][0])
1159 if i['payment_ids']:
1160 account_move_line_obj = self.pool.get('account.move.line')
1161 pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
1162 for move_line in pay_ids:
1163 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
1164 raise osv.except_osv(_('Error!'), _('You cannot cancel an invoice which is partially paid. You need to unreconcile related payment entries first.'))
1166 # First, set the invoices as cancelled and detach the move ids
1167 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
1169 # second, invalidate the move(s)
1170 account_move_obj.button_cancel(cr, uid, move_ids, context=context)
1171 # delete the move this invoice was pointing to
1172 # Note that the corresponding move_lines and move_reconciles
1173 # will be automatically deleted too
1174 account_move_obj.unlink(cr, uid, move_ids, context=context)
1175 self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
1180 def list_distinct_taxes(self, cr, uid, ids):
1181 invoices = self.browse(cr, uid, ids)
1183 for inv in invoices:
1184 for tax in inv.tax_line:
1185 if not tax['name'] in taxes:
1186 taxes[tax['name']] = {'name': tax['name']}
1187 return taxes.values()
1189 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
1190 #TODO: implement messages system
1193 def name_get(self, cr, uid, ids, context=None):
1197 'out_invoice': _('Invoice'),
1198 'in_invoice': _('Supplier Invoice'),
1199 'out_refund': _('Refund'),
1200 'in_refund': _('Supplier Refund'),
1202 return [(r['id'], '%s %s' % (r['number'] or types[r['type']], r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
1204 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1211 ids = self.search(cr, user, [('number','=',name)] + args, limit=limit, context=context)
1213 ids = self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context)
1214 return self.name_get(cr, user, ids, context)
1216 def _refund_cleanup_lines(self, cr, uid, lines, context=None):
1217 """Convert records to dict of values suitable for one2many line creation
1219 :param list(browse_record) lines: records to convert
1220 :return: list of command tuple for one2many line creation [(0, 0, dict of valueis), ...]
1225 for field in line._all_columns.keys():
1226 if line._all_columns[field].column._type == 'many2one':
1227 clean_line[field] = line[field].id
1228 elif line._all_columns[field].column._type not in ['many2many','one2many']:
1229 clean_line[field] = line[field]
1230 elif field == 'invoice_line_tax_id':
1232 for tax in line[field]:
1233 tax_list.append(tax.id)
1234 clean_line[field] = [(6,0, tax_list)]
1235 clean_lines.append(clean_line)
1236 return map(lambda x: (0,0,x), clean_lines)
1238 def _prepare_refund(self, cr, uid, invoice, date=None, period_id=None, description=None, journal_id=None, context=None):
1239 """Prepare the dict of values to create the new refund from the invoice.
1240 This method may be overridden to implement custom
1241 refund generation (making sure to call super() to establish
1242 a clean extension chain).
1244 :param integer invoice_id: id of the invoice to refund
1245 :param dict invoice: read of the invoice to refund
1246 :param string date: refund creation date from the wizard
1247 :param integer period_id: force account.period from the wizard
1248 :param string description: description of the refund from the wizard
1249 :param integer journal_id: account.journal from the wizard
1250 :return: dict of value to create() the refund
1252 obj_journal = self.pool.get('account.journal')
1255 'out_invoice': 'out_refund', # Customer Invoice
1256 'in_invoice': 'in_refund', # Supplier Invoice
1257 'out_refund': 'out_invoice', # Customer Refund
1258 'in_refund': 'in_invoice', # Supplier Refund
1261 for field in ['name', 'reference', 'comment', 'date_due', 'partner_id', 'company_id',
1262 'account_id', 'currency_id', 'payment_term', 'user_id', 'fiscal_position']:
1263 if invoice._all_columns[field].column._type == 'many2one':
1264 invoice_data[field] = invoice[field].id
1266 invoice_data[field] = invoice[field] if invoice[field] else False
1268 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice.invoice_line, context=context)
1270 tax_lines = filter(lambda l: l['manual'], invoice.tax_line)
1271 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines, context=context)
1273 refund_journal_ids = [journal_id]
1274 elif invoice['type'] == 'in_invoice':
1275 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')], context=context)
1277 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')], context=context)
1280 date = fields.date.context_today(self, cr, uid, context=context)
1281 invoice_data.update({
1282 'type': type_dict[invoice['type']],
1283 'date_invoice': date,
1286 'invoice_line': invoice_lines,
1287 'tax_line': tax_lines,
1288 'journal_id': refund_journal_ids and refund_journal_ids[0] or False,
1291 invoice_data['period_id'] = period_id
1293 invoice_data['name'] = description
1296 def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None, context=None):
1298 for invoice in self.browse(cr, uid, ids, context=context):
1299 invoice = self._prepare_refund(cr, uid, invoice,
1301 period_id=period_id,
1302 description=description,
1303 journal_id=journal_id,
1305 # create the new invoice
1306 new_ids.append(self.create(cr, uid, invoice, context=context))
1310 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=''):
1313 #TODO check if we can use different period for payment and the writeoff line
1314 assert len(ids)==1, "Can only pay one invoice at a time."
1315 invoice = self.browse(cr, uid, ids[0], context=context)
1316 src_account_id = invoice.account_id.id
1317 # Take the seq as name for move
1318 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1319 direction = types[invoice.type]
1320 #take the choosen date
1321 if 'date_p' in context and context['date_p']:
1322 date=context['date_p']
1324 date=time.strftime('%Y-%m-%d')
1326 # Take the amount in currency and the currency of the payment
1327 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1328 amount_currency = context['amount_currency']
1329 currency_id = context['currency_id']
1331 amount_currency = False
1334 pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1335 if invoice.type in ('in_invoice', 'out_invoice'):
1336 if pay_journal['type'] == 'bank':
1337 entry_type = 'bank_pay_voucher' # Bank payment
1339 entry_type = 'pay_voucher' # Cash payment
1341 entry_type = 'cont_voucher'
1342 if invoice.type in ('in_invoice', 'in_refund'):
1343 ref = invoice.reference
1345 ref = self._convert_ref(cr, uid, invoice.number)
1346 partner = self.pool['res.partner']._find_accounting_partner(invoice.partner_id)
1347 # Pay attention to the sign for both debit/credit AND amount_currency
1349 'debit': direction * pay_amount>0 and direction * pay_amount,
1350 'credit': direction * pay_amount<0 and - direction * pay_amount,
1351 'account_id': src_account_id,
1352 'partner_id': partner.id,
1355 'currency_id':currency_id,
1356 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1357 'company_id': invoice.company_id.id,
1360 'debit': direction * pay_amount<0 and - direction * pay_amount,
1361 'credit': direction * pay_amount>0 and direction * pay_amount,
1362 'account_id': pay_account_id,
1363 'partner_id': partner.id,
1366 'currency_id':currency_id,
1367 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1368 'company_id': invoice.company_id.id,
1372 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1376 lines = [(0, 0, l1), (0, 0, l2)]
1377 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1378 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1382 line = self.pool.get('account.move.line')
1383 move_ids = [move_id,]
1385 move_ids.append(invoice.move_id.id)
1386 cr.execute('SELECT id FROM account_move_line '\
1387 'WHERE move_id IN %s',
1388 ((move_id, invoice.move_id.id),))
1389 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1390 for l in lines+invoice.payment_ids:
1391 if l.account_id.id == src_account_id:
1392 line_ids.append(l.id)
1393 total += (l.debit or 0.0) - (l.credit or 0.0)
1395 inv_id, name = self.name_get(cr, uid, [invoice.id], context=context)[0]
1396 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1397 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1399 code = invoice.currency_id.symbol
1400 # TODO: use currency's formatting function
1401 msg = _("Invoice partially paid: %s%s of %s%s (%s%s remaining).") % \
1402 (pay_amount, code, invoice.amount_total, code, total, code)
1403 self.message_post(cr, uid, [inv_id], body=msg, context=context)
1404 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1406 # Update the stored value (fields.function), so we write to trigger recompute
1407 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1411 class account_invoice_line(osv.osv):
1413 def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1415 tax_obj = self.pool.get('account.tax')
1416 cur_obj = self.pool.get('res.currency')
1417 for line in self.browse(cr, uid, ids):
1418 price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1419 taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity, product=line.product_id, partner=line.invoice_id.partner_id)
1420 res[line.id] = taxes['total']
1422 cur = line.invoice_id.currency_id
1423 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1426 def _price_unit_default(self, cr, uid, context=None):
1429 if context.get('check_total', False):
1430 t = context['check_total']
1431 for l in context.get('invoice_line', {}):
1432 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1433 tax_obj = self.pool.get('account.tax')
1434 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1435 t = t - (p * l[2].get('quantity'))
1436 taxes = l[2].get('invoice_line_tax_id')
1437 if len(taxes[0]) >= 3 and taxes[0][2]:
1438 taxes = tax_obj.browse(cr, uid, list(taxes[0][2]))
1439 for tax in tax_obj.compute_all(cr, uid, taxes, p,l[2].get('quantity'), l[2].get('product_id', False), context.get('partner_id', False))['taxes']:
1440 t = t - tax['amount']
1444 _name = "account.invoice.line"
1445 _description = "Invoice Line"
1446 _order = "invoice_id,sequence,id"
1448 'name': fields.text('Description', required=True),
1449 'origin': fields.char('Source Document', size=256, help="Reference of the document that produced this invoice."),
1450 'sequence': fields.integer('Sequence', help="Gives the sequence of this line when displaying the invoice."),
1451 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1452 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null', select=True),
1453 'product_id': fields.many2one('product.product', 'Product', ondelete='set null', select=True),
1454 'account_id': fields.many2one('account.account', 'Account', required=True, domain=[('type','<>','view'), ('type', '<>', 'closed')], help="The income or expense account related to the selected product."),
1455 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
1456 'price_subtotal': fields.function(_amount_line, string='Amount', type="float",
1457 digits_compute= dp.get_precision('Account'), store=True),
1458 'quantity': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
1459 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
1460 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1461 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1462 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1463 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1466 def _default_account_id(self, cr, uid, context=None):
1467 # XXX this gets the default account for the user's company,
1468 # it should get the default account for the invoice's company
1469 # however, the invoice's company does not reach this point
1472 if context.get('type') in ('out_invoice','out_refund'):
1473 prop = self.pool.get('ir.property').get(cr, uid, 'property_account_income_categ', 'product.category', context=context)
1475 prop = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category', context=context)
1476 return prop and prop.id or False
1481 'price_unit': _price_unit_default,
1482 'account_id': _default_account_id,
1486 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1489 res = super(account_invoice_line,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
1490 if context.get('type', False):
1491 doc = etree.XML(res['arch'])
1492 for node in doc.xpath("//field[@name='product_id']"):
1493 if context['type'] in ('in_invoice', 'in_refund'):
1494 node.set('domain', "[('purchase_ok', '=', True)]")
1496 node.set('domain', "[('sale_ok', '=', True)]")
1497 res['arch'] = etree.tostring(doc)
1500 def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
1503 company_id = company_id if company_id != None else context.get('company_id',False)
1504 context = dict(context)
1505 context.update({'company_id': company_id, 'force_company': company_id})
1507 raise osv.except_osv(_('No Partner Defined!'),_("You must first select a partner!") )
1509 if type in ('in_invoice', 'in_refund'):
1510 return {'value': {}, 'domain':{'product_uom':[]}}
1512 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1513 part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
1514 fpos_obj = self.pool.get('account.fiscal.position')
1515 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
1518 context.update({'lang': part.lang})
1520 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1522 result['name'] = res.partner_ref
1523 if type in ('out_invoice','out_refund'):
1524 a = res.property_account_income.id
1526 a = res.categ_id.property_account_income_categ.id
1528 a = res.property_account_expense.id
1530 a = res.categ_id.property_account_expense_categ.id
1531 a = fpos_obj.map_account(cr, uid, fpos, a)
1533 result['account_id'] = a
1535 if type in ('out_invoice', 'out_refund'):
1536 taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
1537 if res.description_sale:
1538 result['name'] += '\n'+res.description_sale
1540 taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
1541 if res.description_purchase:
1542 result['name'] += '\n'+res.description_purchase
1544 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1546 if type in ('in_invoice', 'in_refund'):
1547 result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
1549 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1551 result['uos_id'] = uom_id or res.uom_id.id
1553 domain = {'uos_id':[('category_id','=',res.uom_id.category_id.id)]}
1555 res_final = {'value':result, 'domain':domain}
1557 if not company_id or not currency_id:
1560 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
1561 currency = self.pool.get('res.currency').browse(cr, uid, currency_id, context=context)
1563 if company.currency_id.id != currency.id:
1564 if type in ('in_invoice', 'in_refund'):
1565 res_final['value']['price_unit'] = res.standard_price
1566 new_price = res_final['value']['price_unit'] * currency.rate
1567 res_final['value']['price_unit'] = new_price
1569 if result['uos_id'] and result['uos_id'] != res.uom_id.id:
1570 selected_uom = self.pool.get('product.uom').browse(cr, uid, result['uos_id'], context=context)
1571 new_price = self.pool.get('product.uom')._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uos_id'])
1572 res_final['value']['price_unit'] = new_price
1575 def uos_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
1578 company_id = company_id if company_id != None else context.get('company_id',False)
1579 context = dict(context)
1580 context.update({'company_id': company_id})
1582 res = self.product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, currency_id, context=context)
1584 res['value']['price_unit'] = 0.0
1586 prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
1587 prod_uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1588 if prod.uom_id.category_id.id != prod_uom.category_id.id:
1590 'title': _('Warning!'),
1591 'message': _('The selected unit of measure is not compatible with the unit of measure of the product.')
1593 res['value'].update({'uos_id': prod.uom_id.id})
1594 return {'value': res['value'], 'warning': warning}
1597 def move_line_get(self, cr, uid, invoice_id, context=None):
1599 tax_obj = self.pool.get('account.tax')
1600 cur_obj = self.pool.get('res.currency')
1603 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1604 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
1605 for line in inv.invoice_line:
1606 mres = self.move_line_get_item(cr, uid, line, context)
1607 mres['invl_id'] = line.id
1609 tax_code_found= False
1610 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1611 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1612 line.quantity, line.product_id,
1613 inv.partner_id)['taxes']:
1615 if inv.type in ('out_invoice', 'in_invoice'):
1616 tax_code_id = tax['base_code_id']
1617 tax_amount = line.price_subtotal * tax['base_sign']
1619 tax_code_id = tax['ref_base_code_id']
1620 tax_amount = line.price_subtotal * tax['ref_base_sign']
1625 res.append(self.move_line_get_item(cr, uid, line, context))
1626 res[-1]['price'] = 0.0
1627 res[-1]['account_analytic_id'] = False
1628 elif not tax_code_id:
1630 tax_code_found = True
1632 res[-1]['tax_code_id'] = tax_code_id
1633 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1636 def move_line_get_item(self, cr, uid, line, context=None):
1639 'name': line.name.split('\n')[0][:64],
1640 'price_unit':line.price_unit,
1641 'quantity':line.quantity,
1642 'price':line.price_subtotal,
1643 'account_id':line.account_id.id,
1644 'product_id':line.product_id.id,
1645 'uos_id':line.uos_id.id,
1646 'account_analytic_id':line.account_analytic_id.id,
1647 'taxes':line.invoice_line_tax_id,
1650 # Set the tax field according to the account and the fiscal position
1652 def onchange_account_id(self, cr, uid, ids, product_id, partner_id, inv_type, fposition_id, account_id):
1656 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1657 account = self.pool.get('account.account').browse(cr, uid, account_id)
1659 taxes = account.tax_ids
1660 unique_tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1662 product_change_result = self.product_id_change(cr, uid, ids, product_id, False, type=inv_type,
1663 partner_id=partner_id, fposition_id=fposition_id,
1664 company_id=account.company_id.id)
1665 if product_change_result and 'value' in product_change_result and 'invoice_line_tax_id' in product_change_result['value']:
1666 unique_tax_ids = product_change_result['value']['invoice_line_tax_id']
1667 return {'value':{'invoice_line_tax_id': unique_tax_ids}}
1670 class account_invoice_tax(osv.osv):
1671 _name = "account.invoice.tax"
1672 _description = "Invoice Tax"
1674 def _count_factor(self, cr, uid, ids, name, args, context=None):
1676 for invoice_tax in self.browse(cr, uid, ids, context=context):
1677 res[invoice_tax.id] = {
1681 if invoice_tax.amount <> 0.0:
1682 factor_tax = invoice_tax.tax_amount / invoice_tax.amount
1683 res[invoice_tax.id]['factor_tax'] = factor_tax
1685 if invoice_tax.base <> 0.0:
1686 factor_base = invoice_tax.base_amount / invoice_tax.base
1687 res[invoice_tax.id]['factor_base'] = factor_base
1692 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1693 'name': fields.char('Tax Description', size=64, required=True),
1694 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1695 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
1696 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1697 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1698 'manual': fields.boolean('Manual'),
1699 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1700 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1701 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1702 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1703 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1704 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
1705 'factor_base': fields.function(_count_factor, string='Multipication factor for Base code', type='float', multi="all"),
1706 'factor_tax': fields.function(_count_factor, string='Multipication factor Tax code', type='float', multi="all")
1709 def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1710 cur_obj = self.pool.get('res.currency')
1711 company_obj = self.pool.get('res.company')
1712 company_currency = False
1715 factor = self.read(cr, uid, ids[0], ['factor_base'])['factor_base']
1717 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1718 if currency_id and company_currency:
1719 base = cur_obj.compute(cr, uid, currency_id, company_currency, base*factor, context={'date': date_invoice or fields.date.context_today(self, cr, uid)}, round=False)
1720 return {'value': {'base_amount':base}}
1722 def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1723 cur_obj = self.pool.get('res.currency')
1724 company_obj = self.pool.get('res.company')
1725 company_currency = False
1728 factor = self.read(cr, uid, ids[0], ['factor_tax'])['factor_tax']
1730 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1731 if currency_id and company_currency:
1732 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount*factor, context={'date': date_invoice or fields.date.context_today(self, cr, uid)}, round=False)
1733 return {'value': {'tax_amount': amount}}
1741 def compute(self, cr, uid, invoice_id, context=None):
1743 tax_obj = self.pool.get('account.tax')
1744 cur_obj = self.pool.get('res.currency')
1745 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1746 cur = inv.currency_id
1747 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
1748 for line in inv.invoice_line:
1749 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, line.product_id, inv.partner_id)['taxes']:
1751 val['invoice_id'] = inv.id
1752 val['name'] = tax['name']
1753 val['amount'] = tax['amount']
1754 val['manual'] = False
1755 val['sequence'] = tax['sequence']
1756 val['base'] = cur_obj.round(cr, uid, cur, tax['price_unit'] * line['quantity'])
1758 if inv.type in ('out_invoice','in_invoice'):
1759 val['base_code_id'] = tax['base_code_id']
1760 val['tax_code_id'] = tax['tax_code_id']
1761 val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
1762 val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
1763 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1764 val['account_analytic_id'] = tax['account_analytic_collected_id']
1766 val['base_code_id'] = tax['ref_base_code_id']
1767 val['tax_code_id'] = tax['ref_tax_code_id']
1768 val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
1769 val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
1770 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1771 val['account_analytic_id'] = tax['account_analytic_paid_id']
1773 # If the taxes generate moves on the same financial account as the invoice line
1774 # and no default analytic account is defined at the tax level, propagate the
1775 # analytic account from the invoice line to the tax line. This is necessary
1776 # in situations were (part of) the taxes cannot be reclaimed,
1777 # to ensure the tax move is allocated to the proper analytic account.
1778 if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
1779 val['account_analytic_id'] = line.account_analytic_id.id
1781 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1782 if not key in tax_grouped:
1783 tax_grouped[key] = val
1785 tax_grouped[key]['amount'] += val['amount']
1786 tax_grouped[key]['base'] += val['base']
1787 tax_grouped[key]['base_amount'] += val['base_amount']
1788 tax_grouped[key]['tax_amount'] += val['tax_amount']
1790 for t in tax_grouped.values():
1791 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1792 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1793 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1794 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1797 def move_line_get(self, cr, uid, invoice_id):
1799 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1800 for t in cr.dictfetchall():
1801 if not t['amount'] \
1802 and not t['tax_code_id'] \
1803 and not t['tax_amount']:
1808 'price_unit': t['amount'],
1810 'price': t['amount'] or 0.0,
1811 'account_id': t['account_id'],
1812 'tax_code_id': t['tax_code_id'],
1813 'tax_amount': t['tax_amount'],
1814 'account_analytic_id': t['account_analytic_id'],
1819 class res_partner(osv.osv):
1820 """ Inherits partner and adds invoice information in the partner form """
1821 _inherit = 'res.partner'
1823 'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1826 def _find_accounting_partner(self, partner):
1828 Find the partner for which the accounting entries will be created
1830 return partner.commercial_partner_id
1832 def copy(self, cr, uid, id, default=None, context=None):
1833 default = default or {}
1834 default.update({'invoice_ids' : []})
1835 return super(res_partner, self).copy(cr, uid, id, default, context)
1838 class mail_compose_message(osv.Model):
1839 _inherit = 'mail.compose.message'
1841 def send_mail(self, cr, uid, ids, context=None):
1842 context = context or {}
1843 if context.get('default_model') == 'account.invoice' and context.get('default_res_id') and context.get('mark_invoice_as_sent'):
1844 context = dict(context, mail_post_autofollow=True)
1845 self.pool.get('account.invoice').write(cr, uid, [context['default_res_id']], {'sent': True}, context=context)
1846 self.pool.get('account.invoice').message_post(cr, uid, [context['default_res_id']], body=_("Invoice sent"), context=context)
1847 return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
1849 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: