From 8394afcf8a8b87cd450e259753a117416a56c3d8 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 5 Jan 2012 17:06:35 +0100 Subject: [PATCH 01/12] Extend the function action_invoice_create() so that the dict of the invoice is generated inside a dedicated function. We can now inherit this function in custom modules to add/modify values easily. bzr revid: alexis@via.ecp.fr-20120105160635-ac6drtj2ei363r8l --- addons/stock/stock.py | 87 ++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index cd9242c6b1f..ce344804347 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -968,6 +968,49 @@ class stock_picking(osv.osv): 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""" + 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 + } + + def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None): + """Builds the dict containing the values for the invoice""" + 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._get_address_invoice(cr, uid, picking).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': self._get_payment_term(cr, uid, picking), + 'fiscal_position': partner.property_account_position.id, + 'date_invoice': context.get('date_inv', False), + 'company_id': picking.company_id.id, + '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 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. @@ -981,7 +1024,6 @@ class stock_picking(osv.osv): invoice_obj = self.pool.get('account.invoice') invoice_line_obj = self.pool.get('account.invoice.line') - address_obj = self.pool.get('res.partner.address') invoices_group = {} res = {} inv_type = type @@ -996,48 +1038,15 @@ class stock_picking(osv.osv): if not inv_type: inv_type = self._get_invoice_type(picking) - 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._get_address_invoice(cr, uid, picking).values() - address = address_obj.browse(cr, uid, address_contact_id, context=context) - - comment = self._get_comment_invoice(cr, uid, picking) if group and partner.id in invoices_group: invoice_id = invoices_group[partner.id] invoice = invoice_obj.browse(cr, uid, invoice_id) - invoice_vals = { - '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 - } - invoice_obj.write(cr, uid, [invoice_id], invoice_vals, context=context) + invoice_obj.write(cr, uid, [invoice_id], + self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context), + context=context) else: - invoice_vals = { - 'name': picking.name, - 'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''), - 'type': inv_type, - 'account_id': account_id, - 'partner_id': address.partner_id.id, - 'address_invoice_id': address_invoice_id, - 'address_contact_id': address_contact_id, - 'comment': comment, - 'payment_term': self._get_payment_term(cr, uid, picking), - 'fiscal_position': partner.property_account_position.id, - 'date_invoice': context.get('date_inv',False), - 'company_id': picking.company_id.id, - '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 - invoice_id = invoice_obj.create(cr, uid, invoice_vals, + invoice_id = invoice_obj.create(cr, uid, + self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context), context=context) invoices_group[partner.id] = invoice_id res[picking.id] = invoice_id From 638ca9122e950b4b91f80748f4edf888dbbc07bd Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 5 Jan 2012 17:08:45 +0100 Subject: [PATCH 02/12] Modularize the selection of the partner to invoice when invoicing from the picking. With this modification, all the parameters of the invoice are correct even when the goods are delivered to a third party company i.e. when the partner who receives the goods (picking.address_id.partner_id) is not to partner who ordered the goods (picking.sale_id.partner_id). bzr revid: alexis@via.ecp.fr-20120105160845-c6wk08k7v7i64zyl --- addons/sale/stock.py | 7 ++++++- addons/stock/stock.py | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/addons/sale/stock.py b/addons/sale/stock.py index 653b147d3d0..618867821fb 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -48,6 +48,11 @@ class stock_picking(osv.osv): else: return super(stock_picking, self).get_currency_id(cursor, user, picking) + def _get_partner_to_invoice(self, cr, uid, picking, context=None): + if picking.sale_id: + return picking.sale_id.partner_id + return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context) + 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 @@ -195,4 +200,4 @@ class stock_picking(osv.osv): }) return result -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/stock/stock.py b/addons/stock/stock.py index ce344804347..c1a334a4b6d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -874,6 +874,13 @@ class stock_picking(osv.osv): def get_currency_id(self, cr, uid, picking): return False + 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 + @return: partner object + """ + return picking.address_id and picking.address_id.partner_id + def _get_payment_term(self, cr, uid, picking): """ Gets payment term from partner. @return: Payment term @@ -1030,7 +1037,7 @@ class stock_picking(osv.osv): for picking in self.browse(cr, uid, ids, context=context): if picking.invoice_state != '2binvoiced': continue - partner = picking.address_id and picking.address_id.partner_id + partner = self._get_partner_to_invoice(cr, uid, picking, context=context) if not partner: raise osv.except_osv(_('Error, no partner !'), _('Please put a partner on the picking list if you want to generate invoice.')) From 752186173fed4c2a3b71f4e504a0e8336cfb12a9 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 5 Jan 2012 17:23:43 +0100 Subject: [PATCH 03/12] If the fiscal position has been changed on the sale order, the invoice should use the fiscal posion of the sale order and not the fiscal position of the partner. bzr revid: alexis@via.ecp.fr-20120105162343-tf8359d8hrk7r2j1 --- addons/sale/stock.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/sale/stock.py b/addons/sale/stock.py index 618867821fb..30b9367cf4c 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -71,6 +71,12 @@ class stock_picking(osv.osv): 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): + invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context) + if picking.sale_id and picking.sale_id.fiscal_position: + invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.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 From 1f9401bf416cf7c3b7dc553bdff6e33ca986b9b5 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 5 Jan 2012 22:42:30 +0100 Subject: [PATCH 04/12] Add _get_partner_to_invoice() to purchase module. Inheritance of _prepare_invoice() now replaces the functions _get_payment_term() and _get_address_invoice(). I have checked that these two functions are not used elsewere in the addons and extra-addons. bzr revid: alexis@via.ecp.fr-20120105214230-3wetu0v1foyeate3 --- addons/purchase/stock.py | 27 ++++++++++++++++----------- addons/sale/stock.py | 27 ++++++++++++--------------- addons/stock/stock.py | 22 ++++------------------ 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index c212a171b21..ca84d88babd 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -58,18 +58,23 @@ 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 - """ - res = super(stock_picking, self)._get_address_invoice(cr, uid, picking) + def _get_partner_to_invoice(self, cr, uid, picking, context=None): 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 + """ + 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() + if picking.purchase_id.fiscal_position: + 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: diff --git a/addons/sale/stock.py b/addons/sale/stock.py index 30b9367cf4c..6daff3a83b0 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -53,28 +53,25 @@ class stock_picking(osv.osv): return picking.sale_id.partner_id return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context) - 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 = {} - 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) - 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 + """ invoice_vals = super(stock_picking, self)._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context) - if picking.sale_id and picking.sale_id.fiscal_position: - invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id + 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 + if picking.sale_id.fiscal_position: + invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id + if picking.sale_id.payment_term: + invoice_vals['payment_term'] = picking.sale_id.payment_term.id + if picking.sale_id.user_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): diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c1a334a4b6d..a1b06adedc6 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -881,22 +881,6 @@ class stock_picking(osv.osv): """ return picking.address_id and picking.address_id.partner_id - def _get_payment_term(self, cr, uid, picking): - """ Gets payment term from partner. - @return: Payment term - """ - 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']) - def _get_comment_invoice(self, cr, uid, picking): """ @return: comment string for invoice @@ -994,7 +978,8 @@ class stock_picking(osv.osv): else: account_id = partner.property_account_payable.id address_contact_id, address_invoice_id = \ - self._get_address_invoice(cr, uid, picking).values() + 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, @@ -1005,7 +990,8 @@ class stock_picking(osv.osv): 'address_invoice_id': address_invoice_id, 'address_contact_id': address_contact_id, 'comment': comment, - 'payment_term': self._get_payment_term(cr, uid, picking), + '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, From da9d5063499fecbee58681ce55010ecab9afc262 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 16 Jan 2012 15:16:43 +0100 Subject: [PATCH 05/12] Override values even when they are empty (a user may empty a value on a sale order or purchase order and would like the empty field to be "copied" on the invoice). bzr revid: alexis@via.ecp.fr-20120116141643-boglddgratoro59p --- addons/purchase/stock.py | 3 +-- addons/sale/stock.py | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index ca84d88babd..acb4ae13c05 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -72,8 +72,7 @@ class stock_picking(osv.osv): invoice_vals['address_contact_id'], invoice_vals['address_invoice_id'] = \ self.pool.get('res.partner').address_get(cr, uid, [partner.id], ['contact', 'invoice']).values() - if picking.purchase_id.fiscal_position: - invoice_vals['fiscal_position'] = picking.purchase_id.fiscal_position.id + invoice_vals['fiscal_position'] = picking.purchase_id.fiscal_position.id return invoice_vals def get_currency_id(self, cursor, user, picking): diff --git a/addons/sale/stock.py b/addons/sale/stock.py index 6daff3a83b0..f42f09eb20c 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -66,12 +66,9 @@ class stock_picking(osv.osv): 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 - if picking.sale_id.fiscal_position: - invoice_vals['fiscal_position'] = picking.sale_id.fiscal_position.id - if picking.sale_id.payment_term: - invoice_vals['payment_term'] = picking.sale_id.payment_term.id - if picking.sale_id.user_id: - invoice_vals['user_id'] = picking.sale_id.user_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): From 13e80f5e0e7ad934d386c06ce3ead24d5298a5fe Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Mon, 16 Jan 2012 15:18:24 +0100 Subject: [PATCH 06/12] Make the creation of the invoice lines extensible too. This change also fixes a bug when the fiscal position is changed on the sale order/purchase order : now, the account on the invoice line uses the fiscal position of the sale order/purchase order and not the fiscal position of the partner. bzr revid: alexis@via.ecp.fr-20120116141824-iw7y1wdnueahahuz --- addons/stock/stock.py | 109 ++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a1b06adedc6..e16b982aee9 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution @@ -1004,6 +1005,53 @@ class stock_picking(osv.osv): 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""" + 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']))], + '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. @@ -1034,64 +1082,21 @@ class stock_picking(osv.osv): if group and partner.id in invoices_group: invoice_id = invoices_group[partner.id] invoice = invoice_obj.browse(cr, uid, invoice_id) - invoice_obj.write(cr, uid, [invoice_id], - self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context), - context=context) + invoice_vals_group = self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context) + invoice_obj.write(cr, uid, [invoice_id], invoice_vals_group, context=context) else: - invoice_id = invoice_obj.create(cr, uid, - self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context), - context=context) + invoice_vals = self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context) + invoice_id = invoice_obj.create(cr, uid, invoice_vals, context=context) invoices_group[partner.id] = invoice_id res[picking.id] = invoice_id for move_line in picking.move_lines: if move_line.state == 'cancel': continue - origin = move_line.picking_id.name or '' - if move_line.picking_id.origin: - origin += ':' + move_line.picking_id.origin - if group: - name = (picking.name or '') + '-' + move_line.name - else: - name = move_line.name - - if inv_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 - - price_unit = self._get_price_unit_invoice(cr, uid, - move_line, inv_type) - discount = self._get_discount_invoice(cr, uid, move_line) - tax_ids = self._get_taxes_invoice(cr, uid, move_line, inv_type) - account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line) - - #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 inv_type in ('out_invoice', 'out_refund'): - uos_id = move_line.product_uom.id - - account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id) - invoice_line_id = invoice_line_obj.create(cr, uid, { - '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': price_unit, - 'discount': discount, - 'quantity': move_line.product_uos_qty or move_line.product_qty, - 'invoice_line_tax_id': [(6, 0, tax_ids)], - 'account_analytic_id': account_analytic_id, - }, context=context) + print "invoice_vals=", invoice_vals + invoice_line_id = invoice_line_obj.create(cr, uid, + self._prepare_invoice_line(cr, uid, group, picking, move_line, + invoice_id, invoice_vals, context=context), + context=context) self._invoice_line_hook(cr, uid, move_line, invoice_line_id) invoice_obj.button_compute(cr, uid, [invoice_id], context=context, From 7b6622a2ebff7e1ace500e88d8a63592f2c0ea95 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Thu, 19 Jan 2012 22:40:53 +0100 Subject: [PATCH 07/12] Document the new methods I introduced with the standard @param and @return syntax Remove a print bzr revid: alexis@via.ecp.fr-20120119214053-8tf189abeszi208b --- addons/purchase/stock.py | 3 +++ addons/sale/stock.py | 3 +++ addons/stock/stock.py | 28 +++++++++++++++++++++++----- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index acb4ae13c05..e9346d230d9 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -59,6 +59,9 @@ class stock_picking(osv.osv): } 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 + """ if picking.purchase_id: return picking.purchase_id.partner_id return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context) diff --git a/addons/sale/stock.py b/addons/sale/stock.py index f42f09eb20c..e78f3c77e33 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -49,6 +49,9 @@ class stock_picking(osv.osv): return super(stock_picking, self).get_currency_id(cursor, user, picking) 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 + """ if picking.sale_id: return picking.sale_id.partner_id return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index e16b982aee9..5942e148df5 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -878,7 +878,8 @@ class stock_picking(osv.osv): 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 - @return: partner object + @param picking: object of the picking for which we are selecting the partner to invoice + @return: object of the partner to invoice """ return picking.address_id and picking.address_id.partner_id @@ -961,7 +962,12 @@ class stock_picking(osv.osv): return inv_type def _prepare_invoice_group(self, cr, uid, picking, partner, invoice, context=None): - """Builds the dict for grouped invoices""" + """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 { @@ -973,7 +979,13 @@ class stock_picking(osv.osv): } def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None): - """Builds the dict containing the values for the invoice""" + """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: @@ -1007,7 +1019,14 @@ class stock_picking(osv.osv): 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""" + """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: @@ -1092,7 +1111,6 @@ class stock_picking(osv.osv): for move_line in picking.move_lines: if move_line.state == 'cancel': continue - print "invoice_vals=", invoice_vals invoice_line_id = invoice_line_obj.create(cr, uid, self._prepare_invoice_line(cr, uid, group, picking, move_line, invoice_id, invoice_vals, context=context), From 25cb9845a3b75dd86d4f7840119d47743a5c36c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Fri, 27 Jan 2012 02:50:32 -0200 Subject: [PATCH 08/12] [IMP] stock: in some extension, we may want to group some invoice lines, by checking vals, we let such a chance change the cardinality without having first to create the lines and then delete them to group them, which is very inefficient bzr revid: rvalyi@gmail.com-20120127045032-isxspwmhmk78yrbt --- addons/stock/stock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 00af35bae2f..f730b8483ca 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1097,11 +1097,11 @@ class stock_picking(osv.osv): for move_line in picking.move_lines: if move_line.state == 'cancel': continue - invoice_line_id = invoice_line_obj.create(cr, uid, - self._prepare_invoice_line(cr, uid, group, picking, move_line, + vals = self._prepare_invoice_line(cr, uid, group, picking, move_line, invoice_id, invoice_vals, context=context), - context=context) - self._invoice_line_hook(cr, uid, move_line, invoice_line_id) + if vals: + invoice_line_id = invoice_line_obj.create(cr, uid, vals, context=context) + self._invoice_line_hook(cr, uid, move_line, invoice_line_id) invoice_obj.button_compute(cr, uid, [invoice_id], context=context, set_total=(inv_type in ('in_invoice', 'in_refund'))) From bff885382ccfb50cdb9e3e6fc109fd4a1d7eff9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Fri, 27 Jan 2012 02:54:58 -0200 Subject: [PATCH 09/12] [REF] sale: extracted invoice line creation from sale order line in a prepare dict method. The code is almost copied/pasted. The only things I changed is: - I renamed a var to account_id for more clarity - I let the user force some account_id as we will reuse the method from service invoicing from picking - By checking vals, I let a chance to an overrider to group lines together (like my previous commit) bzr revid: rvalyi@gmail.com-20120127045458-hrq9emlx8kro0kri --- addons/sale/sale.py | 91 ++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 4f430c0a957..94fa8f71f36 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -981,10 +981,12 @@ 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""" + def _get_line_qty(line): if (line.order_id.invoice_quantity=='order') or not line.procurement_id: if line.product_uos: @@ -1003,48 +1005,59 @@ class sale_order_line(osv.osv): 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)') % \ + (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 + + def invoice_line_create(self, cr, uid, ids, context=None): + if context is None: + context = {} + + create_ids = [] + sales = {} + 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 From 12185b8c88135e630381caf53b405465e18b3d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Fri, 27 Jan 2012 03:00:05 -0200 Subject: [PATCH 10/12] [REF] sale: refactored invoice line creation from picking including service. So we now use the same method as in invoicing upon order (the met$ This is better to have the code factored as checks upon accounts are factored in a single stronger code. Also this fixes bug https://bugs.launchpad.net/openobject-addons/+bug/922427 I discovered while refactoring. Notice that we still look at the picking type and eventually force the account_id (and yes that service line is a product). lp bug: https://launchpad.net/bugs/922427 fixed bzr revid: rvalyi@gmail.com-20120127050005-z1s4aby1qqcrpgbi --- addons/sale/stock.py | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/addons/sale/stock.py b/addons/sale/stock.py index e78f3c77e33..48359bc26ec 100644 --- a/addons/sale/stock.py +++ b/addons/sale/stock.py @@ -175,32 +175,15 @@ class stock_picking(osv.osv): 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])]}) return result # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From c0d40869f898d7611534e0e6da3425ed4a2eb029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Fri, 27 Jan 2012 03:37:26 -0200 Subject: [PATCH 11/12] [FIX] stock: fixed typo comma in previous commit bzr revid: rvalyi@gmail.com-20120127053726-zb70tf125roz9f2u --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index f730b8483ca..5f7b7ea781d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1098,7 +1098,7 @@ class stock_picking(osv.osv): if move_line.state == 'cancel': continue vals = self._prepare_invoice_line(cr, uid, group, picking, move_line, - invoice_id, invoice_vals, context=context), + invoice_id, invoice_vals, context=context) if vals: invoice_line_id = invoice_line_obj.create(cr, uid, vals, context=context) self._invoice_line_hook(cr, uid, move_line, invoice_line_id) From b21c41276485ae7689c29821a6c069cbabb784cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Fri, 27 Jan 2012 03:38:07 -0200 Subject: [PATCH 12/12] [FIX] purchase: completed invoice line hook to set purchase order line as invoice and relate it properly to the invoice lines lp bug: https://launchpad.net/bugs/922442 fixed bzr revid: rvalyi@gmail.com-20120127053807-4r31khr8jt7anpv4 --- addons/purchase/stock.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index e9346d230d9..028d20eea40 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -118,6 +118,12 @@ class stock_picking(osv.osv): 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)], + }) 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)