[MERGE]: Merged with trunk-addons.
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import time
23 from lxml import etree
24 import decimal_precision as dp
25
26 import netsvc
27 import pooler
28 from osv import fields, osv, orm
29 from tools.translate import _
30
31 class account_invoice(osv.osv):
32     def _amount_all(self, cr, uid, ids, name, args, context=None):
33         res = {}
34         for invoice in self.browse(cr, uid, ids, context=context):
35             res[invoice.id] = {
36                 'amount_untaxed': 0.0,
37                 'amount_tax': 0.0,
38                 'amount_total': 0.0
39             }
40             for line in invoice.invoice_line:
41                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
42             for line in invoice.tax_line:
43                 res[invoice.id]['amount_tax'] += line.amount
44             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
45         return res
46
47     def _get_journal(self, cr, uid, context=None):
48         if context is None:
49             context = {}
50         type_inv = context.get('type', 'out_invoice')
51         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
52         company_id = context.get('company_id', user.company_id.id)
53         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
54         refund_journal = {'out_invoice': False, 'in_invoice': False, 'out_refund': True, 'in_refund': True}
55         journal_obj = self.pool.get('account.journal')
56         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale')),
57                                             ('company_id', '=', company_id),
58                                             ('refund_journal', '=', refund_journal.get(type_inv, False))],
59                                                 limit=1)
60         return res and res[0] or False
61
62     def _get_currency(self, cr, uid, context=None):
63         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
64         if user.company_id:
65             return user.company_id.currency_id.id
66         return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=', 1.0)])[0]
67
68     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
69         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
70         tt = type2journal.get(type_inv, 'sale')
71         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
72         if not result:
73             raise osv.except_osv(_('No Analytic Journal !'),_("You must define an analytic journal of type '%s' !") % (tt,))
74         return result[0]
75
76     def _get_type(self, cr, uid, context=None):
77         if context is None:
78             context = {}
79         return context.get('type', 'out_invoice')
80
81     def _reconciled(self, cr, uid, ids, name, args, context=None):
82         res = {}
83         for id in ids:
84             res[id] = self.test_paid(cr, uid, [id])
85         return res
86
87     def _get_reference_type(self, cr, uid, context=None):
88         return [('none', _('Free Reference'))]
89
90     def _amount_residual(self, cr, uid, ids, name, args, context=None):
91         res = {}
92         if context is None:
93             context = {}
94
95         cur_obj = self.pool.get('res.currency')
96         data_inv = self.browse(cr, uid, ids, context=context)
97         for inv in data_inv:
98             debit = credit = 0.0
99             context.update({'date':inv.date_invoice})
100             context_unreconciled=context.copy()
101             for lines in inv.move_lines:
102                 debit_tmp = lines.debit
103                 credit_tmp = lines.credit
104                 # If currency conversion needed
105                 if inv.company_id.currency_id.id <> inv.currency_id.id:
106                     # If invoice paid, compute currency amount according to invoice date
107                     # otherwise, take the line date
108                     if not inv.reconciled:
109                         context.update({'date':lines.date})
110                     context_unreconciled.update({'date':lines.date})
111                     # If amount currency setted, compute for debit and credit in company currency
112                     if lines.amount_currency < 0:
113                         credit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False, context=context_unreconciled))
114                     elif lines.amount_currency > 0:
115                         debit_tmp=abs(cur_obj.compute(cr, uid, lines.currency_id.id, inv.company_id.currency_id.id, lines.amount_currency, round=False, context=context_unreconciled))
116                     # Then, recomput into invoice currency to avoid rounding trouble !
117                     debit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, debit_tmp, round=False, context=context)
118                     credit += cur_obj.compute(cr, uid, inv.company_id.currency_id.id, inv.currency_id.id, credit_tmp, round=False, context=context)
119                 else:
120                     debit+=debit_tmp
121                     credit+=credit_tmp
122
123             if not inv.amount_total:
124                 result = 0.0
125             elif inv.type in ('out_invoice','in_refund'):
126                 amount = credit-debit
127                 result = inv.amount_total - amount
128             else:
129                 amount = debit-credit
130                 result = inv.amount_total - amount
131             # Use is_zero function to avoid rounding trouble => should be fixed into ORM
132             res[inv.id] = not self.pool.get('res.currency').is_zero(cr, uid, inv.company_id.currency_id, result) and result or 0.0
133         return res
134
135     # Give Journal Items related to the payment reconciled to this invoice
136     # Return ids of partial and total payments related to the selected invoices
137     def _get_lines(self, cr, uid, ids, name, arg, context=None):
138         res = {}
139         for invoice in self.browse(cr, uid, ids, context=context):
140             id = invoice.id
141             res[id] = []
142             if not invoice.move_id:
143                 continue
144             data_lines = [x for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
145             partial_ids = []
146             for line in data_lines:
147                 ids_line = []
148                 if line.reconcile_id:
149                     ids_line = line.reconcile_id.line_id
150                 elif line.reconcile_partial_id:
151                     ids_line = line.reconcile_partial_id.line_partial_ids
152                 l = map(lambda x: x.id, ids_line)
153                 partial_ids.append(line.id)
154                 res[id] =[x for x in l if x <> line.id and x not in partial_ids]
155         return res
156
157     def _get_invoice_line(self, cr, uid, ids, context=None):
158         result = {}
159         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
160             result[line.invoice_id.id] = True
161         return result.keys()
162
163     def _get_invoice_tax(self, cr, uid, ids, context=None):
164         result = {}
165         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
166             result[tax.invoice_id.id] = True
167         return result.keys()
168
169     def _compute_lines(self, cr, uid, ids, name, args, context=None):
170         result = {}
171         for invoice in self.browse(cr, uid, ids, context=context):
172             src = []
173             lines = []
174             if invoice.move_id:
175                 for m in invoice.move_id.line_id:
176                     temp_lines = []
177                     if m.reconcile_id:
178                         temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
179                     elif m.reconcile_partial_id:
180                         temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
181                     lines += [x for x in temp_lines if x not in lines]
182                     src.append(m.id)
183
184             lines = filter(lambda x: x not in src, lines)
185             result[invoice.id] = lines
186         return result
187
188     def _get_invoice_from_line(self, cr, uid, ids, context=None):
189         move = {}
190         for line in self.pool.get('account.move.line').browse(cr, uid, ids, context=context):
191             if line.reconcile_partial_id:
192                 for line2 in line.reconcile_partial_id.line_partial_ids:
193                     move[line2.move_id.id] = True
194             if line.reconcile_id:
195                 for line2 in line.reconcile_id.line_id:
196                     move[line2.move_id.id] = True
197         invoice_ids = []
198         if move:
199             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
200         return invoice_ids
201
202     def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
203         move = {}
204         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids, context=context):
205             for line in r.line_partial_ids:
206                 move[line.move_id.id] = True
207             for line in r.line_id:
208                 move[line.move_id.id] = True
209
210         invoice_ids = []
211         if move:
212             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
213         return invoice_ids
214
215     _name = "account.invoice"
216     _description = 'Invoice'
217     _order = "id desc"
218
219     _columns = {
220         'name': fields.char('Description', size=64, select=True, readonly=True, states={'draft':[('readonly',False)]}),
221         'origin': fields.char('Source Document', size=64, help="Reference of the document that produced this invoice.", readonly=True, states={'draft':[('readonly',False)]}),
222         'type': fields.selection([
223             ('out_invoice','Customer Invoice'),
224             ('in_invoice','Supplier Invoice'),
225             ('out_refund','Customer Refund'),
226             ('in_refund','Supplier Refund'),
227             ],'Type', readonly=True, select=True, change_default=True),
228
229         'number': fields.related('move_id','name', type='char', readonly=True, size=64, relation='account.move', store=True, string='Number'),
230         'internal_number': fields.char('Invoice Number', size=32, readonly=True, help="Unique number of the invoice, computed automatically when the invoice is created."),
231         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
232         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
233             required=True, readonly=True, states={'draft':[('readonly',False)]}),
234         'comment': fields.text('Additional Information'),
235
236         'state': fields.selection([
237             ('draft','Draft'),
238             ('proforma','Pro-forma'),
239             ('proforma2','Pro-forma'),
240             ('open','Open'),
241             ('paid','Paid'),
242             ('cancel','Cancelled')
243             ],'State', select=True, readonly=True,
244             help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed Invoice. \
245             \n* The \'Pro-forma\' when invoice is in Pro-forma state,invoice does not have an invoice number. \
246             \n* The \'Open\' state is used when user create invoice,a invoice number is generated.Its in open state till user does not pay invoice. \
247             \n* The \'Paid\' state is set automatically when invoice is paid.\
248             \n* The \'Cancelled\' state is used when user cancel invoice.'),
249         'date_invoice': fields.date('Invoice Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]}, help="Keep empty to use the current date"),
250         'date_due': fields.date('Due Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 'close':[('readonly',True)]},
251             help="If you use payment terms, the due date will be computed automatically at the generation "\
252                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. The payment term may compute several due dates, for example 50% now, 50% in one month."),
253         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
254         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
255         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
256         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
257             help="If you use payment terms, the due date will be computed automatically at the generation "\
258                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
259                 "The payment term may compute several due dates, for example 50% now, 50% in one month."),
260         '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)]}),
261
262         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
263         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
264         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
265
266         'move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, select=1, help="Link to the automatically generated Journal Items."),
267         'amount_untaxed': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Untaxed',
268             store={
269                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
270                 'account.invoice.tax': (_get_invoice_tax, None, 20),
271                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
272             },
273             multi='all'),
274         'amount_tax': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Tax',
275             store={
276                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
277                 'account.invoice.tax': (_get_invoice_tax, None, 20),
278                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
279             },
280             multi='all'),
281         'amount_total': fields.function(_amount_all, method=True, digits_compute=dp.get_precision('Account'), string='Total',
282             store={
283                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
284                 'account.invoice.tax': (_get_invoice_tax, None, 20),
285                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 20),
286             },
287             multi='all'),
288         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
289         'journal_id': fields.many2one('account.journal', 'Journal', required=True, readonly=True, states={'draft':[('readonly',False)]}),
290         'company_id': fields.many2one('res.company', 'Company', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
291         'check_total': fields.float('Total', digits_compute=dp.get_precision('Account'), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
292         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
293             store={
294                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50), # Check if we can remove ?
295                 'account.move.line': (_get_invoice_from_line, None, 50),
296                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
297             }, help="The Journal Entry of the invoice have been totally reconciled with one or several Journal Entries of payment."),
298         'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
299             help='Bank Account Number, Company bank account if Invoice is customer or supplier refund, otherwise Partner bank account number.', readonly=True, states={'draft':[('readonly',False)]}),
300         'move_lines':fields.function(_get_lines, method=True, type='many2many', relation='account.move.line', string='Entry Lines'),
301         'residual': fields.function(_amount_residual, method=True, digits_compute=dp.get_precision('Account'), string='Residual',
302             store={
303                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 50),
304                 'account.invoice.tax': (_get_invoice_tax, None, 50),
305                 'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount'], 50),
306                 'account.move.line': (_get_invoice_from_line, None, 50),
307                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
308             },
309             help="Remaining amount due."),
310         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
311         'move_name': fields.char('Journal Entry', size=64, readonly=True, states={'draft':[('readonly',False)]}),
312         'user_id': fields.many2one('res.users', 'Salesman', readonly=True, states={'draft':[('readonly',False)]}),
313         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position', readonly=True, states={'draft':[('readonly',False)]})
314     }
315     _defaults = {
316         'type': _get_type,
317         'state': 'draft',
318         'journal_id': _get_journal,
319         'currency_id': _get_currency,
320         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
321         'reference_type': 'none',
322         'check_total': 0.0,
323         'internal_number': False,
324         'user_id': lambda s, cr, u, c: u,
325     }
326
327     def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
328         journal_obj = self.pool.get('account.journal')
329         if context is None:
330             context = {}
331
332         if context.get('active_model', '') in ['res.partner'] and context.get('active_ids', False) and context['active_ids']:
333             partner = self.pool.get(context['active_model']).read(cr, uid, context['active_ids'], ['supplier','customer'])[0]
334             if not view_type:
335                 view_id = self.pool.get('ir.ui.view').search(cr, uid, [('name', '=', 'account.invoice.tree')])
336                 view_type = 'tree'
337             if view_type == 'form':
338                 if partner['supplier'] and not partner['customer']:
339                     view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.supplier.form')])
340                 else:
341                     view_id = self.pool.get('ir.ui.view').search(cr,uid,[('name', '=', 'account.invoice.form')])
342         if view_id and isinstance(view_id, (list, tuple)):
343             view_id = view_id[0]
344         res = super(account_invoice,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
345
346         type = context.get('journal_type', 'sale')
347         for field in res['fields']:
348             if field == 'journal_id':
349                 journal_select = journal_obj._name_search(cr, uid, '', [('type', '=', type)], context=context, limit=None, name_get_uid=1)
350                 res['fields'][field]['selection'] = journal_select
351
352         if view_type == 'tree':
353             doc = etree.XML(res['arch'])
354             nodes = doc.xpath("//field[@name='partner_id']")
355             partner_string = _('Customer')
356             if context.get('type', 'out_invoice') in ('in_invoice', 'in_refund'):
357                 partner_string = _('Supplier')
358             for node in nodes:
359                 node.set('string', partner_string)
360             res['arch'] = etree.tostring(doc)
361         return res
362
363     def get_log_context(self, cr, uid, context=None):
364         if context is None:
365             context = {}
366         mob_obj = self.pool.get('ir.model.data')
367         res = mob_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
368         view_id = res and res[1] or False
369         context.update({'view_id': view_id})
370         return context
371
372     def create(self, cr, uid, vals, context=None):
373         if context is None:
374             context = {}
375         try:
376             res = super(account_invoice, self).create(cr, uid, vals, context)
377             for inv_id, name in self.name_get(cr, uid, [res], context=context):
378                 ctx = context.copy()
379                 if vals.get('type', 'in_invoice') in ('out_invoice', 'out_refund'):
380                     ctx = self.get_log_context(cr, uid, context=ctx)
381                 message = _("Invoice '%s' is waiting for validation.") % name
382                 self.log(cr, uid, inv_id, message, context=ctx)
383             return res
384         except Exception, e:
385             if '"journal_id" viol' in e.args[0]:
386                 raise orm.except_orm(_('Configuration Error!'),
387                      _('There is no Accounting Journal of type Sale/Purchase defined!'))
388             else:
389                 raise orm.except_orm(_('Unknown Error'), str(e))
390
391     def confirm_paid(self, cr, uid, ids, context=None):
392         self.write(cr, uid, ids, {'state':'paid'}, context=context)
393         for inv_id, name in self.name_get(cr, uid, ids, context=context):
394             message = _("Invoice '%s' is paid.") % name
395             self.log(cr, uid, inv_id, message)
396         return True
397
398     def unlink(self, cr, uid, ids, context=None):
399         invoices = self.read(cr, uid, ids, ['state'])
400         unlink_ids = []
401         for t in invoices:
402             if t['state'] in ('draft', 'cancel'):
403                 unlink_ids.append(t['id'])
404             else:
405                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) that are already opened or paid !'))
406         osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
407         return True
408
409     def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
410             date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
411         invoice_addr_id = False
412         contact_addr_id = False
413         partner_payment_term = False
414         acc_id = False
415         bank_id = False
416         fiscal_position = False
417
418         opt = [('uid', str(uid))]
419         if partner_id:
420
421             opt.insert(0, ('id', partner_id))
422             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
423             contact_addr_id = res['contact']
424             invoice_addr_id = res['invoice']
425             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
426             if company_id:
427                 if p.property_account_receivable.company_id.id != company_id and p.property_account_payable.company_id.id != company_id:
428                     property_obj = self.pool.get('ir.property')
429                     rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
430                     pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('res_id','=','res.partner,'+str(partner_id)+''),('company_id','=',company_id)])
431                     if not rec_pro_id:
432                         rec_pro_id = property_obj.search(cr,uid,[('name','=','property_account_receivable'),('company_id','=',company_id)])
433                     if not pay_pro_id:
434                         pay_pro_id = property_obj.search(cr,uid,[('name','=','property_account_payable'),('company_id','=',company_id)])
435                     rec_line_data = property_obj.read(cr,uid,rec_pro_id,['name','value_reference','res_id'])
436                     pay_line_data = property_obj.read(cr,uid,pay_pro_id,['name','value_reference','res_id'])
437                     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
438                     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
439                     if not rec_res_id and not pay_res_id:
440                         raise osv.except_osv(_('Configuration Error !'),
441                             _('Can not find account chart for this company, Please Create account.'))
442                     account_obj = self.pool.get('account.account')
443                     rec_obj_acc = account_obj.browse(cr, uid, [rec_res_id])
444                     pay_obj_acc = account_obj.browse(cr, uid, [pay_res_id])
445                     p.property_account_receivable = rec_obj_acc[0]
446                     p.property_account_payable = pay_obj_acc[0]
447
448             if type in ('out_invoice', 'out_refund'):
449                 acc_id = p.property_account_receivable.id
450             else:
451                 acc_id = p.property_account_payable.id
452             fiscal_position = p.property_account_position and p.property_account_position.id or False
453             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
454             if p.bank_ids:
455                 bank_id = p.bank_ids[0].id
456
457         result = {'value': {
458             'address_contact_id': contact_addr_id,
459             'address_invoice_id': invoice_addr_id,
460             'account_id': acc_id,
461             'payment_term': partner_payment_term,
462             'fiscal_position': fiscal_position
463             }
464         }
465
466         if type in ('in_invoice', 'in_refund'):
467             result['value']['partner_bank_id'] = bank_id
468
469         if payment_term != partner_payment_term:
470             if partner_payment_term:
471                 to_update = self.onchange_payment_term_date_invoice(
472                     cr, uid, ids, partner_payment_term, date_invoice)
473                 result['value'].update(to_update['value'])
474             else:
475                 result['value']['date_due'] = False
476
477         if partner_bank_id != bank_id:
478             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
479             result['value'].update(to_update['value'])
480         return result
481
482     def onchange_journal_id(self, cr, uid, ids, journal_id=False):
483         result = {}
484         if journal_id:
485             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
486             currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id
487             result = {'value': {
488                     'currency_id': currency_id,
489                     }
490                 }
491         return result
492
493     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
494         if not payment_term_id:
495             return {}
496         res = {}
497         pt_obj = self.pool.get('account.payment.term')
498         if not date_invoice:
499             date_invoice = time.strftime('%Y-%m-%d')
500
501         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
502
503         if pterm_list:
504             pterm_list = [line[0] for line in pterm_list]
505             pterm_list.sort()
506             res = {'value':{'date_due': pterm_list[-1]}}
507         else:
508              raise osv.except_osv(_('Data Insufficient !'), _('The Payment Term of Supplier does not have Payment Term Lines(Computation) defined !'))
509         return res
510
511     def onchange_invoice_line(self, cr, uid, ids, lines):
512         return {}
513
514     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id=False):
515         return {'value': {}}
516
517     def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
518         val = {}
519         dom = {}
520         obj_journal = self.pool.get('account.journal')
521         account_obj = self.pool.get('account.account')
522         inv_line_obj = self.pool.get('account.invoice.line')
523         if company_id and part_id and type:
524             acc_id = False
525             partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
526             if partner_obj.property_account_payable and partner_obj.property_account_receivable:
527                 if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
528                     property_obj = self.pool.get('ir.property')
529                     rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
530                     pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
531                     if not rec_pro_id:
532                         rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
533                     if not pay_pro_id:
534                         pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
535                     rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value_reference','res_id'])
536                     pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value_reference','res_id'])
537                     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
538                     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
539                     if not rec_res_id and not pay_res_id:
540                         raise osv.except_osv(_('Configuration Error !'),
541                             _('Can not find account chart for this company, Please Create account.'))
542                     if type in ('out_invoice', 'out_refund'):
543                         acc_id = rec_res_id
544                     else:
545                         acc_id = pay_res_id
546                     val= {'account_id': acc_id}
547             if ids:
548                 if company_id:
549                     inv_obj = self.browse(cr,uid,ids)
550                     for line in inv_obj[0].invoice_line:
551                         if line.account_id:
552                             if line.account_id.company_id.id != company_id:
553                                 result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
554                                 if not result_id:
555                                     raise osv.except_osv(_('Configuration Error !'),
556                                         _('Can not find account chart for this company in invoice line account, Please Create account.'))
557                                 inv_line_obj.write(cr, uid, [line.id], {'account_id': result_id[0]})
558             else:
559                 if invoice_line:
560                     for inv_line in invoice_line:
561                         obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
562                         if obj_l.company_id.id != company_id:
563                             raise osv.except_osv(_('Configuration Error !'),
564                                 _('Invoice line account company does not match with invoice company.'))
565                         else:
566                             continue
567         if company_id and type:
568             if type in ('out_invoice'):
569                 journal_type = 'sale'
570             elif type in ('out_refund'):
571                 journal_type = 'sale_refund'
572             elif type in ('in_refund'):
573                 journal_type = 'purchase_refund'
574             else:
575                 journal_type = 'purchase'
576             journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
577             if journal_ids:
578                 val['journal_id'] = journal_ids[0]
579             else:
580                 raise osv.except_osv(_('Configuration Error !'), (_('Can\'t find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Financial Accounting\Accounts\Journals.') % (journal_type)))
581             dom = {'journal_id':  [('id', 'in', journal_ids)]}
582         else:
583             journal_ids = obj_journal.search(cr, uid, [])
584
585         if currency_id and company_id:
586             currency = self.pool.get('res.currency').browse(cr, uid, currency_id)
587             if currency.company_id and currency.company_id.id != company_id:
588                 val['currency_id'] = False
589             else:
590                 val['currency_id'] = currency.id
591         if company_id:
592             company = self.pool.get('res.company').browse(cr, uid, company_id)
593             if company.currency_id.company_id and company.currency_id.company_id.id != company_id:
594                 val['currency_id'] = False
595             else:
596                 val['currency_id'] = company.currency_id.id
597
598         return {'value': val, 'domain': dom}
599
600     # go from canceled state to draft state
601     def action_cancel_draft(self, cr, uid, ids, *args):
602         self.write(cr, uid, ids, {'state':'draft'})
603         wf_service = netsvc.LocalService("workflow")
604         for inv_id in ids:
605             wf_service.trg_delete(uid, 'account.invoice', inv_id, cr)
606             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
607         return True
608
609     # Workflow stuff
610     #################
611
612     # return the ids of the move lines which has the same account than the invoice
613     # whose id is in ids
614     def move_line_id_payment_get(self, cr, uid, ids, *args):
615         if not ids: return []
616         result = self.move_line_id_payment_gets(cr, uid, ids, *args)
617         return result.get(ids[0], [])
618
619     def move_line_id_payment_gets(self, cr, uid, ids, *args):
620         res = {}
621         if not ids: return res
622         cr.execute('SELECT i.id, l.id '\
623                    'FROM account_move_line l '\
624                    'LEFT JOIN account_invoice i ON (i.move_id=l.move_id) '\
625                    'WHERE i.id IN %s '\
626                    'AND l.account_id=i.account_id',
627                    (tuple(ids),))
628         for r in cr.fetchall():
629             res.setdefault(r[0], [])
630             res[r[0]].append( r[1] )
631         return res
632
633     def copy(self, cr, uid, id, default={}, context=None):
634         default.update({
635             'state':'draft',
636             'number':False,
637             'move_id':False,
638             'move_name':False,
639             'internal_number': False,
640         })
641         if 'date_invoice' not in default:
642             default.update({
643                 'date_invoice':False
644             })
645         if 'date_due' not in default:
646             default.update({
647                 'date_due':False
648             })
649         return super(account_invoice, self).copy(cr, uid, id, default, context)
650
651     def test_paid(self, cr, uid, ids, *args):
652         res = self.move_line_id_payment_get(cr, uid, ids)
653         if not res:
654             return False
655         ok = True
656         for id in res:
657             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
658             ok = ok and  bool(cr.fetchone()[0])
659         return ok
660
661     def button_reset_taxes(self, cr, uid, ids, context=None):
662         if context is None:
663             context = {}
664         ctx = context.copy()
665         ait_obj = self.pool.get('account.invoice.tax')
666         for id in ids:
667             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s AND manual is False", (id,))
668             partner = self.browse(cr, uid, id, context=ctx).partner_id
669             if partner.lang:
670                 ctx.update({'lang': partner.lang})
671             for taxe in ait_obj.compute(cr, uid, id, context=ctx).values():
672                 ait_obj.create(cr, uid, taxe)
673         # Update the stored value (fields.function), so we write to trigger recompute
674         self.pool.get('account.invoice').write(cr, uid, ids, {'invoice_line':[]}, context=ctx)
675         return True
676
677     def button_compute(self, cr, uid, ids, context=None, set_total=False):
678         self.button_reset_taxes(cr, uid, ids, context)
679         for inv in self.browse(cr, uid, ids, context=context):
680             if set_total:
681                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
682         return True
683
684     def _convert_ref(self, cr, uid, ref):
685         return (ref or '').replace('/','')
686
687     def _get_analytic_lines(self, cr, uid, id):
688         inv = self.browse(cr, uid, id)
689         cur_obj = self.pool.get('res.currency')
690
691         company_currency = inv.company_id.currency_id.id
692         if inv.type in ('out_invoice', 'in_refund'):
693             sign = 1
694         else:
695             sign = -1
696
697         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
698         for il in iml:
699             if il['account_analytic_id']:
700                 if inv.type in ('in_invoice', 'in_refund'):
701                     ref = inv.reference
702                 else:
703                     ref = self._convert_ref(cr, uid, inv.number)
704                 if not inv.journal_id.analytic_journal_id:
705                     raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (inv.journal_id.name,))
706                 il['analytic_lines'] = [(0,0, {
707                     'name': il['name'],
708                     'date': inv['date_invoice'],
709                     'account_id': il['account_analytic_id'],
710                     'unit_amount': il['quantity'],
711                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
712                     'product_id': il['product_id'],
713                     'product_uom_id': il['uos_id'],
714                     'general_account_id': il['account_id'],
715                     'journal_id': inv.journal_id.analytic_journal_id.id,
716                     'ref': ref,
717                 })]
718         return iml
719
720     def action_date_assign(self, cr, uid, ids, *args):
721         for inv in self.browse(cr, uid, ids):
722             res = self.onchange_payment_term_date_invoice(cr, uid, inv.id, inv.payment_term.id, inv.date_invoice)
723             if res and res['value']:
724                 self.write(cr, uid, [inv.id], res['value'])
725         return True
726
727     def finalize_invoice_move_lines(self, cr, uid, invoice_browse, move_lines):
728         """finalize_invoice_move_lines(cr, uid, invoice, move_lines) -> move_lines
729         Hook method to be overridden in additional modules to verify and possibly alter the
730         move lines to be created by an invoice, for special cases.
731         :param invoice_browse: browsable record of the invoice that is generating the move lines
732         :param move_lines: list of dictionaries with the account.move.lines (as for create())
733         :return: the (possibly updated) final move_lines to create for this invoice
734         """
735         return move_lines
736
737     def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
738         if not inv.tax_line:
739             for tax in compute_taxes.values():
740                 ait_obj.create(cr, uid, tax)
741         else:
742             tax_key = []
743             for tax in inv.tax_line:
744                 if tax.manual:
745                     continue
746                 key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
747                 tax_key.append(key)
748                 if not key in compute_taxes:
749                     raise osv.except_osv(_('Warning !'), _('Global taxes defined, but are not in invoice lines !'))
750                 base = compute_taxes[key]['base']
751                 if abs(base - tax.base) > inv.company_id.currency_id.rounding:
752                     raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
753             for key in compute_taxes:
754                 if not key in tax_key:
755                     raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
756
757     def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines):
758         total = 0
759         total_currency = 0
760         cur_obj = self.pool.get('res.currency')
761         for i in invoice_move_lines:
762             if inv.currency_id.id != company_currency:
763                 i['currency_id'] = inv.currency_id.id
764                 i['amount_currency'] = i['price']
765                 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
766                         company_currency, i['price'],
767                         context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
768             else:
769                 i['amount_currency'] = False
770                 i['currency_id'] = False
771             i['ref'] = ref
772             if inv.type in ('out_invoice','in_refund'):
773                 total += i['price']
774                 total_currency += i['amount_currency'] or i['price']
775                 i['price'] = - i['price']
776             else:
777                 total -= i['price']
778                 total_currency -= i['amount_currency'] or i['price']
779         return total, total_currency, invoice_move_lines
780
781     def inv_line_characteristic_hashcode(self, invoice, invoice_line):
782         """Overridable hashcode generation for invoice lines. Lines having the same hashcode
783         will be grouped together if the journal has the 'group line' option. Of course a module
784         can add fields to invoice lines that would need to be tested too before merging lines
785         or not."""
786         return "%s-%s-%s-%s-%s"%(
787             invoice_line['account_id'],
788             invoice_line.get('tax_code_id',"False"),
789             invoice_line.get('product_id',"False"),
790             invoice_line.get('analytic_account_id',"False"),
791             invoice_line.get('date_maturity',"False"))
792
793     def group_lines(self, cr, uid, iml, line, inv):
794         """Merge account move lines (and hence analytic lines) if invoice line hashcodes are equals"""
795         if inv.journal_id.group_invoice_lines:
796             line2 = {}
797             for x, y, l in line:
798                 tmp = self.inv_line_characteristic_hashcode(inv, l)
799
800                 if tmp in line2:
801                     am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
802                     line2[tmp]['debit'] = (am > 0) and am or 0.0
803                     line2[tmp]['credit'] = (am < 0) and -am or 0.0
804                     line2[tmp]['tax_amount'] += l['tax_amount']
805                     line2[tmp]['analytic_lines'] += l['analytic_lines']
806                 else:
807                     line2[tmp] = l
808             line = []
809             for key, val in line2.items():
810                 line.append((0,0,val))
811         return line
812
813     def action_move_create(self, cr, uid, ids, *args):
814         """Creates invoice related analytics and financial move lines"""
815         ait_obj = self.pool.get('account.invoice.tax')
816         cur_obj = self.pool.get('res.currency')
817         context = {}
818         for inv in self.browse(cr, uid, ids):
819             if not inv.journal_id.sequence_id:
820                 raise osv.except_osv(_('Error !'), _('Please define sequence on invoice journal'))
821             if not inv.invoice_line:
822                 raise osv.except_osv(_('No Invoice Lines !'), _('Please create some invoice lines.'))
823             if inv.move_id:
824                 continue
825
826             if not inv.date_invoice:
827                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
828             company_currency = inv.company_id.currency_id.id
829             # create the analytical lines
830             # one move line per invoice line
831             iml = self._get_analytic_lines(cr, uid, inv.id)
832             # check if taxes are all computed
833             ctx = context.copy()
834             ctx.update({'lang': inv.partner_id.lang})
835             compute_taxes = ait_obj.compute(cr, uid, inv.id, context=ctx)
836             self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
837
838             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
839                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
840
841             if inv.payment_term:
842                 total_fixed = total_percent = 0
843                 for line in inv.payment_term.line_ids:
844                     if line.value == 'fixed':
845                         total_fixed += line.value_amount
846                     if line.value == 'procent':
847                         total_percent += line.value_amount
848                 total_fixed = (total_fixed * 100) / (inv.amount_total or 1.0)
849                 if (total_fixed + total_percent) > 100:
850                     raise osv.except_osv(_('Error !'), _("Cannot create the invoice !\nThe payment term defined gives a computed amount greater than the total invoiced amount."))
851
852             # one move line per tax line
853             iml += ait_obj.move_line_get(cr, uid, inv.id)
854
855             entry_type = ''
856             if inv.type in ('in_invoice', 'in_refund'):
857                 ref = inv.reference
858                 entry_type = 'journal_pur_voucher'
859                 if inv.type == 'in_refund':
860                     entry_type = 'cont_voucher'
861             else:
862                 ref = self._convert_ref(cr, uid, inv.number)
863                 entry_type = 'journal_sale_vou'
864                 if inv.type == 'out_refund':
865                     entry_type = 'cont_voucher'
866
867             diff_currency_p = inv.currency_id.id <> company_currency
868             # create one move line for the total and possibly adjust the other lines amount
869             total = 0
870             total_currency = 0
871             total, total_currency, iml = self.compute_invoice_totals(cr, uid, inv, company_currency, ref, iml)
872             acc_id = inv.account_id.id
873
874             name = inv['name'] or '/'
875             totlines = False
876             if inv.payment_term:
877                 totlines = self.pool.get('account.payment.term').compute(cr,
878                         uid, inv.payment_term.id, total, inv.date_invoice or False)
879             if totlines:
880                 res_amount_currency = total_currency
881                 i = 0
882                 for t in totlines:
883                     if inv.currency_id.id != company_currency:
884                         amount_currency = cur_obj.compute(cr, uid,
885                                 company_currency, inv.currency_id.id, t[1])
886                     else:
887                         amount_currency = False
888
889                     # last line add the diff
890                     res_amount_currency -= amount_currency or 0
891                     i += 1
892                     if i == len(totlines):
893                         amount_currency += res_amount_currency
894
895                     iml.append({
896                         'type': 'dest',
897                         'name': name,
898                         'price': t[1],
899                         'account_id': acc_id,
900                         'date_maturity': t[0],
901                         'amount_currency': diff_currency_p \
902                                 and  amount_currency or False,
903                         'currency_id': diff_currency_p \
904                                 and inv.currency_id.id or False,
905                         'ref': ref,
906                     })
907             else:
908                 iml.append({
909                     'type': 'dest',
910                     'name': name,
911                     'price': total,
912                     'account_id': acc_id,
913                     'date_maturity': inv.date_due or False,
914                     'amount_currency': diff_currency_p \
915                             and total_currency or False,
916                     'currency_id': diff_currency_p \
917                             and inv.currency_id.id or False,
918                     'ref': ref
919             })
920
921             date = inv.date_invoice or time.strftime('%Y-%m-%d')
922             part = inv.partner_id.id
923
924             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})),iml)
925
926             line = self.group_lines(cr, uid, iml, line, inv)
927
928             journal_id = inv.journal_id.id
929             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
930             if journal.centralisation:
931                 raise osv.except_osv(_('UserError'),
932                         _('Cannot create invoice move on centralised journal'))
933
934             line = self.finalize_invoice_move_lines(cr, uid, inv, line)
935
936             move = {
937                 'ref': inv.reference and inv.reference or inv.name,
938                 'line_id': line,
939                 'journal_id': journal_id,
940                 'date': date,
941                 'type': entry_type,
942                 'narration':inv.comment
943             }
944             period_id = inv.period_id and inv.period_id.id or False
945             if not period_id:
946                 period_ids = self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or time.strftime('%Y-%m-%d')),('date_stop','>=',inv.date_invoice or time.strftime('%Y-%m-%d'))])
947                 if period_ids:
948                     period_id = period_ids[0]
949             if period_id:
950                 move['period_id'] = period_id
951                 for i in line:
952                     i[2]['period_id'] = period_id
953
954             move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
955             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
956             # make the invoice point to that move
957             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
958             # Pass invoice in context in method post: used if you want to get the same
959             # account move reference when creating the same invoice after a cancelled one:
960             self.pool.get('account.move').post(cr, uid, [move_id], context={'invoice':inv})
961         self._log_event(cr, uid, ids)
962         return True
963
964     def line_get_convert(self, cr, uid, x, part, date, context=None):
965         return {
966             'date_maturity': x.get('date_maturity', False),
967             'partner_id':part,
968             'name':x['name'][:64],
969             'date': date,
970             'debit':x['price']>0 and x['price'],
971             'credit':x['price']<0 and -x['price'],
972             'account_id':x['account_id'],
973             'analytic_lines':x.get('analytic_lines', []),
974             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
975             'currency_id':x.get('currency_id', False),
976             'tax_code_id': x.get('tax_code_id', False),
977             'tax_amount': x.get('tax_amount', False),
978             'ref':x.get('ref',False),
979             'quantity':x.get('quantity',1.00),
980             'product_id':x.get('product_id', False),
981             'product_uom_id':x.get('uos_id',False),
982             'analytic_account_id':x.get('account_analytic_id',False),
983         }
984
985     def action_number(self, cr, uid, ids, context=None):
986         if context is None:
987             context = {}
988         #TODO: not correct fix but required a frech values before reading it.
989         self.write(cr, uid, ids, {})
990
991         for obj_inv in self.browse(cr, uid, ids, context=context):
992             id = obj_inv.id
993             invtype = obj_inv.type
994             number = obj_inv.number
995             move_id = obj_inv.move_id and obj_inv.move_id.id or False
996             reference = obj_inv.reference or ''
997
998             self.write(cr, uid, ids, {'internal_number':number})
999
1000             if invtype in ('in_invoice', 'in_refund'):
1001                 if not reference:
1002                     ref = self._convert_ref(cr, uid, number)
1003                 else:
1004                     ref = reference
1005             else:
1006                 ref = self._convert_ref(cr, uid, number)
1007
1008             cr.execute('UPDATE account_move SET ref=%s ' \
1009                     'WHERE id=%s AND (ref is null OR ref = \'\')',
1010                     (ref, move_id))
1011             cr.execute('UPDATE account_move_line SET ref=%s ' \
1012                     'WHERE move_id=%s AND (ref is null OR ref = \'\')',
1013                     (ref, move_id))
1014             cr.execute('UPDATE account_analytic_line SET ref=%s ' \
1015                     'FROM account_move_line ' \
1016                     'WHERE account_move_line.move_id = %s ' \
1017                         'AND account_analytic_line.move_id = account_move_line.id',
1018                         (ref, move_id))
1019
1020             for inv_id, name in self.name_get(cr, uid, [id]):
1021                 ctx = context.copy()
1022                 if obj_inv.type in ('out_invoice', 'out_refund'):
1023                     ctx = self.get_log_context(cr, uid, context=ctx)
1024                 message = _('Invoice ') + " '" + name + "' "+ _("is validated.")
1025                 self.log(cr, uid, inv_id, message, context=ctx)
1026         return True
1027
1028     def action_cancel(self, cr, uid, ids, *args):
1029         account_move_obj = self.pool.get('account.move')
1030         invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids'])
1031         for i in invoices:
1032             if i['move_id']:
1033                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
1034                 # delete the move this invoice was pointing to
1035                 # Note that the corresponding move_lines and move_reconciles
1036                 # will be automatically deleted too
1037                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
1038             if i['payment_ids']:
1039                 account_move_line_obj = self.pool.get('account.move.line')
1040                 pay_ids = account_move_line_obj.browse(cr, uid, i['payment_ids'])
1041                 for move_line in pay_ids:
1042                     if move_line.reconcile_partial_id and move_line.reconcile_partial_id.line_partial_ids:
1043                         raise osv.except_osv(_('Error !'), _('You cannot cancel the Invoice which is Partially Paid! You need to unreconcile concerned payment entries!'))
1044
1045         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
1046         self._log_event(cr, uid, ids, -1.0, 'Cancel Invoice')
1047         return True
1048
1049     ###################
1050
1051     def list_distinct_taxes(self, cr, uid, ids):
1052         invoices = self.browse(cr, uid, ids)
1053         taxes = {}
1054         for inv in invoices:
1055             for tax in inv.tax_line:
1056                 if not tax['name'] in taxes:
1057                     taxes[tax['name']] = {'name': tax['name']}
1058         return taxes.values()
1059
1060     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
1061         #TODO: implement messages system
1062         return True
1063
1064     def name_get(self, cr, uid, ids, context=None):
1065         if not ids:
1066             return []
1067         types = {
1068                 'out_invoice': 'CI: ',
1069                 'in_invoice': 'SI: ',
1070                 'out_refund': 'OR: ',
1071                 'in_refund': 'SR: ',
1072                 }
1073         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')]
1074
1075     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1076         if not args:
1077             args = []
1078         if context is None:
1079             context = {}
1080         ids = []
1081         if name:
1082             ids = self.search(cr, user, [('number','=',name)] + args, limit=limit, context=context)
1083         if not ids:
1084             ids = self.search(cr, user, [('name',operator,name)] + args, limit=limit, context=context)
1085         return self.name_get(cr, user, ids, context)
1086
1087     def _refund_cleanup_lines(self, cr, uid, lines):
1088         for line in lines:
1089             del line['id']
1090             del line['invoice_id']
1091             for field in ('company_id', 'partner_id', 'account_id', 'product_id',
1092                           'uos_id', 'account_analytic_id', 'tax_code_id', 'base_code_id'):
1093                 line[field] = line.get(field, False) and line[field][0]
1094             if 'invoice_line_tax_id' in line:
1095                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
1096         return map(lambda x: (0,0,x), lines)
1097
1098     def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
1099         invoices = self.read(cr, uid, ids, ['name', 'type', 'number', 'reference', 'comment', 'date_due', 'partner_id', 'address_contact_id', 'address_invoice_id', 'partner_contact', 'partner_insite', 'partner_ref', 'payment_term', 'account_id', 'currency_id', 'invoice_line', 'tax_line', 'journal_id'])
1100         obj_invoice_line = self.pool.get('account.invoice.line')
1101         obj_invoice_tax = self.pool.get('account.invoice.tax')
1102         obj_journal = self.pool.get('account.journal')
1103         new_ids = []
1104         for invoice in invoices:
1105             del invoice['id']
1106
1107             type_dict = {
1108                 'out_invoice': 'out_refund', # Customer Invoice
1109                 'in_invoice': 'in_refund',   # Supplier Invoice
1110                 'out_refund': 'out_invoice', # Customer Refund
1111                 'in_refund': 'in_invoice',   # Supplier Refund
1112             }
1113
1114             invoice_lines = obj_invoice_line.read(cr, uid, invoice['invoice_line'])
1115             invoice_lines = self._refund_cleanup_lines(cr, uid, invoice_lines)
1116
1117             tax_lines = obj_invoice_tax.read(cr, uid, invoice['tax_line'])
1118             tax_lines = filter(lambda l: l['manual'], tax_lines)
1119             tax_lines = self._refund_cleanup_lines(cr, uid, tax_lines)
1120             if journal_id:
1121                 refund_journal_ids = [journal_id]
1122             elif invoice['type'] == 'in_invoice':
1123                 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','purchase_refund')])
1124             else:
1125                 refund_journal_ids = obj_journal.search(cr, uid, [('type','=','sale_refund')])
1126
1127             if not date:
1128                 date = time.strftime('%Y-%m-%d')
1129             invoice.update({
1130                 'type': type_dict[invoice['type']],
1131                 'date_invoice': date,
1132                 'state': 'draft',
1133                 'number': False,
1134                 'invoice_line': invoice_lines,
1135                 'tax_line': tax_lines,
1136                 'journal_id': refund_journal_ids
1137             })
1138             if period_id:
1139                 invoice.update({
1140                     'period_id': period_id,
1141                 })
1142             if description:
1143                 invoice.update({
1144                     'name': description,
1145                 })
1146             # take the id part of the tuple returned for many2one fields
1147             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
1148                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
1149                 invoice[field] = invoice[field] and invoice[field][0]
1150             # create the new invoice
1151             new_ids.append(self.create(cr, uid, invoice))
1152         return new_ids
1153
1154     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=''):
1155         if context is None:
1156             context = {}
1157         #TODO check if we can use different period for payment and the writeoff line
1158         assert len(ids)==1, "Can only pay one invoice at a time"
1159         invoice = self.browse(cr, uid, ids[0], context=context)
1160         src_account_id = invoice.account_id.id
1161         # Take the seq as name for move
1162         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
1163         direction = types[invoice.type]
1164         #take the choosen date
1165         if 'date_p' in context and context['date_p']:
1166             date=context['date_p']
1167         else:
1168             date=time.strftime('%Y-%m-%d')
1169
1170         # Take the amount in currency and the currency of the payment
1171         if 'amount_currency' in context and context['amount_currency'] and 'currency_id' in context and context['currency_id']:
1172             amount_currency = context['amount_currency']
1173             currency_id = context['currency_id']
1174         else:
1175             amount_currency = False
1176             currency_id = False
1177
1178         pay_journal = self.pool.get('account.journal').read(cr, uid, pay_journal_id, ['type'], context=context)
1179         if invoice.type in ('in_invoice', 'out_invoice'):
1180             if pay_journal['type'] == 'bank':
1181                 entry_type = 'bank_pay_voucher' # Bank payment
1182             else:
1183                 entry_type = 'pay_voucher' # Cash payment
1184         else:
1185             entry_type = 'cont_voucher'
1186         if invoice.type in ('in_invoice', 'in_refund'):
1187             ref = invoice.reference
1188         else:
1189             ref = self._convert_ref(cr, uid, invoice.number)
1190         # Pay attention to the sign for both debit/credit AND amount_currency
1191         l1 = {
1192             'debit': direction * pay_amount>0 and direction * pay_amount,
1193             'credit': direction * pay_amount<0 and - direction * pay_amount,
1194             'account_id': src_account_id,
1195             'partner_id': invoice.partner_id.id,
1196             'ref':ref,
1197             'date': date,
1198             'currency_id':currency_id,
1199             'amount_currency':amount_currency and direction * amount_currency or 0.0,
1200             'company_id': invoice.company_id.id,
1201         }
1202         l2 = {
1203             'debit': direction * pay_amount<0 and - direction * pay_amount,
1204             'credit': direction * pay_amount>0 and direction * pay_amount,
1205             'account_id': pay_account_id,
1206             'partner_id': invoice.partner_id.id,
1207             'ref':ref,
1208             'date': date,
1209             'currency_id':currency_id,
1210             'amount_currency':amount_currency and - direction * amount_currency or 0.0,
1211             'company_id': invoice.company_id.id,
1212         }
1213
1214         if not name:
1215             name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
1216         l1['name'] = name
1217         l2['name'] = name
1218
1219         lines = [(0, 0, l1), (0, 0, l2)]
1220         move = {'ref': ref, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date, 'type': entry_type}
1221         move_id = self.pool.get('account.move').create(cr, uid, move, context=context)
1222
1223         line_ids = []
1224         total = 0.0
1225         line = self.pool.get('account.move.line')
1226         move_ids = [move_id,]
1227         if invoice.move_id:
1228             move_ids.append(invoice.move_id.id)
1229         cr.execute('SELECT id FROM account_move_line '\
1230                    'WHERE move_id IN %s',
1231                    ((move_id, invoice.move_id.id),))
1232         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
1233         for l in lines+invoice.payment_ids:
1234             if l.account_id.id == src_account_id:
1235                 line_ids.append(l.id)
1236                 total += (l.debit or 0.0) - (l.credit or 0.0)
1237
1238         inv_id, name = self.name_get(cr, uid, [invoice.id], context=context)[0]
1239         if (not round(total,self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))) or writeoff_acc_id:
1240             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
1241         else:
1242             code = invoice.currency_id.symbol
1243             # TODO: use currency's formatting function
1244             msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining)") % \
1245                     (name, pay_amount, code, invoice.amount_total, code, total, code)
1246             self.log(cr, uid, inv_id,  msg)
1247             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
1248
1249         # Update the stored value (fields.function), so we write to trigger recompute
1250         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
1251         return True
1252 account_invoice()
1253
1254 class account_invoice_line(osv.osv):
1255     def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1256         res = {}
1257         tax_obj = self.pool.get('account.tax')
1258         cur_obj = self.pool.get('res.currency')
1259         for line in self.browse(cr, uid, ids):
1260             price = line.price_unit * (1-(line.discount or 0.0)/100.0)
1261             taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity, product=line.product_id, address_id=line.invoice_id.address_invoice_id, partner=line.invoice_id.partner_id)
1262             res[line.id] = taxes['total']
1263             if line.invoice_id:
1264                 cur = line.invoice_id.currency_id
1265                 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
1266         return res
1267
1268     def _price_unit_default(self, cr, uid, context=None):
1269         if context is None:
1270             context = {}
1271         if 'check_total' in context:
1272             t = context['check_total']
1273             for l in context.get('invoice_line', {}):
1274                 if isinstance(l, (list, tuple)) and len(l) >= 3 and l[2]:
1275                     tax_obj = self.pool.get('account.tax')
1276                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
1277                     t = t - (p * l[2].get('quantity'))
1278                     taxes = l[2].get('invoice_line_tax_id')
1279                     if len(taxes[0]) >= 3 and taxes[0][2]:
1280                         taxes = tax_obj.browse(cr, uid, taxes[0][2])
1281                         for tax in tax_obj.compute_all(cr, uid, taxes, p,l[2].get('quantity'), context.get('address_invoice_id', False), l[2].get('product_id', False), context.get('partner_id', False))['taxes']:
1282                             t = t - tax['amount']
1283             return t
1284         return 0
1285
1286     _name = "account.invoice.line"
1287     _description = "Invoice Line"
1288     _columns = {
1289         'name': fields.char('Description', size=256, required=True),
1290         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
1291         'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True),
1292         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
1293         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
1294         '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."),
1295         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account')),
1296         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', type="float",
1297             digits_compute= dp.get_precision('Account'), store=True),
1298         'quantity': fields.float('Quantity', required=True),
1299         'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Account')),
1300         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
1301         'note': fields.text('Notes'),
1302         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
1303         'company_id': fields.related('invoice_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1304         'partner_id': fields.related('invoice_id','partner_id',type='many2one',relation='res.partner',string='Partner',store=True)
1305     }
1306     _defaults = {
1307         'quantity': 1,
1308         'discount': 0.0,
1309         'price_unit': _price_unit_default,
1310     }
1311
1312     def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, address_invoice_id=False, currency_id=False, context=None):
1313         if context is None:
1314             context = {}
1315         company_id = context.get('company_id',False)
1316         if not partner_id:
1317             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
1318         if not product:
1319             if type in ('in_invoice', 'in_refund'):
1320                 return {'value': {'categ_id': False}, 'domain':{'product_uom':[]}}
1321             else:
1322                 return {'value': {'price_unit': 0.0, 'categ_id': False}, 'domain':{'product_uom':[]}}
1323         part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
1324         fpos_obj = self.pool.get('account.fiscal.position')
1325         fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
1326
1327         if part.lang:
1328             context.update({'lang': part.lang})
1329         result = {}
1330         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
1331
1332         if company_id:
1333             property_obj = self.pool.get('ir.property')
1334             account_obj = self.pool.get('account.account')
1335             in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1336             if not in_pro_id:
1337                 in_pro_id = property_obj.search(cr, uid, [('name','=','property_account_income_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1338             exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense'),('res_id','=','product.template,'+str(res.product_tmpl_id.id)+''),('company_id','=',company_id)])
1339             if not exp_pro_id:
1340                 exp_pro_id = property_obj.search(cr, uid, [('name','=','property_account_expense_categ'),('res_id','=','product.template,'+str(res.categ_id.id)+''),('company_id','=',company_id)])
1341
1342             if not in_pro_id:
1343                 in_acc = res.product_tmpl_id.property_account_income
1344                 in_acc_cate = res.categ_id.property_account_income_categ
1345                 if in_acc:
1346                     app_acc_in = in_acc
1347                 else:
1348                     app_acc_in = in_acc_cate
1349             else:
1350                 # Get the fields from the ir.property record
1351                 my_value = property_obj.read(cr,uid,in_pro_id,['name','value_reference','res_id'])
1352                 # Parse the value_reference field to get the ID of the account.account record
1353                 account_id = int (my_value[0]["value_reference"].split(",")[1])
1354                 # Use the ID of the account.account record in the browse for the account.account record
1355                 app_acc_in = account_obj.browse(cr, uid, account_id, context=context)
1356             if not exp_pro_id:
1357                 ex_acc = res.product_tmpl_id.property_account_expense
1358                 ex_acc_cate = res.categ_id.property_account_expense_categ
1359                 if ex_acc:
1360                     app_acc_exp = ex_acc
1361                 else:
1362                     app_acc_exp = ex_acc_cate
1363             else:
1364                 app_acc_exp = account_obj.browse(cr, uid, exp_pro_id, context=context)[0]
1365             if not in_pro_id and not exp_pro_id:
1366                 in_acc = res.product_tmpl_id.property_account_income
1367                 in_acc_cate = res.categ_id.property_account_income_categ
1368                 ex_acc = res.product_tmpl_id.property_account_expense
1369                 ex_acc_cate = res.categ_id.property_account_expense_categ
1370                 if in_acc or ex_acc:
1371                     app_acc_in = in_acc
1372                     app_acc_exp = ex_acc
1373                 else:
1374                     app_acc_in = in_acc_cate
1375                     app_acc_exp = ex_acc_cate
1376             if app_acc_in and app_acc_in.company_id.id != company_id and app_acc_exp and app_acc_exp.company_id.id != company_id:
1377                 in_res_id = account_obj.search(cr, uid, [('name','=',app_acc_in.name),('company_id','=',company_id)])
1378                 exp_res_id = account_obj.search(cr, uid, [('name','=',app_acc_exp.name),('company_id','=',company_id)])
1379                 if not in_res_id and not exp_res_id:
1380                     raise osv.except_osv(_('Configuration Error !'),
1381                         _('Can not find account chart for this company, Please Create account.'))
1382                 in_obj_acc = account_obj.browse(cr, uid, in_res_id, context=context)
1383                 exp_obj_acc = account_obj.browse(cr, uid, exp_res_id, context=context)
1384                 if in_acc or ex_acc:
1385                     res.product_tmpl_id.property_account_income = in_obj_acc[0]
1386                     res.product_tmpl_id.property_account_expense = exp_obj_acc[0]
1387                 else:
1388                     res.categ_id.property_account_income_categ = in_obj_acc[0]
1389                     res.categ_id.property_account_expense_categ = exp_obj_acc[0]
1390
1391         if type in ('out_invoice','out_refund'):
1392             a = res.product_tmpl_id.property_account_income.id
1393             if not a:
1394                 a = res.categ_id.property_account_income_categ.id
1395         else:
1396             a = res.product_tmpl_id.property_account_expense.id
1397             if not a:
1398                 a = res.categ_id.property_account_expense_categ.id
1399
1400         a = fpos_obj.map_account(cr, uid, fpos, a)
1401         if a:
1402             result['account_id'] = a
1403
1404         if type in ('out_invoice', 'out_refund'):
1405             taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1406             tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1407         else:
1408             taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a).tax_ids or False)
1409             tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
1410         if type in ('in_invoice', 'in_refund'):
1411             result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
1412         else:
1413             result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
1414
1415 #        if not name:
1416         result['name'] = res.partner_ref
1417
1418         domain = {}
1419         result['uos_id'] = res.uom_id.id or uom or False
1420         if result['uos_id']:
1421             res2 = res.uom_id.category_id.id
1422             if res2:
1423                 domain = {'uos_id':[('category_id','=',res2 )]}
1424
1425         result['categ_id'] = res.categ_id.id
1426         res_final = {'value':result, 'domain':domain}
1427
1428         if not company_id or not currency_id:
1429             return res_final
1430
1431         company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
1432         currency = self.pool.get('res.currency').browse(cr, uid, currency_id, context=context)
1433
1434         if company.currency_id.id != currency.id:
1435             new_price = res_final['value']['price_unit'] * currency.rate
1436             res_final['value']['price_unit'] = new_price
1437
1438         if uom:
1439             uom = self.pool.get('product.uom').browse(cr, uid, uom, context=context)
1440             if res.uom_id.category_id.id == uom.category_id.id:
1441                 new_price = res_final['value']['price_unit'] * uom.factor_inv
1442                 res_final['value']['price_unit'] = new_price
1443         return res_final
1444
1445     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, address_invoice_id=False, currency_id=False, context=None):
1446         res = self.product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, address_invoice_id, currency_id, context)
1447         if 'uos_id' in res['value']:
1448             del res['value']['uos_id']
1449         if not uom:
1450             res['value']['price_unit'] = 0.0
1451         return res
1452
1453     def move_line_get(self, cr, uid, invoice_id, context=None):
1454         res = []
1455         tax_obj = self.pool.get('account.tax')
1456         cur_obj = self.pool.get('res.currency')
1457         if context is None:
1458             context = {}
1459         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1460         company_currency = inv.company_id.currency_id.id
1461
1462         for line in inv.invoice_line:
1463             mres = self.move_line_get_item(cr, uid, line, context)
1464             if not mres:
1465                 continue
1466             res.append(mres)
1467             tax_code_found= False
1468             for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
1469                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1470                     line.quantity, inv.address_invoice_id.id, line.product_id,
1471                     inv.partner_id)['taxes']:
1472
1473                 if inv.type in ('out_invoice', 'in_invoice'):
1474                     tax_code_id = tax['base_code_id']
1475                     tax_amount = line.price_subtotal * tax['base_sign']
1476                 else:
1477                     tax_code_id = tax['ref_base_code_id']
1478                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1479
1480                 if tax_code_found:
1481                     if not tax_code_id:
1482                         continue
1483                     res.append(self.move_line_get_item(cr, uid, line, context))
1484                     res[-1]['price'] = 0.0
1485                     res[-1]['account_analytic_id'] = False
1486                 elif not tax_code_id:
1487                     continue
1488                 tax_code_found = True
1489
1490                 res[-1]['tax_code_id'] = tax_code_id
1491                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1492         return res
1493
1494     def move_line_get_item(self, cr, uid, line, context=None):
1495         return {
1496             'type':'src',
1497             'name': line.name[:64],
1498             'price_unit':line.price_unit,
1499             'quantity':line.quantity,
1500             'price':line.price_subtotal,
1501             'account_id':line.account_id.id,
1502             'product_id':line.product_id.id,
1503             'uos_id':line.uos_id.id,
1504             'account_analytic_id':line.account_analytic_id.id,
1505             'taxes':line.invoice_line_tax_id,
1506         }
1507     #
1508     # Set the tax field according to the account and the fiscal position
1509     #
1510     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1511         if not account_id:
1512             return {}
1513         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1514         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1515         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1516         return {'value':{'invoice_line_tax_id': res}}
1517
1518 account_invoice_line()
1519
1520 class account_invoice_tax(osv.osv):
1521     _name = "account.invoice.tax"
1522     _description = "Invoice Tax"
1523
1524     def _count_factor(self, cr, uid, ids, name, args, context=None):
1525         res = {}
1526         for invoice_tax in self.browse(cr, uid, ids, context=context):
1527             res[invoice_tax.id] = {
1528                 'factor_base': 1.0,
1529                 'factor_tax': 1.0,
1530             }
1531             if invoice_tax.amount <> 0.0:
1532                 factor_tax = invoice_tax.tax_amount / invoice_tax.amount
1533                 res[invoice_tax.id]['factor_tax'] = factor_tax
1534
1535             if invoice_tax.base <> 0.0:
1536                 factor_base = invoice_tax.base_amount / invoice_tax.base
1537                 res[invoice_tax.id]['factor_base'] = factor_base
1538
1539         return res
1540
1541     _columns = {
1542         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1543         'name': fields.char('Tax Description', size=64, required=True),
1544         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1545         'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
1546         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
1547         'manual': fields.boolean('Manual'),
1548         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of invoice tax."),
1549         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The account basis of the tax declaration."),
1550         'base_amount': fields.float('Base Code Amount', digits_compute=dp.get_precision('Account')),
1551         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The tax basis of the tax declaration."),
1552         'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Account')),
1553         'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True),
1554         'factor_base': fields.function(_count_factor, method=True, string='Multipication factor for Base code', type='float', multi="all"),
1555         'factor_tax': fields.function(_count_factor, method=True, string='Multipication factor Tax code', type='float', multi="all")
1556     }
1557
1558     def base_change(self, cr, uid, ids, base, currency_id=False, company_id=False, date_invoice=False):
1559         cur_obj = self.pool.get('res.currency')
1560         company_obj = self.pool.get('res.company')
1561         company_currency = False
1562         factor = 1
1563         if ids:
1564             factor = self.read(cr, uid, ids[0], ['factor_base'])['factor_base']
1565         if company_id:
1566             company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1567         if currency_id and company_currency:
1568             base = cur_obj.compute(cr, uid, currency_id, company_currency, base*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1569         return {'value': {'base_amount':base}}
1570
1571     def amount_change(self, cr, uid, ids, amount, currency_id=False, company_id=False, date_invoice=False):
1572         cur_obj = self.pool.get('res.currency')
1573         company_obj = self.pool.get('res.company')
1574         company_currency = False
1575         factor = 1
1576         if ids:
1577             factor = self.read(cr, uid, ids[0], ['factor_tax'])['factor_tax']
1578         if company_id:
1579             company_currency = company_obj.read(cr, uid, [company_id], ['currency_id'])[0]['currency_id'][0]
1580         if currency_id and company_currency:
1581             amount = cur_obj.compute(cr, uid, currency_id, company_currency, amount*factor, context={'date': date_invoice or time.strftime('%Y-%m-%d')}, round=False)
1582         return {'value': {'tax_amount': amount}}
1583
1584     _order = 'sequence'
1585     _defaults = {
1586         'manual': 1,
1587         'base_amount': 0.0,
1588         'tax_amount': 0.0,
1589     }
1590     def compute(self, cr, uid, invoice_id, context=None):
1591         tax_grouped = {}
1592         tax_obj = self.pool.get('account.tax')
1593         cur_obj = self.pool.get('res.currency')
1594         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
1595         cur = inv.currency_id
1596         company_currency = inv.company_id.currency_id.id
1597
1598         for line in inv.invoice_line:
1599             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, inv.address_invoice_id.id, line.product_id, inv.partner_id)['taxes']:
1600                 val={}
1601                 val['invoice_id'] = inv.id
1602                 val['name'] = tax['name']
1603                 val['amount'] = tax['amount']
1604                 val['manual'] = False
1605                 val['sequence'] = tax['sequence']
1606                 val['base'] = tax['price_unit'] * line['quantity']
1607
1608                 if inv.type in ('out_invoice','in_invoice'):
1609                     val['base_code_id'] = tax['base_code_id']
1610                     val['tax_code_id'] = tax['tax_code_id']
1611                     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)
1612                     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)
1613                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1614                 else:
1615                     val['base_code_id'] = tax['ref_base_code_id']
1616                     val['tax_code_id'] = tax['ref_tax_code_id']
1617                     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)
1618                     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)
1619                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1620
1621                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1622                 if not key in tax_grouped:
1623                     tax_grouped[key] = val
1624                 else:
1625                     tax_grouped[key]['amount'] += val['amount']
1626                     tax_grouped[key]['base'] += val['base']
1627                     tax_grouped[key]['base_amount'] += val['base_amount']
1628                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1629
1630         for t in tax_grouped.values():
1631             t['base'] = cur_obj.round(cr, uid, cur, t['base'])
1632             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1633             t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
1634             t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
1635         return tax_grouped
1636
1637     def move_line_get(self, cr, uid, invoice_id):
1638         res = []
1639         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1640         for t in cr.dictfetchall():
1641             if not t['amount'] \
1642                     and not t['tax_code_id'] \
1643                     and not t['tax_amount']:
1644                 continue
1645             res.append({
1646                 'type':'tax',
1647                 'name':t['name'],
1648                 'price_unit': t['amount'],
1649                 'quantity': 1,
1650                 'price': t['amount'] or 0.0,
1651                 'account_id': t['account_id'],
1652                 'tax_code_id': t['tax_code_id'],
1653                 'tax_amount': t['tax_amount']
1654             })
1655         return res
1656 account_invoice_tax()
1657
1658
1659 class res_partner(osv.osv):
1660     """ Inherits partner and adds invoice information in the partner form """
1661     _inherit = 'res.partner'
1662     _columns = {
1663         'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
1664     }
1665
1666 res_partner()
1667
1668 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: