Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions sale_stock_partner_delivery_window/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==================================
Sale Stock Partner Delivery Window
==================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:75bb9a80a0fa2af909228102b088319b45e6d3dd055d95e797521fdfd19c0bd5
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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/license-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%2Fstock--logistics--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-workflow/tree/19.0/sale_stock_partner_delivery_window
:alt: OCA/stock-logistics-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-workflow-19-0/stock-logistics-workflow-19-0-sale_stock_partner_delivery_window
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Use the partner's "Delivery schedule preference" to compute the Sales
Order's Expected Date, which ends up being propagated to the Delivery.

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-workflow/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 <https://github.com/OCA/stock-logistics-workflow/issues/new?body=module:%20sale_stock_partner_delivery_window%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
-------

* Camptocamp

Contributors
------------

- `Camptocamp <https://www.camptocamp.com>`__

- Iván Todorovich <[email protected]>
- Gaëtan Vaujour <[email protected]>

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-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px
:target: https://github.com/ivantodorovich
:alt: ivantodorovich

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-ivantodorovich|

This module is part of the `OCA/stock-logistics-workflow <https://github.com/OCA/stock-logistics-workflow/tree/19.0/sale_stock_partner_delivery_window>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_stock_partner_delivery_window/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions sale_stock_partner_delivery_window/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2026 Camptocamp SA (https://www.camptocamp.com).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Sale Stock Partner Delivery Window",
"summary": "Use the partner's 'Delivery schedule preference' in Sales Orders",
"version": "19.0.1.0.0",
"author": "Camptocamp, Odoo Community Association (OCA)",
"maintainers": ["ivantodorovich"],
"website": "https://github.com/OCA/stock-logistics-workflow",
"license": "AGPL-3",
"category": "Sales",
"depends": ["sale_stock", "stock_partner_delivery_window"],
"auto_install": True,
}
2 changes: 2 additions & 0 deletions sale_stock_partner_delivery_window/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import res_partner
from . import sale_order_line
82 changes: 82 additions & 0 deletions sale_stock_partner_delivery_window/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2026 Camptocamp SA (https://www.camptocamp.com).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from datetime import date, datetime, timedelta

import pytz

from odoo import fields, models
from odoo.exceptions import UserError
from odoo.tools.date_utils import start_of


class ResPartner(models.Model):
_inherit = "res.partner"

def _next_available_delivery_date(
self, from_date: date | datetime | None = None
) -> datetime:
"""Compute the next available delivery date"""
# If from_date is not provided, use the current datetime
if from_date is None: # pragma: no cover
from_date = fields.Datetime.now()
# Pre-compute the from_datetime, in case from_date is a `date`
# We use the start of the day in the partner's timezone
tz = pytz.timezone(self.tz or self.env.company.partner_id.tz or "UTC")
from_datetime_tz_aware = tz.localize(fields.Datetime.to_datetime(from_date))
from_datetime = from_datetime_tz_aware.astimezone(pytz.utc).replace(tzinfo=None)
# If the delivery is anytime, simply return the from_datetime
if self.delivery_time_preference == "anytime":
return from_datetime
# If it's workdays, we need to check if the from_datetime is a weekday
# or adjust accordingly
elif self.delivery_time_preference == "workdays":
weekday = from_datetime_tz_aware.weekday()
if weekday <= 4:
return from_datetime
return from_datetime + timedelta(days=7 - weekday)
# If we're using time windows, we search for the next available slot
# We use the tz-aware datetime, as windows are expressed in the partner's tz
elif self.delivery_time_preference == "time_windows":
for days_to_add in range(7):
next_date = (
start_of(
from_datetime_tz_aware + timedelta(days=days_to_add), "day"
)
if days_to_add
else from_datetime_tz_aware
)
for window in self.delivery_time_window_ids:
# Check if the window is available for this day
weekdays = set(
map(int, window.time_window_weekday_ids.mapped("name"))
)
if next_date.weekday() not in weekdays:
continue
start_time = window.get_time_window_start_time()
end_time = window.get_time_window_end_time()
# If we're adding days (evaluating today), and we're providing time,
# we must ensure it's within the window's time range, or at least
# the day range hasn't passed yet
if not days_to_add:
if not isinstance(from_date, datetime):
return from_datetime
elif start_time <= from_datetime_tz_aware.time() <= end_time:
return from_datetime
elif from_datetime_tz_aware.time() > end_time:
continue
# Otherwise, since we're looking at days ahead, simply pick the
# window's start time
return (
datetime.combine(next_date, start_time)
.astimezone(pytz.utc)
.replace(tzinfo=None)
)
else: # pragma: no cover
raise ValueError(
self.env._(
"Invalid delivery time preference: %s",
self.delivery_time_preference,
)
)
raise UserError(self.env._("No available delivery date found"))
24 changes: 24 additions & 0 deletions sale_stock_partner_delivery_window/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 Camptocamp SA (https://www.camptocamp.com).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

def _expected_date(self):
# OVERRIDE to account for the partner's delivery schedule preference
# The original method, called with `super()`, returns the earliest deliverable
# date based on our customer lead time.
# We want to pick up on that and compare against the partner's delivery schedule
# preference. If it doesn't match, we will postpone the expected date to the
# next available time window.
expected_date = super()._expected_date()
partner = self.order_id.partner_id
return partner._next_available_delivery_date(expected_date)

@api.depends("order_id.partner_id")
def _compute_qty_at_date(self):
# OVERRIDE to add the `partner_id` to the dependencies
return super()._compute_qty_at_date()
3 changes: 3 additions & 0 deletions sale_stock_partner_delivery_window/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
3 changes: 3 additions & 0 deletions sale_stock_partner_delivery_window/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [Camptocamp](https://www.camptocamp.com)
- Iván Todorovich \<<[email protected]>\>
- Gaëtan Vaujour \<<[email protected]>\>
2 changes: 2 additions & 0 deletions sale_stock_partner_delivery_window/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Use the partner's "Delivery schedule preference" to compute the Sales Order's
Expected Date, which ends up being propagated to the Delivery.
Loading
Loading