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.translate import _
30 class account_invoice(osv.osv):
31 def _amount_all(self, cr, uid, ids, name, args, context=None):
33 for invoice in self.browse(cr, uid, ids, context=context):
35 'amount_untaxed': 0.0,
39 for line in invoice.invoice_line:
40 res[invoice.id]['amount_untaxed'] += line.price_subtotal
41 for line in invoice.tax_line:
42 res[invoice.id]['amount_tax'] += line.amount
43 res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
46 def _get_journal(self, cr, uid, context=None):
49 type_inv = context.get('type', 'out_invoice')
50 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
51 company_id = context.get('company_id', user.company_id.id)
52 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
53 journal_obj = self.pool.get('account.journal')
54 res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
55 ('company_id', '=', company_id)],
57 return res and res[0] or False
59 def _get_currency(self, cr, uid, context=None):
61 journal_id = self._get_journal(cr, uid, context=context)
63 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
64 res = journal.currency and journal.currency.id or journal.company_id.currency_id.id
67 def _get_journal_analytic(self, cr, uid, type_inv, context=None):
68 type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
69 tt = type2journal.get(type_inv, 'sale')
70 result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
72 raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s'!") % (tt,))
75 def _get_type(self, cr, uid, context=None):
78 return context.get('type', 'out_invoice')
80 def _reconciled(self, cr, uid, ids, name, args, context=None):
82 for inv in self.browse(cr, uid, ids, context=context):
83 res[inv.id] = self.test_paid(cr, uid, [inv.id])
84 if not res[inv.id] and inv.state == 'paid':
85 self.signal_open_test(cr, uid, [inv.id])
88 def _get_reference_type(self, cr, uid, context=None):
89 return [('none', _('Free Reference'))]
91 def _amount_residual(self, cr, uid, ids, name, args, context=None):
93 for invoice in self.browse(cr, uid, ids, context=context):
94 result[invoice.id] = 0.0
96 for m in invoice.move_id.line_id:
97 if m.account_id.type in ('receivable','payable'):
98 result[invoice.id] += m.amount_residual_currency
99 #prevent the residual amount on the invoice to be less than 0
100 result[invoice.id] = max(result[invoice.id], 0.0)
103 # Give Journal Items related to the payment reconciled to this invoice
104 # Return ids of partial and total payments related to the selected invoices
105 def _get_lines(self, cr, uid, ids, name, arg, context=None):
107 for invoice in self.browse(cr, uid, ids, context=context):
110 if not invoice.move_id:
112 data_lines = [x for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
114 for line in data_lines:
116 if line.reconcile_id:
117 ids_line = line.reconcile_id.line_id
118 elif line.reconcile_partial_id:
119 ids_line = line.reconcile_partial_id.line_partial_ids
120 l = map(lambda x: x.id, ids_line)
121 partial_ids.append(line.id)
122 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
125 def _get_invoice_line(self, cr, uid, ids, context=None):
127 for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
128 result[line.invoice_id.id] = True
131 def _get_invoice_tax(self, cr, uid, ids, context=None):
133 for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
134 result[tax.invoice_id.id] = True
137 def _compute_lines(self, cr, uid, ids, name, args, context=None):
139 for invoice in self.browse(cr, uid, ids, context=context):
143 for m in invoice.move_id.line_id:
146 temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
147 elif m.reconcile_partial_id:
148 temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
149 lines += [x for x in temp_lines if x not in lines]
152 lines = filter(lambda x: x not in src, lines)
153 result[invoice.id] = lines
156 def _get_invoice_from_line(self, cr, uid, ids, context=None):
158 for line in self.pool.get('account.move.line').browse(cr, uid, ids, context=context):
159 if line.reconcile_partial_id:
160 for line2 in line.reconcile_partial_id.line_partial_ids:
161 move[line2.move_id.id] = True
162 if line.reconcile_id:
163 for line2 in line.reconcile_id.line_id:
164 move[line2.move_id.id] = True
167 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
170 def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
172 for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids, context=context):
173 for line in r.line_partial_ids:
174 move[line.move_id.id] = True
175 for line in r.line_id:
176 move[line.move_id.id] = True
180 invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
183 _name = "account.invoice"
184 _inherit = ['mail.thread']
185 _description = 'Invoice'
191 'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'paid' and obj['type'] in ('out_invoice', 'out_refund'),
192 'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'open' and obj['type'] in ('out_invoice', 'out_refund'),
196 'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
197 'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
198 '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)]}),
199 'type': fields.selection([
200 ('out_invoice','Customer Invoice'),
201 ('in_invoice','Supplier Invoice'),
202 ('out_refund','Customer Refund'),
203 ('in_refund','Supplier Refund'),
204 ],'Type', readonly=True, select=True, change_default=True, track_visibility='always'),
206 'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
207 'internal_number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
208 'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
209 'reference_type': fields.selection(_get_reference_type, 'Payment Reference',
210 required=True, readonly=True, states={'draft':[('readonly',False)]}),
211 'comment': fields.text('Additional Information'),
213 'state': fields.selection([
215 ('proforma','Pro-forma'),
216 ('proforma2','Pro-forma'),
219 ('cancel','Cancelled'),
220 ],'Status', select=True, readonly=True, track_visibility='onchange',
221 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed Invoice. \
222 \n* The \'Pro-forma\' when invoice is in Pro-forma status,invoice does not have an invoice number. \
223 \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. \
224 \n* The \'Paid\' status is set automatically when the invoice is paid. Its related journal entries may or may not be reconciled. \
225 \n* The \'Cancelled\' status is used when user cancel invoice.'),
226 'sent': fields.boolean('Sent', readonly=True, help="It indicates that the invoice has been sent."),
227 'date_invoice': fields.date('Invoice Date', readonly=True, states={'draft':[('readonly',False)]}, select=True, help="Keep empty to use the current date"),
228 'date_due': fields.date('Due Date', readonly=True, states={'draft':[('readonly',False)]}, select=True,
229 help="If you use payment terms, the due date will be computed automatically at the generation "\
230 "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."),
231 'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
232 'payment_term': fields.many2one('account.payment.term', 'Payment Terms',readonly=True, states={'draft':[('readonly',False)]},
233 help="If you use payment terms, the due date will be computed automatically at the generation "\
234 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
235 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
236 '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)]}),
238 'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
239 'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
240 'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
242 'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, select=1, ondelete='restrict', help="Link to the automatically generated Journal Items."),
243 'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Subtotal', track_visibility='always',
245 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
246 'account.invoice.tax': (_get_invoice_tax, None, 20),
247 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
250 'amount_tax': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Tax',
252 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
253 'account.invoice.tax': (_get_invoice_tax, None, 20),
254 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
257 'amount_total': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Total',
259 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
260 'account.invoice.tax': (_get_invoice_tax, None, 20),
261 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
264 'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}, track_visibility='always'),
265 'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
266 'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
267 'check_total': fields.float('Verification Total', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft':[('readonly',False)]}),
268 'reconciled': fields.function(_reconciled, string='Paid/Reconciled', type='boolean',
270 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
271 'account.move.line': (_get_invoice_from_line, None, 50),
272 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
273 }, 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."),
274 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
275 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)]}),
276 'move_lines':fields.function(_get_lines, type='many2many', relation='account.move.line', string='Entry Lines'),
277 'residual': fields.function(_amount_residual, digits_compute=dp.get_precision('Account'), string='Balance',
279 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line','move_id'], 50),
280 'account.invoice.tax': (_get_invoice_tax, None, 50),
281 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 50),
282 'account.move.line': (_get_invoice_from_line, None, 50),
283 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
285 help="Remaining amount due."),
286 'payment_ids': fields.function(_compute_lines, relation='account.move.line', type="many2many", string='Payments'),
287 'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
288 'user_id': fields.many2one('res.users', 'Salesperson', readonly=True, track_visibility='onchange', states={'draft':[('readonly',False)]}),
289 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]}),
290 'commercial_partner_id': fields.related('partner_id', 'commercial_partner_id', string='Commercial Entity', type='many2one',
291 relation='res.partner', store=True, readonly=True,
292 help="The commercial entity that will be used on Journal Entries for this invoice")
297 'journal_id': _get_journal,
298 'currency_id': _get_currency,
299 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
300 'reference_type': 'none',
302 'internal_number': False,
303 'user_id': lambda s, cr, u, c: u,
307 ('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
313 def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
314 journal_obj = self.pool.get('account.journal')
318 if context.get('active_model', '') in ['res.partner'] and context.get('active_ids', False) and context['active_ids']:
319 partner = self.pool[context['active_model']].read(cr, uid, context['active_ids'], ['supplier','customer'])[0]
321 view_id = self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'account.invoice.tree')])
323 if view_type == 'form':
324 if partner['supplier'] and not partner['customer']:
325 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.supplier.form')])
326 elif partner['customer'] and not partner['supplier']:
327 view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.form')])
328 if view_id and isinstance(view_id, (list, tuple)):
330 res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
332 type = context.get('journal_type', False)
333 for field in res['fields']:
334 if field == 'journal_id' and type:
335 journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
336 res['fields'][field]['selection'] = journal_select
338 doc = etree.XML(res['arch'])
340 if context.get('type', False):
341 for node in doc.xpath("//field[@name='partner_bank_id']"):
342 if context['type'] == 'in_refund':
343 node.set('domain', "[('partner_id.ref_companies', 'in', [company_id])]")
344 elif context['type'] == 'out_refund':
345 node.set('domain', "[('partner_id', '=', partner_id)]")
346 res['arch'] = etree.tostring(doc)
348 if view_type == 'search':
349 if context.get('type', 'in_invoice') in ('out_invoice', 'out_refund'):
350 for node in doc.xpath("//group[@name='extended filter']"):
352 res['arch'] = etree.tostring(doc)
354 if view_type == 'tree':
355 partner_string = _('Customer')
356 if context.get('type', 'out_invoice') in ('in_invoice', 'in_refund'):
357 partner_string = _('Supplier')
358 for node in doc.xpath("//field[@name='reference']"):
359 node.set('invisible', '0')
360 for node in doc.xpath("//field[@name='partner_id']"):
361 node.set('string', partner_string)
362 res['arch'] = etree.tostring(doc)
365 def get_log_context(self, cr, uid, context=None):
368 res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
369 view_id = res and res[1] or False
370 context['view_id'] = view_id
373 def invoice_print(self, cr, uid, ids, context=None):
375 This function prints the invoice and mark it as sent, so that we can see more easily the next step of the workflow
377 assert len(ids) == 1, 'This option should only be used for a single id at a time.'
378 self.write(cr, uid, ids, {'sent': True}, context=context)
381 'model': 'account.invoice',
382 'form': self.read(cr, uid, ids[0], context=context)
385 'type': 'ir.actions.report.xml',
386 'report_name': 'account.invoice',
391 def action_invoice_sent(self, cr, uid, ids, context=None):
393 This function opens a window to compose an email, with the edi invoice template message loaded by default
395 assert len(ids) == 1, 'This option should only be used for a single id at a time.'
396 ir_model_data = self.pool.get('ir.model.data')
398 template_id = ir_model_data.get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1]
402 compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
404 compose_form_id = False
407 'default_model': 'account.invoice',
408 'default_res_id': ids[0],
409 'default_use_template': bool(template_id),
410 'default_template_id': template_id,
411 'default_composition_mode': 'comment',
412 'mark_invoice_as_sent': True,
415 'name': _('Compose Email'),
416 'type': 'ir.actions.act_window',
419 'res_model': 'mail.compose.message',
420 'views': [(compose_form_id, 'form')],
421 'view_id': compose_form_id,
426 def confirm_paid(self, cr, uid, ids, context=None):
429 self.write(cr, uid, ids, {'state':'paid'}, context=context)
432 def unlink(self, cr, uid, ids, context=None):
435 invoices = self.read(cr, uid, ids, ['state','internal_number'], context=context)
439 if t['state'] not in ('draft', 'cancel'):
440 raise openerp.exceptions.Warning(_('You cannot delete an invoice which is not draft or cancelled. You should refund it instead.'))
441 elif t['internal_number']:
442 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.'))
444 unlink_ids.append(t['id'])
446 osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
449 def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
450 date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
451 partner_payment_term = False
454 fiscal_position = False
456 opt = [('uid', str(uid))]
459 opt.insert(0, ('id', partner_id))
460 p = self.pool.get('res.partner').browse(cr, uid, partner_id)
462 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)):
463 property_obj = self.pool.get('ir.property')
464 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
465 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
467 rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
469 pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
470 rec_line_data = property_obj.read(cr,uid,rec_pro_id,['name','value_reference','res_id'])
471 pay_line_data = property_obj.read(cr,uid,pay_pro_id,['name','value_reference','res_id'])
472 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
473 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
474 if not rec_res_id and not pay_res_id:
475 raise osv.except_osv(_('Configuration Error!'),
476 _('Cannot find a chart of accounts for this company, you should create one.'))
477 account_obj = self.pool.get('account.account')
478 rec_obj_acc = account_obj.browse(cr, uid, [rec_res_id])
479 pay_obj_acc = account_obj.browse(cr, uid, [pay_res_id])
480 p.property_account_receivable = rec_obj_acc[0]
481 p.property_account_payable = pay_obj_acc[0]
483 if type in ('out_invoice', 'out_refund'):
484 acc_id = p.property_account_receivable.id
485 partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
487 acc_id = p.property_account_payable.id
488 partner_payment_term = p.property_supplier_payment_term and p.property_supplier_payment_term.id or False
489 fiscal_position = p.property_account_position and p.property_account_position.id or False
491 bank_id = p.bank_ids[0].id
494 'account_id': acc_id,
495 'payment_term': partner_payment_term,
496 'fiscal_position': fiscal_position
500 if type in ('in_invoice', 'in_refund'):
501 result['value']['partner_bank_id'] = bank_id
503 if payment_term != partner_payment_term:
504 if partner_payment_term:
505 to_update = self.onchange_payment_term_date_invoice(
506 cr, uid, ids, partner_payment_term, date_invoice)
507 result['value'].update(to_update['value'])
509 result['value']['date_due'] = False
511 if partner_bank_id != bank_id:
512 to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
513 result['value'].update(to_update['value'])
516 def onchange_journal_id(self, cr, uid, ids, journal_id=False, context=None):
519 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
520 currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id
521 company_id = journal.company_id.id
523 'currency_id': currency_id,
524 'company_id': company_id,
529 def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
532 date_invoice = time.strftime('%Y-%m-%d')
533 if not payment_term_id:
534 return {'value':{'date_due': date_invoice}} #To make sure the invoice has a due date when no payment term
535 pterm_list = self.pool.get('account.payment.term').compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
537 pterm_list = [line[0] for line in pterm_list]
539 res = {'value':{'date_due': pterm_list[-1]}}
541 raise osv.except_osv(_('Insufficient Data!'), _('The payment term of supplier does not have a payment term line.'))
544 def onchange_invoice_line(self, cr, uid, ids, lines):
547 def onchange_partner_bank(self, cursor, user, ids, partner_bank_id=False):
550 def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id, context=None):
553 obj_journal = self.pool.get('account.journal')
554 account_obj = self.pool.get('account.account')
555 inv_line_obj = self.pool.get('account.invoice.line')
557 if company_id and part_id and type:
559 partner_obj = self.pool.get('res.partner').browse(cr, uid, part_id, context=context)
561 if partner_obj.property_account_payable and partner_obj.property_account_receivable:
562 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
563 property_obj = self.pool.get('ir.property')
564 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
565 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
568 rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
570 pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
572 rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value_reference','res_id'])
573 pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value_reference','res_id'])
574 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
575 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
577 if not rec_res_id and not pay_res_id:
578 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)
580 if type in ('out_invoice', 'out_refund'):
585 val= {'account_id': acc_id}
588 inv_obj = self.browse(cr,uid,ids)
589 for line in inv_obj[0].invoice_line:
591 if line.account_id.company_id.id != company_id:
592 result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
594 raise osv.except_osv(_('Configuration Error!'),
595 _('Cannot find a chart of account, you should create one from Settings\Configuration\Accounting menu.'))
596 inv_line_obj.write(cr, uid, [line.id], {'account_id': result_id[-1]})
599 for inv_line in invoice_line:
600 obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
601 if obj_l.company_id.id != company_id:
602 raise osv.except_osv(_('Configuration Error!'),
603 _('Invoice line account\'s company and invoice\'s compnay does not match.'))
606 if company_id and type:
607 if type in ('out_invoice'):
608 journal_type = 'sale'
609 elif type in ('out_refund'):
610 journal_type = 'sale_refund'
611 elif type in ('in_refund'):
612 journal_type = 'purchase_refund'
614 journal_type = 'purchase'
615 journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
617 val['journal_id'] = journal_ids[0]
618 ir_values_obj = self.pool.get('ir.values')
619 res_journal_default = ir_values_obj.get(cr, uid, 'default', 'type=%s' % (type), ['account.invoice'])
620 for r in res_journal_default:
621 if r[1] == 'journal_id' and r[2] in journal_ids:
622 val['journal_id'] = r[2]
623 if not val.get('journal_id', False):
624 raise osv.except_osv(_('Configuration Error!'), (_('Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Journals\Journals.') % (journal_type)))
625 dom = {'journal_id': [('id', 'in', journal_ids)]}
627 journal_ids = obj_journal.search(cr, uid, [])
629 return {'value': val, 'domain': dom}
631 # go from canceled state to draft state
632 def action_cancel_draft(self, cr, uid, ids, *args):
633 self.write(cr, uid, ids, {'state':'draft'})
634 self.delete_workflow(cr, uid, ids)
635 self.create_workflow(cr, uid, ids)
638 # ----------------------------------------
639 # Mail related methods
640 # ----------------------------------------
642 def _get_formview_action(self, cr, uid, id, context=None):
643 """ Update form view id of action to open the invoice """
644 action = super(account_invoice, self)._get_formview_action(cr, uid, id, context=context)
645 obj = self.browse(cr, uid, id, context=context)
646 if obj.type == 'in_invoice':
647 model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
649 'views': [(view_id, 'form')],
652 model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
654 'views': [(view_id, 'form')],
661 # return the ids of the move lines which has the same account than the invoice
663 def move_line_id_payment_get(self, cr, uid, ids, *args):
664 if not ids: return []
665 result = self.move_line_id_payment_gets(cr, uid, ids, *args)
666 return result.get(ids[0], [])
668 def move_line_id_payment_gets(self, cr, uid, ids, *args):
670 if not ids: return res
671 cr.execute('SELECT i.id, l.id '\
672 'FROM account_move_line l '\
673 'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
675 'AND l.account_id=i.account_id',
677 for r in cr.fetchall():
678 res.setdefault(r[0], [])
679 res[r[0]].append( r[1] )
682 def copy(self, cr, uid, id, default=None, context=None):
683 default = default or {}
689 'internal_number': False,
693 if 'date_invoice' not in default:
697 if 'date_due' not in default:
701 return super(account_invoice, self).copy(cr, uid, id, default, context)
703 def test_paid(self, cr, uid, ids, *args):
704 res = self.move_line_id_payment_get(cr, uid, ids)
709 cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
710 ok = ok and bool(cr.fetchone()[0])
713 def button_reset_taxes(self, cr, uid, ids, context=None):
717 ait_obj = self.pool.get('account.invoice.tax')
719 cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s AND manual is False", (id,))
720 partner = self.browse(cr, uid, id, context=ctx).partner_id
722 ctx.update({'lang': partner.lang})
723 for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
724 ait_obj.create(cr, uid, taxe)
725 # Update the stored value (fields.function), so we write to trigger recompute
726 self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
729 def button_compute(self, cr, uid, ids, context=None, set_total=False):
730 self.button_reset_taxes(cr, uid, ids, context)
731 for inv in self.browse(cr, uid, ids, context=context):
733 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
736 def _convert_ref(self, cr, uid, ref):
737 return (ref or '').replace('/','')
739 def _get_analytic_lines(self, cr, uid, id, context=None):
742 inv = self.browse(cr, uid, id)
743 cur_obj = self.pool.get('res.currency')
745 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
746 if inv.type in ('out_invoice', 'in_refund'):
751 iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id, context=context)
753 if il['account_analytic_id']:
754 if inv.type in ('in_invoice', 'in_refund'):
757 ref = self._convert_ref(cr, uid, inv.number)
758 if not inv.journal_id.analytic_journal_id:
759 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (inv.journal_id.name,))
760 il['analytic_lines'] = [(0,0, {
762 'date': inv['date_invoice'],
763 'account_id': il['account_analytic_id'],
764 'unit_amount': il['quantity'],
765 'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
766 'product_id': il['product_id'],
767 'product_uom_id': il['uos_id'],
768 'general_account_id': il['account_id'],
769 'journal_id': inv.journal_id.analytic_journal_id.id,
774 def action_date_assign(self, cr, uid, ids, *args):
775 for inv in self.browse(cr, uid, ids):
776 res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
777 if res and res['value']:
778 self.write(cr, uid, [inv.id], res['value'])
781 def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
782 """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
783 Hook method to be overridden in additional modules to verify and possibly alter the
784 move lines to be created by an invoice, for special cases.
785 :param invoice_browse: browsable record of the invoice that is generating the move lines
786 :param move_lines: list of dictionaries with the account.move.lines (as for create())
787 :return: the (possibly updated) final move_lines to create for this invoice
791 def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
792 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id
794 for tax in compute_taxes.values():
795 ait_obj.create(cr, uid, tax)
798 for tax in inv.tax_line:
801 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id, tax.account_analytic_id.id)
803 if not key in compute_taxes:
804 raise osv.except_osv(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
805 base = compute_taxes[key]['base']
806 if abs(base - tax.base) > company_currency.rounding:
807 raise osv.except_osv(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
808 for key in compute_taxes:
809 if not key in tax_key:
810 raise osv.except_osv(_('Warning!'), _('Taxes are missing!\nClick on compute button.'))
812 def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None):
817 cur_obj = self.pool.get('res.currency')
818 for i in invoice_move_lines:
819 if inv.currency_id.id != company_currency:
820 context.update({'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
821 i['currency_id'] = inv.currency_id.id
822 i['amount_currency'] = i['price']
823 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
824 company_currency, i['price'],
827 i['amount_currency'] = False
828 i['currency_id'] = False
830 if inv.type in ('out_invoice','in_refund'):
832 total_currency += i['amount_currency'] or i['price']
833 i['price'] = - i['price']
836 total_currency -= i['amount_currency'] or i['price']
837 return total, total_currency, invoice_move_lines
839 def inv_line_characteristic_hashcode(self, invoice, invoice_line):
840 """Overridable hashcode generation for invoice lines. Lines having the same hashcode
841 will be grouped together if the journal has the 'group line' option. Of course a module
842 can add fields to invoice lines that would need to be tested too before merging lines
844 return "%s-%s-%s-%s-%s"%(
845 invoice_line['account_id'],
846 invoice_line.get('tax_code_id',"False"),
847 invoice_line.get('product_id',"False"),
848 invoice_line.get('analytic_account_id',"False"),
849 invoice_line.get('date_maturity',"False"))
851 def group_lines(self, cr, uid, iml, line, inv):
852 """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
853 if inv.journal_id.group_invoice_lines:
856 tmp = self.inv_line_characteristic_hashcode(inv, l)
859 am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
860 line2[tmp]['debit'] = (am > 0) and am or 0.0
861 line2[tmp]['credit'] = (am < 0) and -am or 0.0
862 line2[tmp]['tax_amount'] += l['tax_amount']
863 line2[tmp]['analytic_lines'] += l['analytic_lines']
867 for key, val in line2.items():
868 line.append((0,0,val))
871 def action_move_create(self, cr, uid, ids, context=None):
872 """Creates invoice related analytics and financial move lines"""
873 ait_obj = self.pool.get('account.invoice.tax')
874 cur_obj = self.pool.get('res.currency')
875 period_obj = self.pool.get('account.period')
876 payment_term_obj = self.pool.get('account.payment.term')
877 journal_obj = self.pool.get('account.journal')
878 move_obj = self.pool.get('account.move')
881 for inv in self.browse(cr, uid, ids, context=context):
882 if not inv.journal_id.sequence_id:
883 raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.'))
884 if not inv.invoice_line:
885 raise osv.except_osv(_('No Invoice Lines !'), _('Please create some invoice lines.'))
890 ctx.update({'lang': inv.partner_id.lang})
891 if not inv.date_invoice:
892 self.write(cr, uid, [inv.id], {'date_invoice': fields.date.context_today(self,cr,uid,context=context)}, context=ctx)
893 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
894 # create the analytical lines
895 # one move line per invoice line
896 iml = self._get_analytic_lines(cr, uid, inv.id, context=ctx)
897 # check if taxes are all computed
898 compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
899 self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
901 # I disabled the check_total feature
902 group_check_total_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'group_supplier_inv_check_total')[1]
903 group_check_total = self.pool.get('res.groups').browse(cr, uid, group_check_total_id, context=context)
904 if group_check_total and uid in [x.id for x in group_check_total.users]:
905 if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)):
906 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe encoded total does not match the computed total.'))
909 total_fixed = total_percent = 0
910 for line in inv.payment_term.line_ids:
911 if line.value == 'fixed':
912 total_fixed += line.value_amount
913 if line.value == 'procent':
914 total_percent += line.value_amount
915 total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
916 if (total_fixed + total_percent) > 100:
917 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'."))
919 # one move line per tax line
920 iml += ait_obj.move_line_get(cr, uid, inv.id)
923 if inv.type in ('in_invoice', 'in_refund'):
925 entry_type = 'journal_pur_voucher'
926 if inv.type == 'in_refund':
927 entry_type = 'cont_voucher'
929 ref = self._convert_ref(cr, uid, inv.number)
930 entry_type = 'journal_sale_vou'
931 if inv.type == 'out_refund':
932 entry_type = 'cont_voucher'
934 diff_currency_p = inv.currency_id.id <> company_currency
935 # create one move line for the total and possibly adjust the other lines amount
938 total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml, context=ctx)
939 acc_id = inv.account_id.id
941 name = inv['name'] or '/'
944 totlines = payment_term_obj.compute(cr,
945 uid, inv.payment_term.id, total, inv.date_invoice or False, context=ctx)
947 res_amount_currency = total_currency
949 ctx.update({'date': inv.date_invoice})
951 if inv.currency_id.id != company_currency:
952 amount_currency = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, t[1], context=ctx)
954 amount_currency = False
956 # last line add the diff
957 res_amount_currency -= amount_currency or 0
959 if i == len(totlines):
960 amount_currency += res_amount_currency
966 'account_id': acc_id,
967 'date_maturity': t[0],
968 'amount_currency': diff_currency_p \
969 and amount_currency or False,
970 'currency_id': diff_currency_p \
971 and inv.currency_id.id or False,
979 'account_id': acc_id,
980 'date_maturity': inv.date_due or False,
981 'amount_currency': diff_currency_p \
982 and total_currency or False,
983 'currency_id': diff_currency_p \
984 and inv.currency_id.id or False,
988 date = inv.date_invoice or time.strftime('%Y-%m-%d')
990 part = self.pool.get("res.partner")._find_accounting_partner(inv.partner_id)
992 line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part.id, date, context=ctx)),iml)
994 line = self.group_lines(cr, uid, iml, line, inv)
996 journal_id = inv.journal_id.id
997 journal = journal_obj.browse(cr, uid, journal_id, context=ctx)
998 if journal.centralisation:
999 raise osv.except_osv(_('User Error!'),
1000 _('You cannot create an invoice on a centralized journal. Uncheck the centralized counterpart box in the related journal from the configuration menu.'))
1002 line = self.finalize_invoice_move_lines(cr, uid, inv, line)
1005 'ref': inv.reference and inv.reference or inv.name,
1007 'journal_id': journal_id,
1009 'narration': inv.comment,
1010 'company_id': inv.company_id.id,
1012 period_id = inv.period_id and inv.period_id.id or False
1013 ctx.update(company_id=inv.company_id.id)
1015 period_ids = period_obj.find(cr, uid, inv.date_invoice, context=ctx)
1016 period_id = period_ids and period_ids[0] or False
1018 move['period_id'] = period_id
1020 i[2]['period_id'] = period_id
1022 ctx.update(invoice=inv)
1023 move_id = move_obj.create(cr, uid, move, context=ctx)
1024 new_move_name = move_obj.browse(cr, uid, move_id, context=ctx).name
1025 # make the invoice point to that move
1026 self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name}, context=ctx)
1027 # Pass invoice in context in method post: used if you want to get the same
1028 # account move reference when creating the same invoice after a cancelled one:
1029 move_obj.post(cr, uid, [move_id], context=ctx)
1030 self._log_event(cr, uid, ids)
1033 def invoice_validate(self, cr, uid, ids, context=None):
1034 self.write(cr, uid, ids, {'state':'open'}, context=context)
1037 def line_get_convert(self, cr, uid, x, part, date, context=None):
1039 'date_maturity': x.get('date_maturity', False),
1041 'name': x['name'][:64],
1043 'debit': x['price']>0 and x['price'],
1044 'credit': x['price']<0 and -x['price'],
1045 'account_id': x['account_id'],
1046 'analytic_lines': x.get('analytic_lines', []),
1047 'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
1048 'currency_id': x.get('currency_id', False),
1049 'tax_code_id': x.get('tax_code_id', False),
1050 'tax_amount': x.get('tax_amount', False),
1051 'ref': x.get('ref', False),
1052 'quantity': x.get('quantity',1.00),
1053 'product_id': x.get('product_id', False),
1054 'product_uom_id': x.get('uos_id', False),
1055 'analytic_account_id': x.get('account_analytic_id', False),
1058 def action_number(self, cr, uid, ids, context=None):
1061 #TODO: not correct fix but required a frech values before reading it.
1062 self.write(cr, uid, ids, {})
1064 for obj_inv in self.browse(cr, uid, ids, context=context):
1065 invtype = obj_inv.type
1066 number = obj_inv.number
1067 move_id = obj_inv.move_id and obj_inv.move_id.id or False
1068 reference = obj_inv.reference or ''
1070 self.write(cr, uid, ids, {'internal_number': number})
1072 if invtype in ('in_invoice', 'in_refund'):
1074 ref = self._convert_ref(cr, uid, number)
1078 ref = self._convert_ref(cr, uid, number)
1080 cr.execute('UPDATE account_move SET ref=%s ' \
1081 'WHERE id=%s AND (ref is null OR ref = \'\')',
1083 cr.execute('UPDATE account_move_line SET ref=%s ' \
1084 'WHERE move_id=%s AND (ref is null OR ref = \'\')',
1086 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
1087 'FROM account_move_line ' \
1088 'WHERE account_move_line.move_id = %s ' \
1089 'AND account_analytic_line.move_id = account_move_line.id',
1093 def action_cancel(self, cr, uid, ids, context=None):
1096 account_move_obj = self.pool.get('account.move')
1097 invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
1098 move_ids = [] # ones that we will need to remove
1101 move_ids.append(i['move_id'][0])
1102 if i['payment_ids']:
1103 account_move_line_obj = self.pool.get('account.move.line')
1104 pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
1105 for move_line in pay_ids:
1106 if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
1107 raise osv.except_osv(_('Error!'), _('You cannot cancel an invoice which is partially paid. You need to unreconcile related payment entries first.'))
1109 # First, set the invoices as cancelled and detach the move ids
1110 self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
1112 # second, invalidate the move(s)
1113 account_move_obj.button_cancel(cr, uid, move_ids, context=context)
1114 # delete the move this invoice was pointing to
1115 # Note that the corresponding move_lines and move_reconciles
1116 # will be automatically deleted too
1117 account_move_obj.unlink(cr, uid, move_ids, context=context)
1118 self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
1123 def list_distinct_taxes(self, cr, uid, ids):
1124 invoices = self.browse(cr, uid, ids)
1126 for inv in invoices:
1127 for tax in inv.tax_line:
1128 if not tax['name'] in taxes:
1129 taxes[tax['name']] = {'name': tax['name']}
1130 return taxes.values()
1132 def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
1133 #TODO: implement messages system
1136 def name_get(self, cr, uid, ids, context=None):
1140 'out_invoice': 'Invoice ',
1141 'in_invoice': 'Sup. Invoice ',
1142 'out_refund': 'Refund ',
1143 'in_refund': 'Supplier Refund ',
1145 return [(r['id'], (r['number']) or types[r['type']] + (r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
1147 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1154 ids = self.search(cr, user, [('number','=',name)] + args, limit=limit, context=context)
1156 ids = self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context)
1157 return self.name_get(cr, user, ids, context)
1159 def _refund_cleanup_lines(self, cr, uid, lines, context=None):
1160 """Convert records to dict of values suitable for one2many line creation
1162 :param list(browse_record) lines: records to convert
1163 :return: list of command tuple for one2many line creation [(0, 0, dict of valueis), ...]
1168 for field in line._all_columns.keys():
1169 if line._all_columns[field].column._type == 'many2one':
1170 clean_line[field] = line[field].id
1171 elif line._all_columns[field].column._type not in ['many2many','one2many']:
1172 clean_line[field] = line[field]
1173 elif field == 'invoice_line_tax_id':
1175 for tax in line[field]:
1176 tax_list.append(tax.id)
1177 clean_line[field] = [(6,0, tax_list)]
1178 clean_lines.append(clean_line)
1179 return map(lambda x: (0,0,x), clean_lines)
1181 def _prepare_refund(self, cr, uid, invoice, date=None, period_id=None, description=None, journal_id=None, context=None):
1182 """Prepare the dict of values to create the new refund from the invoice.
1183 This method may be overridden to implement custom
1184 refund generation (making sure to call super() to establish
1185 a clean extension chain).
1187 :param integer invoice_id: id of the invoice to refund
1188 :param dict invoice: read of the invoice to refund
1189 :param string date: refund creation date from the wizard
1190 :param integer period_id: force account.period from the wizard
1191 :param string description: description of the refund from the wizard
1192 :param integer journal_id: account.journal from the wizard
1193 :return: dict of value to create() the refund
1195 obj_journal = self.pool.get('account.journal')
1198 'out_invoice': 'out_refund', # Customer Invoice
1199 'in_invoice': 'in_refund', # Supplier Invoice
1200 'out_refund': 'out_invoice', # Customer Refund
1201 'in_refund': 'in_invoice', # Supplier Refund
1204 for field in ['name', 'reference', 'comment', 'date_due', 'partner_id', 'company_id',
1205 'account_id', 'currency_id', 'payment_term', 'user_id', 'fiscal_position']:
1206 if invoice._all_columns[field].column._type == 'many2one':
1207 invoice_data[field] = invoice[field].id
1209 invoice_data[field] = invoice[field] if invoice[field] else False
1211 invoice_lines = self._refund_cleanup_lines(cr, uid, invoice.invoice_line, context=context)
1213 tax_lines = filter(lambda l: l['manual'], invoice.tax_line)
1214 tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines, context=context)
1216 refund_journal_ids = [journal_id]
1217 elif invoice['type'] == 'in_invoice':
1218 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')], context=context)
1220 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')], context=context)
1223 date = time.strftime('%Y-%m-%d')
1224 invoice_data.update({
1225 'type': type_dict[invoice['type']],
1226 'date_invoice': date,
1229 'invoice_line': invoice_lines,
1230 'tax_line': tax_lines,
1231 'journal_id': refund_journal_ids and refund_journal_ids[0] or False,
1234 invoice_data['period_id'] = period_id
1236 invoice_data['name'] = description
1239 def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None, context=None):
1241 for invoice in self.browse(cr, uid, ids, context=context):
1242 invoice = self._prepare_refund(cr, uid, invoice,
1244 period_id=period_id,
1245 description=description,
1246 journal_id=journal_id,
1248 # create the new invoice
1249 new_ids.append(self.create(cr, uid, invoice, context=context))
1253 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=''):
1256 #TODO check if we can use different period for payment and the writeoff line
1257 assert len(ids)==1, "Can only pay one invoice at a time."
1258 invoice = self.browse(cr, uid, ids[0], context=context)
1259 src_account_id = invoice.account_id.id
1260 # Take the seq as name for move
1261 types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1262 direction = types[invoice.type]
1263 #take the choosen date
1264 if 'date_p' in context and context['date_p']:
1265 date=context['date_p']
1267 date=time.strftime('%Y-%m-%d')
1269 # Take the amount in currency and the currency of the payment
1270 if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1271 amount_currency = context['amount_currency']
1272 currency_id = context['currency_id']
1274 amount_currency = False
1277 pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1278 if invoice.type in ('in_invoice', 'out_invoice'):
1279 if pay_journal['type'] == 'bank':
1280 entry_type = 'bank_pay_voucher' # Bank payment
1282 entry_type = 'pay_voucher' # Cash payment
1284 entry_type = 'cont_voucher'
1285 if invoice.type in ('in_invoice', 'in_refund'):
1286 ref = invoice.reference
1288 ref = self._convert_ref(cr, uid, invoice.number)
1289 partner = self.pool['res.partner']._find_accounting_partner(invoice.partner_id)
1290 # Pay attention to the sign for both debit/credit AND amount_currency
1292 'debit': direction * pay_amount>0 and direction * pay_amount,
1293 'credit': direction * pay_amount<0 and - direction * pay_amount,
1294 'account_id': src_account_id,
1295 'partner_id': partner.id,
1298 'currency_id':currency_id,
1299 'amount_currency':amount_currency and direction * amount_currency or 0.0,
1300 'company_id': invoice.company_id.id,
1303 'debit': direction * pay_amount<0 and - direction * pay_amount,
1304 'credit': direction * pay_amount>0 and direction * pay_amount,
1305 'account_id': pay_account_id,
1306 'partner_id': partner.id,
1309 'currency_id':currency_id,
1310 'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1311 'company_id': invoice.company_id.id,
1315 name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1319 lines = [(0, 0, l1), (0, 0, l2)]
1320 move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
1321 move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1325 line = self.pool.get('account.move.line')
1326 move_ids = [move_id,]
1328 move_ids.append(invoice.move_id.id)
1329 cr.execute('SELECT id FROM account_move_line '\
1330 'WHERE move_id IN %s',
1331 ((move_id, invoice.move_id.id),))
1332 lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1333 for l in lines+invoice.payment_ids:
1334 if l.account_id.id == src_account_id:
1335 line_ids.append(l.id)
1336 total += (l.debit or 0.0) - (l.credit or 0.0)
1338 inv_id, name = self.name_get(cr, uid, [invoice.id], context=context)[0]
1339 if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1340 self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1342 code = invoice.currency_id.symbol
1343 # TODO: use currency's formatting function
1344 msg = _("Invoice partially paid: %s%s of %s%s (%s%s remaining).") % \
1345 (pay_amount, code, invoice.amount_total, code, total, code)
1346 self.message_post(cr, uid, [inv_id], body=msg, context=context)
1347 self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1349 # Update the stored value (fields.function), so we write to trigger recompute
1350 self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1354 class account_invoice_line(osv.osv):
1356 def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1358 tax_obj = self.pool.get('account.tax')
1359 cur_obj = self.pool.get('res.currency')
1360 for line in self.browse(cr, uid, ids):
1361 price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1362 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)
1363 res[line.id] = taxes['total']
1365 cur = line.invoice_id.currency_id
1366 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1369 def _price_unit_default(self, cr, uid, context=None):
1372 if context.get('check_total', False):
1373 t = context['check_total']
1374 for l in context.get('invoice_line', {}):
1375 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1376 tax_obj = self.pool.get('account.tax')
1377 p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1378 t = t - (p * l[2].get('quantity'))
1379 taxes = l[2].get('invoice_line_tax_id')
1380 if len(taxes[0]) >= 3 and taxes[0][2]:
1381 taxes = tax_obj.browse(cr, uid, list(taxes[0][2]))
1382 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']:
1383 t = t - tax['amount']
1387 _name = "account.invoice.line"
1388 _description = "Invoice Line"
1390 'name': fields.text('Description', required=True),
1391 'origin': fields.char('Source Document', size=256, help="Reference of the document that produced this invoice."),
1392 'sequence': fields.integer('Sequence', help="Gives the sequence of this line when displaying the invoice."),
1393 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1394 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null', select=True),
1395 'product_id': fields.many2one('product.product', 'Product', ondelete='set null', select=True),
1396 '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."),
1397 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
1398 'price_subtotal': fields.function(_amount_line, string='Amount', type="float",
1399 digits_compute= dp.get_precision('Account'), store=True),
1400 'quantity': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
1401 'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount')),
1402 'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1403 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account'),
1404 'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1405 'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1408 def _default_account_id(self, cr, uid, context=None):
1409 # XXX this gets the default account for the user's company,
1410 # it should get the default account for the invoice's company
1411 # however, the invoice's company does not reach this point
1414 if context.get('type') in ('out_invoice','out_refund'):
1415 prop = self.pool.get('ir.property').get(cr, uid, 'property_account_income_categ', 'product.category', context=context)
1417 prop = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category', context=context)
1418 return prop and prop.id or False
1423 'price_unit': _price_unit_default,
1424 'account_id': _default_account_id,
1427 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1430 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)
1431 if context.get('type', False):
1432 doc = etree.XML(res['arch'])
1433 for node in doc.xpath("//field[@name='product_id']"):
1434 if context['type'] in ('in_invoice', 'in_refund'):
1435 node.set('domain', "[('purchase_ok', '=', True)]")
1437 node.set('domain', "[('sale_ok', '=', True)]")
1438 res['arch'] = etree.tostring(doc)
1441 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):
1444 company_id = company_id if company_id != None else context.get('company_id',False)
1445 context = dict(context)
1446 context.update({'company_id': company_id, 'force_company': company_id})
1448 raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1450 if type in ('in_invoice', 'in_refund'):
1451 return {'value': {}, 'domain':{'product_uom':[]}}
1453 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
1454 part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
1455 fpos_obj = self.pool.get('account.fiscal.position')
1456 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
1459 context.update({'lang': part.lang})
1461 res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1463 if type in ('out_invoice','out_refund'):
1464 a = res.property_account_income.id
1466 a = res.categ_id.property_account_income_categ.id
1468 a = res.property_account_expense.id
1470 a = res.categ_id.property_account_expense_categ.id
1471 a = fpos_obj.map_account(cr, uid, fpos, a)
1473 result['account_id'] = a
1475 if type in ('out_invoice', 'out_refund'):
1476 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)
1478 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)
1479 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1481 if type in ('in_invoice', 'in_refund'):
1482 result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
1484 result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1485 result['name'] = res.partner_ref
1487 result['uos_id'] = uom_id or res.uom_id.id
1489 result['name'] += '\n'+res.description
1491 domain = {'uos_id':[('category_id','=',res.uom_id.category_id.id)]}
1493 res_final = {'value':result, 'domain':domain}
1495 if not company_id or not currency_id:
1498 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
1499 currency = self.pool.get('res.currency').browse(cr, uid, currency_id, context=context)
1501 if company.currency_id.id != currency.id:
1502 if type in ('in_invoice', 'in_refund'):
1503 res_final['value']['price_unit'] = res.standard_price
1504 new_price = res_final['value']['price_unit'] * currency.rate
1505 res_final['value']['price_unit'] = new_price
1507 if result['uos_id'] and result['uos_id'] != res.uom_id.id:
1508 selected_uom = self.pool.get('product.uom').browse(cr, uid, result['uos_id'], context=context)
1509 new_price = self.pool.get('product.uom')._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uos_id'])
1510 res_final['value']['price_unit'] = new_price
1513 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):
1516 company_id = company_id if company_id != None else context.get('company_id',False)
1517 context = dict(context)
1518 context.update({'company_id': company_id})
1520 res = self.product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, currency_id, context=context)
1522 res['value']['price_unit'] = 0.0
1524 prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
1525 prod_uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1526 if prod.uom_id.category_id.id != prod_uom.category_id.id:
1528 'title': _('Warning!'),
1529 'message': _('The selected unit of measure is not compatible with the unit of measure of the product.')
1531 res['value'].update({'uos_id': prod.uom_id.id})
1532 return {'value': res['value'], 'warning': warning}
1535 def move_line_get(self, cr, uid, invoice_id, context=None):
1537 tax_obj = self.pool.get('account.tax')
1538 cur_obj = self.pool.get('res.currency')
1541 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1542 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
1543 for line in inv.invoice_line:
1544 mres = self.move_line_get_item(cr, uid, line, context)
1548 tax_code_found= False
1549 for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1550 (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1551 line.quantity, line.product_id,
1552 inv.partner_id)['taxes']:
1554 if inv.type in ('out_invoice', 'in_invoice'):
1555 tax_code_id = tax['base_code_id']
1556 tax_amount = line.price_subtotal * tax['base_sign']
1558 tax_code_id = tax['ref_base_code_id']
1559 tax_amount = line.price_subtotal * tax['ref_base_sign']
1564 res.append(self.move_line_get_item(cr, uid, line, context))
1565 res[-1]['price'] = 0.0
1566 res[-1]['account_analytic_id'] = False
1567 elif not tax_code_id:
1569 tax_code_found = True
1571 res[-1]['tax_code_id'] = tax_code_id
1572 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1575 def move_line_get_item(self, cr, uid, line, context=None):
1578 'name': line.name.split('\n')[0][:64],
1579 'price_unit':line.price_unit,
1580 'quantity':line.quantity,
1581 'price':line.price_subtotal,
1582 'account_id':line.account_id.id,
1583 'product_id':line.product_id.id,
1584 'uos_id':line.uos_id.id,
1585 'account_analytic_id':line.account_analytic_id.id,
1586 'taxes':line.invoice_line_tax_id,
1589 # Set the tax field according to the account and the fiscal position
1591 def onchange_account_id(self, cr, uid, ids, product_id, partner_id, inv_type, fposition_id, account_id):
1595 fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1596 account = self.pool.get('account.account').browse(cr, uid, account_id)
1598 taxes = account.tax_ids
1599 unique_tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1601 product_change_result = self.product_id_change(cr, uid, ids, product_id, False, type=inv_type,
1602 partner_id=partner_id, fposition_id=fposition_id,
1603 company_id=account.company_id.id)
1604 if product_change_result and 'value' in product_change_result and 'invoice_line_tax_id' in product_change_result['value']:
1605 unique_tax_ids = product_change_result['value']['invoice_line_tax_id']
1606 return {'value':{'invoice_line_tax_id': unique_tax_ids}}
1609 class account_invoice_tax(osv.osv):
1610 _name = "account.invoice.tax"
1611 _description = "Invoice Tax"
1613 def _count_factor(self, cr, uid, ids, name, args, context=None):
1615 for invoice_tax in self.browse(cr, uid, ids, context=context):
1616 res[invoice_tax.id] = {
1620 if invoice_tax.amount <> 0.0:
1621 factor_tax = invoice_tax.tax_amount / invoice_tax.amount
1622 res[invoice_tax.id]['factor_tax'] = factor_tax
1624 if invoice_tax.base <> 0.0:
1625 factor_base = invoice_tax.base_amount / invoice_tax.base
1626 res[invoice_tax.id]['factor_base'] = factor_base
1631 'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1632 'name': fields.char('Tax Description', size=64, required=True),
1633 'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1634 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
1635 'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1636 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1637 'manual': fields.boolean('Manual'),
1638 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1639 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1640 'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1641 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1642 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1643 'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
1644 'factor_base': fields.function(_count_factor, string='Multipication factor for Base code', type='float', multi="all"),
1645 'factor_tax': fields.function(_count_factor, string='Multipication factor Tax code', type='float', multi="all")
1648 def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1649 cur_obj = self.pool.get('res.currency')
1650 company_obj = self.pool.get('res.company')
1651 company_currency = False
1654 factor = self.read(cr, uid, ids[0], ['factor_base'])['factor_base']
1656 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1657 if currency_id and company_currency:
1658 base = cur_obj.compute(cr, uid, currency_id, company_currency, base*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1659 return {'value': {'base_amount':base}}
1661 def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1662 cur_obj = self.pool.get('res.currency')
1663 company_obj = self.pool.get('res.company')
1664 company_currency = False
1667 factor = self.read(cr, uid, ids[0], ['factor_tax'])['factor_tax']
1669 company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1670 if currency_id and company_currency:
1671 amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1672 return {'value': {'tax_amount': amount}}
1680 def compute(self, cr, uid, invoice_id, context=None):
1682 tax_obj = self.pool.get('account.tax')
1683 cur_obj = self.pool.get('res.currency')
1684 inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1685 cur = inv.currency_id
1686 company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
1687 for line in inv.invoice_line:
1688 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']:
1690 val['invoice_id'] = inv.id
1691 val['name'] = tax['name']
1692 val['amount'] = tax['amount']
1693 val['manual'] = False
1694 val['sequence'] = tax['sequence']
1695 val['base'] = cur_obj.round(cr, uid, cur, tax['price_unit'] * line['quantity'])
1697 if inv.type in ('out_invoice','in_invoice'):
1698 val['base_code_id'] = tax['base_code_id']
1699 val['tax_code_id'] = tax['tax_code_id']
1700 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 time.strftime('%Y-%m-%d')}, round=False)
1701 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 time.strftime('%Y-%m-%d')}, round=False)
1702 val['account_id'] = tax['account_collected_id'] or line.account_id.id
1703 val['account_analytic_id'] = tax['account_analytic_collected_id']
1705 val['base_code_id'] = tax['ref_base_code_id']
1706 val['tax_code_id'] = tax['ref_tax_code_id']
1707 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 time.strftime('%Y-%m-%d')}, round=False)
1708 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 time.strftime('%Y-%m-%d')}, round=False)
1709 val['account_id'] = tax['account_paid_id'] or line.account_id.id
1710 val['account_analytic_id'] = tax['account_analytic_paid_id']
1712 key = (val['tax_code_id'], val['base_code_id'], val['account_id'], val['account_analytic_id'])
1713 if not key in tax_grouped:
1714 tax_grouped[key] = val
1716 tax_grouped[key]['amount'] += val['amount']
1717 tax_grouped[key]['base'] += val['base']
1718 tax_grouped[key]['base_amount'] += val['base_amount']
1719 tax_grouped[key]['tax_amount'] += val['tax_amount']
1721 for t in tax_grouped.values():
1722 t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1723 t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1724 t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1725 t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1728 def move_line_get(self, cr, uid, invoice_id):
1730 cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1731 for t in cr.dictfetchall():
1732 if not t['amount'] \
1733 and not t['tax_code_id'] \
1734 and not t['tax_amount']:
1739 'price_unit': t['amount'],
1741 'price': t['amount'] or 0.0,
1742 'account_id': t['account_id'],
1743 'tax_code_id': t['tax_code_id'],
1744 'tax_amount': t['tax_amount'],
1745 'account_analytic_id': t['account_analytic_id'],
1750 class res_partner(osv.osv):
1751 """ Inherits partner and adds invoice information in the partner form """
1752 _inherit = 'res.partner'
1754 'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1757 def _find_accounting_partner(self, partner):
1759 Find the partner for which the accounting entries will be created
1761 return partner.commercial_partner_id
1763 def copy(self, cr, uid, id, default=None, context=None):
1764 default = default or {}
1765 default.update({'invoice_ids' : []})
1766 return super(res_partner, self).copy(cr, uid, id, default, context)
1769 class mail_compose_message(osv.Model):
1770 _inherit = 'mail.compose.message'
1772 def send_mail(self, cr, uid, ids, context=None):
1773 context = context or {}
1774 if context.get('default_model') == 'account.invoice' and context.get('default_res_id') and context.get('mark_invoice_as_sent'):
1775 context = dict(context, mail_post_autofollow=True)
1776 self.pool.get('account.invoice').write(cr, uid, [context['default_res_id']], {'sent': True}, context=context)
1777 self.pool.get('account.invoice').message_post(cr, uid, [context['default_res_id']], body=_("Invoice sent"), context=context)
1778 return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
1780 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: