[IMP] sale_stock: decrease delivered quantity
The commit adds a "Refund" field on the stock move, in the module sale_stock. This allows to decrease the quantity delivered in the associated SO, and therefore to generate refunds more easily.
This commit is contained in:
@@ -166,10 +166,10 @@ class SaleOrderLine(models.Model):
|
||||
super(SaleOrderLine, self)._get_delivered_qty()
|
||||
qty = 0.0
|
||||
for move in self.procurement_ids.mapped('move_ids').filtered(lambda r: r.state == 'done' and not r.scrapped):
|
||||
#Note that we don't decrease quantity for customer returns on purpose: these are exeptions that must be treated manually. Indeed,
|
||||
#modifying automatically the delivered quantity may trigger an automatic reinvoicing (refund) of the SO, which is definitively not wanted
|
||||
if move.location_dest_id.usage == "customer":
|
||||
qty += self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
|
||||
elif move.location_dest_id.usage == "internal" and move.to_refund_so:
|
||||
qty -= self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
|
||||
return qty
|
||||
|
||||
@api.multi
|
||||
@@ -249,6 +249,9 @@ class ProcurementOrder(models.Model):
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
to_refund_so = fields.Boolean(string="To Refund in SO", default=False,
|
||||
help='Trigger a decrease of the delivered quantity in the associated Sale Order')
|
||||
|
||||
@api.multi
|
||||
def action_done(self):
|
||||
result = super(StockMove, self).action_done()
|
||||
@@ -279,6 +282,27 @@ class StockPicking(models.Model):
|
||||
sale_id = fields.Many2one(comodel_name='sale.order', string="Sale Order", compute='_compute_sale_id')
|
||||
|
||||
|
||||
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 in SO", help='Trigger a decrease of the delivered quantity in the associated Sale Order')
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = "account.invoice.line"
|
||||
|
||||
|
||||
@@ -50,6 +50,17 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_return_picking_form_inherit_sale_stock" model="ir.ui.view">
|
||||
<field name="name">stock.return.picking.sale.stock.form</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_return_picking_form"/>
|
||||
<field name="model">stock.return.picking</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="quantity" position="after">
|
||||
<field name="to_refund_so"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<template id="report_saleorder_document_inherit_sale_stock" inherit_id="sale.report_saleorder_document">
|
||||
<xpath expr="//div[@name='payment_term']" position="after">
|
||||
<div class="col-xs-3" t-if="doc.incoterm" groups="sale.group_display_incoterm">
|
||||
|
||||
@@ -132,7 +132,7 @@ class TestSaleStock(TestSale):
|
||||
self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
|
||||
|
||||
# invoice in on delivery, nothing should be invoiced
|
||||
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "nothing to invoice"')
|
||||
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s".' % self.so.invoice_status)
|
||||
|
||||
# deliver completely
|
||||
pick = self.so.picking_ids
|
||||
@@ -142,27 +142,39 @@ class TestSaleStock(TestSale):
|
||||
|
||||
# Check quantity delivered
|
||||
del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
|
||||
self.assertEqual(del_qty, 5.0, 'Sale Stock: delivered quantity should be 5.0 after complete delivery')
|
||||
self.assertEqual(del_qty, 5.0, 'Sale Stock: delivered quantity should be 5.0 instead of %s after complete delivery' % del_qty)
|
||||
|
||||
# Check invoice
|
||||
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" before invoicing')
|
||||
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" before invoicing' % self.so.invoice_status)
|
||||
inv_1_id = self.so.action_invoice_create()
|
||||
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" after invoicing')
|
||||
self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice should be created')
|
||||
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" instead of "%s" after invoicing' % self.so.invoice_status)
|
||||
self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice instead of "%s" should be created' % len(inv_1_id))
|
||||
self.inv_1 = self.env['account.invoice'].browse(inv_1_id)
|
||||
self.assertEqual(self.inv_1.amount_untaxed, self.inv_1.amount_untaxed, 'Sale Stock: amount in SO and invoice should be the same')
|
||||
self.inv_1.signal_workflow('invoice_open')
|
||||
|
||||
# Create return picking
|
||||
StockReturnPicking = self.env['stock.return.picking']
|
||||
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
|
||||
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': 5})
|
||||
return_pick.pack_operation_product_ids.write({'qty_done': 2})
|
||||
return_pick.do_new_transfer()
|
||||
|
||||
# Check invoice
|
||||
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" after picking return')
|
||||
self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" after picking return' % self.so.invoice_status)
|
||||
self.assertEqual(self.so.order_line[0].qty_delivered, 3.0, 'Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered)
|
||||
# let's do an invoice with refunds
|
||||
adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
|
||||
'advance_payment_method': 'all',
|
||||
})
|
||||
adv_wiz.with_context(open_invoices=True).create_invoices()
|
||||
self.inv_2 = self.so.invoice_ids.filtered(lambda r: r.state == 'draft')
|
||||
self.assertEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, 'Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity)
|
||||
self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s" after invoicing the return' % self.so.invoice_status)
|
||||
|
||||
Reference in New Issue
Block a user