From c3b7a87462cd41654d0e2bd3beb2f0e065ddfb75 Mon Sep 17 00:00:00 2001
From: kir-odoo
Date: Wed, 27 Jul 2022 05:49:08 +0000
Subject: [PATCH] [IMP] stock: improvements in replenishment wizard
With this commit:
========================
- Added a new field `forecasted_qty` in replenishment to display the
forecasted qty based on a selected warehouse in the wizard
- Changed the `route_ids`(m2m) field to `route_id`(m2o) so that
we can apply a specific route for the
replenishment instead of the product's default routes
- Reduced the width of all the fields
- Remove the time from the scheduled date and make the unit field not editable.
- Can see vendor information with info icon field when buy is selected
- Hide the Dropship route from the Route
TaskId : 2579425
closes odoo/odoo#96948
Related: odoo/upgrade#3796
Signed-off-by: William Henrotin (whe)
---
addons/mrp/models/stock_orderpoint.py | 2 +-
addons/mrp_subcontracting/wizard/__init__.py | 1 +
.../wizard/product_replenish.py | 13 +++
.../__init__.py | 1 +
.../wizard/__init__.py | 4 +
.../wizard/product_replenish.py | 13 +++
addons/purchase_stock/__manifest__.py | 3 +-
addons/purchase_stock/models/product.py | 11 +++
addons/purchase_stock/models/stock.py | 2 +-
.../tests/test_replenish_wizard.py | 62 +++++++++-----
.../tests/test_stockvaluation.py | 14 ++--
addons/purchase_stock/wizard/__init__.py | 1 +
.../wizard/product_replenish.py | 71 ++++++++++++++++
.../wizard/product_replenish_views.xml | 22 +++++
.../wizard/stock_replenishment_info.py | 18 ++++
.../wizard/stock_replenishment_info.xml | 3 +-
addons/stock/models/stock_move.py | 2 +-
addons/stock/models/stock_orderpoint.py | 10 ++-
addons/stock/tests/common.py | 6 ++
addons/stock/tests/test_move2.py | 10 ++-
addons/stock/wizard/product_replenish.py | 83 +++++++++++++------
.../stock/wizard/product_replenish_views.xml | 50 ++++++-----
.../stock/wizard/stock_replenishment_info.xml | 2 +-
addons/stock_dropshipping/__init__.py | 3 +-
addons/stock_dropshipping/wizard/__init__.py | 4 +
.../wizard/product_replenish.py | 13 +++
26 files changed, 340 insertions(+), 84 deletions(-)
create mode 100644 addons/mrp_subcontracting/wizard/product_replenish.py
create mode 100644 addons/mrp_subcontracting_dropshipping/wizard/__init__.py
create mode 100644 addons/mrp_subcontracting_dropshipping/wizard/product_replenish.py
create mode 100644 addons/purchase_stock/wizard/product_replenish.py
create mode 100644 addons/purchase_stock/wizard/product_replenish_views.xml
create mode 100644 addons/stock_dropshipping/wizard/__init__.py
create mode 100644 addons/stock_dropshipping/wizard/product_replenish.py
diff --git a/addons/mrp/models/stock_orderpoint.py b/addons/mrp/models/stock_orderpoint.py
index 282210ab871..1550eeee22a 100644
--- a/addons/mrp/models/stock_orderpoint.py
+++ b/addons/mrp/models/stock_orderpoint.py
@@ -20,7 +20,7 @@ class StockWarehouseOrderpoint(models.Model):
self.ensure_one()
domain = [('orderpoint_id', 'in', self.ids)]
if self.env.context.get('written_after'):
- domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
+ domain = AND([domain, [('write_date', '>=', self.env.context.get('written_after'))]])
production = self.env['mrp.production'].search(domain, limit=1)
if production:
action = self.env.ref('mrp.action_mrp_production_form')
diff --git a/addons/mrp_subcontracting/wizard/__init__.py b/addons/mrp_subcontracting/wizard/__init__.py
index 0594ad02b2a..319a4950ae7 100644
--- a/addons/mrp_subcontracting/wizard/__init__.py
+++ b/addons/mrp_subcontracting/wizard/__init__.py
@@ -4,3 +4,4 @@
from . import stock_picking_return
from . import mrp_consumption_warning
from . import change_production_qty
+from . import product_replenish
diff --git a/addons/mrp_subcontracting/wizard/product_replenish.py b/addons/mrp_subcontracting/wizard/product_replenish.py
new file mode 100644
index 00000000000..3162ddb762a
--- /dev/null
+++ b/addons/mrp_subcontracting/wizard/product_replenish.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+from odoo.osv import expression
+
+
+class ProductReplenish(models.TransientModel):
+ _inherit = 'product.replenish'
+
+ def _get_allowed_route_domain(self):
+ domains = super()._get_allowed_route_domain()
+ return expression.AND([domains, [('id', '!=', self.env.ref('mrp_subcontracting.route_resupply_subcontractor_mto', raise_if_not_found=False).id)]])
diff --git a/addons/mrp_subcontracting_dropshipping/__init__.py b/addons/mrp_subcontracting_dropshipping/__init__.py
index dc5e6b693d1..2ae6446f9dc 100644
--- a/addons/mrp_subcontracting_dropshipping/__init__.py
+++ b/addons/mrp_subcontracting_dropshipping/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+from . import wizard
diff --git a/addons/mrp_subcontracting_dropshipping/wizard/__init__.py b/addons/mrp_subcontracting_dropshipping/wizard/__init__.py
new file mode 100644
index 00000000000..6326fb55f39
--- /dev/null
+++ b/addons/mrp_subcontracting_dropshipping/wizard/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import product_replenish
diff --git a/addons/mrp_subcontracting_dropshipping/wizard/product_replenish.py b/addons/mrp_subcontracting_dropshipping/wizard/product_replenish.py
new file mode 100644
index 00000000000..28c98264cd8
--- /dev/null
+++ b/addons/mrp_subcontracting_dropshipping/wizard/product_replenish.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+from odoo.osv import expression
+
+
+class ProductReplenish(models.TransientModel):
+ _inherit = 'product.replenish'
+
+ def _get_allowed_route_domain(self):
+ domains = super()._get_allowed_route_domain()
+ return expression.AND([domains, [('id', '!=', self.env.ref('mrp_subcontracting_dropshipping.route_subcontracting_dropshipping', raise_if_not_found=False).id)]])
diff --git a/addons/purchase_stock/__manifest__.py b/addons/purchase_stock/__manifest__.py
index 47ce2b411a4..16ef01ac165 100644
--- a/addons/purchase_stock/__manifest__.py
+++ b/addons/purchase_stock/__manifest__.py
@@ -23,7 +23,8 @@
'report/purchase_report_views.xml',
'report/purchase_report_templates.xml',
'report/report_stock_rule.xml',
- 'wizard/stock_replenishment_info.xml'
+ 'wizard/stock_replenishment_info.xml',
+ 'wizard/product_replenish_views.xml',
],
'demo': [
'data/purchase_stock_demo.xml',
diff --git a/addons/purchase_stock/models/product.py b/addons/purchase_stock/models/product.py
index 4eacaac340e..025418d3ab2 100644
--- a/addons/purchase_stock/models/product.py
+++ b/addons/purchase_stock/models/product.py
@@ -130,3 +130,14 @@ class SupplierInfo(models.Model):
orderpoint.supplier_id = self
if orderpoint.qty_to_order < self.min_qty:
orderpoint.qty_to_order = self.min_qty
+ if self._context.get('replenish_id'):
+ replenish = self.env['product.replenish'].browse(self._context.get('replenish_id'))
+ replenish.supplier_id = self
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Replenish',
+ 'res_model': 'product.replenish',
+ 'res_id': replenish.id,
+ 'target': 'new',
+ 'view_mode': 'form',
+ }
diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py
index 84af9ffcf32..b77048c64bf 100644
--- a/addons/purchase_stock/models/stock.py
+++ b/addons/purchase_stock/models/stock.py
@@ -152,7 +152,7 @@ class Orderpoint(models.Model):
self.ensure_one()
domain = [('orderpoint_id', 'in', self.ids)]
if self.env.context.get('written_after'):
- domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
+ domain = AND([domain, [('write_date', '>=', self.env.context.get('written_after'))]])
order = self.env['purchase.order.line'].search(domain, limit=1).order_id
if order:
action = self.env.ref('purchase.action_rfq_form')
diff --git a/addons/purchase_stock/tests/test_replenish_wizard.py b/addons/purchase_stock/tests/test_replenish_wizard.py
index 7ed82d33bb9..a60624905cd 100644
--- a/addons/purchase_stock/tests/test_replenish_wizard.py
+++ b/addons/purchase_stock/tests/test_replenish_wizard.py
@@ -43,11 +43,14 @@ class TestReplenishWizard(TestStockCommon):
'quantity': self.product_uom_qty,
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ('partner_id', '=', self.vendor.id)
- ])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
self.assertTrue(last_po_id, 'Purchase Order not found')
order_line = last_po_id.order_line.search([('product_id', '=', self.product1.id)])
self.assertTrue(order_line, 'The product is not in the Purchase Order')
@@ -93,10 +96,14 @@ class TestReplenishWizard(TestStockCommon):
'quantity': 10,
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
self.assertEqual(last_po_id.partner_id, vendor1)
self.assertEqual(last_po_id.order_line.price_unit, 100)
@@ -148,10 +155,14 @@ class TestReplenishWizard(TestStockCommon):
'quantity': 10,
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
self.assertEqual(last_po_id.partner_id, vendor1)
self.assertEqual(last_po_id.order_line.price_unit, 100)
@@ -193,10 +204,15 @@ class TestReplenishWizard(TestStockCommon):
'quantity': 10,
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
+
self.assertEqual(last_po_id.partner_id, vendor2)
def test_chose_supplier_4(self):
@@ -241,10 +257,14 @@ class TestReplenishWizard(TestStockCommon):
'quantity': 10,
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
self.assertEqual(last_po_id.partner_id, vendor1)
self.assertEqual(last_po_id.order_line.price_unit, 60)
diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py
index 81a73034458..8a141046c73 100644
--- a/addons/purchase_stock/tests/test_stockvaluation.py
+++ b/addons/purchase_stock/tests/test_stockvaluation.py
@@ -11,12 +11,14 @@ from odoo.tests import Form
from odoo.tests.common import TransactionCase, tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from odoo.addons.stock.tests.common import TestStockCommon
class TestStockValuation(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
+ cls.url_extract_rec_id_and_model = TestStockCommon.url_extract_rec_id_and_model
cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
cls.stock_location = cls.env.ref('stock.stock_location_stock')
cls.partner_id = cls.env['res.partner'].create({
@@ -107,12 +109,14 @@ class TestStockValuation(TransactionCase):
'quantity': replenishment_uom_qty,
'warehouse_id': self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1).id,
})
- replenish_wizard.launch_replenishment()
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ purchase_order_id, model_name = self.url_extract_rec_id_and_model(url)
+ last_po_id = False
+ if purchase_order_id and model_name:
+ last_po_id = self.env[model_name[0]].browse(int(purchase_order_id[0]))
- last_po_id = self.env['purchase.order'].search([
- ('origin', 'ilike', '%Manual Replenishment%'),
- ('partner_id', '=', vendor.id)
- ])[-1]
order_line = last_po_id.order_line.search([('product_id', '=', self.product1.id)])
self.assertEqual(order_line.product_qty,
ap._compute_quantity(replenishment_uom_qty, kgm, rounding_method='HALF-UP'),
diff --git a/addons/purchase_stock/wizard/__init__.py b/addons/purchase_stock/wizard/__init__.py
index 28595d05268..2a77252ebc4 100644
--- a/addons/purchase_stock/wizard/__init__.py
+++ b/addons/purchase_stock/wizard/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_replenishment_info
+from . import product_replenish
diff --git a/addons/purchase_stock/wizard/product_replenish.py b/addons/purchase_stock/wizard/product_replenish.py
new file mode 100644
index 00000000000..50adced44f9
--- /dev/null
+++ b/addons/purchase_stock/wizard/product_replenish.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import _, api, fields, models
+from odoo.osv.expression import AND
+
+
+class ProductReplenish(models.TransientModel):
+ _inherit = 'product.replenish'
+
+ supplier_id = fields.Many2one("product.supplierinfo", string="Vendor")
+ show_vendor = fields.Boolean(compute="_compute_show_vendor")
+
+ @api.model
+ def default_get(self, fields):
+ res = super().default_get(fields)
+ if res.get('product_id'):
+ product_id = self.env['product.product'].browse(res['product_id'])
+ product_tmpl_id = product_id.product_tmpl_id
+ if 'warehouse_id' not in res:
+ company = product_tmpl_id.company_id or self.env.company
+ res['warehouse_id'] = self.env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1).id
+ orderpoint = self.env['stock.warehouse.orderpoint'].search([('product_id', 'in', [product_tmpl_id.product_variant_id.id, product_id.id]), ("warehouse_id", "=", res['warehouse_id'])], limit=1)
+ if orderpoint:
+ res['supplier_id'] = orderpoint.supplier_id.id
+ elif product_tmpl_id.seller_ids:
+ res['supplier_id'] = product_tmpl_id.seller_ids[0].id
+ if not product_tmpl_id.seller_ids:
+ company = product_tmpl_id.company_id or self.env.company
+ domain = ['|', ('company_id', '=', False), ('company_id', '=', company.id)] + self._get_allowed_route_domain()
+ domain = AND([domain, [('id', '!=', self.env.ref('purchase_stock.route_warehouse0_buy', raise_if_not_found=False).id)]])
+ res['route_id'] = self.env['stock.route'].search(domain, limit=1).id
+ return res
+
+ @api.depends('route_id')
+ def _compute_show_vendor(self):
+ for rec in self:
+ rec.show_vendor = rec.route_id == self.env.ref('purchase_stock.route_warehouse0_buy', raise_if_not_found=False)
+
+ @api.onchange('route_id')
+ def _onchange_route_id(self):
+ for rec in self:
+ if rec.route_id == self.env.ref('purchase_stock.route_warehouse0_buy', raise_if_not_found=False) and not rec.product_id.product_tmpl_id.seller_ids:
+ return {
+ 'warning': {
+ 'title': _("Vendor Not Found in Product %s", rec.product_id.name),
+ 'message': _("Go on the product form and add the list of vendors"),
+ },
+ }
+
+ def _prepare_orderpoint_values(self):
+ res = super()._prepare_orderpoint_values()
+ if self.supplier_id:
+ res['supplier_id'] = self.supplier_id.id
+ return res
+
+ def action_stock_replenishment_info(self):
+ self.ensure_one()
+ orderpoint = self.env["stock.warehouse.orderpoint"].search([("product_id", "=", self.product_id.id), ("warehouse_id", "=", self.warehouse_id.id)], limit=1)
+ if not orderpoint:
+ orderpoint = self.env["stock.warehouse.orderpoint"].create({
+ "product_id": self.product_id.id,
+ "warehouse_id": self.warehouse_id.id,
+ })
+ action = orderpoint.action_stock_replenishment_info()
+
+ action["context"] = {
+ 'default_orderpoint_id': orderpoint.id,
+ 'replenish_id': self.id,
+ }
+ return action
diff --git a/addons/purchase_stock/wizard/product_replenish_views.xml b/addons/purchase_stock/wizard/product_replenish_views.xml
new file mode 100644
index 00000000000..a3e356ae015
--- /dev/null
+++ b/addons/purchase_stock/wizard/product_replenish_views.xml
@@ -0,0 +1,22 @@
+
+
+
+ product.replenish.form.inherit.stock
+ product.replenish
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/purchase_stock/wizard/stock_replenishment_info.py b/addons/purchase_stock/wizard/stock_replenishment_info.py
index 0c6b2db3857..ac0a484616f 100644
--- a/addons/purchase_stock/wizard/stock_replenishment_info.py
+++ b/addons/purchase_stock/wizard/stock_replenishment_info.py
@@ -17,3 +17,21 @@ class StockReplenishmentInfo(models.TransientModel):
def _compute_supplierinfo_ids(self):
for replenishment_info in self:
replenishment_info.supplierinfo_ids = replenishment_info.product_id.seller_ids
+
+
+class StockReplenishmentOption(models.TransientModel):
+ _inherit = 'stock.replenishment.option'
+
+ def select_route(self):
+ if self._context.get('replenish_id'):
+ replenish = self.env['product.replenish'].browse(self._context.get('replenish_id'))
+ replenish.route_id = self.route_id.id
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Replenish',
+ 'res_model': 'product.replenish',
+ 'res_id': replenish.id,
+ 'target': 'new',
+ 'view_mode': 'form',
+ }
+ return super().select_route()
diff --git a/addons/purchase_stock/wizard/stock_replenishment_info.xml b/addons/purchase_stock/wizard/stock_replenishment_info.xml
index c85f62229a1..1ca77bb6792 100644
--- a/addons/purchase_stock/wizard/stock_replenishment_info.xml
+++ b/addons/purchase_stock/wizard/stock_replenishment_info.xml
@@ -12,7 +12,6 @@
@@ -38,7 +37,7 @@
-
+
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index bb8d1e61141..703f6bd965e 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -180,7 +180,7 @@ class StockMove(models.Model):
display_clear_serial = fields.Boolean(compute='_compute_display_clear_serial')
next_serial = fields.Char('First SN')
next_serial_count = fields.Integer('Number of SN')
- orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Original Reordering Rule', check_company=True, index=True)
+ orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Original Reordering Rule', index=True)
forecast_availability = fields.Float('Forecast Availability', compute='_compute_forecast_information', digits='Product Unit of Measure', compute_sudo=True)
forecast_expected_date = fields.Datetime('Forecasted Expected date', compute='_compute_forecast_information', compute_sudo=True)
lot_ids = fields.Many2many('stock.lot', compute='_compute_lot_ids', inverse='_set_lot_ids', string='Serial Numbers', readonly=False)
diff --git a/addons/stock/models/stock_orderpoint.py b/addons/stock/models/stock_orderpoint.py
index dcc2ff8402a..d59b80de522 100644
--- a/addons/stock/models/stock_orderpoint.py
+++ b/addons/stock/models/stock_orderpoint.py
@@ -224,7 +224,7 @@ class StockWarehouseOrderpoint(models.Model):
return action
def action_replenish(self):
- now = datetime.now()
+ now = self.env.cr.now()
try:
self._procure_orderpoint_confirm(company_id=self.env.company)
except UserError as e:
@@ -444,14 +444,20 @@ class StockWarehouseOrderpoint(models.Model):
self.ensure_one()
domain = [('orderpoint_id', 'in', self.ids)]
if self.env.context.get('written_after'):
- domain = expression.AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
+ domain = expression.AND([domain, [('write_date', '>=', self.env.context.get('written_after'))]])
move = self.env['stock.move'].search(domain, limit=1)
if move.picking_id:
+ action = self.env.ref('stock.stock_picking_action_picking_type')
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('The inter-warehouse transfers have been generated'),
+ 'message': '%s',
+ 'links': [{
+ 'label': move.picking_id.name,
+ 'url': f'#action={action.id}&id={move.picking_id.id}&model=stock.picking&view_type=form'
+ }],
'sticky': False,
}
}
diff --git a/addons/stock/tests/common.py b/addons/stock/tests/common.py
index 8f24d430b77..49aca34b7a6 100644
--- a/addons/stock/tests/common.py
+++ b/addons/stock/tests/common.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+import re
from odoo.tests import common
@@ -95,3 +96,8 @@ class TestStockCommon(common.TransactionCase):
cls.UnitA = cls.ProductObj.create({'name': 'Unit-A', 'type': 'product'})
cls.kgB = cls.ProductObj.create({'name': 'kg-B', 'type': 'product', 'uom_id': cls.uom_kg.id, 'uom_po_id': cls.uom_kg.id})
cls.gB = cls.ProductObj.create({'name': 'g-B', 'type': 'product', 'uom_id': cls.uom_gm.id, 'uom_po_id': cls.uom_gm.id})
+
+ def url_extract_rec_id_and_model(self, url):
+ rec_id = re.findall(r'[?&]id=([^&]+).*', url)
+ model_name = re.findall(r'[?&]model=([^&]+).*', url)
+ return rec_id, model_name
diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py
index 6f36a7ecaf6..c88d70f3064 100644
--- a/addons/stock/tests/test_move2.py
+++ b/addons/stock/tests/test_move2.py
@@ -2722,8 +2722,14 @@ class TestRoutes(TestStockCommon):
'warehouse_id': self.wh.id,
})
- replenish_wizard.launch_replenishment()
- last_picking_id = self.env['stock.picking'].search([('origin', '=', 'Manual Replenishment')])[-1]
+ genrated_picking = replenish_wizard.launch_replenishment()
+ links = genrated_picking.get("params", {}).get("links")
+ url = links and links[0].get("url", "") or ""
+ picking_id, model_name = self.url_extract_rec_id_and_model(url)
+
+ last_picking_id = False
+ if picking_id and model_name:
+ last_picking_id = self.env[model_name[0]].browse(int(picking_id[0]))
self.assertTrue(last_picking_id, 'Picking not found')
move_line = last_picking_id.move_ids.search([('product_id', '=', self.product1.id)])
self.assertTrue(move_line,'The product is not in the picking')
diff --git a/addons/stock/wizard/product_replenish.py b/addons/stock/wizard/product_replenish.py
index 843117492e8..1a63c24bd25 100644
--- a/addons/stock/wizard/product_replenish.py
+++ b/addons/stock/wizard/product_replenish.py
@@ -5,6 +5,7 @@ import datetime
from odoo import _, api, fields, models
from odoo.exceptions import UserError
+from odoo.osv import expression
from odoo.tools.misc import clean_context
@@ -16,22 +17,25 @@ class ProductReplenish(models.TransientModel):
product_tmpl_id = fields.Many2one('product.template', string='Product Template', required=True)
product_has_variants = fields.Boolean('Has variants', default=False, required=True)
product_uom_category_id = fields.Many2one('uom.category', related='product_id.uom_id.category_id', readonly=True, required=True)
- product_uom_id = fields.Many2one('uom.uom', string='Unit of measure', required=True)
+ product_uom_id = fields.Many2one('uom.uom', string='Unity of measure', required=True)
+ forecast_uom_id = fields.Many2one(related='product_id.uom_id')
quantity = fields.Float('Quantity', default=1, required=True)
date_planned = fields.Datetime('Scheduled Date', required=True, help="Date at which the replenishment should take place.")
warehouse_id = fields.Many2one(
'stock.warehouse', string='Warehouse', required=True,
domain="[('company_id', '=', company_id)]")
- route_ids = fields.Many2many(
- 'stock.route', string='Preferred Routes',
- help="Apply specific route(s) for the replenishment instead of product's default routes.",
+ route_id = fields.Many2one(
+ 'stock.route', string='Preferred Route',
+ help="Apply specific route for the replenishment instead of product's default routes.",
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
company_id = fields.Many2one('res.company')
+ forecasted_quantity = fields.Float(string="Forecasted Quantity", compute="_compute_forecasted_quantity")
+ allowed_route_ids = fields.Many2many("stock.route", compute="_compute_allowed_route_ids")
- @api.onchange('product_id')
+ @api.onchange('product_id', 'warehouse_id')
def _onchange_product_id(self):
if not self.env.context.get('default_quantity'):
- self.quantity = abs(self.product_id.virtual_available) if self.product_id.virtual_available < 0 else 1
+ self.quantity = abs(self.forecasted_quantity) if self.forecasted_quantity < 0 else 1
@api.model
def default_get(self, fields):
@@ -60,38 +64,65 @@ class ProductReplenish(models.TransientModel):
res['warehouse_id'] = warehouse.id
if 'date_planned' in fields:
res['date_planned'] = datetime.datetime.now()
+ if 'route_id' in fields and 'route_id' not in res:
+ route_id = False
+ domain = expression.AND([self._get_allowed_route_domain(), ['|', ('company_id', '=', False), ('company_id', '=', company.id)]])
+ if product_tmpl_id.route_ids:
+ product_route_domain = expression.AND([domain, [('product_ids', '=', product_tmpl_id.id)]])
+ route_id = self.env['stock.route'].search(product_route_domain, limit=1).id
+ if not route_id:
+ route_id = self.env['stock.route'].search(domain, limit=1).id
+ if route_id:
+ res['route_id'] = route_id
return res
def launch_replenishment(self):
uom_reference = self.product_id.uom_id
self.quantity = self.product_uom_id._compute_quantity(self.quantity, uom_reference, rounding_method='HALF-UP')
try:
- self.env['procurement.group'].with_context(clean_context(self.env.context)).run([
- self.env['procurement.group'].Procurement(
- self.product_id,
- self.quantity,
- uom_reference,
- self.warehouse_id.lot_stock_id, # Location
- _("Manual Replenishment"), # Name
- _("Manual Replenishment"), # Origin
- self.warehouse_id.company_id,
- self._prepare_run_values() # Values
- )
- ])
- return {
+ orderpoint = self.env['stock.warehouse.orderpoint'].search([('product_id', '=', self.product_id.id)])
+ if orderpoint:
+ orderpoint.write(self._prepare_orderpoint_values())
+ else:
+ orderpoint = self.env['stock.warehouse.orderpoint'].create(self._prepare_orderpoint_values())
+ notification = orderpoint.action_replenish()
+ act_window_close = {
'type': 'ir.actions.act_window_close',
'infos': {'done': True},
}
+ if notification:
+ notification['params']['next'] = act_window_close
+ return notification
+ return act_window_close
except UserError as error:
raise UserError(error)
- def _prepare_run_values(self):
- replenishment = self.env['procurement.group'].create({})
-
+ def _prepare_orderpoint_values(self):
values = {
- 'warehouse_id': self.warehouse_id,
- 'route_ids': self.route_ids,
- 'date_planned': self.date_planned,
- 'group_id': replenishment,
+ 'location_id': self.warehouse_id.lot_stock_id.id,
+ 'product_id': self.product_id.id,
+ 'qty_to_order': self.quantity,
}
+ if self.route_id:
+ values['route_id'] = self.route_id.id
return values
+
+ @api.depends('warehouse_id', 'product_id')
+ def _compute_forecasted_quantity(self):
+ for rec in self:
+ rec.forecasted_quantity = rec.product_id.with_context(warehouse=rec.warehouse_id.id).virtual_available
+
+ # OVERWRITE in 'Drop Shipping', 'Dropship and Subcontracting Management' and 'Dropship and Subcontracting Management' to hide it
+ def _get_allowed_route_domain(self):
+ stock_location_inter_wh_id = self.env.ref('stock.stock_location_inter_wh').id
+ return [
+ ('product_selectable', '=', True),
+ ('rule_ids.location_src_id', '!=', stock_location_inter_wh_id),
+ ('rule_ids.location_dest_id', '!=', stock_location_inter_wh_id)
+ ]
+
+ @api.depends('product_id', 'product_tmpl_id')
+ def _compute_allowed_route_ids(self):
+ domain = self._get_allowed_route_domain()
+ route_ids = self.env['stock.route'].search(domain)
+ self.allowed_route_ids = route_ids
diff --git a/addons/stock/wizard/product_replenish_views.xml b/addons/stock/wizard/product_replenish_views.xml
index 55b5439ab49..6d2b04a4161 100644
--- a/addons/stock/wizard/product_replenish_views.xml
+++ b/addons/stock/wizard/product_replenish_views.xml
@@ -11,26 +11,36 @@
a manufacturing order or a transfer.
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+