diff --git a/joint_buying_base/__manifest__.py b/joint_buying_base/__manifest__.py index 04b3b6a1..4e80430f 100644 --- a/joint_buying_base/__manifest__.py +++ b/joint_buying_base/__manifest__.py @@ -11,6 +11,8 @@ "depends": [ "base", "mail", + "web", + "decimal_precision", # OCA "base_geolocalize_company", "res_company_code", @@ -21,7 +23,9 @@ # GRAP "name_search_reset_res_partner", ], - "external_dependencies": {"python": ["openupgradelib", "geopy", "bokeh", "pandas"]}, + "external_dependencies": { + "python": ["openupgradelib", "geopy", "bokeh", "pandas", "treelib"] + }, "data": [ "security/ir_module_category.xml", "security/res_groups.xml", @@ -38,7 +42,12 @@ "views/view_joint_buying_tour_type.xml", "views/view_joint_buying_tour.xml", "views/view_joint_buying_tour_line.xml", + "views/view_joint_buying_transport_request.xml", + "views/view_joint_buying_transport_request_line.xml", "wizards/view_joint_buying_wizard_set_tour.xml", + "wizards/joint_buying_wizard_find_route.xml", + "reports/report_joint_buying_tour.xml", + "reports/report.xml", "views/templates.xml", ], "demo": [ @@ -49,6 +58,7 @@ "demo/joint_buying_carrier.xml", "demo/joint_buying_tour_type.xml", "demo/joint_buying_tour.xml", + "demo/joint_buying_transport_request.xml", ], "post_init_hook": "_create_joint_buying_partner_for_companies", "installable": True, diff --git a/joint_buying_product/demo/joint_buying_transport_request.xml b/joint_buying_base/demo/joint_buying_transport_request.xml similarity index 87% rename from joint_buying_product/demo/joint_buying_transport_request.xml rename to joint_buying_base/demo/joint_buying_transport_request.xml index f7940a0e..ac741e88 100644 --- a/joint_buying_product/demo/joint_buying_transport_request.xml +++ b/joint_buying_base/demo/joint_buying_transport_request.xml @@ -42,4 +42,14 @@ 33 + + + + diff --git a/joint_buying_base/demo/res_partner.xml b/joint_buying_base/demo/res_partner.xml index 0fc7b424..2efb0752 100644 --- a/joint_buying_base/demo/res_partner.xml +++ b/joint_buying_base/demo/res_partner.xml @@ -7,7 +7,7 @@ - Joint Buying Supplier for Your Company / Demo User + Joint Buying Supplier for Your Company street street 2 City diff --git a/joint_buying_base/models/__init__.py b/joint_buying_base/models/__init__.py index d5c93963..7f073775 100644 --- a/joint_buying_base/models/__init__.py +++ b/joint_buying_base/models/__init__.py @@ -10,3 +10,5 @@ from . import joint_buying_tour_type from . import joint_buying_tour from . import joint_buying_tour_line +from . import joint_buying_transport_request +from . import joint_buying_transport_request_line diff --git a/joint_buying_base/models/joint_buying_tour_line.py b/joint_buying_base/models/joint_buying_tour_line.py index 5905457f..0445fe31 100644 --- a/joint_buying_base/models/joint_buying_tour_line.py +++ b/joint_buying_base/models/joint_buying_tour_line.py @@ -7,6 +7,8 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError +from odoo.addons import decimal_precision as dp + from .res_partner import _JOINT_BUYING_PARTNER_CONTEXT _TOUR_LINE_SEQUENCE_TYPES = [ @@ -64,6 +66,17 @@ class JointBuyingTourLine(models.Model): compute="_compute_costs", store=True, currency_field="currency_id" ) + transport_request_line_ids = fields.One2many( + comodel_name="joint.buying.transport.request.line", + string="Transport Lines", + inverse_name="tour_line_id", + ) + + load = fields.Float( + compute="_compute_load", + digits=dp.get_precision("Stock Weight"), + ) + @api.depends("start_date", "arrival_date") def _compute_hours(self): for line in self: @@ -86,6 +99,12 @@ def _compute_costs(self): line.salary_cost = line.duration * line.tour_id.hourly_cost line.vehicle_cost = +line.distance * line.tour_id.kilometer_cost + def _compute_load(self): + for tour_line in self: + tour_line.load = sum( + tour_line.mapped("transport_request_line_ids.request_id.total_weight") + ) + def _estimate_route_project_osrm(self): self.ensure_one() url = ( diff --git a/joint_buying_base/models/joint_buying_transport_request.py b/joint_buying_base/models/joint_buying_transport_request.py new file mode 100644 index 00000000..b4603606 --- /dev/null +++ b/joint_buying_base/models/joint_buying_transport_request.py @@ -0,0 +1,202 @@ +# Copyright (C) 2023-Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + +from odoo.addons import decimal_precision as dp + +from .res_partner import _JOINT_BUYING_PARTNER_CONTEXT + + +class JointBuyingTransportRequest(models.Model): + _name = "joint.buying.transport.request" + _description = "Joint Buying Transport Request" + + name = fields.Char(readonly=True, compute="_compute_name", store=True) + + state = fields.Selection( + selection=[ + ("to_compute", "To Compute"), + ("computed", "Computed"), + ("not_computable", "Not Computable"), + ], + required=True, + readonly=True, + default="to_compute", + ) + + manual_start_date = fields.Datetime( + string="Start Date (Manual)", + ) + + start_date = fields.Datetime( + string="Start Date", + compute="_compute_start_date", + store=True, + ) + + manual_origin_partner_id = fields.Many2one( + comodel_name="res.partner", + string="Origin (Manual)", + context=_JOINT_BUYING_PARTNER_CONTEXT, + domain="[('is_joint_buying_stage', '=', True)]", + ) + + origin_partner_id = fields.Many2one( + comodel_name="res.partner", + compute="_compute_origin_partner_id", + string="Origin", + store=True, + context=_JOINT_BUYING_PARTNER_CONTEXT, + ) + + manual_destination_partner_id = fields.Many2one( + comodel_name="res.partner", + string="Destination (Manual)", + context=_JOINT_BUYING_PARTNER_CONTEXT, + domain="[('is_joint_buying_stage', '=', True)]", + ) + + destination_partner_id = fields.Many2one( + comodel_name="res.partner", + compute="_compute_destination_partner_id", + string="Destination", + store=True, + context=_JOINT_BUYING_PARTNER_CONTEXT, + ) + + manual_amount_untaxed = fields.Float( + string="Untaxed Amount (Manual)", + digits=dp.get_precision("Product Price"), + ) + + amount_untaxed = fields.Float( + string="Untaxed Amount", + compute="_compute_amount_untaxed", + store=True, + digits=dp.get_precision("Product Price"), + ) + + manual_total_weight = fields.Float( + string="Weight (Manual)", + digits=dp.get_precision("Stock Weight"), + ) + + total_weight = fields.Float( + string="Weight", + compute="_compute_weight", + store=True, + digits=dp.get_precision("Stock Weight"), + ) + + line_ids = fields.One2many( + comodel_name="joint.buying.transport.request.line", + string="Transport Lines", + inverse_name="request_id", + ) + + arrival_date = fields.Datetime(string="Arrival Date", readonly=True) + + manual_description = fields.Html() + + def _get_depends_start_date(self): + return ["manual_start_date"] + + def _get_depends_origin_partner_id(self): + return ["manual_origin_partner_id"] + + def _get_depends_destination_partner_id(self): + return ["manual_destination_partner_id"] + + def _get_depends_amount_untaxed(self): + return ["manual_amount_untaxed"] + + def _get_depends_total_weight(self): + return ["manual_total_weight"] + + @api.depends("origin_partner_id", "destination_partner_id", "start_date") + def _compute_name(self): + for request in self: + request.name = ( + f"{request.origin_partner_id.joint_buying_code}" + f" -> {request.destination_partner_id.joint_buying_code}" + f" ({request.start_date})" + ) + + @api.depends(lambda x: x._get_depends_start_date()) + def _compute_start_date(self): + for request in self: + request.start_date = request.manual_start_date + + @api.depends(lambda x: x._get_depends_origin_partner_id()) + def _compute_origin_partner_id(self): + for request in self: + request.origin_partner_id = request.manual_origin_partner_id + + @api.depends(lambda x: x._get_depends_destination_partner_id()) + def _compute_destination_partner_id(self): + for request in self: + request.destination_partner_id = request.manual_destination_partner_id + + @api.depends(lambda x: x._get_depends_amount_untaxed()) + def _compute_amount_untaxed(self): + for request in self: + request.amount_untaxed = request.manual_amount_untaxed + + @api.depends(lambda x: x._get_depends_total_weight()) + def _compute_weight(self): + for request in self: + request.total_weight = request.manual_total_weight + + def _set_tour_lines(self, tour_lines): + self.ensure_one() + # If lines are valid + if ( + tour_lines + and tour_lines[-1].arrival_point_id == self.destination_partner_id + ): + line_vals = [] + previous_tour_id = False + for i, tour_line in enumerate(tour_lines): + # Compute if it is a loading at the beginning of the tour line + if previous_tour_id != tour_line.tour_id.id: + start_action_type = "loading" + else: + start_action_type = "no" + previous_tour_id = tour_line.tour_id.id + + # Compute if it is an unloading at the end of the tour line + if i < len(tour_lines) - 1: + if tour_line.tour_id != tour_lines[i + 1].tour_id: + arrival_action_type = "unloading" + else: + arrival_action_type = "no" + else: + arrival_action_type = "unloading" + line_vals.append( + { + "start_action_type": start_action_type, + "arrival_action_type": arrival_action_type, + "tour_line_id": tour_line.id, + } + ) + + vals = { + "arrival_date": max(tour_lines.mapped("arrival_date")), + "state": "computed", + "line_ids": [(5,)] + [(0, 0, x) for x in line_vals], + } + else: + vals = { + "arrival_date": False, + "state": "not_computable", + "line_ids": [(5,)], + } + self.write(vals) + + def button_compute_tour(self): + Wizard = self.env["joint.buying.wizard.find.route"] + results = Wizard.compute_tours(self) + for request in self: + request._set_tour_lines(results[request][1]) diff --git a/joint_buying_product/models/joint_buying_transport_request_line.py b/joint_buying_base/models/joint_buying_transport_request_line.py similarity index 95% rename from joint_buying_product/models/joint_buying_transport_request_line.py rename to joint_buying_base/models/joint_buying_transport_request_line.py index 257a9288..021c788c 100644 --- a/joint_buying_product/models/joint_buying_transport_request_line.py +++ b/joint_buying_base/models/joint_buying_transport_request_line.py @@ -4,9 +4,7 @@ from odoo import fields, models -from odoo.addons.joint_buying_base.models.res_partner import ( - _JOINT_BUYING_PARTNER_CONTEXT, -) +from .res_partner import _JOINT_BUYING_PARTNER_CONTEXT class JointBuyingTransportRequestLine(models.Model): diff --git a/joint_buying_base/reports/report.xml b/joint_buying_base/reports/report.xml new file mode 100644 index 00000000..13c8e173 --- /dev/null +++ b/joint_buying_base/reports/report.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/joint_buying_product/reports/report_joint_buying_tour.xml b/joint_buying_base/reports/report_joint_buying_tour.xml similarity index 95% rename from joint_buying_product/reports/report_joint_buying_tour.xml rename to joint_buying_base/reports/report_joint_buying_tour.xml index 9bdcc403..d7915e2f 100644 --- a/joint_buying_product/reports/report_joint_buying_tour.xml +++ b/joint_buying_base/reports/report_joint_buying_tour.xml @@ -154,8 +154,8 @@

Loading

- - + @@ -176,8 +176,8 @@

Unloading

- - + @@ -189,7 +189,7 @@ diff --git a/joint_buying_base/security/ir.model.access.csv b/joint_buying_base/security/ir.model.access.csv index a532d77e..700dcf90 100644 --- a/joint_buying_base/security/ir.model.access.csv +++ b/joint_buying_base/security/ir.model.access.csv @@ -6,6 +6,8 @@ access_joint_buying_tour_user,access_joint_buying_tour_user,model_joint_buying_t access_joint_buying_tour_manager,access_joint_buying_tour_manager,model_joint_buying_tour,joint_buying_base.group_joint_buying_manager,1,1,1,1 access_joint_buying_tour_line_user,access_joint_buying_tour_line_user,model_joint_buying_tour_line,joint_buying_base.group_joint_buying_user,1,,, access_joint_buying_tour_line_manager,access_joint_buying_tour_line_manager,model_joint_buying_tour_line,joint_buying_base.group_joint_buying_manager,1,1,1,1 +access_joint_buying_transport_request_user,access_joint_buying_transport_request_user,model_joint_buying_transport_request,joint_buying_base.group_joint_buying_user,1,1,1,1 +access_joint_buying_transport_request_line_user,access_joint_buying_transport_request_line_user,model_joint_buying_transport_request_line,joint_buying_base.group_joint_buying_user,1,1,1,1 access_joint_buying_carrier_user,access_joint_buying_carrier_user,model_joint_buying_carrier,joint_buying_base.group_joint_buying_user,1,,, access_joint_buying_carrier_manager,access_joint_buying_carrier_manager,model_joint_buying_carrier,joint_buying_base.group_joint_buying_manager,1,1,1,1 access_joint_buying_tour_type_user,access_joint_buying_tour_type_user,model_joint_buying_tour_type,joint_buying_base.group_joint_buying_user,1,,, diff --git a/joint_buying_base/tests/__init__.py b/joint_buying_base/tests/__init__.py index 8407548c..bb2b96f6 100644 --- a/joint_buying_base/tests/__init__.py +++ b/joint_buying_base/tests/__init__.py @@ -4,4 +4,5 @@ from . import test_joint_buying_mixin from . import test_partner_global_local from . import test_partner_subscription +from . import test_joint_buying_wizard_find_route from . import test_tour diff --git a/joint_buying_product/tests/test_joint_buying_wizard_find_route.py b/joint_buying_base/tests/test_joint_buying_wizard_find_route.py similarity index 89% rename from joint_buying_product/tests/test_joint_buying_wizard_find_route.py rename to joint_buying_base/tests/test_joint_buying_wizard_find_route.py index 01ceca4d..5f4be7e2 100644 --- a/joint_buying_product/tests/test_joint_buying_wizard_find_route.py +++ b/joint_buying_base/tests/test_joint_buying_wizard_find_route.py @@ -16,7 +16,7 @@ def setUp(self): def test_20_transport_request_vev_cda_week_1(self): """simplest case: direct route""" self._verify_tour_lines_computation( - "joint_buying_product.request_vev_cda_week_1", + "joint_buying_base.request_vev_cda_week_1", ["joint_buying_base.tour_lyon_loire_1_line_4"], "computed", ) @@ -24,7 +24,7 @@ def test_20_transport_request_vev_cda_week_1(self): def test_21_transport_request_vev_che_week_1(self): """Classic case: 1 change""" self._verify_tour_lines_computation( - "joint_buying_product.request_vev_che_week_1", + "joint_buying_base.request_vev_che_week_1", [ "joint_buying_base.tour_lyon_loire_1_line_4", "joint_buying_base.tour_lyon_loire_1_line_6", @@ -37,7 +37,7 @@ def test_21_transport_request_vev_che_week_1(self): def test_22_transport_request_vev_edc_1(self): """Classic case: 2 change""" self._verify_tour_lines_computation( - "joint_buying_product.request_vev_edc_week_1", + "joint_buying_base.request_vev_edc_week_1", [ "joint_buying_base.tour_lyon_loire_1_line_4", "joint_buying_base.tour_lyon_loire_1_line_6", @@ -50,13 +50,13 @@ def test_22_transport_request_vev_edc_1(self): def test_23_transport_request_vev_fumet_week_1(self): """Use case: No route available""" self._verify_tour_lines_computation( - "joint_buying_product.request_vev_fumet_dombes_week_1", [], "not_computable" + "joint_buying_base.request_vev_fumet_dombes_week_1", [], "not_computable" ) def test_24_transport_request_vev_che_week_2(self): """Complex case: a later start arrives earlier""" self._verify_tour_lines_computation( - "joint_buying_product.request_vev_che_week_2", + "joint_buying_base.request_vev_che_week_2", [ "joint_buying_base.tour_lyon_loire_3_line_2", "joint_buying_base.tour_lyon_drome_2_line_2", diff --git a/joint_buying_base/tests/test_tour.py b/joint_buying_base/tests/test_tour.py index 11f499fd..e1e4da62 100644 --- a/joint_buying_base/tests/test_tour.py +++ b/joint_buying_base/tests/test_tour.py @@ -20,6 +20,9 @@ def setUp(self): "joint_buying_base.company_3PP" ).joint_buying_partner_id self.tour_lyon_savoie = self.env.ref("joint_buying_base.tour_lyon_savoie_1") + self.tour_report = self.env.ref( + "joint_buying_base.action_report_joint_buying_tour" + ) # Test Section def _assert_not_very_differrent(self, value_1, value_2): @@ -99,3 +102,8 @@ def test_104_check_cost_chart(self): self.assertIn("Salary", self.tour_lyon_savoie.cost_chart) self.assertIn("Vehicle", self.tour_lyon_savoie.cost_chart) self.assertIn("Toll", self.tour_lyon_savoie.cost_chart) + + def test_110_report(self): + # Generate report to make sure the syntax is correct + tours = self.JointBuyingTour.search([]) + self.tour_report.render_qweb_html(tours.ids) diff --git a/joint_buying_base/views/menu.xml b/joint_buying_base/views/menu.xml index afea641d..39d200b2 100644 --- a/joint_buying_base/views/menu.xml +++ b/joint_buying_base/views/menu.xml @@ -12,6 +12,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). groups="group_joint_buying_user" /> + + - @@ -36,7 +35,6 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - @@ -56,12 +54,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). />