From 94cc077e21f35bb410cfd5c053e179206cdcaf12 Mon Sep 17 00:00:00 2001 From: stcc-odoo Date: Mon, 31 Jul 2023 13:48:11 +0000 Subject: [PATCH] [FIX] product_expiry: update expiry dates correctly Steps to reproduce: - Install product_expiry - Create Product P: - Product type: Storable - Inventory tab > Tracking: By Lots - Check Expiration Date - Expiration Date: 100 days after receipt - Best Before Date, Removal Date, Alert Date: 10 days before expiration date - Save - Products > Lots/Serial Numbers > New - Product: P - open Dates tab There are two different usecases and issues starting from this point. Usecase 1: - Dates tab, delete all dates, save - set the Expiration Date, all other dates are set - without saving, update the Expiration Date again Issue 1: The other dates don't update according to the new Expiration Date. This happens because neither if conditions in `_compute_dates` match the case where the product has an expiration date set, but it has not been saved to the lot. Solution 1: Add an additional condition in the `_compute_dates` method where `lot.expiration_date` is set, but `lot._origin.expiration_date` is not set. This will ensure the other dates are updated correctly when setting the Expiration Date without saving. Usecase 2: - Dates tab, set an Expiration Date, save - Without saving the record, update the Expiration Date several times, with dates in the future Issue 2: The other dates (Best Before Date, Removal Date, Alert Date) will drift off further in the future, while they should always be 10 days apart from the expiration date. This happens because the new dates are being set relative to the actual value of the date, which changes with each successive update. Solution 2: set the new dates relative to the value of the `lot._origin` dates, which remain constant between successive (saveless) updates. This will ensure that the other dates maintain a 10-day difference from the Expiration Date consistently. opw-3374135 closes odoo/odoo#130599 X-original-commit: a275cbcd2d8a87bbd8cadf85c094625ebd8358c4 Signed-off-by: Tiffany Chang --- .../product_expiry/models/production_lot.py | 11 ++-- addons/product_expiry/tests/test_stock_lot.py | 63 +++++++++++-------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/addons/product_expiry/models/production_lot.py b/addons/product_expiry/models/production_lot.py index 4da3dc74d08..0b8f32c03fd 100644 --- a/addons/product_expiry/models/production_lot.py +++ b/addons/product_expiry/models/production_lot.py @@ -47,8 +47,9 @@ class StockLot(models.Model): lot.alert_date = False elif lot.expiration_date: # when create - if lot.product_id != lot._origin.product_id or\ - (not lot.use_date and not lot.removal_date and not lot.alert_date): + if lot.product_id != lot._origin.product_id or \ + (not lot.use_date and not lot.removal_date and not lot.alert_date) or \ + (lot.expiration_date and not lot._origin.expiration_date): product_tmpl = lot.product_id.product_tmpl_id lot.use_date = lot.expiration_date - datetime.timedelta(days=product_tmpl.use_time) lot.removal_date = lot.expiration_date - datetime.timedelta(days=product_tmpl.removal_time) @@ -56,9 +57,9 @@ class StockLot(models.Model): # when change elif lot._origin.expiration_date: time_delta = lot.expiration_date - lot._origin.expiration_date - lot.use_date = lot.use_date and lot.use_date + time_delta - lot.removal_date = lot.removal_date and lot.removal_date + time_delta - lot.alert_date = lot.alert_date and lot.alert_date + time_delta + lot.use_date = lot._origin.use_date and lot._origin.use_date + time_delta + lot.removal_date = lot._origin.removal_date and lot._origin.removal_date + time_delta + lot.alert_date = lot._origin.alert_date and lot._origin.alert_date + time_delta @api.model def _alert_date_exceeded(self): diff --git a/addons/product_expiry/tests/test_stock_lot.py b/addons/product_expiry/tests/test_stock_lot.py index f3af6d487f2..3ee0f1619f9 100644 --- a/addons/product_expiry/tests/test_stock_lot.py +++ b/addons/product_expiry/tests/test_stock_lot.py @@ -236,6 +236,20 @@ class TestStockLot(TestStockCommon): def test_03_onchange_expiration_date(self): """ Updates the `expiration_date` of the lot production and checks other date fields are updated as well. """ + def check_expiration_dates(product, lot, start_date, delta): + self.assertAlmostEqual( + start_date + timedelta(days=product.expiration_time), + lot.expiration_date, delta=delta) + self.assertAlmostEqual( + lot.expiration_date - timedelta(days=product.use_time), + lot.use_date, delta=delta) + self.assertAlmostEqual( + lot.expiration_date - timedelta(days=product.removal_time), + lot.removal_date, delta=delta) + self.assertAlmostEqual( + lot.expiration_date - timedelta(days=product.alert_time), + lot.alert_date, delta=delta) + # Keeps track of the current datetime and set a delta for the compares. today_date = datetime.today() time_gap = timedelta(seconds=10) @@ -246,37 +260,36 @@ class TestStockLot(TestStockCommon): lot_form.company_id = self.env.company apple_lot = lot_form.save() # ...then checks date fields have the expected values. - self.assertAlmostEqual( - today_date + timedelta(days=self.apple_product.expiration_time), - apple_lot.expiration_date, delta=time_gap) - self.assertAlmostEqual( - apple_lot.expiration_date - timedelta(days=self.apple_product.use_time), - apple_lot.use_date, delta=time_gap) - self.assertAlmostEqual( - apple_lot.expiration_date - timedelta(days=self.apple_product.removal_time), - apple_lot.removal_date, delta=time_gap) - self.assertAlmostEqual( - apple_lot.expiration_date - timedelta(days=self.apple_product.alert_time), - apple_lot.alert_date, delta=time_gap) + check_expiration_dates(self.apple_product, apple_lot, today_date, time_gap) difference = timedelta(days=20) - new_date = apple_lot.expiration_date + difference - old_use_date = apple_lot.use_date - old_removal_date = apple_lot.removal_date - old_alert_date = apple_lot.alert_date + new_expiration_date = apple_lot.expiration_date + difference + new_start_date = new_expiration_date - timedelta(days=self.apple_product.expiration_time) + random_date = new_expiration_date + difference - # Modifies the lot `expiration_date`... + # Modifies the lot `expiration_date` several times, without saving... lot_form = Form(apple_lot) - lot_form.expiration_date = new_date + lot_form.expiration_date = new_expiration_date + lot_form.expiration_date = random_date + lot_form.expiration_date = new_expiration_date apple_lot = lot_form.save() - # ...then checks all other date fields were correclty updated. - self.assertAlmostEqual( - apple_lot.use_date, old_use_date + difference, delta=time_gap) - self.assertAlmostEqual( - apple_lot.removal_date, old_removal_date + difference, delta=time_gap) - self.assertAlmostEqual( - apple_lot.alert_date, old_alert_date + difference, delta=time_gap) + # ...then checks all other date fields were correctly updated. + check_expiration_dates(self.apple_product, apple_lot, new_start_date, time_gap) + + # Remove all dates, save, update expiration date twice, then save again + lot_form = Form(apple_lot) + lot_form.expiration_date = False + lot_form.use_date = False + lot_form.removal_date = False + lot_form.alert_date = False + lot_form.save() + lot_form.expiration_date = random_date + lot_form.expiration_date = new_expiration_date + apple_lot = lot_form.save() + + # ...then check all other date fields were correctly updated. + check_expiration_dates(self.apple_product, apple_lot, new_start_date, time_gap) def test_04_expiration_date_on_receipt(self): """ Test we can set an expiration date on receipt and all expiration