diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 4d1ad503c31..c93d04b8abf 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -542,10 +542,11 @@ class PurchaseOrderLine(models.Model):
total = 0.0
for move in line.move_ids:
if move.state == 'done':
- if move.product_uom != line.product_uom:
- total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
+ if move.location_dest_id.usage == "supplier":
+ if move.to_refund:
+ total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
else:
- total += move.product_uom_qty
+ total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom)
line.qty_received = total
@api.model
@@ -639,7 +640,7 @@ class PurchaseOrderLine(models.Model):
return res
qty = 0.0
price_unit = self._get_stock_move_price_unit()
- for move in self.move_ids.filtered(lambda x: x.state != 'cancel'):
+ for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"):
qty += move.product_qty
template = {
'name': self.name or '',
diff --git a/addons/purchase/models/stock.py b/addons/purchase/models/stock.py
index e3e66104591..3bfc0877f74 100644
--- a/addons/purchase/models/stock.py
+++ b/addons/purchase/models/stock.py
@@ -60,8 +60,10 @@ class StockMove(models.Model):
def copy(self, default=None):
self.ensure_one()
default = default or {}
- if not default.get('split_from'):
- #we don't want to propagate the link to the purchase order line except in case of move split
+ # we don't want to propagate the link to the purchase order line on the move copied,
+ # except when it's a split or a returned move
+
+ if not default.get('split_from') and not default.get('origin_returned_move_id'):
default['purchase_line_id'] = False
return super(StockMove, self).copy(default)
diff --git a/addons/purchase/tests/test_purchase_order.py b/addons/purchase/tests/test_purchase_order.py
index a7b83a0e237..42c67a62b63 100644
--- a/addons/purchase/tests/test_purchase_order.py
+++ b/addons/purchase/tests/test_purchase_order.py
@@ -16,23 +16,12 @@ class TestPurchaseOrder(AccountingTestCase):
self.PurchaseOrderLine = self.env['purchase.order.line']
self.AccountInvoice = self.env['account.invoice']
self.AccountInvoiceLine = self.env['account.invoice.line']
-
- def test_00_purchase_order_flow(self):
self.partner_id = self.env.ref('base.res_partner_1')
self.product_id_1 = self.env.ref('product.product_product_8')
self.product_id_2 = self.env.ref('product.product_product_11')
(self.product_id_1 | self.product_id_2).write({'purchase_method': 'purchase'})
- # Ensure product_id_2 doesn't have res_partner_1 as supplier
- if self.partner_id in self.product_id_2.seller_ids.mapped('name'):
- id_to_remove = self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id).ids[0] if self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id) else False
- if id_to_remove:
- self.product_id_2.write({
- 'seller_ids': [(2, id_to_remove, False)],
- })
- self.assertFalse(self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id), 'Purchase: the partner should not be in the list of the product suppliers')
-
- po_vals = {
+ self.po_vals = {
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
@@ -52,7 +41,20 @@ class TestPurchaseOrder(AccountingTestCase):
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
})],
}
- self.po = self.PurchaseOrder.create(po_vals)
+
+
+ def test_00_purchase_order_flow(self):
+
+ # Ensure product_id_2 doesn't have res_partner_1 as supplier
+ if self.partner_id in self.product_id_2.seller_ids.mapped('name'):
+ id_to_remove = self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id).ids[0] if self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id) else False
+ if id_to_remove:
+ self.product_id_2.write({
+ 'seller_ids': [(2, id_to_remove, False)],
+ })
+ self.assertFalse(self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id), 'Purchase: the partner should not be in the list of the product suppliers')
+
+ self.po = self.PurchaseOrder.create(self.po_vals)
self.assertTrue(self.po, 'Purchase: no purchase order created')
self.assertEqual(self.po.invoice_status, 'no', 'Purchase: PO invoice_status should be "Not purchased"')
self.assertEqual(self.po.order_line.mapped('qty_received'), [0.0, 0.0], 'Purchase: no product should be received"')
@@ -85,3 +87,72 @@ class TestPurchaseOrder(AccountingTestCase):
})
self.invoice.purchase_order_change()
self.assertEqual(self.po.order_line.mapped('qty_invoiced'), [5.0, 5.0], 'Purchase: all products should be invoiced"')
+
+ def test_02_po_return(self):
+ """
+ Test a PO with a product on Incoming shipment. Validate the PO, then do a return
+ of the picking with Refund.
+ """
+ # Draft purchase order created
+ self.po = self.env['purchase.order'].create(self.po_vals)
+ self.assertTrue(self.po, 'Purchase: no purchase order created')
+ self.assertEqual(self.po.order_line.mapped('qty_received'), [0.0, 0.0], 'Purchase: no product should be received"')
+ self.assertEqual(self.po.order_line.mapped('qty_invoiced'), [0.0, 0.0], 'Purchase: no product should be invoiced"')
+
+ self.po.button_confirm()
+ self.assertEqual(self.po.state, 'purchase', 'Purchase: PO state should be "Purchase"')
+ self.assertEqual(self.po.invoice_status, 'to invoice', 'Purchase: PO invoice_status should be "Waiting Invoices"')
+
+ # Confirm the purchase order
+ self.po.button_confirm()
+ self.assertEqual(self.po.state, 'purchase', 'Purchase: PO state should be "Purchase')
+ self.assertEqual(self.po.picking_count, 1, 'Purchase: one picking should be created"')
+ self.picking = self.po.picking_ids[0]
+ self.picking.force_assign()
+ self.picking.pack_operation_product_ids.write({'qty_done': 5.0})
+ self.picking.do_new_transfer()
+ self.assertEqual(self.po.order_line.mapped('qty_received'), [5.0, 5.0], 'Purchase: all products should be received"')
+
+ #After Receiving all products create vendor bill.
+ self.invoice = self.AccountInvoice.create({
+ 'partner_id': self.partner_id.id,
+ 'purchase_id': self.po.id,
+ 'account_id': self.partner_id.property_account_payable_id.id,
+ 'type': 'in_invoice',
+ })
+ self.invoice.purchase_order_change()
+ self.invoice.invoice_validate()
+ self.assertEqual(self.po.order_line.mapped('qty_invoiced'), [5.0, 5.0], 'Purchase: all products should be invoiced"')
+
+ # Check quantity received
+ received_qty = sum(pol.qty_received for pol in self.po.order_line)
+ self.assertEqual(received_qty, 10.0, 'Purchase: Received quantity should be 10.0 instead of %s after validating incomming shipment' % received_qty)
+
+ # Create return picking
+ StockReturnPicking = self.env['stock.return.picking']
+ pick = self.po.picking_ids
+ default_data = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).default_get(['move_dest_exists', 'original_location_id', 'product_return_moves', 'parent_location_id', 'location_id'])
+ return_wiz = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).create(default_data)
+ return_wiz.product_return_moves.write({'quantity': 2.0, 'to_refund': True}) # Return only 2
+ res = return_wiz.create_returns()
+ return_pick = self.env['stock.picking'].browse(res['res_id'])
+
+ # Validate picking
+ return_pick.force_assign()
+ return_pick.pack_operation_product_ids.write({'qty_done': 2})
+ return_pick.do_new_transfer()
+
+ # Check Received quantity
+ self.assertEqual(self.po.order_line[0].qty_received, 3.0, 'Purchase: delivered quantity should be 3.0 instead of "%s" after picking return' % self.po.order_line[0].qty_received)
+ #Create vendor bill for refund qty
+ self.invoice = self.AccountInvoice.create({
+ 'partner_id': self.partner_id.id,
+ 'purchase_id': self.po.id,
+ 'account_id': self.partner_id.property_account_payable_id.id,
+ 'type': 'in_refund',
+ })
+ self.invoice.purchase_order_change()
+ self.invoice.invoice_line_ids[0].quantity = 2.0
+ self.invoice.invoice_line_ids[1].quantity = 2.0
+ self.invoice.invoice_validate()
+ self.assertEqual(self.po.order_line.mapped('qty_invoiced'), [3.0, 3.0], 'Purchase: Billed quantity should be 3.0')
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 313d7e1afaa..9fcf8471fe7 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -192,7 +192,7 @@ class SaleOrderLine(models.Model):
if move.location_dest_id.usage == "customer":
if not move.origin_returned_move_id:
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom)
- elif move.location_dest_id.usage == "internal" and move.to_refund_so:
+ elif move.location_dest_id.usage == "internal" and move.to_refund:
qty -= move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom)
return qty
diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py
index 41d1c3b0d13..2b571f5df0a 100644
--- a/addons/sale_stock/models/stock.py
+++ b/addons/sale_stock/models/stock.py
@@ -13,10 +13,6 @@ class StockLocationRoute(models.Model):
class StockMove(models.Model):
_inherit = "stock.move"
- to_refund_so = fields.Boolean(
- "To Refund in SO", default=False,
- help='Trigger a decrease of the delivered quantity in the associated Sales Order')
-
@api.multi
def action_done(self):
result = super(StockMove, self).action_done()
@@ -66,24 +62,3 @@ class StockPicking(models.Model):
values={'self': backorder, 'origin': order},
subtype_id=self.env.ref('mail.mt_note').id)
return res
-
-
-class StockReturnPicking(models.TransientModel):
- _inherit = "stock.return.picking"
-
- @api.multi
- def _create_returns(self):
- new_picking_id, pick_type_id = super(StockReturnPicking, self)._create_returns()
- new_picking = self.env['stock.picking'].browse([new_picking_id])
- for move in new_picking.move_lines:
- return_picking_line = self.product_return_moves.filtered(lambda r: r.move_id == move.origin_returned_move_id)
- if return_picking_line and return_picking_line.to_refund_so:
- move.to_refund_so = True
-
- return new_picking_id, pick_type_id
-
-
-class StockReturnPickingLine(models.TransientModel):
- _inherit = "stock.return.picking.line"
-
- to_refund_so = fields.Boolean(string="To Refund", help='Trigger a decrease of the delivered quantity in the associated Sales Order')
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
index ab39a0d2129..6b71bb07975 100644
--- a/addons/sale_stock/tests/test_sale_stock.py
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -161,7 +161,7 @@ class TestSaleStock(TestSale):
default_data = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).default_get(['move_dest_exists', 'original_location_id', 'product_return_moves', 'parent_location_id', 'location_id'])
return_wiz = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).create(default_data)
return_wiz.product_return_moves.quantity = 2.0 # Return only 2
- return_wiz.product_return_moves.to_refund_so = True # Refund these 2
+ return_wiz.product_return_moves.to_refund = True # Refund these 2
res = return_wiz.create_returns()
return_pick = self.env['stock.picking'].browse(res['res_id'])
diff --git a/addons/sale_stock/views/stock_views.xml b/addons/sale_stock/views/stock_views.xml
index 2d6828b88d4..4dd032e1519 100644
--- a/addons/sale_stock/views/stock_views.xml
+++ b/addons/sale_stock/views/stock_views.xml
@@ -23,16 +23,5 @@
-
- stock.return.picking.sale.stock.form
-
- stock.return.picking
-
-
-
-
-
-
-
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index 9fec58c20ab..822057e5d69 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -176,6 +176,9 @@ class StockQuant(models.Model):
class StockMove(models.Model):
_inherit = "stock.move"
+ to_refund = fields.Boolean(string="To Refund",
+ help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order')
+
@api.multi
def action_done(self):
self.product_price_update_before_done()
@@ -346,3 +349,24 @@ class StockMove(models.Model):
}
res.append((0, 0, price_diff_line))
return res
+
+
+class StockReturnPicking(models.TransientModel):
+ _inherit = "stock.return.picking"
+
+ @api.multi
+ def _create_returns(self):
+ new_picking_id, pick_type_id = super(StockReturnPicking, self)._create_returns()
+ new_picking = self.env['stock.picking'].browse([new_picking_id])
+ for move in new_picking.move_lines:
+ return_picking_line = self.product_return_moves.filtered(lambda r: r.move_id == move.origin_returned_move_id)
+ if return_picking_line and return_picking_line.to_refund:
+ move.to_refund = True
+
+ return new_picking_id, pick_type_id
+
+
+class StockReturnPickingLine(models.TransientModel):
+ _inherit = "stock.return.picking.line"
+
+ to_refund = fields.Boolean(string="To Refund", help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order')
diff --git a/addons/stock_account/views/stock_account_views.xml b/addons/stock_account/views/stock_account_views.xml
index bd817ab8bd3..688646ba89d 100644
--- a/addons/stock_account/views/stock_account_views.xml
+++ b/addons/stock_account/views/stock_account_views.xml
@@ -38,5 +38,16 @@
+
+ stock.return.picking.stock.account.form
+
+ stock.return.picking
+
+
+
+
+
+
+