-
-
Notifications
You must be signed in to change notification settings - Fork 799
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[REF] project_consumable: improve consumable detection
In the previous implementation project_id field was reused on account move line but a lot of the odoo code source assume that an account move line with a project_id is a timesheet, making very hard to distinguish Materials and Timesheet. Adding a consumable_project_id it avoid to breaks existing code make module much more easier to maintains
- Loading branch information
Showing
22 changed files
with
297 additions
and
375 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,8 @@ Project consumable | |
|
||
|badge1| |badge2| |badge3| |badge4| |badge5| | ||
|
||
This module provides a 'closed' flag on project task stages. | ||
This module allow to collect materials/consumable linked to a project | ||
adding account analytic lines. | ||
|
||
**Table of contents** | ||
|
||
|
@@ -65,7 +66,6 @@ quantities and Unit of Mesure provided by users, analytic amount will be | |
computed based on product cost. | ||
|
||
- Material & Consumable Menu | ||
- On task tab | ||
- Project tab | ||
|
||
Review consumable amount | ||
|
@@ -98,7 +98,7 @@ Authors | |
Contributors | ||
------------ | ||
|
||
- Pierre Verkest <[email protected]> | ||
- Pierre Verkest <[email protected]> | ||
|
||
Maintainers | ||
----------- | ||
|
@@ -113,6 +113,14 @@ 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-petrus-v| image:: https://github.com/petrus-v.png?size=40px | ||
:target: https://github.com/petrus-v | ||
:alt: petrus-v | ||
|
||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__: | ||
|
||
|maintainer-petrus-v| | ||
|
||
This module is part of the `OCA/project <https://github.com/OCA/project/tree/17.0/project_consumable>`_ project on GitHub. | ||
|
||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
from . import models | ||
from . import report | ||
from .hooks import set_project_ok_for_consumable_products |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
# Copyright 2021 - Pierre Verkest | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
{ | ||
"name": "Project consumable", | ||
|
@@ -8,6 +9,7 @@ | |
"category": "Project Management", | ||
"version": "17.0.1.0.0", | ||
"license": "AGPL-3", | ||
"maintainers": ["petrus-v"], | ||
"depends": [ | ||
"account", | ||
"hr_timesheet", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# Copyright 2021 - Pierre Verkest | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
from . import account_analytic_line | ||
from . import product_template | ||
from . import project_project | ||
from . import project_task |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,102 @@ | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import models | ||
from odoo import _, api, fields, models | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class AccountAnalyticLine(models.Model): | ||
_inherit = "account.analytic.line" | ||
|
||
def _timesheet_preprocess(self, vals_list): | ||
consumable_project_id = fields.Many2one( | ||
"project.project", | ||
domain='[("allow_consumables", "=", True)]', | ||
string="Project (consumable)", | ||
) | ||
|
||
def _consumable_preprocess_create(self, vals_list): | ||
for vals in vals_list: | ||
if not vals.get("name") and "consumable_project_id" in vals: | ||
vals["name"] = "/" | ||
return vals_list | ||
|
||
@api.model_create_multi | ||
def create(self, vals_list): | ||
vals_list = self._consumable_preprocess(vals_list) | ||
vals_list = self._consumable_preprocess_create(vals_list) | ||
|
||
lines = super().create(vals_list) | ||
|
||
for line, values in zip(lines, vals_list, strict=False): | ||
if line.consumable_project_id: | ||
line._consumable_postprocess(values) | ||
return lines | ||
|
||
def write(self, values): | ||
values = self._consumable_preprocess([values])[0] | ||
result = super().write(values) | ||
# applied only for timesheet | ||
self.filtered(lambda t: t.consumable_project_id)._consumable_postprocess(values) | ||
return result | ||
|
||
def _consumable_preprocess(self, vals_list): | ||
"""Deduce other field values from the one given. | ||
Overrride this to compute on the fly some field that can not be computed fields. | ||
Override this to compute on the fly some field that can not be computed fields. | ||
:param values: dict values for `create`or `write`. | ||
""" | ||
for vals in vals_list: | ||
if all(v in vals for v in ["product_id", "project_id"]): | ||
if all(v in vals for v in ["product_id", "consumable_project_id"]): | ||
if "product_uom_id" not in vals: | ||
product = ( | ||
self.env["product.product"].sudo().browse(vals["product_id"]) | ||
) | ||
vals["product_uom_id"] = product.uom_id.id | ||
return super()._timesheet_preprocess(vals_list) | ||
if not vals.get("account_id") and "consumable_project_id" in vals: | ||
account = ( | ||
self.env["project.project"] | ||
.browse(vals["consumable_project_id"]) | ||
.analytic_account_id | ||
) | ||
if not account or not account.active: | ||
raise ValidationError( | ||
_( | ||
"Materials must be created on a project " | ||
"with an active analytic account." | ||
) | ||
) | ||
vals["account_id"] = account.id | ||
return vals_list | ||
|
||
def _timesheet_postprocess_values(self, values): | ||
def _consumable_postprocess(self, values): | ||
sudo_self = self.sudo() | ||
values_to_write = self._consumable_postprocess_values(values) | ||
for consumable in sudo_self: | ||
if values_to_write[consumable.id]: | ||
consumable.write(values_to_write[consumable.id]) | ||
return values | ||
|
||
def _consumable_postprocess_values(self, values): | ||
"""Get the addionnal values to write on record | ||
:param dict values: values for the model's fields, as a dictionary:: | ||
{'field_name': field_value, ...} | ||
:return: a dictionary mapping each record id to its corresponding | ||
dictionary values to write (may be empty). | ||
""" | ||
result = super()._timesheet_postprocess_values(values) | ||
result = {id_: {} for id_ in self.ids} | ||
sudo_self = self.sudo() | ||
|
||
if any( | ||
field_name in values | ||
for field_name in [ | ||
"unit_amount", | ||
"product_id", | ||
"account_id", | ||
"product_uom_id", | ||
"date", | ||
] | ||
): | ||
for material in sudo_self: | ||
if material.project_id and material.product_id: | ||
if material.consumable_project_id and material.product_id: | ||
cost = material.product_id.standard_price or 0.0 | ||
qty = material.unit_amount | ||
if ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
from odoo import api, fields, models | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,90 @@ | ||
# Copyright 2021-2025 Pierre Verkest | ||
# Copyright 2021-2025 - Pierre Verkest | ||
# @author Pierre Verkest <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
from odoo import fields, models | ||
from odoo import _, _lt, api, fields, models | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class Project(models.Model): | ||
_inherit = "project.project" | ||
|
||
timesheet_ids = fields.One2many( | ||
domain=[("product_id", "=", None)], | ||
company_currency_id = fields.Many2one( | ||
string="Company Currency", | ||
related="company_id.currency_id", | ||
readonly=True, | ||
) | ||
allow_consumables = fields.Boolean( | ||
"Consumable", default=False, help="Project allowed while collecting consumable" | ||
) | ||
consumable_ids = fields.One2many( | ||
"account.analytic.line", "consumable_project_id", "Associated Consumables" | ||
) | ||
|
||
consumable_count = fields.Integer( | ||
compute="_compute_consumable_count", | ||
compute="_compute_consumable_total_price", | ||
compute_sudo=True, | ||
help="Number of consumable lines collected.", | ||
) | ||
consumable_total_price = fields.Monetary( | ||
compute="_compute_consumable_total_price", | ||
help="Total price of all consumables recorded in the project.", | ||
compute_sudo=True, | ||
currency_field="company_currency_id", | ||
) | ||
|
||
def _compute_consumable_count(self): | ||
read_group = { | ||
group["project_id"][0]: group["project_id_count"] | ||
for group in self.env["account.analytic.line"].read_group( | ||
[ | ||
("project_id", "in", self.ids), | ||
("product_id", "!=", False), | ||
], | ||
["project_id"], | ||
["project_id"], | ||
) | ||
} | ||
@api.constrains("allow_consumables", "analytic_account_id") | ||
def _check_allow_consumables(self): | ||
for project in self: | ||
project.consumable_count = read_group.get(project.id, 0) | ||
if project.allow_consumables and not project.analytic_account_id: | ||
raise ValidationError( | ||
_("You cannot use consumables without an analytic account.") | ||
) | ||
|
||
@api.depends( | ||
"consumable_ids", | ||
"consumable_ids.amount", | ||
"consumable_ids.consumable_project_id", | ||
) | ||
def _compute_consumable_total_price(self): | ||
consumables_read_group = self.env["account.analytic.line"]._read_group( | ||
[("consumable_project_id", "in", self.ids)], | ||
["consumable_project_id"], | ||
["consumable_project_id:count", "amount:sum"], | ||
) | ||
self.consumable_total_price = 0 | ||
self.consumable_count = 0 | ||
for project, count, amount_sum in consumables_read_group: | ||
project.consumable_total_price = amount_sum | ||
project.consumable_count = count | ||
|
||
def action_project_consumable(self): | ||
action = self.env["ir.actions.act_window"]._for_xml_id( | ||
"project_consumable.consumable_action_report_by_project" | ||
) | ||
action["display_name"] = _("%(name)s's Materials", name=self.name) | ||
action["domain"] = [("consumable_project_id", "in", self.ids)] | ||
return action | ||
|
||
def _get_stat_buttons(self): | ||
buttons = super()._get_stat_buttons() | ||
if not self.allow_consumables or not self.env.user.has_group( | ||
"project.group_project_manager" | ||
): | ||
return buttons | ||
|
||
buttons.append( | ||
{ | ||
"icon": "copy", | ||
"text": _lt("Materials"), | ||
"number": _lt( | ||
"%(amount)s € (%(count)s)", | ||
amount=self.consumable_total_price, | ||
count=self.consumable_count, | ||
), | ||
"action_type": "object", | ||
"action": "action_project_consumable", | ||
"show": True, | ||
"sequence": 6, | ||
} | ||
) | ||
return buttons |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
- Pierre Verkest \<<[email protected]>\> | ||
- Pierre Verkest \<<[email protected]>\> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
This module provides a 'closed' flag on project task stages. | ||
This module allow to collect materials/consumable linked to a | ||
project adding account analytic lines. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
64 changes: 0 additions & 64 deletions
64
project_consumable/report/hr_timesheet_attendance_report.py
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.