diff --git a/fleet_vehicle_analytic_purchase/README.rst b/fleet_vehicle_analytic_purchase/README.rst new file mode 100644 index 00000000..cb0e38a2 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/README.rst @@ -0,0 +1,120 @@ +=============================== +Fleet Vehicle Analytic Purchase +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:701e9ec0a02919199987496264b1b75d1a12413369d3d3fefe9306a5adae49d0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Ffleet-lightgray.png?logo=github + :target: https://github.com/OCA/fleet/tree/18.0/fleet_vehicle_analytic_purchase + :alt: OCA/fleet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/fleet-18-0/fleet-18-0-fleet_vehicle_analytic_purchase + :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/fleet&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of fleet management to link each +vehicle to one or more analytic accounts (cost centers), using the same +analytic distribution field used everywhere else in Odoo accounting. It +also gives direct access, from the vehicle form, to every purchase order +that shares an analytic account with the vehicle. + +It also makes the analytic accounts of a purchase order searchable from +the user interface, reusing the ``analytic_distribution`` field that the +OCA ``purchase_analytic`` module already adds on the purchase order +header. In Odoo that field is stored as JSON, which is not searchable +from a regular search view out of the box; the ``analytic.mixin`` it +relies on already solves that by exposing a computed and searchable +``distribution_analytic_account_ids`` field, which this module simply +adds to the Requests for Quotation and Purchase Orders search views. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module you need to: + +1. Go to *Fleet > Vehicles* and open a vehicle. +2. In the *Vehicle* group, set the **Analytic Distribution** of the + vehicle (its cost center or centers, with the percentage of each + one). +3. A **Purchase Orders** smart button appears at the top of the form. It + shows how many purchase orders have at least one line whose analytic + distribution shares an analytic account with the vehicle, and opens + them when clicked. + +To filter purchase orders by analytic account: + +1. Go to *Purchase > Orders > Purchase Orders* (or *Requests for + Quotation*). +2. Type the name of an analytic account in the search box and pick the + **Analytic Account** entry, or add it from the filter dropdown. + +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 +------- + +* Escodoo + +Contributors +------------ + +- `Escodoo `__: + + - Cristiano Mafra Junior + +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-marcelsavegnago| image:: https://github.com/marcelsavegnago.png?size=40px + :target: https://github.com/marcelsavegnago + :alt: marcelsavegnago + +Current `maintainer `__: + +|maintainer-marcelsavegnago| + +This module is part of the `OCA/fleet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fleet_vehicle_analytic_purchase/__init__.py b/fleet_vehicle_analytic_purchase/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/fleet_vehicle_analytic_purchase/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/fleet_vehicle_analytic_purchase/__manifest__.py b/fleet_vehicle_analytic_purchase/__manifest__.py new file mode 100644 index 00000000..fe94b6ca --- /dev/null +++ b/fleet_vehicle_analytic_purchase/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2026 - TODAY, Cristiano Mafra Junior +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Fleet Vehicle Analytic Purchase", + "summary": """ + Link vehicles to analytic accounts (cost centers) and browse the + purchase orders of those cost centers from the vehicle form.""", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "category": "Human Resources/Fleet", + "author": "Escodoo,Odoo Community Association (OCA)", + "maintainers": ["marcelsavegnago"], + "website": "https://github.com/OCA/fleet", + "depends": [ + "analytic", + "fleet", + "purchase", + "purchase_analytic", + ], + "data": [ + "views/fleet_vehicle.xml", + "views/purchase_order.xml", + ], + "installable": True, + "auto_install": False, +} diff --git a/fleet_vehicle_analytic_purchase/models/__init__.py b/fleet_vehicle_analytic_purchase/models/__init__.py new file mode 100644 index 00000000..53bbf7bd --- /dev/null +++ b/fleet_vehicle_analytic_purchase/models/__init__.py @@ -0,0 +1 @@ +from . import fleet_vehicle diff --git a/fleet_vehicle_analytic_purchase/models/fleet_vehicle.py b/fleet_vehicle_analytic_purchase/models/fleet_vehicle.py new file mode 100644 index 00000000..ae900ad7 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/models/fleet_vehicle.py @@ -0,0 +1,59 @@ +# Copyright 2026 - TODAY, Cristiano Mafra Junior +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from collections import defaultdict + +from odoo import api, fields, models + + +class FleetVehicle(models.Model): + _name = "fleet.vehicle" + _inherit = ["fleet.vehicle", "analytic.mixin"] + + purchase_order_count = fields.Integer( + string="Purchase Orders", + compute="_compute_purchase_order_count", + ) + + @api.depends("distribution_analytic_account_ids") + def _compute_purchase_order_count(self): + self.purchase_order_count = 0 + vehicles = self.filtered("distribution_analytic_account_ids") + if not vehicles: + return + lines = self.env["purchase.order.line"].search( + [ + ( + "distribution_analytic_account_ids", + "in", + vehicles.distribution_analytic_account_ids.ids, + ) + ] + ) + orders_per_account = defaultdict(set) + for line in lines: + for account in line.distribution_analytic_account_ids: + orders_per_account[account.id].add(line.order_id.id) + for vehicle in vehicles: + order_ids = set() + for account in vehicle.distribution_analytic_account_ids: + order_ids |= orders_per_account[account.id] + vehicle.purchase_order_count = len(order_ids) + + def action_view_purchase_orders(self): + self.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "purchase.purchase_form_action" + ) + action.update( + { + "domain": [ + ( + "order_line.distribution_analytic_account_ids", + "in", + self.distribution_analytic_account_ids.ids, + ) + ], + } + ) + return action diff --git a/fleet_vehicle_analytic_purchase/pyproject.toml b/fleet_vehicle_analytic_purchase/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/fleet_vehicle_analytic_purchase/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/fleet_vehicle_analytic_purchase/readme/CONTRIBUTORS.md b/fleet_vehicle_analytic_purchase/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..1b182270 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Escodoo](https://www.escodoo.com.br): + - Cristiano Mafra Junior \<\> diff --git a/fleet_vehicle_analytic_purchase/readme/DESCRIPTION.md b/fleet_vehicle_analytic_purchase/readme/DESCRIPTION.md new file mode 100644 index 00000000..2e00242e --- /dev/null +++ b/fleet_vehicle_analytic_purchase/readme/DESCRIPTION.md @@ -0,0 +1,14 @@ +This module extends the functionality of fleet management to link each vehicle +to one or more analytic accounts (cost centers), using the same analytic +distribution field used everywhere else in Odoo accounting. It also gives +direct access, from the vehicle form, to every purchase order that shares an +analytic account with the vehicle. + +It also makes the analytic accounts of a purchase order searchable from the +user interface, reusing the `analytic_distribution` field that the OCA +`purchase_analytic` module already adds on the purchase order header. In Odoo +that field is stored as JSON, which is not searchable from a regular search +view out of the box; the `analytic.mixin` it relies on already solves that by +exposing a computed and searchable `distribution_analytic_account_ids` field, +which this module simply adds to the Requests for Quotation and Purchase +Orders search views. diff --git a/fleet_vehicle_analytic_purchase/readme/USAGE.md b/fleet_vehicle_analytic_purchase/readme/USAGE.md new file mode 100644 index 00000000..97c8c551 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/readme/USAGE.md @@ -0,0 +1,15 @@ +To use this module you need to: + +1. Go to *Fleet > Vehicles* and open a vehicle. +2. In the *Vehicle* group, set the **Analytic Distribution** of the vehicle + (its cost center or centers, with the percentage of each one). +3. A **Purchase Orders** smart button appears at the top of the form. It shows + how many purchase orders have at least one line whose analytic + distribution shares an analytic account with the vehicle, and opens them + when clicked. + +To filter purchase orders by analytic account: + +1. Go to *Purchase > Orders > Purchase Orders* (or *Requests for Quotation*). +2. Type the name of an analytic account in the search box and pick the + **Analytic Account** entry, or add it from the filter dropdown. diff --git a/fleet_vehicle_analytic_purchase/static/description/icon.png b/fleet_vehicle_analytic_purchase/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/fleet_vehicle_analytic_purchase/static/description/icon.png differ diff --git a/fleet_vehicle_analytic_purchase/static/description/index.html b/fleet_vehicle_analytic_purchase/static/description/index.html new file mode 100644 index 00000000..5a857187 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/static/description/index.html @@ -0,0 +1,462 @@ + + + + + +Fleet Vehicle Analytic Purchase + + + +
+

Fleet Vehicle Analytic Purchase

+ + +

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

+

This module extends the functionality of fleet management to link each +vehicle to one or more analytic accounts (cost centers), using the same +analytic distribution field used everywhere else in Odoo accounting. It +also gives direct access, from the vehicle form, to every purchase order +that shares an analytic account with the vehicle.

+

It also makes the analytic accounts of a purchase order searchable from +the user interface, reusing the analytic_distribution field that the +OCA purchase_analytic module already adds on the purchase order +header. In Odoo that field is stored as JSON, which is not searchable +from a regular search view out of the box; the analytic.mixin it +relies on already solves that by exposing a computed and searchable +distribution_analytic_account_ids field, which this module simply +adds to the Requests for Quotation and Purchase Orders search views.

+

Table of contents

+ +
+

Usage

+

To use this module you need to:

+
    +
  1. Go to Fleet > Vehicles and open a vehicle.
  2. +
  3. In the Vehicle group, set the Analytic Distribution of the +vehicle (its cost center or centers, with the percentage of each +one).
  4. +
  5. A Purchase Orders smart button appears at the top of the form. It +shows how many purchase orders have at least one line whose analytic +distribution shares an analytic account with the vehicle, and opens +them when clicked.
  6. +
+

To filter purchase orders by analytic account:

+
    +
  1. Go to Purchase > Orders > Purchase Orders (or Requests for +Quotation).
  2. +
  3. Type the name of an analytic account in the search box and pick the +Analytic Account entry, or add it from the filter dropdown.
  4. +
+
+
+

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

+
    +
  • Escodoo
  • +
+
+
+

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:

+

marcelsavegnago

+

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

+

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

+
+
+
+ + diff --git a/fleet_vehicle_analytic_purchase/tests/__init__.py b/fleet_vehicle_analytic_purchase/tests/__init__.py new file mode 100644 index 00000000..34c6a83d --- /dev/null +++ b/fleet_vehicle_analytic_purchase/tests/__init__.py @@ -0,0 +1 @@ +from . import test_fleet_vehicle_analytic_purchase diff --git a/fleet_vehicle_analytic_purchase/tests/test_fleet_vehicle_analytic_purchase.py b/fleet_vehicle_analytic_purchase/tests/test_fleet_vehicle_analytic_purchase.py new file mode 100644 index 00000000..9f24e874 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/tests/test_fleet_vehicle_analytic_purchase.py @@ -0,0 +1,99 @@ +# Copyright 2026 - TODAY, Cristiano Mafra Junior +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestFleetVehicleAnalyticPurchase(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.plan = cls.env["account.analytic.plan"].create({"name": "Test Plan"}) + cls.account = cls.env["account.analytic.account"].create( + {"name": "Cost Center A", "plan_id": cls.plan.id} + ) + cls.other_account = cls.env["account.analytic.account"].create( + {"name": "Cost Center B", "plan_id": cls.plan.id} + ) + cls.brand = cls.env["fleet.vehicle.model.brand"].create({"name": "Audi"}) + cls.model = cls.env["fleet.vehicle.model"].create( + {"brand_id": cls.brand.id, "name": "A3"} + ) + cls.vehicle = cls.env["fleet.vehicle"].create( + { + "model_id": cls.model.id, + "driver_id": cls.env.user.partner_id.id, + "plan_to_change_car": False, + "analytic_distribution": {str(cls.account.id): 100}, + } + ) + cls.product = cls.env["product.product"].create( + {"name": "product", "type": "service"} + ) + + @classmethod + def _create_purchase_order(cls, distribution): + return cls.env["purchase.order"].create( + { + "partner_id": cls.env.user.partner_id.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": cls.product.id, + "name": cls.product.name, + "product_qty": 1.0, + "price_unit": 100.0, + "analytic_distribution": distribution, + }, + ) + ], + } + ) + + def test_purchase_order_count_without_orders(self): + self.assertEqual(self.vehicle.purchase_order_count, 0) + + def test_purchase_order_count_without_analytic_distribution(self): + self._create_purchase_order({str(self.account.id): 100}) + self.vehicle.analytic_distribution = False + self.vehicle.invalidate_recordset(["purchase_order_count"]) + self.assertEqual(self.vehicle.purchase_order_count, 0) + + def test_purchase_order_count_and_action(self): + order = self._create_purchase_order({str(self.account.id): 100}) + self._create_purchase_order({str(self.other_account.id): 100}) + self.vehicle.invalidate_recordset(["purchase_order_count"]) + self.assertEqual(self.vehicle.purchase_order_count, 1) + action = self.vehicle.action_view_purchase_orders() + self.assertEqual(self.env[action["res_model"]].search(action["domain"]), order) + + def test_purchase_order_count_multiple_analytic_accounts(self): + self.vehicle.analytic_distribution = { + str(self.account.id): 50, + str(self.other_account.id): 50, + } + order_a = self._create_purchase_order({str(self.account.id): 100}) + order_b = self._create_purchase_order({str(self.other_account.id): 100}) + self.vehicle.invalidate_recordset(["purchase_order_count"]) + self.assertEqual(self.vehicle.purchase_order_count, 2) + action = self.vehicle.action_view_purchase_orders() + self.assertEqual( + self.env[action["res_model"]].search(action["domain"]), + order_a | order_b, + ) + + def test_search_vehicle_by_analytic_account(self): + other_vehicle = self.env["fleet.vehicle"].create( + { + "model_id": self.model.id, + "plan_to_change_car": False, + "analytic_distribution": {str(self.other_account.id): 100}, + } + ) + vehicles = self.env["fleet.vehicle"].search( + [("distribution_analytic_account_ids", "=", self.account.id)] + ) + self.assertIn(self.vehicle, vehicles) + self.assertNotIn(other_vehicle, vehicles) diff --git a/fleet_vehicle_analytic_purchase/views/fleet_vehicle.xml b/fleet_vehicle_analytic_purchase/views/fleet_vehicle.xml new file mode 100644 index 00000000..ca4c8907 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/views/fleet_vehicle.xml @@ -0,0 +1,37 @@ + + + + + fleet.vehicle.form (in fleet_vehicle_analytic_purchase) + fleet.vehicle + + +
+ +
+ + + + +
+
diff --git a/fleet_vehicle_analytic_purchase/views/purchase_order.xml b/fleet_vehicle_analytic_purchase/views/purchase_order.xml new file mode 100644 index 00000000..ec15c218 --- /dev/null +++ b/fleet_vehicle_analytic_purchase/views/purchase_order.xml @@ -0,0 +1,53 @@ + + + + + request.quotation.select (in fleet_vehicle_analytic_purchase) + purchase.order + + + + + + + + + + purchase.order.select (in fleet_vehicle_analytic_purchase) + purchase.order + + + + + + + + + + purchase.order.list (in fleet_vehicle_analytic_purchase) + purchase.order + + + + + + + +