mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[MIG] stock_inventory_discrepancy: Migration to 13.0
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
from .hooks import post_load_hook
|
||||||
|
|||||||
@@ -5,18 +5,20 @@
|
|||||||
"summary": "Adds the capability to show the discrepancy of every line in "
|
"summary": "Adds the capability to show the discrepancy of every line in "
|
||||||
"an inventory and to block the inventory validation when the "
|
"an inventory and to block the inventory validation when the "
|
||||||
"discrepancy is over a user defined threshold.",
|
"discrepancy is over a user defined threshold.",
|
||||||
"version": "12.0.1.0.0",
|
"version": "13.0.1.0.0",
|
||||||
"author": "ForgeFlow, Odoo Community Association (OCA)",
|
"author": "ForgeFlow, Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||||
"category": "Warehouse Management",
|
"category": "Warehouse",
|
||||||
"depends": ["stock"],
|
"depends": ["stock"],
|
||||||
"data": [
|
"data": [
|
||||||
"security/stock_inventory_discrepancy_security.xml",
|
"security/stock_inventory_discrepancy_security.xml",
|
||||||
|
"views/assets_backend.xml",
|
||||||
"views/stock_inventory_view.xml",
|
"views/stock_inventory_view.xml",
|
||||||
"views/stock_warehouse_view.xml",
|
"views/stock_warehouse_view.xml",
|
||||||
"views/stock_location_view.xml",
|
"views/stock_location_view.xml",
|
||||||
],
|
],
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
|
"post_load": "post_load_hook",
|
||||||
"installable": True,
|
"installable": True,
|
||||||
"application": False,
|
"application": False,
|
||||||
}
|
}
|
||||||
|
|||||||
79
stock_inventory_discrepancy/hooks.py
Normal file
79
stock_inventory_discrepancy/hooks.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Copyright 2019 Eficent Business and IT Consulting Services S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||||
|
|
||||||
|
|
||||||
|
from odoo import _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools.float_utils import float_compare
|
||||||
|
|
||||||
|
from odoo.addons.stock.models.stock_inventory import Inventory
|
||||||
|
|
||||||
|
|
||||||
|
def post_load_hook():
|
||||||
|
def action_validate_discrepancy(self):
|
||||||
|
"""Override method to avoid inline group validation"""
|
||||||
|
if not self.exists():
|
||||||
|
return
|
||||||
|
self.ensure_one()
|
||||||
|
# START HOOK: - Allow specific group to validate inventory
|
||||||
|
# - Allow validate on pending status
|
||||||
|
if (
|
||||||
|
not self.user_has_groups("stock.group_stock_manager")
|
||||||
|
and not self.user_has_groups(
|
||||||
|
"stock_inventory_discrepancy.group_stock_inventory_validation"
|
||||||
|
)
|
||||||
|
and not self.user_has_groups(
|
||||||
|
"stock_inventory_discrepancy.group_stock_inventory_validation_always"
|
||||||
|
)
|
||||||
|
):
|
||||||
|
raise UserError(
|
||||||
|
_("Only a stock manager can validate an inventory adjustment.")
|
||||||
|
)
|
||||||
|
if self.state not in ["confirm", "pending"]:
|
||||||
|
raise UserError(
|
||||||
|
_(
|
||||||
|
"You can't validate the inventory '%s', maybe this inventory "
|
||||||
|
+ "has been already validated or isn't ready."
|
||||||
|
)
|
||||||
|
% (self.name)
|
||||||
|
)
|
||||||
|
# END HOOK
|
||||||
|
inventory_lines = self.line_ids.filtered(
|
||||||
|
lambda l: l.product_id.tracking in ["lot", "serial"]
|
||||||
|
and not l.prod_lot_id
|
||||||
|
and l.theoretical_qty != l.product_qty
|
||||||
|
)
|
||||||
|
lines = self.line_ids.filtered(
|
||||||
|
lambda l: float_compare(
|
||||||
|
l.product_qty, 1, precision_rounding=l.product_uom_id.rounding
|
||||||
|
)
|
||||||
|
> 0
|
||||||
|
and l.product_id.tracking == "serial"
|
||||||
|
and l.prod_lot_id
|
||||||
|
)
|
||||||
|
if inventory_lines and not lines:
|
||||||
|
wiz_lines = [
|
||||||
|
(0, 0, {"product_id": product.id, "tracking": product.tracking})
|
||||||
|
for product in inventory_lines.mapped("product_id")
|
||||||
|
]
|
||||||
|
wiz = self.env["stock.track.confirmation"].create(
|
||||||
|
{"inventory_id": self.id, "tracking_line_ids": wiz_lines}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"name": _("Tracked Products in Inventory Adjustment"),
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"view_mode": "form",
|
||||||
|
"views": [(False, "form")],
|
||||||
|
"res_model": "stock.track.confirmation",
|
||||||
|
"target": "new",
|
||||||
|
"res_id": wiz.id,
|
||||||
|
}
|
||||||
|
self._action_done()
|
||||||
|
self.line_ids._check_company()
|
||||||
|
self._check_company()
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not hasattr(Inventory, "action_validate_original"):
|
||||||
|
Inventory.action_validate_original = Inventory.action_validate
|
||||||
|
|
||||||
|
Inventory._patch_method("action_validate", action_validate_discrepancy)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2017-2020 ForgeFlow S.L.
|
# Copyright 2017-2020 ForgeFlow S.L.
|
||||||
# (http://www.eficent.com)
|
# (http://www.forgeflow.com)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
@@ -9,16 +9,8 @@ from odoo.exceptions import UserError
|
|||||||
class StockInventory(models.Model):
|
class StockInventory(models.Model):
|
||||||
_inherit = "stock.inventory"
|
_inherit = "stock.inventory"
|
||||||
|
|
||||||
INVENTORY_STATE_SELECTION = [
|
|
||||||
("draft", "Draft"),
|
|
||||||
("cancel", "Cancelled"),
|
|
||||||
("confirm", "In Progress"),
|
|
||||||
("pending", "Pending to Approve"),
|
|
||||||
("done", "Validated"),
|
|
||||||
]
|
|
||||||
|
|
||||||
state = fields.Selection(
|
state = fields.Selection(
|
||||||
selection=INVENTORY_STATE_SELECTION,
|
selection_add=[("pending", "Pending to Approve"), ("done",)],
|
||||||
string="Status",
|
string="Status",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
index=True,
|
index=True,
|
||||||
@@ -37,7 +29,6 @@ class StockInventory(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
@api.depends("line_ids.product_qty", "line_ids.theoretical_qty")
|
@api.depends("line_ids.product_qty", "line_ids.theoretical_qty")
|
||||||
def _compute_over_discrepancy_line_count(self):
|
def _compute_over_discrepancy_line_count(self):
|
||||||
for inventory in self:
|
for inventory in self:
|
||||||
@@ -46,13 +37,12 @@ class StockInventory(models.Model):
|
|||||||
)
|
)
|
||||||
inventory.over_discrepancy_line_count = len(lines)
|
inventory.over_discrepancy_line_count = len(lines)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_over_discrepancies(self):
|
def action_over_discrepancies(self):
|
||||||
self.write({"state": "pending"})
|
self.write({"state": "pending"})
|
||||||
|
|
||||||
def _check_group_inventory_validation_always(self):
|
def _check_group_inventory_validation_always(self):
|
||||||
grp_inv_val = self.env.ref(
|
grp_inv_val = self.env.ref(
|
||||||
"stock_inventory_discrepancy.group_" "stock_inventory_validation_always"
|
"stock_inventory_discrepancy.group_stock_inventory_validation_always"
|
||||||
)
|
)
|
||||||
if grp_inv_val in self.env.user.groups_id:
|
if grp_inv_val in self.env.user.groups_id:
|
||||||
return True
|
return True
|
||||||
@@ -70,13 +60,17 @@ class StockInventory(models.Model):
|
|||||||
if inventory.over_discrepancy_line_count and inventory.line_ids.filtered(
|
if inventory.over_discrepancy_line_count and inventory.line_ids.filtered(
|
||||||
lambda t: t.discrepancy_threshold > 0.0
|
lambda t: t.discrepancy_threshold > 0.0
|
||||||
):
|
):
|
||||||
if inventory.env.context.get("normal_view", False):
|
if self.user_has_groups(
|
||||||
|
"stock_inventory_discrepancy.group_stock_inventory_validation"
|
||||||
|
) and not self.user_has_groups(
|
||||||
|
"stock_inventory_discrepancy."
|
||||||
|
"group_stock_inventory_validation_always"
|
||||||
|
):
|
||||||
inventory.action_over_discrepancies()
|
inventory.action_over_discrepancies()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
inventory._check_group_inventory_validation_always()
|
inventory._check_group_inventory_validation_always()
|
||||||
return super(StockInventory, self)._action_done()
|
return super(StockInventory, self)._action_done()
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_force_done(self):
|
def action_force_done(self):
|
||||||
return super(StockInventory, self)._action_done()
|
return super(StockInventory, self)._action_done()
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
# Copyright 2017-2020 ForgeFlow S.L.
|
# Copyright 2017-2020 ForgeFlow S.L.
|
||||||
# (http://www.eficent.com)
|
# (http://www.forgeflow.com)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
from odoo.addons import decimal_precision as dp
|
|
||||||
|
|
||||||
|
|
||||||
class StockInventoryLine(models.Model):
|
class StockInventoryLine(models.Model):
|
||||||
_inherit = "stock.inventory.line"
|
_inherit = "stock.inventory.line"
|
||||||
@@ -15,7 +13,7 @@ class StockInventoryLine(models.Model):
|
|||||||
compute="_compute_discrepancy",
|
compute="_compute_discrepancy",
|
||||||
help="The difference between the actual qty counted and the "
|
help="The difference between the actual qty counted and the "
|
||||||
"theoretical quantity on hand.",
|
"theoretical quantity on hand.",
|
||||||
digits=dp.get_precision("Product Unit of Measure"),
|
digits="Product Unit of Measure",
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
discrepancy_percent = fields.Float(
|
discrepancy_percent = fields.Float(
|
||||||
@@ -32,7 +30,6 @@ class StockInventoryLine(models.Model):
|
|||||||
compute="_compute_discrepancy_threshold",
|
compute="_compute_discrepancy_threshold",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
@api.depends("theoretical_qty", "product_qty")
|
@api.depends("theoretical_qty", "product_qty")
|
||||||
def _compute_discrepancy(self):
|
def _compute_discrepancy(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
@@ -43,8 +40,9 @@ class StockInventoryLine(models.Model):
|
|||||||
)
|
)
|
||||||
elif not line.theoretical_qty and line.product_qty:
|
elif not line.theoretical_qty and line.product_qty:
|
||||||
line.discrepancy_percent = 100.0
|
line.discrepancy_percent = 100.0
|
||||||
|
else:
|
||||||
|
line.discrepancy_percent = 0.0
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _compute_discrepancy_threshold(self):
|
def _compute_discrepancy_threshold(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
whs = line.location_id.get_warehouse()
|
whs = line.location_id.get_warehouse()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2017-2020 ForgeFlow S.L.
|
# Copyright 2017-2020 ForgeFlow S.L.
|
||||||
# (http://www.eficent.com)
|
# (http://www.forgeflow.com)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import fields, models
|
||||||
@@ -15,3 +15,22 @@ class StockLocation(models.Model):
|
|||||||
"an Inventory Adjustment. Thresholds defined in Locations have "
|
"an Inventory Adjustment. Thresholds defined in Locations have "
|
||||||
"preference over Warehouse's ones.",
|
"preference over Warehouse's ones.",
|
||||||
)
|
)
|
||||||
|
propagate_discrepancy_threshold = fields.Boolean(
|
||||||
|
string="Propagate discrepancy threshold",
|
||||||
|
help="Propagate Maximum Discrepancy Rate Threshold to child locations",
|
||||||
|
)
|
||||||
|
|
||||||
|
def write(self, values):
|
||||||
|
res = super().write(values)
|
||||||
|
# Set the discrepancy threshold for all child locations
|
||||||
|
if values.get("discrepancy_threshold", False):
|
||||||
|
for location in self.filtered(
|
||||||
|
lambda loc: loc.propagate_discrepancy_threshold and loc.child_ids
|
||||||
|
):
|
||||||
|
location.child_ids.write(
|
||||||
|
{
|
||||||
|
"discrepancy_threshold": values["discrepancy_threshold"],
|
||||||
|
"propagate_discrepancy_threshold": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2017-2020 ForgeFlow S.L.
|
# Copyright 2017-2020 ForgeFlow S.L.
|
||||||
# (http://www.eficent.com)
|
# (http://www.forgeflow.com)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo import fields, models
|
from odoo import fields, models
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
* Lois Rilo <lois.rilo@forgeflow.com>
|
* Lois Rilo <lois.rilo@forgeflow.com>
|
||||||
* Andreas Dian Sukarno Putro <andreasdian777@gmail.com>
|
* Andreas Dian Sukarno Putro <andreasdian777@gmail.com>
|
||||||
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
|
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
|
||||||
|
* Héctor Villarreal <hector.villarreal@forgeflow.com>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
odoo.define('stock_inventory_discrepancy.InventoryValidationController', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var core = require('web.core');
|
||||||
|
var InventoryValidationController = require('stock.InventoryValidationController');
|
||||||
|
|
||||||
|
var _t = core._t;
|
||||||
|
|
||||||
|
InventoryValidationController.include({
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @see displayNotification
|
||||||
|
*/
|
||||||
|
do_notify: function (title, message, sticky, className) {
|
||||||
|
var self = this;
|
||||||
|
if (this.modelName === "stock.inventory.line") {
|
||||||
|
this._rpc({
|
||||||
|
model: 'stock.inventory',
|
||||||
|
method: 'read',
|
||||||
|
args: [this.inventory_id, ['state']],
|
||||||
|
}).then(function (res) {
|
||||||
|
if (res[0].state === "pending") {
|
||||||
|
title = _t("Pending to Approve");
|
||||||
|
message = _t("The inventory needs to be approved");
|
||||||
|
}
|
||||||
|
}).finally(function () {
|
||||||
|
return self.displayNotification({
|
||||||
|
type: 'warning',
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
sticky: sticky,
|
||||||
|
className: className,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2017-2020 ForgeFlow S.L.
|
# Copyright 2017-2020 ForgeFlow S.L.
|
||||||
# (http://www.eficent.com)
|
# (http://www.forgeflow.com)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
@@ -14,7 +14,6 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
self.obj_inventory = self.env["stock.inventory"]
|
self.obj_inventory = self.env["stock.inventory"]
|
||||||
self.obj_product = self.env["product.product"]
|
self.obj_product = self.env["product.product"]
|
||||||
self.obj_warehouse = self.env["stock.warehouse"]
|
self.obj_warehouse = self.env["stock.warehouse"]
|
||||||
self.obj_upd_qty_wizard = self.env["stock.change.product.qty"]
|
|
||||||
|
|
||||||
self.product1 = self.obj_product.create(
|
self.product1 = self.obj_product.create(
|
||||||
{"name": "Test Product 1", "type": "product", "default_code": "PROD1"}
|
{"name": "Test Product 1", "type": "product", "default_code": "PROD1"}
|
||||||
@@ -33,8 +32,10 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
# Create Stock manager able to force validation on inventories.
|
# Create Stock manager able to force validation on inventories.
|
||||||
group_stock_man = self.env.ref("stock.group_stock_manager")
|
group_stock_man = self.env.ref("stock.group_stock_manager")
|
||||||
group_inventory_all = self.env.ref(
|
group_inventory_all = self.env.ref(
|
||||||
"stock_inventory_discrepancy." "group_stock_inventory_validation_always"
|
"stock_inventory_discrepancy.group_stock_inventory_validation_always"
|
||||||
)
|
)
|
||||||
|
group_employee = self.env.ref("base.group_user")
|
||||||
|
|
||||||
self.manager = self.env["res.users"].create(
|
self.manager = self.env["res.users"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Manager",
|
"name": "Test Manager",
|
||||||
@@ -53,10 +54,27 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.user_2 = self.env["res.users"].create(
|
||||||
|
{
|
||||||
|
"name": "Test User 2",
|
||||||
|
"login": "user_2",
|
||||||
|
"email": "test2.user@example.com",
|
||||||
|
"groups_id": [(6, 0, [group_stock_user.id, group_inventory_all.id])],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.no_user = self.env["res.users"].create(
|
||||||
|
{
|
||||||
|
"name": "No User",
|
||||||
|
"login": "no_user",
|
||||||
|
"email": "test.no_user@example.com",
|
||||||
|
"groups_id": [(6, 0, [group_employee.id])],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
starting_inv = self.obj_inventory.create(
|
starting_inv = self.obj_inventory.create(
|
||||||
{
|
{
|
||||||
"name": "Starting inventory",
|
"name": "Starting inventory",
|
||||||
"filter": "product",
|
|
||||||
"line_ids": [
|
"line_ids": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -89,8 +107,7 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
inventory = self.obj_inventory.create(
|
inventory = self.obj_inventory.create(
|
||||||
{
|
{
|
||||||
"name": "Test Discrepancy Computation",
|
"name": "Test Discrepancy Computation",
|
||||||
"location_id": self.test_loc.id,
|
"location_ids": [(4, self.test_loc.id)],
|
||||||
"filter": "none",
|
|
||||||
"line_ids": [
|
"line_ids": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -131,8 +148,7 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
inventory = self.obj_inventory.create(
|
inventory = self.obj_inventory.create(
|
||||||
{
|
{
|
||||||
"name": "Test Forcing Validation Method",
|
"name": "Test Forcing Validation Method",
|
||||||
"location_id": self.test_loc.id,
|
"location_ids": [(4, self.test_loc.id)],
|
||||||
"filter": "none",
|
|
||||||
"line_ids": [
|
"line_ids": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -155,7 +171,8 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
0.1,
|
0.1,
|
||||||
"Threshold wrongly computed in Inventory Line.",
|
"Threshold wrongly computed in Inventory Line.",
|
||||||
)
|
)
|
||||||
inventory.with_context({"normal_view": True}).action_validate()
|
inventory.with_user(self.user).action_start()
|
||||||
|
inventory.with_user(self.user).action_validate()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
inventory.over_discrepancy_line_count,
|
inventory.over_discrepancy_line_count,
|
||||||
1,
|
1,
|
||||||
@@ -166,7 +183,7 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
"pending",
|
"pending",
|
||||||
"Inventory Adjustment not changing to Pending to " "Approve.",
|
"Inventory Adjustment not changing to Pending to " "Approve.",
|
||||||
)
|
)
|
||||||
inventory.sudo(self.manager).action_force_done()
|
inventory.with_user(self.manager).action_force_done()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
inventory.state,
|
inventory.state,
|
||||||
"done",
|
"done",
|
||||||
@@ -174,13 +191,56 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
"not working properly.",
|
"not working properly.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_discrepancy_validation_always(self):
|
||||||
|
"""Tests the new workflow"""
|
||||||
|
inventory = self.obj_inventory.create(
|
||||||
|
{
|
||||||
|
"name": "Test Forcing Validation Method",
|
||||||
|
"location_ids": [(4, self.test_loc.id)],
|
||||||
|
"line_ids": [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"product_id": self.product1.id,
|
||||||
|
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||||
|
"product_qty": 3.0,
|
||||||
|
"location_id": self.test_loc.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
inventory.state, "draft", "Testing Inventory wrongly configurated"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
inventory.line_ids.discrepancy_threshold,
|
||||||
|
0.1,
|
||||||
|
"Threshold wrongly computed in Inventory Line.",
|
||||||
|
)
|
||||||
|
inventory.with_user(self.user_2).action_start()
|
||||||
|
# User with no privileges can't validate a Inventory Adjustment.
|
||||||
|
with self.assertRaises(UserError):
|
||||||
|
inventory.with_user(self.no_user).action_validate()
|
||||||
|
inventory.with_user(self.user_2).action_validate()
|
||||||
|
self.assertEqual(
|
||||||
|
inventory.over_discrepancy_line_count,
|
||||||
|
1,
|
||||||
|
"Computation of over-discrepancies failed.",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
inventory.state,
|
||||||
|
"done",
|
||||||
|
"Stock Managers belongs to group Validate All inventory Adjustments",
|
||||||
|
)
|
||||||
|
|
||||||
def test_warehouse_threshold(self):
|
def test_warehouse_threshold(self):
|
||||||
"""Tests the behaviour if the threshold is set on the WH."""
|
"""Tests the behaviour if the threshold is set on the WH."""
|
||||||
inventory = self.obj_inventory.create(
|
inventory = self.obj_inventory.create(
|
||||||
{
|
{
|
||||||
"name": "Test Threshold Defined in WH",
|
"name": "Test Threshold Defined in WH",
|
||||||
"location_id": self.test_wh.view_location_id.id,
|
"location_ids": [(4, self.test_wh.view_location_id.id)],
|
||||||
"filter": "none",
|
|
||||||
"line_ids": [
|
"line_ids": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -201,17 +261,23 @@ class TestInventoryDiscrepancy(TransactionCase):
|
|||||||
"Threshold wrongly computed in Inventory Line.",
|
"Threshold wrongly computed in Inventory Line.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_qty_user_error(self):
|
def test_propagate_discrepancy_threshold(self):
|
||||||
"""Test if a user error raises when a stock user tries to update the
|
view_test_loc = self.obj_location.create(
|
||||||
qty for a product and the correction is a discrepancy over the
|
{"name": "Test Location", "usage": "view", "discrepancy_threshold": 0.1}
|
||||||
threshold."""
|
)
|
||||||
upd_qty = self.obj_upd_qty_wizard.sudo(self.user).create(
|
child_test_loc = self.obj_location.create(
|
||||||
{
|
{
|
||||||
"product_id": self.product1.id,
|
"name": "Child Test Location",
|
||||||
"product_tmpl_id": self.product1.product_tmpl_id.id,
|
"usage": "internal",
|
||||||
"new_quantity": 10.0,
|
"discrepancy_threshold": 0.2,
|
||||||
"location_id": self.test_loc.id,
|
"location_id": view_test_loc.id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with self.assertRaises(UserError):
|
view_test_loc.write(
|
||||||
upd_qty.change_product_qty()
|
{"discrepancy_threshold": 0.3, "propagate_discrepancy_threshold": True}
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
child_test_loc.discrepancy_threshold,
|
||||||
|
0.3,
|
||||||
|
"Threshold Discrepancy wrongly propagated",
|
||||||
|
)
|
||||||
|
|||||||
10
stock_inventory_discrepancy/views/assets_backend.xml
Normal file
10
stock_inventory_discrepancy/views/assets_backend.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<template id="stock_assets_backend" name="stock_inventory assets" inherit_id="web.assets_backend">
|
||||||
|
<xpath expr="." position="inside">
|
||||||
|
<script type="text/javascript" src="/stock_inventory_discrepancy/static/src/js/inventory_validate_button_controller.js"></script>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
<xpath expr="//button[@name='action_validate']"
|
<xpath expr="//button[@name='action_validate']"
|
||||||
position="attributes">
|
position="attributes">
|
||||||
<attribute name="groups">stock_inventory_discrepancy.group_stock_inventory_validation</attribute>
|
<attribute name="groups">stock_inventory_discrepancy.group_stock_inventory_validation</attribute>
|
||||||
<attribute name="context">{'normal_view': True}</attribute>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//button[@name='action_validate']"
|
<xpath expr="//button[@name='action_validate']"
|
||||||
position="after">
|
position="after">
|
||||||
@@ -30,19 +29,31 @@
|
|||||||
groups="stock_inventory_discrepancy.group_stock_inventory_validation_always"
|
groups="stock_inventory_discrepancy.group_stock_inventory_validation_always"
|
||||||
attrs="{'invisible': ['|',('state', '!=', 'pending'),('over_discrepancy_line_count', '=', 0)]}"/>
|
attrs="{'invisible': ['|',('state', '!=', 'pending'),('over_discrepancy_line_count', '=', 0)]}"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<field name="date" position="after">
|
<xpath expr="//button[@name='action_open_inventory_lines']" position="attributes">
|
||||||
|
<attribute name="states">pending,confirm</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@name='action_cancel_draft'][2]" position="attributes">
|
||||||
|
<attribute name="states">pending,confirm</attribute>
|
||||||
|
</xpath>
|
||||||
|
<field name="company_id" position="before">
|
||||||
<field name="over_discrepancy_line_count" attrs="{'invisible': [('state', '!=', 'pending')]}"/>
|
<field name="over_discrepancy_line_count" attrs="{'invisible': [('state', '!=', 'pending')]}"/>
|
||||||
</field>
|
</field>
|
||||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='product_qty']"
|
|
||||||
position="after">
|
|
||||||
<field name="discrepancy_qty"/>
|
|
||||||
<field name="discrepancy_percent"/>
|
|
||||||
<field name="discrepancy_threshold"/>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='line_ids']/tree" position="attributes">
|
|
||||||
<attribute name="decoration-danger">theoretical_qty < 0 or discrepancy_percent > discrepancy_threshold</attribute>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="stock_inventory_line_tree2" model="ir.ui.view">
|
||||||
|
<field name="name">stock.inventory.line.tree2</field>
|
||||||
|
<field name="model">stock.inventory.line</field>
|
||||||
|
<field name="inherit_id" ref="stock.stock_inventory_line_tree2"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="product_qty" position="after">
|
||||||
|
<field name="discrepancy_qty"/>
|
||||||
|
<field name="discrepancy_percent"/>
|
||||||
|
<field name="discrepancy_threshold"/>
|
||||||
|
</field>
|
||||||
|
<xpath expr="//tree" position="attributes">
|
||||||
|
<attribute name="decoration-danger">theoretical_qty < 0 or discrepancy_percent > discrepancy_threshold or "product_qty != theoretical_qty"</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
<field name="model">stock.location</field>
|
<field name="model">stock.location</field>
|
||||||
<field name="inherit_id" ref="stock.view_location_form"/>
|
<field name="inherit_id" ref="stock.view_location_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="partner_id" position="after">
|
<field name="return_location" position="after">
|
||||||
<label for="discrepancy_threshold"/>
|
<label for="discrepancy_threshold"/>
|
||||||
<div>
|
<div>
|
||||||
<field name="discrepancy_threshold" class="oe_inline"/> %
|
<field name="discrepancy_threshold" class="oe_inline"/> %
|
||||||
</div>
|
</div>
|
||||||
|
<field name="propagate_discrepancy_threshold"/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="view_warehouse_form" model="ir.ui.view">
|
<record id="view_warehouse" model="ir.ui.view">
|
||||||
<field name="name">Warehouse form - Inventory Discrepancy
|
<field name="name">Warehouse form - Inventory Discrepancy
|
||||||
extension</field>
|
extension</field>
|
||||||
<field name="model">stock.warehouse</field>
|
<field name="model">stock.warehouse</field>
|
||||||
|
|||||||
Reference in New Issue
Block a user