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 @@