#
##############################################################################
-import time
+import itertools
from lxml import etree
-import openerp.addons.decimal_precision as dp
-import openerp.exceptions
-from openerp.osv import fields, osv, orm
+from openerp import models, fields, api, _
+from openerp.exceptions import except_orm, Warning, RedirectWarning
+ from openerp.tools import float_compare
-from openerp.tools.translate import _
-from openerp import SUPERUSER_ID
-
-class account_invoice(osv.osv):
- def _amount_all(self, cr, uid, ids, name, args, context=None):
- res = {}
- for invoice in self.browse(cr, uid, ids, context=context):
- res[invoice.id] = {
- 'amount_untaxed': 0.0,
- 'amount_tax': 0.0,
- 'amount_total': 0.0
- }
- for line in invoice.invoice_line:
- res[invoice.id]['amount_untaxed'] += line.price_subtotal
- for line in invoice.tax_line:
- res[invoice.id]['amount_tax'] += line.amount
- res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
- return res
-
- def _get_journal(self, cr, uid, context=None):
- if context is None:
- context = {}
- type_inv = context.get('type', 'out_invoice')
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- company_id = context.get('company_id', user.company_id.id)
- type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale_refund', 'in_refund': 'purchase_refund'}
- journal_obj = self.pool.get('account.journal')
- domain = [('company_id', '=', company_id)]
- if isinstance(type_inv, list):
- domain.append(('type', 'in', [type2journal.get(type) for type in type_inv if type2journal.get(type)]))
- else:
- domain.append(('type', '=', type2journal.get(type_inv, 'sale')))
- res = journal_obj.search(cr, uid, domain, limit=1)
- return res and res[0] or False
-
- def _get_currency(self, cr, uid, context=None):
- res = False
- journal_id = self._get_journal(cr, uid, context=context)
- if journal_id:
- journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
- res = journal.currency and journal.currency.id or journal.company_id.currency_id.id
- return res
-
- def _get_journal_analytic(self, cr, uid, type_inv, context=None):
- type2journal = {'out_invoice': 'sale', 'in_invoice': 'purchase', 'out_refund': 'sale', 'in_refund': 'purchase'}
- tt = type2journal.get(type_inv, 'sale')
- result = self.pool.get('account.analytic.journal').search(cr, uid, [('type','=',tt)], context=context)
- if not result:
- raise osv.except_osv(_('No Analytic Journal!'),_("You must define an analytic journal of type '%s'!") % (tt,))
- return result[0]
-
- def _get_type(self, cr, uid, context=None):
- if context is None:
- context = {}
- return context.get('type', 'out_invoice')
-
- def _reconciled(self, cr, uid, ids, name, args, context=None):
- res = {}
- for inv in self.browse(cr, uid, ids, context=context):
- res[inv.id] = self.test_paid(cr, uid, [inv.id])
- if not res[inv.id] and inv.state == 'paid':
- self.signal_open_test(cr, uid, [inv.id])
- return res
-
- def _get_reference_type(self, cr, uid, context=None):
- return [('none', _('Free Reference'))]
+import openerp.addons.decimal_precision as dp
- def _amount_residual(self, cr, uid, ids, name, args, context=None):
- """Function of the field residua. It computes the residual amount (balance) for each invoice"""
- if context is None:
- context = {}
- ctx = context.copy()
- result = {}
- currency_obj = self.pool.get('res.currency')
- for invoice in self.browse(cr, SUPERUSER_ID, ids, context=context):
- nb_inv_in_partial_rec = max_invoice_id = 0
- result[invoice.id] = 0.0
- if invoice.move_id:
- for aml in invoice.move_id.line_id:
- if aml.account_id.type in ('receivable','payable'):
- if aml.currency_id and aml.currency_id.id == invoice.currency_id.id:
- result[invoice.id] += aml.amount_residual_currency
- else:
- ctx['date'] = aml.date
- result[invoice.id] += currency_obj.compute(cr, uid, aml.company_id.currency_id.id, invoice.currency_id.id, aml.amount_residual, context=ctx)
-
- if aml.reconcile_partial_id.line_partial_ids:
- #we check if the invoice is partially reconciled and if there are other invoices
- #involved in this partial reconciliation (and we sum these invoices)
- for line in aml.reconcile_partial_id.line_partial_ids:
- if line.invoice and invoice.type == line.invoice.type:
- nb_inv_in_partial_rec += 1
- #store the max invoice id as for this invoice we will make a balance instead of a simple division
- max_invoice_id = max(max_invoice_id, line.invoice.id)
- if nb_inv_in_partial_rec:
- #if there are several invoices in a partial reconciliation, we split the residual by the number
- #of invoice to have a sum of residual amounts that matches the partner balance
- new_value = currency_obj.round(cr, uid, invoice.currency_id, result[invoice.id] / nb_inv_in_partial_rec)
- if invoice.id == max_invoice_id:
- #if it's the last the invoice of the bunch of invoices partially reconciled together, we make a
- #balance to avoid rounding errors
- result[invoice.id] = result[invoice.id] - ((nb_inv_in_partial_rec - 1) * new_value)
- else:
- result[invoice.id] = new_value
+# mapping invoice type to journal type
+TYPE2JOURNAL = {
+ 'out_invoice': 'sale',
+ 'in_invoice': 'purchase',
+ 'out_refund': 'sale_refund',
+ 'in_refund': 'purchase_refund',
+}
- #prevent the residual amount on the invoice to be less than 0
- result[invoice.id] = max(result[invoice.id], 0.0)
- return result
+# mapping invoice type to refund type
+TYPE2REFUND = {
+ 'out_invoice': 'out_refund', # Customer Invoice
+ 'in_invoice': 'in_refund', # Supplier Invoice
+ 'out_refund': 'out_invoice', # Customer Refund
+ 'in_refund': 'in_invoice', # Supplier Refund
+}
- # Give Journal Items related to the payment reconciled to this invoice
- # Return ids of partial and total payments related to the selected invoices
- def _get_lines(self, cr, uid, ids, name, arg, context=None):
- res = {}
- for invoice in self.browse(cr, uid, ids, context=context):
- id = invoice.id
- res[id] = []
- if not invoice.move_id:
- continue
- data_lines = [x for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
- partial_ids = []
- for line in data_lines:
- ids_line = []
- if line.reconcile_id:
- ids_line = line.reconcile_id.line_id
- elif line.reconcile_partial_id:
- ids_line = line.reconcile_partial_id.line_partial_ids
- l = map(lambda x: x.id, ids_line)
- partial_ids.append(line.id)
- res[id] =[x for x in l if x <> line.id and x not in partial_ids]
- return res
+MAGIC_COLUMNS = ('id', 'create_uid', 'create_date', 'write_uid', 'write_date')
- def _get_invoice_line(self, cr, uid, ids, context=None):
- result = {}
- for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
- result[line.invoice_id.id] = True
- return result.keys()
-
- def _get_invoice_tax(self, cr, uid, ids, context=None):
- result = {}
- for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
- result[tax.invoice_id.id] = True
- return result.keys()
-
- def _compute_lines(self, cr, uid, ids, name, args, context=None):
- result = {}
- for invoice in self.browse(cr, uid, ids, context=context):
- src = []
- lines = []
- if invoice.move_id:
- for m in invoice.move_id.line_id:
- if m.account_id != invoice.account_id:
- continue
- temp_lines = []
- if m.reconcile_id:
- temp_lines = map(lambda x: x.id, m.reconcile_id.line_id)
- elif m.reconcile_partial_id:
- temp_lines = map(lambda x: x.id, m.reconcile_partial_id.line_partial_ids)
- lines += [x for x in temp_lines if x not in lines]
- src.append(m.id)
-
- lines = filter(lambda x: x not in src, lines)
- result[invoice.id] = lines
- return result
-
- def _get_invoice_from_line(self, cr, uid, ids, context=None):
- move = {}
- for line in self.pool.get('account.move.line').browse(cr, uid, ids, context=context):
- if line.reconcile_partial_id:
- for line2 in line.reconcile_partial_id.line_partial_ids:
- move[line2.move_id.id] = True
- if line.reconcile_id:
- for line2 in line.reconcile_id.line_id:
- move[line2.move_id.id] = True
- invoice_ids = []
- if move:
- invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
- return invoice_ids
-
- def _get_invoice_from_reconcile(self, cr, uid, ids, context=None):
- move = {}
- for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids, context=context):
- for line in r.line_partial_ids:
- move[line.move_id.id] = True
- for line in r.line_id:
- move[line.move_id.id] = True
-
- invoice_ids = []
- if move:
- invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('move_id','in',move.keys())], context=context)
- return invoice_ids
+class account_invoice(models.Model):
_name = "account.invoice"
_inherit = ['mail.thread']
- _description = 'Invoice'
- _order = "id desc"
+ _description = "Invoice"
+ _order = "number desc, id desc"
_track = {
'type': {
},
"""
return move_lines
- def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
- company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id
- if not inv.tax_line:
+ @api.multi
+ def check_tax_lines(self, compute_taxes):
+ account_invoice_tax = self.env['account.invoice.tax']
+ company_currency = self.company_id.currency_id
+ if not self.tax_line:
for tax in compute_taxes.values():
- ait_obj.create(cr, uid, tax)
+ account_invoice_tax.create(tax)
else:
tax_key = []
- for tax in inv.tax_line:
++ precision = self.env['decimal.precision'].precision_get('Account')
+ for tax in self.tax_line:
if tax.manual:
continue
key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id, tax.account_analytic_id.id)
tax_key.append(key)
- if not key in compute_taxes:
- raise osv.except_osv(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
+ if key not in compute_taxes:
+ raise except_orm(_('Warning!'), _('Global taxes defined, but they are not in invoice lines !'))
base = compute_taxes[key]['base']
- if abs(base - tax.base) > company_currency.rounding:
- precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
+ if float_compare(abs(base - tax.base), company_currency.rounding, precision_digits=precision) == 1:
- raise osv.except_osv(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
+ raise except_orm(_('Warning!'), _('Tax base different!\nClick on compute to update the tax base.'))
for key in compute_taxes:
- if not key in tax_key:
- raise osv.except_osv(_('Warning!'), _('Taxes are missing!\nClick on compute button.'))
+ if key not in tax_key:
+ raise except_orm(_('Warning!'), _('Taxes are missing!\nClick on compute button.'))
- def compute_invoice_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None):
- if context is None:
- context={}
+ @api.multi
+ def compute_invoice_totals(self, company_currency, ref, invoice_move_lines):
total = 0
total_currency = 0
- cur_obj = self.pool.get('res.currency')
- for i in invoice_move_lines:
- if inv.currency_id.id != company_currency:
- context.update({'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)})
- i['currency_id'] = inv.currency_id.id
- i['amount_currency'] = i['price']
- i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
- company_currency, i['price'],
- context=context)
+ for line in invoice_move_lines:
+ if self.currency_id != company_currency:
+ currency = self.currency_id.with_context(date=self.date_invoice or fields.Date.context_today(self))
+ line['currency_id'] = currency.id
+ line['amount_currency'] = line['price']
+ line['price'] = currency.compute(line['price'], company_currency)
else:
- i['amount_currency'] = False
- i['currency_id'] = False
- i['ref'] = ref
- if inv.type in ('out_invoice','in_refund'):
- total += i['price']
- total_currency += i['amount_currency'] or i['price']
- i['price'] = - i['price']
+ line['currency_id'] = False
+ line['amount_currency'] = False
+ line['ref'] = ref
+ if self.type in ('out_invoice','in_refund'):
+ total += line['price']
+ total_currency += line['amount_currency'] or line['price']
+ line['price'] = - line['price']
else:
- total -= i['price']
- total_currency -= i['amount_currency'] or i['price']
+ total -= line['price']
+ total_currency -= line['amount_currency'] or line['price']
return total, total_currency, invoice_move_lines
- def inv_line_characteristic_hashcode(self, invoice, invoice_line):
+ def inv_line_characteristic_hashcode(self, invoice_line):
"""Overridable hashcode generation for invoice lines. Lines having the same hashcode
will be grouped together if the journal has the 'group line' option. Of course a module
can add fields to invoice lines that would need to be tested too before merging lines