From b30afcbdb98fa6b444035a3bb43707f734bd07b5 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Mon, 7 Mar 2016 11:58:30 +0100 Subject: [PATCH] [IMP] sale_stock: set partially delivered products to fully invoiced --- addons/sale_stock/sale_stock.py | 16 +++++++ addons/sale_stock/tests/test_sale_stock.py | 52 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 0387dbd6783..9c7fe4bea02 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -95,6 +95,22 @@ class SaleOrderLine(models.Model): route_id = fields.Many2one('stock.location.route', string='Route', domain=[('sale_selectable', '=', True)]) product_tmpl_id = fields.Many2one('product.template', related='product_id.product_tmpl_id', string='Product Template', readonly=True) + @api.depends('order_id.state') + def _compute_invoice_status(self): + super(SaleOrderLine, self)._compute_invoice_status() + for line in self: + # We handle the following specific situation: a physical product is partially delivered, + # but we would like to set its invoice status to 'Fully Invoiced'. The use case is for + # products sold by weight, where the delivered quantity rarely matches exactly the + # quantity ordered. + if line.order_id.state == 'done'\ + and line.invoice_status == 'no'\ + and line.product_id.type in ['consu', 'product']\ + and line.product_id.invoice_policy == 'delivery'\ + and line.procurement_ids.mapped('move_ids')\ + and all(move.state in ['done', 'cancel'] for move in line.procurement_ids.mapped('move_ids')): + line.invoice_status = 'invoiced' + @api.multi @api.depends('product_id') def _compute_qty_delivered_updateable(self): diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 95874b67d7b..368e552096d 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -165,3 +165,55 @@ class TestSaleStock(TestSale): # Check invoice self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" after picking return') + + def test_03_sale_stock_delivery_partial(self): + """ + Test a SO with a product invoiced on delivery. Deliver partially and invoice the SO, when + the SO is set on 'done', the SO should be fully invoiced. + """ + # intial so + self.partner = self.env.ref('base.res_partner_1') + self.product = self.env.ref('product.product_product_47') + so_vals = { + 'partner_id': self.partner.id, + 'partner_invoice_id': self.partner.id, + 'partner_shipping_id': self.partner.id, + 'order_line': [(0, 0, { + 'name': self.product.name, + 'product_id': self.product.id, + 'product_uom_qty': 5.0, + 'product_uom': self.product.uom_id.id, + 'price_unit': self.product.list_price})], + 'pricelist_id': self.env.ref('product.list0').id, + } + self.so = self.env['sale.order'].create(so_vals) + + # confirm our standard so, check the picking + self.so.action_confirm() + 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"') + + # deliver partially + pick = self.so.picking_ids + pick.force_assign() + pick.pack_operation_product_ids.write({'qty_done': 4}) + backorder_wiz_id = pick.do_new_transfer()['res_id'] + backorder_wiz = self.env['stock.backorder.confirmation'].browse([backorder_wiz_id]) + backorder_wiz.process_cancel_backorder() + + # Check quantity delivered + del_qty = sum(sol.qty_delivered for sol in self.so.order_line) + self.assertEqual(del_qty, 4.0, 'Sale Stock: delivered quantity should be 4.0 after partial delivery') + + # Check invoice + self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" before invoicing') + inv_1_id = self.so.action_invoice_create() + self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" after invoicing') + self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice should be created') + 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.so.action_done() + self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" when set to done')