[IMP] stock_available: black, isort

This commit is contained in:
ps-tubtim
2019-12-19 13:26:19 +07:00
committed by Florian da Costa
parent c7a37b0216
commit dd90188d8a
5 changed files with 170 additions and 144 deletions

View File

@@ -3,18 +3,18 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Stock available to promise',
'version': '12.0.1.0.1',
'author': 'Numérigraphe, Sodexis, Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/stock-logistics-warehouse',
'development_status': 'Production/Stable',
'category': 'Warehouse',
'depends': ['stock'],
'license': 'AGPL-3',
'data': [
'views/product_template_view.xml',
'views/product_product_view.xml',
'views/res_config_settings_views.xml',
"name": "Stock available to promise",
"version": "13.0.1.0.0",
"author": "Numérigraphe, Sodexis, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"development_status": "Production/Stable",
"category": "Warehouse",
"depends": ["stock"],
"license": "AGPL-3",
"data": [
"views/product_template_view.xml",
"views/product_product_view.xml",
"views/res_config_settings_views.xml",
],
'installable': True,
"installable": True,
}

View File

@@ -3,6 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo.addons import decimal_precision as dp
from odoo.addons.stock.models.product import OPERATORS
@@ -13,27 +14,28 @@ class ProductProduct(models.Model):
Useful implementations need to be installed through the Settings menu or by
installing one of the modules stock_available_*
"""
_inherit = 'product.product'
_inherit = "product.product"
@api.multi
def _compute_available_quantities_dict(self):
stock_dict = self._compute_quantities_dict(
self._context.get('lot_id'),
self._context.get('owner_id'),
self._context.get('package_id'),
self._context.get('from_date'),
self._context.get('to_date'))
self._context.get("lot_id"),
self._context.get("owner_id"),
self._context.get("package_id"),
self._context.get("from_date"),
self._context.get("to_date"),
)
res = {}
for product in self:
res[product.id] = {
'immediately_usable_qty': stock_dict[product.id][
'virtual_available'],
'potential_qty': 0.0
"immediately_usable_qty": stock_dict[product.id]["virtual_available"],
"potential_qty": 0.0,
}
return res, stock_dict
@api.multi
@api.depends('virtual_available')
@api.depends("virtual_available")
def _compute_available_quantities(self):
res, _ = self._compute_available_quantities_dict()
for product in self:
@@ -42,20 +44,22 @@ class ProductProduct(models.Model):
product[key] = value
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_compute_available_quantities',
digits=dp.get_precision("Product Unit of Measure"),
compute="_compute_available_quantities",
search="_search_immediately_usable_qty",
string='Available to promise',
string="Available to promise",
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs.")
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs.",
)
potential_qty = fields.Float(
compute='_compute_available_quantities',
digits=dp.get_precision('Product Unit of Measure'),
string='Potential',
compute="_compute_available_quantities",
digits=dp.get_precision("Product Unit of Measure"),
string="Potential",
help="Quantity of this Product that could be produced using "
"the materials already at hand.")
"the materials already at hand.",
)
@api.model
def _search_immediately_usable_qty(self, operator, value):
@@ -73,4 +77,4 @@ class ProductProduct(models.Model):
for product in products:
if OPERATORS[operator](product.immediately_usable_qty, value):
product_ids.append(product.id)
return [('id', 'in', product_ids)]
return [("id", "in", product_ids)]

View File

@@ -2,17 +2,20 @@
# Copyright 2016 Sodexis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
from odoo.addons import decimal_precision as dp
from odoo.addons.stock.models.product import OPERATORS
class ProductTemplate(models.Model):
_inherit = 'product.template'
_inherit = "product.template"
@api.multi
@api.depends('product_variant_ids.immediately_usable_qty',
'product_variant_ids.potential_qty')
@api.depends(
"product_variant_ids.immediately_usable_qty",
"product_variant_ids.potential_qty",
)
def _compute_available_quantities(self):
res = self._compute_available_quantities_dict()
for product in self:
@@ -23,40 +26,49 @@ class ProductTemplate(models.Model):
@api.multi
def _compute_available_quantities_dict(self):
variants_dict, _ = self.mapped(
'product_variant_ids')._compute_available_quantities_dict()
"product_variant_ids"
)._compute_available_quantities_dict()
res = {}
for template in self:
immediately_usable_qty = sum(
[variants_dict[p.id]["immediately_usable_qty"] -
variants_dict[p.id]["potential_qty"] for p in
template.product_variant_ids])
[
variants_dict[p.id]["immediately_usable_qty"]
- variants_dict[p.id]["potential_qty"]
for p in template.product_variant_ids
]
)
potential_qty = max(
[variants_dict[p.id]["potential_qty"] for p in
template.product_variant_ids] or [0.0])
[
variants_dict[p.id]["potential_qty"]
for p in template.product_variant_ids
]
or [0.0]
)
res[template.id] = {
"immediately_usable_qty": immediately_usable_qty +
potential_qty,
"immediately_usable_qty": immediately_usable_qty + potential_qty,
"potential_qty": potential_qty,
}
return res
immediately_usable_qty = fields.Float(
digits=dp.get_precision('Product Unit of Measure'),
compute='_compute_available_quantities',
digits=dp.get_precision("Product Unit of Measure"),
compute="_compute_available_quantities",
search="_search_immediately_usable_qty",
string='Available to promise',
string="Available to promise",
help="Stock for this Product that can be safely proposed "
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs")
"for sale to Customers.\n"
"The definition of this value can be configured to suit "
"your needs",
)
potential_qty = fields.Float(
compute='_compute_available_quantities',
digits=dp.get_precision('Product Unit of Measure'),
string='Potential',
compute="_compute_available_quantities",
digits=dp.get_precision("Product Unit of Measure"),
string="Potential",
help="Quantity of this Product that could be produced using "
"the materials already at hand. "
"If the product has several variants, this will be the biggest "
"quantity that can be made for a any single variant.")
"the materials already at hand. "
"If the product has several variants, this will be the biggest "
"quantity that can be made for a any single variant.",
)
@api.model
def _search_immediately_usable_qty(self, operator, value):
@@ -74,4 +86,4 @@ class ProductTemplate(models.Model):
for product in products:
if OPERATORS[operator](product.immediately_usable_qty, value):
product_ids.append(product.id)
return [('id', 'in', product_ids)]
return [("id", "in", product_ids)]

View File

@@ -8,62 +8,67 @@ from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
"""Add options to easily install the submodules"""
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
@api.model
def _get_stock_available_mrp_based_on(self):
"""Gets the available languages for the selection."""
pdct_fields = self.env['ir.model.fields'].search(
[('model', '=', 'product.product'),
('ttype', '=', 'float')])
pdct_fields = self.env["ir.model.fields"].search(
[("model", "=", "product.product"), ("ttype", "=", "float")]
)
return [
(field.name, field.field_description)
for field in sorted(pdct_fields, key=lambda f: f.field_description)
]
module_stock_available_immediately = fields.Boolean(
string='Exclude incoming goods',
string="Exclude incoming goods",
help="This will subtract incoming quantities from the quantities "
"available to promise.\n"
"This installs the module stock_available_immediately.")
"available to promise.\n"
"This installs the module stock_available_immediately.",
)
module_stock_available_sale = fields.Boolean(
string='Exclude goods already in sale quotations',
string="Exclude goods already in sale quotations",
help="This will subtract quantities from the sale quotations from "
"the quantities available to promise.\n"
"This installs the modules stock_available_sale.\n"
"If the modules sale and sale_delivery_date are not "
"installed, this will install them too")
"the quantities available to promise.\n"
"This installs the modules stock_available_sale.\n"
"If the modules sale and sale_delivery_date are not "
"installed, this will install them too",
)
module_stock_available_mrp = fields.Boolean(
string='Include the production potential',
string="Include the production potential",
help="This will add the quantities of goods that can be "
"immediately manufactured, to the quantities available to "
"promise.\n"
"This installs the module stock_available_mrp.\n"
"If the module mrp is not installed, this will install it "
"too")
"immediately manufactured, to the quantities available to "
"promise.\n"
"This installs the module stock_available_mrp.\n"
"If the module mrp is not installed, this will install it "
"too",
)
stock_available_mrp_based_on = fields.Selection(
_get_stock_available_mrp_based_on,
string='based on',
string="based on",
help="Choose the field of the product which will be used to compute "
"potential.\nIf empty, Quantity On Hand is used.\n"
"Only the quantity fields have meaning for computing stock",
"potential.\nIf empty, Quantity On Hand is used.\n"
"Only the quantity fields have meaning for computing stock",
)
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
res.update(stock_available_mrp_based_on=self.env[
'ir.config_parameter'].sudo().get_param(
'stock_available_mrp_based_on',
'qty_available')
res.update(
stock_available_mrp_based_on=self.env["ir.config_parameter"]
.sudo()
.get_param("stock_available_mrp_based_on", "qty_available")
)
return res
@api.multi
def set_values(self):
super(ResConfigSettings, self).set_values()
self.env['ir.config_parameter'].sudo().set_param(
'stock_available_mrp_based_on', self.stock_available_mrp_based_on)
self.env["ir.config_parameter"].sudo().set_param(
"stock_available_mrp_based_on", self.stock_available_mrp_based_on
)

View File

@@ -8,71 +8,74 @@ from odoo.tests.common import TransactionCase
class TestStockLogisticsWarehouse(TransactionCase):
def test_res_config(self):
"""Test the config file"""
stock_setting = self.env['res.config.settings'].create({})
stock_setting = self.env["res.config.settings"].create({})
self.assertEquals(
stock_setting.stock_available_mrp_based_on,
'qty_available')
stock_setting.stock_available_mrp_based_on = 'immediately_usable_qty'
self.assertEquals(stock_setting.stock_available_mrp_based_on, "qty_available")
stock_setting.stock_available_mrp_based_on = "immediately_usable_qty"
stock_setting.set_values()
self.assertEquals(
stock_setting.stock_available_mrp_based_on,
'immediately_usable_qty')
stock_setting.stock_available_mrp_based_on, "immediately_usable_qty"
)
def test01_stock_levels(self):
"""checking that immediately_usable_qty actually reflects \
the variations in stock, both on product and template"""
moveObj = self.env['stock.move']
productObj = self.env['product.product']
templateObj = self.env['product.template']
supplier_location = self.env.ref('stock.stock_location_suppliers')
stock_location = self.env.ref('stock.stock_location_stock')
customer_location = self.env.ref('stock.stock_location_customers')
uom_unit = self.env.ref('uom.product_uom_unit')
moveObj = self.env["stock.move"]
productObj = self.env["product.product"]
templateObj = self.env["product.template"]
supplier_location = self.env.ref("stock.stock_location_suppliers")
stock_location = self.env.ref("stock.stock_location_stock")
customer_location = self.env.ref("stock.stock_location_customers")
uom_unit = self.env.ref("uom.product_uom_unit")
# Create product template
templateAB = templateObj.create(
{'name': 'templAB',
'uom_id': uom_unit.id,
})
templateAB = templateObj.create({"name": "templAB", "uom_id": uom_unit.id})
# Create product A and B
productA = productObj.create(
{'name': 'product A',
'standard_price': 1,
'type': 'product',
'uom_id': uom_unit.id,
'default_code': 'A',
'product_tmpl_id': templateAB.id,
})
{
"name": "product A",
"standard_price": 1,
"type": "product",
"uom_id": uom_unit.id,
"default_code": "A",
"product_tmpl_id": templateAB.id,
}
)
productB = productObj.create(
{'name': 'product B',
'standard_price': 1,
'type': 'product',
'uom_id': uom_unit.id,
'default_code': 'B',
'product_tmpl_id': templateAB.id,
})
{
"name": "product B",
"standard_price": 1,
"type": "product",
"uom_id": uom_unit.id,
"default_code": "B",
"product_tmpl_id": templateAB.id,
}
)
# Create a stock move from INCOMING to STOCK
stockMoveInA = moveObj.create(
{'location_id': supplier_location.id,
'location_dest_id': stock_location.id,
'name': 'MOVE INCOMING -> STOCK ',
'product_id': productA.id,
'product_uom': productA.uom_id.id,
'product_uom_qty': 2,
})
{
"location_id": supplier_location.id,
"location_dest_id": stock_location.id,
"name": "MOVE INCOMING -> STOCK ",
"product_id": productA.id,
"product_uom": productA.uom_id.id,
"product_uom_qty": 2,
}
)
stockMoveInB = moveObj.create(
{'location_id': supplier_location.id,
'location_dest_id': stock_location.id,
'name': 'MOVE INCOMING -> STOCK ',
'product_id': productB.id,
'product_uom': productB.uom_id.id,
'product_uom_qty': 3,
})
{
"location_id": supplier_location.id,
"location_dest_id": stock_location.id,
"name": "MOVE INCOMING -> STOCK ",
"product_id": productB.id,
"product_uom": productB.uom_id.id,
"product_uom_qty": 3,
}
)
def compare_product_usable_qty(product, value):
"""
@@ -87,16 +90,16 @@ class TestStockLogisticsWarehouse(TransactionCase):
product.refresh()
self.assertEqual(product.immediately_usable_qty, value)
# Now check search function
domain = [('immediately_usable_qty', '=', value)]
domain = [("immediately_usable_qty", "=", value)]
results = self.env[product._name].search(domain)
self.assertIn(product.id, results.ids)
domain = [('immediately_usable_qty', '!=', value)]
domain = [("immediately_usable_qty", "!=", value)]
results = self.env[product._name].search(domain)
self.assertNotIn(product.id, results.ids)
domain = [('immediately_usable_qty', '>', value-1)]
domain = [("immediately_usable_qty", ">", value - 1)]
results = self.env[product._name].search(domain)
self.assertIn(product.id, results.ids)
domain = [('immediately_usable_qty', '<', value+1)]
domain = [("immediately_usable_qty", "<", value + 1)]
results = self.env[product._name].search(domain)
self.assertIn(product.id, results.ids)
@@ -123,14 +126,16 @@ class TestStockLogisticsWarehouse(TransactionCase):
# Create a stock move from STOCK to CUSTOMER
stockMoveOutA = moveObj.create(
{'location_id': stock_location.id,
'location_dest_id': customer_location.id,
'name': ' STOCK --> CUSTOMER ',
'product_id': productA.id,
'product_uom': productA.uom_id.id,
'product_uom_qty': 1,
'state': 'confirmed',
})
{
"location_id": stock_location.id,
"location_dest_id": customer_location.id,
"name": " STOCK --> CUSTOMER ",
"product_id": productA.id,
"product_uom": productA.uom_id.id,
"product_uom_qty": 1,
"state": "confirmed",
}
)
stockMoveOutA._action_done()
compare_product_usable_qty(productA, 1)