account_invoice.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # -*- coding: utf-8 -*-
  2. # © 2010-2011 Ian Li <ian.li@elico-corp.com>
  3. # © 2015 Cédric Pigeon <cedric.pigeon@acsone.eu>
  4. # © 2016 Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>
  5. # © 2016 Luc De Meyer <luc.demeyer@noviat.com>
  6. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  7. from openerp import models, api
  8. from openerp import workflow
  9. from openerp.osv.orm import browse_record, browse_null
  10. class AccountInvoice(models.Model):
  11. _inherit = "account.invoice"
  12. @api.multi
  13. def _get_invoice_key_cols(self):
  14. return [
  15. 'partner_id', 'user_id', 'type', 'account_id', 'currency_id',
  16. 'journal_id', 'company_id', 'partner_bank_id',
  17. ]
  18. @api.multi
  19. def _get_invoice_line_key_cols(self):
  20. fields = [
  21. 'name', 'origin', 'discount', 'invoice_line_tax_id', 'price_unit',
  22. 'product_id', 'account_id', 'account_analytic_id',
  23. ]
  24. for field in ['analytics_id']:
  25. if field in self.env['account.invoice.line']._fields:
  26. fields.append(field)
  27. return fields
  28. @api.multi
  29. def _get_first_invoice_fields(self, invoice):
  30. return {
  31. 'origin': '%s' % (invoice.origin or '',),
  32. 'partner_id': invoice.partner_id.id,
  33. 'journal_id': invoice.journal_id.id,
  34. 'user_id': invoice.user_id.id,
  35. 'currency_id': invoice.currency_id.id,
  36. 'company_id': invoice.company_id.id,
  37. 'type': invoice.type,
  38. 'account_id': invoice.account_id.id,
  39. 'state': 'draft',
  40. 'reference': '%s' % (invoice.reference or '',),
  41. 'name': '%s' % (invoice.name or '',),
  42. 'fiscal_position': invoice.fiscal_position.id,
  43. 'payment_term': invoice.payment_term.id,
  44. 'period_id': invoice.period_id.id,
  45. 'invoice_line': {},
  46. 'partner_bank_id': invoice.partner_bank_id.id,
  47. }
  48. @api.multi
  49. def _merge_invoice_line_values(self, vals, new_invoice_line):
  50. """This method merges an invoice line with the existing values from
  51. previous line(s) that matches the merging key.
  52. :param vals: Dictionary of values of the previous invoice line(s)
  53. :param new_invoice_line: Recordset of the new line to merge.
  54. :return: None
  55. """
  56. uom = self.env['product.uom'].browse(vals.get('uos_id', False))
  57. uom_factor = uom.factor if uom.exists() else 1.0
  58. uos_factor = new_invoice_line.uos_id.factor or 1.0
  59. # merge the line with an existing line
  60. vals['quantity'] += (new_invoice_line.quantity *
  61. uos_factor / uom_factor)
  62. @api.multi
  63. def _get_draft_invoices(self):
  64. """Overridable function to return draft invoices in selection"""
  65. return self.filtered(lambda x: x.state == 'draft')
  66. @api.multi
  67. def do_merge(self, keep_references=True, date_invoice=False):
  68. """
  69. To merge similar type of account invoices.
  70. Invoices will only be merged if:
  71. * Account invoices are in draft
  72. * Account invoices belong to the same partner
  73. * Account invoices are have same company, partner, address, currency,
  74. journal, currency, salesman, account, type
  75. Lines will only be merged if:
  76. * Invoice lines are exactly the same except for the quantity and unit
  77. @param self: The object pointer.
  78. @param keep_references: If True, keep reference of original invoices
  79. @return: new account invoice id
  80. """
  81. def make_key(br, fields):
  82. list_key = []
  83. for field in fields:
  84. field_val = getattr(br, field)
  85. if field in ('product_id', 'account_id'):
  86. if not field_val:
  87. field_val = False
  88. if (isinstance(field_val, browse_record) and
  89. field != 'invoice_line_tax_id'):
  90. field_val = field_val.id
  91. elif isinstance(field_val, browse_null):
  92. field_val = False
  93. elif (isinstance(field_val, list) or
  94. field == 'invoice_line_tax_id'):
  95. field_val = ((6, 0, tuple([v.id for v in field_val])),)
  96. list_key.append((field, field_val))
  97. list_key.sort()
  98. return tuple(list_key)
  99. # compute what the new invoices should contain
  100. new_invoices = {}
  101. seen_origins = {}
  102. seen_client_refs = {}
  103. line_sequence = 1
  104. for account_invoice in self._get_draft_invoices():
  105. invoice_key = make_key(
  106. account_invoice, self._get_invoice_key_cols())
  107. new_invoice = new_invoices.setdefault(invoice_key, ({}, []))
  108. origins = seen_origins.setdefault(invoice_key, set())
  109. client_refs = seen_client_refs.setdefault(invoice_key, set())
  110. new_invoice[1].append(account_invoice.id)
  111. invoice_infos = new_invoice[0]
  112. if not invoice_infos:
  113. invoice_infos.update(
  114. self._get_first_invoice_fields(account_invoice))
  115. origins.add(account_invoice.origin)
  116. client_refs.add(account_invoice.reference)
  117. if not keep_references:
  118. invoice_infos.pop('name')
  119. else:
  120. if account_invoice.name and keep_references:
  121. invoice_infos['name'] = \
  122. (invoice_infos['name'] or '') + \
  123. (' %s' % (account_invoice.name,))
  124. if account_invoice.origin and \
  125. account_invoice.origin not in origins:
  126. invoice_infos['origin'] = \
  127. (invoice_infos['origin'] or '') + ' ' + \
  128. account_invoice.origin
  129. origins.add(account_invoice.origin)
  130. if account_invoice.reference \
  131. and account_invoice.reference not in client_refs:
  132. invoice_infos['reference'] = \
  133. (invoice_infos['reference'] or '') + \
  134. (' %s' % (account_invoice.reference,))
  135. client_refs.add(account_invoice.reference)
  136. for invoice_line in account_invoice.invoice_line:
  137. line_key = make_key(
  138. invoice_line, self._get_invoice_line_key_cols())
  139. o_line = invoice_infos['invoice_line'].setdefault(line_key, {})
  140. if o_line:
  141. self._merge_invoice_line_values(o_line, invoice_line)
  142. o_line['o_line_ids'].append(invoice_line.id)
  143. else:
  144. # append a new "standalone" line
  145. o_line.update(
  146. invoice_line._convert_to_write(invoice_line._cache))
  147. if 'invoice_id' in o_line:
  148. del o_line['invoice_id']
  149. o_line['o_line_ids'] = [invoice_line.id]
  150. o_line['sequence'] = line_sequence
  151. line_sequence += 1
  152. invoices_info = {}
  153. invoice_lines_info = {}
  154. for invoice_key, (invoice_data, old_ids) in new_invoices.iteritems():
  155. # skip merges with only one invoice
  156. if len(old_ids) < 2:
  157. continue
  158. if date_invoice:
  159. invoice_data['date_invoice'] = date_invoice
  160. # create the new invoice
  161. invoice_line_data = invoice_data['invoice_line']
  162. del invoice_data['invoice_line']
  163. newinvoice = self.with_context(is_merge=True).create(invoice_data)
  164. invoice_lines_info[newinvoice.id] = {}
  165. for entry in invoice_line_data.values():
  166. o_line_ids = entry['o_line_ids']
  167. del entry['o_line_ids']
  168. entry['invoice_id'] = newinvoice.id
  169. inv_line = self.env['account.invoice.line'].create(entry)
  170. for o_line_id in o_line_ids:
  171. invoice_lines_info[newinvoice.id][o_line_id] = inv_line.id
  172. newinvoice.button_reset_taxes()
  173. invoices_info.update({newinvoice.id: old_ids})
  174. # make triggers pointing to the old invoices point to the new
  175. # invoice
  176. for old_id in old_ids:
  177. workflow.trg_redirect(
  178. self.env.uid, 'account.invoice', old_id, newinvoice.id,
  179. self.env.cr)
  180. workflow.trg_validate(
  181. self.env.uid, 'account.invoice', old_id, 'invoice_cancel',
  182. self.env.cr)
  183. # make link between original sale order if sale is not installed
  184. # None if purchase is not installed
  185. if 'sale.order' in self.env.registry:
  186. so_obj = self.env['sale.order']
  187. for new_invoice_id in invoices_info:
  188. todos = so_obj.search(
  189. [('invoice_ids', 'in', invoices_info[new_invoice_id])])
  190. todos.write({'invoice_ids': [(4, new_invoice_id)]})
  191. for org_so in todos:
  192. for so_line in org_so.order_line:
  193. org_ilines = so_line.mapped('invoice_lines')
  194. invoice_line_ids = []
  195. for org_iline in org_ilines:
  196. # the sale.order, _prepare_invoice method
  197. # allows to remove created invoice lines
  198. # from the final sale order invoice.
  199. # We need as a consequence to add a check.
  200. if org_iline.id \
  201. in invoice_lines_info[new_invoice_id]:
  202. invoice_line_ids.append(
  203. invoice_lines_info[
  204. new_invoice_id][org_iline.id])
  205. so_line.write(
  206. {'invoice_lines': [(6, 0, invoice_line_ids)]})
  207. # recreate link (if any) between original analytic account line
  208. # (invoice time sheet for example) and this new invoice
  209. anal_line_obj = self.env['account.analytic.line']
  210. if 'invoice_id' in anal_line_obj._columns:
  211. for new_invoice_id in invoices_info:
  212. todos = anal_line_obj.search(
  213. [('invoice_id', 'in', invoices_info[new_invoice_id])])
  214. todos.write({'invoice_id': new_invoice_id})
  215. return invoices_info, invoice_lines_info