From 9b1ab76eb29eb6297425a24a4a8d8beb86e5372f Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 31 Jul 2015 16:18:03 +0200 Subject: [PATCH] [IMP] purchase: invoice state of PO line calculated from invoice state of move Before this fix, a PO line was set as invoiced if all associated invoices were validated. This is an issue if the invoice_method of a PO is set on 'picking', and that only a partial quantity is received and invoiced. Indeed, in this case it was never possible to meet the condition, and the PO could not be set to 'done'. After this fix, the PO line invoice state is calculated from the invoice state of the related moves, if the invoice_method of a PO is set on 'picking'. This is more logical, and moreover this makes possible to set a PO line as invoiced as soon as all the related moves are done or cancelled. opw-644399 --- addons/purchase/purchase.py | 43 +++++++++++++++++++++++-------------- addons/purchase/stock.py | 10 ++++++++- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 139dcaf92a8..8060e55d74b 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -990,6 +990,30 @@ class purchase_order(osv.osv): return orders_info + def _set_po_lines_invoiced(self, cr, uid, ids, context=None): + for po in self.browse(cr, uid, ids, context=context): + is_invoiced = [] + if po.invoice_method == 'picking': + # We determine the invoiced state of the PO line based on the invoiced state + # of the associated moves. This should cover all possible cases: + # - all moves are done and invoiced + # - a PO line is split into multiple moves (e.g. if multiple pickings): some + # pickings are done, some are in progress, some are cancelled + for po_line in po.order_line: + if (po_line.move_ids and + all(move.state in ('done', 'cancel') for move in po_line.move_ids) and + not all(move.state == 'cancel' for move in po_line.move_ids) and + all(move.invoice_state == 'invoiced' for move in po_line.move_ids if move.state == 'done')): + is_invoiced.append(po_line.id) + else: + for po_line in po.order_line: + if (po_line.invoice_lines and + all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines)): + is_invoiced.append(po_line.id) + if is_invoiced: + self.pool['purchase.order.line'].write(cr, uid, is_invoiced, {'invoiced': True}) + workflow.trg_write(uid, 'purchase.order', po.id, cr) + class purchase_order_line(osv.osv): def _amount_line(self, cr, uid, ids, prop, arg, context=None): @@ -1583,22 +1607,9 @@ class account_invoice(osv.Model): else: user_id = uid po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context) - for order in purchase_order_obj.browse(cr, user_id, po_ids, context=context): - purchase_order_obj.message_post(cr, user_id, order.id, body=_("Invoice received"), context=context) - invoiced = [] - shipped = True - # for invoice method manual or order, don't care about shipping state - # for invoices based on incoming shippment, beware of partial deliveries - if (order.invoice_method == 'picking' and - not all(picking.invoice_state in ['invoiced'] for picking in order.picking_ids)): - shipped = False - for po_line in order.order_line: - if (po_line.invoice_lines and - all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines)): - invoiced.append(po_line.id) - if invoiced and shipped: - self.pool['purchase.order.line'].write(cr, user_id, invoiced, {'invoiced': True}) - workflow.trg_write(user_id, 'purchase.order', order.id, cr) + for po_id in po_ids: + purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice received"), context=context) + purchase_order_obj._set_po_lines_invoiced(cr, user_id, [po_id], context=context) return res def confirm_paid(self, cr, uid, ids, context=None): diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 4c2998474f4..bfd1d69c01d 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -44,15 +44,23 @@ class stock_move(osv.osv): res = super(stock_move, self).write(cr, uid, ids, vals, context=context) from openerp import workflow if vals.get('state') in ['done', 'cancel']: + po_to_check = [] for move in self.browse(cr, uid, ids, context=context): if move.purchase_line_id and move.purchase_line_id.order_id: - order_id = move.purchase_line_id.order_id.id + order = move.purchase_line_id.order_id + order_id = order.id # update linked purchase order as superuser as the warehouse # user may not have rights to access purchase.order if self.pool.get('purchase.order').test_moves_done(cr, uid, [order_id], context=context): workflow.trg_validate(SUPERUSER_ID, 'purchase.order', order_id, 'picking_done', cr) if self.pool.get('purchase.order').test_moves_except(cr, uid, [order_id], context=context): workflow.trg_validate(SUPERUSER_ID, 'purchase.order', order_id, 'picking_cancel', cr) + if order_id not in po_to_check and vals['state'] == 'cancel' and order.invoice_method == 'picking': + po_to_check.append(order_id) + # Some moves which are cancelled might be part of a PO line which is partially + # invoiced, so we check if some PO line can be set on "invoiced = True". + if po_to_check: + self.pool.get('purchase.order')._set_po_lines_invoiced(cr, uid, po_to_check, context=context) return res def copy(self, cr, uid, id, default=None, context=None):