[MERGE] sale, purchase, stock: refactoring of invoice creation (Alexis de Lattre...
authorRaphael Collet <rco@openerp.com>
Tue, 31 Jan 2012 10:53:22 +0000 (11:53 +0100)
committerRaphael Collet <rco@openerp.com>
Tue, 31 Jan 2012 10:53:22 +0000 (11:53 +0100)
bzr revid: rco@openerp.com-20120131105322-80x7fp2u1z5fyo6o

1  2 
addons/purchase/stock.py
addons/sale/sale.py
addons/sale/stock.py
addons/stock/stock.py

@@@ -58,18 -58,25 +58,24 @@@ class stock_picking(osv.osv)
          'purchase_id': False,
      }
  
-     def _get_address_invoice(self, cr, uid, picking):
-         """ Gets invoice address of a partner
-         @return {'contact': address, 'invoice': address} for invoice
+     def _get_partner_to_invoice(self, cr, uid, picking, context=None):
 -        """Inherit the original function of the 'stock' module
 -        We select the partner of the sale order as the partner of the customer invoice
++        """ Inherit the original function of the 'stock' module
++            We select the partner of the sale order as the partner of the customer invoice
          """
-         res = super(stock_picking, self)._get_address_invoice(cr, uid, picking)
          if picking.purchase_id:
-             partner_obj = self.pool.get('res.partner')
-             partner = picking.purchase_id.partner_id or picking.address_id.partner_id
-             data = partner_obj.address_get(cr, uid, [partner.id],
-                 ['contact', 'invoice'])
-             res.update(data)
-         return res
+             return picking.purchase_id.partner_id
+         return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
+     def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
 -        """Inherit the original function of the 'stock' module in order to override some
 -        values if the picking has been generated by a purchase order
++        """ Inherit the original function of the 'stock' module in order to override some
++            values if the picking has been generated by a purchase order
+         """
+         invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
+         if picking.purchase_id:
+             invoice_vals['address_contact_id'], invoice_vals['address_invoice_id'] = \
 -                    self.pool.get('res.partner').address_get(cr, uid, [partner.id],
 -                            ['contact', 'invoice']).values()
++                self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['contact', 'invoice']).values()
+             invoice_vals['fiscal_position'] = picking.purchase_id.fiscal_position.id
+         return invoice_vals
  
      def get_currency_id(self, cursor, user, picking):
          if picking.purchase_id:
      def _invoice_line_hook(self, cursor, user, move_line, invoice_line_id):
          if move_line.purchase_line_id:
              invoice_line_obj = self.pool.get('account.invoice.line')
+             purchase_line_obj = self.pool.get('purchase.order.line') 
 -            purchase_line_obj.write(cursor, user, [move_line.purchase_line_id.id],
 -                                    {
 -                                        'invoiced': True,
 -                                        'invoice_lines': [(4, invoice_line_id)],
 -                                    })
++            purchase_line_obj.write(cursor, user, [move_line.purchase_line_id.id], {
++                'invoiced': True,
++                'invoice_lines': [(4, invoice_line_id)],
++            })
              invoice_line_obj.write(cursor, user, [invoice_line_id], {'note':  move_line.purchase_line_id.notes,})
          return super(stock_picking, self)._invoice_line_hook(cursor, user, move_line, invoice_line_id)
  
@@@ -981,10 -981,12 +981,13 @@@ class sale_order_line(osv.osv)
          'price_unit': 0.0,
      }
  
-     def invoice_line_create(self, cr, uid, ids, context=None):
-         if context is None:
-             context = {}
+     def _prepare_order_line_invoice_line(self, cr, uid, ids, line, account_id=False, context=None):
 -        """Builds the invoice line dict from a sale order line
 -        @param line: sale order line object
 -        @param account_id: the id of the account to force eventually (the method is used for picking return including service)
 -        @return: dict that will be used to create the invoice line"""
 -        
++        """ Builds the invoice line dict from a sale order line
++            @param line: sale order line object
++            @param account_id: the id of the account to force eventually (the method is used for picking return including service)
++            @return: dict that will be used to create the invoice line
++        """
 +
          def _get_line_qty(line):
              if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
                  if line.product_uos:
                  return self.pool.get('procurement.order').uom_get(cr, uid,
                          line.procurement_id.id, context=context)
  
-         create_ids = []
-         sales = {}
-         for line in self.browse(cr, uid, ids, context=context):
-             if not line.invoiced:
+         if not line.invoiced:
+             if not account_id:
                  if line.product_id:
-                     a = line.product_id.product_tmpl_id.property_account_income.id
-                     if not a:
-                         a = line.product_id.categ_id.property_account_income_categ.id
-                     if not a:
+                     account_id = line.product_id.product_tmpl_id.property_account_income.id
+                     if not account_id:
+                         account_id = line.product_id.categ_id.property_account_income_categ.id
+                     if not account_id:
                          raise osv.except_osv(_('Error !'),
--                                _('There is no income account defined ' \
-                                         'for this product: "%s" (id:%d)') % \
-                                         (line.product_id.name, line.product_id.id,))
 -                                    'for this product: "%s" (id:%d)') % \
++                                _('There is no income account defined for this product: "%s" (id:%d)') % \
+                                     (line.product_id.name, line.product_id.id,))
                  else:
                      prop = self.pool.get('ir.property').get(cr, uid,
                              'property_account_income_categ', 'product.category',
                              context=context)
-                     a = prop and prop.id or False
-                 uosqty = _get_line_qty(line)
-                 uos_id = _get_line_uom(line)
-                 pu = 0.0
-                 if uosqty:
-                     pu = round(line.price_unit * line.product_uom_qty / uosqty,
-                             self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
-                 fpos = line.order_id.fiscal_position or False
-                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
-                 if not a:
-                     raise osv.except_osv(_('Error !'),
-                                 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
-                 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
-                     'name': line.name,
-                     'origin': line.order_id.name,
-                     'account_id': a,
-                     'price_unit': pu,
-                     'quantity': uosqty,
-                     'discount': line.discount,
-                     'uos_id': uos_id,
-                     'product_id': line.product_id.id or False,
-                     'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
-                     'note': line.notes,
-                     'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
-                 })
+                     account_id = prop and prop.id or False
+             uosqty = _get_line_qty(line)
+             uos_id = _get_line_uom(line)
+             pu = 0.0
+             if uosqty:
+                 pu = round(line.price_unit * line.product_uom_qty / uosqty,
+                         self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
+             fpos = line.order_id.fiscal_position or False
+             account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account_id)
+             if not account_id:
+                 raise osv.except_osv(_('Error !'),
+                             _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
+             return {
+                 'name': line.name,
+                 'origin': line.order_id.name,
+                 'account_id': account_id,
+                 'price_unit': pu,
+                 'quantity': uosqty,
+                 'discount': line.discount,
+                 'uos_id': uos_id,
+                 'product_id': line.product_id.id or False,
+                 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
+                 'note': line.notes,
+                 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
+             }
 -        else:
 -            return False
++
++        return False
+     def invoice_line_create(self, cr, uid, ids, context=None):
+         if context is None:
+             context = {}
+         create_ids = []
 -        sales = {}
++        sales = set()
+         for line in self.browse(cr, uid, ids, context=context):
+             vals = self._prepare_order_line_invoice_line(cr, uid, ids, line, False, context)
+             if vals:
+                 inv_id = self.pool.get('account.invoice.line').create(cr, uid, vals, context=context)
                  cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
                  self.write(cr, uid, [line.id], {'invoiced': True})
--                sales[line.order_id.id] = True
++                sales.add(line.order_id.id)
                  create_ids.append(inv_id)
          # Trigger workflow events
          wf_service = netsvc.LocalService("workflow")
--        for sid in sales.keys():
--            wf_service.trg_write(uid, 'sale.order', sid, cr)
++        for sale_id in sales:
++            wf_service.trg_write(uid, 'sale.order', sale_id, cr)
          return create_ids
  
      def button_cancel(self, cr, uid, ids, context=None):
@@@ -48,24 -48,32 +48,32 @@@ class stock_picking(osv.osv)
          else:
              return super(stock_picking, self).get_currency_id(cursor, user, picking)
  
-     def _get_payment_term(self, cursor, user, picking):
-         if picking.sale_id and picking.sale_id.payment_term:
-             return picking.sale_id.payment_term.id
-         return super(stock_picking, self)._get_payment_term(cursor, user, picking)
-     def _get_address_invoice(self, cursor, user, picking):
-         res = {}
+     def _get_partner_to_invoice(self, cr, uid, picking, context=None):
 -        """Inherit the original function of the 'stock' module
 -        We select the partner of the sale order as the partner of the customer invoice
++        """ Inherit the original function of the 'stock' module
++            We select the partner of the sale order as the partner of the customer invoice
+         """
          if picking.sale_id:
-             res['contact'] = picking.sale_id.partner_order_id.id
-             res['invoice'] = picking.sale_id.partner_invoice_id.id
-             return res
-         return super(stock_picking, self)._get_address_invoice(cursor, user, picking)
+             return picking.sale_id.partner_id
+         return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
  
      def _get_comment_invoice(self, cursor, user, picking):
          if picking.note or (picking.sale_id and picking.sale_id.note):
              return picking.note or picking.sale_id.note
          return super(stock_picking, self)._get_comment_invoice(cursor, user, picking)
  
+     def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
 -        """Inherit the original function of the 'stock' module in order to override some
 -        values if the picking has been generated by a sale order
++        """ Inherit the original function of the 'stock' module in order to override some
++            values if the picking has been generated by a sale order
+         """
+         invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
+         if picking.sale_id:
+             invoice_vals['address_contact_id'] = picking.sale_id.partner_order_id.id
+             invoice_vals['address_invoice_id'] = picking.sale_id.partner_invoice_id.id
+             invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id
+             invoice_vals['payment_term'] = picking.sale_id.payment_term.id
+             invoice_vals['user_id'] = picking.sale_id.user_id.id
+         return invoice_vals
      def _get_price_unit_invoice(self, cursor, user, move_line, type):
          if move_line.sale_line_id and move_line.sale_line_id.product_id.id == move_line.product_id.id:
              uom_id = move_line.product_id.uom_id.id
                          if not account_id:
                              account_id = sale_line.product_id.categ_id.\
                                      property_account_expense_categ.id
-                     price_unit = sale_line.price_unit
-                     discount = sale_line.discount
-                     tax_ids = sale_line.tax_id
-                     tax_ids = map(lambda x: x.id, tax_ids)
-                     account_analytic_id = self._get_account_analytic_invoice(cursor,
-                             user, picking, sale_line)
-                     account_id = fiscal_position_obj.map_account(cursor, user, picking.sale_id.partner_id.property_account_position, account_id)
-                     invoice = invoices[result[picking.id]]
-                     invoice_line_id = invoice_line_obj.create(cursor, user, {
-                         'name': name,
-                         'invoice_id': invoice.id,
-                         'uos_id': sale_line.product_uos.id or sale_line.product_uom.id,
-                         'product_id': sale_line.product_id.id,
-                         'account_id': account_id,
-                         'price_unit': price_unit,
-                         'discount': discount,
-                         'quantity': sale_line.product_uos_qty,
-                         'invoice_line_tax_id': [(6, 0, tax_ids)],
-                         'account_analytic_id': account_analytic_id,
-                         'notes':sale_line.notes
-                     }, context=context)
-                     order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
-                         'invoice_lines': [(6, 0, [invoice_line_id])],
-                     })
+                     vals = order_line_obj._prepare_order_line_invoice_line(cursor, user, ids, sale_line, account_id, context)
+                     if vals: #note: in some cases we may not want to include all service lines as invoice lines
+                         vals['name'] = name
+                         vals['account_analytic_id'] = self._get_account_analytic_invoice(cursor, user, picking, sale_line)
+                         vals['invoice_id'] = invoices[result[picking.id]].id
+                         invoice_line_id = invoice_line_obj.create(cursor, user, vals, context=context)
 -                        order_line_obj.write(cursor, user, [sale_line.id], {'invoiced': True,
 -                        'invoice_lines': [(6, 0, [invoice_line_id])]})
++                        order_line_obj.write(cursor, user, [sale_line.id], {
++                            'invoiced': True,
++                            'invoice_lines': [(6, 0, [invoice_line_id])],
++                        })
          return result
  
- # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+ # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -864,21 -861,13 +865,13 @@@ class stock_picking(osv.osv)
      def get_currency_id(self, cr, uid, picking):
          return False
  
-     def _get_payment_term(self, cr, uid, picking):
-         """ Gets payment term from partner.
-         @return: Payment term
+     def _get_partner_to_invoice(self, cr, uid, picking, context=None):
+         """ Gets the partner that will be invoiced
 -        Note that this function is inherited in the sale module
 -        @param picking: object of the picking for which we are selecting the partner to invoice
 -        @return: object of the partner to invoice
++            Note that this function is inherited in the sale module
++            @param picking: object of the picking for which we are selecting the partner to invoice
++            @return: object of the partner to invoice
          """
-         partner = picking.address_id.partner_id
-         return partner.property_payment_term and partner.property_payment_term.id or False
-     def _get_address_invoice(self, cr, uid, picking):
-         """ Gets invoice address of a partner
-         @return {'contact': address, 'invoice': address} for invoice
-         """
-         partner_obj = self.pool.get('res.partner')
-         partner = picking.address_id.partner_id
-         return partner_obj.address_get(cr, uid, [partner.id],
-                 ['contact', 'invoice'])
+         return picking.address_id and picking.address_id.partner_id
  
      def _get_comment_invoice(self, cr, uid, picking):
          """
                  inv_type = 'out_invoice'
          return inv_type
  
+     def _prepare_invoice_group(self, cr, uid, picking, partner, invoice, context=None):
 -        """Builds the dict for grouped invoices
 -        @param picking: picking object
 -        @param partner: object of the partner to invoice (not used here, but may be usefull if this function is inherited)
 -        @param invoice: object of the invoice that we are updating
 -        @return: dict that will be used to update the invoice
++        """ Builds the dict for grouped invoices
++            @param picking: picking object
++            @param partner: object of the partner to invoice (not used here, but may be usefull if this function is inherited)
++            @param invoice: object of the invoice that we are updating
++            @return: dict that will be used to update the invoice
+         """
+         comment = self._get_comment_invoice(cr, uid, picking)
 -
+         return {
+             'name': (invoice.name or '') + ', ' + (picking.name or ''),
+             'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
+             'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
+             'date_invoice': context.get('date_inv', False),
 -            'user_id': uid
++            'user_id': uid,
+         }
+     def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
 -        """Builds the dict containing the values for the invoice
 -        @param picking: picking object
 -        @param partner: object of the partner to invoice
 -        @param inv_type: type of the invoice ('out_invoice', 'in_invoice', ...)
 -        @param journal_id: ID of the accounting journal
 -        @return: dict that will be used to create the invoice object
++        """ Builds the dict containing the values for the invoice
++            @param picking: picking object
++            @param partner: object of the partner to invoice
++            @param inv_type: type of the invoice ('out_invoice', 'in_invoice', ...)
++            @param journal_id: ID of the accounting journal
++            @return: dict that will be used to create the invoice object
+         """
+         if inv_type in ('out_invoice', 'out_refund'):
+             account_id = partner.property_account_receivable.id
+         else:
+             account_id = partner.property_account_payable.id
+         address_contact_id, address_invoice_id = \
+                 self.pool.get('res.partner').address_get(cr, uid, [partner.id],
+                         ['contact', 'invoice']).values()
+         comment = self._get_comment_invoice(cr, uid, picking)
+         invoice_vals = {
+             'name': picking.name,
+             'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
+             'type': inv_type,
+             'account_id': account_id,
+             'partner_id': partner.id,
+             'address_invoice_id': address_invoice_id,
+             'address_contact_id': address_contact_id,
+             'comment': comment,
 -            'payment_term': partner.property_payment_term and partner.property_payment_term.id
 -                    or False,
++            'payment_term': partner.property_payment_term and partner.property_payment_term.id or False,
+             'fiscal_position': partner.property_account_position.id,
+             'date_invoice': context.get('date_inv', False),
+             'company_id': picking.company_id.id,
 -            'user_id': uid
++            'user_id': uid,
+         }
+         cur_id = self.get_currency_id(cr, uid, picking)
+         if cur_id:
+             invoice_vals['currency_id'] = cur_id
+         if journal_id:
+             invoice_vals['journal_id'] = journal_id
+         return invoice_vals
+     def _prepare_invoice_line(self, cr, uid, group, picking, move_line, invoice_id,
+         invoice_vals, context=None):
 -        """Builds the dict containing the values for the invoice line
 -        @param group: True or False
 -        @param picking: picking object
 -        @param: move_line: move_line object
 -        @param: invoice_id: ID of the related invoice
 -        @param: invoice_vals: dict used to created the invoice
 -        @return: dict that will be used to create the invoice line
++        """ Builds the dict containing the values for the invoice line
++            @param group: True or False
++            @param picking: picking object
++            @param: move_line: move_line object
++            @param: invoice_id: ID of the related invoice
++            @param: invoice_vals: dict used to created the invoice
++            @return: dict that will be used to create the invoice line
+         """
+         if group:
+             name = (picking.name or '') + '-' + move_line.name
+         else:
+             name = move_line.name
+         origin = move_line.picking_id.name or ''
+         if move_line.picking_id.origin:
+             origin += ':' + move_line.picking_id.origin
+         if invoice_vals['type'] in ('out_invoice', 'out_refund'):
+             account_id = move_line.product_id.product_tmpl_id.\
+                     property_account_income.id
+             if not account_id:
+                 account_id = move_line.product_id.categ_id.\
+                         property_account_income_categ.id
+         else:
+             account_id = move_line.product_id.product_tmpl_id.\
+                     property_account_expense.id
+             if not account_id:
+                 account_id = move_line.product_id.categ_id.\
+                         property_account_expense_categ.id
+         if invoice_vals['fiscal_position']:
+             fp_obj = self.pool.get('account.fiscal.position')
+             fiscal_position = fp_obj.browse(cr, uid, invoice_vals['fiscal_position'], context=context)
+             account_id = fp_obj.map_account(cr, uid, fiscal_position, account_id)
+         # set UoS if it's a sale and the picking doesn't have one
+         uos_id = move_line.product_uos and move_line.product_uos.id or False
+         if not uos_id and invoice_vals['type'] in ('out_invoice', 'out_refund'):
+             uos_id = move_line.product_uom.id
+         return {
+             'name': name,
+             'origin': origin,
+             'invoice_id': invoice_id,
+             'uos_id': uos_id,
+             'product_id': move_line.product_id.id,
+             'account_id': account_id,
+             'price_unit': self._get_price_unit_invoice(cr, uid, move_line, invoice_vals['type']),
+             'discount': self._get_discount_invoice(cr, uid, move_line),
+             'quantity': move_line.product_uos_qty or move_line.product_qty,
 -            'invoice_line_tax_id': [(6, 0,
 -                    self._get_taxes_invoice(cr, uid, move_line, invoice_vals['type']))],
++            'invoice_line_tax_id': [(6, 0, self._get_taxes_invoice(cr, uid, move_line, invoice_vals['type']))],
+             'account_analytic_id': self._get_account_analytic_invoice(cr, uid, picking, move_line),
 -            }
++        }
      def action_invoice_create(self, cr, uid, ids, journal_id=False,
              group=False, type='out_invoice', context=None):
          """ Creates invoice based on the invoice state selected for picking.