From f3a69ceeb37ca0a0d44e60c45081cf1e329a3529 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Thu, 22 Apr 2021 15:52:32 +0200 Subject: [PATCH] [FIX] mrp_multi_level: Consider timezone of the warehouse to generate mrp inventory records. --- mrp_multi_level/models/mrp_area.py | 19 +++++++ mrp_multi_level/tests/test_mrp_multi_level.py | 53 +++++++++++++++++++ mrp_multi_level/views/mrp_area_views.xml | 1 + mrp_multi_level/wizards/mrp_multi_level.py | 19 ++++--- 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/mrp_multi_level/models/mrp_area.py b/mrp_multi_level/models/mrp_area.py index a61d4ecb2..8fee6d166 100644 --- a/mrp_multi_level/models/mrp_area.py +++ b/mrp_multi_level/models/mrp_area.py @@ -5,6 +5,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import api, fields, models +from odoo.addons.base.res.res_partner import _tz_get class MrpArea(models.Model): @@ -30,6 +31,24 @@ class MrpArea(models.Model): string='Working Hours', related='warehouse_id.calendar_id', ) + tz = fields.Selection( + _tz_get, string='Timezone', required=True, + default=lambda self: self._context.get( + 'tz') or self.env.user.tz or 'UTC', + help="This field is used in order to define in which timezone " + "the MRP Area will work.") + + @api.model + def _datetime_to_date_tz(self, dt_to_convert=None): + """Coverts a datetime to date considering the timezone + of MRP Area. If no datetime is provided, it returns today's + date in the timezone.""" + if isinstance(dt_to_convert, str): + dt_to_convert = fields.Datetime.from_string(dt_to_convert) + return fields.Date.context_today( + self.with_context(tz=self.tz), + timestamp=dt_to_convert, + ) @api.multi def _get_locations(self): diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index a7293e26d..c394501bd 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -100,6 +100,20 @@ class TestMrpMultiLevel(SavepointCase): 'product_id': cls.prod_test.id, 'mrp_area_id': cls.mrp_area.id, }) + # Another product: + cls.product_tz = cls.product_obj.create( + { + "name": "Product Timezone", + "type": "product", + "list_price": 100.0, + "route_ids": [(6, 0, [route_buy])], + "seller_ids": [(0, 0, {"name": vendor1.id, "price": 20.0})], + } + ) + cls.product_mrp_area_obj.create( + {"product_id": cls.product_tz.id, "mrp_area_id": cls.cases_area.id} + ) + # Parameters in secondary area with nbr_days set. cls.product_mrp_area_obj.create({ 'product_id': cls.prod_test.id, @@ -675,3 +689,42 @@ class TestMrpMultiLevel(SavepointCase): self.assertEqual(mrp_invs[0].to_procure, 130) # Net needs = 18, available on-hand = 3 -> 15 self.assertEqual(mrp_invs[1].to_procure, 15) + + def test_timezone_handling(self): + self.cases_area.tz = "Australia/Sydney" + # Sidney timezone -> Oct-Apr/Apr-Oct: UTC+11/UTC+10 + date_move = datetime(2090, 4, 19, 20, 00) # Apr 20 6/7 am in Sidney + sidney_date = "2090-04-20" + self._create_picking_in( + self.product_tz, 10.0, date_move, location=self.cases_loc + ) + self.mrp_multi_level_wiz.create( + {"mrp_area_ids": [(6, 0, self.cases_area.ids)]} + ).run_mrp_multi_level() + inventory = self.mrp_inventory_obj.search( + [ + ("mrp_area_id", "=", self.cases_area.id), + ("product_id", "=", self.product_tz.id), + ] + ) + self.assertEqual(len(inventory), 1) + self.assertEqual(inventory.date, sidney_date) + + def test_timezone_not_set(self): + self.wh.calendar_id = False + date_move = datetime(2090, 4, 19, 20, 00) + no_tz_date = "2090-04-19" + self._create_picking_in( + self.product_tz, 10.0, date_move, location=self.cases_loc + ) + self.mrp_multi_level_wiz.create( + {"mrp_area_ids": [(6, 0, self.cases_area.ids)]} + ).run_mrp_multi_level() + inventory = self.mrp_inventory_obj.search( + [ + ("mrp_area_id", "=", self.cases_area.id), + ("product_id", "=", self.product_tz.id), + ] + ) + self.assertEqual(len(inventory), 1) + self.assertEqual(inventory.date, no_tz_date) diff --git a/mrp_multi_level/views/mrp_area_views.xml b/mrp_multi_level/views/mrp_area_views.xml index d43633dab..b2605445f 100644 --- a/mrp_multi_level/views/mrp_area_views.xml +++ b/mrp_multi_level/views/mrp_area_views.xml @@ -40,6 +40,7 @@ + diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 1c6f68890..333f8494c 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -5,8 +5,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import api, fields, models, exceptions, _ -from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT -from datetime import date, datetime, timedelta +from datetime import date, timedelta import logging from odoo.tools.float_utils import float_round logger = logging.getLogger(__name__) @@ -21,8 +20,6 @@ class MultiLevelMrp(models.TransientModel): help="If empty, all areas will be computed.", ) - # TODO: dates are not being correctly computed for supply... - @api.model def _prepare_product_mrp_area_data(self, product_mrp_area): qty_available = 0.0 @@ -73,6 +70,7 @@ class MultiLevelMrp(models.TransientModel): @api.model def _prepare_mrp_move_data_from_stock_move( self, product_mrp_area, move, direction='in'): + area = product_mrp_area.mrp_area_id if direction == 'out': mrp_type = 'd' product_qty = -move.product_qty @@ -107,12 +105,13 @@ class MultiLevelMrp(models.TransientModel): else: order_number = move.name origin = "mv" - mrp_date = date.today() - if datetime.date(datetime.strptime( - move.date_expected, - DEFAULT_SERVER_DATETIME_FORMAT)) > date.today(): - mrp_date = datetime.date(datetime.strptime( - move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)) + # The date to display is based on the timezone of the warehouse. + today_tz = area._datetime_to_date_tz() + move_date_tz = area._datetime_to_date_tz(move.date_expected) + if move_date_tz > today_tz: + mrp_date = move_date_tz + else: + mrp_date = today_tz return { 'product_id': move.product_id.id, 'product_mrp_area_id': product_mrp_area.id,