mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] : black, isort, prettier
This commit is contained in:
@@ -0,0 +1 @@
|
||||
../../../../stock_account_inventory_discrepancy
|
||||
6
setup/stock_account_inventory_discrepancy/setup.py
Normal file
6
setup/stock_account_inventory_discrepancy/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
@@ -3,11 +3,10 @@
|
||||
{
|
||||
"name": "Stock Account Inventory Discrepancy",
|
||||
"summary": "Adds the capability to show the value discrepancy of every "
|
||||
"line in an inventory and to block the inventory validation "
|
||||
"when the discrepancy is over a user defined threshold.",
|
||||
"line in an inventory and to block the inventory validation "
|
||||
"when the discrepancy is over a user defined threshold.",
|
||||
"version": "12.0.1.0.0",
|
||||
"author": "ForgeFlow, "
|
||||
"Odoo Community Association (OCA)",
|
||||
"author": "ForgeFlow, " "Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||
"category": "Warehouse",
|
||||
"depends": ["stock_inventory_discrepancy"],
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo import api, fields, models
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
|
||||
class StockInventoryLine(models.Model):
|
||||
_inherit = "stock.inventory.line"
|
||||
@@ -13,13 +14,16 @@ class StockInventoryLine(models.Model):
|
||||
compute="_compute_discrepancy_amount",
|
||||
currency_field="company_currency_id",
|
||||
help="The difference between the actual qty counted and the "
|
||||
"theoretical quantity on hand expressed in the cost amount.",
|
||||
digits=dp.get_precision("Product Unit of Measure"), default=0)
|
||||
"theoretical quantity on hand expressed in the cost amount.",
|
||||
digits=dp.get_precision("Product Unit of Measure"),
|
||||
default=0,
|
||||
)
|
||||
discrepancy_amount_threshold = fields.Monetary(
|
||||
string="Amount Threshold",
|
||||
currency_field="company_currency_id",
|
||||
help="Maximum Discrepancy Amount Threshold",
|
||||
compute="_compute_discrepancy_amount_threshold")
|
||||
compute="_compute_discrepancy_amount_threshold",
|
||||
)
|
||||
company_currency_id = fields.Many2one(
|
||||
string="Company Currency",
|
||||
comodel_name="res.currency",
|
||||
@@ -40,16 +44,17 @@ class StockInventoryLine(models.Model):
|
||||
for line in self:
|
||||
whs = line.location_id.get_warehouse()
|
||||
if line.location_id.discrepancy_amount_threshold > 0.0:
|
||||
line.discrepancy_amount_threshold = line.location_id.\
|
||||
discrepancy_amount_threshold
|
||||
line.discrepancy_amount_threshold = (
|
||||
line.location_id.discrepancy_amount_threshold
|
||||
)
|
||||
elif whs.discrepancy_amount_threshold > 0.0:
|
||||
line.discrepancy_amount_threshold = \
|
||||
whs.discrepancy_amount_threshold
|
||||
line.discrepancy_amount_threshold = whs.discrepancy_amount_threshold
|
||||
else:
|
||||
line.discrepancy_amount_threshold = False
|
||||
|
||||
@api.multi
|
||||
def _has_over_discrepancy(self):
|
||||
res = super()._has_over_discrepancy()
|
||||
return res or abs(
|
||||
self.discrepancy_amount) > self.discrepancy_amount_threshold > 0
|
||||
return (
|
||||
res or abs(self.discrepancy_amount) > self.discrepancy_amount_threshold > 0
|
||||
)
|
||||
|
||||
@@ -11,11 +11,9 @@ class StockLocation(models.Model):
|
||||
string="Maximum Discrepancy Amount Threshold",
|
||||
currency_field="discrepancy_amount_threshold_currency_id",
|
||||
help="Maximum Discrepancy Amount allowed for any product when doing "
|
||||
"an Inventory Adjustment. Thresholds defined in Locations have "
|
||||
"preference over Warehouse's ones.",
|
||||
"an Inventory Adjustment. Thresholds defined in Locations have "
|
||||
"preference over Warehouse's ones.",
|
||||
)
|
||||
discrepancy_amount_threshold_currency_id = fields.Many2one(
|
||||
comodel_name="res.currency",
|
||||
related="company_id.currency_id",
|
||||
readonly=True,
|
||||
comodel_name="res.currency", related="company_id.currency_id", readonly=True,
|
||||
)
|
||||
|
||||
@@ -11,11 +11,9 @@ class StockWarehouse(models.Model):
|
||||
string="Maximum Discrepancy Amount Threshold",
|
||||
currency_field="discrepancy_amount_threshold_currency_id",
|
||||
help="Maximum Discrepancy Amount allowed for any product when doing "
|
||||
"an Inventory Adjustment. Threshold defined in involved Location "
|
||||
"has preference.",
|
||||
"an Inventory Adjustment. Threshold defined in involved Location "
|
||||
"has preference.",
|
||||
)
|
||||
discrepancy_amount_threshold_currency_id = fields.Many2one(
|
||||
comodel_name="res.currency",
|
||||
related="company_id.currency_id",
|
||||
readonly=True,
|
||||
comodel_name="res.currency", related="company_id.currency_id", readonly=True,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestInventoryDiscrepancy(TransactionCase):
|
||||
@@ -15,114 +15,143 @@ class TestInventoryDiscrepancy(TransactionCase):
|
||||
self.obj_warehouse = self.env["stock.warehouse"]
|
||||
self.obj_upd_qty_wizard = self.env["stock.change.product.qty"]
|
||||
|
||||
self.product1 = self.obj_product.create({
|
||||
"name": "Test Product 1",
|
||||
"type": "product",
|
||||
"default_code": "PROD1",
|
||||
"standard_price": 110.0,
|
||||
})
|
||||
self.product2 = self.obj_product.create({
|
||||
"name": "Test Product 2",
|
||||
"type": "product",
|
||||
"default_code": "PROD2",
|
||||
"standard_price": 150.0,
|
||||
})
|
||||
self.test_loc = self.obj_location.create({
|
||||
"name": "Test Location",
|
||||
"usage": "internal",
|
||||
"discrepancy_amount_threshold": 100,
|
||||
})
|
||||
self.test_wh = self.obj_warehouse.create({
|
||||
"name": "Test WH",
|
||||
"code": "T",
|
||||
"discrepancy_amount_threshold": 300,
|
||||
})
|
||||
self.product1 = self.obj_product.create(
|
||||
{
|
||||
"name": "Test Product 1",
|
||||
"type": "product",
|
||||
"default_code": "PROD1",
|
||||
"standard_price": 110.0,
|
||||
}
|
||||
)
|
||||
self.product2 = self.obj_product.create(
|
||||
{
|
||||
"name": "Test Product 2",
|
||||
"type": "product",
|
||||
"default_code": "PROD2",
|
||||
"standard_price": 150.0,
|
||||
}
|
||||
)
|
||||
self.test_loc = self.obj_location.create(
|
||||
{
|
||||
"name": "Test Location",
|
||||
"usage": "internal",
|
||||
"discrepancy_amount_threshold": 100,
|
||||
}
|
||||
)
|
||||
self.test_wh = self.obj_warehouse.create(
|
||||
{"name": "Test WH", "code": "T", "discrepancy_amount_threshold": 300,}
|
||||
)
|
||||
self.obj_location._parent_store_compute()
|
||||
|
||||
# Create Stock manager able to force validation on inventories.
|
||||
group_stock_man = self.env.ref("stock.group_stock_manager")
|
||||
group_inventory_all = self.env.ref(
|
||||
"stock_inventory_discrepancy."
|
||||
"group_stock_inventory_validation_always")
|
||||
self.manager = self.env["res.users"].create({
|
||||
"name": "Test Manager",
|
||||
"login": "manager",
|
||||
"email": "test.manager@example.com",
|
||||
"groups_id": [(6, 0, [group_stock_man.id, group_inventory_all.id])]
|
||||
})
|
||||
"stock_inventory_discrepancy." "group_stock_inventory_validation_always"
|
||||
)
|
||||
self.manager = self.env["res.users"].create(
|
||||
{
|
||||
"name": "Test Manager",
|
||||
"login": "manager",
|
||||
"email": "test.manager@example.com",
|
||||
"groups_id": [(6, 0, [group_stock_man.id, group_inventory_all.id])],
|
||||
}
|
||||
)
|
||||
group_stock_user = self.env.ref("stock.group_stock_user")
|
||||
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])]
|
||||
})
|
||||
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])],
|
||||
}
|
||||
)
|
||||
|
||||
starting_inv = self.obj_inventory.create({
|
||||
"name": "Starting inventory",
|
||||
"filter": "product",
|
||||
"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 = self.obj_inventory.create(
|
||||
{
|
||||
"name": "Starting inventory",
|
||||
"filter": "product",
|
||||
"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_compute_discrepancy(self):
|
||||
"""Tests if the amount discrepancy is correctly computed.
|
||||
"""
|
||||
inventory = self.obj_inventory.create({
|
||||
"name": "Test Discrepancy Computation",
|
||||
"location_id": self.test_loc.id,
|
||||
"filter": "none",
|
||||
"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,
|
||||
})
|
||||
],
|
||||
})
|
||||
inventory = self.obj_inventory.create(
|
||||
{
|
||||
"name": "Test Discrepancy Computation",
|
||||
"location_id": self.test_loc.id,
|
||||
"filter": "none",
|
||||
"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_amount, 110.0)
|
||||
self.assertEqual(inventory.line_ids[1].discrepancy_amount, - 150.0)
|
||||
self.assertEqual(inventory.line_ids[1].discrepancy_amount, -150.0)
|
||||
|
||||
def test_amount_discrepancy_validation(self):
|
||||
"""Tests the workflow with amount threshold."""
|
||||
inventory = self.obj_inventory.create({
|
||||
"name": "Test Forcing Validation Method",
|
||||
"location_id": self.test_loc.id,
|
||||
"filter": "none",
|
||||
"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 = self.obj_inventory.create(
|
||||
{
|
||||
"name": "Test Forcing Validation Method",
|
||||
"location_id": self.test_loc.id,
|
||||
"filter": "none",
|
||||
"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")
|
||||
self.assertEqual(inventory.line_ids.discrepancy_amount_threshold, 100)
|
||||
self.assertTrue(inventory.line_ids[0].has_over_discrepancy)
|
||||
@@ -132,31 +161,38 @@ class TestInventoryDiscrepancy(TransactionCase):
|
||||
|
||||
def test_warehouse_amount_threshold(self):
|
||||
"""Tests the behaviour if the threshold is set on the WH."""
|
||||
inventory = self.obj_inventory.create({
|
||||
"name": "Test Threshold Defined in WH",
|
||||
"location_id": self.test_wh.view_location_id.id,
|
||||
"filter": "none",
|
||||
"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,
|
||||
}),
|
||||
],
|
||||
})
|
||||
inventory = self.obj_inventory.create(
|
||||
{
|
||||
"name": "Test Threshold Defined in WH",
|
||||
"location_id": self.test_wh.view_location_id.id,
|
||||
"filter": "none",
|
||||
"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,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(inventory.line_ids.discrepancy_amount_threshold, 300)
|
||||
|
||||
def test_update_qty_user_error_amount(self):
|
||||
"""Test if a user error raises when a stock user tries to update the
|
||||
qty for a product and the correction is a discrepancy over the
|
||||
threshold."""
|
||||
upd_qty = self.obj_upd_qty_wizard.sudo(self.user).create({
|
||||
"product_id": self.product1.id,
|
||||
"product_tmpl_id": self.product1.product_tmpl_id.id,
|
||||
"new_quantity": 10.0,
|
||||
"location_id": self.test_loc.id,
|
||||
})
|
||||
upd_qty = self.obj_upd_qty_wizard.sudo(self.user).create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"product_tmpl_id": self.product1.product_tmpl_id.id,
|
||||
"new_quantity": 10.0,
|
||||
"location_id": self.test_loc.id,
|
||||
}
|
||||
)
|
||||
with self.assertRaises(UserError):
|
||||
upd_qty.change_product_qty()
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
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">stock.inventory.form - stock_account_inventory_discrepancy</field>
|
||||
<field
|
||||
name="name"
|
||||
>stock.inventory.form - stock_account_inventory_discrepancy</field>
|
||||
<field name="model">stock.inventory</field>
|
||||
<field name="inherit_id" ref="stock.view_inventory_form"/>
|
||||
<field name="inherit_id" ref="stock.view_inventory_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='product_qty']"
|
||||
position="after">
|
||||
<field name="discrepancy_amount"/>
|
||||
<field name="discrepancy_amount_threshold"/>
|
||||
<field name="company_currency_id" invisible="1" readonly="1"/>
|
||||
<xpath
|
||||
expr="//field[@name='line_ids']/tree/field[@name='product_qty']"
|
||||
position="after"
|
||||
>
|
||||
<field name="discrepancy_amount" />
|
||||
<field name="discrepancy_amount_threshold" />
|
||||
<field name="company_currency_id" invisible="1" readonly="1" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_location_form" model="ir.ui.view">
|
||||
<field name="name">stock.location.form - stock_account_inventory_discrepancy</field>
|
||||
<field
|
||||
name="name"
|
||||
>stock.location.form - stock_account_inventory_discrepancy</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="partner_id" position="after">
|
||||
<label for="discrepancy_amount_threshold"/>
|
||||
<label for="discrepancy_amount_threshold" />
|
||||
<div>
|
||||
<field name="discrepancy_amount_threshold" class="oe_inline"/>
|
||||
<field name="discrepancy_amount_threshold_currency_id" invisible="1"/>
|
||||
<field name="discrepancy_amount_threshold" class="oe_inline" />
|
||||
<field
|
||||
name="discrepancy_amount_threshold_currency_id"
|
||||
invisible="1"
|
||||
/>
|
||||
</div>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_warehouse" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse - stock_account_inventory_discrepancy</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="inherit_id" ref="stock.view_warehouse"/>
|
||||
<field name="inherit_id" ref="stock.view_warehouse" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<label for="discrepancy_amount_threshold"/>
|
||||
<label for="discrepancy_amount_threshold" />
|
||||
<div>
|
||||
<field name="discrepancy_amount_threshold"/>
|
||||
<field name="discrepancy_amount_threshold_currency_id" invisible="1"/>
|
||||
<field name="discrepancy_amount_threshold" />
|
||||
<field
|
||||
name="discrepancy_amount_threshold_currency_id"
|
||||
invisible="1"
|
||||
/>
|
||||
</div>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user