[MIG] stock_inventory_discrepancy: Migration to 15.0

This commit is contained in:
Ernesto Tejeda
2023-04-05 13:51:34 +02:00
parent 1ea1e3e1fe
commit 0f8ce6ff97
24 changed files with 441 additions and 520 deletions

View File

@@ -0,0 +1 @@
../../../../stock_inventory_discrepancy

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@@ -14,14 +14,14 @@ Stock Inventory Discrepancy
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_inventory_discrepancy
:target: https://github.com/OCA/stock-logistics-warehouse/tree/15.0/stock_inventory_discrepancy
:alt: OCA/stock-logistics-warehouse
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_inventory_discrepancy
:target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-15-0/stock-logistics-warehouse-15-0-stock_inventory_discrepancy
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/153/14.0
:alt: Try me on Runbot
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/stock-logistics-warehouse&target_branch=15.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -66,7 +66,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-warehouse/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_inventory_discrepancy%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_inventory_discrepancy%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
@@ -85,6 +85,9 @@ Contributors
* Andreas Dian Sukarno Putro <andreasdian777@gmail.com>
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
* `Tecnativa <https://www.tecnativa.com>`_:
* Ernesto Tejeda
Maintainers
~~~~~~~~~~~
@@ -99,6 +102,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_inventory_discrepancy>`_ project on GitHub.
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/15.0/stock_inventory_discrepancy>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -1,4 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import models
from . import wizards
from .hooks import post_load_hook

View File

@@ -5,17 +5,18 @@
"summary": "Adds the capability to show the discrepancy of every line in "
"an inventory and to block the inventory validation when the "
"discrepancy is over a user defined threshold.",
"version": "14.0.1.1.0",
"version": "15.0.1.0.0",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"category": "Warehouse",
"depends": ["stock"],
"data": [
"security/stock_inventory_discrepancy_security.xml",
"views/assets_backend.xml",
"views/stock_inventory_view.xml",
"security/ir.model.access.csv",
"views/stock_quant_view.xml",
"views/stock_warehouse_view.xml",
"views/stock_location_view.xml",
"wizards/confirm_discrepancy_wiz.xml",
],
"license": "AGPL-3",
"post_load": "post_load_hook",

View File

@@ -2,19 +2,17 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _
from odoo import _, fields
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare
from odoo.addons.stock.models.stock_inventory import Inventory
from odoo.addons.stock.models.stock_quant import StockQuant
def post_load_hook():
def action_validate_discrepancy(self):
def _apply_inventory_discrepancy(self):
"""Override method to avoid inline group validation"""
if not self.exists():
return
self.ensure_one()
move_vals = []
# START HOOK: - Allow specific group to validate inventory
# - Allow validate on pending status
if (
@@ -29,51 +27,50 @@ def post_load_hook():
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)
)
# Allow to write last_inventory_date on stock.location
self = self.sudo()
# 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
for quant in self:
# Create and validate a move so that the quant matches its `inventory_quantity`.
if (
float_compare(
quant.inventory_diff_quantity,
0,
precision_rounding=quant.product_uom_id.rounding,
)
> 0
):
move_vals.append(
quant._get_inventory_move_values(
quant.inventory_diff_quantity,
quant.product_id.with_company(
quant.company_id
).property_stock_inventory,
quant.location_id,
)
)
else:
move_vals.append(
quant._get_inventory_move_values(
-quant.inventory_diff_quantity,
quant.location_id,
quant.product_id.with_company(
quant.company_id
).property_stock_inventory,
out=True,
)
)
moves = (
self.env["stock.move"].with_context(inventory_mode=False).create(move_vals)
)
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
moves._action_done()
self.location_id.write({"last_inventory_date": fields.Date.today()})
date_by_location = {
loc: loc._get_next_inventory_date() for loc in self.mapped("location_id")
}
for quant in self:
quant.inventory_date = date_by_location[quant.location_id]
self.write({"inventory_quantity": 0, "user_id": False})
self.write({"inventory_diff_quantity": 0})
if not hasattr(Inventory, "action_validate_original"):
Inventory.action_validate_original = Inventory.action_validate
Inventory._patch_method("action_validate", action_validate_discrepancy)
StockQuant._patch_method("_apply_inventory", _apply_inventory_discrepancy)

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- There must be the possibility to have an inventory users who cannot
apply an inventory adjustment -->
<record model="res.groups" id="stock.group_stock_user">
<field
name="implied_ids"
eval="[(3, ref('stock_inventory_discrepancy.group_stock_inventory_validation'))]"
/>
</record>
</odoo>

View File

@@ -0,0 +1,13 @@
# Copyright 2023 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
openupgrade.load_data(
env.cr,
"stock_inventory_discrepancy",
"migrations/15.0.1.0.0/noupdate_changes.xml",
)

View File

@@ -1,6 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import stock_inventory
from . import stock_inventory_line
from . import stock_quant
from . import stock_warehouse
from . import stock_location

View File

@@ -1,73 +0,0 @@
# Copyright 2017-21 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class StockInventory(models.Model):
_inherit = "stock.inventory"
state = fields.Selection(
selection_add=[("pending", "Pending to Approve"), ("done",)],
string="Status",
readonly=True,
index=True,
copy=False,
help="States of the Inventory Adjustment:\n"
"- Draft: Inventory not started.\n"
"- In Progress: Inventory in execution.\n"
"- Pending to Approve: Inventory have some discrepancies "
"greater than the predefined threshold and it's waiting for the "
"Control Manager approval.\n"
"- Validated: Inventory Approved.",
)
over_discrepancy_line_count = fields.Integer(
string="Number of Discrepancies Over Threshold",
compute="_compute_over_discrepancy_line_count",
store=True,
)
@api.depends("line_ids.product_qty", "line_ids.theoretical_qty")
def _compute_over_discrepancy_line_count(self):
for inventory in self:
lines = inventory.line_ids.filtered(
lambda line: line._has_over_discrepancy()
)
inventory.over_discrepancy_line_count = len(lines)
def action_over_discrepancies(self):
self.write({"state": "pending"})
def _check_group_inventory_validation_always(self):
grp_inv_val = self.env.ref(
"stock_inventory_discrepancy.group_stock_inventory_validation_always"
)
if grp_inv_val in self.env.user.groups_id:
return True
else:
raise UserError(
_(
"The Qty Update is over the Discrepancy Threshold.\n "
"Please, contact a user with rights to perform "
"this action."
)
)
def _action_done(self):
for inventory in self:
if inventory.over_discrepancy_line_count > 0.0:
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()
return True
else:
inventory._check_group_inventory_validation_always()
return super(StockInventory, self)._action_done()
def action_force_done(self):
return super(StockInventory, self)._action_done()

View File

@@ -1,67 +0,0 @@
# Copyright 2017-21 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class StockInventoryLine(models.Model):
_inherit = "stock.inventory.line"
discrepancy_qty = fields.Float(
string="Discrepancy",
compute="_compute_discrepancy",
help="The difference between the actual qty counted and the "
"theoretical quantity on hand.",
digits="Product Unit of Measure",
default=0,
compute_sudo=True,
)
discrepancy_percent = fields.Float(
string="Discrepancy percent (%)",
compute="_compute_discrepancy",
digits=(3, 2),
help="The discrepancy expressed in percent with theoretical quantity "
"as basis",
group_operator="avg",
store=True,
compute_sudo=True,
)
discrepancy_threshold = fields.Float(
string="Threshold (%)",
digits=(3, 2),
help="Maximum Discrepancy Rate Threshold",
compute="_compute_discrepancy_threshold",
)
has_over_discrepancy = fields.Boolean(
compute="_compute_has_over_discrepancy",
)
@api.depends("theoretical_qty", "product_qty")
def _compute_discrepancy(self):
for line in self:
line.discrepancy_qty = line.product_qty - line.theoretical_qty
if line.theoretical_qty:
line.discrepancy_percent = 100 * abs(
(line.product_qty - line.theoretical_qty) / line.theoretical_qty
)
elif not line.theoretical_qty and line.product_qty:
line.discrepancy_percent = 100.0
else:
line.discrepancy_percent = 0.0
def _compute_discrepancy_threshold(self):
for line in self:
whs = line.location_id.get_warehouse()
if line.location_id.discrepancy_threshold > 0.0:
line.discrepancy_threshold = line.location_id.discrepancy_threshold
elif whs.discrepancy_threshold > 0.0:
line.discrepancy_threshold = whs.discrepancy_threshold
else:
line.discrepancy_threshold = False
def _compute_has_over_discrepancy(self):
for rec in self:
rec.has_over_discrepancy = rec._has_over_discrepancy()
def _has_over_discrepancy(self):
return self.discrepancy_percent > self.discrepancy_threshold > 0

View File

@@ -0,0 +1,68 @@
# Copyright 2023 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class StockQuant(models.Model):
_inherit = "stock.quant"
discrepancy_percent = fields.Float(
string="Discrepancy percent (%)",
compute="_compute_discrepancy",
digits=(3, 2),
help="The discrepancy expressed in percent with theoretical quantity "
"as basis",
group_operator="avg",
store=True,
compute_sudo=True,
)
discrepancy_threshold = fields.Float(
string="Threshold (%)",
digits=(3, 2),
help="Maximum Discrepancy Rate Threshold",
compute="_compute_discrepancy_threshold",
)
has_over_discrepancy = fields.Boolean(
compute="_compute_has_over_discrepancy",
)
@api.depends("quantity", "inventory_quantity")
def _compute_discrepancy(self):
for quant in self:
if not quant.quantity or not quant.inventory_quantity_set:
quant.discrepancy_percent = 0
else:
quant.discrepancy_percent = abs(
100 * (quant.inventory_diff_quantity) / quant.quantity
)
def _compute_discrepancy_threshold(self):
for quant in self:
whs = quant.location_id.warehouse_id
if quant.location_id.discrepancy_threshold > 0.0:
quant.discrepancy_threshold = quant.location_id.discrepancy_threshold
elif whs.discrepancy_threshold > 0.0:
quant.discrepancy_threshold = whs.discrepancy_threshold
else:
quant.discrepancy_threshold = False
def _compute_has_over_discrepancy(self):
for rec in self:
rec.has_over_discrepancy = (
rec.discrepancy_percent > rec.discrepancy_threshold
)
def action_apply_inventory(self):
if self.env.context.get("skip_exceeded_discrepancy", False):
return super().action_apply_inventory()
over_discrepancy = self.filtered(lambda r: r.has_over_discrepancy)
if over_discrepancy:
action = self.env["ir.actions.act_window"]._for_xml_id(
"stock_inventory_discrepancy.confirm_discrepancy_action"
)
action["context"] = dict(
self._context.copy(),
discrepancy_quant_ids=over_discrepancy.ids,
)
return action
return super().action_apply_inventory()

View File

@@ -2,3 +2,6 @@
* Andreas Dian Sukarno Putro <andreasdian777@gmail.com>
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
* `Tecnativa <https://www.tecnativa.com>`_:
* Ernesto Tejeda

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_confirm_discrepancy_wiz,confirm_discrepancy_wiz,model_confirm_discrepancy_wiz,stock.group_stock_user,1,1,1,0
access_user_adjustment_name,user.stock.inventory.adjustment.name,stock.model_stock_inventory_adjustment_name,stock.group_stock_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_confirm_discrepancy_wiz confirm_discrepancy_wiz model_confirm_discrepancy_wiz stock.group_stock_user 1 1 1 0
3 access_user_adjustment_name user.stock.inventory.adjustment.name stock.model_stock_inventory_adjustment_name stock.group_stock_user 1 1 1 0

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017-2020 ForgeFlow S.L.
<!-- Copyright 2023 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo noupdate="1">
<record id="group_stock_inventory_validation" model="res.groups">
<field name="name">Validate Inventory Adjustments Under Threshold</field>
<field name="name">Validate Inventory Adjustments</field>
<field name="category_id" ref="base.module_category_usability" />
</record>
<record id="group_stock_inventory_validation_always" model="res.groups">
<field name="name">Validate All inventory Adjustments</field>
<field name="name">Validate Inventory Adjustments excceded threshold</field>
<field name="category_id" ref="base.module_category_usability" />
<field
name="implied_ids"
@@ -15,12 +15,6 @@
/>
<field name="users" eval="[(4, ref('base.user_root'))]" />
</record>
<record model="res.groups" id="stock.group_stock_user">
<field
name="implied_ids"
eval="[(4, ref('stock_inventory_discrepancy.group_stock_inventory_validation'))]"
/>
</record>
<record model="res.groups" id="stock.group_stock_manager">
<field
name="implied_ids"

View File

@@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Stock Inventory Discrepancy</title>
<style type="text/css">
@@ -367,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_inventory_discrepancy"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_inventory_discrepancy"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/153/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/15.0/stock_inventory_discrepancy"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-15-0/stock-logistics-warehouse-15-0-stock_inventory_discrepancy"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runboat.odoo-community.org/webui/builds.html?repo=OCA/stock-logistics-warehouse&amp;target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Adds the capability to show the discrepancy of every line in an inventory and
to block the inventory validation (setting it as Pending to Approve) when the
discrepancy is greater than an user defined threshold.</p>
@@ -416,7 +416,7 @@ validation of an inventory pending to approve.</li>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_inventory_discrepancy%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_inventory_discrepancy%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
@@ -434,6 +434,10 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<li>Andreas Dian Sukarno Putro &lt;<a class="reference external" href="mailto:andreasdian777&#64;gmail.com">andreasdian777&#64;gmail.com</a>&gt;</li>
<li>Bhavesh Odedra &lt;<a class="reference external" href="mailto:bodedra&#64;opensourceintegrators.com">bodedra&#64;opensourceintegrators.com</a>&gt;</li>
<li>Héctor Villarreal &lt;<a class="reference external" href="mailto:hector.villarreal&#64;forgeflow.com">hector.villarreal&#64;forgeflow.com</a>&gt;</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Ernesto Tejeda</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
@@ -443,7 +447,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_inventory_discrepancy">OCA/stock-logistics-warehouse</a> project on GitHub.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/15.0/stock_inventory_discrepancy">OCA/stock-logistics-warehouse</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>

View File

@@ -1,44 +0,0 @@
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 (ptitle, pmessage, sticky, className) {
var self = this;
var title = ptitle;
var message = pmessage;
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,
});
});
}
},
});
});

View File

@@ -3,17 +3,17 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from odoo.tests.common import TransactionCase, tagged
@tagged("post_install", "-at_install")
class TestInventoryDiscrepancy(TransactionCase):
def setUp(self, *args, **kwargs):
super(TestInventoryDiscrepancy, self).setUp(*args, **kwargs)
self.obj_wh = self.env["stock.warehouse"]
def setUp(self):
super().setUp()
self.obj_location = self.env["stock.location"]
self.obj_inventory = self.env["stock.inventory"]
self.obj_product = self.env["product.product"]
self.obj_warehouse = self.env["stock.warehouse"]
self.obj_quant = self.env["stock.quant"]
self.product1 = self.obj_product.create(
{"name": "Test Product 1", "type": "product", "default_code": "PROD1"}
@@ -22,10 +22,10 @@ class TestInventoryDiscrepancy(TransactionCase):
{"name": "Test Product 2", "type": "product", "default_code": "PROD2"}
)
self.test_loc = self.obj_location.create(
{"name": "Test Location", "usage": "internal", "discrepancy_threshold": 0.1}
{"name": "Test Location", "usage": "internal", "discrepancy_threshold": 10}
)
self.test_wh = self.obj_warehouse.create(
{"name": "Test WH", "code": "T", "discrepancy_threshold": 0.2}
{"name": "Test WH", "code": "T", "discrepancy_threshold": 20}
)
self.obj_location._parent_store_compute()
@@ -45,12 +45,15 @@ class TestInventoryDiscrepancy(TransactionCase):
}
)
group_stock_user = self.env.ref("stock.group_stock_user")
group_inventory = self.env.ref(
"stock_inventory_discrepancy.group_stock_inventory_validation"
)
self.user = self.env["res.users"].create(
{
"name": "Test User",
"login": "user",
"email": "test.user@example.com",
"groups_id": [(6, 0, [group_stock_user.id])],
"groups_id": [(6, 0, [group_stock_user.id, group_inventory.id])],
}
)
@@ -72,194 +75,136 @@ class TestInventoryDiscrepancy(TransactionCase):
}
)
starting_inv = self.obj_inventory.create(
self.quant_line1 = self.obj_quant.with_context(inventory_mode=True).create(
{
"name": "Starting inventory",
"line_ids": [
(
0,
0,
{
"product_id": self.product1.id,
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
"product_qty": 2.0,
"location_id": self.test_loc.id,
},
),
(
0,
0,
{
"product_id": self.product2.id,
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
"product_qty": 4.0,
"location_id": self.test_loc.id,
},
),
],
"product_id": self.product1.id,
"quantity": 2.0,
"location_id": self.test_loc.id,
}
)
self.quant_line2 = self.obj_quant.with_context(inventory_mode=True).create(
{
"product_id": self.product2.id,
"quantity": 4.0,
"location_id": self.test_loc.id,
}
)
starting_inv.action_force_done()
def test_compute_discrepancy(self):
"""Tests if the discrepancy is correctly computed."""
inventory = self.obj_inventory.create(
{
"name": "Test Discrepancy Computation",
"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,
},
),
(
0,
0,
{
"product_id": self.product2.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.line_ids[0].discrepancy_qty,
1.0,
"Wrong Discrepancy qty computation.",
)
self.assertEqual(
inventory.line_ids[1].discrepancy_qty,
-1.0,
"Wrong Discrepancy qty computation.",
)
# starting_inv = self.obj_inventory.create(
# {
# "name": "Starting inventory",
# "line_ids": [
# (
# 0,
# 0,
# {
# "product_id": self.product1.id,
# "product_uom_id": self.env.ref("uom.product_uom_unit").id,
# "product_qty": 2.0,
# "location_id": self.test_loc.id,
# },
# ),
# (
# 0,
# 0,
# {
# "product_id": self.product2.id,
# "product_uom_id": self.env.ref("uom.product_uom_unit").id,
# "product_qty": 4.0,
# "location_id": self.test_loc.id,
# },
# ),
# ],
# }
# )
# starting_inv.action_force_done()
def test_discrepancy_validation(self):
"""Tests the new workflow"""
inventory = self.obj_inventory.create(
# quant_line1 is over discrepancy but quant_line2 is not
self.quant_line1.write(
{
"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,
},
)
],
"inventory_quantity": 3.0,
"inventory_quantity_set": True,
}
)
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).action_start()
inventory.with_user(self.user).action_validate()
self.assertTrue(inventory.line_ids.has_over_discrepancy)
self.assertEqual(
inventory.over_discrepancy_line_count,
1,
"Computation of over-discrepancies failed.",
)
self.assertEqual(
inventory.state,
"pending",
"Inventory Adjustment not changing to Pending to " "Approve.",
)
inventory.with_user(self.manager).action_force_done()
self.assertEqual(
inventory.state,
"done",
"Forcing the validation of the inventory adjustment "
"not working properly.",
self.quant_line1._compute_discrepancy_threshold()
self.assertEqual(self.quant_line1.discrepancy_threshold, 10)
self.assertEqual(self.quant_line1.discrepancy_percent, 50)
self.assertTrue(self.quant_line1.has_over_discrepancy)
self.quant_line2.inventory_quantity = 4.1
self.quant_line2._compute_discrepancy_threshold()
self.assertEqual(self.quant_line1.discrepancy_threshold, 10)
self.assertEqual(self.quant_line2.discrepancy_percent, 2.5)
self.assertFalse(self.quant_line2.has_over_discrepancy)
# Select all quants and try to apply the quantity adjustment
all_quants = self.quant_line1 | self.quant_line2
action_dic = all_quants.with_user(self.user).action_apply_inventory()
model_wiz = action_dic["res_model"]
wiz = (
self.env[model_wiz]
.with_user(self.user)
.with_context(
action_dic["context"],
active_model="stock.quant",
active_ids=all_quants.ids,
)
.create({})
)
# Apply the wizard with a stock user will get an error
self.assertEqual(wiz.discrepancy_quant_ids, self.quant_line1)
with self.assertRaises(UserError):
wiz.button_apply()
# Apply the wizard with a stock manager will apply the adjustment
wiz.with_user(self.manager).button_apply()
self.assertEqual(self.quant_line1.quantity, 3)
self.assertEqual(self.quant_line2.quantity, 4.1)
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.quant_line1.inventory_quantity = 3.0
self.quant_line1._compute_discrepancy_threshold()
self.assertEqual(self.quant_line1.discrepancy_threshold, 10)
self.assertEqual(self.quant_line1.discrepancy_percent, 50)
self.assertTrue(self.quant_line1.has_over_discrepancy)
self.quant_line2.inventory_quantity = 4.1
self.quant_line2._compute_discrepancy_threshold()
self.assertEqual(self.quant_line1.discrepancy_threshold, 10)
self.assertEqual(self.quant_line2.discrepancy_percent, 2.5)
self.assertFalse(self.quant_line2.has_over_discrepancy)
# Select all quants and try to apply the quantity adjustment
all_quants = self.quant_line1 | self.quant_line2
action_dic = all_quants.with_user(self.user).action_apply_inventory()
model_wiz = action_dic["res_model"]
wiz = (
self.env[model_wiz]
.with_user(self.user)
.with_context(
action_dic["context"],
active_model="stock.quant",
active_ids=all_quants.ids,
)
.create({})
)
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.
# Apply the wizard with a stock user will get an error
self.assertEqual(wiz.discrepancy_quant_ids, self.quant_line1)
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",
)
wiz.button_apply()
# Apply the wizard with a stock manager will apply the adjustment
wiz.with_user(self.user_2).button_apply()
self.assertEqual(self.quant_line1.quantity, 3)
self.assertEqual(self.quant_line2.quantity, 4.1)
def test_warehouse_threshold(self):
"""Tests the behaviour if the threshold is set on the WH."""
inventory = self.obj_inventory.create(
quant_other_loc = self.obj_quant.with_context(inventory_mode=True).create(
{
"name": "Test Threshold Defined in WH",
"location_ids": [(4, self.test_wh.view_location_id.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_wh.lot_stock_id.id,
},
)
],
"product_id": self.product1.id,
"inventory_quantity": 3.0,
"location_id": self.test_wh.lot_stock_id.id,
}
)
self.assertEqual(
inventory.line_ids.discrepancy_threshold,
0.2,
"Threshold wrongly computed in Inventory Line.",
)
self.assertEqual(quant_other_loc.discrepancy_threshold, 20)
def test_propagate_discrepancy_threshold(self):
view_test_loc = self.obj_location.create(

View File

@@ -1,15 +0,0 @@
<?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"
/>
</xpath>
</template>
</odoo>

View File

@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017-2020 ForgeFlow S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_inventory_form" model="ir.ui.view">
<field name="name">Inventory form view - discrepancy extension </field>
<field name="model">stock.inventory</field>
<field name="inherit_id" ref="stock.view_inventory_form" />
<field name="arch" type="xml">
<field name="state" position="attributes">
<attribute name="statusbar_visible">
draft,confirm,pending,done
</attribute>
<attribute name="statusbar_colors">
{"pending":"red"}
</attribute>
</field>
<xpath expr="//button[@name='action_validate']" position="attributes">
<attribute
name="groups"
>stock_inventory_discrepancy.group_stock_inventory_validation</attribute>
</xpath>
<xpath expr="//button[@name='action_validate']" position="after">
<button
name="action_force_done"
string="Force Validation"
type="object"
class="oe_highlight"
groups="stock_inventory_discrepancy.group_stock_inventory_validation_always"
attrs="{'invisible': ['|',('state', '!=', 'pending'),('over_discrepancy_line_count', '=', 0)]}"
/>
</xpath>
<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>
</field>
</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_tree" />
<field name="arch" type="xml">
<field name="product_qty" position="after">
<field name="discrepancy_qty" optional="show" />
<field name="discrepancy_percent" optional="show" />
<field name="discrepancy_threshold" optional="show" />
<field name="has_over_discrepancy" invisible="1" />
</field>
<xpath expr="//tree" position="attributes">
<attribute
name="decoration-danger"
>theoretical_qty &lt; 0 or has_over_discrepancy</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017-2020 ForgeFlow S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_stock_quant_tree_inventory_editable" model="ir.ui.view">
<field name="model">stock.quant</field>
<field name="inherit_id" ref="stock.view_stock_quant_tree_inventory_editable" />
<field name="arch" type="xml">
<button
name="stock.action_stock_inventory_adjustement_name"
position="attributes"
>
<attribute
name="groups"
>stock_inventory_discrepancy.group_stock_inventory_validation</attribute>
</button>
<button name="action_apply_inventory" position="attributes">
<attribute
name="groups"
>stock_inventory_discrepancy.group_stock_inventory_validation</attribute>
</button>
<field name="inventory_diff_quantity" position="after">
<field
name="discrepancy_percent"
attrs="{'invisible': ['|', ('inventory_quantity_set', '=', False), ('quantity', '=', False)]}"
decoration-bf="inventory_quantity_set and has_over_discrepancy"
optional="show"
/>
<field name="discrepancy_threshold" optional="show" />
<field name="has_over_discrepancy" invisible="1" />
</field>
<xpath expr="//tree" position="attributes">
<attribute
name="decoration-danger"
>inventory_quantity_set and (inventory_quantity &lt; 0 or has_over_discrepancy)</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import confirm_discrepancy_wiz

View File

@@ -0,0 +1,41 @@
# Copyright 2023 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command, _, fields, models
from odoo.exceptions import UserError
class ConfirmDiscrepancyWiz(models.TransientModel):
_name = "confirm.discrepancy.wiz"
_description = "Confim discrepancy wizard"
def _default_discrepancy_quant_ids(self):
return [
Command.set(
self.env["stock.quant"]
.browse(self.env.context.get("discrepancy_quant_ids"))
.ids
)
]
discrepancy_quant_ids = fields.Many2many(
comodel_name="stock.quant",
readonly=True,
default=_default_discrepancy_quant_ids,
)
def button_apply(self):
self.ensure_one()
if not self.user_has_groups(
"stock_inventory_discrepancy.group_stock_inventory_validation_always"
):
raise UserError(
_(
"You cannot apply inventory adjustments "
"if there are products that exceed the discrepancy threshold. "
"Only users with rights to apply them can proceed."
)
)
self.env["stock.quant"].browse(self.env.context.get("active_ids")).with_context(
skip_exceeded_discrepancy=True
).action_apply_inventory()

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="stock_quant_confirm_discrepancy" model="ir.ui.view">
<field name="model">stock.quant</field>
<field name="inherit_id" ref="stock.view_stock_quant_tree_inventory_editable" />
<field name="mode">primary</field>
<field name="priority">999</field>
<field name="arch" type="xml">
<button name="action_apply_inventory" position="attributes">
<attribute name="attrs">{'invisible': True}</attribute>
</button>
<button name="action_set_inventory_quantity_to_zero" position="attributes">
<attribute name="attrs">{'invisible': True}</attribute>
</button>
</field>
</record>
<record id="confirm_discrepancy_wizard" model="ir.ui.view">
<field name="name">confirm.discrepancy.wiz.form</field>
<field name="model">confirm.discrepancy.wiz</field>
<field name="arch" type="xml">
<form string="Dates does not match">
<p
groups="!stock_inventory_discrepancy.group_stock_inventory_validation_always"
>
You cannot confirm the inventory adjustment,
the following products have exceeded the discrepancy threshold.
Only users with permissions to apply them can proceed.
</p>
<p
groups="stock_inventory_discrepancy.group_stock_inventory_validation_always"
>
The following products have exceeded the discrepancy threshold.
Are you sure you want to proceed?
</p>
<field
name="discrepancy_quant_ids"
context="{'tree_view_ref':'stock_inventory_discrepancy.stock_quant_confirm_discrepancy'}"
options="{'no_create': True}"
/>
<footer>
<button
string="Apply"
class="oe_highlight"
name="button_apply"
type="object"
groups="stock_inventory_discrepancy.group_stock_inventory_validation_always"
/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="confirm_discrepancy_action" model="ir.actions.act_window">
<field name="name">Confirm discrepancy</field>
<field name="res_model">confirm.discrepancy.wiz</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>