diff --git a/setup/supplier_calendar/odoo/addons/supplier_calendar b/setup/supplier_calendar/odoo/addons/supplier_calendar new file mode 120000 index 00000000000..e7ecab260e1 --- /dev/null +++ b/setup/supplier_calendar/odoo/addons/supplier_calendar @@ -0,0 +1 @@ +../../../../supplier_calendar \ No newline at end of file diff --git a/setup/supplier_calendar/setup.py b/setup/supplier_calendar/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/supplier_calendar/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/supplier_calendar/README.rst b/supplier_calendar/README.rst new file mode 100644 index 00000000000..a99beb60236 --- /dev/null +++ b/supplier_calendar/README.rst @@ -0,0 +1,116 @@ +================= +Supplier Calendar +================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:429613e871df8a7263098638f7d34c4e1a7ac06a6e9a9da6a81ad2427aa9e6e2 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/purchase-workflow/tree/14.0/supplier_calendar + :alt: OCA/purchase-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/purchase-workflow-14-0/purchase-workflow-14-0-supplier_calendar + :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/purchase-workflow&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a Calendar to the ResPartner model. This calendar can then +used as the basis of the proper computation of start/end dates based on the +delivery lead time of the supplier in this and other modules. + +In this module, the calendar is considered in the computation of the schedules +date of a stock picking and in the order date of a purchase order. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Go to *Settings* and activate the developer mode. + +* Go to *Settings > Technical > Resource > Working Time* and define your + resource calendar. + +* Go to *Contacts > Sales&Purchases > Purchase > Delay Calendar Type* + and assign Supplier Calendar and in *Factory Closing Days* assign the + Resource Calendar desired. + +Usage +===== + +When a picking is created from a purchase order of a supplier, the lead +time used to calculate the scheduled date is computed in natural days. At the +same time, when a purchase order is created due to a a procurement +evaluation, its order date is also computed considering the lead time in +natural days. THis module adds the possibility of expressing the lead time +of a vendor in his own calendar. This way, the order dates of purchase +orders and the scheduled dates of receipts will only take into account the +supplier working days. + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + + +* Núria Martín +* Jordi Ballester +* Lois Rilo + +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-LoisRForgeFlow| image:: https://github.com/LoisRForgeFlow.png?size=40px + :target: https://github.com/LoisRForgeFlow + :alt: LoisRForgeFlow + +Current `maintainer `__: + +|maintainer-LoisRForgeFlow| + +This module is part of the `OCA/purchase-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/supplier_calendar/__init__.py b/supplier_calendar/__init__.py new file mode 100644 index 00000000000..5d9d2438128 --- /dev/null +++ b/supplier_calendar/__init__.py @@ -0,0 +1,2 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/supplier_calendar/__manifest__.py b/supplier_calendar/__manifest__.py new file mode 100644 index 00000000000..318b9e30c33 --- /dev/null +++ b/supplier_calendar/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2020 ForgeFlow +# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Supplier Calendar", + "summary": "Supplier Calendar", + "version": "14.0.1.0.0", + "website": "https://github.com/OCA/purchase-workflow", + "category": "Purchase Management", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "maintainers": ["LoisRForgeFlow"], + "license": "LGPL-3", + "application": False, + "installable": True, + "auto_install": False, + "depends": ["purchase_stock", "resource"], + "data": ["views/res_partner_view.xml", "views/product_view.xml"], +} diff --git a/supplier_calendar/i18n/supplier_calendar.pot b/supplier_calendar/i18n/supplier_calendar.pot new file mode 100644 index 00000000000..a8ff24274eb --- /dev/null +++ b/supplier_calendar/i18n/supplier_calendar.pot @@ -0,0 +1,57 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * supplier_calendar +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: supplier_calendar +#: model:ir.model,name:supplier_calendar.model_res_partner +msgid "Contact" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model.fields,field_description:supplier_calendar.field_product_supplierinfo__delay_calendar_type +#: model:ir.model.fields,field_description:supplier_calendar.field_res_partner__delay_calendar_type +#: model:ir.model.fields,field_description:supplier_calendar.field_res_users__delay_calendar_type +msgid "Delay Calendar Type" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model.fields,field_description:supplier_calendar.field_res_partner__factory_calendar_id +#: model:ir.model.fields,field_description:supplier_calendar.field_res_users__factory_calendar_id +msgid "Factory Calendar" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model.fields.selection,name:supplier_calendar.selection__res_partner__delay_calendar_type__natural +msgid "Natural days" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model,name:supplier_calendar.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model,name:supplier_calendar.model_stock_rule +msgid "Stock Rule" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model.fields.selection,name:supplier_calendar.selection__res_partner__delay_calendar_type__supplier_calendar +msgid "Supplier Calendar" +msgstr "" + +#. module: supplier_calendar +#: model:ir.model,name:supplier_calendar.model_product_supplierinfo +msgid "Supplier Pricelist" +msgstr "" diff --git a/supplier_calendar/models/__init__.py b/supplier_calendar/models/__init__.py new file mode 100644 index 00000000000..9fdc3838d39 --- /dev/null +++ b/supplier_calendar/models/__init__.py @@ -0,0 +1,5 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import res_partner +from . import product +from . import stock_rule +from . import purchase diff --git a/supplier_calendar/models/product.py b/supplier_calendar/models/product.py new file mode 100644 index 00000000000..6ee662654eb --- /dev/null +++ b/supplier_calendar/models/product.py @@ -0,0 +1,12 @@ +# Copyright 2020 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductSupplierInfo(models.Model): + _inherit = "product.supplierinfo" + + delay_calendar_type = fields.Selection( + related="name.delay_calendar_type", readonly=True + ) diff --git a/supplier_calendar/models/purchase.py b/supplier_calendar/models/purchase.py new file mode 100644 index 00000000000..c67d929735b --- /dev/null +++ b/supplier_calendar/models/purchase.py @@ -0,0 +1,23 @@ +# Copyright 2020 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from datetime import datetime + +from odoo import api, models + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + @api.model + def _get_date_planned(self, seller, po=False): + date_planned = super(PurchaseOrderLine, self)._get_date_planned(seller, po) + if seller.name.factory_calendar_id: + date_order = po.date_order if po else self.order_id.date_order + if date_order: + date_planned = seller.name.supplier_plan_days(date_order, seller.delay) + else: + date_planned = seller.name.supplier_plan_days( + datetime.today(), seller.delay + ) + return date_planned diff --git a/supplier_calendar/models/res_partner.py b/supplier_calendar/models/res_partner.py new file mode 100644 index 00000000000..1255e51f4f1 --- /dev/null +++ b/supplier_calendar/models/res_partner.py @@ -0,0 +1,51 @@ +# Copyright 2020 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from datetime import datetime, timedelta + +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + factory_calendar_id = fields.Many2one(comodel_name="resource.calendar") + delay_calendar_type = fields.Selection( + [("natural", "Natural days"), ("supplier_calendar", "Supplier Calendar")], + default="natural", + required=True, + ) + + @api.onchange("delay_calendar_type") + def _onchange_delay_calendar_type(self): + for rec in self: + if rec.delay_calendar_type == "natural": + rec.factory_calendar_id = False + + def supplier_plan_days(self, date_from, delta): + """Helper method to calculate supplier delay based on its + working days (if set). + + :param datetime date_from: reference date. + :param integer delta: delay established. + :return: datetime: resulting date. + """ + self.ensure_one() + if not isinstance(date_from, datetime): + date_from = fields.Datetime.to_datetime(date_from) + if delta == 0: + return date_from + if self.factory_calendar_id: + if delta < 0: + # We force the date planned to be at the beginning of the day. + # So no work intervals are found in the reference date. + dt_planned = date_from.replace(hour=0) + else: + # We force the date planned at the end of the day. + dt_planned = date_from.replace(hour=23) + date_result = self.factory_calendar_id.plan_days( + delta, dt_planned, compute_leaves=True + ) + else: + date_result = date_from + timedelta(days=delta) + return date_result diff --git a/supplier_calendar/models/stock_rule.py b/supplier_calendar/models/stock_rule.py new file mode 100644 index 00000000000..285c49747f6 --- /dev/null +++ b/supplier_calendar/models/stock_rule.py @@ -0,0 +1,24 @@ +# Copyright 2020 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from dateutil.relativedelta import relativedelta + +from odoo import fields, models + + +class StockRule(models.Model): + _inherit = "stock.rule" + + def _prepare_purchase_order(self, company_id, origins, values): + res = super()._prepare_purchase_order(company_id, origins, values) + dates = [fields.Datetime.from_string(value["date_planned"]) for value in values] + values = values[0] + partner = values["supplier"].name + procurement_date_planned = min(dates) + schedule_date = procurement_date_planned - relativedelta( + days=company_id.po_lead + ) + delay = -1 * values["supplier"].delay + purchase_date = partner.supplier_plan_days(schedule_date, delay) + res["date_order"] = purchase_date + return res diff --git a/supplier_calendar/readme/CONFIGURE.rst b/supplier_calendar/readme/CONFIGURE.rst new file mode 100644 index 00000000000..2174bb6fd39 --- /dev/null +++ b/supplier_calendar/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ +* Go to *Settings* and activate the developer mode. + +* Go to *Settings > Technical > Resource > Working Time* and define your + resource calendar. + +* Go to *Contacts > Sales&Purchases > Purchase > Delay Calendar Type* + and assign Supplier Calendar and in *Factory Closing Days* assign the + Resource Calendar desired. diff --git a/supplier_calendar/readme/CONTRIBUTORS.rst b/supplier_calendar/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..20c4e69ea5d --- /dev/null +++ b/supplier_calendar/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ + +* Núria Martín +* Jordi Ballester +* Lois Rilo diff --git a/supplier_calendar/readme/DESCRIPTION.rst b/supplier_calendar/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..0c250a6aa20 --- /dev/null +++ b/supplier_calendar/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This module adds a Calendar to the ResPartner model. This calendar can then +used as the basis of the proper computation of start/end dates based on the +delivery lead time of the supplier in this and other modules. + +In this module, the calendar is considered in the computation of the schedules +date of a stock picking and in the order date of a purchase order. diff --git a/supplier_calendar/readme/USAGE.rst b/supplier_calendar/readme/USAGE.rst new file mode 100644 index 00000000000..02daadd61ad --- /dev/null +++ b/supplier_calendar/readme/USAGE.rst @@ -0,0 +1,8 @@ +When a picking is created from a purchase order of a supplier, the lead +time used to calculate the scheduled date is computed in natural days. At the +same time, when a purchase order is created due to a a procurement +evaluation, its order date is also computed considering the lead time in +natural days. THis module adds the possibility of expressing the lead time +of a vendor in his own calendar. This way, the order dates of purchase +orders and the scheduled dates of receipts will only take into account the +supplier working days. diff --git a/supplier_calendar/static/description/icon.png b/supplier_calendar/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/supplier_calendar/static/description/icon.png differ diff --git a/supplier_calendar/static/description/index.html b/supplier_calendar/static/description/index.html new file mode 100644 index 00000000000..187724c9914 --- /dev/null +++ b/supplier_calendar/static/description/index.html @@ -0,0 +1,453 @@ + + + + + + +Supplier Calendar + + + +
+

Supplier Calendar

+ + +

Beta License: LGPL-3 OCA/purchase-workflow Translate me on Weblate Try me on Runboat

+

This module adds a Calendar to the ResPartner model. This calendar can then +used as the basis of the proper computation of start/end dates based on the +delivery lead time of the supplier in this and other modules.

+

In this module, the calendar is considered in the computation of the schedules +date of a stock picking and in the order date of a purchase order.

+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Settings and activate the developer mode.
  • +
  • Go to Settings > Technical > Resource > Working Time and define your +resource calendar.
  • +
  • Go to Contacts > Sales&Purchases > Purchase > Delay Calendar Type +and assign Supplier Calendar and in Factory Closing Days assign the +Resource Calendar desired.
  • +
+
+
+

Usage

+

When a picking is created from a purchase order of a supplier, the lead +time used to calculate the scheduled date is computed in natural days. At the +same time, when a purchase order is created due to a a procurement +evaluation, its order date is also computed considering the lead time in +natural days. THis module adds the possibility of expressing the lead time +of a vendor in his own calendar. This way, the order dates of purchase +orders and the scheduled dates of receipts will only take into account the +supplier working days.

+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

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:

+

LoisRForgeFlow

+

This module is part of the OCA/purchase-workflow project on GitHub.

+

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

+
+
+
+ + diff --git a/supplier_calendar/tests/__init__.py b/supplier_calendar/tests/__init__.py new file mode 100644 index 00000000000..f73ec27681c --- /dev/null +++ b/supplier_calendar/tests/__init__.py @@ -0,0 +1,2 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from . import test_supplier_calendar diff --git a/supplier_calendar/tests/test_supplier_calendar.py b/supplier_calendar/tests/test_supplier_calendar.py new file mode 100644 index 00000000000..2579b84ab4b --- /dev/null +++ b/supplier_calendar/tests/test_supplier_calendar.py @@ -0,0 +1,128 @@ +# Copyright 2020 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +@tagged("post_install", "-at_install") +class TestStockWarehouseCalendar(TransactionCase): + def setUp(self): + super(TestStockWarehouseCalendar, self).setUp() + self.move_obj = self.env["stock.move"] + self.company = self.env.ref("base.main_company") + self.company_partner = self.env.ref("base.main_partner") + self.calendar = self.env.ref("resource.resource_calendar_std") + self.supplier_info = self.env["product.supplierinfo"] + self.PurchaseOrder = self.env["purchase.order"] + self.PurchaseOrderLine = self.env["purchase.order.line"] + self.stock_location = self.env["ir.model.data"].xmlid_to_object( + "stock.stock_location_stock" + ) + self.customer_location = self.env["ir.model.data"].xmlid_to_object( + "stock.stock_location_customers" + ) + self.picking_type_out = self.env["ir.model.data"].xmlid_to_object( + "stock.picking_type_out" + ) + self.route_buy = self.env.ref("purchase_stock.route_warehouse0_buy").id + + # Create product + self.product = self.env["product.product"].create( + { + "name": "test product", + "default_code": "PRD", + "type": "product", + "route_ids": [ + (4, self.ref("stock.route_warehouse0_mto")), + (4, self.ref("purchase_stock.route_warehouse0_buy")), + ], + } + ) + + # Partner and Supplierinfo + self.company_partner.write( + { + "delay_calendar_type": "supplier_calendar", + "factory_calendar_id": self.calendar.id, + } + ) + self.seller_01 = self.supplier_info.create( + { + "name": self.company_partner.id, + "product_id": self.product.id, + "product_tmpl_id": self.product.product_tmpl_id.id, + "delay": 3, + } + ) + + def test_01_purchase_order_with_supplier_calendar(self): + # Create a customer picking + customer_picking = self.env["stock.picking"].create( + { + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + "partner_id": self.company_partner.id, + "picking_type_id": self.picking_type_out.id, + } + ) + + customer_move = self.env["stock.move"].create( + { + "name": "move out", + "location_id": self.stock_location.id, + "location_dest_id": self.customer_location.id, + "product_id": self.product.id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": 80.0, + "procure_method": "make_to_order", + "picking_id": customer_picking.id, + "date": "2097-01-14 09:00:00", # Monday + } + ) + + customer_move._action_confirm() + + purchase_order = self.env["purchase.order"].search( + [("partner_id", "=", self.company_partner.id)] + ) + self.assertTrue(purchase_order, "No purchase order created.") + date_order = fields.Date.to_date(purchase_order.date_order) + wednesday = fields.Date.to_date("2097-01-09 09:00:00") + self.assertEqual(date_order, wednesday) # Wednesday + + def test_02_purchase_order_supplier_calendar_global_leaves(self): + # Global leaves + self.calendar.write( + { + "global_leave_ids": [ + ( + 0, + False, + { + "name": "Test", + "date_from": "2097-01-14", # Monday + "date_to": "2097-01-19", # Saturday + }, + ), + ], + } + ) + + reference = "2097-01-14 09:00:00" # Monday + # With calendar + result = self.company_partner.supplier_plan_days(reference, 3).date() + next_wednesday = fields.Date.to_date("2097-01-23") + self.assertEqual(result, next_wednesday) + reference_2 = "2097-01-11 12:00:00" # friday + result = self.company_partner.supplier_plan_days(reference_2, 3).date() + self.assertEqual(result, next_wednesday) + # Without calendar + self.company_partner.write( + {"delay_calendar_type": "natural", "factory_calendar_id": False} + ) + reference_3 = "2097-01-25 12:00:00" # friday + result = self.company_partner.supplier_plan_days(reference_3, 3).date() + monday = fields.Date.to_date("2097-01-28") + self.assertEqual(result, monday) diff --git a/supplier_calendar/views/product_view.xml b/supplier_calendar/views/product_view.xml new file mode 100644 index 00000000000..bd7ee590fcc --- /dev/null +++ b/supplier_calendar/views/product_view.xml @@ -0,0 +1,15 @@ + + + + + product.supplierinfo.form.view + product.supplierinfo + + + + + + + + diff --git a/supplier_calendar/views/res_partner_view.xml b/supplier_calendar/views/res_partner_view.xml new file mode 100644 index 00000000000..c0891c0966c --- /dev/null +++ b/supplier_calendar/views/res_partner_view.xml @@ -0,0 +1,22 @@ + + + + + partner.factory.calendar.form + res.partner + + + + + + + + +