[IMP] modified invoices in order to use fiscal position direclty on account.invoice...
[odoo/odoo.git] / addons / account / invoice.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv
26 import ir
27 import pooler
28 import mx.DateTime
29 from mx.DateTime import RelativeDateTime
30 from tools import config
31 from tools.translate import _
32
33 class fiscalyear_seq(osv.osv):
34     _name = "fiscalyear.seq"
35     _description = "Maintains Invoice sequences with Fiscal Year"
36     _rec_name = 'fiscalyear_id'
37     _columns = {
38         'journal_id': fields.many2one('account.journal', 'Journal'),
39         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year',required=True),
40         'sequence_id':fields.many2one('ir.sequence', 'Sequence',required=True),
41     }
42
43 fiscalyear_seq()
44
45 class account_invoice(osv.osv):
46     def _amount_all(self, cr, uid, ids, name, args, context=None):
47         res = {}
48         for invoice in self.browse(cr,uid,ids, context=context):
49             res[invoice.id] = {
50                 'amount_untaxed': 0.0,
51                 'amount_tax': 0.0,
52                 'amount_total': 0.0
53             }
54             for line in invoice.invoice_line:
55                 res[invoice.id]['amount_untaxed'] += line.price_subtotal
56             for line in invoice.tax_line:
57                 res[invoice.id]['amount_tax'] += line.amount
58             res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
59         return res
60
61     def _get_journal(self, cr, uid, context):
62         if context is None:
63             context = {}
64         type_inv = context.get('type', 'out_invoice')
65         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
66         journal_obj = self.pool.get('account.journal')
67         res = journal_obj.search(cr, uid, [('type', '=', type2journal.get(type_inv, 'sale'))], limit=1)
68         if res:
69             return res[0]
70         else:
71             return False
72
73     def _get_currency(self, cr, uid, context):
74         user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0]
75         if user.company_id:
76             return user.company_id.currency_id.id
77         else:
78             return pooler.get_pool(cr.dbname).get('res.currency').search(cr, uid, [('rate','=',1.0)])[0]
79
80     def _get_journal_analytic(self, cr, uid, type_inv, context=None):
81         type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
82         tt = type2journal.get(type_inv, 'sale')
83         result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
84         if not result:
85             raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal of type '%s' !") % (tt,))
86         return result[0]
87
88     def _get_type(self, cr, uid, context=None):
89         if context is None:
90             context = {}
91         type = context.get('type', 'out_invoice')
92         return type
93
94     def _reconciled(self, cr, uid, ids, name, args, context):
95         res = {}
96         for id in ids:
97             res[id] = self.test_paid(cr, uid, [id])
98         return res
99
100     def _get_reference_type(self, cr, uid, context=None):
101         return [('none', _('Free Reference'))]
102
103     def _amount_residual(self, cr, uid, ids, name, args, context=None):
104         res = {}
105         data_inv = self.browse(cr, uid, ids)
106         for inv in data_inv:
107             paid_amt = 0.0
108             to_pay = inv.amount_total
109             for lines in inv.move_lines:
110                 paid_amt = paid_amt - lines.credit + lines.debit
111             res[inv.id] = to_pay - abs(paid_amt)
112         return res
113
114     def _get_lines(self, cr, uid, ids, name, arg, context=None):
115         res = {}
116         for id in ids:
117             move_lines = self.move_line_id_payment_get(cr,uid,[id])
118             if not move_lines:
119                 res[id] = []
120                 continue
121             data_lines = self.pool.get('account.move.line').browse(cr,uid,move_lines)
122             for line in data_lines:
123                 ids_line = []
124                 if line.reconcile_id:
125                     ids_line = line.reconcile_id.line_id
126                 elif line.reconcile_partial_id:
127                     ids_line = line.reconcile_partial_id.line_partial_ids
128                 l = map(lambda x: x.id, ids_line)
129                 res[id]=[x for x in l if x <> line.id]
130         return res
131
132     def _get_invoice_line(self, cr, uid, ids, context=None):
133         result = {}
134         for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
135             result[line.invoice_id.id] = True
136         return result.keys()
137
138     def _get_invoice_tax(self, cr, uid, ids, context=None):
139         result = {}
140         for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
141             result[tax.invoice_id.id] = True
142         return result.keys()
143
144     def _compute_lines(self, cr, uid, ids, name, args, context=None):
145         result = {}
146         for invoice in self.browse(cr, uid, ids, context):
147             moves = self.move_line_id_payment_get(cr, uid, [invoice.id])
148             src = []
149             lines = []
150             for m in self.pool.get('account.move.line').browse(cr, uid, moves, context):
151                 if m.reconcile_id:
152                     lines += map(lambda x: x.id, m.reconcile_id.line_id)
153                 elif m.reconcile_partial_id:
154                     lines += map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
155                 src.append(m.id)
156             lines = filter(lambda x: x not in src, lines)
157             result[invoice.id] = lines
158         return result
159
160     def _get_invoice_from_line(self, cr, uid, ids, context={}):
161         move = {}
162         for line in self.pool.get('account.move.line').browse(cr, uid, ids):
163             if line.reconcile_partial_id:
164                 for line2 in line.reconcile_partial_id.line_partial_ids:
165                     move[line2.move_id.id] = True
166             if line.reconcile_id:
167                 for line2 in line.reconcile_id.line_id:
168                     move[line2.move_id.id] = True
169         invoice_ids = []
170         if move:
171             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
172         return invoice_ids
173
174     def _get_invoice_from_reconcile(self, cr, uid, ids, context={}):
175         move = {}
176         for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
177             for line in r.line_partial_ids:
178                 move[line.move_id.id] = True
179             for line in r.line_id:
180                 move[line.move_id.id] = True
181         invoice_ids = []
182         if move:
183             invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
184         return invoice_ids
185
186     _name = "account.invoice"
187     _description = 'Invoice'
188     _order = "number"
189     _columns = {
190         'name': fields.char('Description', size=64, select=True,readonly=True, states={'draft':[('readonly',False)]}),
191         'origin': fields.char('Origin', size=64, help="Reference of the document that produced this invoice."),
192         'type': fields.selection([
193             ('out_invoice','Customer Invoice'),
194             ('in_invoice','Supplier Invoice'),
195             ('out_refund','Customer Refund'),
196             ('in_refund','Supplier Refund'),
197             ],'Type', readonly=True, select=True),
198
199         'number': fields.char('Invoice Number', size=32, readonly=True, help="Uniq number of the invoice, computed automatically when the invoice is created."),
200         'reference': fields.char('Invoice Reference', size=64, help="The partner reference of this invoice."),
201         'reference_type': fields.selection(_get_reference_type, 'Reference Type',
202             required=True),
203         'comment': fields.text('Additionnal Information'),
204
205         'state': fields.selection([
206             ('draft','Draft'),
207             ('proforma','Pro-forma'),
208             ('proforma2','Pro-forma'),
209             ('open','Open'),
210             ('paid','Done'),
211             ('cancel','Canceled')
212         ],'State', select=True, readonly=True),
213
214         'date_invoice': fields.date('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}),
215         'date_due': fields.date('Due Date', states={'open':[('readonly',True)],'close':[('readonly',True)]},
216             help="If you use payment terms, the due date will be computed automatically at the generation "\
217                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment."),
218         'partner_id': fields.many2one('res.partner', 'Partner', change_default=True, readonly=True, required=True, states={'draft':[('readonly',False)]}),
219         'address_contact_id': fields.many2one('res.partner.address', 'Contact Address', readonly=True, states={'draft':[('readonly',False)]}),
220         'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft':[('readonly',False)]}),
221         'payment_term': fields.many2one('account.payment.term', 'Payment Term',readonly=True, states={'draft':[('readonly',False)]},
222             help="If you use payment terms, the due date will be computed automatically at the generation "\
223                 "of accounting entries. If you keep the payment term and the due date empty, it means direct payment. "\
224                 "The payment term may compute several due dates: 50% now, 50% in one month."),
225         'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation date."),
226
227         'account_id': fields.many2one('account.account', 'Account', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The partner account used for this invoice."),
228         'invoice_line': fields.one2many('account.invoice.line', 'invoice_id', 'Invoice Lines', readonly=True, states={'draft':[('readonly',False)]}),
229         'tax_line': fields.one2many('account.invoice.tax', 'invoice_id', 'Tax Lines', readonly=True, states={'draft':[('readonly',False)]}),
230
231         'move_id': fields.many2one('account.move', 'Invoice Movement', readonly=True, help="Link to the automatically generated account moves."),
232         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16,2),string='Untaxed',
233             store={
234                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
235                 'account.invoice.tax': (_get_invoice_tax, None, 20),
236                 'account.invoice.line': (_get_invoice_line, None, 20),
237             },
238             multi='all'),
239         'amount_tax': fields.function(_amount_all, method=True, digits=(16,2), string='Tax',
240             store={
241                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
242                 'account.invoice.tax': (_get_invoice_tax, None, 20),
243                 'account.invoice.line': (_get_invoice_line, None, 20),
244             },
245             multi='all'),
246         'amount_total': fields.function(_amount_all, method=True, digits=(16,2), string='Total',
247             store={
248                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 20),
249                 'account.invoice.tax': (_get_invoice_tax, None, 20),
250                 'account.invoice.line': (_get_invoice_line, None, 20),
251             },
252             multi='all'),
253         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)]}),
254         'journal_id': fields.many2one('account.journal', 'Journal', required=True,readonly=True, states={'draft':[('readonly',False)]}),
255         'company_id': fields.many2one('res.company', 'Company', required=True),
256         'check_total': fields.float('Total', digits=(16,2), states={'open':[('readonly',True)],'close':[('readonly',True)]}),
257         'reconciled': fields.function(_reconciled, method=True, string='Paid/Reconciled', type='boolean',
258             store=True, help="The account moves of the invoice have been reconciled with account moves of the payment(s)."),
259         'partner_bank': fields.many2one('res.partner.bank', 'Bank Account',
260             help='The bank account to pay to or to be paid from'),
261         'move_lines':fields.function(_get_lines , method=True,type='many2many' , relation='account.move.line',string='Move Lines'),
262         'residual': fields.function(_amount_residual, method=True, digits=(16,2),string='Residual',
263             store={
264                 'account.invoice': (lambda self, cr, uid, ids, c={}: ids, None, 50),
265                 'account.invoice.tax': (_get_invoice_tax, None, 50),
266                 'account.invoice.line': (_get_invoice_line, None, 50),
267                 'account.move.line': (_get_invoice_from_line, None, 50),
268                 'account.move.reconcile': (_get_invoice_from_reconcile, None, 50),
269             },
270             help="Remaining amount due."),
271         'payment_ids': fields.function(_compute_lines, method=True, relation='account.move.line', type="many2many", string='Payments'),
272         'move_name': fields.char('Account Move', size=64),
273         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
274     }
275     _defaults = {
276         'type': _get_type,
277         #'date_invoice': lambda *a: time.strftime('%Y-%m-%d'),
278         'state': lambda *a: 'draft',
279         'journal_id': _get_journal,
280         'currency_id': _get_currency,
281         'company_id': lambda self, cr, uid, context: \
282                 self.pool.get('res.users').browse(cr, uid, uid,
283                     context=context).company_id.id,
284         'reference_type': lambda *a: 'none',
285     }
286
287     def unlink(self, cr, uid, ids):
288         invoices = self.read(cr, uid, ids, ['state'])
289         unlink_ids = []
290         for t in invoices:
291             if t['state'] in ('draft', 'cancel'):
292                 unlink_ids.append(t['id'])
293             else:
294                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete invoice(s) which are already opened or paid !'))
295         osv.osv.unlink(self, cr, uid, unlink_ids)
296         return True
297
298 #   def get_invoice_address(self, cr, uid, ids):
299 #       res = self.pool.get('res.partner').address_get(cr, uid, [part], ['invoice'])
300 #       return [{}]
301
302     def onchange_partner_id(self, cr, uid, ids, type, partner_id,
303             date_invoice=False, payment_term=False, partner_bank_id=False):
304         invoice_addr_id = False
305         contact_addr_id = False
306         partner_payment_term = False
307         acc_id = False
308         bank_id = False
309         fiscal_position = False
310
311         opt = [('uid', str(uid))]
312         if partner_id:
313
314             opt.insert(0, ('id', partner_id))
315             res = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact', 'invoice'])
316             contact_addr_id = res['contact']
317             invoice_addr_id = res['invoice']
318             p = self.pool.get('res.partner').browse(cr, uid, partner_id)
319             if type in ('out_invoice', 'out_refund'):
320                 acc_id = p.property_account_receivable.id
321             else:
322                 acc_id = p.property_account_payable.id
323             fiscal_position = p.property_account_position and p.property_account_position.id or False
324             partner_payment_term = p.property_payment_term and p.property_payment_term.id or False
325             if p.bank_ids:
326                 bank_id = p.bank_ids[0].id
327
328         result = {'value': {
329             'address_contact_id': contact_addr_id,
330             'address_invoice_id': invoice_addr_id,
331             'account_id': acc_id,
332             'payment_term': partner_payment_term,
333             'fiscal_position': fiscal_position
334             }
335         }
336
337         if type in ('in_invoice', 'in_refund'):
338             result['value']['partner_bank'] = bank_id
339
340         if payment_term != partner_payment_term:
341             if partner_payment_term:
342                 to_update = self.onchange_payment_term_date_invoice(
343                     cr,uid,ids,partner_payment_term,date_invoice)
344                 result['value'].update(to_update['value'])
345             else:
346                 result['value']['date_due'] = False
347
348         if partner_bank_id != bank_id:
349             to_update = self.onchange_partner_bank(cr, uid, ids, bank_id)
350             result['value'].update(to_update['value'])
351         return result
352
353     def onchange_currency_id(self, cr, uid, ids, curr_id):
354         return {}
355
356     def onchange_payment_term_date_invoice(self, cr, uid, ids, payment_term_id, date_invoice):
357         if not payment_term_id:
358             return {}
359         res={}
360         pt_obj= self.pool.get('account.payment.term')
361
362         if not date_invoice :
363             date_invoice = time.strftime('%Y-%m-%d')
364
365         pterm_list= pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_invoice)
366
367         if pterm_list:
368             pterm_list = [line[0] for line in pterm_list]
369             pterm_list.sort()
370             res= {'value':{'date_due': pterm_list[-1]}}
371
372         return res
373
374     def onchange_invoice_line(self, cr, uid, ids, lines):
375         return {}
376
377     def onchange_partner_bank(self, cursor, user, ids, partner_bank_id):
378         return {'value': {}}
379
380     # go from canceled state to draft state
381     def action_cancel_draft(self, cr, uid, ids, *args):
382         self.write(cr, uid, ids, {'state':'draft'})
383         wf_service = netsvc.LocalService("workflow")
384         for inv_id in ids:
385             wf_service.trg_create(uid, 'account.invoice', inv_id, cr)
386         return True
387
388     # Workflow stuff
389     #################
390
391     # return the ids of the move lines which has the same account than the invoice
392     # whose id is in ids
393     def move_line_id_payment_get(self, cr, uid, ids, *args):
394         ml = self.pool.get('account.move.line')
395         res = []
396         for inv in self.read(cr, uid, ids, ['move_id','account_id']):
397             if inv['move_id']:
398                 move_line_ids = ml.search(cr, uid, [('move_id', '=', inv['move_id'][0])])
399                 for line in ml.read(cr, uid, move_line_ids, ['account_id']):
400                     if line['account_id']==inv['account_id']:
401                         res.append(line['id'])
402         return res
403
404     def copy(self, cr, uid, id, default=None, context=None):
405         if default is None:
406             default = {}
407         default = default.copy()
408         default.update({'state':'draft', 'number':False, 'move_id':False, 'move_name':False,})
409         if 'date_invoice' not in default:
410             default['date_invoice'] = False
411         if 'date_due' not in default:
412             default['date_due'] = False
413         return super(account_invoice, self).copy(cr, uid, id, default, context)
414
415     def test_paid(self, cr, uid, ids, *args):
416         res = self.move_line_id_payment_get(cr, uid, ids)
417         if not res:
418             return False
419         ok = True
420         for id in res:
421             cr.execute('select reconcile_id from account_move_line where id=%s', (id,))
422             ok = ok and  bool(cr.fetchone()[0])
423         return ok
424
425     def button_reset_taxes(self, cr, uid, ids, context=None):
426         ait_obj = self.pool.get('account.invoice.tax')
427         for id in ids:
428             cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s", (id,))
429             for taxe in ait_obj.compute(cr, uid, id).values():
430                 ait_obj.create(cr, uid, taxe)
431          # Update the stored value (fields.function), so we write to trigger recompute
432         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
433         return True
434
435     def button_compute(self, cr, uid, ids, context=None, set_total=False):
436         self.button_reset_taxes(cr, uid, ids, context)
437         for inv in self.browse(cr, uid, ids):
438             if set_total:
439                 self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': inv.amount_total})
440         return True
441
442     def _convert_ref(self, cr, uid, ref):
443         return (ref or '').replace('/','')
444
445     def _get_analytic_lines(self, cr, uid, id):
446         inv = self.browse(cr, uid, [id])[0]
447         cur_obj = self.pool.get('res.currency')
448
449         company_currency = inv.company_id.currency_id.id
450         if inv.type in ('out_invoice', 'in_refund'):
451             sign = 1
452         else:
453             sign = -1
454
455         iml = self.pool.get('account.invoice.line').move_line_get(cr, uid, inv.id)
456         for il in iml:
457             if il['account_analytic_id']:
458                 if inv.type in ('in_invoice', 'in_refund'):
459                     ref = inv.reference
460                 else:
461                     ref = self._convert_ref(cr, uid, inv.number)
462                 il['analytic_lines'] = [(0,0, {
463                     'name': il['name'],
464                     'date': inv['date_invoice'],
465                     'account_id': il['account_analytic_id'],
466                     'unit_amount': il['quantity'],
467                     'amount': cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, il['price'], context={'date': inv.date_invoice}) * sign,
468                     'product_id': il['product_id'],
469                     'product_uom_id': il['uos_id'],
470                     'general_account_id': il['account_id'],
471                     'journal_id': self._get_journal_analytic(cr, uid, inv.type),
472                     'ref': ref,
473                 })]
474         return iml
475
476     def action_move_create(self, cr, uid, ids, *args):
477         ait_obj = self.pool.get('account.invoice.tax')
478         cur_obj = self.pool.get('res.currency')
479
480         for inv in self.browse(cr, uid, ids):
481             if not inv.tax_line:
482                 self.button_compute(cr, uid, [inv.id], context={}, set_total=False)
483             if inv.move_id:
484                 continue
485
486             if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
487                 raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
488             if not inv.date_invoice:
489                 self.write(cr, uid, [inv.id], {'date_invoice':time.strftime('%Y-%m-%d')})
490             company_currency = inv.company_id.currency_id.id
491             # create the analytical lines
492             line_ids = self.read(cr, uid, [inv.id], ['invoice_line'])[0]['invoice_line']
493             ils = self.pool.get('account.invoice.line').read(cr, uid, line_ids)
494             # one move line per invoice line
495             iml = self._get_analytic_lines(cr, uid, inv.id)
496             # check if taxes are all computed
497             compute_taxes = ait_obj.compute(cr, uid, inv.id)
498             if not inv.tax_line:
499                 for tax in compute_taxes.values():
500                     ait_obj.create(cr, uid, tax)
501             else:
502                 tax_key = []
503                 for tax in inv.tax_line:
504                     if tax.manual:
505                         continue
506                     key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
507                     tax_key.append(key)
508                     if not key in compute_taxes:
509                         raise osv.except_osv(_('Warning !'), _('Global taxes defined, but not in invoice lines !'))
510                     base = compute_taxes[key]['base']
511                     if abs(base - tax.base) > inv.company_id.currency_id.rounding:
512                         raise osv.except_osv(_('Warning !'), _('Tax base different !\nClick on compute to update tax base'))
513                 for key in compute_taxes:
514                     if not key in tax_key:
515                         raise osv.except_osv(_('Warning !'), _('Taxes missing !'))
516
517             # one move line per tax line
518             iml += ait_obj.move_line_get(cr, uid, inv.id)
519
520             if inv.type in ('in_invoice', 'in_refund'):
521                 ref = inv.reference
522             else:
523                 ref = self._convert_ref(cr, uid, inv.number)
524
525             diff_currency_p = inv.currency_id.id <> company_currency
526             # create one move line for the total and possibly adjust the other lines amount
527             total = 0
528             total_currency = 0
529             for i in iml:
530                 if inv.currency_id.id != company_currency:
531                     i['currency_id'] = inv.currency_id.id
532                     i['amount_currency'] = i['price']
533                     i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
534                             company_currency, i['price'],
535                             context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')})
536                 else:
537                     i['amount_currency'] = False
538                     i['currency_id'] = False
539                 i['ref'] = ref
540                 if inv.type in ('out_invoice','in_refund'):
541                     total += i['price']
542                     total_currency += i['amount_currency'] or i['price']
543                     i['price'] = - i['price']
544                 else:
545                     total -= i['price']
546                     total_currency -= i['amount_currency'] or i['price']
547             acc_id = inv.account_id.id
548
549             name = inv['name'] or '/'
550             totlines = False
551             if inv.payment_term:
552                 totlines = self.pool.get('account.payment.term').compute(cr,
553                         uid, inv.payment_term.id, total, inv.date_invoice or False)
554             if totlines:
555                 res_amount_currency = total_currency
556                 i = 0
557                 for t in totlines:
558                     if inv.currency_id.id != company_currency:
559                         amount_currency = cur_obj.compute(cr, uid,
560                                 company_currency, inv.currency_id.id, t[1])
561                     else:
562                         amount_currency = False
563
564                     # last line add the diff
565                     res_amount_currency -= amount_currency or 0
566                     i += 1
567                     if i == len(totlines):
568                         amount_currency += res_amount_currency
569
570                     iml.append({
571                         'type': 'dest',
572                         'name': name,
573                         'price': t[1],
574                         'account_id': acc_id,
575                         'date_maturity': t[0],
576                         'amount_currency': diff_currency_p \
577                                 and  amount_currency or False,
578                         'currency_id': diff_currency_p \
579                                 and inv.currency_id.id or False,
580                         'ref': ref,
581                     })
582             else:
583                 iml.append({
584                     'type': 'dest',
585                     'name': name,
586                     'price': total,
587                     'account_id': acc_id,
588                     'date_maturity' : inv.date_due or False,
589                     'amount_currency': diff_currency_p \
590                             and total_currency or False,
591                     'currency_id': diff_currency_p \
592                             and inv.currency_id.id or False,
593                     'ref': ref
594             })
595
596             date = inv.date_invoice or time.strftime('%Y-%m-%d')
597             part = inv.partner_id.id
598
599             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part, date, context={})) ,iml)
600
601             if inv.journal_id.group_invoice_lines:
602                 line2 = {}
603                 for x, y, l in line:
604                     tmp = str(l['account_id'])
605                     tmp += '-'+str('tax_code_id' in l and l['tax_code_id'] or "False")
606                     tmp += '-'+str('product_id' in l and l['product_id'] or "False")
607                     tmp += '-'+str('analytic_account_id' in l and l['analytic_account_id'] or "False")
608
609                     if tmp in line2:
610                         am = line2[tmp]['debit'] - line2[tmp]['credit'] + (l['debit'] - l['credit'])
611                         line2[tmp]['debit'] = (am > 0) and am or 0.0
612                         line2[tmp]['credit'] = (am < 0) and -am or 0.0
613                         line2[tmp]['tax_amount'] += l['tax_amount']
614                         line2[tmp]['analytic_lines'] += l['analytic_lines']
615                     else:
616                         line2[tmp] = l
617                 line = []
618                 for key, val in line2.items():
619                     line.append((0,0,val))
620
621             journal_id = inv.journal_id.id #self._get_journal(cr, uid, {'type': inv['type']})
622             journal = self.pool.get('account.journal').browse(cr, uid, journal_id)
623             if journal.centralisation:
624                 raise osv.except_osv(_('UserError'),
625                         _('Can not create invoice move on centralized journal'))
626             move = {'ref': inv.number, 'line_id': line, 'journal_id': journal_id, 'date': date}
627             period_id=inv.period_id and inv.period_id.id or False
628             if not period_id:
629                 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'))])
630                 if len(period_ids):
631                     period_id=period_ids[0]
632             if period_id:
633                 move['period_id'] = period_id
634                 for i in line:
635                     i[2]['period_id'] = period_id
636
637             if not 'name' in move:
638                 move['name'] = inv.name or '/'
639
640             move_id = self.pool.get('account.move').create(cr, uid, move)
641             new_move_name = self.pool.get('account.move').browse(cr, uid, move_id).name
642             # make the invoice point to that move
643             self.write(cr, uid, [inv.id], {'move_id': move_id,'period_id':period_id, 'move_name':new_move_name})
644             self.pool.get('account.move').post(cr, uid, [move_id])
645         self._log_event(cr, uid, ids)
646         return True
647
648     def line_get_convert(self, cr, uid, x, part, date, context=None):
649         return {
650             'date_maturity': x.get('date_maturity', False),
651             'partner_id':part,
652             'name':x['name'][:64],
653             'debit':x['price']>0 and x['price'],
654             'credit':x['price']<0 and -x['price'],
655             'account_id':x['account_id'],
656             'analytic_lines':x.get('analytic_lines', []),
657             'amount_currency':x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
658             'currency_id':x.get('currency_id', False),
659             'tax_code_id': x.get('tax_code_id', False),
660             'tax_amount': x.get('tax_amount', False),
661             'ref':x.get('ref',False),
662             'quantity':x.get('quantity',1.00),
663             'product_id':x.get('product_id', False),
664             'product_uom_id':x.get('uos_id',False),
665             'analytic_account_id':x.get('account_analytic_id',False),
666         }
667
668     def action_number(self, cr, uid, ids, *args):
669         cr.execute('SELECT id, type, number, move_id, reference ' \
670                 'FROM account_invoice ' \
671                 'WHERE id IN ('+','.join(map(str,ids))+')')
672         obj_inv = self.browse(cr, uid, ids)[0]
673         for (id, invtype, number, move_id, reference) in cr.fetchall():
674             if not number:
675                 flag = True
676                 for seq in obj_inv.journal_id.fy_seq_id:
677                     if seq.fiscalyear_id.id == obj_inv.move_id.period_id.fiscalyear_id.id:
678                         number =  self.pool.get('ir.sequence').get_id(cr, uid,seq.sequence_id.id)
679                         flag = False
680                         break
681                 if flag:
682                     number = self.pool.get('ir.sequence').get(cr, uid,
683                             'account.invoice.' + invtype)
684                 if invtype in ('in_invoice', 'in_refund'):
685                     ref = reference
686                 else:
687                     ref = self._convert_ref(cr, uid, number)
688                 cr.execute('UPDATE account_invoice SET number=%s ' \
689                         'WHERE id=%s', (number, id))
690                 cr.execute('UPDATE account_move SET ref=%s ' \
691                         'WHERE id=%s AND (ref is null OR ref = \'\')',
692                         (ref, move_id))
693                 cr.execute('UPDATE account_move_line SET ref=%s ' \
694                         'WHERE move_id=%s AND (ref is null OR ref = \'\')',
695                         (ref, move_id))
696                 cr.execute('UPDATE account_analytic_line SET ref=%s ' \
697                         'FROM account_move_line ' \
698                         'WHERE account_move_line.move_id = %s ' \
699                             'AND account_analytic_line.move_id = account_move_line.id',
700                             (ref, move_id))
701         return True
702
703     def action_cancel(self, cr, uid, ids, *args):
704         account_move_obj = self.pool.get('account.move')
705         invoices = self.read(cr, uid, ids, ['move_id'])
706         for i in invoices:
707             if i['move_id']:
708                 account_move_obj.button_cancel(cr, uid, [i['move_id'][0]])
709                 # delete the move this invoice was pointing to
710                 # Note that the corresponding move_lines and move_reconciles
711                 # will be automatically deleted too
712                 account_move_obj.unlink(cr, uid, [i['move_id'][0]])
713         self.write(cr, uid, ids, {'state':'cancel', 'move_id':False})
714         self._log_event(cr, uid, ids,-1.0, 'Cancel Invoice')
715         return True
716
717     ###################
718
719     def list_distinct_taxes(self, cr, uid, ids):
720         invoices = self.browse(cr, uid, ids)
721         taxes = {}
722         for inv in invoices:
723             for tax in inv.tax_line:
724                 if not tax['name'] in taxes:
725                     taxes[tax['name']] = {'name': tax['name']}
726         return taxes.values()
727
728     def _log_event(self, cr, uid, ids, factor=1.0, name='Open Invoice'):
729         invs = self.read(cr, uid, ids, ['type','partner_id','amount_untaxed'])
730         for inv in invs:
731             part=inv['partner_id'] and inv['partner_id'][0]
732             pc = pr = 0.0
733             cr.execute('select sum(quantity*price_unit) from account_invoice_line where invoice_id=%s', (inv['id'],))
734             total = inv['amount_untaxed']
735             if inv['type'] in ('in_invoice','in_refund'):
736                 partnertype='supplier'
737                 eventtype = 'purchase'
738                 pc = total*factor
739             else:
740                 partnertype = 'customer'
741                 eventtype = 'sale'
742                 pr = total*factor
743             if self.pool.get('res.partner.event.type').check(cr, uid, 'invoice_open'):
744                 self.pool.get('res.partner.event').create(cr, uid, {'name':'Invoice: '+name, 'som':False, 'description':name+' '+str(inv['id']), 'document':name, 'partner_id':part, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'canal_id':False, 'user_id':uid, 'partner_type':partnertype, 'probability':1.0, 'planned_revenue':pr, 'planned_cost':pc, 'type':eventtype})
745         return len(invs)
746
747     def name_get(self, cr, uid, ids, context=None):
748         if not len(ids):
749             return []
750         types = {
751                 'out_invoice': 'CI: ',
752                 'in_invoice': 'SI: ',
753                 'out_refund': 'OR: ',
754                 'in_refund': 'SR: ',
755                 }
756         return [(r['id'], types[r['type']]+(r['number'] or '')+' '+(r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
757
758     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
759         if not args:
760             args=[]
761         if context is None:
762             context={}
763         ids = []
764         if name:
765             ids = self.search(cr, user, [('number','=',name)]+ args, limit=limit, context=context)
766         if not ids:
767             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
768         return self.name_get(cr, user, ids, context)
769
770     def _refund_cleanup_lines(self, lines):
771         for line in lines:
772             del line['id']
773             del line['invoice_id']
774             if 'account_id' in line:
775                 line['account_id'] = line.get('account_id', False) and line['account_id'][0]
776             if 'product_id' in line:
777                 line['product_id'] = line.get('product_id', False) and line['product_id'][0]
778             if 'uos_id' in line:
779                 line['uos_id'] = line.get('uos_id', False) and line['uos_id'][0]
780             if 'invoice_line_tax_id' in line:
781                 line['invoice_line_tax_id'] = [(6,0, line.get('invoice_line_tax_id', [])) ]
782             if 'account_analytic_id' in line:
783                 line['account_analytic_id'] = line.get('account_analytic_id', False) and line['account_analytic_id'][0]
784             if 'tax_code_id' in line :
785                 if isinstance(line['tax_code_id'],tuple)  and len(line['tax_code_id']) >0 :
786                     line['tax_code_id'] = line['tax_code_id'][0]
787             if 'base_code_id' in line :
788                 if isinstance(line['base_code_id'],tuple)  and len(line['base_code_id']) >0 :
789                     line['base_code_id'] = line['base_code_id'][0]
790         return map(lambda x: (0,0,x), lines)
791
792     def refund(self, cr, uid, ids, date=None, period_id=None, description=None):
793         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'])
794
795         new_ids = []
796         for invoice in invoices:
797             del invoice['id']
798
799             type_dict = {
800                 'out_invoice': 'out_refund', # Customer Invoice
801                 'in_invoice': 'in_refund',   # Supplier Invoice
802                 'out_refund': 'out_invoice', # Customer Refund
803                 'in_refund': 'in_invoice',   # Supplier Refund
804             }
805
806
807             invoice_lines = self.pool.get('account.invoice.line').read(cr, uid, invoice['invoice_line'])
808             invoice_lines = self._refund_cleanup_lines(invoice_lines)
809
810             tax_lines = self.pool.get('account.invoice.tax').read(cr, uid, invoice['tax_line'])
811             tax_lines = filter(lambda l: l['manual'], tax_lines)
812             tax_lines = self._refund_cleanup_lines(tax_lines)
813             if not date :
814                 date = time.strftime('%Y-%m-%d')
815             invoice.update({
816                 'type': type_dict[invoice['type']],
817                 'date_invoice': date,
818                 'state': 'draft',
819                 'number': False,
820                 'invoice_line': invoice_lines,
821                 'tax_line': tax_lines
822             })
823             if period_id :
824                 invoice.update({
825                     'period_id': period_id,
826                 })
827             if description :
828                 invoice.update({
829                     'name': description,
830                 })
831             # take the id part of the tuple returned for many2one fields
832             for field in ('address_contact_id', 'address_invoice_id', 'partner_id',
833                     'account_id', 'currency_id', 'payment_term', 'journal_id'):
834                 invoice[field] = invoice[field] and invoice[field][0]
835             # create the new invoice
836             new_ids.append(self.create(cr, uid, invoice))
837         return new_ids
838
839     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=''):
840         if context is None:
841             context = {}
842         #TODO check if we can use different period for payment and the writeoff line
843         assert len(ids)==1, "Can only pay one invoice at a time"
844         invoice = self.browse(cr, uid, ids[0])
845         src_account_id = invoice.account_id.id
846         # Take the seq as name for move
847         types = {'out_invoice': -1, 'in_invoice': 1, 'out_refund': 1, 'in_refund': -1}
848         direction = types[invoice.type]
849         #take the choosen date
850         if 'date_p' in context and context['date_p']:
851             date=context['date_p']
852         else:
853             date=time.strftime('%Y-%m-%d')
854         l1 = {
855             'debit': direction * pay_amount>0 and direction * pay_amount,
856             'credit': direction * pay_amount<0 and - direction * pay_amount,
857             'account_id': src_account_id,
858             'partner_id': invoice.partner_id.id,
859             'ref':invoice.number,
860         }
861         l2 = {
862             'debit': direction * pay_amount<0 and - direction * pay_amount,
863             'credit': direction * pay_amount>0 and direction * pay_amount,
864             'account_id': pay_account_id,
865             'partner_id': invoice.partner_id.id,
866             'ref':invoice.number,
867         }
868
869         name = invoice.invoice_line and invoice.invoice_line[0].name or invoice.number
870         l1['name'] = name
871         l2['name'] = name
872
873         lines = [(0, 0, l1), (0, 0, l2)]
874         move = {'ref': invoice.number, 'line_id': lines, 'journal_id': pay_journal_id, 'period_id': period_id, 'date': date}
875         move_id = self.pool.get('account.move').create(cr, uid, move)
876
877         line_ids = []
878         total = 0.0
879         line = self.pool.get('account.move.line')
880         cr.execute('select id from account_move_line where move_id in ('+str(move_id)+','+str(invoice.move_id.id)+')')
881         lines = line.browse(cr, uid, map(lambda x: x[0], cr.fetchall()) )
882         for l in lines:
883             if l.account_id.id==src_account_id:
884                 line_ids.append(l.id)
885                 total += (l.debit or 0.0) - (l.credit or 0.0)
886         if (not total) or writeoff_acc_id:
887             self.pool.get('account.move.line').reconcile(cr, uid, line_ids, 'manual', writeoff_acc_id, writeoff_period_id, writeoff_journal_id, context)
888         else:
889             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
890
891         # Update the stored value (fields.function), so we write to trigger recompute
892         self.pool.get('account.invoice').write(cr, uid, ids, {}, context=context)
893         return True
894 account_invoice()
895
896 class account_invoice_line(osv.osv):
897     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
898         res = {}
899         for line in self.browse(cr, uid, ids):
900             res[line.id] = round(line.price_unit * line.quantity * (1-(line.discount or 0.0)/100.0),2)
901         return res
902
903     def _price_unit_default(self, cr, uid, context=None):
904         if context is None:
905             context = {}
906         if 'check_total' in context:
907             t = context['check_total']
908             for l in context.get('invoice_line', {}):
909                 if len(l) >= 3 and l[2]:
910                     tax_obj = self.pool.get('account.tax')
911                     p = l[2].get('price_unit', 0) * (1-l[2].get('discount', 0)/100.0)
912                     t = t - (p * l[2].get('quantity'))
913                     taxes = l[2].get('invoice_line_tax_id')
914                     if len(taxes[0]) >= 3 and taxes[0][2]:
915                         taxes=tax_obj.browse(cr, uid, taxes[0][2])
916                         for tax in tax_obj.compute(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)):
917                             t = t - tax['amount']
918             return t
919         return 0
920
921     _name = "account.invoice.line"
922     _description = "Invoice line"
923     _columns = {
924         'name': fields.char('Description', size=256, required=True),
925         'origin': fields.char('Origin', size=256, help="Reference of the document that produced this invoice."),
926         'invoice_id': fields.many2one('account.invoice', 'Invoice Ref', ondelete='cascade', select=True),
927         'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'),
928         'product_id': fields.many2one('product.product', 'Product', ondelete='set null'),
929         '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."),
930         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
931         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',store=True),
932         'quantity': fields.float('Quantity', required=True),
933         'discount': fields.float('Discount (%)', digits=(16,2)),
934         'invoice_line_tax_id': fields.many2many('account.tax', 'account_invoice_line_tax', 'invoice_line_id', 'tax_id', 'Taxes', domain=[('parent_id','=',False)]),
935         'note': fields.text('Notes'),
936         'account_analytic_id':  fields.many2one('account.analytic.account', 'Analytic Account'),
937     }
938     _defaults = {
939         'quantity': lambda *a: 1,
940         'discount': lambda *a: 0.0,
941         'price_unit': _price_unit_default,
942     }
943
944     def product_id_change_unit_price_inv(self, cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=None):
945         tax_obj = self.pool.get('account.tax')
946         if price_unit:
947             taxes = tax_obj.browse(cr, uid, tax_id)
948             for tax in tax_obj.compute_inv(cr, uid, taxes, price_unit, qty, address_invoice_id, product, partner_id):
949                 price_unit = price_unit - tax['amount']
950         return {'price_unit': price_unit,'invoice_line_tax_id': tax_id}
951
952     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, context=None):
953         if context is None:
954             context = {}
955         if not partner_id:
956             raise osv.except_osv(_('No Partner Defined !'),_("You must first select a partner !") )
957         if not product:
958             if type in ('in_invoice', 'in_refund'):
959                 return {'domain':{'product_uom':[]}}
960             else:
961                 return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
962         part = self.pool.get('res.partner').browse(cr, uid, partner_id)
963         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
964
965         lang=part.lang
966         context.update({'lang': lang})
967         res = self.pool.get('product.product').browse(cr, uid, product, context=context)
968         taxep=None
969         tax_obj = self.pool.get('account.tax')
970         if type in ('out_invoice', 'out_refund'):
971             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.taxes_id)
972         else:
973             tax_id = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, res.supplier_taxes_id)
974         if type in ('in_invoice', 'in_refund'):
975             result = self.product_id_change_unit_price_inv(cr, uid, tax_id, price_unit, qty, address_invoice_id, product, partner_id, context=context)
976         else:
977             result = {'price_unit': res.list_price, 'invoice_line_tax_id': tax_id}
978
979         if not name:
980             result['name'] = res.name
981
982         if type in ('out_invoice','out_refund'):
983             a =  res.product_tmpl_id.property_account_income.id
984             if not a:
985                 a = res.categ_id.property_account_income_categ.id
986         else:
987             a =  res.product_tmpl_id.property_account_expense.id
988             if not a:
989                 a = res.categ_id.property_account_expense_categ.id
990
991         a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
992         if a:
993             result['account_id'] = a
994
995         domain = {}
996         result['uos_id'] = uom or res.uom_id.id or False
997         if result['uos_id']:
998             res2 = res.uom_id.category_id.id
999             if res2 :
1000                 domain = {'uos_id':[('category_id','=',res2 )]}
1001         return {'value':result, 'domain':domain}
1002
1003     def move_line_get(self, cr, uid, invoice_id, context=None):
1004         res = []
1005         tax_grouped = {}
1006         tax_obj = self.pool.get('account.tax')
1007         cur_obj = self.pool.get('res.currency')
1008         ait_obj = self.pool.get('account.invoice.tax')
1009         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1010         company_currency = inv.company_id.currency_id.id
1011         cur = inv.currency_id
1012
1013         for line in inv.invoice_line:
1014             mres = self.move_line_get_item(cr, uid, line, context)
1015             if not mres:
1016                 continue
1017             res.append(mres)
1018             tax_code_found= False
1019             for tax in tax_obj.compute(cr, uid, line.invoice_line_tax_id,
1020                     (line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
1021                     line.quantity, inv.address_invoice_id.id, line.product_id,
1022                     inv.partner_id):
1023
1024                 if inv.type in ('out_invoice', 'in_invoice'):
1025                     tax_code_id = tax['base_code_id']
1026                     tax_amount = line.price_subtotal * tax['base_sign']
1027                 else:
1028                     tax_code_id = tax['ref_base_code_id']
1029                     tax_amount = line.price_subtotal * tax['ref_base_sign']
1030
1031                 if tax_code_found:
1032                     if not tax_code_id:
1033                         continue
1034                     res.append(self.move_line_get_item(cr, uid, line, context))
1035                     res[-1]['price'] = 0.0
1036                     res[-1]['account_analytic_id'] = False
1037                 elif not tax_code_id:
1038                     continue
1039                 tax_code_found = True
1040
1041                 res[-1]['tax_code_id'] = tax_code_id
1042                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
1043         return res
1044
1045     def move_line_get_item(self, cr, uid, line, context=None):
1046         return {
1047             'type':'src',
1048             'name': line.name[:64],
1049             'price_unit':line.price_unit,
1050             'quantity':line.quantity,
1051             'price':line.price_subtotal,
1052             'account_id':line.account_id.id,
1053             'product_id':line.product_id.id,
1054             'uos_id':line.uos_id.id,
1055             'account_analytic_id':line.account_analytic_id.id,
1056             'taxes':line.invoice_line_tax_id,
1057         }
1058     #
1059     # Set the tax field according to the account and the partner
1060     #
1061     def onchange_account_id(self, cr, uid, ids, fposition_id, account_id):
1062         if not (fposition_id and account_id):
1063             return {}
1064         taxes = self.pool.get('account.account').browse(cr, uid, account_id).tax_ids
1065         fpos = fposition_id and self.pool.get('account.fiscal.position').browse(cr, uid, fposition_id) or False
1066         res = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
1067         r = {'value':{'invoice_line_tax_id': res}}
1068         return r
1069 account_invoice_line()
1070
1071 class account_invoice_tax(osv.osv):
1072     _name = "account.invoice.tax"
1073     _description = "Invoice Tax"
1074     _columns = {
1075         'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
1076         'name': fields.char('Tax Description', size=64, required=True),
1077         'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
1078         'base': fields.float('Base', digits=(16,2)),
1079         'amount': fields.float('Amount', digits=(16,2)),
1080         'manual': fields.boolean('Manual'),
1081         'sequence': fields.integer('Sequence'),
1082
1083         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="The case of the tax declaration."),
1084         'base_amount': fields.float('Base Code Amount', digits=(16,2)),
1085         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="The case of the tax declaration."),
1086         'tax_amount': fields.float('Tax Code Amount', digits=(16,2)),
1087     }
1088     def base_change(self, cr, uid, ids, base):
1089         return {'value': {'base_amount':base}}
1090     def amount_change(self, cr, uid, ids, amount):
1091         return {'value': {'tax_amount':amount}}
1092     _order = 'sequence'
1093     _defaults = {
1094         'manual': lambda *a: 1,
1095         'base_amount': lambda *a: 0.0,
1096         'tax_amount': lambda *a: 0.0,
1097     }
1098     def compute(self, cr, uid, invoice_id):
1099         tax_grouped = {}
1100         tax_obj = self.pool.get('account.tax')
1101         cur_obj = self.pool.get('res.currency')
1102         inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
1103         cur = inv.currency_id
1104         company_currency = inv.company_id.currency_id.id
1105
1106         for line in inv.invoice_line:
1107             for tax in tax_obj.compute(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):
1108                 val={}
1109                 val['invoice_id'] = inv.id
1110                 val['name'] = tax['name']
1111                 val['amount'] = tax['amount']
1112                 val['manual'] = False
1113                 val['sequence'] = tax['sequence']
1114                 val['base'] = tax['price_unit'] * line['quantity']
1115
1116                 if inv.type in ('out_invoice','in_invoice'):
1117                     val['base_code_id'] = tax['base_code_id']
1118                     val['tax_code_id'] = tax['tax_code_id']
1119                     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')})
1120                     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')})
1121                     val['account_id'] = tax['account_collected_id'] or line.account_id.id
1122                 else:
1123                     val['base_code_id'] = tax['ref_base_code_id']
1124                     val['tax_code_id'] = tax['ref_tax_code_id']
1125                     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')})
1126                     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')})
1127                     val['account_id'] = tax['account_paid_id'] or line.account_id.id
1128
1129                 key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
1130                 if not key in tax_grouped:
1131                     tax_grouped[key] = val
1132                 else:
1133                     tax_grouped[key]['amount'] += val['amount']
1134                     tax_grouped[key]['base'] += val['base']
1135                     tax_grouped[key]['base_amount'] += val['base_amount']
1136                     tax_grouped[key]['tax_amount'] += val['tax_amount']
1137
1138         for t in tax_grouped.values():
1139             t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
1140         return tax_grouped
1141
1142     def move_line_get(self, cr, uid, invoice_id):
1143         res = []
1144         cr.execute('SELECT * FROM account_invoice_tax WHERE invoice_id=%s', (invoice_id,))
1145         for t in cr.dictfetchall():
1146             if not t['amount'] \
1147                     and not t['tax_code_id'] \
1148                     and not t['tax_amount']:
1149                 continue
1150             res.append({
1151                 'type':'tax',
1152                 'name':t['name'],
1153                 'price_unit': t['amount'],
1154                 'quantity': 1,
1155                 'price': t['amount'] or 0.0,
1156                 'account_id': t['account_id'],
1157                 'tax_code_id': t['tax_code_id'],
1158                 'tax_amount': t['tax_amount']
1159             })
1160         return res
1161 account_invoice_tax()
1162
1163 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1164