diff --git a/setup/stock_release_channel_process_end_time/odoo/addons/stock_release_channel_process_end_time b/setup/stock_release_channel_process_end_time/odoo/addons/stock_release_channel_process_end_time new file mode 120000 index 0000000000..8e8afb1176 --- /dev/null +++ b/setup/stock_release_channel_process_end_time/odoo/addons/stock_release_channel_process_end_time @@ -0,0 +1 @@ +../../../../stock_release_channel_process_end_time \ No newline at end of file diff --git a/setup/stock_release_channel_process_end_time/setup.py b/setup/stock_release_channel_process_end_time/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/stock_release_channel_process_end_time/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_release_channel_process_end_time/README.rst b/stock_release_channel_process_end_time/README.rst new file mode 100644 index 0000000000..0d1845a391 --- /dev/null +++ b/stock_release_channel_process_end_time/README.rst @@ -0,0 +1,109 @@ +====================================== +Stock Release Channel Process End Date +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:84e66197d5580b417a6dc94e376b88b6466a007cd8946990b65689e9dea8647e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwms-lightgray.png?logo=github + :target: https://github.com/OCA/wms/tree/16.0/stock_release_channel_process_end_time + :alt: OCA/wms +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/wms-16-0/wms-16-0-stock_release_channel_process_end_time + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/wms&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to set an end time on a release channel that will be transmitted on +related stock pickings (on scheduled date) when the channel awakes. + +That allows to use Odoo core sorting feature on stock pickings level. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +#. Assign a timezone on the Warehouse address if defined and if needed + If you have a lot of warehouses in the same timezone, you can also define + the timezone on the company partner. + If you don't define a timezone on the warehouse(s) nor the company, the process + end time on channels will be considered as UTC. +#. Propagate/update scheduled date of picking follow process end date/time automatically + by setting "Update Scheduled Date" configuration in Settings/General Settings/Inventory + +#. Go To Release Channels +#. Set an end time +#. Wake up the channel +#. The assigned pickings have their scheduled date set at the next end time. (if enabled "Update Scheduled Date" config) + +Known issues / Roadmap +====================== + +* Maybe something to do on pickings level when setting channels asleep (storing + the original scheduled date or ...). + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Denis Roussel + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-rousseldenis| image:: https://github.com/rousseldenis.png?size=40px + :target: https://github.com/rousseldenis + :alt: rousseldenis + +Current `maintainer `__: + +|maintainer-rousseldenis| + +This module is part of the `OCA/wms `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_release_channel_process_end_time/__init__.py b/stock_release_channel_process_end_time/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/stock_release_channel_process_end_time/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_release_channel_process_end_time/__manifest__.py b/stock_release_channel_process_end_time/__manifest__.py new file mode 100644 index 0000000000..b2f66da5e7 --- /dev/null +++ b/stock_release_channel_process_end_time/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Release Channel Process End Date", + "summary": """ + Allows to define an end date (and time) on a release channel and + propagate it to the concerned pickings""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "maintainers": ["rousseldenis"], + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/wms", + "depends": [ + "partner_tz", + "stock_release_channel", + "stock_available_to_promise_release", + ], + "data": [ + "views/res_config_settings.xml", + "views/stock_release_channel.xml", + ], +} diff --git a/stock_release_channel_process_end_time/models/__init__.py b/stock_release_channel_process_end_time/models/__init__.py new file mode 100644 index 0000000000..b2326b5874 --- /dev/null +++ b/stock_release_channel_process_end_time/models/__init__.py @@ -0,0 +1,3 @@ +from . import stock_picking +from . import stock_release_channel +from . import res_config_settings diff --git a/stock_release_channel_process_end_time/models/res_config_settings.py b/stock_release_channel_process_end_time/models/res_config_settings.py new file mode 100644 index 0000000000..d4354a6df0 --- /dev/null +++ b/stock_release_channel_process_end_time/models/res_config_settings.py @@ -0,0 +1,15 @@ +# Copyright 2023 Trobz +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + stock_release_use_channel_end_date = fields.Boolean( + help="Will update scheduled date of picking based on process end date " + "instead of release date + delay.", + config_parameter="stock_release_channel_process_end_time." + "stock_release_use_channel_end_date", + ) diff --git a/stock_release_channel_process_end_time/models/stock_picking.py b/stock_release_channel_process_end_time/models/stock_picking.py new file mode 100644 index 0000000000..7d5c8552b2 --- /dev/null +++ b/stock_release_channel_process_end_time/models/stock_picking.py @@ -0,0 +1,82 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class StockPicking(models.Model): + + _inherit = "stock.picking" + + schedule_date_prior_to_channel_process_end_date_search = fields.Boolean( + store=False, + search="_search_schedule_date_prior_to_channel_process_end_date", + help="Technical field to search on not processed pickings where the scheduled " + "date is prior to the process end date of available channels.", + ) + + @api.model + def fields_get(self, allfields=None, attributes=None): + """Hide schedule_date_prior_to_channel_process_end_date_search from + filterable/searchable fields""" + res = super().fields_get(allfields, attributes) + if res.get("schedule_date_prior_to_channel_process_end_date_search"): + res["schedule_date_prior_to_channel_process_end_date_search"][ + "searchable" + ] = False + return res + + @api.model + def _search_schedule_date_prior_to_channel_process_end_date(self, operator, value): + """Search on not processed pickings where the scheduled date is prior to + the process end date of available channels. + """ + # We use a searchable field to be able to use 'inselect' operator in + # order to avoid a subquery in the search method. This is a workaround + # since the 'inselect' operator is not supported when calling search + # method but only into search method definition. + if operator not in ["=", "!="] or not isinstance(value, bool): + raise UserError(_("Operation not supported")) + query = """ + SELECT + stock_picking.id + FROM + stock_picking + WHERE + stock_picking.state NOT IN ('done', 'cancel') + AND exists ( + SELECT + TRUE + FROM + stock_release_channel + WHERE + stock_release_channel.process_end_date is not null + and stock_picking.scheduled_date <= stock_release_channel.process_end_date + ) + """ + if value: + operator_inselect = "inselect" if operator == "=" else "not inselect" + else: + operator_inselect = "not inselect" if operator == "=" else "inselect" + return [("id", operator_inselect, (query, []))] + + def _after_release_set_expected_date(self): + enabled_update_scheduled_date = bool( + self.env["ir.config_parameter"] + .sudo() + .get_param( + "stock_release_channel_process_end_time.stock_release_use_channel_end_date" + ) + ) + res = super()._after_release_set_expected_date() + for rec in self: + # Check if a channel has been assigned to the picking and write + # scheduled_date if different to avoid unnecessary write + if ( + rec.release_channel_id + and rec.release_channel_id.process_end_date + and rec.scheduled_date != rec.release_channel_id.process_end_date + and enabled_update_scheduled_date + ): + rec.scheduled_date = rec.release_channel_id.process_end_date + return res diff --git a/stock_release_channel_process_end_time/models/stock_release_channel.py b/stock_release_channel_process_end_time/models/stock_release_channel.py new file mode 100644 index 0000000000..1c84fcf6db --- /dev/null +++ b/stock_release_channel_process_end_time/models/stock_release_channel.py @@ -0,0 +1,100 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + +from odoo.addons.base.models.res_partner import _tz_get + +from ..utils import float_to_time, next_datetime + + +class StockReleaseChannel(models.Model): + + _inherit = "stock.release.channel" + + process_end_time = fields.Float( + help="Fill in this to indicates when this channel release process would " + "be ended. This information will be used to compute the channel pickings " + "scheduled date at channel awaking.", + ) + process_end_time_tz = fields.Selection( + selection=_tz_get, + compute="_compute_process_end_time_tz", + help="Technical field to compute the timezone for the process end time.", + ) + process_end_time_can_edit = fields.Boolean( + compute="_compute_process_end_time_can_edit", + help="Technical field in order to know if user can edit the end date in views", + ) + process_end_date = fields.Datetime( + compute="_compute_process_end_date", + store=True, + readonly=False, + help="This is the end date for this window of opened channel.", + ) + + @api.depends_context("uid") + def _compute_process_end_time_can_edit(self): + if self.user_has_groups("stock.group_stock_manager"): + self.update({"process_end_time_can_edit": True}) + else: + self.update({"process_end_time_can_edit": False}) + + @api.depends("state", "process_end_time") + def _compute_process_end_date(self): + now = fields.Datetime.now() + for channel in self: + # We check if a date is not already set (manually) + if channel.state != "asleep" and not channel.process_end_date: + end = next_datetime( + now, + float_to_time( + channel.process_end_time, + tz=channel.process_end_time_tz, + ), + ) + channel.process_end_date = end + elif channel.state == "asleep": + channel.process_end_date = False + + @api.depends("warehouse_id.partner_id.tz") + @api.depends_context("company") + def _compute_process_end_time_tz(self): + # As the time is timezone-agnostic, we use the channel warehouse adress timezone + # or the company one, either it will be considered as UTC + company_tz = self.env.company.partner_id.tz + for channel in self: + channel.process_end_time_tz = ( + channel.warehouse_id.partner_id.tz or company_tz or "UTC" + ) + + @api.model + def assign_release_channel(self, picking): + res = super().assign_release_channel(picking) + picking._after_release_set_expected_date() + return res + + def _field_picking_domains(self): + res = super()._field_picking_domains() + release_ready_domain = res["count_picking_release_ready"] + # the initial scheduled_date condition based on datetime.now() must + # be replaced by a condition based on the process_end_date + # since the processe_end_date is a field on the release channel + # and not on the picking we all use an 'inselect' operator + # (join in where clause is not possible). The 'inselect' operator + # is not available in the ORM so we use a search with a domain + # on a specialized field defined in the stock.picking model + # (see stock.picking._search_schedule_date_prior_to_channel_process_end_date) + new_domain = [] + for criteria in release_ready_domain: + if criteria[0] == "scheduled_date": + new_domain.append( + ( + "schedule_date_prior_to_channel_process_end_date_search", + "=", + True, + ) + ) + else: + new_domain.append(criteria) + res["count_picking_release_ready"] = new_domain + return res diff --git a/stock_release_channel_process_end_time/readme/CONTRIBUTORS.rst b/stock_release_channel_process_end_time/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..9179ee4b8f --- /dev/null +++ b/stock_release_channel_process_end_time/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Denis Roussel diff --git a/stock_release_channel_process_end_time/readme/DESCRIPTION.rst b/stock_release_channel_process_end_time/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..b52c964d3d --- /dev/null +++ b/stock_release_channel_process_end_time/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module allows to set an end time on a release channel that will be transmitted on +related stock pickings (on scheduled date) when the channel awakes. + +That allows to use Odoo core sorting feature on stock pickings level. diff --git a/stock_release_channel_process_end_time/readme/ROADMAP.rst b/stock_release_channel_process_end_time/readme/ROADMAP.rst new file mode 100644 index 0000000000..9f8713d2d5 --- /dev/null +++ b/stock_release_channel_process_end_time/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Maybe something to do on pickings level when setting channels asleep (storing + the original scheduled date or ...). diff --git a/stock_release_channel_process_end_time/readme/USAGE.rst b/stock_release_channel_process_end_time/readme/USAGE.rst new file mode 100644 index 0000000000..3ddf7ed70b --- /dev/null +++ b/stock_release_channel_process_end_time/readme/USAGE.rst @@ -0,0 +1,12 @@ +#. Assign a timezone on the Warehouse address if defined and if needed + If you have a lot of warehouses in the same timezone, you can also define + the timezone on the company partner. + If you don't define a timezone on the warehouse(s) nor the company, the process + end time on channels will be considered as UTC. +#. Propagate/update scheduled date of picking follow process end date/time automatically + by setting "Update Scheduled Date" configuration in Settings/General Settings/Inventory + +#. Go To Release Channels +#. Set an end time +#. Wake up the channel +#. The assigned pickings have their scheduled date set at the next end time. (if enabled "Update Scheduled Date" config) diff --git a/stock_release_channel_process_end_time/static/description/icon.png b/stock_release_channel_process_end_time/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/stock_release_channel_process_end_time/static/description/icon.png differ diff --git a/stock_release_channel_process_end_time/static/description/index.html b/stock_release_channel_process_end_time/static/description/index.html new file mode 100644 index 0000000000..96704294ec --- /dev/null +++ b/stock_release_channel_process_end_time/static/description/index.html @@ -0,0 +1,450 @@ + + + + + + +Stock Release Channel Process End Date + + + +
+

Stock Release Channel Process End Date

+ + +

Beta License: AGPL-3 OCA/wms Translate me on Weblate Try me on Runboat

+

This module allows to set an end time on a release channel that will be transmitted on +related stock pickings (on scheduled date) when the channel awakes.

+

That allows to use Odoo core sorting feature on stock pickings level.

+

Table of contents

+ +
+

Usage

+
    +
  1. Assign a timezone on the Warehouse address if defined and if needed +If you have a lot of warehouses in the same timezone, you can also define +the timezone on the company partner. +If you don’t define a timezone on the warehouse(s) nor the company, the process +end time on channels will be considered as UTC.
  2. +
  3. Propagate/update scheduled date of picking follow process end date/time automatically +by setting “Update Scheduled Date” configuration in Settings/General Settings/Inventory
  4. +
  5. Go To Release Channels
  6. +
  7. Set an end time
  8. +
  9. Wake up the channel
  10. +
  11. The assigned pickings have their scheduled date set at the next end time. (if enabled “Update Scheduled Date” config)
  12. +
+
+
+

Known issues / Roadmap

+
    +
  • Maybe something to do on pickings level when setting channels asleep (storing +the original scheduled date or …).
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

rousseldenis

+

This module is part of the OCA/wms project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_release_channel_process_end_time/tests/__init__.py b/stock_release_channel_process_end_time/tests/__init__.py new file mode 100644 index 0000000000..b2a297cef2 --- /dev/null +++ b/stock_release_channel_process_end_time/tests/__init__.py @@ -0,0 +1 @@ +from . import test_release_end_date diff --git a/stock_release_channel_process_end_time/tests/test_release_end_date.py b/stock_release_channel_process_end_time/tests/test_release_end_date.py new file mode 100644 index 0000000000..c3c88521c8 --- /dev/null +++ b/stock_release_channel_process_end_time/tests/test_release_end_date.py @@ -0,0 +1,143 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from freezegun import freeze_time + +from odoo import fields + +from odoo.addons.queue_job.job import Job +from odoo.addons.stock_release_channel.tests.common import ChannelReleaseCase + + +class ReleaseChannelEndDateCase(ChannelReleaseCase): + @freeze_time("2023-01-27") + def test_channel_end_date(self): + # Set the end time + self.channel.process_end_time = 23.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + # Wake up the channel to set the process end date + self.channel.action_wake_up() + self.assertEqual( + "2023-01-27 23:00:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) + + @freeze_time("2023-01-27 10:00:00") + def test_channel_end_date_tomorrow(self): + # Set the end time + self.channel.process_end_time = 1.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + # Wake up the channel to set the process end date + # Current time is 10:00:00 + self.channel.action_wake_up() + self.assertEqual( + "2023-01-28 01:00:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) + + @freeze_time("2023-01-27 10:00:00") + def test_channel_end_date_manual(self): + # Set the end time + self.channel.process_end_time = 1.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + # Wake up the channel to set the process end date + # Current time is 10:00:00 + self.channel.action_wake_up() + self.assertEqual( + "2023-01-28 01:00:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) + + # We force the end date + self.channel.process_end_date = "2023-01-27 23:30:00" + self.assertEqual( + "2023-01-27 23:30:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) + + @freeze_time("2023-01-27 10:00:00") + def test_picking_scheduled_date(self): + self.env["ir.config_parameter"].sudo().set_param( + "stock_release_channel_process_end_time.stock_release_use_channel_end_date", + True, + ) + # Remove existing jobs as some already exists to assign pickings to channel + jobs_before = self.env["queue.job"].search([]) + jobs_before.unlink() + # Set the end time + self.channel.process_end_time = 23.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + self.channel.action_wake_up() + # Execute the picking channel assignations + jobs_after = self.env["queue.job"].search([]) + for job in jobs_after: + job = Job.load(job.env, job.uuid) + job.perform() + pickings = self.channel.picking_ids + # Check the scheduled date is corresponding to the one on channel + for picking in pickings: + self.assertEqual( + "2023-01-27 23:00:00", fields.Datetime.to_string(picking.scheduled_date) + ) + # at this stage, the pickings are not ready to be released as the + # qty available is not enough + self.assertFalse(self.channel._get_pickings_to_release()) + self._update_qty_in_location(self.loc_bin1, self.product1, 100.0) + self._update_qty_in_location(self.loc_bin1, self.product2, 100.0) + pickings.refresh() + # the pickings are now ready to be released + self.assertEqual(pickings, self.channel._get_pickings_to_release()) + # if the scheduled date of one picking is changed to be after the + # process end date, it should not be releasable anymore + pickings[0].scheduled_date = fields.Datetime.from_string("2023-01-28 00:00:00") + self.assertEqual(pickings[1:], self.channel._get_pickings_to_release()) + + def test_can_edit_time(self): + user = self.env.ref("base.user_demo") + group = self.env.ref("stock.group_stock_manager") + user.groups_id -= group + self.assertFalse(self.channel.with_user(user).process_end_time_can_edit) + + user.groups_id |= self.env.ref("stock.group_stock_manager") + self.assertTrue(self.channel.with_user(user).process_end_time_can_edit) + + @freeze_time("2023-01-27") + def test_channel_end_date_warehouse_timezone(self): + # Set a warehouse with an adress and a timezone on channel + self.channel.warehouse_id = self.env.ref("stock.warehouse0") + self.channel.warehouse_id.partner_id.tz = "Europe/Brussels" + # Set the end time - In UTC == 22:00 + self.channel.process_end_time = 23.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + # Wake up the channel to set the process end date + self.channel.action_wake_up() + self.assertEqual( + "2023-01-27 22:00:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) + + @freeze_time("2023-01-27") + def test_channel_end_date_company_timezone(self): + # Set a warehouse with an adress and a timezone on channel + self.assertFalse(self.channel.warehouse_id) + self.env.company.partner_id.tz = "Europe/Brussels" + # Set the end time - In UTC == 22:00 + self.channel.process_end_time = 23.0 + # Asleep the release channel to void the process end date + self.channel.action_sleep() + self.channel.invalidate_recordset() + # Wake up the channel to set the process end date + self.channel.action_wake_up() + self.assertEqual( + "2023-01-27 22:00:00", + fields.Datetime.to_string(self.channel.process_end_date), + ) diff --git a/stock_release_channel_process_end_time/utils.py b/stock_release_channel_process_end_time/utils.py new file mode 100644 index 0000000000..81e01fe6e4 --- /dev/null +++ b/stock_release_channel_process_end_time/utils.py @@ -0,0 +1,35 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import math +from datetime import datetime, time, timedelta + +import pytz + +from odoo.tools import float_round + +from odoo.addons.partner_tz.tools.tz_utils import tz_to_utc_time + + +def float_to_time(hours, tz=False) -> time: + """This returns a tuple (hours, minutes) from a float representation of time""" + if not tz: + tz = pytz.utc + if hours == 24.0: + return time.max + fractional, integral = math.modf(hours) + time_result = time( + int(integral), int(float_round(60 * fractional, precision_digits=0)), 0 + ) + time_result = tz_to_utc_time(tz, time_result) + return time_result + + +def next_datetime(current: datetime, hours: time, **kwargs) -> datetime: + repl = current.replace( + hour=hours.hour, minute=hours.minute, second=0, microsecond=0, **kwargs + ) + if hours.tzinfo: + repl += hours.utcoffset() + while repl <= current: + repl = repl + timedelta(days=1) + return repl diff --git a/stock_release_channel_process_end_time/views/res_config_settings.xml b/stock_release_channel_process_end_time/views/res_config_settings.xml new file mode 100644 index 0000000000..795efedbd0 --- /dev/null +++ b/stock_release_channel_process_end_time/views/res_config_settings.xml @@ -0,0 +1,29 @@ + + + + stock_release_channel_end_time res.config.settings form + res.config.settings + + +
+
+
+ +
+
+
+
+
+
+
+
diff --git a/stock_release_channel_process_end_time/views/stock_release_channel.xml b/stock_release_channel_process_end_time/views/stock_release_channel.xml new file mode 100644 index 0000000000..474239df95 --- /dev/null +++ b/stock_release_channel_process_end_time/views/stock_release_channel.xml @@ -0,0 +1,108 @@ + + + + + + stock.release.channel.form (in stock_release_channel_process_end_date) + stock.release.channel + + + + + + + + + + + + + + + stock.release.channel.search (in stock_release_channel_process_end_date) + stock.release.channel + + + + + + + + + + + + stock.release.channel.tree (in stock_release_channel_process_end_date) + stock.release.channel + + + + + + + + + + + stock.release.channel.kanban + stock.release.channel + + + +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+ + +