diff --git a/project_consumable/README.rst b/project_consumable/README.rst index 181d46f0b2..85fe7ae2d7 100644 --- a/project_consumable/README.rst +++ b/project_consumable/README.rst @@ -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 +- Pierre Verkest 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 `__: + +|maintainer-petrus-v| + This module is part of the `OCA/project `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/project_consumable/__init__.py b/project_consumable/__init__.py index d604717fa0..a952e1fe2a 100644 --- a/project_consumable/__init__.py +++ b/project_consumable/__init__.py @@ -1,5 +1,5 @@ # Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # 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 diff --git a/project_consumable/__manifest__.py b/project_consumable/__manifest__.py index 70312b7c8b..d799b46176 100644 --- a/project_consumable/__manifest__.py +++ b/project_consumable/__manifest__.py @@ -1,4 +1,5 @@ -# Copyright 2021 - Pierre Verkest +# Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # 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", diff --git a/project_consumable/models/__init__.py b/project_consumable/models/__init__.py index 442ddff5ce..6dabf604c0 100644 --- a/project_consumable/models/__init__.py +++ b/project_consumable/models/__init__.py @@ -1,6 +1,6 @@ -# Copyright 2021 - Pierre Verkest +# Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # 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 diff --git a/project_consumable/models/account_analytic_line.py b/project_consumable/models/account_analytic_line.py index fadc64a8b1..e263ce0521 100644 --- a/project_consumable/models/account_analytic_line.py +++ b/project_consumable/models/account_analytic_line.py @@ -1,46 +1,102 @@ # Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # 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 ( diff --git a/project_consumable/models/product_template.py b/project_consumable/models/product_template.py index d20d20b702..bcb48aebf1 100644 --- a/project_consumable/models/product_template.py +++ b/project_consumable/models/product_template.py @@ -1,4 +1,5 @@ # Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import api, fields, models diff --git a/project_consumable/models/project_project.py b/project_consumable/models/project_project.py index 305e60c8cd..105a7ef541 100644 --- a/project_consumable/models/project_project.py +++ b/project_consumable/models/project_project.py @@ -1,32 +1,90 @@ -# Copyright 2021-2025 Pierre Verkest +# Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # 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 diff --git a/project_consumable/models/project_task.py b/project_consumable/models/project_task.py deleted file mode 100644 index 310bf041fa..0000000000 --- a/project_consumable/models/project_task.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2021-2025 - Pierre Verkest -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models - - -class ProjectTask(models.Model): - _inherit = "project.task" - - timesheet_ids = fields.One2many( - domain=[("product_id", "=", None)], - ) diff --git a/project_consumable/readme/CONTRIBUTORS.md b/project_consumable/readme/CONTRIBUTORS.md index 926261edf6..2b71f93ab0 100644 --- a/project_consumable/readme/CONTRIBUTORS.md +++ b/project_consumable/readme/CONTRIBUTORS.md @@ -1 +1 @@ -- Pierre Verkest \<\> +- Pierre Verkest \<\> diff --git a/project_consumable/readme/DESCRIPTION.md b/project_consumable/readme/DESCRIPTION.md index 58f6c80ac9..7260c005c1 100644 --- a/project_consumable/readme/DESCRIPTION.md +++ b/project_consumable/readme/DESCRIPTION.md @@ -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. diff --git a/project_consumable/readme/USAGE.md b/project_consumable/readme/USAGE.md index 2748c75406..03ae31aa07 100644 --- a/project_consumable/readme/USAGE.md +++ b/project_consumable/readme/USAGE.md @@ -7,7 +7,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 diff --git a/project_consumable/report/__init__.py b/project_consumable/report/__init__.py deleted file mode 100644 index 0a399b8dda..0000000000 --- a/project_consumable/report/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import hr_timesheet_attendance_report -from . import timesheets_analysis_report diff --git a/project_consumable/report/hr_timesheet_attendance_report.py b/project_consumable/report/hr_timesheet_attendance_report.py deleted file mode 100644 index 3088ed7424..0000000000 --- a/project_consumable/report/hr_timesheet_attendance_report.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2025 - Pierre Verkest -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import models, tools - - -class TimesheetAttendance(models.Model): - _inherit = "hr.timesheet.attendance.report" - - def init(self): - tools.drop_view_if_exists(self.env.cr, self._table) - self._cr.execute( - """CREATE OR REPLACE VIEW %s AS ( - SELECT - max(id) AS id, - t.employee_id, - t.date, - t.company_id, - coalesce(sum(t.attendance), 0) AS total_attendance, - coalesce(sum(t.timesheet), 0) AS total_timesheet, - coalesce( - sum(t.attendance), 0) - coalesce(sum(t.timesheet), - 0 - ) as total_difference, - NULLIF(sum(t.timesheet) * t.emp_cost, 0) as timesheets_cost, - NULLIF(sum(t.attendance) * t.emp_cost, 0) as attendance_cost, - NULLIF( - (coalesce(sum(t.attendance), 0) - coalesce(sum(t.timesheet), 0)) - * t.emp_cost, - 0 - ) as cost_difference - FROM ( - SELECT - -hr_attendance.id AS id, - hr_employee.hourly_cost AS emp_cost, - hr_attendance.employee_id AS employee_id, - hr_attendance.worked_hours AS attendance, - NULL AS timesheet, - hr_attendance.check_in::date AS date, - hr_employee.company_id as company_id - FROM hr_attendance - LEFT JOIN hr_employee ON hr_employee.id = hr_attendance.employee_id - UNION ALL - SELECT - ts.id AS id, - hr_employee.hourly_cost AS emp_cost, - ts.employee_id AS employee_id, - NULL AS attendance, - ts.unit_amount AS timesheet, - ts.date AS date, - ts.company_id AS company_id - FROM account_analytic_line AS ts - LEFT JOIN hr_employee ON hr_employee.id = ts.employee_id - WHERE ts.project_id IS NOT NULL - -- change start - AND ts.product_id IS NULL - -- change end - ) AS t - GROUP BY t.employee_id, t.date, t.company_id, t.emp_cost - ORDER BY t.date - ) - """ - % self._table - ) diff --git a/project_consumable/report/timesheets_analysis_report.py b/project_consumable/report/timesheets_analysis_report.py deleted file mode 100644 index 7179cf7e70..0000000000 --- a/project_consumable/report/timesheets_analysis_report.py +++ /dev/null @@ -1,11 +0,0 @@ -from odoo import api, models - - -class TimesheetsAnalysisReport(models.Model): - _inherit = "timesheets.analysis.report" - - @api.model - def _where(self): - where_clause = super()._where() - where_clause += " AND A.product_id IS NULL" - return where_clause diff --git a/project_consumable/static/description/index.html b/project_consumable/static/description/index.html index 83898a65b7..e0ac296b36 100644 --- a/project_consumable/static/description/index.html +++ b/project_consumable/static/description/index.html @@ -370,7 +370,8 @@

Project consumable

!! source digest: sha256:d945aa69887faf24cdade547c25c80aaa2d02c752193444173ee867f598bed91 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

-

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

@@ -449,7 +449,7 @@

Authors

Contributors

@@ -461,6 +461,8 @@

Maintainers

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:

+

petrus-v

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

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

diff --git a/project_consumable/tests/__init__.py b/project_consumable/tests/__init__.py index f3f47da7ed..9c49fcabe8 100644 --- a/project_consumable/tests/__init__.py +++ b/project_consumable/tests/__init__.py @@ -1,5 +1 @@ -# Copyright 2021 - Pierre Verkest -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from . import test_project_consumable_report from . import test_project_consumable diff --git a/project_consumable/tests/test_project_consumable.py b/project_consumable/tests/test_project_consumable.py index 4cbb7c3b7f..acb6baba5f 100644 --- a/project_consumable/tests/test_project_consumable.py +++ b/project_consumable/tests/test_project_consumable.py @@ -1,7 +1,9 @@ -# Copyright 2021 - Pierre Verkest +# Copyright 2021-2025 - Pierre Verkest +# @author Pierre Verkest # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from datetime import date +from odoo.exceptions import ValidationError from odoo.tests import TransactionCase, users @@ -21,6 +23,7 @@ def setUpClass(cls): cls.project = cls.env["project.project"].create( { "name": "Test", + "allow_consumables": True, "analytic_account_id": cls.analytic_account.id, } ) @@ -42,7 +45,7 @@ def test_onchange_product_type_project_ok_to_be_false(self): def _prepare_consumable_line_data(self, **kwargs): data = { "name": "collect test material", - "project_id": self.project.id, + "consumable_project_id": self.project.id, "account_id": None, "product_id": self.product.id, "unit_amount": 6, @@ -67,7 +70,13 @@ def test_user_id(self): account_analytic_line = self.env["account.analytic.line"].create( self._prepare_consumable_line_data(user_id=None) ) - self.assertEqual(account_analytic_line.user_id.id, self.user_demo.id) + self.assertEqual(account_analytic_line.user_id.id, self.env.user.id) + + def test_name(self): + account_analytic_line = self.env["account.analytic.line"].create( + self._prepare_consumable_line_data(name=None) + ) + self.assertEqual(account_analytic_line.name, "/") def test_date(self): account_analytic_line = self.env["account.analytic.line"].create( @@ -102,6 +111,7 @@ def test_consumable_amount_force_uom(self): ) def test_consumable_amount_default_product_uom(self): + # self.product.standard_price = 0.33 account_analytic_line = self.env["account.analytic.line"].create( self._prepare_consumable_line_data(unit_amount=6, product_uom_id=None) ) @@ -147,3 +157,47 @@ def test_consumable_count(self): ) ) self.assertEqual(self.project.consumable_count, 1) + + def test_project_with_inactive_analytic_account_raise(self): + self.project.analytic_account_id.active = False + with self.assertRaisesRegex( + ValidationError, + r"Materials must be created on a project with " + r"an active analytic account", + ): + self.env["account.analytic.line"].create( + self._prepare_consumable_line_data( + unit_amount=7, + product_uom_id=self.env.ref( + "project_consumable.uom_cat_coffee_capsule_box_10" + ).id, + ) + ) + + def test_project_without_analytic_account_raise(self): + with self.assertRaisesRegex( + ValidationError, r"You cannot use consumables without an analytic account" + ): + self.project.analytic_account_id = False + + def test_action_project_consumable(self): + action = self.project.action_project_consumable() + self.assertEqual( + action["domain"], [("consumable_project_id", "in", self.project.ids)] + ) + + def test_get_stat_buttons(self): + buttons = self.project._get_stat_buttons() + self.assertTrue( + "action_project_consumable" in [button["action"] for button in buttons], + buttons, + ) + + def test_get_stat_buttons_non_project_manager(self): + user_demo = self.env.ref("base.user_demo") + self.assertFalse(user_demo.has_group("project.group_project_manager")) + buttons = self.project.with_user(user_demo)._get_stat_buttons() + self.assertTrue( + "action_project_consumable" not in [button["action"] for button in buttons], + buttons, + ) diff --git a/project_consumable/tests/test_project_consumable_report.py b/project_consumable/tests/test_project_consumable_report.py deleted file mode 100644 index daf781dd53..0000000000 --- a/project_consumable/tests/test_project_consumable_report.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2021 - Pierre Verkest -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from datetime import datetime - -from odoo.tests import TransactionCase - - -class TestProjectConsumableReporting(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.product = cls.env.ref("project_consumable.product_coffee_capsule") - default_plan_id = cls.env["account.analytic.plan"].search([], limit=1) - cls.analytic_account = cls.env["account.analytic.account"].create( - { - "name": "Test", - "plan_id": default_plan_id.id, - "company_id": cls.env.company.id, - } - ) - cls.project = cls.env["project.project"].create( - { - "name": "Test", - "analytic_account_id": cls.analytic_account.id, - "company_id": cls.env.company.id, - } - ) - cls.user_demo = cls.env.ref("base.user_demo") - cls.employee = cls.user_demo.employee_id - - cls.env["hr.attendance"].create( - { - "employee_id": cls.employee.id, - "check_in": datetime(2022, 2, 9, 8, 0), # Wednesday - "check_out": datetime(2022, 2, 9, 16, 0), - } - ) - - def _prepare_consumable_line_data(self, **kwargs): - data = { - "name": "collect test material", - "project_id": self.project.id, - "account_id": None, - "product_id": self.product.id, - "unit_amount": 6, - "employee_id": self.employee.id, - "product_uom_id": self.product.uom_id.id, - "task_id": None, - "amount": None, - "date": None, - "partner_id": None, - } - data.update(**kwargs) - return {k: v for k, v in data.items() if v is not None} - - def test_timesheet_analysis_report_exclude_consumable(self): - self.env["account.analytic.line"].create( - { - "name": "test timesheet", - "project_id": self.project.id, - "unit_amount": 3, - "employee_id": self.employee.id, - } - ) - self.env["account.analytic.line"].create( - self._prepare_consumable_line_data( - unit_amount=7, - product_uom_id=self.env.ref( - "project_consumable.uom_cat_coffee_capsule_box_10" - ).id, - ) - ) - analysis = self.env["timesheets.analysis.report"].search( - [ - ("project_id", "=", self.project.id), - ("employee_id", "=", self.employee.id), - ] - ) - self.assertEqual(len(analysis), 1) - self.assertEqual(analysis.unit_amount, 3) - - def test_timesheet_attendance_report_with_consumable(self): - self.env["account.analytic.line"].with_user(self.user_demo).create( - { - "name": "Test timesheet 1", - "project_id": self.project.id, - "unit_amount": 6.0, - "date": datetime(2022, 2, 9), - } - ) - self.env["account.analytic.line"].create( - self._prepare_consumable_line_data( - unit_amount=7, - product_uom_id=self.env.ref( - "project_consumable.uom_cat_coffee_capsule_box_10" - ).id, - employee_id=self.employee.id, - date=datetime(2022, 2, 9), - ) - ) - total_timesheet, total_attendance = self.env[ - "hr.timesheet.attendance.report" - ]._read_group( - [ - ("employee_id", "=", self.employee.id), - ("date", ">=", datetime(2022, 2, 9, 8, 0)), - ("date", "<=", datetime(2022, 2, 9, 16, 0)), - ], - aggregates=["total_timesheet:sum", "total_attendance:sum"], - )[0] - self.assertEqual( - total_timesheet, 6.0, "Total timesheet in report should be 4.0" - ) - self.assertEqual( - total_attendance, 7.0, "Total attendance in report should be 8.0" - ) - self.assertEqual(total_attendance - total_timesheet, 1) diff --git a/project_consumable/views/analytic_account_line.xml b/project_consumable/views/analytic_account_line.xml index 098c1a96c0..f06626166c 100644 --- a/project_consumable/views/analytic_account_line.xml +++ b/project_consumable/views/analytic_account_line.xml @@ -1,29 +1,8 @@ + - - [('project_id', '!=', False), ('user_id', '=', uid), ('product_id', '=', False)] - - - - [('task_id', 'in', active_ids), ('product_id', '=', False)] - - - - [('project_id', 'in', active_ids), ('product_id', '=', False)] - - - - [('project_id', '!=', False), ('product_id', '=', False)] - - account.analytic.line.tree.project_consumable account.analytic.line @@ -32,18 +11,11 @@ - 10 - 0 + False 1 many2one_avatar_user @@ -83,8 +55,8 @@ + - @@ -94,8 +66,7 @@ account.analytic.line - - + @@ -114,15 +85,10 @@ - - - + - - + - -
-
-
- +
+
+ -
-
-
- +
+
+ + +
-
- - - -
+
+
-
- -
-
- - - - - - Quantity: - +
+
+ + + + + + Quantity: + +
+
@@ -323,7 +282,7 @@ tree,form,kanban [('project_id', '!=', False), ('user_id', '=', uid), ('product_id', '!=', False)] + >[('consumable_project_id', '!=', False), ('user_id', '=', uid), ('product_id', '!=', False)] { "search_default_week":1, } @@ -336,7 +295,7 @@ No products found. Let's start a new one!

- Track consummed products by projects every day and analyse cost per projects. + Track consumed products by projects every day and analysis cost per projects.

@@ -379,22 +338,12 @@ sequence="25" /> - - Task's Materials - account.analytic.line - [('task_id', 'in', active_ids), ('product_id', '!=', False)] - tree - - - Project's Materials account.analytic.line [('project_id', 'in', active_ids), ('product_id', '!=', False)] + >[('consumable_project_id', 'in', active_ids), ('product_id', '!=', False)] tree @@ -406,7 +355,7 @@ [('project_id', '!=', False), ('product_id', '!=', False)] + >[('consumable_project_id', '!=', False), ('product_id', '!=', False)] { 'search_default_week':1, } @@ -467,7 +416,7 @@ > kanban 7 - + diff --git a/project_consumable/views/analytic_account_line_report.xml b/project_consumable/views/analytic_account_line_report.xml index a5b169b937..3da6efb96d 100644 --- a/project_consumable/views/analytic_account_line_report.xml +++ b/project_consumable/views/analytic_account_line_report.xml @@ -1,4 +1,7 @@ + @@ -6,7 +9,7 @@ account.analytic.line [('project_id', '!=', False), ('product_id', '!=', False)] + >[('consumable_project_id', '!=', False), ('product_id', '!=', False)] {'search_default_groupby_project': 1} diff --git a/project_consumable/views/product.xml b/project_consumable/views/product.xml index 2ebaf707f2..8e35c8c125 100644 --- a/project_consumable/views/product.xml +++ b/project_consumable/views/product.xml @@ -1,4 +1,7 @@ + product.template diff --git a/project_consumable/views/project_views.xml b/project_consumable/views/project_views.xml index 768f649eb8..c4e765205e 100644 --- a/project_consumable/views/project_views.xml +++ b/project_consumable/views/project_views.xml @@ -1,13 +1,8 @@ + - - [('project_id', '!=', False), ('product_id', '=', False)] - Consumables @@ -16,16 +11,16 @@ [('project_id', '!=', False), ('product_id', '!=', False)] + >[('project_consumable_id', '!=', False), ('product_id', '!=', False)] {"default_project_id": active_id, "search_default_project_id": [active_id]} + >{"default_project_consumable_id": active_id, "search_default_project_consumable_id": [active_id]}

Record a new activity

- Track your working hours by projects every day and invoice this time to your customers. + Track your consumable by projects every day and invoice those materials to your customers.