summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHetashree Chauhan <hch@odoo.com>2019-01-02 09:46:54 +0000
committerHiral Bhavsar <hbh@odoo.com>2019-11-06 13:19:40 +0000
commit4c36a35bf14f4cdb858273f3986b49e3b326e4b9 (patch)
treedced81aba00b66674ffe778f670ae53c3e5daf9c
parentcfc65bd1b72b6fb7833f67fa580fa9fdac28b1f5 (diff)
[IMP] sale(_stock): add wizard to cancel the confirmed SO
Purpose of the task is be able to cancel an order if the products have been delivered or if an invoice has already been confirmed -the remaining pickings (back orders) should be cancelled. -the draft invoices should be cancelled too. [IMP] product_expiry: compute method should iterate on self task-1919761 Closes #29920 Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com> Co-authored-by: Hiral Bhavsar <hbh@odoo.com>
-rw-r--r--addons/product_expiry/models/stock_move_line.py6
-rw-r--r--addons/sale/__manifest__.py1
-rw-r--r--addons/sale/models/sale.py19
-rw-r--r--addons/sale/wizard/__init__.py1
-rw-r--r--addons/sale/wizard/sale_order_cancel.py20
-rw-r--r--addons/sale/wizard/sale_order_cancel_views.xml20
-rw-r--r--addons/sale_stock/__manifest__.py1
-rw-r--r--addons/sale_stock/models/sale_order.py8
-rw-r--r--addons/sale_stock/tests/test_sale_stock.py53
-rw-r--r--addons/sale_stock/wizard/__init__.py1
-rw-r--r--addons/sale_stock/wizard/sale_order_cancel.py15
-rw-r--r--addons/sale_stock/wizard/sale_order_cancel_views.xml16
12 files changed, 156 insertions, 5 deletions
diff --git a/addons/product_expiry/models/stock_move_line.py b/addons/product_expiry/models/stock_move_line.py
index af78f86f248..569e8c03034 100644
--- a/addons/product_expiry/models/stock_move_line.py
+++ b/addons/product_expiry/models/stock_move_line.py
@@ -16,10 +16,8 @@ class StockMoveLine(models.Model):
@api.depends('picking_id', 'product_id')
def _compute_expiration_date_readonly(self):
- if not self.picking_id.picking_type_id.use_existing_lots and self.product_id.use_expiration_date:
- self.expiration_date_readonly = False
- else:
- self.expiration_date_readonly = True
+ for line in self:
+ line.expiration_date_readonly = bool(not line.picking_id.picking_type_id.use_existing_lots and line.product_id.use_expiration_date)
@api.onchange('product_id', 'product_uom_id')
def onchange_product_id(self):
diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py
index c085ff2e094..5ed9042790a 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -35,6 +35,7 @@ This module contains all the common features of Sales Management and eCommerce.
'views/product_attribute_views.xml',
'views/product_views.xml',
'views/utm_campaign_views.xml',
+ 'wizard/sale_order_cancel_views.xml',
'wizard/sale_payment_link_views.xml',
],
'demo': [
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index c53b718a279..73753fac631 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -645,8 +645,27 @@ class SaleOrder(models.Model):
})
def action_cancel(self):
+ cancel_warning = self._show_cancel_wizard()
+ if cancel_warning:
+ return {
+ 'name': _('Cancel Sales Order'),
+ 'view_mode': 'form',
+ 'res_model': 'sale.order.cancel',
+ 'view_id': self.env.ref('sale.sale_order_cancel_view_form').id,
+ 'type': 'ir.actions.act_window',
+ 'context': {'default_order_id': self.id},
+ 'target': 'new'
+ }
+ inv = self.invoice_ids.filtered(lambda inv: inv.state == 'draft')
+ inv.button_cancel()
return self.write({'state': 'cancel'})
+ def _show_cancel_wizard(self):
+ for order in self:
+ if order.invoice_ids.filtered(lambda inv: inv.state == 'draft') and not order._context.get('disable_cancel_warning'):
+ return True
+ return False
+
def _find_mail_template(self, force_confirmation_template=False):
template_id = False
diff --git a/addons/sale/wizard/__init__.py b/addons/sale/wizard/__init__.py
index f2750c30fa5..b0b5d57194d 100644
--- a/addons/sale/wizard/__init__.py
+++ b/addons/sale/wizard/__init__.py
@@ -3,4 +3,5 @@
from . import payment_acquirer_onboarding_wizard
from . import sale_make_invoice_advance
+from . import sale_order_cancel
from . import sale_payment_link
diff --git a/addons/sale/wizard/sale_order_cancel.py b/addons/sale/wizard/sale_order_cancel.py
new file mode 100644
index 00000000000..b2ac441aaa5
--- /dev/null
+++ b/addons/sale/wizard/sale_order_cancel.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class SaleOrderCancel(models.TransientModel):
+ _name = 'sale.order.cancel'
+ _description = "Sales Order Cancel"
+
+ order_id = fields.Many2one('sale.order', string='Sale Order', required=True, ondelete='cascade')
+ display_invoice_alert = fields.Boolean('Invoice Alert', compute='_compute_display_invoice_alert')
+
+ @api.depends('order_id')
+ def _compute_display_invoice_alert(self):
+ for wizard in self:
+ wizard.display_invoice_alert = bool(wizard.order_id.invoice_ids.filtered(lambda inv: inv.state == 'draft'))
+
+ def action_cancel(self):
+ return self.order_id.with_context({'disable_cancel_warning': True}).action_cancel()
diff --git a/addons/sale/wizard/sale_order_cancel_views.xml b/addons/sale/wizard/sale_order_cancel_views.xml
new file mode 100644
index 00000000000..722d2797919
--- /dev/null
+++ b/addons/sale/wizard/sale_order_cancel_views.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="sale_order_cancel_view_form" model="ir.ui.view">
+ <field name="name">sale.order.cancel.form</field>
+ <field name="model">sale.order.cancel</field>
+ <field name="arch" type="xml">
+ <form>
+ <field name="order_id" invisible="1"/>
+ <field name="display_invoice_alert" invisible="1"/>
+ <div attrs="{'invisible': [('display_invoice_alert', '=', False)]}">
+ Draft invoices for this order will be cancelled.
+ </div>
+ <footer>
+ <button string="Confirm" name="action_cancel" type="object" class="oe_highlight"/>
+ <button string="Cancel" class="btn btn-default" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py
index aa1e6be8563..7f693cd831e 100644
--- a/addons/sale_stock/__manifest__.py
+++ b/addons/sale_stock/__manifest__.py
@@ -33,6 +33,7 @@ Preferences
'report/stock_report_deliveryslip.xml',
'data/sale_stock_data.xml',
'wizard/stock_rules_report_views.xml',
+ 'wizard/sale_order_cancel_views.xml',
],
'demo': ['data/sale_order_demo.xml'],
'qweb': ['static/src/xml/qty_at_date.xml'],
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 445533135b6..8b972aca5b5 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -150,7 +150,7 @@ class SaleOrder(models.Model):
if sale_order.state == 'sale' and sale_order.order_line:
sale_order_lines_quantities = {order_line: (order_line.product_uom_qty, 0) for order_line in sale_order.order_line}
documents = self.env['stock.picking']._log_activity_get_documents(sale_order_lines_quantities, 'move_ids', 'UP')
- self.mapped('picking_ids').action_cancel()
+ self.picking_ids.filtered(lambda p: p.state != 'done').action_cancel()
if documents:
filtered_documents = {}
for (parent, responsible), rendering_context in documents.items():
@@ -190,6 +190,12 @@ class SaleOrder(models.Model):
self.env['stock.picking']._log_activity(_render_note_exception_quantity_so, documents)
+ def _show_cancel_wizard(self):
+ res = super(SaleOrder, self)._show_cancel_wizard()
+ for order in self:
+ if any(picking.state == 'done' for picking in order.picking_ids) and not order._context.get('disable_cancel_warning'):
+ return True
+ return res
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
index 6d5eaf6c753..e22d52f6e35 100644
--- a/addons/sale_stock/tests/test_sale_stock.py
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -742,3 +742,56 @@ class TestSaleStock(TestSale):
return_picking.button_validate()
# Checks the delivery amount (must still be 10).
self.assertEqual(sale_order.order_line.qty_delivered, 10)
+
+ def test_08_sale_return_qty_and_cancel(self):
+ """
+ Test a SO with a product on delivery with a 5 quantity.
+ Create two invoices: one for 3 quantity and one for 2 quantity
+ Then cancel Sale order, it won't raise any warning, it should be cancelled.
+ """
+ partner = self.partner
+ product = self.products['prod_del']
+ so_vals = {
+ 'partner_id': partner.id,
+ 'partner_invoice_id': partner.id,
+ 'partner_shipping_id': partner.id,
+ 'order_line': [(0, 0, {
+ 'name': product.name,
+ 'product_id': product.id,
+ 'product_uom_qty': 5.0,
+ 'product_uom': product.uom_id.id,
+ 'price_unit': product.list_price})],
+ 'pricelist_id': self.env.ref('product.list0').id,
+ }
+ so = self.env['sale.order'].create(so_vals)
+
+ # confirm the so
+ so.action_confirm()
+
+ # deliver partially
+ pick = so.picking_ids
+ pick.move_lines.write({'quantity_done': 3})
+
+ wiz_act = pick.button_validate()
+ wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id']).with_context(wiz_act['context'])
+ wiz.process()
+
+ # create invoice for 3 quantity and post it
+ inv_1 = so._create_invoices()
+ inv_1.post()
+ self.assertEqual(inv_1.state, 'posted', 'invoice should be in posted state')
+
+ pick_2 = so.picking_ids.filtered('backorder_id')
+ pick_2.move_lines.write({'quantity_done': 2})
+ pick_2.button_validate()
+
+ # create invoice for remaining 2 quantity
+ inv_2 = so._create_invoices()
+ self.assertEqual(inv_2.state, 'draft', 'invoice should be in draft state')
+
+ # check the status of invoices after cancelling the order
+ so.action_cancel()
+ wizard = self.env['sale.order.cancel'].with_context({'order_id': so.id}).create({'order_id': so.id})
+ wizard.action_cancel()
+ self.assertEqual(inv_1.state, 'posted', 'A posted invoice state should remain posted')
+ self.assertEqual(inv_2.state, 'cancel', 'A drafted invoice state should be cancelled')
diff --git a/addons/sale_stock/wizard/__init__.py b/addons/sale_stock/wizard/__init__.py
index 5b103cfeab6..8cacb400495 100644
--- a/addons/sale_stock/wizard/__init__.py
+++ b/addons/sale_stock/wizard/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_rules_report
+from . import sale_order_cancel
diff --git a/addons/sale_stock/wizard/sale_order_cancel.py b/addons/sale_stock/wizard/sale_order_cancel.py
new file mode 100644
index 00000000000..8057c85c483
--- /dev/null
+++ b/addons/sale_stock/wizard/sale_order_cancel.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class SaleOrderCancel(models.TransientModel):
+ _inherit = 'sale.order.cancel'
+
+ display_delivery_alert = fields.Boolean('Delivery Alert', compute='_compute_display_delivery_alert')
+
+ @api.depends('order_id')
+ def _compute_display_delivery_alert(self):
+ for wizard in self:
+ wizard.display_delivery_alert = bool(any(picking.state == 'done' for picking in wizard.order_id.picking_ids))
diff --git a/addons/sale_stock/wizard/sale_order_cancel_views.xml b/addons/sale_stock/wizard/sale_order_cancel_views.xml
new file mode 100644
index 00000000000..3f98b5c5eb1
--- /dev/null
+++ b/addons/sale_stock/wizard/sale_order_cancel_views.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="sale_order_cancel_view_form_inherit" model="ir.ui.view">
+ <field name="name">sale.order.cancel.form.inherit</field>
+ <field name="model">sale.order.cancel</field>
+ <field name="inherit_id" ref="sale.sale_order_cancel_view_form"/>
+ <field name="arch" type="xml">
+ <field name="display_invoice_alert" position="after">
+ <field name="display_delivery_alert" invisible="1"/>
+ <div attrs="{'invisible': [('display_delivery_alert', '=', False)]}">
+ Some products have already been delivered. Returns can be created from the Delivery Orders.
+ </div>
+ </field>
+ </field>
+ </record>
+</odoo>