-
-
Notifications
You must be signed in to change notification settings - Fork 796
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] sale_project_reimbursement_cost: display provisions and reimbur…
…sement costs in the Project Updates dashboard
- Loading branch information
1 parent
9769163
commit 5e093d2
Showing
20 changed files
with
1,365 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,162 @@ | ||
=============================== | ||
Sale project reimbursement cost | ||
=============================== | ||
|
||
.. | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
!! This file is generated by oca-gen-addon-readme !! | ||
!! changes will be overwritten. !! | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
!! source digest: sha256:bad249e38e180277d68f401bdf0bd9385ca0561141736c76f646a7f277648b82 | ||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
.. |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%2Fproject-lightgray.png?logo=github | ||
:target: https://github.com/OCA/project/tree/17.0/sale_project_reimbursement_cost | ||
:alt: OCA/project | ||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png | ||
:target: https://translation.odoo-community.org/projects/project-17-0/project-17-0-sale_project_reimbursement_cost | ||
: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/project&target_branch=17.0 | ||
:alt: Try me on Runboat | ||
|
||
|badge1| |badge2| |badge3| |badge4| |badge5| | ||
|
||
This module allows the display of provisions and reimbursement costs in | ||
the ``Project Updates`` dashboard. | ||
|
||
**Table of contents** | ||
|
||
.. contents:: | ||
:local: | ||
|
||
Use Cases / Context | ||
=================== | ||
|
||
In businesses, especially those in services or consulting, where | ||
expenses may be incurred on behalf of a client but later need to be | ||
reimbursed by the client, Odoo’s functionality for | ||
``Re-Invoice Expenses`` products (without using the hr_expenses module) | ||
lacks visibility of this information in the project dashboard. | ||
|
||
With this module, two sections are added to the project dashboard: | ||
|
||
- **Provisions**: Values invoiced to the client in advance to cover any | ||
necessary expenses. This section displays the analytic entries | ||
generated from customer invoices for specific products. | ||
- **Reimbursements cost:** Values generated from vendor bills that need | ||
to be charged to the client. Using Odoo’s functionality, these are | ||
the sales order lines automatically created from a vendor bill when | ||
the product is configured as ``Re-Invoice Expenses``. | ||
|
||
Configuration | ||
============= | ||
|
||
**To automatically create a project from a sale order:** | ||
|
||
- Go to ``Sales > Products > Products``. | ||
- Create a new product with the following options: | ||
|
||
- Product Type: Service | ||
- Invoicing Policy: ``Prepaid/Fixed Price``. | ||
- Create on Order: Set an option other than ``Nothing``. | ||
|
||
**For Provisions:** | ||
|
||
- Go to ``Sales > Products > Products``. | ||
- Create a new product of type Service. | ||
|
||
**For Reimbursement:** | ||
|
||
- Go to ``Sales > Products > Products``. | ||
- Create a new product with the following options: | ||
|
||
- ``Product Type``: Service | ||
- Invoicing Policy: ``Based on Delivered Quantity (Manual)``. | ||
- ``Re-Invoice Expenses``: At cost or Sales price | ||
- Fill in the ``Provision Product`` field created in the previous | ||
step. | ||
|
||
Usage | ||
===== | ||
|
||
**To generate a new project from a sale order**: | ||
|
||
- Go to ``Sales > Orders > Quotations``. | ||
- Create a new sales order and select the first product configured. | ||
- Confirm the sales order. | ||
|
||
**To generate provisions**: | ||
|
||
- Go to ``Invoicing > Customers > Invoices``. | ||
- Create a new invoice and select the second product configured for | ||
``Provisions``. | ||
- In the invoice line, set the analytic distribution with the analytic | ||
account for the project. | ||
- Confirm the invoice. | ||
|
||
**To generate reimbursement costs**: | ||
|
||
- Go to ``Invoicing > Vendors > Bills``. | ||
- Create a new bill and select the third product configured for | ||
``Reimbursement``. | ||
- In the invoice line, set the analytic distribution with the analytic | ||
account for the project. | ||
- Confirm the bill, and a new line will be added to the sale order with | ||
the reimbursement cost. | ||
|
||
**Project status dashboard**: | ||
|
||
- Go to ``Project > Projects``. | ||
- Search for the respective project. | ||
- In the Kanban view, click the top-right icon to display project | ||
settings, then click ``Reporting / Project Updates``. | ||
|
||
Bug Tracker | ||
=========== | ||
|
||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/project/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 <https://github.com/OCA/project/issues/new?body=module:%20sale_project_reimbursement_cost%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. | ||
|
||
Do not contact contributors directly about support or help with technical issues. | ||
|
||
Credits | ||
======= | ||
|
||
Authors | ||
------- | ||
|
||
* Tecnativa | ||
|
||
Contributors | ||
------------ | ||
|
||
- `Tecnativa <https://www.tecnativa.com>`__: | ||
|
||
- Pedro M. Baeza | ||
- Carlos López | ||
|
||
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. | ||
|
||
This module is part of the `OCA/project <https://github.com/OCA/project/tree/17.0/sale_project_reimbursement_cost>`_ 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 |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import 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 |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Copyright 2024 Tecnativa - Carlos López | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
{ | ||
"name": "Sale project reimbursement cost", | ||
"version": "17.0.1.0.0", | ||
"summary": """Display provisions and reimbursement costs | ||
in the Project Updates dashboard.""", | ||
"author": "Tecnativa,Odoo Community Association (OCA)", | ||
"website": "https://github.com/OCA/project", | ||
"category": "Sales", | ||
"depends": [ | ||
"sale_project", | ||
], | ||
"data": ["views/product_template_views.xml", "views/sale_order_line_views.xml"], | ||
"assets": { | ||
"web.assets_backend": [ | ||
"sale_project_reimbursement_cost/static/src/components/project_right_side_panel/**/*", | ||
], | ||
}, | ||
"installable": True, | ||
"license": "AGPL-3", | ||
} |
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 |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import product_template | ||
from . import project_project |
12 changes: 12 additions & 0 deletions
12
sale_project_reimbursement_cost/models/product_template.py
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 |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright 2024 Tecnativa - Carlos López | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
from odoo import fields, models | ||
|
||
|
||
class ProductTemplate(models.Model): | ||
_inherit = "product.template" | ||
|
||
provision_product_id = fields.Many2one( | ||
"product.product", | ||
domain=[("type", "=", "service")], | ||
) |
151 changes: 151 additions & 0 deletions
151
sale_project_reimbursement_cost/models/project_project.py
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 |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# Copyright 2024 Tecnativa - Carlos López | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
from odoo import models | ||
from odoo.osv import expression | ||
|
||
|
||
class ProjectProject(models.Model): | ||
_inherit = "project.project" | ||
|
||
def get_panel_data(self): | ||
panel_data = super().get_panel_data() | ||
return { | ||
**panel_data, | ||
"reimbursement_items": self._get_reimbursement_items(), | ||
"provision_items": self._get_provision_items(), | ||
} | ||
|
||
def _get_reimbursement_items(self, with_action=True): | ||
domain = self._get_reimbursement_items_domain() | ||
return { | ||
"total": self.env["sale.order.line"].sudo().search_count(domain), | ||
"data": self.get_reimbursement_items_data( | ||
domain, limit=5, with_action=with_action | ||
), | ||
} | ||
|
||
def get_reimbursement_items_data( | ||
self, domain=None, offset=0, limit=None, with_action=True | ||
): | ||
if not self.user_has_groups("project.group_project_user"): | ||
return [] | ||
sols = ( | ||
self.env["sale.order.line"] | ||
.sudo() | ||
.search( | ||
domain or self._get_reimbursement_items_domain(), | ||
offset=offset, | ||
limit=limit, | ||
) | ||
) | ||
|
||
def get_action(res_id): | ||
"""Return the action vals to call it in frontend to the sale lines""" | ||
action_id = "sale_project_reimbursement_cost.sol_reimbursement_cost_action" | ||
return { | ||
"action": { | ||
"name": action_id, | ||
"resId": res_id, | ||
} | ||
} | ||
|
||
return [ | ||
{ | ||
**sol_read, | ||
**get_action(sol_read["id"]), | ||
} | ||
for sol_read in sols.with_context(with_price_unit=True).read( | ||
[ | ||
"display_name", | ||
"untaxed_amount_invoiced", | ||
"untaxed_amount_to_invoice", | ||
] | ||
) | ||
] | ||
|
||
def _get_reimbursement_items_domain(self, additional_domain=None): | ||
sale_items = self.sudo()._get_sale_order_items() | ||
domain = [ | ||
("order_id", "in", sale_items.sudo().order_id.ids), | ||
("is_expense", "=", True), | ||
("qty_to_invoice", ">", 0), | ||
("state", "=", "sale"), | ||
("display_type", "=", False), | ||
"|", | ||
("project_id", "in", self.ids + [False]), | ||
("id", "in", sale_items.ids), | ||
] | ||
if additional_domain: | ||
domain = expression.AND([domain, additional_domain]) | ||
return domain | ||
|
||
def _get_provision_items(self, with_action=True): | ||
domain = self._get_provision_items_domain() | ||
return { | ||
"total": self.env["account.analytic.line"].sudo().search_count(domain), | ||
"data": self.get_provision_items_data( | ||
domain, limit=5, with_action=with_action | ||
), | ||
} | ||
|
||
def get_provision_items_data( | ||
self, domain=None, offset=0, limit=None, with_action=True | ||
): | ||
if not self.user_has_groups("project.group_project_user"): | ||
return [] | ||
analytic_lines = ( | ||
self.env["account.analytic.line"] | ||
.sudo() | ||
.search( | ||
domain or self._get_provision_items_domain(), | ||
offset=offset, | ||
limit=limit, | ||
order="date,id", | ||
) | ||
) | ||
|
||
def get_action(res_id): | ||
"""Return the action vals to call it in frontend to the analytic lines""" | ||
return { | ||
"action": { | ||
"name": "analytic.account_analytic_line_action_entries", | ||
"resId": res_id, | ||
} | ||
} | ||
|
||
return [ | ||
{ | ||
**analytic_line_read, | ||
**get_action(analytic_line_read["id"]), | ||
} | ||
for analytic_line_read in analytic_lines.read( | ||
[ | ||
"display_name", | ||
"amount", | ||
"date", | ||
] | ||
) | ||
] | ||
|
||
def _get_provision_items_domain(self, additional_domain=None): | ||
provision_products = self.sudo()._get_provision_products() | ||
domain = [ | ||
("product_id", "in", provision_products.ids), | ||
("account_id", "=", self.analytic_account_id.id), | ||
] | ||
if additional_domain: | ||
domain = expression.AND([domain, additional_domain]) | ||
return domain | ||
|
||
def _get_provision_products(self): | ||
"""Get the provision products set in the all products""" | ||
provision_products = self.env["product.template"]._read_group( | ||
domain=[("provision_product_id", "!=", False)], | ||
groupby=[], | ||
aggregates=["provision_product_id:recordset"], | ||
) | ||
return ( | ||
provision_products[0][0] | ||
if provision_products | ||
else self.env["product.product"] | ||
) |
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 |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[build-system] | ||
requires = ["whool"] | ||
build-backend = "whool.buildapi" |
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 |
---|---|---|
@@ -0,0 +1,23 @@ | ||
**To automatically create a project from a sale order:** | ||
|
||
- Go to `Sales > Products > Products`. | ||
- Create a new product with the following options: | ||
- Product Type: Service | ||
- Invoicing Policy: `Prepaid/Fixed Price`. | ||
- Create on Order: Set an option other than `Nothing`. | ||
|
||
|
||
**For Provisions:** | ||
|
||
- Go to `Sales > Products > Products`. | ||
- Create a new product of type Service. | ||
|
||
|
||
**For Reimbursement:** | ||
|
||
- Go to `Sales > Products > Products`. | ||
- Create a new product with the following options: | ||
- `Product Type`: Service | ||
- Invoicing Policy: `Based on Delivered Quantity (Manual)`. | ||
- `Re-Invoice Expenses`: At cost or Sales price | ||
- Fill in the `Provision Product` field created in the previous step. |
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 |
---|---|---|
@@ -0,0 +1,6 @@ | ||
In businesses, especially those in services or consulting, where expenses may be incurred on behalf of a client but later need to be reimbursed by the client, Odoo’s functionality for `Re-Invoice Expenses` products (without using the hr_expenses module) lacks visibility of this information in the project dashboard. | ||
|
||
With this module, two sections are added to the project dashboard: | ||
|
||
- **Provisions**: Values invoiced to the client in advance to cover any necessary expenses. This section displays the analytic entries generated from customer invoices for specific products. | ||
- **Reimbursements cost:** Values generated from vendor bills that need to be charged to the client. Using Odoo’s functionality, these are the sales order lines automatically created from a vendor bill when the product is configured as `Re-Invoice Expenses`. |
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 |
---|---|---|
@@ -0,0 +1,3 @@ | ||
- [Tecnativa](https://www.tecnativa.com): | ||
- Pedro M. Baeza | ||
- Carlos López |
Oops, something went wrong.