Skip to content

Commit acb5010

Browse files
committed
[ADD] sale_order_zero_stock_blockage: add manager approval on insufficient stock
Currently, a sales user can confirm a Quotation even if it contains no products or if the products have insufficient stock compared to the order quantity. This leads to the creation of invalid Sales Orders, which negatively impacts inventory planning and invoicing. This module introduces a validation to the Sales Order confirmation process to prevent these issues. The following checks are added: 1. Prevent confirmation if the order has no lines. 2. Prevent confirmation if the product stock is insufficient. To bypass the insufficient stock restriction, a user with the 'Sales Manager' group must enable the new 'Approval' (zero_stock_approval) field on the order. Technical details: - Model `sale.order`: Added `zero_stock_approval` Boolean field. - Method `fields_get`: Overridden to set `zero_stock_approval` as readonly for users who are not in the `sales_team.group_sale_manager` group. - Method `action_confirm`: Added logic to raise a `UserError` if: - The order has no lines. - A product's `qty_available` is less than `product_uom_qty` (for goods) and the `zero_stock_approval` is not enabled. - added TestCases. Task-5382939
1 parent b68a192 commit acb5010

File tree

7 files changed

+136
-0
lines changed

7 files changed

+136
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "Sale Order Zero Stock Blockage",
3+
"description": """
4+
Zero Stock Blockage is module to prevent order to confirm if product is out of stock.
5+
6+
But if manager wants then he can aprove that order.
7+
""",
8+
"version": "1.0",
9+
"depends": ['sale_management', 'stock'],
10+
"author": "danal",
11+
"category": "Category",
12+
"license": "LGPL-3",
13+
"data": [
14+
"views/sale_order_view.xml",
15+
],
16+
"installable": True,
17+
'application': False,
18+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import sale_order
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from odoo.exceptions import UserError
2+
3+
from odoo import fields, models, api
4+
5+
6+
class SaleOrder(models.Model):
7+
_inherit = 'sale.order'
8+
9+
zero_stock_approval = fields.Boolean(
10+
string="Approval",
11+
help="Order Approval by manager,\nif order has insufficient stock then this approval is required by manager.",
12+
copy=False,
13+
)
14+
15+
@api.model
16+
def fields_get(self, allfields=None, attributes=None):
17+
res = super().fields_get(allfields, attributes)
18+
if not self.env.user.has_group("sales_team.group_sale_manager"):
19+
if "zero_stock_approval" in res:
20+
res["zero_stock_approval"]["readonly"] = True
21+
return res
22+
23+
def action_confirm(self):
24+
for record in self:
25+
if not record.order_line:
26+
raise UserError("You cannot confirm a Quotation without any products.")
27+
if record.zero_stock_approval:
28+
return super().action_confirm()
29+
for line in record.order_line:
30+
if (
31+
line.product_id.qty_available < line.product_uom_qty
32+
and line.product_id.type == "consu"
33+
and not record.zero_stock_approval
34+
and not self.env.user.has_group("sales_team.group_sale_manager")
35+
):
36+
raise UserError(
37+
"Cannot confirm this Sale Order due to insufficient stock.\n\nPlease get approval or adjust the quantities."
38+
)
39+
return super().action_confirm()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
2+
3+
from . import test_sale_blockage
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from odoo.tests.common import TransactionCase
2+
from odoo.exceptions import UserError
3+
4+
class TestSaleZeroStockBlockage(TransactionCase):
5+
6+
@classmethod
7+
def setUpClass(self):
8+
super().setUpClass()
9+
10+
self.sale_user = self.env['res.users'].create({
11+
'name': 'Salesman Test User',
12+
'login': 'sales_test_user',
13+
'email': '[email protected]',
14+
'group_ids': [(4, self.env.ref('sales_team.group_sale_salesman').id)],
15+
})
16+
17+
self.partner = self.sale_user.partner_id
18+
19+
self.product_no_stock = self.env['product.product'].create({
20+
'name': 'Zero Stock Product',
21+
'type': 'consu',
22+
'list_price': 100.0,
23+
})
24+
25+
self.sale_order = self.env['sale.order'].with_user(self.sale_user).create({
26+
'partner_id': self.partner.id,
27+
'order_line': [(0, 0, {
28+
'product_id': self.product_no_stock.id,
29+
'product_uom_qty': 1.0,
30+
'price_unit': 100.0,
31+
})],
32+
})
33+
34+
def test_block_confirm_no_stock(self):
35+
""" Test that confirming a sale order with zero stock raises an error """
36+
self.assertEqual(self.product_no_stock.qty_available, 0, "Initial stock should be 0")
37+
with self.assertRaises(UserError):
38+
self.sale_order.action_confirm()
39+
40+
def test_allow_confirm_no_stock(self):
41+
""" Test that confirming a sale order with zero stock will not raise an error if `zero_stock_approval` is True """
42+
self.assertEqual(self.product_no_stock.qty_available, 0, "Initial stock should be 0")
43+
self.sale_order.zero_stock_approval = True
44+
self.assertEqual(self.sale_order.zero_stock_approval, True, "Cannot comfirm order of insufficent product")
45+
self.sale_order.action_confirm()
46+
47+
def test_allow_confirm_with_stock(self):
48+
""" Test that adding stock allows the order to be confirmed """
49+
stock_location = self.env.ref('stock.stock_location_stock')
50+
self.product_no_stock.is_storable = True
51+
self.env['stock.quant'].create({
52+
'product_id': self.product_no_stock.id,
53+
'location_id': stock_location.id,
54+
'inventory_quantity': 10.0,
55+
}).action_apply_inventory()
56+
57+
self.assertEqual(self.product_no_stock.qty_available, 10.0, "Stock should be updated to 10")
58+
self.sale_order.action_confirm()
59+
self.assertEqual(self.sale_order.state, 'sale', "Order should be in 'sale' state after confirmation")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<record id="sale_order_view_inherit" model="ir.ui.view">
4+
<field name="name">sale.order.view.inherit</field>
5+
<field name="model">sale.order</field>
6+
<field name="inherit_id" ref="sale.view_order_form"/>
7+
<field name="arch" type="xml">
8+
<xpath expr="//field[@name='payment_term_id']" position="after">
9+
<field name="zero_stock_approval"/>
10+
</xpath>
11+
</field>
12+
</record>
13+
</odoo>

0 commit comments

Comments
 (0)