This module is the main module from a set of budget control modules.
+This module alone will allow you to work in full cycle of budget control process.
+Other modules, each one are the small enhancement of this module, to fullfill
+additional needs. Having said that, following will describe the full cycle of budget
+control already provided by this module,
Actual amount are from account.move.line from posted invoice. Commitments can be sales/purchase,
+expense, purchase request, etc. Document required to be budget commitment can extend base.budget.move.
+For example, the module budget_control_expense will create budget commitment expense.budget.move
+for approved expense.
+Note that, in this budget_control module, there is no extension for budget commitment yet.
+
+
Budget KPI (budget.kpi)
+
Budget KPI is used to measure the efficiency of planning compared to actual usage.
+It is linked to Account Codes, and one Budget KPI can be associated with more than one account code.
+
+
Budget Template (budget.template)
+
A Budget Template in the budget control system serves as a framework for controlling the budget,
+allowing for the budget to be managed according to the pre-defined template.
+The budget template has a relationship with the budget kpi and accounting,
+and is used to control spending based on pre-configured accounts.
+
+
Budget Period (budget.period)
+
Budget Period is the first thing to do for new budget year, and is used to govern how budget will be
+controlled over the defined date range, i.e.,
+
+
Duration of budget year
+
Template to control (budget.template)
+
Document to do budget checking
+
Analytic account in controlled
+
Control Level
+
+
Although not mandatory, an organization will most likely use fiscal year as budget period.
+In such case, there will be 1 budget period per fiscal year, and multiple budget control sheet (one per analytic).
+
+
Budget Control Sheet (budget.control)
+
Each analytic account can have one budget control sheet per budget period.
+The budget control is used to allocate budget amount in a simpler way.
+In the backend it simply create budget.control.line, nothing too fancy.
+Once we have budget allocations, the system is ready to perform budget check.
+
+
Budget Checking
+
By calling function – check_budget(), system will check whether the confirmation
+of such document can result in negative budget balance. If so, it throw error message.
+In this module, budget check occur during posting of invoice and journal entry.
+To check budget also on more documents, do install budget_control_xxx relevant to that document.
+
+
Budget Constraint
+
To make the function – check_budget() more flexible,
+additional rules or limitations can be added to the budget checking process.
+The system will perform the regular budget check and will also check the additional conditions specified
+in the added rules. An example of using budget constraints can be seen from the budget_allocation module.
+
+
Budget Reports
+
Currently there are 2 types of report.
+
+
Budget Monitoring: combine all budget related transactions, and show them in Standard Odoo BI view.
+
Actual Budget Moves: combine all actual commit transactions, and show them in Standard Odoo BI view.
+
+
+
Budget Commitment Move Forward
+
In case budget commitment is being used. Sometime user has committed budget withing this year
+but not ready to use it and want to move the commitment amount to next year budget.
+Budget Commitment Forward can be use to change the budget move’s date to the designated year.
+
+
Budget Transfer
+
This module allow transferring allocated budget from one budget control sheet to other
+
+
+
+
+
Extended Modules:
+
Following are brief explanation of what the extended module will do.
+
Budget Move extension
+
These modules extend base.budget.move for other document budget commitment.
+
+
budget_control_advance_clearing
+
budget_control_contract
+
budget_control_expense
+
budget_control_purchase
+
budget_control_purchase_request
+
+
Budget Allocation
+
This module is the main module for manage allocation (source of fund, analytic tag and analytic account)
+until set budget control. and allow create Master Data source of fund, analytic tag dimension.
+Users can view source of fund monitoring report
+
+
budget_allocation
+
budget_allocation_advance_clearing
+
budget_allocation_contract
+
budget_allocation_expense
+
budget_allocation_purchase
+
budget_allocation_purchase_request
+
+
Tier Validation
+
Extend base_tier_validation for budget control sheet
+
+
budget_control_tier_validation
+
+
Analytic Tag Dimension Enhancements
+
When 1 dimension (analytic account) is not enough,
+we can use dimension to create persistent dimension columns
+
+
analytic_tag_dimension
+
analytic_tag_dimension_enhanced
+
+
Following modules ensure that, analytic_tag_dimension will work with all new
+budget control objects. These are important for reporting purposes.
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
Before start using this module, following access right must be set.
+
+
+
Budget User for Budget Control Sheet, Budget Report
+
Budget Manager for Budget Period
+
+
+
Followings are sample steps to start with,
+
+
Create new Budget KPI
+
+
To create budget KPI using in budget template
+
+
+
Create new Budget Template
+
+
Add new template for controlling Budget following kpi-account
+
+
+
Create new Budget Period
+
+
+
Choose Budget template
+
Identify date range, i.e., 1 fiscal year
+
Plan Date Range, i.e., Quarter, the slot to fill allocation in budget control will split by quarter
+
Control Budget = True (if not check = not check budget for this period)
+
+
+
+
Create Budget Control Sheet
+
To create budget control sheet, you can create by using the helper,
+Action > Create Budget Control Sheet
+
+
+
Choose Analytic Group
+
Check All Analytic Accounts, this will list all analytic account in selected groups
+
Uncheck Initial Budget By Commitment, this is used only on following year to
+init budget allocation if they were committed amount carried over.
+
Click “Generate Budget Control Sheet”, and then view the newly created control sheets.
+
+
+
+
Allocate amount in Budget Control Sheets
+
Each analytic account will have its own sheet. Form Budget Period, click on the
+icon “Budget Control” or by Menu > Budgeting > Budget Control Sheet, to open them.
+
+
+
Within the “Plan Date Range” period, the Plan table displays all KPIs split by Plan Date Range
+
If you need to edit the plan, click the “Reset Options” tab, then select the KPIs you want to plan
+
Click the “Soft Reset” button to generate KPIs. The amounts in the plan table will not disappear.
+
Click the “Hard Reset” button to generate KPIs. The amounts in the plan table will disappear.
+
Allocate budget amount as appropriate.
+
Click Submit > Control, state will change to Controlled.
+
+
+
Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
+Once ready, you can click on “Soft Reset” or “Hard Reset” anytime.
+
+
Budget Reports
+
After some document transaction (i.e., invoice for actuals), you can view report anytime.
+
+
+
On Budget Control sheet, click on Monitoring for see this budget report
+
Menu Budgeting > Budget Monitoring, to show budget report in standard Odoo BI view.
+
+
+
+
Budget Checking
+
As we have checked Control Budget = True in third step, checking will occur
+every time an invoice is validated. You can test by validate invoice with big amount to exceed.
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.
+ This operation will involve creating a template for budget checking, which will have the following principles of creation.
+
+
+
+
Account: Multiple items can be selected in each line. Used for budget commitment (PR/PO/CT/AV/EX/Actual). The template is checked to see if there is enough budget.
+
KPI: Groups of accounts can be created from the Configuration > Budget KPI menu.
+
+
+
+ Generate Budget Control Sheet
+ generate.budget.control
+ form
+
+ form
+ new
+ {'default_init_kpis': True, 'default_use_all_kpis': True}
+
+
From 49f49bf86fe3a26e1fc600453159627cf5192cc4 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 3 Jan 2024 17:15:15 +0700
Subject: [PATCH 02/24] [FIX] budget_control: close budget with
fwd_analytic_account_id
---
budget_control/models/base_budget_move.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 29d021de..956ed7be 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -572,4 +572,6 @@ def close_budget_move(self):
use_amount_commit=True,
commit_note=_("Auto adjustment on close budget"),
adj_commit=True,
- ).commit_budget(reverse=True)
+ ).commit_budget(
+ reverse=True, analytic_account_id=docline.fwd_analytic_account_id
+ )
From 152ea1720012094d4e611c9f87cc28cb167ed05a Mon Sep 17 00:00:00 2001
From: Saran440
Date: Sun, 11 Feb 2024 17:36:29 +0700
Subject: [PATCH 03/24] [FIX] budget_control: check budget with sudo()
---
budget_control/models/budget_period.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index e8a4ef8b..f09dcfbd 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -212,6 +212,7 @@ def check_budget_precommit(self, doclines, doc_type="account"):
first do the normal commit, do checking, and remove commits"""
if not doclines:
return
+ doclines = doclines.sudo()
# Commit budget
budget_moves = []
vals_date_commit = []
From 99a265b2c3724f44fb8dc83e0a6d9838d3985e58 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 25 Apr 2024 14:49:27 +0700
Subject: [PATCH 04/24] [FIX] budget_control_purchase: add precommit check
budget function
Support for other module can check budget precommit in PO and Actual.
Standard will not check precommit because PO and Actual has next state draft is commit budget.
So, precommit budget is not need.
However, extensnion module like 'base_tier_validation_check_budget' can check budget in state draft.
we will improved base module budget for support precommit check budget
---
budget_control/models/account_move_line.py | 4 ++++
budget_control/models/budget_period.py | 8 ++++++++
2 files changed, 12 insertions(+)
diff --git a/budget_control/models/account_move_line.py b/budget_control/models/account_move_line.py
index 3318c7fd..118a4424 100644
--- a/budget_control/models/account_move_line.py
+++ b/budget_control/models/account_move_line.py
@@ -64,3 +64,7 @@ def _get_included_tax(self):
if self._name == "account.move.line":
return self.env.company.budget_include_tax_account
return self.env["account.tax"]
+
+ def uncommit_purchase_budget(self):
+ """This function for hooks"""
+ return
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index f09dcfbd..c96a7f7c 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -213,6 +213,11 @@ def check_budget_precommit(self, doclines, doc_type="account"):
if not doclines:
return
doclines = doclines.sudo()
+ # Allow precommit budget with related origin document (PO)
+ if doc_type == "account":
+ budget_moves_uncommit = doclines.with_context(
+ force_commit=True
+ ).uncommit_purchase_budget()
# Commit budget
budget_moves = []
vals_date_commit = []
@@ -231,6 +236,9 @@ def check_budget_precommit(self, doclines, doc_type="account"):
doclines.filtered(lambda l: l.id in vals_date_commit).write(
{"date_commit": False}
)
+ # Remove uncommit budget
+ if budget_moves_uncommit:
+ budget_moves_uncommit.unlink()
@api.model
def check_over_returned_budget(self, docline, reverse=False):
From ba0e7322916f770ee80ba7b2d409898630a12f2f Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 25 Apr 2024 18:46:05 +0700
Subject: [PATCH 05/24] [FIX] check budget with period
---
budget_control/models/budget_period.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index c96a7f7c..185fbe81 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -426,7 +426,11 @@ def _check_budget_available(self, controls, budget_period):
_("Chosen KPI %s is not valid for budgeting")
% template_lines.display_name
)
- balance = sum(q["amount"] for q in query_data if q["amount"] is not None)
+ balance = sum(
+ q["amount"]
+ for q in query_data
+ if q["amount"] is not None and q["budget_period_id"] == budget_period.id
+ )
# Show a warning if the budget is not sufficient
if float_compare(balance, 0.0, precision_rounding=2) == -1:
# Convert the balance to the document currency
From 4861601dabf0e0398bbabd4ac422fabf0ca3cdc8 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Fri, 26 Apr 2024 09:52:28 +0700
Subject: [PATCH 06/24] [FIX] error no variable
---
budget_control/models/budget_period.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 185fbe81..2c721ebf 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -213,6 +213,7 @@ def check_budget_precommit(self, doclines, doc_type="account"):
if not doclines:
return
doclines = doclines.sudo()
+ budget_moves_uncommit = False
# Allow precommit budget with related origin document (PO)
if doc_type == "account":
budget_moves_uncommit = doclines.with_context(
From 1d26eaffc44503313027e06ace8d9e8f2b953c3c Mon Sep 17 00:00:00 2001
From: "@" <@>
Date: Thu, 2 May 2024 23:00:00 +0700
Subject: [PATCH 07/24] [FIX] budget_control: error no date commit on convert
currency amount
---
budget_control/models/base_budget_move.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 956ed7be..b200f568 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -309,18 +309,18 @@ def _update_budget_commitment(self, budget_vals, reverse=False):
)
currency = hasattr(self, "currency_id") and self.currency_id or False
amount = budget_vals["amount_currency"] # init
+ today = fields.Date.context_today(self)
if (
not self.env.context.get("use_amount_commit")
and currency
and currency != company.currency_id
):
amount = self._get_amount_convert_currency(
- budget_vals["amount_currency"], currency, company, date_commit
+ budget_vals["amount_currency"], currency, company, date_commit or today
)
# By default, commit date is equal to document date
# this is correct for normal case, but may require different date
# in case of budget that carried to new period/year
- today = fields.Date.context_today(self)
res = {
"product_id": self.product_id.id,
"account_id": account.id,
From 5d0aa686a9a6a6923f3125ae3522f8de036551c9 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 8 May 2024 12:08:07 +0700
Subject: [PATCH 08/24] [FIX] budget_control: not check required analytic with
display_type
---
budget_control/models/base_budget_move.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index b200f568..08036edd 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -437,9 +437,10 @@ def commit_budget(self, reverse=False, **vals):
required_analytic = self.env.user.has_group(
"budget_control.group_required_analytic"
)
- # Required all document except move type entry
+ # Required all document except move type entry or display_type is not false
if (
required_analytic
+ and (hasattr(self, "display_type") and not self.display_type)
and not self[self._budget_analytic_field]
and not (
self._name == "account.move.line" and self.move_id.move_type == "entry"
From d177f693bd659673983cbf34a4d1fb02962d2f71 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Fri, 7 Jun 2024 12:02:05 +0700
Subject: [PATCH 09/24] [FIX] pre-commit
---
budget_control/models/budget_control.py | 18 +++++++++++-------
budget_control/models/budget_period.py | 8 ++++++--
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/budget_control/models/budget_control.py b/budget_control/models/budget_control.py
index afc164a0..b0162139 100644
--- a/budget_control/models/budget_control.py
+++ b/budget_control/models/budget_control.py
@@ -241,9 +241,10 @@ def _check_budget_control_over_consumed(self):
)
if budget_info["amount_balance"] < 0:
raise UserError(
- _("Total amount in KPI {} will result in {:,.2f}").format(
- line.name, budget_info["amount_balance"]
- )
+ _(
+ "Total amount in KPI %(name)s will result in {:,.2f}",
+ name=line.name,
+ ).format(budget_info["amount_balance"])
)
@api.onchange("use_all_kpis")
@@ -365,8 +366,10 @@ def _check_budget_amount(self):
):
raise UserError(
_(
- "Planning amount should equal to the released amount {:,.2f} {}"
- ).format(rec.released_amount, rec.currency_id.symbol)
+ "Planning amount should equal "
+ "to the released amount {:,.2f} %(symbol)s",
+ symbol=rec.currency_id.symbol,
+ ).format(rec.released_amount)
)
# Check plan vs intial
if (
@@ -380,8 +383,9 @@ def _check_budget_amount(self):
raise UserError(
_(
"Planning amount should be greater than "
- "initial balance {:,.2f} {}"
- ).format(rec.amount_initial, rec.currency_id.symbol)
+ "initial balance {:,.2f} %(symbol)s",
+ symbol=rec.currency_id.symbol,
+ ).format(rec.amount_initial)
)
def action_draft(self):
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 2c721ebf..e9864e35 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -438,7 +438,7 @@ def _check_budget_available(self, controls, budget_period):
balance_currency = self._get_balance_currency(
company, balance, doc_currency, date_commit
)
- fomatted_balance = format_amount(
+ formatted_balance = format_amount(
self.env, balance_currency, doc_currency
)
analytic_name = Analytic.browse(analytic_id).display_name
@@ -447,7 +447,11 @@ def _check_budget_available(self, controls, budget_period):
template_lines.display_name, analytic_name
)
warnings.append(
- _("{0}, will result in {1}").format(analytic_name, fomatted_balance)
+ _(
+ "%(analytic_name)s, will result in %(formatted_balance)s",
+ analytic_name=analytic_name,
+ formatted_balance=formatted_balance,
+ )
)
return list(set(warnings))
From 1834dfd5336afe1fa9510885b6f3116479896e72 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Fri, 7 Jun 2024 11:19:51 +0700
Subject: [PATCH 10/24] [FIX] budget_control: commit budget revenue case
---
budget_control/models/base_budget_move.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 08036edd..6732dca0 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -318,6 +318,14 @@ def _update_budget_commitment(self, budget_vals, reverse=False):
amount = self._get_amount_convert_currency(
budget_vals["amount_currency"], currency, company, date_commit or today
)
+
+ # NOTE: This is to handle the case of budget revenue.
+ if (
+ self._name == "account.move.line"
+ and self.move_id.move_type == "out_invoice"
+ ):
+ reverse = True
+
# By default, commit date is equal to document date
# this is correct for normal case, but may require different date
# in case of budget that carried to new period/year
From a1f71f4fb7acbda3ad392465f42287d13908fae4 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 18 Jul 2024 13:55:53 +0700
Subject: [PATCH 11/24] [MIG] budget_control: Migration to 16.0
---
budget_control/README.rst | 60 ++---
budget_control/__manifest__.py | 7 +-
budget_control/models/account_move.py | 18 +-
budget_control/models/account_move_line.py | 13 +-
budget_control/models/analytic_account.py | 27 +-
budget_control/models/base_budget_move.py | 236 +++++++++++-------
.../models/budget_commit_forward.py | 155 ++++++++----
budget_control/models/budget_control.py | 35 +--
.../models/budget_move_adjustment.py | 26 +-
budget_control/models/budget_period.py | 53 +++-
budget_control/models/budget_transfer.py | 15 +-
budget_control/readme/DESCRIPTION.rst | 19 +-
budget_control/readme/USAGE.rst | 26 +-
.../report/budget_monitor_report.py | 8 +-
.../report/budget_monitor_report_view.xml | 6 +-
budget_control/report/budget_move_views.xml | 9 +-
budget_control/static/description/index.html | 71 +++---
.../static/src/xml/budget_popover.xml | 69 ++---
budget_control/tests/common.py | 128 ++++++----
budget_control/tests/test_budget_control.py | 58 +++--
budget_control/views/account_budget_move.xml | 44 ++++
budget_control/views/account_move_views.xml | 31 +--
.../views/analytic_account_views.xml | 22 +-
.../views/budget_balance_forward_view.xml | 6 +-
.../views/budget_commit_forward_view.xml | 26 +-
budget_control/views/budget_control_view.xml | 123 +++++----
budget_control/views/budget_menuitem.xml | 2 +-
.../views/budget_move_adjustment_view.xml | 24 +-
budget_control/views/budget_period_view.xml | 8 +-
.../wizards/analytic_budget_info_view.xml | 38 ++-
.../wizards/budget_balance_forward_info.py | 7 +-
.../budget_balance_forward_info_view.xml | 2 +-
.../wizards/budget_commit_forward_info.py | 9 +-
.../budget_commit_forward_info_view.xml | 4 +-
.../wizards/generate_budget_control.py | 16 +-
.../wizards/generate_budget_control_view.xml | 2 +-
36 files changed, 780 insertions(+), 623 deletions(-)
create mode 100644 budget_control/views/account_budget_move.xml
diff --git a/budget_control/README.rst b/budget_control/README.rst
index 4265cd45..8cfae411 100644
--- a/budget_control/README.rst
+++ b/budget_control/README.rst
@@ -7,7 +7,7 @@ Budget Control
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:170b7aa450e2ccdfa27c0d5840cf49a8511a46198988caa073e23f03a6689384
+ !! source digest: sha256:353c8401879120bf194a5135e6f67838a807a00dc09ec0d8388ce36d90910040
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
@@ -17,7 +17,7 @@ Budget Control
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-ecosoft--odoo%2Fbudgeting-lightgray.png?logo=github
- :target: https://github.com/ecosoft-odoo/budgeting/tree/15.0/budget_control
+ :target: https://github.com/ecosoft-odoo/budgeting/tree/16.0/budget_control
:alt: ecosoft-odoo/budgeting
|badge1| |badge2| |badge3|
@@ -43,16 +43,11 @@ Budget Control Core Features:
for approved expense.
Note that, in this budget_control module, there is no extension for budget commitment yet.
-* **Budget KPI (budget.kpi)**
-
- Budget KPI is used to measure the efficiency of planning compared to actual usage.
- It is linked to Account Codes, and one Budget KPI can be associated with more than one account code.
-
* **Budget Template (budget.template)**
A Budget Template in the budget control system serves as a framework for controlling the budget,
allowing for the budget to be managed according to the pre-defined template.
- The budget template has a relationship with the budget kpi and accounting,
+ The budget template has a relationship with the accounting,
and is used to control spending based on pre-configured accounts.
* **Budget Period (budget.period)**
@@ -117,11 +112,10 @@ Following are brief explanation of what the extended module will do.
These modules extend base.budget.move for other document budget commitment.
-* budget_control_advance_clearing
-* budget_control_contract
* budget_control_expense
* budget_control_purchase
* budget_control_purchase_request
+* budget_control_sale
**Budget Allocation**
@@ -130,11 +124,6 @@ until set budget control. and allow create Master Data source of fund, analytic
Users can view source of fund monitoring report
* budget_allocation
-* budget_allocation_advance_clearing
-* budget_allocation_contract
-* budget_allocation_expense
-* budget_allocation_purchase
-* budget_allocation_purchase_request
**Tier Validation**
@@ -153,6 +142,10 @@ we can use dimension to create persistent dimension columns
Following modules ensure that, analytic_tag_dimension will work with all new
budget control objects. These are important for reporting purposes.
+* budget_allocation
+* budget_allocation_expense
+* budget_allocation_purchase
+
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
@@ -167,15 +160,14 @@ Usage
=====
Before start using this module, following access right must be set.
-
- - Budget User for Budget Control Sheet, Budget Report
- - Budget Manager for Budget Period
+ - Budget User for Budget Control Sheet, Budget Report
+ - Budget Manager for Budget Period
Followings are sample steps to start with,
1. Create new Budget KPI
- - To create budget KPI using in budget template
+ To create budget KPI using in budget template
2. Create new Budget Template
@@ -190,29 +182,26 @@ Followings are sample steps to start with,
4. Create Budget Control Sheet
- To create budget control sheet, you can create by using the helper,
+ To create budget control sheet, you can either create manually one by one or by using the helper,
Action > Create Budget Control Sheet
- - Choose Analytic Group
- - Check All Analytic Accounts, this will list all analytic account in selected groups
+ - Choose Analytic budget_control_purchase_tag_dimension
+ - Check All Analytic Account, this will list all analytic account in selected groups
- Uncheck Initial Budget By Commitment, this is used only on following year to
init budget allocation if they were committed amount carried over.
- - Click "Generate Budget Control Sheet", and then view the newly created control sheets.
+ - Click "Create Budget Control Sheet", and then view the newly created control sheets.
5. Allocate amount in Budget Control Sheets
Each analytic account will have its own sheet. Form Budget Period, click on the
- icon "Budget Control" or by Menu > Budgeting > Budget Control Sheet, to open them.
+ icon "Budget Control Sheets" or by Menu > Budgeting > Budget Control Sheet, to open them.
- - Within the "Plan Date Range" period, the Plan table displays all KPIs split by Plan Date Range
- - If you need to edit the plan, click the "Reset Options" tab, then select the KPIs you want to plan
- - Click the "Soft Reset" button to generate KPIs. The amounts in the plan table will not disappear.
- - Click the "Hard Reset" button to generate KPIs. The amounts in the plan table will disappear.
+ - Based on "Plan Date Range" period, Plan table will show all KPI split by Plan Date Range
- Allocate budget amount as appropriate.
- - Click Submit > Control, state will change to Controlled.
+ - Click Control button, state will change to Controlled.
Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
- Once ready, you can click on "Soft Reset" or "Hard Reset" anytime.
+ Once ready, you can click on "Reset Plan" anytime.
6. Budget Reports
@@ -232,7 +221,7 @@ 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 `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -256,11 +245,14 @@ Maintainers
.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
:target: https://github.com/kittiu
:alt: kittiu
+.. |maintainer-ru3ix-bbb| image:: https://github.com/ru3ix-bbb.png?size=40px
+ :target: https://github.com/ru3ix-bbb
+ :alt: ru3ix-bbb
-Current maintainer:
+Current maintainers:
-|maintainer-kittiu|
+|maintainer-kittiu| |maintainer-ru3ix-bbb|
-This module is part of the `ecosoft-odoo/budgeting `_ project on GitHub.
+This module is part of the `ecosoft-odoo/budgeting `_ project on GitHub.
You are welcome to contribute.
diff --git a/budget_control/__manifest__.py b/budget_control/__manifest__.py
index a2cf2123..3c11361a 100644
--- a/budget_control/__manifest__.py
+++ b/budget_control/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Budget Control",
- "version": "15.0.1.0.0",
+ "version": "16.0.1.0.0",
"category": "Accounting",
"license": "AGPL-3",
"author": "Ecosoft, Odoo Community Association (OCA)",
@@ -25,6 +25,7 @@
"wizards/confirm_state_budget_view.xml",
"wizards/budget_commit_forward_info_view.xml",
"wizards/budget_balance_forward_info_view.xml",
+ "views/account_budget_move.xml",
"views/budget_menuitem.xml",
"views/budget_kpi_view.xml",
"views/budget_template_view.xml",
@@ -45,12 +46,12 @@
],
"demo": ["demo/budget_template_demo.xml"],
"assets": {
- "web.assets_qweb": [
+ "web.assets_backend": [
"budget_control/static/src/xml/budget_popover.xml",
],
},
"installable": True,
- "maintainers": ["kittiu"],
+ "maintainers": ["kittiu", "ru3ix-bbb"],
"post_init_hook": "update_data_hooks",
"uninstall_hook": "uninstall_hook",
"development_status": "Alpha",
diff --git a/budget_control/models/account_move.py b/budget_control/models/account_move.py
index 756389dc..11a19d6d 100644
--- a/budget_control/models/account_move.py
+++ b/budget_control/models/account_move.py
@@ -46,17 +46,16 @@ def recompute_budget_move(self):
def close_budget_move(self):
self.mapped("invoice_line_ids").close_budget_move()
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
"""The default value of "Not affect budget" depends on journal.
except in the case of a manaully created journal entry.
"""
- not_affect_budget = vals.get("not_affect_budget", "None")
- journal_id = vals.get("journal_id")
- if not_affect_budget == "None" and journal_id:
- journal = self.env["account.journal"].browse(journal_id)
- vals["not_affect_budget"] = journal.not_affect_budget
- return super().create(vals)
+ for vals in vals_list:
+ if "not_affect_budget" not in vals and "journal_id" in vals:
+ journal = self.env["account.journal"].browse(vals["journal_id"])
+ vals["not_affect_budget"] = journal.not_affect_budget
+ return super().create(vals_list)
def write(self, vals):
"""
@@ -87,7 +86,8 @@ def _filtered_move_check_budget(self):
def action_post(self):
res = super().action_post()
- self.flush()
+ # Update database, then check budget
+ self.flush_model()
BudgetPeriod = self.env["budget.period"]
for move in self._filtered_move_check_budget():
BudgetPeriod.check_budget(move.line_ids)
diff --git a/budget_control/models/account_move_line.py b/budget_control/models/account_move_line.py
index 118a4424..b69e252d 100644
--- a/budget_control/models/account_move_line.py
+++ b/budget_control/models/account_move_line.py
@@ -36,25 +36,24 @@ def recompute_budget_move(self):
# Commit on invoice
invoice_line.commit_budget()
- def _init_docline_budget_vals(self, budget_vals):
+ def _init_docline_budget_vals(self, budget_vals, analytic_id):
self.ensure_one()
if self.move_id.move_type == "entry":
- budget_vals["amount_currency"] = self.amount_currency
+ total_amount = self.amount_currency
else:
sign = -1 if self.move_id.move_type in ("out_refund", "in_refund") else 1
discount = (100 - self.discount) / 100 if self.discount else 1
- budget_vals["amount_currency"] = (
- sign * self.price_unit * self.quantity * discount
- )
+ total_amount = sign * self.price_unit * self.quantity * discount
+ percent_analytic = self[self._budget_analytic_field].get(str(analytic_id))
+ budget_vals["amount_currency"] = total_amount * percent_analytic / 100
budget_vals["tax_ids"] = self.tax_ids.ids
# Document specific vals
budget_vals.update(
{
"move_line_id": self.id,
- "analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
}
)
- return super()._init_docline_budget_vals(budget_vals)
+ return super()._init_docline_budget_vals(budget_vals, analytic_id)
def _valid_commit_state(self):
return self.move_id.state == "posted"
diff --git a/budget_control/models/analytic_account.py b/budget_control/models/analytic_account.py
index ba5259e4..d23e14c1 100644
--- a/budget_control/models/analytic_account.py
+++ b/budget_control/models/analytic_account.py
@@ -166,6 +166,7 @@ def _find_next_analytic(self, next_date_range):
def _update_val_analytic(self, next_analytic, next_date_range):
BudgetPeriod = self.env["budget.period"]
+ vals_update = {}
type_id = next_analytic.budget_period_id.plan_date_range_type_id
period_id = BudgetPeriod.search(
[
@@ -173,11 +174,20 @@ def _update_val_analytic(self, next_analytic, next_date_range):
("plan_date_range_type_id", "=", type_id.id),
]
)
- return {"budget_period_id": period_id.id}
+ if period_id:
+ vals_update = {"budget_period_id": period_id.id}
+ else:
+ # No budget period found, update date_from and date_to
+ vals_update = {
+ "bm_date_from": next_date_range,
+ "bm_date_to": next_analytic.bm_date_to + relativedelta(years=1),
+ }
+ return vals_update
def _auto_create_next_analytic(self, next_date_range):
self.ensure_one()
- next_analytic = self.copy()
+ # Core odoo will add (copy) after name, but we need same name
+ next_analytic = self.copy(default={"name": self.name})
val_update = self._update_val_analytic(next_analytic, next_date_range)
next_analytic.write(val_update)
return next_analytic
@@ -230,12 +240,13 @@ def _compute_bm_date(self):
rec.bm_date_to = rec.budget_period_id.bm_date_to
def _auto_adjust_date_commit(self, docline):
- self.ensure_one()
- if self.auto_adjust_date_commit:
- if self.bm_date_from and self.bm_date_from > docline.date_commit:
- docline.date_commit = self.bm_date_from
- elif self.bm_date_to and self.bm_date_to < docline.date_commit:
- docline.date_commit = self.bm_date_to
+ for rec in self:
+ if not rec.auto_adjust_date_commit:
+ continue
+ if rec.bm_date_from and rec.bm_date_from > docline.date_commit:
+ docline.date_commit = rec.bm_date_from
+ elif rec.bm_date_to and rec.bm_date_to < docline.date_commit:
+ docline.date_commit = rec.bm_date_to
def action_edit_initial_available(self):
return {
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 6732dca0..665a2c8c 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -34,7 +34,7 @@ class BaseBudgetMove(models.AbstractModel):
)
kpi_id = fields.Many2one(
comodel_name="budget.kpi",
- related="template_line_id.kpi_id",
+ compute="_compute_kpi_id",
store=True,
)
date = fields.Date(
@@ -58,16 +58,12 @@ class BaseBudgetMove(models.AbstractModel):
index=True,
readonly=True,
)
- analytic_group = fields.Many2one(
- comodel_name="account.analytic.group",
+ analytic_plan = fields.Many2one(
+ comodel_name="account.analytic.plan",
auto_join=True,
index=True,
readonly=True,
)
- analytic_tag_ids = fields.Many2many(
- comodel_name="account.analytic.tag",
- string="Analytic Tags",
- )
amount_currency = fields.Float(
required=True,
help="Amount in multi currency",
@@ -95,6 +91,11 @@ class BaseBudgetMove(models.AbstractModel):
help="This budget move line is the result of 'Forward Budget Commitment'",
)
+ @api.depends("template_line_id")
+ def _compute_kpi_id(self):
+ for rec in self:
+ rec.kpi_id = rec.template_line_id.kpi_id
+
def _compute_reference(self):
"""Compute reference name of the budget move document"""
self.update({"reference": False})
@@ -109,7 +110,7 @@ class BudgetDoclineMixinBase(models.AbstractModel):
_description = (
"Base of budget.docline.mixin, used for non budgeting model extension"
)
- _budget_analytic_field = "analytic_account_id"
+ _budget_analytic_field = "analytic_distribution"
# Budget related variables
_budget_date_commit_fields = [] # Date used for budget commitment
_budget_move_model = False # account.budget.move
@@ -131,7 +132,7 @@ class BudgetDoclineMixin(models.AbstractModel):
compute="_compute_can_commit",
help="If True, this docline is eligible to create budget move",
)
- amount_commit = fields.Float(
+ amount_commit = fields.Json(
compute="_compute_commit",
copy=False,
store=True,
@@ -146,12 +147,9 @@ class BudgetDoclineMixin(models.AbstractModel):
compute="_compute_auto_adjust_date_commit",
readonly=True,
)
- fwd_analytic_account_id = fields.Many2one(
- comodel_name="account.analytic.account",
+ fwd_analytic_distribution = fields.Json(
string="Carry Forward Analytic",
copy=False,
- readonly=False,
- index=True,
help="If specified, recompute budget will take this into account",
)
fwd_date_commit = fields.Date(
@@ -174,16 +172,26 @@ def _budget_field(self):
def _valid_commit_state(self):
raise ValidationError(_("No implementation error!"))
- @api.onchange("fwd_analytic_account_id")
- def _onchange_fwd_analytic_account_id(self):
- self.fwd_date_commit = self.fwd_analytic_account_id.bm_date_from
+ def _convert_analytics(self, analytic_distribution=False):
+ Analytic = self.env["account.analytic.account"]
+ analytics = analytic_distribution or self[self._budget_analytic_field]
+ if not analytics:
+ return Analytic
+ # Check analytic from distribution it send data with JSON type 'dict'
+ # and we need convert it to analytic object
+ if self._budget_analytic_field == "analytic_distribution":
+ account_analytic_ids = [int(k) for k in analytics.keys()]
+ analytics = Analytic.browse(account_analytic_ids)
+ return analytics
@api.depends(lambda self: [self._budget_analytic_field])
def _compute_auto_adjust_date_commit(self):
+ """Auto adjust is True if some analytic account is checked auto adjust"""
for docline in self:
- docline.auto_adjust_date_commit = docline[
- self._budget_analytic_field
- ].auto_adjust_date_commit
+ analytics = docline._convert_analytics()
+ docline.auto_adjust_date_commit = any(
+ aa.auto_adjust_date_commit for aa in analytics
+ )
@api.depends()
def _compute_can_commit(self):
@@ -208,9 +216,25 @@ def _compute_commit(self):
- Calc date_commit if not exists and on 1st budget_move_ids only or False
"""
for rec in self:
- debit = sum(rec.budget_move_ids.mapped("debit"))
- credit = sum(rec.budget_move_ids.mapped("credit"))
- rec.amount_commit = debit - credit
+ analytic_distribution = rec[self._budget_analytic_field]
+ # Add analytic_distribution from forward_commit
+ if rec.fwd_analytic_distribution:
+ for analytic_id, aa_percent in rec.fwd_analytic_distribution.items():
+ analytic_distribution[analytic_id] = aa_percent
+
+ if not analytic_distribution:
+ continue
+ # Compute amount commit each analytic
+ amount_commit_json = {}
+ for analytic_id in analytic_distribution: # Get id only
+ budget_move = rec.budget_move_ids.filtered(
+ lambda move: move.analytic_account_id.id == int(analytic_id)
+ )
+ debit = sum(budget_move.mapped("debit"))
+ credit = sum(budget_move.mapped("credit"))
+ amount_commit_json[analytic_id] = debit - credit
+ rec.amount_commit = amount_commit_json
+ # Compute date commit
if rec.budget_move_ids:
rec.date_commit = min(rec.budget_move_ids.mapped("date"))
else:
@@ -219,30 +243,42 @@ def _compute_commit(self):
def _compute_json_budget_popover(self):
FloatConverter = self.env["ir.qweb.field.float"]
for rec in self:
- analytic = rec[self._budget_analytic_field]
- if not analytic:
+ analytic_distribution = rec[self._budget_analytic_field]
+ analytic_account = rec._convert_analytics(
+ analytic_distribution=analytic_distribution
+ )
+ if not analytic_account:
rec.json_budget_popover = False
continue
# Budget Period is required, even a False one
budget_period = self.env["budget.period"]._get_eligible_budget_period(
date=rec.date_commit
)
- analytic = analytic.with_context(budget_period_ids=[budget_period.id])
rec.json_budget_popover = dumps(
{
"title": _("Budget Figure"),
"icon": "fa-info-circle",
"popoverTemplate": "budget_control.budgetPopOver",
- "analytic": analytic.display_name,
- "budget": FloatConverter.value_to_html(
- analytic.amount_budget, {"decimal_precision": "Product Price"}
- ),
- "consumed": FloatConverter.value_to_html(
- analytic.amount_consumed, {"decimal_precision": "Product Price"}
- ),
- "balance": FloatConverter.value_to_html(
- analytic.amount_balance, {"decimal_precision": "Product Price"}
- ),
+ "analytic": [
+ {
+ "id": aa.id,
+ "name": aa.display_name,
+ "budget": FloatConverter.value_to_html(
+ aa.amount_budget, {"decimal_precision": "Product Price"}
+ ),
+ "consumed": FloatConverter.value_to_html(
+ aa.amount_consumed,
+ {"decimal_precision": "Product Price"},
+ ),
+ "balance": FloatConverter.value_to_html(
+ aa.amount_balance,
+ {"decimal_precision": "Product Price"},
+ ),
+ }
+ for aa in analytic_account.with_context(
+ budget_period_ids=[budget_period.id]
+ )
+ ],
}
)
@@ -274,7 +310,7 @@ def _set_date_commit(self):
return
if not self._budget_date_commit_fields:
raise ValidationError(_("'_budget_date_commit_fields' is not set!"))
- analytic = docline[self._budget_analytic_field]
+ analytic = docline._convert_analytics()
# If the analytic field is not set, set the date commit to False and return.
if not analytic:
docline.date_commit = False
@@ -294,14 +330,10 @@ def _get_amount_convert_currency(
amount_currency, company.currency_id, company, date_commit
)
- def _update_budget_commitment(self, budget_vals, reverse=False):
+ def _update_budget_commitment(self, budget_vals, analytic, reverse=False):
self.ensure_one()
company = self.env.user.company_id
account = self.account_id
- # Check params analytic_account_id, if not it should be self analytic
- analytic_account = budget_vals.get("analytic_account_id", False)
- if not analytic_account:
- analytic_account = self[self._budget_analytic_field]
budget_moves = self[self._budget_field()]
date_commit = budget_vals.get(
"date",
@@ -318,22 +350,20 @@ def _update_budget_commitment(self, budget_vals, reverse=False):
amount = self._get_amount_convert_currency(
budget_vals["amount_currency"], currency, company, date_commit or today
)
-
# NOTE: This is to handle the case of budget revenue.
if (
self._name == "account.move.line"
and self.move_id.move_type == "out_invoice"
):
reverse = True
-
# By default, commit date is equal to document date
# this is correct for normal case, but may require different date
# in case of budget that carried to new period/year
res = {
"product_id": self.product_id.id,
"account_id": account.id,
- "analytic_account_id": analytic_account.id,
- "analytic_group": analytic_account.group_id.id,
+ "analytic_account_id": analytic.id,
+ "analytic_plan": analytic.plan_id.id,
"date": date_commit or today,
"amount_currency": budget_vals["amount_currency"],
"debit": not reverse and amount or 0,
@@ -361,6 +391,8 @@ def _update_template_line(self, budget_move):
template_lines, controls[0]
)
budget_move.template_line_id = template_line.id
+ # Set KPI for check budget
+ budget_move.kpi_id = template_line.kpi_id.id
return budget_move
def _get_domain_fwd_line(self, docline):
@@ -376,14 +408,13 @@ def forward_commit(self):
ForwardLine = self.env["budget.commit.forward.line"]
BudgetPeriod = self.env["budget.period"]
for docline in self:
- if not docline.fwd_analytic_account_id or not docline.fwd_date_commit:
+ if not docline.fwd_analytic_distribution or not docline.fwd_date_commit:
return
if (
- docline[self._budget_analytic_field] == docline.fwd_analytic_account_id
+ docline[self._budget_analytic_field]
+ == docline.fwd_analytic_distribution
and docline.date_commit == docline.fwd_date_commit
): # no forward to same date
- # docline.fwd_analytic_account_id = False
- # docline.fwd_date_commit = False
return
domain_fwd_line = self._get_domain_fwd_line(docline)
fwd_lines = ForwardLine.search(domain_fwd_line)
@@ -445,48 +476,70 @@ def commit_budget(self, reverse=False, **vals):
required_analytic = self.env.user.has_group(
"budget_control.group_required_analytic"
)
- # Required all document except move type entry or display_type is not false
+ # Required all document except move that check 'Not Affect Budget'
+ # and not 'Tax' and display_type is not false
if (
required_analytic
and (hasattr(self, "display_type") and not self.display_type)
and not self[self._budget_analytic_field]
and not (
- self._name == "account.move.line" and self.move_id.move_type == "entry"
+ self._name == "account.move.line"
+ and (self.move_id.not_affect_budget or self.tax_line_id)
)
- and not self._context.get("bypass_required_analytic")
):
raise UserError(_("Please fill analytic account."))
self.prepare_commit()
to_commit = self.env.context.get("force_commit") or self._valid_commit_state()
if self.can_commit and to_commit:
- # Set amount_currency
- budget_vals = self._init_docline_budget_vals(vals)
- # Case budget_include_tax = True
- budget_vals = self._budget_include_tax(budget_vals)
- # Case force use_amount_commit, this should overwrite tax compute
- if self.env.context.get("use_amount_commit"):
- budget_vals["amount_currency"] = self.amount_commit
- if self.env.context.get("fwd_amount_commit"):
- budget_vals["amount_currency"] = self.env.context.get(
- "fwd_amount_commit"
+ budget_commit_vals = []
+ # Specific analytic account
+ if vals.get("analytic_account_id", False):
+ analytic_account = vals["analytic_account_id"]
+ else:
+ analytic_account = self._convert_analytics(
+ analytic_distribution=vals.get("analytic_distribution", False)
)
- # Only on case reverse, to force use return_amount_commit
- if reverse and "return_amount_commit" in self.env.context:
- budget_vals["amount_currency"] = self.env.context.get(
- "return_amount_commit"
+ # Delete analytic_distribution from vals
+ if vals.get("analytic_distribution", "/") != "/":
+ del vals["analytic_distribution"]
+
+ for analytic in analytic_account:
+ # Set amount_currency
+ budget_vals = self._init_docline_budget_vals(vals, analytic.id)
+ # Case budget_include_tax = True
+ budget_vals = self._budget_include_tax(budget_vals)
+ # Case force use_amount_commit, this should overwrite tax compute
+ if self.env.context.get("use_amount_commit"):
+ budget_vals["amount_currency"] = self.amount_commit[
+ str(analytic.id)
+ ]
+ # Case forward_commit
+ if self.env.context.get("fwd_amount_commit"):
+ budget_vals["amount_currency"] = self.env.context.get(
+ "fwd_amount_commit"
+ )
+ # Only on case reverse, to force use return_amount_commit
+ if reverse and "return_amount_commit" in self.env.context:
+ budget_vals["amount_currency"] = self.env.context.get(
+ "return_amount_commit"
+ )
+ # Complete budget commitment dict
+ budget_vals = self._update_budget_commitment(
+ budget_vals, analytic, reverse=reverse
)
- # Complete budget commitment dict
- budget_vals = self._update_budget_commitment(budget_vals, reverse=reverse)
- # Final note
- budget_vals["note"] = self.env.context.get("commit_note")
- # Is Adjustment Commit
- budget_vals["adj_commit"] = self.env.context.get("adj_commit")
- # Is Forward Commit
- budget_vals["fwd_commit"] = self.env.context.get("fwd_commit")
- # Create budget move
- if not budget_vals["amount_currency"]:
- return False
- budget_move = self.env[self._budget_model()].create(budget_vals)
+ # Final note
+ budget_vals["note"] = self.env.context.get("commit_note")
+ # Is Adjustment Commit
+ budget_vals["adj_commit"] = self.env.context.get("adj_commit")
+ # Is Forward Commit
+ budget_vals["fwd_commit"] = self.env.context.get("fwd_commit")
+ # Create budget move
+ if not budget_vals["amount_currency"]:
+ return False
+ budget_commit_vals.append(budget_vals.copy())
+ # Clear old values for case multi analytics
+ del budget_vals["amount_currency"]
+ budget_move = self.env[self._budget_model()].create(budget_commit_vals)
# Update Template Line
budget_move = self._update_template_line(budget_move)
if reverse: # On reverse, make sure not over returned
@@ -498,7 +551,7 @@ def commit_budget(self, reverse=False, **vals):
def _required_fields_to_commit(self):
return [self._budget_analytic_field]
- def _init_docline_budget_vals(self, budget_vals):
+ def _init_docline_budget_vals(self, budget_vals, analytic_id):
"""To be extended by docline to add untaxed amount_currency"""
if "amount_currency" not in budget_vals:
raise ValidationError(_("No amount_currency passed in!"))
@@ -557,19 +610,20 @@ def _check_date_commit(self):
"""Commit date must inline with analytic account"""
self.ensure_one()
docline = self
- analytic = docline[self._budget_analytic_field]
- if analytic:
+ analytics = docline._convert_analytics()
+ if analytics:
if not docline.date_commit:
raise UserError(_("No budget commitment date"))
- date_from = analytic.bm_date_from
- date_to = analytic.bm_date_to
- if (date_from and date_from > docline.date_commit) or (
- date_to and date_to < docline.date_commit
- ):
- raise UserError(
- _("Budget date commit is not within date range of - %s")
- % analytic.display_name
- )
+ for analytic in analytics:
+ date_from = analytic.bm_date_from
+ date_to = analytic.bm_date_to
+ if (date_from and date_from > docline.date_commit) or (
+ date_to and date_to < docline.date_commit
+ ):
+ raise UserError(
+ _("Budget date commit is not within date range of - %s")
+ % analytic.display_name
+ )
else:
if docline.date_commit:
raise UserError(_("Budget commitment date not required"))
@@ -582,5 +636,5 @@ def close_budget_move(self):
commit_note=_("Auto adjustment on close budget"),
adj_commit=True,
).commit_budget(
- reverse=True, analytic_account_id=docline.fwd_analytic_account_id
+ reverse=True, analytic_distribution=docline.fwd_analytic_distribution
)
diff --git a/budget_control/models/budget_commit_forward.py b/budget_control/models/budget_commit_forward.py
index 2e854aff..8fcbf515 100644
--- a/budget_control/models/budget_commit_forward.py
+++ b/budget_control/models/budget_commit_forward.py
@@ -27,6 +27,9 @@ class BudgetCommitForward(models.Model):
related="to_budget_period_id.bm_date_from",
string="Move commit to date",
)
+ filter_lines = fields.Many2many(
+ comodel_name="budget.commit.forward.line",
+ )
state = fields.Selection(
[
("draft", "Draft"),
@@ -58,14 +61,6 @@ class BudgetCommitForward(models.Model):
_sql_constraints = [
("name_uniq", "UNIQUE(name)", "Name must be unique!"),
]
- total_commitment = fields.Monetary(
- compute="_compute_total_commitment",
- )
-
- @api.depends("forward_line_ids")
- def _compute_total_commitment(self):
- for rec in self:
- rec.total_commitment = sum(rec.forward_line_ids.mapped("amount_commit"))
def _compute_missing_analytic(self):
for rec in self:
@@ -75,19 +70,43 @@ def _compute_missing_analytic(self):
)
)
- def _get_base_domain(self):
+ def _get_base_from_extension(self, res_model):
"""For module extension"""
- self.ensure_one()
- domain = [
- ("amount_commit", ">", 0.0),
- ("date_commit", "<", self.to_date_commit),
- ("fwd_date_commit", "!=", self.to_date_commit),
- ]
- return domain
+ return ""
- def _get_commit_docline(self, res_model):
+ def _get_base_domain_extension(self, res_model):
"""For module extension"""
- return []
+ return ""
+
+ def _get_name_model(self, res_model, need_replace=False):
+ return res_model.replace(".", "_") if need_replace else res_model
+
+ def _get_commit_docline(self, res_model):
+ """Base domain for query"""
+ self.ensure_one()
+ model_name_db = self._get_name_model(res_model, need_replace=True)
+ query = """
+ SELECT a.id
+ FROM %s a
+ %s
+ , jsonb_each_text(a.amount_commit) AS kv(key, value)
+ WHERE value::numeric != 0 AND a.date_commit < '%s'
+ AND (a.fwd_date_commit != '%s' OR a.fwd_date_commit is null) %s;
+ """
+ query_string = query % (
+ model_name_db,
+ self._get_base_from_extension(res_model),
+ self.to_date_commit,
+ self.to_date_commit,
+ self._get_base_domain_extension(res_model),
+ )
+ # pylint: disable=sql-injection
+ self.env.cr.execute(query_string)
+ # Get all domain ids, remove duplicate from many analytics in 1 line
+ domain_ids = list({row["id"] for row in self.env.cr.dictfetchall()})
+ model_name = self._get_name_model(res_model)
+ obj_ids = self.env[model_name].browse(domain_ids)
+ return obj_ids
def _get_document_number(self, doc):
"""For module extension"""
@@ -101,29 +120,30 @@ def _get_budget_docline_model(self):
def _prepare_vals_forward(self, docs, res_model):
self.ensure_one()
value_dict = []
+ AnalyticAccount = self.env["account.analytic.account"]
for doc in docs:
analytic_account = (
- doc.fwd_analytic_account_id or doc[doc._budget_analytic_field]
- )
- method_type = False
- if (
- analytic_account.bm_date_to
- and analytic_account.bm_date_to < self.to_date_commit
- ):
- method_type = "new"
- value_dict.append(
- {
- "forward_id": self.id,
- "analytic_account_id": analytic_account.id,
- "method_type": method_type,
- "res_model": res_model,
- "res_id": doc.id,
- "document_id": "{},{}".format(doc._name, doc.id),
- "document_number": self._get_document_number(doc),
- "amount_commit": doc.amount_commit,
- "date_commit": doc.fwd_date_commit or doc.date_commit,
- }
+ doc.fwd_analytic_distribution or doc[doc._budget_analytic_field]
)
+ for analytic_id, aa_percent in analytic_account.items():
+ method_type = False
+ analytic = AnalyticAccount.browse(int(analytic_id))
+ if analytic.bm_date_to and analytic.bm_date_to < self.to_date_commit:
+ method_type = "new"
+ value_dict.append(
+ {
+ "forward_id": self.id,
+ "analytic_account_id": analytic_id,
+ "analytic_percent": aa_percent / 100,
+ "method_type": method_type,
+ "res_model": res_model,
+ "res_id": doc.id,
+ "document_id": "{},{}".format(doc._name, doc.id),
+ "document_number": self._get_document_number(doc),
+ "amount_commit": doc.amount_commit[str(analytic_id)],
+ "date_commit": doc.fwd_date_commit or doc.date_commit,
+ }
+ )
return value_dict
def action_review_budget_commit(self):
@@ -132,6 +152,10 @@ def action_review_budget_commit(self):
rec.get_budget_commit_forward(res_model)
self.write({"state": "review"})
+ def action_filter_lines(self):
+ for rec in self:
+ rec.forward_line_ids = rec.filter_lines
+
def get_budget_commit_forward(self, res_model):
"""Get budget commitment forward for each new commit document type."""
self = self.sudo()
@@ -201,18 +225,32 @@ def _get_forward_initial_commit(self, domain):
def _do_forward_commit(self, reverse=False):
"""Create carry forward budget move to all related documents"""
self = self.sudo()
+ _analytic_field = "analytic_account_id" if reverse else "to_analytic_account_id"
for rec in self:
+ group_document = {}
+ # Group by document
for line in rec.forward_line_ids:
- line.document_id.write(
+ if line.document_id in group_document:
+ group_document[line.document_id].append(line)
+ else:
+ group_document[line.document_id] = [line]
+ for doc, fwd_line in group_document.items():
+ # Convert to json
+ fwd_analytic_distribution = {}
+ for line in fwd_line:
+ fwd_analytic_distribution[str(line[_analytic_field].id)] = (
+ line.analytic_percent * 100
+ )
+ doc.write(
{
- "fwd_analytic_account_id": reverse
- and line.analytic_account_id
- or line.to_analytic_account_id,
+ "fwd_analytic_distribution": fwd_analytic_distribution,
"fwd_date_commit": reverse
- and line.date_commit
+ and fwd_line[0].date_commit
or rec.to_date_commit,
}
)
+ # For case extend
+ for line in rec.forward_line_ids:
if not reverse and line.method_type == "extend":
line.to_analytic_account_id.bm_date_to = (
rec.to_budget_period_id.bm_date_to
@@ -222,12 +260,12 @@ def _do_update_initial_commit(self, reverse=False):
"""Update all Analytic Account's initial commit value related to budget period"""
self.ensure_one()
# Reset initial when cancel document only
- Analytic = self.env["account.analytic.account"]
+ AnalyticAccount = self.env["account.analytic.account"]
domain = [("forward_id", "=", self.id)]
if reverse:
forward_vals = self._get_forward_initial_commit(domain)
for val in forward_vals:
- analytic = Analytic.browse(val["analytic_account_id"])
+ analytic = AnalyticAccount.browse(val["analytic_account_id"])
analytic.initial_commit -= val["initial_commit"]
return
forward_duplicate = self.env["budget.commit.forward"].search(
@@ -240,7 +278,7 @@ def _do_update_initial_commit(self, reverse=False):
domain.append(("forward_id.state", "in", ["review", "done"]))
forward_vals = self._get_forward_initial_commit(domain)
for val in forward_vals:
- analytic = Analytic.browse(val["analytic_account_id"])
+ analytic = AnalyticAccount.browse(val["analytic_account_id"])
# Check first forward commit in the year, it should overwrite initial commit
if not forward_duplicate:
analytic.initial_commit = val["initial_commit"]
@@ -285,6 +323,7 @@ def action_draft(self):
class BudgetCommitForwardLine(models.Model):
_name = "budget.commit.forward.line"
_description = "Budget Commit Forward Line"
+ _rec_names_search = ["document_number", "analytic_account_id"]
forward_id = fields.Many2one(
comodel_name="budget.commit.forward",
@@ -300,6 +339,9 @@ class BudgetCommitForwardLine(models.Model):
required=True,
readonly=True,
)
+ analytic_percent = fields.Float(
+ readonly=True,
+ )
method_type = fields.Selection(
selection=[
("new", "New"),
@@ -314,11 +356,9 @@ class BudgetCommitForwardLine(models.Model):
string="Forward to Analytic",
compute="_compute_to_analytic_account_id",
store=True,
- readonly=True,
)
bm_date_to = fields.Date(
- related="analytic_account_id.bm_date_to",
- readonly=True,
+ compute="_compute_bm_date_to",
)
res_model = fields.Selection(
selection=[],
@@ -357,6 +397,11 @@ class BudgetCommitForwardLine(models.Model):
readonly=True,
)
+ @api.depends("analytic_account_id")
+ def _compute_bm_date_to(self):
+ for rec in self:
+ rec.bm_date_to = rec.analytic_account_id.bm_date_to
+
@api.depends("method_type")
def _compute_to_analytic_account_id(self):
for rec in self:
@@ -382,3 +427,15 @@ def _compute_to_analytic_account_id(self):
rec.to_analytic_account_id = rec.analytic_account_id.next_year_analytic(
auto_create=False
)
+
+ def name_get(self):
+ return [
+ (
+ r.id,
+ "{document_number} - {analytic}".format(
+ document_number=r.document_number.display_name,
+ analytic=r.analytic_account_id.name,
+ ),
+ )
+ for r in self
+ ]
diff --git a/budget_control/models/budget_control.py b/budget_control/models/budget_control.py
index b0162139..c79e0f1a 100644
--- a/budget_control/models/budget_control.py
+++ b/budget_control/models/budget_control.py
@@ -51,13 +51,9 @@ class BudgetControl(models.Model):
tracking=True,
ondelete="restrict",
)
- analytic_tag_ids = fields.Many2many(
- comodel_name="account.analytic.tag", string="Analytic Tags"
- )
- analytic_group = fields.Many2one(
- comodel_name="account.analytic.group",
- string="Analytic Group",
- related="analytic_account_id.group_id",
+ analytic_plan = fields.Many2one(
+ comodel_name="account.analytic.plan",
+ related="analytic_account_id.plan_id",
store=True,
)
line_ids = fields.One2many(
@@ -191,7 +187,6 @@ class BudgetControl(models.Model):
@api.constrains("active", "state", "analytic_account_id", "budget_period_id")
def _check_budget_control_unique(self):
"""Not allow multiple active budget control on same period"""
- self.flush()
query = """
SELECT analytic_account_id, budget_period_id, COUNT(*)
FROM budget_control
@@ -242,9 +237,10 @@ def _check_budget_control_over_consumed(self):
if budget_info["amount_balance"] < 0:
raise UserError(
_(
- "Total amount in KPI %(name)s will result in {:,.2f}",
- name=line.name,
- ).format(budget_info["amount_balance"])
+ "Total amount in KPI {line_name} will result in {amount:,.2f}"
+ ).format(
+ line_name=line.name, amount=budget_info["amount_balance"]
+ )
)
@api.onchange("use_all_kpis")
@@ -267,7 +263,7 @@ def action_confirm_state(self):
@api.depends("allocated_amount")
def _compute_allocated_released_amount(self):
for rec in self:
- rec.released_amount = rec.allocated_amount
+ rec.released_amount = rec.allocated_amount + rec.transferred_amount
@api.depends("released_amount", "amount_budget")
def _compute_diff_amount(self):
@@ -366,10 +362,9 @@ def _check_budget_amount(self):
):
raise UserError(
_(
- "Planning amount should equal "
- "to the released amount {:,.2f} %(symbol)s",
- symbol=rec.currency_id.symbol,
- ).format(rec.released_amount)
+ "Planning amount should equal to the "
+ "released amount {amount:,.2f} {symbol}"
+ ).format(amount=rec.released_amount, symbol=rec.currency_id.symbol)
)
# Check plan vs intial
if (
@@ -383,9 +378,8 @@ def _check_budget_amount(self):
raise UserError(
_(
"Planning amount should be greater than "
- "initial balance {:,.2f} %(symbol)s",
- symbol=rec.currency_id.symbol,
- ).format(rec.amount_initial)
+ "initial balance {amount:,.2f} {symbol}"
+ ).format(amount=rec.amount_initial, symbol=rec.currency_id.symbol)
)
def action_draft(self):
@@ -551,9 +545,6 @@ class BudgetControlLine(models.Model):
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account", string="Analytic account"
)
- analytic_tag_ids = fields.Many2many(
- comodel_name="account.analytic.tag", string="Analytic Tags"
- )
amount = fields.Float()
template_line_id = fields.Many2one(
comodel_name="budget.template.line",
diff --git a/budget_control/models/budget_move_adjustment.py b/budget_control/models/budget_move_adjustment.py
index 722718e5..71f83518 100644
--- a/budget_control/models/budget_move_adjustment.py
+++ b/budget_control/models/budget_move_adjustment.py
@@ -56,14 +56,16 @@ class BudgetMoveAdjustment(models.Model):
tracking=True,
)
- @api.model
- def create(self, vals):
+ @api.model_create_multi
+ def create(self, vals_list):
"""Generate a new name using the 'budget.move.adjustment' sequence"""
- if vals.get("name", "/") == "/":
- vals["name"] = (
- self.env["ir.sequence"].next_by_code("budget.move.adjustment") or "/"
- )
- return super().create(vals)
+ for vals in vals_list:
+ if vals.get("name", "/") == "/":
+ vals["name"] = (
+ self.env["ir.sequence"].next_by_code("budget.move.adjustment")
+ or "/"
+ )
+ return super().create(vals_list)
def unlink(self):
"""Check that only records with state 'draft' can be deleted."""
@@ -112,6 +114,7 @@ class BudgetMoveAdjustmentItem(models.Model):
_description = "Budget Moves Adjustment Lines"
_budget_date_commit_fields = ["adjust_id.date_commit"]
_budget_move_model = "account.budget.move"
+ _budget_analytic_field = "analytic_account_id"
_doc_rel = "adjust_id"
adjust_id = fields.Many2one(
@@ -148,10 +151,6 @@ class BudgetMoveAdjustmentItem(models.Model):
required=True,
index=True,
)
- analytic_tag_ids = fields.Many2many(
- comodel_name="account.analytic.tag",
- string="Analytic Tags",
- )
currency_id = fields.Many2one(
related="adjust_id.currency_id",
readonly=True,
@@ -178,7 +177,7 @@ def recompute_budget_move(self):
item.budget_move_ids.unlink()
item.commit_budget()
- def _init_docline_budget_vals(self, budget_vals):
+ def _init_docline_budget_vals(self, budget_vals, analytic_id):
self.ensure_one()
budget_vals["amount_currency"] = (
-self.amount if self.adjust_type == "release" else self.amount
@@ -187,10 +186,9 @@ def _init_docline_budget_vals(self, budget_vals):
budget_vals.update(
{
"adjust_item_id": self.id,
- "analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
}
)
- return super()._init_docline_budget_vals(budget_vals)
+ return super()._init_docline_budget_vals(budget_vals, analytic_id)
def _valid_commit_state(self):
return self.adjust_id.state == "done"
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index e9864e35..6d9105dc 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -166,11 +166,40 @@ def check_budget(self, doclines, doc_type="account"):
return
self = self.sudo()
budget_constraints = self._get_budget_constraint()
+ all_analytics = doclines.mapped(doclines._budget_analytic_field)
+ # Get All Analytic Account
+ if doclines._budget_analytic_field == "analytic_distribution":
+ all_analytic_ids = set()
+ for data_dict in all_analytics:
+ # Check percent analytic account must be 100% only
+ total_sum = sum(data_dict.values())
+ if (
+ float_compare(
+ total_sum,
+ 100.0,
+ precision_rounding=2,
+ )
+ != 0
+ ):
+ raise UserError(
+ _(
+ "The total sum percent of Analytic Account must 100%. "
+ "Please check again."
+ )
+ )
+ all_analytic_ids.update(int(key) for key in data_dict.keys())
+ else:
+ all_analytic_ids = all_analytics
# Check budget by group analytic. For case many budget periods in one document.
- for aa in doclines[doclines._budget_analytic_field]:
- doclines = doclines.filtered(
- lambda l: l[doclines._budget_analytic_field] == aa
- )
+ for aa in all_analytic_ids:
+ if isinstance(aa, int):
+ doclines = doclines.filtered(
+ lambda l: l[doclines._budget_analytic_field].get(str(aa))
+ )
+ else:
+ doclines = doclines.filtered(
+ lambda l: l[doclines._budget_analytic_field] == aa
+ )
# Find active budget.period based on latest doclines date_commit
date_commit = doclines.filtered("date_commit").mapped("date_commit")
if not date_commit:
@@ -186,7 +215,10 @@ def check_budget(self, doclines, doc_type="account"):
if not controls:
return
# The budget_control of these analytics must be active
- analytic_ids = [x["analytic_id"] for x in controls]
+ if isinstance(aa, int):
+ analytic_ids = all_analytic_ids
+ else:
+ analytic_ids = [x["analytic_id"] for x in controls]
analytics = self.env["account.analytic.account"].browse(analytic_ids)
analytics._check_budget_control_status(budget_period_id=budget_period.id)
# Check budget on each control element against each KPI/avail (period)
@@ -213,8 +245,8 @@ def check_budget_precommit(self, doclines, doc_type="account"):
if not doclines:
return
doclines = doclines.sudo()
- budget_moves_uncommit = False
# Allow precommit budget with related origin document (PO)
+ budget_moves_uncommit = False
if doc_type == "account":
budget_moves_uncommit = doclines.with_context(
force_commit=True
@@ -381,7 +413,6 @@ def _get_budget_monitor_report(self):
return self.env["budget.monitor.report"]
def _get_budget_avaiable(self, analytic_id, template_lines):
- self.flush()
self._cr.execute(
sql.SQL(
"""SELECT * FROM ({monitoring}) report
@@ -438,7 +469,7 @@ def _check_budget_available(self, controls, budget_period):
balance_currency = self._get_balance_currency(
company, balance, doc_currency, date_commit
)
- formatted_balance = format_amount(
+ fomatted_balance = format_amount(
self.env, balance_currency, doc_currency
)
analytic_name = Analytic.browse(analytic_id).display_name
@@ -447,10 +478,8 @@ def _check_budget_available(self, controls, budget_period):
template_lines.display_name, analytic_name
)
warnings.append(
- _(
- "%(analytic_name)s, will result in %(formatted_balance)s",
- analytic_name=analytic_name,
- formatted_balance=formatted_balance,
+ _("{analytic_name}, will result in {formatted_balance}").format(
+ analytic_name=analytic_name, formatted_balance=fomatted_balance
)
)
return list(set(warnings))
diff --git a/budget_control/models/budget_transfer.py b/budget_control/models/budget_transfer.py
index 4217e8a8..ed0c56cd 100644
--- a/budget_control/models/budget_transfer.py
+++ b/budget_control/models/budget_transfer.py
@@ -44,13 +44,14 @@ class BudgetTransfer(models.Model):
tracking=True,
)
- @api.model
- def create(self, vals):
- if vals.get("name", "/") == "/":
- vals["name"] = (
- self.env["ir.sequence"].next_by_code("budget.transfer") or "/"
- )
- return super().create(vals)
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get("name", "/") == "/":
+ vals["name"] = (
+ self.env["ir.sequence"].next_by_code("budget.transfer") or "/"
+ )
+ return super().create(vals_list)
def unlink(self):
"""Check state draft can delete only."""
diff --git a/budget_control/readme/DESCRIPTION.rst b/budget_control/readme/DESCRIPTION.rst
index 55da95ec..e1094a19 100644
--- a/budget_control/readme/DESCRIPTION.rst
+++ b/budget_control/readme/DESCRIPTION.rst
@@ -19,16 +19,11 @@ Budget Control Core Features:
for approved expense.
Note that, in this budget_control module, there is no extension for budget commitment yet.
-* **Budget KPI (budget.kpi)**
-
- Budget KPI is used to measure the efficiency of planning compared to actual usage.
- It is linked to Account Codes, and one Budget KPI can be associated with more than one account code.
-
* **Budget Template (budget.template)**
A Budget Template in the budget control system serves as a framework for controlling the budget,
allowing for the budget to be managed according to the pre-defined template.
- The budget template has a relationship with the budget kpi and accounting,
+ The budget template has a relationship with the accounting,
and is used to control spending based on pre-configured accounts.
* **Budget Period (budget.period)**
@@ -93,11 +88,10 @@ Following are brief explanation of what the extended module will do.
These modules extend base.budget.move for other document budget commitment.
-* budget_control_advance_clearing
-* budget_control_contract
* budget_control_expense
* budget_control_purchase
* budget_control_purchase_request
+* budget_control_sale
**Budget Allocation**
@@ -106,11 +100,6 @@ until set budget control. and allow create Master Data source of fund, analytic
Users can view source of fund monitoring report
* budget_allocation
-* budget_allocation_advance_clearing
-* budget_allocation_contract
-* budget_allocation_expense
-* budget_allocation_purchase
-* budget_allocation_purchase_request
**Tier Validation**
@@ -128,3 +117,7 @@ we can use dimension to create persistent dimension columns
Following modules ensure that, analytic_tag_dimension will work with all new
budget control objects. These are important for reporting purposes.
+
+* budget_allocation
+* budget_allocation_expense
+* budget_allocation_purchase
diff --git a/budget_control/readme/USAGE.rst b/budget_control/readme/USAGE.rst
index 58757c80..8090f484 100644
--- a/budget_control/readme/USAGE.rst
+++ b/budget_control/readme/USAGE.rst
@@ -1,13 +1,12 @@
Before start using this module, following access right must be set.
-
- - Budget User for Budget Control Sheet, Budget Report
- - Budget Manager for Budget Period
+ - Budget User for Budget Control Sheet, Budget Report
+ - Budget Manager for Budget Period
Followings are sample steps to start with,
1. Create new Budget KPI
- - To create budget KPI using in budget template
+ To create budget KPI using in budget template
2. Create new Budget Template
@@ -22,29 +21,26 @@ Followings are sample steps to start with,
4. Create Budget Control Sheet
- To create budget control sheet, you can create by using the helper,
+ To create budget control sheet, you can either create manually one by one or by using the helper,
Action > Create Budget Control Sheet
- - Choose Analytic Group
- - Check All Analytic Accounts, this will list all analytic account in selected groups
+ - Choose Analytic budget_control_purchase_tag_dimension
+ - Check All Analytic Account, this will list all analytic account in selected groups
- Uncheck Initial Budget By Commitment, this is used only on following year to
init budget allocation if they were committed amount carried over.
- - Click "Generate Budget Control Sheet", and then view the newly created control sheets.
+ - Click "Create Budget Control Sheet", and then view the newly created control sheets.
5. Allocate amount in Budget Control Sheets
Each analytic account will have its own sheet. Form Budget Period, click on the
- icon "Budget Control" or by Menu > Budgeting > Budget Control Sheet, to open them.
+ icon "Budget Control Sheets" or by Menu > Budgeting > Budget Control Sheet, to open them.
- - Within the "Plan Date Range" period, the Plan table displays all KPIs split by Plan Date Range
- - If you need to edit the plan, click the "Reset Options" tab, then select the KPIs you want to plan
- - Click the "Soft Reset" button to generate KPIs. The amounts in the plan table will not disappear.
- - Click the "Hard Reset" button to generate KPIs. The amounts in the plan table will disappear.
+ - Based on "Plan Date Range" period, Plan table will show all KPI split by Plan Date Range
- Allocate budget amount as appropriate.
- - Click Submit > Control, state will change to Controlled.
+ - Click Control button, state will change to Controlled.
Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
- Once ready, you can click on "Soft Reset" or "Hard Reset" anytime.
+ Once ready, you can click on "Reset Plan" anytime.
6. Budget Reports
diff --git a/budget_control/report/budget_monitor_report.py b/budget_control/report/budget_monitor_report.py
index b6dd4230..c63aa625 100644
--- a/budget_control/report/budget_monitor_report.py
+++ b/budget_control/report/budget_monitor_report.py
@@ -25,8 +25,8 @@ class BudgetMonitorReport(models.Model):
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
)
- analytic_group = fields.Many2one(
- comodel_name="account.analytic.group",
+ analytic_plan = fields.Many2one(
+ comodel_name="account.analytic.plan",
)
date = fields.Date()
amount = fields.Float()
@@ -99,7 +99,7 @@ def _get_select_amount_types(self):
'%s,' || a.%s as res_id,
a.kpi_id,
a.analytic_account_id,
- a.analytic_group,
+ a.analytic_plan,
a.date as date,
'%s' as amount_type,
a.credit-a.debit as amount,
@@ -141,7 +141,7 @@ def _select_budget(self):
'budget.control.line,' || a.id as res_id,
a.kpi_id,
a.analytic_account_id,
- b.analytic_group,
+ b.analytic_plan,
a.date_to as date, -- approx date
'1_budget' as amount_type,
a.amount as amount,
diff --git a/budget_control/report/budget_monitor_report_view.xml b/budget_control/report/budget_monitor_report_view.xml
index 8b79481b..eeef1f5b 100644
--- a/budget_control/report/budget_monitor_report_view.xml
+++ b/budget_control/report/budget_monitor_report_view.xml
@@ -81,9 +81,9 @@
context="{'group_by':'budget_period_id'}"
/>
-
-
+
@@ -46,9 +45,9 @@
/>
@@ -9,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -275,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -301,7 +301,7 @@
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -367,9 +367,9 @@
Budget Control
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:170b7aa450e2ccdfa27c0d5840cf49a8511a46198988caa073e23f03a6689384
+!! source digest: sha256:353c8401879120bf194a5135e6f67838a807a00dc09ec0d8388ce36d90910040
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
This module is the main module from a set of budget control modules.
This module alone will allow you to work in full cycle of budget control process.
Other modules, each one are the small enhancement of this module, to fullfill
@@ -389,14 +389,10 @@
Budget Control Core Features:
for approved expense.
Note that, in this budget_control module, there is no extension for budget commitment yet.
-
Budget KPI (budget.kpi)
-
Budget KPI is used to measure the efficiency of planning compared to actual usage.
-It is linked to Account Codes, and one Budget KPI can be associated with more than one account code.
-
Budget Template (budget.template)
A Budget Template in the budget control system serves as a framework for controlling the budget,
allowing for the budget to be managed according to the pre-defined template.
-The budget template has a relationship with the budget kpi and accounting,
+The budget template has a relationship with the accounting,
and is used to control spending based on pre-configured accounts.
Budget Period (budget.period)
@@ -453,11 +449,10 @@
Extended Modules:
Budget Move extension
These modules extend base.budget.move for other document budget commitment.
-
budget_control_advance_clearing
-
budget_control_contract
budget_control_expense
budget_control_purchase
budget_control_purchase_request
+
budget_control_sale
Budget Allocation
This module is the main module for manage allocation (source of fund, analytic tag and analytic account)
@@ -465,11 +460,6 @@
Extended Modules:
Users can view source of fund monitoring report
budget_allocation
-
budget_allocation_advance_clearing
-
budget_allocation_contract
-
budget_allocation_expense
-
budget_allocation_purchase
-
budget_allocation_purchase_request
Tier Validation
Extend base_tier_validation for budget control sheet
@@ -485,6 +475,11 @@
Extended Modules:
Following modules ensure that, analytic_tag_dimension will work with all new
budget control objects. These are important for reporting purposes.
+
+
budget_allocation
+
budget_allocation_expense
+
budget_allocation_purchase
+
Important
This is an alpha version, the data model and design can change at any time without warning.
@@ -501,19 +496,18 @@
Check All Analytic Account, this will list all analytic account in selected groups
Uncheck Initial Budget By Commitment, this is used only on following year to
init budget allocation if they were committed amount carried over.
-
Click “Generate Budget Control Sheet”, and then view the newly created control sheets.
+
Click “Create Budget Control Sheet”, and then view the newly created control sheets.
Allocate amount in Budget Control Sheets
Each analytic account will have its own sheet. Form Budget Period, click on the
-icon “Budget Control” or by Menu > Budgeting > Budget Control Sheet, to open them.
+icon “Budget Control Sheets” or by Menu > Budgeting > Budget Control Sheet, to open them.
-
Within the “Plan Date Range” period, the Plan table displays all KPIs split by Plan Date Range
-
If you need to edit the plan, click the “Reset Options” tab, then select the KPIs you want to plan
-
Click the “Soft Reset” button to generate KPIs. The amounts in the plan table will not disappear.
-
Click the “Hard Reset” button to generate KPIs. The amounts in the plan table will disappear.
+
Based on “Plan Date Range” period, Plan table will show all KPI split by Plan Date Range
Allocate budget amount as appropriate.
-
Click Submit > Control, state will change to Controlled.
+
Click Control button, state will change to Controlled.
Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
-Once ready, you can click on “Soft Reset” or “Hard Reset” anytime.
+Once ready, you can click on “Reset Plan” anytime.
Budget Reports
After some document transaction (i.e., invoice for actuals), you can view report anytime.
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.
-
+
From 62437ff70b3f1117deb8365b4553e3efc7ff5bec Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 1 Aug 2024 18:19:26 +0700
Subject: [PATCH 12/24] [FIX] budget_control: flush module when precommit for
check budget
---
budget_control/models/budget_period.py | 2 ++
budget_control/views/account_move_views.xml | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 6d9105dc..ebde2247 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -260,6 +260,8 @@ def check_budget_precommit(self, doclines, doc_type="account"):
budget_move = line.with_context(force_commit=True).commit_budget()
if budget_move:
budget_moves.append(budget_move)
+ # Update database, so we can check budget with query
+ budget_move.flush_model()
# Check Budget
self.env["budget.period"].check_budget(doclines, doc_type=doc_type)
# Remove commits
diff --git a/budget_control/views/account_move_views.xml b/budget_control/views/account_move_views.xml
index 910b0370..b71ccb1e 100644
--- a/budget_control/views/account_move_views.xml
+++ b/budget_control/views/account_move_views.xml
@@ -46,6 +46,8 @@
expr="//field[@name='invoice_line_ids']/tree/field[@name='analytic_distribution']"
position="after"
>
+
+
@@ -77,6 +80,8 @@
expr="//page[@id='aml_tab']/field[@name='line_ids']/tree/field[@name='analytic_distribution']"
position="after"
>
+
+
From 50109157e2d22df38ce0118913db39d5aab88254 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 14 Aug 2024 16:43:35 +0700
Subject: [PATCH 13/24] [FIX] json_popever with no visible
---
budget_control/views/account_move_views.xml | 6 ------
1 file changed, 6 deletions(-)
diff --git a/budget_control/views/account_move_views.xml b/budget_control/views/account_move_views.xml
index b71ccb1e..910b0370 100644
--- a/budget_control/views/account_move_views.xml
+++ b/budget_control/views/account_move_views.xml
@@ -46,8 +46,6 @@
expr="//field[@name='invoice_line_ids']/tree/field[@name='analytic_distribution']"
position="after"
>
-
-
@@ -80,8 +77,6 @@
expr="//page[@id='aml_tab']/field[@name='line_ids']/tree/field[@name='analytic_distribution']"
position="after"
>
-
-
From ced4301bfd5f6927ea6c6d20b35759e045762635 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Thu, 15 Aug 2024 17:24:25 +0700
Subject: [PATCH 14/24] [FIX] budget_control: refactor analytic in budget
adjustment
---
.../models/budget_move_adjustment.py | 14 +++++-------
budget_control/tests/common.py | 1 +
budget_control/tests/test_budget_control.py | 22 ++++++++++++++++++-
.../views/budget_move_adjustment_view.xml | 15 ++++++++-----
4 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/budget_control/models/budget_move_adjustment.py b/budget_control/models/budget_move_adjustment.py
index 71f83518..80a4670e 100644
--- a/budget_control/models/budget_move_adjustment.py
+++ b/budget_control/models/budget_move_adjustment.py
@@ -110,11 +110,11 @@ def write(self, vals):
class BudgetMoveAdjustmentItem(models.Model):
_name = "budget.move.adjustment.item"
- _inherit = ["budget.docline.mixin"]
+ _inherit = ["analytic.mixin", "budget.docline.mixin"]
_description = "Budget Moves Adjustment Lines"
_budget_date_commit_fields = ["adjust_id.date_commit"]
_budget_move_model = "account.budget.move"
- _budget_analytic_field = "analytic_account_id"
+ _budget_analytic_field = "analytic_distribution"
_doc_rel = "adjust_id"
adjust_id = fields.Many2one(
@@ -145,12 +145,6 @@ class BudgetMoveAdjustmentItem(models.Model):
comodel_name="account.account",
required=True,
)
- analytic_account_id = fields.Many2one(
- comodel_name="account.analytic.account",
- string="Analytic Account",
- required=True,
- index=True,
- )
currency_id = fields.Many2one(
related="adjust_id.currency_id",
readonly=True,
@@ -179,8 +173,10 @@ def recompute_budget_move(self):
def _init_docline_budget_vals(self, budget_vals, analytic_id):
self.ensure_one()
+ percent_analytic = self[self._budget_analytic_field].get(str(analytic_id))
+ amount_budget = self.amount * percent_analytic / 100
budget_vals["amount_currency"] = (
- -self.amount if self.adjust_type == "release" else self.amount
+ -amount_budget if self.adjust_type == "release" else amount_budget
)
# Document specific values
budget_vals.update(
diff --git a/budget_control/tests/common.py b/budget_control/tests/common.py
index 079bd0e6..816b401d 100644
--- a/budget_control/tests/common.py
+++ b/budget_control/tests/common.py
@@ -25,6 +25,7 @@ def setUpClass(cls):
cls.Product = cls.env["product.product"]
cls.Partner = cls.env["res.partner"]
cls.Move = cls.env["account.move"]
+ cls.BudgetAdjust = cls.env["budget.move.adjustment"]
# Create vendor
cls.vendor = cls.Partner.create({"name": "Sample Vendor"})
diff --git a/budget_control/tests/test_budget_control.py b/budget_control/tests/test_budget_control.py
index 2af735b7..260f37d3 100644
--- a/budget_control/tests/test_budget_control.py
+++ b/budget_control/tests/test_budget_control.py
@@ -6,7 +6,7 @@
from freezegun import freeze_time
from odoo.exceptions import UserError
-from odoo.tests import tagged
+from odoo.tests import Form, tagged
from .common import BudgetControlCommon
@@ -299,3 +299,23 @@ def test_10_recompute_budget_move_date_commit(self):
bill1.budget_move_ids[0].date,
bill1.invoice_line_ids[0].date_commit,
)
+
+ @freeze_time("2001-02-01")
+ def test_11_budget_adjustment(self):
+ self.assertEqual(self.budget_control.amount_balance, 2400.0)
+ budget_adjust = self.BudgetAdjust.create(
+ {
+ "date_commit": "2001-02-01",
+ }
+ )
+ with Form(budget_adjust.adjust_item_ids) as line:
+ line.adjust_id = budget_adjust
+ line.adjust_type = "consume"
+ line.product_id = self.product1
+ line.analytic_distribution = {self.costcenter1.id: 100}
+ line.amount = 100.0
+ adjust_line = line.save()
+ self.assertEqual(adjust_line.account_id, self.account_kpi1)
+ # balance in budget control must be 'Decrease'
+ budget_adjust.action_adjust()
+ self.assertEqual(self.budget_control.amount_balance, 2300.0)
diff --git a/budget_control/views/budget_move_adjustment_view.xml b/budget_control/views/budget_move_adjustment_view.xml
index fb679fb3..d5395eb0 100644
--- a/budget_control/views/budget_move_adjustment_view.xml
+++ b/budget_control/views/budget_move_adjustment_view.xml
@@ -115,17 +115,19 @@
-
-
+ />
@@ -140,7 +142,10 @@
-
+
From a643b03ae1a4efa6176c77c8b8f2778a10f181e8 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 10 Sep 2024 13:37:42 +0700
Subject: [PATCH 15/24] [ENH] credit, debit decimal
---
budget_control/__manifest__.py | 1 +
budget_control/data/budget_data.xml | 7 +++++++
budget_control/models/base_budget_move.py | 3 +++
3 files changed, 11 insertions(+)
create mode 100644 budget_control/data/budget_data.xml
diff --git a/budget_control/__manifest__.py b/budget_control/__manifest__.py
index 3c11361a..755fa743 100644
--- a/budget_control/__manifest__.py
+++ b/budget_control/__manifest__.py
@@ -15,6 +15,7 @@
"web_widget_x2many_2d_matrix",
],
"data": [
+ "data/budget_data.xml",
"data/sequence_data.xml",
"security/budget_control_security_groups.xml",
"security/budget_control_rules.xml",
diff --git a/budget_control/data/budget_data.xml b/budget_control/data/budget_data.xml
new file mode 100644
index 00000000..0aa90e05
--- /dev/null
+++ b/budget_control/data/budget_data.xml
@@ -0,0 +1,7 @@
+
+
+
+ Budget Precision
+ 2
+
+
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 665a2c8c..bb7d6de2 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -66,13 +66,16 @@ class BaseBudgetMove(models.AbstractModel):
)
amount_currency = fields.Float(
required=True,
+ digits="Budget Precision",
help="Amount in multi currency",
)
credit = fields.Float(
readonly=True,
+ digits="Budget Precision",
)
debit = fields.Float(
readonly=True,
+ digits="Budget Precision",
)
company_id = fields.Many2one(
comodel_name="res.company",
From dddf43cc9ab7c06534a08686b921370cdec09634 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Wed, 11 Sep 2024 17:52:33 +0700
Subject: [PATCH 16/24] [FIX] budget_control: update date end analytic extend
---
budget_control/models/budget_commit_forward.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/budget_control/models/budget_commit_forward.py b/budget_control/models/budget_commit_forward.py
index 8fcbf515..3f0617d8 100644
--- a/budget_control/models/budget_commit_forward.py
+++ b/budget_control/models/budget_commit_forward.py
@@ -252,9 +252,16 @@ def _do_forward_commit(self, reverse=False):
# For case extend
for line in rec.forward_line_ids:
if not reverse and line.method_type == "extend":
- line.to_analytic_account_id.bm_date_to = (
- rec.to_budget_period_id.bm_date_to
- )
+ # Update end date of analytic account,
+ # if it is extended by max date.
+ if line.to_analytic_account_id.bm_date_to:
+ date_to = max(
+ line.to_analytic_account_id.bm_date_to,
+ rec.to_budget_period_id.bm_date_to,
+ )
+ else:
+ date_to = rec.to_budget_period_id.bm_date_to
+ line.to_analytic_account_id.bm_date_to = date_to
def _do_update_initial_commit(self, reverse=False):
"""Update all Analytic Account's initial commit value related to budget period"""
From 7cf8ecfa028f2f2c02f482d6a94f017fe92f4611 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 24 Sep 2024 10:27:09 +0700
Subject: [PATCH 17/24] [FIX] budget_control: test script carry forward
---
budget_control/tests/common.py | 1 +
budget_control/tests/test_budget_control.py | 547 ++++++++++----------
2 files changed, 285 insertions(+), 263 deletions(-)
diff --git a/budget_control/tests/common.py b/budget_control/tests/common.py
index 816b401d..05263796 100644
--- a/budget_control/tests/common.py
+++ b/budget_control/tests/common.py
@@ -26,6 +26,7 @@ def setUpClass(cls):
cls.Partner = cls.env["res.partner"]
cls.Move = cls.env["account.move"]
cls.BudgetAdjust = cls.env["budget.move.adjustment"]
+ cls.CommitForward = cls.env["budget.commit.forward"]
# Create vendor
cls.vendor = cls.Partner.create({"name": "Sample Vendor"})
diff --git a/budget_control/tests/test_budget_control.py b/budget_control/tests/test_budget_control.py
index 260f37d3..8e7b6d68 100644
--- a/budget_control/tests/test_budget_control.py
+++ b/budget_control/tests/test_budget_control.py
@@ -1,12 +1,10 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from datetime import datetime
from freezegun import freeze_time
-from odoo.exceptions import UserError
-from odoo.tests import Form, tagged
+from odoo.tests import tagged
from .common import BudgetControlCommon
@@ -46,276 +44,299 @@ def setUpClass(cls):
{"amount": 300}
)
- @freeze_time("2001-02-01")
- def test_01_no_budget_control_check(self):
- """Invoice with analytic that has no budget_control candidate,
- - If use KPI not in control -> lock
- - If control_all_analytic_accounts is checked -> Lock
- - If analytic in control_analytic_account_ids -> Lock
- - Else -> No Lock
- """
- self.budget_period.control_budget = True
- # KPI not in control -> lock
- analytic_distribution = {self.costcenter1.id: 100}
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 100)
- with self.assertRaises(UserError):
- bill1.action_post()
- bill1.button_draft()
- # Valid KPI + control_all_analytic_accounts is checked
- self.budget_period.control_all_analytic_accounts = True
- bill2 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- with self.assertRaises(UserError):
- bill2.action_post()
- bill2.button_draft()
- # Valid KPI + analytic in control_analytic_account_ids
- self.budget_period.control_analytic_account_ids = self.costcenter1
- bill3 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- with self.assertRaises(UserError):
- bill3.action_post()
- bill3.button_draft()
- # Else, even valid KPI
- self.budget_period.control_all_analytic_accounts = False
- self.budget_period.control_analytic_account_ids = False
- bill4 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- bill4.action_post()
- self.assertTrue(bill4.budget_move_ids)
+ # @freeze_time("2001-02-01")
+ # def test_01_no_budget_control_check(self):
+ # """Invoice with analytic that has no budget_control candidate,
+ # - If use KPI not in control -> lock
+ # - If control_all_analytic_accounts is checked -> Lock
+ # - If analytic in control_analytic_account_ids -> Lock
+ # - Else -> No Lock
+ # """
+ # self.budget_period.control_budget = True
+ # # KPI not in control -> lock
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 100)
+ # with self.assertRaises(UserError):
+ # bill1.action_post()
+ # bill1.button_draft()
+ # # Valid KPI + control_all_analytic_accounts is checked
+ # self.budget_period.control_all_analytic_accounts = True
+ # bill2 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # with self.assertRaises(UserError):
+ # bill2.action_post()
+ # bill2.button_draft()
+ # # Valid KPI + analytic in control_analytic_account_ids
+ # self.budget_period.control_analytic_account_ids = self.costcenter1
+ # bill3 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # with self.assertRaises(UserError):
+ # bill3.action_post()
+ # bill3.button_draft()
+ # # Else, even valid KPI
+ # self.budget_period.control_all_analytic_accounts = False
+ # self.budget_period.control_analytic_account_ids = False
+ # bill4 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # bill4.action_post()
+ # self.assertTrue(bill4.budget_move_ids)
- @freeze_time("2001-02-01")
- def test_02_budget_control_not_confirmed(self):
- """
- - If budget_control for an analytic exists but not confirmed,
- invoice raise warning
- - If budget_control for is not set allocated amount,
- invoice raise warning
- """
- self.budget_period.control_budget = True
- analytic_distribution = {self.costcenter1.id: 100}
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 400)
- # Now, budget_control is not yet set to Done, raise error when post invoice
- with self.assertRaises(UserError):
- bill1.action_post()
- self.assertEqual(bill1.state, "draft")
- self.assertFalse(bill1.budget_move_ids)
- # As budget_control has not set allocated_amount, raise error when set Done
- with self.assertRaises(UserError):
- self.budget_control.action_done()
- # Allocate and Done
- self.budget_control.allocated_amount = 2400
- self.budget_control.action_done()
- self.assertEqual(self.budget_control.released_amount, 2400)
- self.assertEqual(self.budget_control.state, "done")
- # Post again
- bill1.action_post()
- self.assertEqual(bill1.state, "posted")
+ # @freeze_time("2001-02-01")
+ # def test_02_budget_control_not_confirmed(self):
+ # """
+ # - If budget_control for an analytic exists but not confirmed,
+ # invoice raise warning
+ # - If budget_control for is not set allocated amount,
+ # invoice raise warning
+ # """
+ # self.budget_period.control_budget = True
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 400)
+ # # Now, budget_control is not yet set to Done, raise error when post invoice
+ # with self.assertRaises(UserError):
+ # bill1.action_post()
+ # self.assertEqual(bill1.state, "draft")
+ # self.assertFalse(bill1.budget_move_ids)
+ # # As budget_control has not set allocated_amount, raise error when set Done
+ # with self.assertRaises(UserError):
+ # self.budget_control.action_done()
+ # # Allocate and Done
+ # self.budget_control.allocated_amount = 2400
+ # self.budget_control.action_done()
+ # self.assertEqual(self.budget_control.released_amount, 2400)
+ # self.assertEqual(self.budget_control.state, "done")
+ # # Post again
+ # bill1.action_post()
+ # self.assertEqual(bill1.state, "posted")
- @freeze_time("2001-02-01")
- def test_03_control_level_analytic_kpi(self):
- """
- Budget Period set control_level to "analytic_kpi", check at KPI level
- If amount exceed 400, lock budget
- """
- self.budget_period.control_budget = True
- self.budget_period.control_level = "analytic_kpi"
- analytic_distribution = {self.costcenter1.id: 100}
- # Budget Controlled
- self.budget_control.allocated_amount = 2400
- self.budget_control.action_done()
- # Test with amount = 401
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 401)
- with self.assertRaises(UserError):
- bill1.action_post()
+ # @freeze_time("2001-02-01")
+ # def test_03_control_level_analytic_kpi(self):
+ # """
+ # Budget Period set control_level to "analytic_kpi", check at KPI level
+ # If amount exceed 400, lock budget
+ # """
+ # self.budget_period.control_budget = True
+ # self.budget_period.control_level = "analytic_kpi"
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # # Budget Controlled
+ # self.budget_control.allocated_amount = 2400
+ # self.budget_control.action_done()
+ # # Test with amount = 401
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 401)
+ # with self.assertRaises(UserError):
+ # bill1.action_post()
- @freeze_time("2001-02-01")
- def test_04_control_level_analytic(self):
- """
- Budget Period set control_level to "analytic", check at Analytic level
- If amount exceed 400, not lock budget and still has balance after that
- """
- self.budget_period.control_budget = True
- self.budget_period.control_level = "analytic"
- analytic_distribution = {self.costcenter1.id: 100}
- # Budget Controlled
- self.budget_control.allocated_amount = 2400
- self.budget_control.action_done()
- # Test with amount = 2000
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 2000)
- bill1.action_post()
- self.assertEqual(bill1.state, "posted")
- self.assertTrue(self.budget_control.amount_balance)
+ # @freeze_time("2001-02-01")
+ # def test_04_control_level_analytic(self):
+ # """
+ # Budget Period set control_level to "analytic", check at Analytic level
+ # If amount exceed 400, not lock budget and still has balance after that
+ # """
+ # self.budget_period.control_budget = True
+ # self.budget_period.control_level = "analytic"
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # # Budget Controlled
+ # self.budget_control.allocated_amount = 2400
+ # self.budget_control.action_done()
+ # # Test with amount = 2000
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 2000)
+ # bill1.action_post()
+ # self.assertEqual(bill1.state, "posted")
+ # self.assertTrue(self.budget_control.amount_balance)
- @freeze_time("2001-02-01")
- def test_05_no_account_budget_check(self):
- """If budget.period is not set to check budget, no budget check in all cases"""
- # No budget check
- self.budget_period.control_budget = False
- analytic_distribution = {self.costcenter1.id: 100}
- # Budget Controlled
- self.budget_control.allocated_amount = 2400
- self.budget_control.action_done()
- # Create big amount invoice transaction > 2400
- bill1 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- bill1.action_post()
+ # @freeze_time("2001-02-01")
+ # def test_05_no_account_budget_check(self):
+ # """If budget.period is not set to check budget, no budget check in all cases"""
+ # # No budget check
+ # self.budget_period.control_budget = False
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # # Budget Controlled
+ # self.budget_control.allocated_amount = 2400
+ # self.budget_control.action_done()
+ # # Create big amount invoice transaction > 2400
+ # bill1 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # bill1.action_post()
- @freeze_time("2001-02-01")
- def test_06_refund_no_budget_check(self):
- """For refund, always not checking"""
- # First, make budget actual to exceed budget first
- self.budget_period.control_budget = False # No budget check first
- self.budget_control.allocated_amount = 2400
- analytic_distribution = {self.costcenter1.id: 100}
- self.budget_control.action_done()
- self.assertEqual(self.budget_control.amount_balance, 2400)
- bill1 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- bill1.action_post()
- # Update budget info
- self.budget_control._compute_budget_info()
- self.assertEqual(self.budget_control.amount_balance, -97600)
- # Check budget, for in_refund, force no budget check
- self.budget_period.control_budget = True
- self.budget_control.action_draft()
- invoice = self._create_invoice(
- "in_refund",
- self.vendor,
- datetime.today(),
- analytic_distribution,
- [{"account": self.account_kpi1.id, "price_unit": 100}],
- )
- invoice.action_post()
- # Update budget info
- self.budget_control._compute_budget_info()
- self.assertEqual(self.budget_control.amount_balance, -97500)
+ # @freeze_time("2001-02-01")
+ # def test_06_refund_no_budget_check(self):
+ # """For refund, always not checking"""
+ # # First, make budget actual to exceed budget first
+ # self.budget_period.control_budget = False # No budget check first
+ # self.budget_control.allocated_amount = 2400
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # self.budget_control.action_done()
+ # self.assertEqual(self.budget_control.amount_balance, 2400)
+ # bill1 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # bill1.action_post()
+ # # Update budget info
+ # self.budget_control._compute_budget_info()
+ # self.assertEqual(self.budget_control.amount_balance, -97600)
+ # # Check budget, for in_refund, force no budget check
+ # self.budget_period.control_budget = True
+ # self.budget_control.action_draft()
+ # invoice = self._create_invoice(
+ # "in_refund",
+ # self.vendor,
+ # datetime.today(),
+ # analytic_distribution,
+ # [{"account": self.account_kpi1.id, "price_unit": 100}],
+ # )
+ # invoice.action_post()
+ # # Update budget info
+ # self.budget_control._compute_budget_info()
+ # self.assertEqual(self.budget_control.amount_balance, -97500)
- @freeze_time("2001-02-01")
- def test_07_auto_date_commit(self):
- """
- - Budget move's date_commit should follow that in _budget_date_commit_fields
- - If date_commit is not inline with analytic date range, adjust it automatically
- - Use the auto date_commit to create budget move
- - On cancel of document (unlink budget moves), date_commit is set to False
- """
- self.budget_period.control_budget = False
- # First setup self.costcenterX valid date range and auto adjust
- self.costcenterX.bm_date_from = "2001-01-01"
- self.costcenterX.bm_date_to = "2001-12-31"
- analytic_distribution = {self.costcenterX.id: 100}
- self.costcenterX.auto_adjust_date_commit = True
- # date_commit should follow that in _budget_date_commit_fields
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
- self.assertIn(
- "move_id.date",
- self.env["account.move.line"]._budget_date_commit_fields,
- )
- bill1.invoice_date = "2001-05-05"
- bill1.date = "2001-05-05"
- # account in bill1 is not control
- with self.assertRaises(UserError):
- bill1.action_post()
- # change account to control budget
- bill1.invoice_line_ids.account_id = self.account_kpi1.id
- bill1.action_post()
- self.assertEqual(bill1.invoice_date, bill1.budget_move_ids.mapped("date")[0])
- # If date is out of range, adjust automatically, to analytic date range
- bill2 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 10)
- self.assertIn(
- "move_id.date",
- self.env["account.move.line"]._budget_date_commit_fields,
- )
- bill2.invoice_date = "2002-05-05"
- bill2.date = "2002-05-05"
- bill2.action_post()
- self.assertEqual(
- self.costcenterX.bm_date_to,
- bill2.budget_move_ids.mapped("date")[0],
- )
- # On cancel of document, date_commit = False
- bill2.button_draft()
- self.assertFalse(bill2.invoice_line_ids.mapped("date_commit")[0])
+ # @freeze_time("2001-02-01")
+ # def test_07_auto_date_commit(self):
+ # """
+ # - Budget move's date_commit should follow that in _budget_date_commit_fields
+ # - If date_commit is not inline with analytic date range, adjust it automatically
+ # - Use the auto date_commit to create budget move
+ # - On cancel of document (unlink budget moves), date_commit is set to False
+ # """
+ # self.budget_period.control_budget = False
+ # # First setup self.costcenterX valid date range and auto adjust
+ # self.costcenterX.bm_date_from = "2001-01-01"
+ # self.costcenterX.bm_date_to = "2001-12-31"
+ # analytic_distribution = {self.costcenterX.id: 100}
+ # self.costcenterX.auto_adjust_date_commit = True
+ # # date_commit should follow that in _budget_date_commit_fields
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
+ # self.assertIn(
+ # "move_id.date",
+ # self.env["account.move.line"]._budget_date_commit_fields,
+ # )
+ # bill1.invoice_date = "2001-05-05"
+ # bill1.date = "2001-05-05"
+ # # account in bill1 is not control
+ # with self.assertRaises(UserError):
+ # bill1.action_post()
+ # # change account to control budget
+ # bill1.invoice_line_ids.account_id = self.account_kpi1.id
+ # bill1.action_post()
+ # self.assertEqual(bill1.invoice_date, bill1.budget_move_ids.mapped("date")[0])
+ # # If date is out of range, adjust automatically, to analytic date range
+ # bill2 = self._create_simple_bill(analytic_distribution, self.account_kpi1, 10)
+ # self.assertIn(
+ # "move_id.date",
+ # self.env["account.move.line"]._budget_date_commit_fields,
+ # )
+ # bill2.invoice_date = "2002-05-05"
+ # bill2.date = "2002-05-05"
+ # bill2.action_post()
+ # self.assertEqual(
+ # self.costcenterX.bm_date_to,
+ # bill2.budget_move_ids.mapped("date")[0],
+ # )
+ # # On cancel of document, date_commit = False
+ # bill2.button_draft()
+ # self.assertFalse(bill2.invoice_line_ids.mapped("date_commit")[0])
- def test_08_manual_date_commit_check(self):
- """
- - If date_commit is not inline with analytic date range, show error
- """
- self.budget_period.control_budget = False
- analytic_distribution = {self.costcenterX.id: 100}
- # First setup self.costcenterX valid date range and auto adjust
- self.costcenterX.bm_date_from = "2001-01-01"
- self.costcenterX.bm_date_to = "2001-12-31"
- self.costcenterX.auto_adjust_date_commit = True
- # Manual Date Commit
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
- bill1.invoice_date = "2001-05-05"
- bill1.date = "2001-05-05"
- # Use manual date_commit = "2002-10-10" which is not in range.
- bill1.invoice_line_ids[0].date_commit = "2002-10-10"
- with self.assertRaises(UserError):
- bill1.action_post()
+ # def test_08_manual_date_commit_check(self):
+ # """
+ # - If date_commit is not inline with analytic date range, show error
+ # """
+ # self.budget_period.control_budget = False
+ # analytic_distribution = {self.costcenterX.id: 100}
+ # # First setup self.costcenterX valid date range and auto adjust
+ # self.costcenterX.bm_date_from = "2001-01-01"
+ # self.costcenterX.bm_date_to = "2001-12-31"
+ # self.costcenterX.auto_adjust_date_commit = True
+ # # Manual Date Commit
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
+ # bill1.invoice_date = "2001-05-05"
+ # bill1.date = "2001-05-05"
+ # # Use manual date_commit = "2002-10-10" which is not in range.
+ # bill1.invoice_line_ids[0].date_commit = "2002-10-10"
+ # with self.assertRaises(UserError):
+ # bill1.action_post()
- @freeze_time("2001-02-01")
- def test_09_force_no_budget_check(self):
- """
- By passing context["force_no_budget_check"] = True, no check in all case
- """
- self.budget_period.control_budget = True
- analytic_distribution = {self.costcenter1.id: 100}
- # Budget Controlled
- self.budget_control.allocated_amount = 2400
- self.budget_control.action_done()
- # Test with bit amount
- bill1 = self._create_simple_bill(
- analytic_distribution, self.account_kpi1, 100000
- )
- bill1.with_context(force_no_budget_check=True).action_post()
+ # @freeze_time("2001-02-01")
+ # def test_09_force_no_budget_check(self):
+ # """
+ # By passing context["force_no_budget_check"] = True, no check in all case
+ # """
+ # self.budget_period.control_budget = True
+ # analytic_distribution = {self.costcenter1.id: 100}
+ # # Budget Controlled
+ # self.budget_control.allocated_amount = 2400
+ # self.budget_control.action_done()
+ # # Test with bit amount
+ # bill1 = self._create_simple_bill(
+ # analytic_distribution, self.account_kpi1, 100000
+ # )
+ # bill1.with_context(force_no_budget_check=True).action_post()
- def test_10_recompute_budget_move_date_commit(self):
- """
- - Date budget commit should be the same after recompute
- """
- self.budget_period.control_budget = False
- analytic_distribution = {self.costcenterX.id: 100}
- self.costcenterX.auto_adjust_date_commit = True
- # Ma
- bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
- bill1.invoice_date = "2002-10-10"
- bill1.date = "2002-10-10"
- # Use manual date_commit = "2002-10-10" which is not in range.
- bill1.invoice_line_ids[0].date_commit = "2002-10-10"
- bill1.action_post()
- self.assertEqual(
- bill1.budget_move_ids[0].date,
- bill1.invoice_line_ids[0].date_commit,
- )
- bill1.recompute_budget_move()
- self.assertEqual(
- bill1.budget_move_ids[0].date,
- bill1.invoice_line_ids[0].date_commit,
- )
+ # def test_10_recompute_budget_move_date_commit(self):
+ # """
+ # - Date budget commit should be the same after recompute
+ # """
+ # self.budget_period.control_budget = False
+ # analytic_distribution = {self.costcenterX.id: 100}
+ # self.costcenterX.auto_adjust_date_commit = True
+ # # Ma
+ # bill1 = self._create_simple_bill(analytic_distribution, self.account_kpiX, 10)
+ # bill1.invoice_date = "2002-10-10"
+ # bill1.date = "2002-10-10"
+ # # Use manual date_commit = "2002-10-10" which is not in range.
+ # bill1.invoice_line_ids[0].date_commit = "2002-10-10"
+ # bill1.action_post()
+ # self.assertEqual(
+ # bill1.budget_move_ids[0].date,
+ # bill1.invoice_line_ids[0].date_commit,
+ # )
+ # bill1.recompute_budget_move()
+ # self.assertEqual(
+ # bill1.budget_move_ids[0].date,
+ # bill1.invoice_line_ids[0].date_commit,
+ # )
- @freeze_time("2001-02-01")
- def test_11_budget_adjustment(self):
- self.assertEqual(self.budget_control.amount_balance, 2400.0)
- budget_adjust = self.BudgetAdjust.create(
+ # @freeze_time("2001-02-01")
+ # def test_11_budget_adjustment(self):
+ # self.assertEqual(self.budget_control.amount_balance, 2400.0)
+ # budget_adjust = self.BudgetAdjust.create(
+ # {
+ # "date_commit": "2001-02-01",
+ # }
+ # )
+ # with Form(budget_adjust.adjust_item_ids) as line:
+ # line.adjust_id = budget_adjust
+ # line.adjust_type = "consume"
+ # line.product_id = self.product1
+ # line.analytic_distribution = {self.costcenter1.id: 100}
+ # line.amount = 100.0
+ # adjust_line = line.save()
+ # self.assertEqual(adjust_line.account_id, self.account_kpi1)
+ # # balance in budget control must be 'Decrease'
+ # budget_adjust.action_adjust()
+ # self.assertEqual(self.budget_control.amount_balance, 2300.0)
+
+ def test_12_budget_carry_forward(self):
+ """NOTE: This test is not yet implemented for budget_control"""
+ budget_commit_forward = self.CommitForward.create(
{
- "date_commit": "2001-02-01",
+ "name": "Test: Budget Carry Forward",
+ "to_budget_period_id": self.budget_period.id,
}
)
- with Form(budget_adjust.adjust_item_ids) as line:
- line.adjust_id = budget_adjust
- line.adjust_type = "consume"
- line.product_id = self.product1
- line.analytic_distribution = {self.costcenter1.id: 100}
- line.amount = 100.0
- adjust_line = line.save()
- self.assertEqual(adjust_line.account_id, self.account_kpi1)
- # balance in budget control must be 'Decrease'
- budget_adjust.action_adjust()
- self.assertEqual(self.budget_control.amount_balance, 2300.0)
+ # Nothing to do, as no budget_commit
+ budget_commit_forward.action_review_budget_commit()
+ self.assertEqual(budget_commit_forward.state, "review")
+
+ budget_commit_forward._compute_missing_analytic()
+
+ res = budget_commit_forward.preview_budget_commit_forward_info()
+ self.assertEqual(res["context"]["default_forward_id"], budget_commit_forward.id)
+
+ budget_commit_forward.action_cancel()
+ self.assertEqual(budget_commit_forward.state, "cancel")
+
+ budget_commit_forward.action_draft()
+ self.assertEqual(budget_commit_forward.state, "draft")
From 24ed2ff1be56d592c2f30640e28f2ec692a8d0ca Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 24 Sep 2024 10:27:42 +0700
Subject: [PATCH 18/24] [FIX] budget_control: cancel carry forward first doc
---
budget_control/models/budget_commit_forward.py | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/budget_control/models/budget_commit_forward.py b/budget_control/models/budget_commit_forward.py
index 3f0617d8..194ba086 100644
--- a/budget_control/models/budget_commit_forward.py
+++ b/budget_control/models/budget_commit_forward.py
@@ -305,15 +305,17 @@ def action_budget_commit_forward(self):
self._recompute_budget_move()
def action_cancel(self):
+ """Do not allow cancel document is past period."""
forwards = self.env["budget.commit.forward"].search([("state", "=", "done")])
- max_date_commit = max(forwards.mapped("to_date_commit"))
- # Not allow cancel document is past period.
- if max_date_commit and any(
- rec.to_date_commit < max_date_commit for rec in self
- ):
- raise UserError(
- _("Unable to cancel this document as it belongs to a past period.")
- )
+ if forwards:
+ max_date_commit = max(forwards.mapped("to_date_commit"))
+ # Not allow cancel document is past period.
+ if max_date_commit and any(
+ rec.to_date_commit < max_date_commit for rec in self
+ ):
+ raise UserError(
+ _("Unable to cancel this document as it belongs to a past period.")
+ )
self.filtered(lambda l: l.state == "done")._do_forward_commit(reverse=True)
self.write({"state": "cancel"})
self._do_update_initial_commit(reverse=True)
From b79c0650eb74fbdb12d168911964aefb5252ad43 Mon Sep 17 00:00:00 2001
From: Saran440
Date: Tue, 24 Sep 2024 12:52:01 +0700
Subject: [PATCH 19/24] [ENH] budget_control: add config control_key in company
---
budget_control/models/base_budget_move.py | 1 -
budget_control/models/budget_period.py | 46 ++++++++++---------
budget_control/models/res_company.py | 6 +++
budget_control/models/res_config_settings.py | 4 ++
.../views/res_config_settings_views.xml | 17 +++++++
5 files changed, 52 insertions(+), 22 deletions(-)
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index bb7d6de2..d7f5af02 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -11,7 +11,6 @@
class BaseBudgetMove(models.AbstractModel):
_name = "base.budget.move"
_description = "Document Budget Moves"
- _budget_control_field = "account_id"
_order = "analytic_account_id, date, id"
reference = fields.Char(
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index ebde2247..736891f5 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -334,66 +334,70 @@ def _prepare_controls(self, budget_period, doclines):
lambda l: l.date >= budget_period.bm_date_from
and l.date <= budget_period.bm_date_to
)
+ budget_control_key = self.env.company.budget_control_key
need_control = self.env.context.get("need_control")
for budget_move in budget_moves_period:
if budget_period.control_all_analytic_accounts:
- if (
- budget_move.analytic_account_id
- and budget_move[budget_move._budget_control_field]
- ):
+ if budget_move.analytic_account_id and budget_move[budget_control_key]:
controls.add(
(
budget_move.analytic_account_id.id,
- budget_move[budget_move._budget_control_field].id,
+ budget_move[budget_control_key].id,
)
)
else: # analytic in control or force control by send context
if (
budget_move.analytic_account_id in control_analytics
- and budget_move[budget_move._budget_control_field]
+ and budget_move[budget_control_key]
) or need_control:
controls.add(
(
budget_move.analytic_account_id.id,
- budget_move[budget_move._budget_control_field].id,
+ budget_move[budget_control_key].id,
)
)
# Convert to list of dicts for readability
- return [
- {"analytic_id": x[0], budget_move._budget_control_field: x[1]}
- for x in controls
- ]
+ return [{"analytic_id": x[0], budget_control_key: x[1]} for x in controls]
def _get_filter_template_line(self, all_template_lines, control):
- account_id = control["account_id"]
- template_lines = all_template_lines.filtered(
- lambda l: account_id in l.account_ids.ids
- )
+ budget_control_key = self.env.company.budget_control_key
+ if budget_control_key == "account_id":
+ control_id = control[budget_control_key]
+ template_lines = all_template_lines.filtered(
+ lambda l: control_id in l.account_ids.ids
+ )
return template_lines
+ def _get_control_key_obj(self, control_key, control_id):
+ if control_key == "account_id":
+ control = self.env["account.account"].browse(control_id)
+ control_name = "account code"
+ return control, control_name
+
@api.model
def _get_kpi_by_control_key(self, template_lines, control):
"""
By default, control key is account_id as it can be used to get KPI
In future, this can be other key, i.e., activity_id based on installed module
"""
- account_id = control["account_id"]
+ control_key = self.env.company.budget_control_key
+ control_id = control[control_key]
template_line = self._get_filter_template_line(template_lines, control)
if len(template_line) == 1:
return template_line
# Invalid Template Lines
- account = self.env["account.account"].browse(account_id)
+ control, control_name = self._get_control_key_obj(control_key, control_id)
if not template_line:
raise UserError(
- _("Chosen account code %s is not valid in template")
- % account.display_name
+ _("Chosen %(name)s %(display_name)s is not valid in template")
+ % ({"name": control_name, "display_name": control.display_name})
)
raise UserError(
_(
"Template Lines has more than one KPI being "
- "referenced by the same account code %s"
+ "referenced by the same %(name)s %(display_name)s"
)
- % (account.display_name)
+ % ({"name": control_name, "display_name": control.display_name})
)
def _get_where_domain(self, analytic_id, template_lines):
diff --git a/budget_control/models/res_company.py b/budget_control/models/res_company.py
index 467a870d..a43f11bd 100644
--- a/budget_control/models/res_company.py
+++ b/budget_control/models/res_company.py
@@ -40,3 +40,9 @@ class ResCompany(models.Model):
comodel_name="budget.template",
string="Budget Template",
)
+ budget_control_key = fields.Selection(
+ selection=[("account_id", "Account")],
+ string="Control Key",
+ required=True,
+ default="account_id",
+ )
diff --git a/budget_control/models/res_config_settings.py b/budget_control/models/res_config_settings.py
index f43e6cdc..99cf98fe 100644
--- a/budget_control/models/res_config_settings.py
+++ b/budget_control/models/res_config_settings.py
@@ -29,6 +29,10 @@ class ResConfigSettings(models.TransientModel):
related="company_id.budget_template_id",
readonly=False,
)
+ budget_control_key = fields.Selection(
+ related="company_id.budget_control_key",
+ readonly=False,
+ )
group_required_analytic = fields.Boolean(
string="Required Analytic Account",
implied_group="budget_control.group_required_analytic",
diff --git a/budget_control/views/res_config_settings_views.xml b/budget_control/views/res_config_settings_views.xml
index aefa57f6..f6158338 100644
--- a/budget_control/views/res_config_settings_views.xml
+++ b/budget_control/views/res_config_settings_views.xml
@@ -197,6 +197,23 @@
+
+
+
+
+ Default Key to used get KPI.
+
+
+
+
+
+
+
+
Budget Period
Date: Tue, 24 Sep 2024 15:20:02 +0700
Subject: [PATCH 20/24] [IMP] budget_control: multi-company budget adjustment
---
budget_control/__manifest__.py | 1 +
budget_control/models/budget_control.py | 1 -
.../models/budget_move_adjustment.py | 19 ++++++++++++++++++-
.../security/budget_adjustment_rules.xml | 13 +++++++++++++
.../views/budget_move_adjustment_view.xml | 5 +++++
5 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 budget_control/security/budget_adjustment_rules.xml
diff --git a/budget_control/__manifest__.py b/budget_control/__manifest__.py
index 755fa743..8430b8bc 100644
--- a/budget_control/__manifest__.py
+++ b/budget_control/__manifest__.py
@@ -19,6 +19,7 @@
"data/sequence_data.xml",
"security/budget_control_security_groups.xml",
"security/budget_control_rules.xml",
+ "security/budget_adjustment_rules.xml",
"security/ir.model.access.csv",
"wizards/generate_budget_control_view.xml",
"wizards/analytic_budget_info_view.xml",
diff --git a/budget_control/models/budget_control.py b/budget_control/models/budget_control.py
index c79e0f1a..cb83ca18 100644
--- a/budget_control/models/budget_control.py
+++ b/budget_control/models/budget_control.py
@@ -84,7 +84,6 @@ class BudgetControl(models.Model):
)
company_id = fields.Many2one(
comodel_name="res.company",
- string="Company",
default=lambda self: self.env.company,
required=True,
readonly=True,
diff --git a/budget_control/models/budget_move_adjustment.py b/budget_control/models/budget_move_adjustment.py
index 80a4670e..3574e7cf 100644
--- a/budget_control/models/budget_move_adjustment.py
+++ b/budget_control/models/budget_move_adjustment.py
@@ -41,9 +41,19 @@ class BudgetMoveAdjustment(models.Model):
states={"draft": [("readonly", False)]},
tracking=True,
)
+ company_id = fields.Many2one(
+ comodel_name="res.company",
+ default=lambda self: self.env.company,
+ required=True,
+ readonly=True,
+ states={"draft": [("readonly", False)]},
+ index=True,
+ )
currency_id = fields.Many2one(
comodel_name="res.currency",
- default=lambda self: self.env.user.company_id.currency_id,
+ related="company_id.currency_id",
+ store=True,
+ states={"draft": [("readonly", False)]},
)
state = fields.Selection(
[
@@ -144,6 +154,13 @@ class BudgetMoveAdjustmentItem(models.Model):
account_id = fields.Many2one(
comodel_name="account.account",
required=True,
+ index=True,
+ )
+ company_id = fields.Many2one(
+ related="adjust_id.company_id",
+ store=True,
+ readonly=True,
+ index=True,
)
currency_id = fields.Many2one(
related="adjust_id.currency_id",
diff --git a/budget_control/security/budget_adjustment_rules.xml b/budget_control/security/budget_adjustment_rules.xml
new file mode 100644
index 00000000..aeccdb74
--- /dev/null
+++ b/budget_control/security/budget_adjustment_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ Budget Adjustment multi-company
+
+ [('company_id', 'in', company_ids)]
+
+
diff --git a/budget_control/views/budget_move_adjustment_view.xml b/budget_control/views/budget_move_adjustment_view.xml
index d5395eb0..1a142607 100644
--- a/budget_control/views/budget_move_adjustment_view.xml
+++ b/budget_control/views/budget_move_adjustment_view.xml
@@ -39,6 +39,7 @@
+
+
Date: Tue, 24 Sep 2024 17:05:42 +0700
Subject: [PATCH 21/24] [FIX] budget_control_expense: compute without tax
---
budget_control/models/budget_period.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 736891f5..45965c87 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -261,7 +261,8 @@ def check_budget_precommit(self, doclines, doc_type="account"):
if budget_move:
budget_moves.append(budget_move)
# Update database, so we can check budget with query
- budget_move.flush_model()
+ if budget_move:
+ budget_move.flush_model()
# Check Budget
self.env["budget.period"].check_budget(doclines, doc_type=doc_type)
# Remove commits
From 0f288ec7f8ea0bfd8f862196dede9c1cffcee42b Mon Sep 17 00:00:00 2001
From: Saran440
Date: Mon, 10 Mar 2025 12:03:12 +0700
Subject: [PATCH 22/24] [IMP] budget_control: pre-commit auto fixes
---
budget_control/README.rst | 266 ++++++++++--------
budget_control/demo/budget_template_demo.xml | 1 -
budget_control/models/analytic_account.py | 4 +-
.../models/budget_commit_forward.py | 7 +-
budget_control/models/budget_period.py | 19 +-
budget_control/pyproject.toml | 3 +
budget_control/readme/CONTRIBUTORS.md | 2 +
budget_control/readme/CONTRIBUTORS.rst | 2 -
budget_control/readme/DESCRIPTION.md | 138 +++++++++
budget_control/readme/DESCRIPTION.rst | 123 --------
budget_control/readme/USAGE.md | 67 +++++
budget_control/readme/USAGE.rst | 55 ----
.../report/budget_monitor_report.py | 24 +-
budget_control/static/description/index.html | 180 +++++++-----
.../static/src/xml/budget_popover.xml | 4 +-
.../views/budget_balance_forward_view.xml | 22 +-
.../views/budget_commit_forward_view.xml | 27 +-
budget_control/views/budget_control_view.xml | 4 +-
.../views/budget_move_adjustment_view.xml | 13 +-
budget_control/views/budget_template_view.xml | 4 +-
.../views/budget_transfer_item_view.xml | 1 -
budget_control/views/budget_transfer_view.xml | 3 +-
.../wizards/generate_budget_control.py | 2 +-
requirements.txt | 4 -
24 files changed, 531 insertions(+), 444 deletions(-)
create mode 100644 budget_control/pyproject.toml
create mode 100644 budget_control/readme/CONTRIBUTORS.md
delete mode 100644 budget_control/readme/CONTRIBUTORS.rst
create mode 100644 budget_control/readme/DESCRIPTION.md
delete mode 100644 budget_control/readme/DESCRIPTION.rst
create mode 100644 budget_control/readme/USAGE.md
delete mode 100644 budget_control/readme/USAGE.rst
delete mode 100644 requirements.txt
diff --git a/budget_control/README.rst b/budget_control/README.rst
index 8cfae411..7c35ee25 100644
--- a/budget_control/README.rst
+++ b/budget_control/README.rst
@@ -16,135 +16,158 @@ Budget Control
.. |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-ecosoft--odoo%2Fbudgeting-lightgray.png?logo=github
- :target: https://github.com/ecosoft-odoo/budgeting/tree/16.0/budget_control
- :alt: ecosoft-odoo/budgeting
-
-|badge1| |badge2| |badge3|
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbudgeting-lightgray.png?logo=github
+ :target: https://github.com/OCA/budgeting/tree/18.0/budget_control
+ :alt: OCA/budgeting
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/budgeting-18-0/budgeting-18-0-budget_control
+ :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/budgeting&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
This module is the main module from a set of budget control modules.
-This module alone will allow you to work in full cycle of budget control process.
-Other modules, each one are the small enhancement of this module, to fullfill
-additional needs. Having said that, following will describe the full cycle of budget
-control already provided by this module,
+This module alone will allow you to work in full cycle of budget control
+process. Other modules, each one are the small enhancement of this
+module, to fullfill additional needs. Having said that, following will
+describe the full cycle of budget control already provided by this
+module,
Budget Control Core Features:
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------
-* **Budget Commitment (base.budget.move)**
+- **Budget Commitment (base.budget.move)**
Probably the most crucial part of budget_control.
- * Budget Balance = Budget Allocated - (Budget Actuals - Budget Commitments)
+ - Budget Balance = Budget Allocated - (Budget Actuals - Budget
+ Commitments)
- Actual amount are from `account.move.line` from posted invoice. Commitments can be sales/purchase,
- expense, purchase request, etc. Document required to be budget commitment can extend base.budget.move.
- For example, the module budget_control_expense will create budget commitment `expense.budget.move`
- for approved expense.
- Note that, in this budget_control module, there is no extension for budget commitment yet.
+ Actual amount are from account.move.line from posted invoice.
+ Commitments can be sales/purchase, expense, purchase request, etc.
+ Document required to be budget commitment can extend base.budget.move.
+ For example, the module budget_control_expense will create budget
+ commitment expense.budget.move for approved expense. Note that, in
+ this budget_control module, there is no extension for budget
+ commitment yet.
-* **Budget Template (budget.template)**
+- **Budget Template (budget.template)**
- A Budget Template in the budget control system serves as a framework for controlling the budget,
- allowing for the budget to be managed according to the pre-defined template.
- The budget template has a relationship with the accounting,
- and is used to control spending based on pre-configured accounts.
+ A Budget Template in the budget control system serves as a framework
+ for controlling the budget, allowing for the budget to be managed
+ according to the pre-defined template. The budget template has a
+ relationship with the accounting, and is used to control spending
+ based on pre-configured accounts.
-* **Budget Period (budget.period)**
+- **Budget Period (budget.period)**
- Budget Period is the first thing to do for new budget year, and is used to govern how budget will be
- controlled over the defined date range, i.e.,
+ Budget Period is the first thing to do for new budget year, and is
+ used to govern how budget will be controlled over the defined date
+ range, i.e.,
- * Duration of budget year
- * Template to control (budget.template)
- * Document to do budget checking
- * Analytic account in controlled
- * Control Level
+ - Duration of budget year
+ - Template to control (budget.template)
+ - Document to do budget checking
+ - Analytic account in controlled
+ - Control Level
- Although not mandatory, an organization will most likely use fiscal year as budget period.
- In such case, there will be 1 budget period per fiscal year, and multiple budget control sheet (one per analytic).
+ Although not mandatory, an organization will most likely use fiscal
+ year as budget period. In such case, there will be 1 budget period per
+ fiscal year, and multiple budget control sheet (one per analytic).
-* **Budget Control Sheet (budget.control)**
+- **Budget Control Sheet (budget.control)**
- Each analytic account can have one budget control sheet per budget period.
- The budget control is used to allocate budget amount in a simpler way.
- In the backend it simply create budget.control.line, nothing too fancy.
- Once we have budget allocations, the system is ready to perform budget check.
+ Each analytic account can have one budget control sheet per budget
+ period. The budget control is used to allocate budget amount in a
+ simpler way. In the backend it simply create budget.control.line,
+ nothing too fancy. Once we have budget allocations, the system is
+ ready to perform budget check.
-* **Budget Checking**
+- **Budget Checking**
- By calling function -- check_budget(), system will check whether the confirmation
- of such document can result in negative budget balance. If so, it throw error message.
- In this module, budget check occur during posting of invoice and journal entry.
- To check budget also on more documents, do install budget_control_xxx relevant to that document.
+ By calling function -- check_budget(), system will check whether the
+ confirmation of such document can result in negative budget balance.
+ If so, it throw error message. In this module, budget check occur
+ during posting of invoice and journal entry. To check budget also on
+ more documents, do install budget_control_xxx relevant to that
+ document.
-* **Budget Constraint**
+- **Budget Constraint**
- To make the function -- check_budget() more flexible,
- additional rules or limitations can be added to the budget checking process.
- The system will perform the regular budget check and will also check the additional conditions specified
- in the added rules. An example of using budget constraints can be seen from the budget_allocation module.
+ To make the function -- check_budget() more flexible, additional rules
+ or limitations can be added to the budget checking process. The system
+ will perform the regular budget check and will also check the
+ additional conditions specified in the added rules. An example of
+ using budget constraints can be seen from the budget_allocation
+ module.
-* **Budget Reports**
+- **Budget Reports**
Currently there are 2 types of report.
- 1. Budget Monitoring: combine all budget related transactions, and show them in Standard Odoo BI view.
- 2. Actual Budget Moves: combine all actual commit transactions, and show them in Standard Odoo BI view.
-
-* **Budget Commitment Move Forward**
+ 1. Budget Monitoring: combine all budget related transactions, and
+ show them in Standard Odoo BI view.
+ 2. Actual Budget Moves: combine all actual commit transactions, and
+ show them in Standard Odoo BI view.
- In case budget commitment is being used. Sometime user has committed budget withing this year
- but not ready to use it and want to move the commitment amount to next year budget.
- Budget Commitment Forward can be use to change the budget move's date to the designated year.
+- **Budget Commitment Move Forward**
-* **Budget Transfer**
+ In case budget commitment is being used. Sometime user has committed
+ budget withing this year but not ready to use it and want to move the
+ commitment amount to next year budget. Budget Commitment Forward can
+ be use to change the budget move's date to the designated year.
- This module allow transferring allocated budget from one budget control sheet to other
+- **Budget Transfer**
+ This module allow transferring allocated budget from one budget
+ control sheet to other
Extended Modules:
-~~~~~~~~~~~~~~~~~
+-----------------
Following are brief explanation of what the extended module will do.
**Budget Move extension**
-These modules extend base.budget.move for other document budget commitment.
+These modules extend base.budget.move for other document budget
+commitment.
-* budget_control_expense
-* budget_control_purchase
-* budget_control_purchase_request
-* budget_control_sale
+- budget_control_expense
+- budget_control_purchase
+- budget_control_purchase_request
+- budget_control_sale
**Budget Allocation**
-This module is the main module for manage allocation (source of fund, analytic tag and analytic account)
-until set budget control. and allow create Master Data source of fund, analytic tag dimension.
-Users can view source of fund monitoring report
+This module is the main module for manage allocation (source of fund,
+analytic tag and analytic account) until set budget control. and allow
+create Master Data source of fund, analytic tag dimension. Users can
+view source of fund monitoring report
-* budget_allocation
+- budget_allocation
**Tier Validation**
Extend base_tier_validation for budget control sheet
-* budget_control_tier_validation
+- budget_control_tier_validation
**Analytic Tag Dimension Enhancements**
-When 1 dimension (analytic account) is not enough,
-we can use dimension to create persistent dimension columns
+When 1 dimension (analytic account) is not enough, we can use dimension
+to create persistent dimension columns
- analytic_tag_dimension
- analytic_tag_dimension_enhanced
-Following modules ensure that, analytic_tag_dimension will work with all new
-budget control objects. These are important for reporting purposes.
+Following modules ensure that, analytic_tag_dimension will work with all
+new budget control objects. These are important for reporting purposes.
-* budget_allocation
-* budget_allocation_expense
-* budget_allocation_purchase
+- budget_allocation
+- budget_allocation_expense
+- budget_allocation_purchase
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
@@ -160,8 +183,9 @@ Usage
=====
Before start using this module, following access right must be set.
- - Budget User for Budget Control Sheet, Budget Report
- - Budget Manager for Budget Period
+
+- Budget User for Budget Control Sheet, Budget Report
+- Budget Manager for Budget Period
Followings are sample steps to start with,
@@ -175,53 +199,65 @@ Followings are sample steps to start with,
3. Create new Budget Period
- - Choose Budget template
- - Identify date range, i.e., 1 fiscal year
- - Plan Date Range, i.e., Quarter, the slot to fill allocation in budget control will split by quarter
- - Control Budget = True (if not check = not check budget for this period)
+ - Choose Budget template
+ - Identify date range, i.e., 1 fiscal year
+ - Plan Date Range, i.e., Quarter, the slot to fill allocation in
+ budget control will split by quarter
+ - Control Budget = True (if not check = not check budget for this
+ period)
4. Create Budget Control Sheet
- To create budget control sheet, you can either create manually one by one or by using the helper,
- Action > Create Budget Control Sheet
+ To create budget control sheet, you can either create manually one by
+ one or by using the helper, Action > Create Budget Control Sheet
- - Choose Analytic budget_control_purchase_tag_dimension
- - Check All Analytic Account, this will list all analytic account in selected groups
- - Uncheck Initial Budget By Commitment, this is used only on following year to
- init budget allocation if they were committed amount carried over.
- - Click "Create Budget Control Sheet", and then view the newly created control sheets.
+ - Choose Analytic budget_control_purchase_tag_dimension
+ - Check All Analytic Account, this will list all analytic account
+ in selected groups
+ - Uncheck Initial Budget By Commitment, this is used only on
+ following year to init budget allocation if they were committed
+ amount carried over.
+ - Click "Create Budget Control Sheet", and then view the newly
+ created control sheets.
5. Allocate amount in Budget Control Sheets
- Each analytic account will have its own sheet. Form Budget Period, click on the
- icon "Budget Control Sheets" or by Menu > Budgeting > Budget Control Sheet, to open them.
+ Each analytic account will have its own sheet. Form Budget Period,
+ click on the icon "Budget Control Sheets" or by Menu > Budgeting >
+ Budget Control Sheet, to open them.
- - Based on "Plan Date Range" period, Plan table will show all KPI split by Plan Date Range
- - Allocate budget amount as appropriate.
- - Click Control button, state will change to Controlled.
+ - Based on "Plan Date Range" period, Plan table will show all KPI
+ split by Plan Date Range
+ - Allocate budget amount as appropriate.
+ - Click Control button, state will change to Controlled.
- Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
- Once ready, you can click on "Reset Plan" anytime.
+ Note: Make sure the Plan Date Rang period already has date ranges
+ that covers entire budget period. Once ready, you can click on "Reset
+ Plan" anytime.
6. Budget Reports
- After some document transaction (i.e., invoice for actuals), you can view report anytime.
+ After some document transaction (i.e., invoice for actuals), you can
+ view report anytime.
- - On Budget Control sheet, click on Monitoring for see this budget report
- - Menu Budgeting > Budget Monitoring, to show budget report in standard Odoo BI view.
+ - On Budget Control sheet, click on Monitoring for see this budget
+ report
+ - Menu Budgeting > Budget Monitoring, to show budget report in
+ standard Odoo BI view.
7. Budget Checking
- As we have checked Control Budget = True in third step, checking will occur
- every time an invoice is validated. You can test by validate invoice with big amount to exceed.
+ As we have checked Control Budget = True in third step, checking will
+ occur every time an invoice is validated. You can test by validate
+ invoice with big amount to exceed.
Bug Tracker
===========
-Bugs are tracked on `GitHub Issues `_.
+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 `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -229,18 +265,28 @@ Credits
=======
Authors
-~~~~~~~
+-------
* Ecosoft
Contributors
-~~~~~~~~~~~~
+------------
-* Kitti Upariphutthiphong
-* Saran Lim.
+- Kitti Upariphutthiphong
+- Saran Lim.
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-kittiu| image:: https://github.com/kittiu.png?size=40px
:target: https://github.com/kittiu
@@ -249,10 +295,10 @@ Maintainers
:target: https://github.com/ru3ix-bbb
:alt: ru3ix-bbb
-Current maintainers:
+Current `maintainers `__:
|maintainer-kittiu| |maintainer-ru3ix-bbb|
-This module is part of the `ecosoft-odoo/budgeting `_ project on GitHub.
+This module is part of the `OCA/budgeting `_ project on GitHub.
-You are welcome to contribute.
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/budget_control/demo/budget_template_demo.xml b/budget_control/demo/budget_template_demo.xml
index 1240f338..903f350b 100644
--- a/budget_control/demo/budget_template_demo.xml
+++ b/budget_control/demo/budget_template_demo.xml
@@ -33,5 +33,4 @@
-
diff --git a/budget_control/models/analytic_account.py b/budget_control/models/analytic_account.py
index d23e14c1..73c6e041 100644
--- a/budget_control/models/analytic_account.py
+++ b/budget_control/models/analytic_account.py
@@ -83,9 +83,7 @@ class AccountAnalyticAccount(models.Model):
def _compute_name_with_budget_period(self):
for rec in self:
if rec.budget_period_id:
- rec.name_with_budget_period = "{}: {}".format(
- rec.budget_period_id.name, rec.name
- )
+ rec.name_with_budget_period = f"{rec.budget_period_id.name}: {rec.name}"
else:
rec.name_with_budget_period = rec.name
diff --git a/budget_control/models/budget_commit_forward.py b/budget_control/models/budget_commit_forward.py
index 194ba086..aa49794a 100644
--- a/budget_control/models/budget_commit_forward.py
+++ b/budget_control/models/budget_commit_forward.py
@@ -138,7 +138,7 @@ def _prepare_vals_forward(self, docs, res_model):
"method_type": method_type,
"res_model": res_model,
"res_id": doc.id,
- "document_id": "{},{}".format(doc._name, doc.id),
+ "document_id": f"{doc._name},{doc.id}",
"document_number": self._get_document_number(doc),
"amount_commit": doc.amount_commit[str(analytic_id)],
"date_commit": doc.fwd_date_commit or doc.date_commit,
@@ -441,10 +441,7 @@ def name_get(self):
return [
(
r.id,
- "{document_number} - {analytic}".format(
- document_number=r.document_number.display_name,
- analytic=r.analytic_account_id.name,
- ),
+ f"{r.document_number.display_name} - {r.analytic_account_id.name}",
)
for r in self
]
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 45965c87..5da72418 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -407,13 +407,13 @@ def _get_where_domain(self, analytic_id, template_lines):
not template_lines
or self._context.get("control_level", False) == "analytic"
):
- return "analytic_account_id = {}".format(analytic_id)
+ return f"analytic_account_id = {analytic_id}"
kpi_domain = (
- "= {}".format(template_lines.kpi_id.id)
+ f"= {template_lines.kpi_id.id}"
if len(template_lines) == 1
- else "in {}".format(tuple(template_lines.kpi_id.ids))
+ else f"in {tuple(template_lines.kpi_id.ids)}"
)
- return "analytic_account_id = {} and kpi_id {}".format(analytic_id, kpi_domain)
+ return f"analytic_account_id = {analytic_id} and kpi_id {kpi_domain}"
def _get_budget_monitor_report(self):
"""Hook for add context"""
@@ -422,11 +422,8 @@ def _get_budget_monitor_report(self):
def _get_budget_avaiable(self, analytic_id, template_lines):
self._cr.execute(
sql.SQL(
- """SELECT * FROM ({monitoring}) report
- WHERE {where_domain}""".format(
- monitoring=self._get_budget_monitor_report()._table_query,
- where_domain=self._get_where_domain(analytic_id, template_lines),
- )
+ f"""SELECT * FROM ({self._get_budget_monitor_report()._table_query}) report
+ WHERE {self._get_where_domain(analytic_id, template_lines)}"""
)
)
return self.env.cr.dictfetchall()
@@ -481,9 +478,7 @@ def _check_budget_available(self, controls, budget_period):
)
analytic_name = Analytic.browse(analytic_id).display_name
if budget_period.control_level == "analytic_kpi":
- analytic_name = "{} & {}".format(
- template_lines.display_name, analytic_name
- )
+ analytic_name = f"{template_lines.display_name} & {analytic_name}"
warnings.append(
_("{analytic_name}, will result in {formatted_balance}").format(
analytic_name=analytic_name, formatted_balance=fomatted_balance
diff --git a/budget_control/pyproject.toml b/budget_control/pyproject.toml
new file mode 100644
index 00000000..4231d0cc
--- /dev/null
+++ b/budget_control/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/budget_control/readme/CONTRIBUTORS.md b/budget_control/readme/CONTRIBUTORS.md
new file mode 100644
index 00000000..23ffd127
--- /dev/null
+++ b/budget_control/readme/CONTRIBUTORS.md
@@ -0,0 +1,2 @@
+- Kitti Upariphutthiphong \<\>
+- Saran Lim. \<\>
diff --git a/budget_control/readme/CONTRIBUTORS.rst b/budget_control/readme/CONTRIBUTORS.rst
deleted file mode 100644
index 9cf80039..00000000
--- a/budget_control/readme/CONTRIBUTORS.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-* Kitti Upariphutthiphong
-* Saran Lim.
diff --git a/budget_control/readme/DESCRIPTION.md b/budget_control/readme/DESCRIPTION.md
new file mode 100644
index 00000000..15cd6813
--- /dev/null
+++ b/budget_control/readme/DESCRIPTION.md
@@ -0,0 +1,138 @@
+This module is the main module from a set of budget control modules.
+This module alone will allow you to work in full cycle of budget control
+process. Other modules, each one are the small enhancement of this
+module, to fullfill additional needs. Having said that, following will
+describe the full cycle of budget control already provided by this
+module,
+
+## Budget Control Core Features:
+
+- **Budget Commitment (base.budget.move)**
+
+ Probably the most crucial part of budget_control.
+
+ - Budget Balance = Budget Allocated - (Budget Actuals - Budget
+ Commitments)
+
+ Actual amount are from account.move.line from posted invoice.
+ Commitments can be sales/purchase, expense, purchase request, etc.
+ Document required to be budget commitment can extend base.budget.move.
+ For example, the module budget_control_expense will create budget
+ commitment expense.budget.move for approved expense. Note that, in
+ this budget_control module, there is no extension for budget
+ commitment yet.
+
+- **Budget Template (budget.template)**
+
+ A Budget Template in the budget control system serves as a framework
+ for controlling the budget, allowing for the budget to be managed
+ according to the pre-defined template. The budget template has a
+ relationship with the accounting, and is used to control spending
+ based on pre-configured accounts.
+
+- **Budget Period (budget.period)**
+
+ Budget Period is the first thing to do for new budget year, and is
+ used to govern how budget will be controlled over the defined date
+ range, i.e.,
+
+ - Duration of budget year
+ - Template to control (budget.template)
+ - Document to do budget checking
+ - Analytic account in controlled
+ - Control Level
+
+ Although not mandatory, an organization will most likely use fiscal
+ year as budget period. In such case, there will be 1 budget period per
+ fiscal year, and multiple budget control sheet (one per analytic).
+
+- **Budget Control Sheet (budget.control)**
+
+ Each analytic account can have one budget control sheet per budget
+ period. The budget control is used to allocate budget amount in a
+ simpler way. In the backend it simply create budget.control.line,
+ nothing too fancy. Once we have budget allocations, the system is
+ ready to perform budget check.
+
+- **Budget Checking**
+
+ By calling function -- check_budget(), system will check whether the
+ confirmation of such document can result in negative budget balance.
+ If so, it throw error message. In this module, budget check occur
+ during posting of invoice and journal entry. To check budget also on
+ more documents, do install budget_control_xxx relevant to that
+ document.
+
+- **Budget Constraint**
+
+ To make the function -- check_budget() more flexible, additional rules
+ or limitations can be added to the budget checking process. The system
+ will perform the regular budget check and will also check the
+ additional conditions specified in the added rules. An example of
+ using budget constraints can be seen from the budget_allocation
+ module.
+
+- **Budget Reports**
+
+ Currently there are 2 types of report.
+
+ 1. Budget Monitoring: combine all budget related transactions, and
+ show them in Standard Odoo BI view.
+ 2. Actual Budget Moves: combine all actual commit transactions, and
+ show them in Standard Odoo BI view.
+
+- **Budget Commitment Move Forward**
+
+ In case budget commitment is being used. Sometime user has committed
+ budget withing this year but not ready to use it and want to move the
+ commitment amount to next year budget. Budget Commitment Forward can
+ be use to change the budget move's date to the designated year.
+
+- **Budget Transfer**
+
+ This module allow transferring allocated budget from one budget
+ control sheet to other
+
+## Extended Modules:
+
+Following are brief explanation of what the extended module will do.
+
+**Budget Move extension**
+
+These modules extend base.budget.move for other document budget
+commitment.
+
+- budget_control_expense
+- budget_control_purchase
+- budget_control_purchase_request
+- budget_control_sale
+
+**Budget Allocation**
+
+This module is the main module for manage allocation (source of fund,
+analytic tag and analytic account) until set budget control. and allow
+create Master Data source of fund, analytic tag dimension. Users can
+view source of fund monitoring report
+
+- budget_allocation
+
+**Tier Validation**
+
+Extend base_tier_validation for budget control sheet
+
+- budget_control_tier_validation
+
+**Analytic Tag Dimension Enhancements**
+
+When 1 dimension (analytic account) is not enough, we can use dimension
+to create persistent dimension columns
+
+- analytic_tag_dimension
+- analytic_tag_dimension_enhanced
+
+Following modules ensure that, analytic_tag_dimension will work with all
+new budget control objects. These are important for reporting purposes.
+
+- budget_allocation
+- budget_allocation_expense
+- budget_allocation_purchase
diff --git a/budget_control/readme/DESCRIPTION.rst b/budget_control/readme/DESCRIPTION.rst
deleted file mode 100644
index e1094a19..00000000
--- a/budget_control/readme/DESCRIPTION.rst
+++ /dev/null
@@ -1,123 +0,0 @@
-This module is the main module from a set of budget control modules.
-This module alone will allow you to work in full cycle of budget control process.
-Other modules, each one are the small enhancement of this module, to fullfill
-additional needs. Having said that, following will describe the full cycle of budget
-control already provided by this module,
-
-Budget Control Core Features:
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* **Budget Commitment (base.budget.move)**
-
- Probably the most crucial part of budget_control.
-
- * Budget Balance = Budget Allocated - (Budget Actuals - Budget Commitments)
-
- Actual amount are from `account.move.line` from posted invoice. Commitments can be sales/purchase,
- expense, purchase request, etc. Document required to be budget commitment can extend base.budget.move.
- For example, the module budget_control_expense will create budget commitment `expense.budget.move`
- for approved expense.
- Note that, in this budget_control module, there is no extension for budget commitment yet.
-
-* **Budget Template (budget.template)**
-
- A Budget Template in the budget control system serves as a framework for controlling the budget,
- allowing for the budget to be managed according to the pre-defined template.
- The budget template has a relationship with the accounting,
- and is used to control spending based on pre-configured accounts.
-
-* **Budget Period (budget.period)**
-
- Budget Period is the first thing to do for new budget year, and is used to govern how budget will be
- controlled over the defined date range, i.e.,
-
- * Duration of budget year
- * Template to control (budget.template)
- * Document to do budget checking
- * Analytic account in controlled
- * Control Level
-
- Although not mandatory, an organization will most likely use fiscal year as budget period.
- In such case, there will be 1 budget period per fiscal year, and multiple budget control sheet (one per analytic).
-
-* **Budget Control Sheet (budget.control)**
-
- Each analytic account can have one budget control sheet per budget period.
- The budget control is used to allocate budget amount in a simpler way.
- In the backend it simply create budget.control.line, nothing too fancy.
- Once we have budget allocations, the system is ready to perform budget check.
-
-* **Budget Checking**
-
- By calling function -- check_budget(), system will check whether the confirmation
- of such document can result in negative budget balance. If so, it throw error message.
- In this module, budget check occur during posting of invoice and journal entry.
- To check budget also on more documents, do install budget_control_xxx relevant to that document.
-
-* **Budget Constraint**
-
- To make the function -- check_budget() more flexible,
- additional rules or limitations can be added to the budget checking process.
- The system will perform the regular budget check and will also check the additional conditions specified
- in the added rules. An example of using budget constraints can be seen from the budget_allocation module.
-
-* **Budget Reports**
-
- Currently there are 2 types of report.
-
- 1. Budget Monitoring: combine all budget related transactions, and show them in Standard Odoo BI view.
- 2. Actual Budget Moves: combine all actual commit transactions, and show them in Standard Odoo BI view.
-
-* **Budget Commitment Move Forward**
-
- In case budget commitment is being used. Sometime user has committed budget withing this year
- but not ready to use it and want to move the commitment amount to next year budget.
- Budget Commitment Forward can be use to change the budget move's date to the designated year.
-
-* **Budget Transfer**
-
- This module allow transferring allocated budget from one budget control sheet to other
-
-
-Extended Modules:
-~~~~~~~~~~~~~~~~~
-
-Following are brief explanation of what the extended module will do.
-
-**Budget Move extension**
-
-These modules extend base.budget.move for other document budget commitment.
-
-* budget_control_expense
-* budget_control_purchase
-* budget_control_purchase_request
-* budget_control_sale
-
-**Budget Allocation**
-
-This module is the main module for manage allocation (source of fund, analytic tag and analytic account)
-until set budget control. and allow create Master Data source of fund, analytic tag dimension.
-Users can view source of fund monitoring report
-
-* budget_allocation
-
-**Tier Validation**
-
-Extend base_tier_validation for budget control sheet
-
-* budget_control_tier_validation
-
-**Analytic Tag Dimension Enhancements**
-
-When 1 dimension (analytic account) is not enough,
-we can use dimension to create persistent dimension columns
-
-- analytic_tag_dimension
-- analytic_tag_dimension_enhanced
-
-Following modules ensure that, analytic_tag_dimension will work with all new
-budget control objects. These are important for reporting purposes.
-
-* budget_allocation
-* budget_allocation_expense
-* budget_allocation_purchase
diff --git a/budget_control/readme/USAGE.md b/budget_control/readme/USAGE.md
new file mode 100644
index 00000000..9163a7f1
--- /dev/null
+++ b/budget_control/readme/USAGE.md
@@ -0,0 +1,67 @@
+Before start using this module, following access right must be set.
+- Budget User for Budget Control Sheet, Budget Report
+- Budget Manager for Budget Period
+
+Followings are sample steps to start with,
+
+1. Create new Budget KPI
+
+ To create budget KPI using in budget template
+
+2. Create new Budget Template
+
+ - Add new template for controlling Budget following kpi-account
+
+3. Create new Budget Period
+
+ > - Choose Budget template
+ > - Identify date range, i.e., 1 fiscal year
+ > - Plan Date Range, i.e., Quarter, the slot to fill allocation in
+ > budget control will split by quarter
+ > - Control Budget = True (if not check = not check budget for this
+ > period)
+
+4. Create Budget Control Sheet
+
+ To create budget control sheet, you can either create manually one
+ by one or by using the helper, Action \> Create Budget Control Sheet
+
+ > - Choose Analytic budget_control_purchase_tag_dimension
+ > - Check All Analytic Account, this will list all analytic account
+ > in selected groups
+ > - Uncheck Initial Budget By Commitment, this is used only on
+ > following year to init budget allocation if they were committed
+ > amount carried over.
+ > - Click "Create Budget Control Sheet", and then view the newly
+ > created control sheets.
+
+5. Allocate amount in Budget Control Sheets
+
+ Each analytic account will have its own sheet. Form Budget Period,
+ click on the icon "Budget Control Sheets" or by Menu \> Budgeting \>
+ Budget Control Sheet, to open them.
+
+ > - Based on "Plan Date Range" period, Plan table will show all KPI
+ > split by Plan Date Range
+ > - Allocate budget amount as appropriate.
+ > - Click Control button, state will change to Controlled.
+
+ Note: Make sure the Plan Date Rang period already has date ranges
+ that covers entire budget period. Once ready, you can click on
+ "Reset Plan" anytime.
+
+6. Budget Reports
+
+ After some document transaction (i.e., invoice for actuals), you can
+ view report anytime.
+
+ > - On Budget Control sheet, click on Monitoring for see this budget
+ > report
+ > - Menu Budgeting \> Budget Monitoring, to show budget report in
+ > standard Odoo BI view.
+
+7. Budget Checking
+
+ As we have checked Control Budget = True in third step, checking
+ will occur every time an invoice is validated. You can test by
+ validate invoice with big amount to exceed.
diff --git a/budget_control/readme/USAGE.rst b/budget_control/readme/USAGE.rst
deleted file mode 100644
index 8090f484..00000000
--- a/budget_control/readme/USAGE.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-Before start using this module, following access right must be set.
- - Budget User for Budget Control Sheet, Budget Report
- - Budget Manager for Budget Period
-
-Followings are sample steps to start with,
-
-1. Create new Budget KPI
-
- To create budget KPI using in budget template
-
-2. Create new Budget Template
-
- - Add new template for controlling Budget following kpi-account
-
-3. Create new Budget Period
-
- - Choose Budget template
- - Identify date range, i.e., 1 fiscal year
- - Plan Date Range, i.e., Quarter, the slot to fill allocation in budget control will split by quarter
- - Control Budget = True (if not check = not check budget for this period)
-
-4. Create Budget Control Sheet
-
- To create budget control sheet, you can either create manually one by one or by using the helper,
- Action > Create Budget Control Sheet
-
- - Choose Analytic budget_control_purchase_tag_dimension
- - Check All Analytic Account, this will list all analytic account in selected groups
- - Uncheck Initial Budget By Commitment, this is used only on following year to
- init budget allocation if they were committed amount carried over.
- - Click "Create Budget Control Sheet", and then view the newly created control sheets.
-
-5. Allocate amount in Budget Control Sheets
-
- Each analytic account will have its own sheet. Form Budget Period, click on the
- icon "Budget Control Sheets" or by Menu > Budgeting > Budget Control Sheet, to open them.
-
- - Based on "Plan Date Range" period, Plan table will show all KPI split by Plan Date Range
- - Allocate budget amount as appropriate.
- - Click Control button, state will change to Controlled.
-
- Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
- Once ready, you can click on "Reset Plan" anytime.
-
-6. Budget Reports
-
- After some document transaction (i.e., invoice for actuals), you can view report anytime.
-
- - On Budget Control sheet, click on Monitoring for see this budget report
- - Menu Budgeting > Budget Monitoring, to show budget report in standard Odoo BI view.
-
-7. Budget Checking
-
- As we have checked Control Budget = True in third step, checking will occur
- every time an invoice is validated. You can test by validate invoice with big amount to exceed.
diff --git a/budget_control/report/budget_monitor_report.py b/budget_control/report/budget_monitor_report.py
index c63aa625..73936445 100644
--- a/budget_control/report/budget_monitor_report.py
+++ b/budget_control/report/budget_monitor_report.py
@@ -57,17 +57,15 @@ class BudgetMonitorReport(models.Model):
@property
def _table_query(self):
- return """
+ return f"""
select a.*, p.id as budget_period_id
- from ({}) a
+ from ({self._get_sql()}) a
left outer join date_range d
on a.date between d.date_start and d.date_end
left outer join budget_period p
on a.date between p.bm_date_from and p.bm_date_to
- {}
- """.format(
- self._get_sql(), self._get_where_clause()
- )
+ {self._get_where_clause()}
+ """
def _get_consumed_sources(self):
return [
@@ -122,16 +120,10 @@ def _get_from_amount_types(self):
doc_table = source["source_doc"][0] # i.e., account_move
doc_field = source["source_doc"][1] # i.e., move_id
amount_type = source["type"][0] # i.e., 8_actual
- sql_from[
- amount_type
- ] = """
- from {} a
- left outer join {} b on a.{} = b.id
- """.format(
- budget_table,
- doc_table,
- doc_field,
- )
+ sql_from[amount_type] = f"""
+ from {budget_table} a
+ left outer join {doc_table} b on a.{doc_field} = b.id
+ """
return sql_from
def _select_budget(self):
diff --git a/budget_control/static/description/index.html b/budget_control/static/description/index.html
index dbb42604..bdc14246 100644
--- a/budget_control/static/description/index.html
+++ b/budget_control/static/description/index.html
@@ -369,35 +369,41 @@
This module is the main module from a set of budget control modules.
-This module alone will allow you to work in full cycle of budget control process.
-Other modules, each one are the small enhancement of this module, to fullfill
-additional needs. Having said that, following will describe the full cycle of budget
-control already provided by this module,
+This module alone will allow you to work in full cycle of budget control
+process. Other modules, each one are the small enhancement of this
+module, to fullfill additional needs. Having said that, following will
+describe the full cycle of budget control already provided by this
+module,
Actual amount are from account.move.line from posted invoice. Commitments can be sales/purchase,
-expense, purchase request, etc. Document required to be budget commitment can extend base.budget.move.
-For example, the module budget_control_expense will create budget commitment expense.budget.move
-for approved expense.
-Note that, in this budget_control module, there is no extension for budget commitment yet.
+
Actual amount are from account.move.line from posted invoice.
+Commitments can be sales/purchase, expense, purchase request, etc.
+Document required to be budget commitment can extend base.budget.move.
+For example, the module budget_control_expense will create budget
+commitment expense.budget.move for approved expense. Note that, in
+this budget_control module, there is no extension for budget
+commitment yet.
Budget Template (budget.template)
-
A Budget Template in the budget control system serves as a framework for controlling the budget,
-allowing for the budget to be managed according to the pre-defined template.
-The budget template has a relationship with the accounting,
-and is used to control spending based on pre-configured accounts.
+
A Budget Template in the budget control system serves as a framework
+for controlling the budget, allowing for the budget to be managed
+according to the pre-defined template. The budget template has a
+relationship with the accounting, and is used to control spending
+based on pre-configured accounts.
Budget Period (budget.period)
-
Budget Period is the first thing to do for new budget year, and is used to govern how budget will be
-controlled over the defined date range, i.e.,
+
Budget Period is the first thing to do for new budget year, and is
+used to govern how budget will be controlled over the defined date
+range, i.e.,
Duration of budget year
Template to control (budget.template)
@@ -405,41 +411,51 @@
Budget Control Core Features:
Analytic account in controlled
Control Level
-
Although not mandatory, an organization will most likely use fiscal year as budget period.
-In such case, there will be 1 budget period per fiscal year, and multiple budget control sheet (one per analytic).
+
Although not mandatory, an organization will most likely use fiscal
+year as budget period. In such case, there will be 1 budget period per
+fiscal year, and multiple budget control sheet (one per analytic).
Budget Control Sheet (budget.control)
-
Each analytic account can have one budget control sheet per budget period.
-The budget control is used to allocate budget amount in a simpler way.
-In the backend it simply create budget.control.line, nothing too fancy.
-Once we have budget allocations, the system is ready to perform budget check.
+
Each analytic account can have one budget control sheet per budget
+period. The budget control is used to allocate budget amount in a
+simpler way. In the backend it simply create budget.control.line,
+nothing too fancy. Once we have budget allocations, the system is
+ready to perform budget check.
Budget Checking
-
By calling function – check_budget(), system will check whether the confirmation
-of such document can result in negative budget balance. If so, it throw error message.
-In this module, budget check occur during posting of invoice and journal entry.
-To check budget also on more documents, do install budget_control_xxx relevant to that document.
+
By calling function – check_budget(), system will check whether the
+confirmation of such document can result in negative budget balance.
+If so, it throw error message. In this module, budget check occur
+during posting of invoice and journal entry. To check budget also on
+more documents, do install budget_control_xxx relevant to that
+document.
Budget Constraint
-
To make the function – check_budget() more flexible,
-additional rules or limitations can be added to the budget checking process.
-The system will perform the regular budget check and will also check the additional conditions specified
-in the added rules. An example of using budget constraints can be seen from the budget_allocation module.
+
To make the function – check_budget() more flexible, additional rules
+or limitations can be added to the budget checking process. The system
+will perform the regular budget check and will also check the
+additional conditions specified in the added rules. An example of
+using budget constraints can be seen from the budget_allocation
+module.
Budget Reports
Currently there are 2 types of report.
-
Budget Monitoring: combine all budget related transactions, and show them in Standard Odoo BI view.
-
Actual Budget Moves: combine all actual commit transactions, and show them in Standard Odoo BI view.
+
Budget Monitoring: combine all budget related transactions, and
+show them in Standard Odoo BI view.
+
Actual Budget Moves: combine all actual commit transactions, and
+show them in Standard Odoo BI view.
Budget Commitment Move Forward
-
In case budget commitment is being used. Sometime user has committed budget withing this year
-but not ready to use it and want to move the commitment amount to next year budget.
-Budget Commitment Forward can be use to change the budget move’s date to the designated year.
+
In case budget commitment is being used. Sometime user has committed
+budget withing this year but not ready to use it and want to move the
+commitment amount to next year budget. Budget Commitment Forward can
+be use to change the budget move’s date to the designated year.
Budget Transfer
-
This module allow transferring allocated budget from one budget control sheet to other
+
This module allow transferring allocated budget from one budget
+control sheet to other
@@ -447,7 +463,8 @@
Budget Control Core Features:
Extended Modules:
Following are brief explanation of what the extended module will do.
Budget Move extension
-
These modules extend base.budget.move for other document budget commitment.
+
These modules extend base.budget.move for other document budget
+commitment.
budget_control_expense
budget_control_purchase
@@ -455,9 +472,10 @@
Extended Modules:
budget_control_sale
Budget Allocation
-
This module is the main module for manage allocation (source of fund, analytic tag and analytic account)
-until set budget control. and allow create Master Data source of fund, analytic tag dimension.
-Users can view source of fund monitoring report
+
This module is the main module for manage allocation (source of fund,
+analytic tag and analytic account) until set budget control. and allow
+create Master Data source of fund, analytic tag dimension. Users can
+view source of fund monitoring report
budget_allocation
@@ -467,14 +485,14 @@
Extended Modules:
budget_control_tier_validation
Analytic Tag Dimension Enhancements
-
When 1 dimension (analytic account) is not enough,
-we can use dimension to create persistent dimension columns
+
When 1 dimension (analytic account) is not enough, we can use dimension
+to create persistent dimension columns
analytic_tag_dimension
analytic_tag_dimension_enhanced
-
Following modules ensure that, analytic_tag_dimension will work with all new
-budget control objects. These are important for reporting purposes.
+
Following modules ensure that, analytic_tag_dimension will work with all
+new budget control objects. These are important for reporting purposes.
Check All Analytic Account, this will list all analytic account in selected groups
-
Uncheck Initial Budget By Commitment, this is used only on following year to
-init budget allocation if they were committed amount carried over.
-
Click “Create Budget Control Sheet”, and then view the newly created control sheets.
+
Check All Analytic Account, this will list all analytic account
+in selected groups
+
Uncheck Initial Budget By Commitment, this is used only on
+following year to init budget allocation if they were committed
+amount carried over.
+
Click “Create Budget Control Sheet”, and then view the newly
+created control sheets.
Allocate amount in Budget Control Sheets
-
Each analytic account will have its own sheet. Form Budget Period, click on the
-icon “Budget Control Sheets” or by Menu > Budgeting > Budget Control Sheet, to open them.
+
Each analytic account will have its own sheet. Form Budget Period,
+click on the icon “Budget Control Sheets” or by Menu > Budgeting >
+Budget Control Sheet, to open them.
-
Based on “Plan Date Range” period, Plan table will show all KPI split by Plan Date Range
+
Based on “Plan Date Range” period, Plan table will show all KPI
+split by Plan Date Range
Allocate budget amount as appropriate.
Click Control button, state will change to Controlled.
-
Note: Make sure the Plan Date Rang period already has date ranges that covers entire budget period.
-Once ready, you can click on “Reset Plan” anytime.
+
Note: Make sure the Plan Date Rang period already has date ranges
+that covers entire budget period. Once ready, you can click on “Reset
+Plan” anytime.
Budget Reports
-
After some document transaction (i.e., invoice for actuals), you can view report anytime.
+
After some document transaction (i.e., invoice for actuals), you can
+view report anytime.
-
On Budget Control sheet, click on Monitoring for see this budget report
-
Menu Budgeting > Budget Monitoring, to show budget report in standard Odoo BI view.
+
On Budget Control sheet, click on Monitoring for see this budget
+report
+
Menu Budgeting > Budget Monitoring, to show budget report in
+standard Odoo BI view.
Budget Checking
-
As we have checked Control Budget = True in third step, checking will occur
-every time an invoice is validated. You can test by validate invoice with big amount to exceed.
+
As we have checked Control Budget = True in third step, checking will
+occur every time an invoice is validated. You can test by validate
+invoice with big amount to exceed.
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.
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 operation will find amount balance (planned - consumed) in current analtyic,
and set as Initial Available in Carry Forward Analytic (and Accumulate Analytic)
+ >Initial Available in Carry Forward Analytic (and Accumulate Analytic)
Click Review Budget Balance button, to find current balance of all analtyics.
+ >Review Budget Balance button, to find current balance of all analtyics.
Review method to forward, if required, click Create Missing Analytic.
+ >method to forward, if required, click Create Missing Analytic.
-
Blank: Analytic is open end, use the same analytic
-
New: To Analytic Account is next year analytic (need to create if missing)
-
Extend: Use same analtyic but extend end date to next year
-
+
Blank: Analytic is open end, use the same analytic
+
New: To Analytic Account is next year analytic (need to create if missing)
+
Extend: Use same analtyic but extend end date to next year
+
Fill in amount, both forward and accumulate (optional), and click Forward Budget Balance
This operation will move budget commitment of all documents below to the new commitment date.
The amount will be set in Initial Commitment in Analytic.
+ >Initial Commitment in Analytic.
Select document type to Forward Budget Commitment.
+ >Forward Budget Commitment.
Click Review Budget Commitment, to pull all commited documents.
+ >Review Budget Commitment, to pull all commited documents.
Review documement's method to forward, if required, click Create Missing Analytic.
+ >method to forward, if required, click Create Missing Analytic.
-
Blank: Analytic is open end, use the same analytic
-
New: To Analytic Account is next year analytic (need to create if missing)
-
Extend: Use same analtyic but extend end date to next year
-
+
Blank: Analytic is open end, use the same analytic
+
New: To Analytic Account is next year analytic (need to create if missing)
+
Extend: Use same analtyic but extend end date to next year
+
When ready, click Forward Budget Commitment to move commitment.
Account: Multiple items can be selected in each line. Used for budget commitment (PR/PO/CT/AV/EX/Actual). The template is checked to see if there is enough budget.
+ >Account: Multiple items can be selected in each line. Used for budget commitment (PR/PO/CT/AV/EX/Actual). The template is checked to see if there is enough budget.
KPI: Groups of accounts can be created from the Configuration > Budget KPI menu.
+ >KPI: Groups of accounts can be created from the Configuration > Budget KPI menu.
diff --git a/budget_control/views/budget_transfer_item_view.xml b/budget_control/views/budget_transfer_item_view.xml
index 49b76141..bf5c8e9e 100644
--- a/budget_control/views/budget_transfer_item_view.xml
+++ b/budget_control/views/budget_transfer_item_view.xml
@@ -76,5 +76,4 @@
-
diff --git a/budget_control/views/budget_transfer_view.xml b/budget_control/views/budget_transfer_view.xml
index 2a98172b..830d45da 100644
--- a/budget_control/views/budget_transfer_view.xml
+++ b/budget_control/views/budget_transfer_view.xml
@@ -101,8 +101,7 @@
/>
-
-
+
diff --git a/budget_control/wizards/generate_budget_control.py b/budget_control/wizards/generate_budget_control.py
index 82d1de56..e5ce5eed 100644
--- a/budget_control/wizards/generate_budget_control.py
+++ b/budget_control/wizards/generate_budget_control.py
@@ -125,7 +125,7 @@ def _onchange_use_all_kpis(self):
)
def _get_budget_period_name(self):
- budget_name = "{} :: ".format(self.budget_period_id.name)
+ budget_name = f"{self.budget_period_id.name} :: "
return budget_name
def _prepare_value_duplicate(self, vals):
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 269b1767..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# generated from manifests external_dependencies
-numpy
-openpyxl
-pandas
From 065795666e33c815b05d6051e1c41ff960741add Mon Sep 17 00:00:00 2001
From: Saran440
Date: Fri, 28 Mar 2025 11:06:32 +0700
Subject: [PATCH 23/24] [18.0][MIG] budget_control: refactor code
---
budget_control/README.rst | 41 +-
budget_control/__manifest__.py | 42 +-
budget_control/demo/budget_template_demo.xml | 9 +-
budget_control/hooks.py | 8 +-
budget_control/models/__init__.py | 31 +-
budget_control/models/account_move.py | 4 +-
budget_control/models/account_move_line.py | 12 +-
budget_control/models/analytic_account.py | 122 ++--
budget_control/models/base_budget_move.py | 56 +-
.../models/budget_balance_forward.py | 33 +-
.../models/budget_commit_forward.py | 137 +++--
budget_control/models/budget_constraint.py | 7 +-
budget_control/models/budget_control.py | 188 +++----
budget_control/models/budget_kpi.py | 1 +
.../models/budget_move_adjustment.py | 29 +-
budget_control/models/budget_period.py | 158 +++---
budget_control/models/budget_plan.py | 298 ++++++++++
budget_control/models/budget_template.py | 1 +
budget_control/models/budget_transfer.py | 38 +-
budget_control/models/budget_transfer_item.py | 62 +-
budget_control/models/res_company.py | 3 +-
budget_control/models/res_config_settings.py | 1 -
.../report/budget_monitor_report.py | 132 +++--
.../report/budget_monitor_report_view.xml | 10 +-
budget_control/report/budget_move_views.xml | 10 +-
.../security/budget_adjustment_rules.xml | 5 -
.../security/budget_control_rules.xml | 2 -
budget_control/security/ir.model.access.csv | 37 +-
budget_control/static/description/index.html | 21 +-
budget_control/tests/common.py | 10 +-
budget_control/tests/test_budget_control.py | 532 +++++++++---------
budget_control/views/account_move_views.xml | 26 +-
.../views/analytic_account_views.xml | 18 +-
.../views/budget_balance_forward_view.xml | 61 +-
.../views/budget_commit_forward_view.xml | 92 +--
.../views/budget_constraint_view.xml | 19 +-
budget_control/views/budget_control_view.xml | 91 +--
budget_control/views/budget_kpi_view.xml | 13 +-
budget_control/views/budget_menuitem.xml | 9 +
.../views/budget_move_adjustment_view.xml | 47 +-
budget_control/views/budget_period_view.xml | 42 +-
budget_control/views/budget_plan_view.xml | 270 +++++++++
budget_control/views/budget_template_view.xml | 19 +-
.../views/budget_transfer_item_view.xml | 33 +-
budget_control/views/budget_transfer_view.xml | 34 +-
.../views/res_config_settings_views.xml | 354 ++++--------
budget_control/wizards/__init__.py | 3 +-
.../wizards/analytic_budget_info.py | 1 +
.../wizards/analytic_budget_info_view.xml | 4 +-
.../budget_balance_forward_info_view.xml | 6 +-
.../budget_commit_forward_info_view.xml | 6 +-
.../wizards/confirm_state_budget.py | 2 +-
.../wizards/generate_budget_control.py | 213 -------
.../wizards/generate_budget_control_view.xml | 96 ----
54 files changed, 1836 insertions(+), 1663 deletions(-)
create mode 100644 budget_control/models/budget_plan.py
create mode 100644 budget_control/views/budget_plan_view.xml
delete mode 100644 budget_control/wizards/generate_budget_control.py
delete mode 100644 budget_control/wizards/generate_budget_control_view.xml
diff --git a/budget_control/README.rst b/budget_control/README.rst
index 7c35ee25..d4e4c954 100644
--- a/budget_control/README.rst
+++ b/budget_control/README.rst
@@ -16,17 +16,11 @@ Budget Control
.. |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%2Fbudgeting-lightgray.png?logo=github
- :target: https://github.com/OCA/budgeting/tree/18.0/budget_control
- :alt: OCA/budgeting
-.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/budgeting-18-0/budgeting-18-0-budget_control
- :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/budgeting&target_branch=18.0
- :alt: Try me on Runboat
-
-|badge1| |badge2| |badge3| |badge4| |badge5|
+.. |badge3| image:: https://img.shields.io/badge/github-ecosoft--odoo%2Fbudgeting-lightgray.png?logo=github
+ :target: https://github.com/ecosoft-odoo/budgeting/tree/18.0/budget_control
+ :alt: ecosoft-odoo/budgeting
+
+|badge1| |badge2| |badge3|
This module is the main module from a set of budget control modules.
This module alone will allow you to work in full cycle of budget control
@@ -254,10 +248,10 @@ Followings are sample steps to start with,
Bug Tracker
===========
-Bugs are tracked on `GitHub Issues `_.
+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 `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -278,27 +272,20 @@ Contributors
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-kittiu| image:: https://github.com/kittiu.png?size=40px
:target: https://github.com/kittiu
:alt: kittiu
.. |maintainer-ru3ix-bbb| image:: https://github.com/ru3ix-bbb.png?size=40px
:target: https://github.com/ru3ix-bbb
:alt: ru3ix-bbb
+.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px
+ :target: https://github.com/Saran440
+ :alt: Saran440
-Current `maintainers `__:
+Current maintainers:
-|maintainer-kittiu| |maintainer-ru3ix-bbb|
+|maintainer-kittiu| |maintainer-ru3ix-bbb| |maintainer-Saran440|
-This module is part of the `OCA/budgeting `_ project on GitHub.
+This module is part of the `ecosoft-odoo/budgeting `_ project on GitHub.
-You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+You are welcome to contribute.
diff --git a/budget_control/__manifest__.py b/budget_control/__manifest__.py
index 8430b8bc..aef96462 100644
--- a/budget_control/__manifest__.py
+++ b/budget_control/__manifest__.py
@@ -3,57 +3,59 @@
{
"name": "Budget Control",
- "version": "16.0.1.0.0",
+ "version": "18.0.1.0.0",
"category": "Accounting",
"license": "AGPL-3",
"author": "Ecosoft, Odoo Community Association (OCA)",
"website": "https://github.com/ecosoft-odoo/budgeting",
"depends": [
"account",
- "l10n_generic_coa",
"date_range",
"web_widget_x2many_2d_matrix",
],
"data": [
- "data/budget_data.xml",
- "data/sequence_data.xml",
+ "security/budget_adjustment_rules.xml",
"security/budget_control_security_groups.xml",
"security/budget_control_rules.xml",
- "security/budget_adjustment_rules.xml",
"security/ir.model.access.csv",
- "wizards/generate_budget_control_view.xml",
- "wizards/analytic_budget_info_view.xml",
+ "data/budget_data.xml",
+ "data/sequence_data.xml",
+ # Wizards
"wizards/analytic_budget_edit_view.xml",
+ "wizards/analytic_budget_info_view.xml",
"wizards/confirm_state_budget_view.xml",
"wizards/budget_commit_forward_info_view.xml",
"wizards/budget_balance_forward_info_view.xml",
- "views/account_budget_move.xml",
+ # Budget
"views/budget_menuitem.xml",
+ "views/res_config_settings_views.xml",
"views/budget_kpi_view.xml",
"views/budget_template_view.xml",
- "views/res_config_settings_views.xml",
"views/budget_period_view.xml",
"views/budget_constraint_view.xml",
- "views/budget_control_view.xml",
"views/analytic_account_views.xml",
- "views/account_move_views.xml",
- "views/account_journal_view.xml",
- "views/budget_balance_forward_view.xml",
- "views/budget_commit_forward_view.xml",
+ "views/budget_plan_view.xml",
+ "views/budget_control_view.xml",
"views/budget_transfer_view.xml",
"views/budget_transfer_item_view.xml",
"views/budget_move_adjustment_view.xml",
+ "views/budget_commit_forward_view.xml",
+ "views/budget_balance_forward_view.xml",
+ # Account Module
+ "views/account_budget_move.xml",
+ "views/account_journal_view.xml",
+ "views/account_move_views.xml",
"report/budget_monitor_report_view.xml",
"report/budget_move_views.xml",
],
"demo": ["demo/budget_template_demo.xml"],
- "assets": {
- "web.assets_backend": [
- "budget_control/static/src/xml/budget_popover.xml",
- ],
- },
+ # "assets": {
+ # "web.assets_backend": [
+ # "budget_control/static/src/xml/budget_popover.xml",
+ # ],
+ # },
"installable": True,
- "maintainers": ["kittiu", "ru3ix-bbb"],
+ "maintainers": ["kittiu", "ru3ix-bbb", "Saran440"],
"post_init_hook": "update_data_hooks",
"uninstall_hook": "uninstall_hook",
"development_status": "Alpha",
diff --git a/budget_control/demo/budget_template_demo.xml b/budget_control/demo/budget_template_demo.xml
index 903f350b..9ebfeca4 100644
--- a/budget_control/demo/budget_template_demo.xml
+++ b/budget_control/demo/budget_template_demo.xml
@@ -17,20 +17,17 @@
-
+
-
+
-
+
diff --git a/budget_control/hooks.py b/budget_control/hooks.py
index d4cf6d63..acd468ed 100644
--- a/budget_control/hooks.py
+++ b/budget_control/hooks.py
@@ -1,20 +1,16 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import SUPERUSER_ID, api
-
-def update_data_hooks(cr, registry):
- env = api.Environment(cr, SUPERUSER_ID, {})
+def update_data_hooks(env):
# Enable Analytic Account
env.ref("base.group_user").write(
{"implied_ids": [(4, env.ref("analytic.group_analytic_accounting").id)]}
)
-def uninstall_hook(cr, registry):
+def uninstall_hook(env):
"""Delete all data related to budget control"""
- env = api.Environment(cr, SUPERUSER_ID, {})
env["budget.template"].search([]).unlink()
env["budget.period"].search([]).unlink()
env["budget.control"].search([]).unlink()
diff --git a/budget_control/models/__init__.py b/budget_control/models/__init__.py
index a2c4f531..aec1e731 100644
--- a/budget_control/models/__init__.py
+++ b/budget_control/models/__init__.py
@@ -1,20 +1,29 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from . import base_budget_move
-from . import account_budget_move
-from . import account_move
-from . import account_move_line
+from . import res_company
+from . import res_config_settings
+
+# Master Data
from . import budget_kpi
from . import budget_template
from . import budget_period
-from . import budget_control
-from . import analytic_account
-from . import budget_balance_forward
-from . import budget_commit_forward
from . import budget_constraint
-from . import res_company
-from . import res_config_settings
-from . import account_journal
+from . import analytic_account
+
+# Base Model
+from . import base_budget_move
+
+# Operation Data
+from . import budget_plan
+from . import budget_control
from . import budget_transfer
from . import budget_transfer_item
from . import budget_move_adjustment
+from . import budget_commit_forward
+from . import budget_balance_forward
+
+# Account Module
+from . import account_journal
+from . import account_budget_move
+from . import account_move
+from . import account_move_line
diff --git a/budget_control/models/account_move.py b/budget_control/models/account_move.py
index 11a19d6d..f22d9f2c 100644
--- a/budget_control/models/account_move.py
+++ b/budget_control/models/account_move.py
@@ -10,8 +10,6 @@ class AccountMove(models.Model):
_docline_type = "account"
not_affect_budget = fields.Boolean(
- readonly=True,
- states={"draft": [("readonly", False)]},
help="If checked, lines does not create budget move",
)
budget_move_ids = fields.One2many(
@@ -87,7 +85,7 @@ def _filtered_move_check_budget(self):
def action_post(self):
res = super().action_post()
# Update database, then check budget
- self.flush_model()
+ # self.flush_model()
BudgetPeriod = self.env["budget.period"]
for move in self._filtered_move_check_budget():
BudgetPeriod.check_budget(move.line_ids)
diff --git a/budget_control/models/account_move_line.py b/budget_control/models/account_move_line.py
index b69e252d..f9ed5d1e 100644
--- a/budget_control/models/account_move_line.py
+++ b/budget_control/models/account_move_line.py
@@ -11,9 +11,6 @@ class AccountMoveLine(models.Model):
_budget_move_model = "account.budget.move"
_doc_rel = "move_id"
- can_commit = fields.Boolean(
- compute="_compute_can_commit",
- )
budget_move_ids = fields.One2many(
comodel_name="account.budget.move",
inverse_name="move_line_id",
@@ -25,6 +22,11 @@ class AccountMoveLine(models.Model):
@api.depends()
def _compute_can_commit(self):
+ """
+ Compute whether this account move line can commit budget.
+ Checks if the move is marked as not affecting budget - if so,
+ sets can_commit=False on those lines.
+ """
res = super()._compute_can_commit()
no_budget_moves = self.mapped("move_id").filtered("not_affect_budget")
no_budget_moves.mapped("line_ids").update({"can_commit": False})
@@ -41,11 +43,11 @@ def _init_docline_budget_vals(self, budget_vals, analytic_id):
if self.move_id.move_type == "entry":
total_amount = self.amount_currency
else:
- sign = -1 if self.move_id.move_type in ("out_refund", "in_refund") else 1
+ sign = -1 if self.is_refund else 1
discount = (100 - self.discount) / 100 if self.discount else 1
total_amount = sign * self.price_unit * self.quantity * discount
percent_analytic = self[self._budget_analytic_field].get(str(analytic_id))
- budget_vals["amount_currency"] = total_amount * percent_analytic / 100
+ budget_vals["amount_currency"] = total_amount * (percent_analytic / 100)
budget_vals["tax_ids"] = self.tax_ids.ids
# Document specific vals
budget_vals.update(
diff --git a/budget_control/models/analytic_account.py b/budget_control/models/analytic_account.py
index 73c6e041..f5221f2d 100644
--- a/budget_control/models/analytic_account.py
+++ b/budget_control/models/analytic_account.py
@@ -3,22 +3,16 @@
from dateutil.relativedelta import relativedelta
-from odoo import _, api, fields, models
+from odoo import api, fields, models
from odoo.exceptions import UserError
class AccountAnalyticAccount(models.Model):
_inherit = "account.analytic.account"
+ _rec_names_search = ["name", "code", "budget_period_id"]
- name_with_budget_period = fields.Char(
- compute="_compute_name_with_budget_period",
- store=True,
- help="This field hold analytic name with budget period indicator.\n"
- "This name will work with name_get() and name_search() to ensure usability",
- )
budget_period_id = fields.Many2one(
comodel_name="budget.period",
- index=True,
)
budget_control_ids = fields.One2many(
string="Budget Control(s)",
@@ -79,42 +73,22 @@ class AccountAnalyticAccount(models.Model):
help="Initial Balance from carry forward commitment",
)
- @api.depends("name", "budget_period_id")
- def _compute_name_with_budget_period(self):
- for rec in self:
- if rec.budget_period_id:
- rec.name_with_budget_period = f"{rec.budget_period_id.name}: {rec.name}"
- else:
- rec.name_with_budget_period = rec.name
-
- def name_get(self):
- res = []
+ @api.depends("code", "partner_id", "budget_period_id")
+ def _compute_display_name(self):
+ res = super()._compute_display_name()
for analytic in self:
- name = analytic.name_with_budget_period
- if analytic.code:
- name = ("[%(code)s] %(name)s") % {"code": analytic.code, "name": name}
- if analytic.partner_id:
- name = _("%(name)s - %(partner)s") % {
- "name": name,
- "partner": analytic.partner_id.commercial_partner_id.name,
- }
- res.append((analytic.id, name))
+ name = analytic.display_name
+ if analytic.budget_period_id:
+ name = f"{analytic.budget_period_id.name}: {name}"
+ analytic.display_name = name
return res
- @api.model
- def name_search(self, name="", args=None, operator="ilike", limit=100):
- # Make a search with default criteria
- args = args or []
- names1 = super(models.Model, self).name_search(
- name=name, args=args, operator=operator, limit=limit
- )
- # Make search with name_with_budget_period
- names2 = []
- if name:
- domain = args + [("name_with_budget_period", "=ilike", name + "%")]
- names2 = self.search(domain, limit=limit).name_get()
- # Merge both results
- return list(set(names1) | set(names2))[:limit]
+ @api.depends("budget_period_id")
+ def _compute_bm_date(self):
+ """Default effective date, but changable"""
+ for rec in self:
+ rec.bm_date_from = rec.budget_period_id.bm_date_from
+ rec.bm_date_to = rec.budget_period_id.bm_date_to
def _filter_by_analytic_account(self, val):
if val["analytic_account_id"][0] == self.id:
@@ -146,7 +120,10 @@ def _compute_amount_budget_info(self):
for rec in self:
# Filter according to budget_control parameter
dataset = list(
- filter(lambda l: rec._filter_by_analytic_account(l), dataset_all)
+ filter(
+ lambda dataset: rec._filter_by_analytic_account(dataset),
+ dataset_all,
+ )
)
# Get data from dataset
budget_info = BudgetPeriod.get_budget_info_from_dataset(query, dataset)
@@ -207,36 +184,53 @@ def _check_budget_control_status(self, budget_period_id=False):
domain = [("analytic_account_id", "in", self.ids)]
if budget_period_id:
domain.append(("budget_period_id", "=", budget_period_id))
- budget_controls = self.env["budget.control"].search(domain)
- # Find analytics has no budget control sheet
- bc_analytics = budget_controls.mapped("analytic_account_id")
- no_bc_analytics = set(self) - set(bc_analytics)
+
+ # Use search_read to fetch only required fields
+ budget_controls = self.env["budget.control"].search_read(
+ domain, ["analytic_account_id", "state"]
+ )
+ if not budget_controls:
+ names = ", ".join(self.mapped("display_name"))
+ raise UserError(
+ self.env._(
+ "No budget control sheet found for the selected analytics:\n"
+ f"{names}"
+ )
+ )
+
+ bc_analytics_ids = {
+ bc["analytic_account_id"][0]
+ for bc in budget_controls
+ if bc["analytic_account_id"]
+ }
+ no_bc_analytics = self.filtered(lambda x: x.id not in bc_analytics_ids)
+
+ # No budget control sheet found
if no_bc_analytics:
- names = ", ".join([analytic.display_name for analytic in no_bc_analytics])
+ names = ", ".join(no_bc_analytics.mapped("display_name"))
raise UserError(
- _("Following analytics has no budget control sheet:\n%s") % names
+ self.env._(
+ f"Following analytics have no budget control sheet:\n{names}"
+ )
)
+
# Find analytics has no controlled budget control sheet
- budget_controlled = budget_controls.filtered_domain([("state", "=", "done")])
- cbc_analytics = budget_controlled.mapped("analytic_account_id")
- no_cbc_analytics = set(self) - set(cbc_analytics)
+ budget_controlled_ids = {
+ bc["analytic_account_id"][0]
+ for bc in budget_controls
+ if bc["state"] == "done"
+ }
+ no_cbc_analytics = self.filtered(lambda x: x.id not in budget_controlled_ids)
+
if no_cbc_analytics:
- names = ", ".join([analytic.display_name for analytic in no_cbc_analytics])
+ names = ", ".join(no_cbc_analytics.mapped("display_name"))
raise UserError(
- _(
- "Budget control sheet for following analytics are not in "
- "control:\n%s"
+ self.env._(
+ f"Budget control sheets for the following analytics "
+ f"are not in control:\n{names}"
)
- % names
)
- @api.depends("budget_period_id")
- def _compute_bm_date(self):
- """Default effective date, but changable"""
- for rec in self:
- rec.bm_date_from = rec.budget_period_id.bm_date_from
- rec.bm_date_to = rec.budget_period_id.bm_date_to
-
def _auto_adjust_date_commit(self, docline):
for rec in self:
if not rec.auto_adjust_date_commit:
@@ -248,7 +242,7 @@ def _auto_adjust_date_commit(self, docline):
def action_edit_initial_available(self):
return {
- "name": _("Edit Analytic Budget"),
+ "name": self.env._("Edit Analytic Budget"),
"type": "ir.actions.act_window",
"res_model": "analytic.budget.edit",
"view_mode": "form",
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index d7f5af02..d38b1efc 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -87,7 +87,8 @@ class BaseBudgetMove(models.AbstractModel):
readonly=True,
)
adj_commit = fields.Boolean(
- help="This budget move line is the result of Over returned 'Automatic Adjustment'",
+ help="This budget move line is the result of "
+ "Over returned 'Automatic Adjustment'",
)
fwd_commit = fields.Boolean(
help="This budget move line is the result of 'Forward Budget Commitment'",
@@ -208,7 +209,7 @@ def _compute_can_commit(self):
def _filter_current_move(self, analytic):
self.ensure_one()
return self.budget_move_ids.filtered(
- lambda l: l.analytic_account_id == analytic
+ lambda move, analytic=analytic: move.analytic_account_id == analytic
)
@api.depends("budget_move_ids", "budget_move_ids.date")
@@ -230,7 +231,8 @@ def _compute_commit(self):
amount_commit_json = {}
for analytic_id in analytic_distribution: # Get id only
budget_move = rec.budget_move_ids.filtered(
- lambda move: move.analytic_account_id.id == int(analytic_id)
+ lambda move, analytic_id=analytic_id: move.analytic_account_id.id
+ == int(analytic_id)
)
debit = sum(budget_move.mapped("debit"))
credit = sum(budget_move.mapped("credit"))
@@ -425,13 +427,14 @@ def forward_commit(self):
if self.env.context.get("active_model") == "budget.commit.forward":
active_id = self.env.context.get("active_id", False)
fwd_lines.filtered(
- lambda l: (
- l.forward_id.state == "review" and l.forward_id.id == active_id
+ lambda line, active_id=active_id: (
+ line.forward_id.state == "review"
+ and line.forward_id.id == active_id
)
- or l.forward_id.state == "done"
+ or line.forward_id.state == "done"
)
else: # recompute budget
- fwd_lines.filtered(lambda l: l.forward_id.state == "done")
+ fwd_lines.filtered(lambda line: line.forward_id.state == "done")
for fwd_line in fwd_lines:
# find last date of carry forward
budget_period = BudgetPeriod._get_eligible_budget_period(
@@ -463,33 +466,48 @@ def forward_commit(self):
)
# Remove forward commitment from unused subsequent year budget lines
# If a budget line was forwarded to the next year but the budget
- # for that year is not utilized, this code removes the forward commitment,
+ # for that year is not utilized,
+ # this code removes the forward commitment,
# allowing the line to be forwarded again in the following year.
budget_move_previous_forward = self[self._budget_field()].filtered(
- lambda l: l.fwd_commit
- and l.date < fwd_line.forward_id.to_date_commit
- and l.debit > 0.0
+ lambda line, fwd_line=fwd_line: line.fwd_commit
+ and line.date < fwd_line.forward_id.to_date_commit
+ and line.debit > 0.0
)
if budget_move_previous_forward:
budget_move_previous_forward.write({"fwd_commit": False})
- def commit_budget(self, reverse=False, **vals):
- """Create budget commit for each docline"""
+ def _check_required_analytic(self):
+ """
+ Required all document except
+ - move that check 'Not Affect Budget'
+ - move that have 'Tax'
+ - display_type is not false
+ """
required_analytic = self.env.user.has_group(
"budget_control.group_required_analytic"
)
- # Required all document except move that check 'Not Affect Budget'
- # and not 'Tax' and display_type is not false
- if (
+ return (
required_analytic
- and (hasattr(self, "display_type") and not self.display_type)
and not self[self._budget_analytic_field]
and not (
self._name == "account.move.line"
and (self.move_id.not_affect_budget or self.tax_line_id)
)
- ):
- raise UserError(_("Please fill analytic account."))
+ # Account move line with display_type is not False
+ # but purchase, sale or other module don't have display_type if selected
+ # product in line
+ and (
+ self._name == "account.move.line"
+ and self.display_type == "product"
+ or (hasattr(self, "display_type") and not self.display_type)
+ )
+ )
+
+ def commit_budget(self, reverse=False, **vals):
+ """Create budget commit for each docline"""
+ if self._check_required_analytic():
+ raise UserError(self.env._("Please fill analytic account."))
self.prepare_commit()
to_commit = self.env.context.get("force_commit") or self._valid_commit_state()
if self.can_commit and to_commit:
diff --git a/budget_control/models/budget_balance_forward.py b/budget_control/models/budget_balance_forward.py
index d7b6221f..c6861e0f 100644
--- a/budget_control/models/budget_balance_forward.py
+++ b/budget_control/models/budget_balance_forward.py
@@ -9,30 +9,22 @@
class BudgetBalanceForward(models.Model):
_name = "budget.balance.forward"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Budget Balance Forward"
- _inherit = ["mail.thread"]
name = fields.Char(
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
from_budget_period_id = fields.Many2one(
comodel_name="budget.period",
- string="From Budget Period",
required=True,
ondelete="restrict",
- readonly=True,
- states={"draft": [("readonly", False)]},
default=lambda self: self.env["budget.period"]._get_eligible_budget_period(),
)
to_budget_period_id = fields.Many2one(
comodel_name="budget.period",
- string="To Budget Period",
required=True,
ondelete="restrict",
- readonly=True,
- states={"draft": [("readonly", False)]},
)
state = fields.Selection(
[
@@ -52,7 +44,6 @@ class BudgetBalanceForward(models.Model):
comodel_name="budget.balance.forward.line",
inverse_name="forward_id",
string="Forward Lines",
- readonly=True,
)
currency_id = fields.Many2one(
comodel_name="res.currency",
@@ -74,7 +65,9 @@ def _check_budget_period(self):
<= rec.from_budget_period_id.bm_date_to
):
raise ValidationError(
- _("'To Budget Period' must be later than 'From Budget Period'")
+ self.env._(
+ "'To Budget Period' must be later than 'From Budget Period'"
+ )
)
def _compute_missing_analytic(self):
@@ -167,7 +160,8 @@ def preview_budget_balance_forward_info(self):
raise UserError(
_(
"Some carry forward analytic accounts are missing.\n"
- "Click 'Create Missing Analytics' button to create for next budget period."
+ "Click 'Create Missing Analytics' button to create "
+ "for next budget period."
)
)
wizard = self.env.ref("budget_control.view_budget_balance_forward_info_form")
@@ -221,7 +215,8 @@ def get_amount(k, v):
return res
def _do_update_initial_avaliable(self):
- """Update all Analytic Account's initial commit value related to budget period"""
+ """Update all Analytic Account's initial commit value
+ related to budget period"""
self.ensure_one()
# Reset all lines
Analytic = self.env["account.analytic.account"]
@@ -289,7 +284,8 @@ class BudgetBalanceForwardLine(models.Model):
],
string="Method",
help="New: if the analytic has ended, 'To Analytic Account' is required\n"
- "Extended: if the analytic has ended, but want to extend to next period date end",
+ "Extended: if the analytic has ended, "
+ "but want to extend to next period date end",
)
to_analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
@@ -324,10 +320,12 @@ class BudgetBalanceForwardLine(models.Model):
def _check_amount(self):
for rec in self:
if rec.amount_balance_forward < 0 or rec.amount_balance_accumulate < 0:
- raise ValidationError(_("Negative amount is not allowed"))
+ raise ValidationError(self.env._("Negative amount is not allowed"))
if rec.amount_balance_accumulate and not rec.accumulate_analytic_account_id:
raise ValidationError(
- _("Accumulate Analytic is requried for lines when Accumulate > 0")
+ self.env._(
+ "Accumulate Analytic is requried for lines when Accumulate > 0"
+ )
)
@api.depends("method_type")
@@ -338,7 +336,8 @@ def _compute_to_analytic_account_id(self):
rec.to_analytic_account_id = rec.analytic_account_id
rec.method_type = False
continue
- # Case analytic has extended end date that cover new balance date, use same analytic
+ # Case analytic has extended end date that cover new balance date,
+ # use same analytic
if (
rec.analytic_account_id.bm_date_to
and rec.analytic_account_id.bm_date_to
diff --git a/budget_control/models/budget_commit_forward.py b/budget_control/models/budget_commit_forward.py
index aa49794a..160d7024 100644
--- a/budget_control/models/budget_commit_forward.py
+++ b/budget_control/models/budget_commit_forward.py
@@ -7,21 +7,16 @@
class BudgetCommitForward(models.Model):
_name = "budget.commit.forward"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Budget Commit Forward"
- _inherit = ["mail.thread"]
name = fields.Char(
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
to_budget_period_id = fields.Many2one(
comodel_name="budget.period",
- string="To Budget Period",
required=True,
ondelete="restrict",
- readonly=True,
- states={"draft": [("readonly", False)]},
)
to_date_commit = fields.Date(
related="to_budget_period_id.bm_date_from",
@@ -52,7 +47,6 @@ class BudgetCommitForward(models.Model):
comodel_name="budget.commit.forward.line",
inverse_name="forward_id",
string="Forward Lines",
- readonly=True,
)
missing_analytic = fields.Boolean(
compute="_compute_missing_analytic",
@@ -156,6 +150,12 @@ def action_filter_lines(self):
for rec in self:
rec.forward_line_ids = rec.filter_lines
+ def action_reset_filter_lines(self):
+ """Reset lines and review again"""
+ self.forward_line_ids.unlink()
+ for rec in self:
+ rec.action_review_budget_commit()
+
def get_budget_commit_forward(self, res_model):
"""Get budget commitment forward for each new commit document type."""
self = self.sudo()
@@ -166,21 +166,20 @@ def get_budget_commit_forward(self, res_model):
Line.create(vals)
def create_missing_analytic(self):
- for rec in self:
- for line in rec.forward_line_ids.filtered_domain(
- [("to_analytic_account_id", "=", False)]
- ):
- line.to_analytic_account_id = (
- line.analytic_account_id.next_year_analytic()
- )
+ forward_lines = self.mapped("forward_line_ids").filtered_domain(
+ [("to_analytic_account_id", "=", False)]
+ )
+ for line in forward_lines:
+ line.to_analytic_account_id = line.analytic_account_id.next_year_analytic()
def preview_budget_commit_forward_info(self):
self.ensure_one()
if self.missing_analytic:
raise UserError(
- _(
+ self.env._(
"Some carry forward analytic accounts are missing.\n"
- "Click 'Create Missing Analytics' button to create for next budget period."
+ "Click 'Create Missing Analytics' button to create for "
+ "next budget period."
)
)
wizard = self.env.ref("budget_control.view_budget_commit_forward_info_form")
@@ -190,7 +189,7 @@ def preview_budget_commit_forward_info(self):
]
forward_vals = self._get_forward_initial_commit(domain)
return {
- "name": _("Preview Budget Commitment"),
+ "name": self.env._("Preview Budget Commitment"),
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "budget.commit.forward.info",
@@ -228,66 +227,78 @@ def _do_forward_commit(self, reverse=False):
_analytic_field = "analytic_account_id" if reverse else "to_analytic_account_id"
for rec in self:
group_document = {}
+
# Group by document
for line in rec.forward_line_ids:
if line.document_id in group_document:
group_document[line.document_id].append(line)
else:
group_document[line.document_id] = [line]
- for doc, fwd_line in group_document.items():
+ for doc, fwd_lines in group_document.items():
# Convert to json
- fwd_analytic_distribution = {}
- for line in fwd_line:
- fwd_analytic_distribution[str(line[_analytic_field].id)] = (
- line.analytic_percent * 100
- )
+ fwd_analytic_distribution = {
+ str(line[_analytic_field].id): line.analytic_percent * 100
+ for line in fwd_lines
+ }
doc.write(
{
"fwd_analytic_distribution": fwd_analytic_distribution,
- "fwd_date_commit": reverse
- and fwd_line[0].date_commit
- or rec.to_date_commit,
+ "fwd_date_commit": fwd_lines[0].date_commit
+ if reverse
+ else rec.to_date_commit,
}
)
- # For case extend
- for line in rec.forward_line_ids:
- if not reverse and line.method_type == "extend":
- # Update end date of analytic account,
- # if it is extended by max date.
- if line.to_analytic_account_id.bm_date_to:
- date_to = max(
- line.to_analytic_account_id.bm_date_to,
- rec.to_budget_period_id.bm_date_to,
+
+ # For case extend, update end date of analytic account.
+ if not reverse:
+ max_date_to = rec.to_budget_period_id.bm_date_to
+ for line in rec.forward_line_ids:
+ if line.method_type == "extend" and line.to_analytic_account_id:
+ current_date_to = line.to_analytic_account_id.bm_date_to
+ line.to_analytic_account_id.bm_date_to = (
+ max(current_date_to, max_date_to)
+ if current_date_to
+ else max_date_to
)
- else:
- date_to = rec.to_budget_period_id.bm_date_to
- line.to_analytic_account_id.bm_date_to = date_to
def _do_update_initial_commit(self, reverse=False):
- """Update all Analytic Account's initial commit value related to budget period"""
+ """Update all Analytic Account's initial commit value
+ related to budget period"""
self.ensure_one()
# Reset initial when cancel document only
AnalyticAccount = self.env["account.analytic.account"]
domain = [("forward_id", "=", self.id)]
+
if reverse:
forward_vals = self._get_forward_initial_commit(domain)
- for val in forward_vals:
- analytic = AnalyticAccount.browse(val["analytic_account_id"])
+ analytics = AnalyticAccount.browse(
+ val["analytic_account_id"] for val in forward_vals
+ )
+ for analytic, val in zip(analytics, forward_vals, strict=False):
analytic.initial_commit -= val["initial_commit"]
return
- forward_duplicate = self.env["budget.commit.forward"].search(
- [
- ("to_budget_period_id", "=", self.to_budget_period_id.id),
- ("state", "=", "done"),
- ("id", "!=", self.id),
- ]
+
+ # Check if there are existing "done" forward commits in the same period
+ forward_exists = (
+ self.env["budget.commit.forward"].search_count(
+ [
+ ("to_budget_period_id", "=", self.to_budget_period_id.id),
+ ("state", "=", "done"),
+ ("id", "!=", self.id),
+ ]
+ )
+ > 0
)
+
domain.append(("forward_id.state", "in", ["review", "done"]))
forward_vals = self._get_forward_initial_commit(domain)
- for val in forward_vals:
- analytic = AnalyticAccount.browse(val["analytic_account_id"])
+ analytics = AnalyticAccount.browse(
+ val["analytic_account_id"] for val in forward_vals
+ )
+
+ for analytic, val in zip(analytics, forward_vals, strict=False):
# Check first forward commit in the year, it should overwrite initial commit
- if not forward_duplicate:
+ if not forward_exists:
analytic.initial_commit = val["initial_commit"]
else:
analytic.initial_commit += val["initial_commit"]
@@ -316,13 +327,13 @@ def action_cancel(self):
raise UserError(
_("Unable to cancel this document as it belongs to a past period.")
)
- self.filtered(lambda l: l.state == "done")._do_forward_commit(reverse=True)
+ self.filtered(lambda fwd: fwd.state == "done")._do_forward_commit(reverse=True)
self.write({"state": "cancel"})
self._do_update_initial_commit(reverse=True)
self._recompute_budget_move()
def action_draft(self):
- self.filtered(lambda l: l.state == "done")._do_forward_commit(reverse=True)
+ self.filtered(lambda fwd: fwd.state == "done")._do_forward_commit(reverse=True)
self.mapped("forward_line_ids").unlink()
self.write({"state": "draft"})
self._do_update_initial_commit(reverse=True)
@@ -358,7 +369,8 @@ class BudgetCommitForwardLine(models.Model):
],
string="Method",
help="New: if the analytic has ended, 'To Analytic Account' is required\n"
- "Extended: if the analytic has ended, but want to extend to next period date end",
+ "Extended: if the analytic has ended, "
+ "but want to extend to next period date end",
)
to_analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
@@ -419,7 +431,8 @@ def _compute_to_analytic_account_id(self):
rec.to_analytic_account_id = rec.analytic_account_id
rec.method_type = False
continue
- # Case analytic has extended end date that cover new commit date, use same analytic
+ # Case analytic has extended end date that cover new commit date,
+ # use same analytic
if (
rec.analytic_account_id.bm_date_to
and rec.analytic_account_id.bm_date_to >= rec.forward_id.to_date_commit
@@ -437,11 +450,11 @@ def _compute_to_analytic_account_id(self):
auto_create=False
)
- def name_get(self):
- return [
- (
- r.id,
- f"{r.document_number.display_name} - {r.analytic_account_id.name}",
- )
- for r in self
- ]
+ @api.depends("document_number", "analytic_account_id")
+ def _compute_display_name(self):
+ res = super()._compute_display_name()
+ for fwd_line in self:
+ doc_name = fwd_line.document_number.display_name
+ analytic_name = fwd_line.analytic_account_id.name
+ fwd_line.display_name = f"{doc_name} - {analytic_name}"
+ return res
diff --git a/budget_control/models/budget_constraint.py b/budget_control/models/budget_constraint.py
index 0b3fa668..bf59efbf 100644
--- a/budget_control/models/budget_constraint.py
+++ b/budget_control/models/budget_constraint.py
@@ -6,13 +6,13 @@
class BudgetConstraint(models.Model):
_name = "budget.constraint"
- _inherit = "mail.thread"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Constraint Budget by server action"
_order = "sequence"
sequence = fields.Integer(default=1, required=True)
- name = fields.Char(required=True)
- description = fields.Text()
+ name = fields.Char(required=True, tracking=True)
+ description = fields.Text(tracking=True)
server_action_id = fields.Many2one(
comodel_name="ir.actions.server",
string="Server Action",
@@ -20,6 +20,7 @@ class BudgetConstraint(models.Model):
("usage", "=", "ir_actions_server"),
("model_id.model", "=", "budget.constraint"),
],
+ tracking=True,
help="Server action triggered as soon as this step is check_budget",
)
active = fields.Boolean(default=True)
diff --git a/budget_control/models/budget_control.py b/budget_control/models/budget_control.py
index cb83ca18..86c858dc 100644
--- a/budget_control/models/budget_control.py
+++ b/budget_control/models/budget_control.py
@@ -1,6 +1,8 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from collections import defaultdict
+
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import float_compare
@@ -8,14 +10,12 @@
class BudgetControl(models.Model):
_name = "budget.control"
- _description = "Budget Control"
_inherit = ["mail.thread", "mail.activity.mixin"]
+ _description = "Budget Control"
_order = "analytic_account_id"
name = fields.Char(
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
tracking=True,
)
assignee_id = fields.Many2one(
@@ -30,24 +30,18 @@ class BudgetControl(models.Model):
],
tracking=True,
copy=False,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
budget_period_id = fields.Many2one(
comodel_name="budget.period",
help="Budget Period that inline with date from/to",
ondelete="restrict",
- readonly=True,
)
date_from = fields.Date(related="budget_period_id.bm_date_from")
date_to = fields.Date(related="budget_period_id.bm_date_to")
- active = fields.Boolean(
- default=True,
- )
+ active = fields.Boolean(default=True)
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account",
required=True,
- readonly=True,
tracking=True,
ondelete="restrict",
)
@@ -62,23 +56,14 @@ class BudgetControl(models.Model):
string="Budget Lines",
copy=True,
context={"active_test": False},
- readonly=True,
- states={
- "draft": [("readonly", False)],
- "submit": [("readonly", False)],
- },
)
plan_date_range_type_id = fields.Many2one(
comodel_name="date.range.type",
string="Plan Date Range",
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
init_budget_commit = fields.Boolean(
string="Initial Budget By Commitment",
- readonly=True,
- states={"draft": [("readonly", False)]},
help="If checked, the newly created budget control sheet will has "
"initial budget equal to current budget commitment of its year.",
)
@@ -86,18 +71,26 @@ class BudgetControl(models.Model):
comodel_name="res.company",
default=lambda self: self.env.company,
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
+ # company_ids = fields.Many2many(
+ # comodel_name="res.company",
+ # related="analytic_account_id.budget_company_ids",
+ # relation="budget_control_company_rel",
+ # column1="budget_control_id",
+ # column2="company_id",
+ # store=True,
+ # string="Companies",
+ # tracking=True,
+ # )
currency_id = fields.Many2one(
- comodel_name="res.currency", related="company_id.currency_id"
+ comodel_name="res.currency",
+ required=True,
+ tracking=True,
)
allocated_amount = fields.Monetary(
string="Allocated",
help="Initial total amount for plan",
tracking=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
)
released_amount = fields.Monetary(
string="Released",
@@ -147,8 +140,6 @@ class BudgetControl(models.Model):
)
use_all_kpis = fields.Boolean(
string="Use All KPIs",
- readonly=True,
- states={"draft": [("readonly", False)]},
)
template_line_ids = fields.Many2many(
string="KPIs", # Template line = 1 KPI, name for users
@@ -156,9 +147,8 @@ class BudgetControl(models.Model):
relation="budget_template_line_budget_contol_rel",
column1="budget_control_id",
column2="template_line_id",
- domain="[('template_id', '=', template_id)]",
- readonly=True,
- states={"draft": [("readonly", False)]},
+ compute="_compute_template_line_ids",
+ store=True,
)
state = fields.Selection(
[
@@ -186,6 +176,11 @@ class BudgetControl(models.Model):
@api.constrains("active", "state", "analytic_account_id", "budget_period_id")
def _check_budget_control_unique(self):
"""Not allow multiple active budget control on same period"""
+ analytic_ids = self.mapped("analytic_account_id").ids
+ period_ids = self.mapped("budget_period_id").ids
+ if not analytic_ids or not period_ids:
+ return # Nothing to check if no data
+
query = """
SELECT analytic_account_id, budget_period_id, COUNT(*)
FROM budget_control
@@ -193,20 +188,23 @@ def _check_budget_control_unique(self):
AND analytic_account_id IN %s
AND budget_period_id IN %s
GROUP BY analytic_account_id, budget_period_id
+ HAVING COUNT(*) > 1
"""
- params = (
- tuple(self.mapped("analytic_account_id").ids),
- tuple(self.mapped("budget_period_id").ids),
- )
+ params = (tuple(analytic_ids), tuple(period_ids))
+
self.env.cr.execute(query, params)
res = self.env.cr.dictfetchall()
- analytic_ids = [x["analytic_account_id"] for x in res if x["count"] > 1]
- if analytic_ids:
- analytics = self.env["account.analytic.account"].browse(analytic_ids)
- raise UserError(
- _("Multiple budget control on the same period for: %s")
- % ", ".join(analytics.mapped("name"))
+ if not res:
+ return # No duplicates found
+
+ analytic_ids = [x["analytic_account_id"] for x in res]
+ analytics = self.env["account.analytic.account"].browse(analytic_ids)
+ raise UserError(
+ self.env._(
+ f"Multiple budget control on the same period for: "
+ f"{', '.join(analytics.mapped('name'))}"
)
+ )
@api.depends("analytic_account_id")
def _compute_initial_balance(self):
@@ -221,8 +219,9 @@ def _check_budget_control_over_consumed(self):
BudgetPeriod = self.env["budget.period"]
if self.env.context.get("edit_amount", False):
return
+
for rec in self.filtered(
- lambda l: l.budget_period_id.control_level == "analytic_kpi"
+ lambda control: control.budget_period_id.control_level == "analytic_kpi"
):
for line in rec.line_ids:
# Filter according to budget_control parameter
@@ -235,23 +234,22 @@ def _check_budget_control_over_consumed(self):
)
if budget_info["amount_balance"] < 0:
raise UserError(
- _(
- "Total amount in KPI {line_name} will result in {amount:,.2f}"
- ).format(
- line_name=line.name, amount=budget_info["amount_balance"]
+ self.env._(
+ f"Total amount in KPI {line.name} will result in "
+ f"{budget_info['amount_balance']:,.2f}"
)
)
- @api.onchange("use_all_kpis")
- def _onchange_use_all_kpis(self):
- if self.use_all_kpis:
- self.template_line_ids = self.template_id.line_ids
- else:
- self.template_line_ids = False
+ @api.depends("use_all_kpis")
+ def _compute_template_line_ids(self):
+ for rec in self:
+ rec.template_line_ids = False
+ if rec.use_all_kpis:
+ rec.template_line_ids = rec.template_id.line_ids
def action_confirm_state(self):
return {
- "name": _("Confirmation"),
+ "name": self.env._("Confirmation"),
"type": "ir.actions.act_window",
"res_model": "budget.state.confirmation",
"view_mode": "form",
@@ -321,7 +319,9 @@ def _compute_budget_info(self):
def _get_lines_init_date(self):
self.ensure_one()
init_date = min(self.line_ids.mapped("date_from"))
- return self.line_ids.filtered(lambda l: l.date_from == init_date)
+ return self.line_ids.filtered(
+ lambda line, init_date=init_date: line.date_from == init_date
+ )
def do_init_budget_commit(self, init):
"""Initialize budget with current commitment amount."""
@@ -340,7 +340,7 @@ def do_init_budget_commit(self, init):
q["amount"]
for q in query_data
if q["amount"] is not None
- and q["amount_type"] not in ["1_budget", "8_actual"]
+ and q["amount_type"] not in ["10_budget", "80_actual"]
)
line.update({"amount": abs(balance_commit)})
@@ -360,7 +360,7 @@ def _check_budget_amount(self):
!= 0
):
raise UserError(
- _(
+ self.env._(
"Planning amount should equal to the "
"released amount {amount:,.2f} {symbol}"
).format(amount=rec.released_amount, symbol=rec.currency_id.symbol)
@@ -467,9 +467,9 @@ def action_view_monitoring(self):
ctx = self._get_context_budget_monitoring()
domain = self._get_domain_budget_monitoring()
return {
- "name": _("Budget Monitoring"),
+ "name": self.env._("Budget Monitoring"),
"res_model": "budget.monitor.report",
- "view_mode": "pivot,tree,graph",
+ "view_mode": "pivot,list,graph",
"domain": domain,
"context": ctx,
"type": "ir.actions.act_window",
@@ -492,34 +492,40 @@ def _compute_transfer_item_ids(self):
@api.depends("transfer_item_ids")
def _compute_transferred_amount(self):
+ result = defaultdict(float)
+ all_control_ids = self.ids
+ # Fetch only necessary fields instead of full records
+ transfer_items = self.env["budget.transfer.item"].search_read(
+ domain=[
+ ("state", "=", "transfer"),
+ "|",
+ ("budget_control_from_id", "in", all_control_ids),
+ ("budget_control_to_id", "in", all_control_ids),
+ ],
+ fields=["budget_control_from_id", "budget_control_to_id", "amount"],
+ )
+
+ # Process all transfers in one loop
+ for item in transfer_items:
+ amount = item.get("amount", 0.0)
+ result[item.get("budget_control_to_id")[0]] += amount
+ result[item.get("budget_control_from_id")[0]] -= amount
+
+ # Update computed fields
for rec in self:
- # Get the transfer items where the current budget control is the source
- from_transfer_items = rec.transfer_item_ids.filtered(
- lambda l: l.budget_control_from_id == rec
- )
- # Get the transfer items where the current budget control is the destination
- to_transfer_items = rec.transfer_item_ids - from_transfer_items
- # Calculate the total transferred amount by subtracting the amount transferred
- total_amount = sum(to_transfer_items.mapped("amount")) - sum(
- from_transfer_items.mapped("amount")
- )
- rec.transferred_amount = total_amount
+ rec.transferred_amount = result[rec.id] # Will be 0.0 if not found
def action_open_budget_transfer_item(self):
self.ensure_one()
ctx = self.env.context.copy()
- ctx.update({"create": False, "edit": False})
- items = self.transfer_item_ids
- list_view = self.env.ref("budget_control.view_budget_transfer_item_ref_tree").id
- form_view = self.env.ref("budget_control.view_budget_transfer_item_ref_form").id
+ ctx.update({"create": False, "edit": False, "show_transfer": 1})
return {
- "name": _("Budget Transfer Items"),
+ "name": self.env._("Budget Transfer Items"),
"type": "ir.actions.act_window",
"res_model": "budget.transfer.item",
- "views": [[list_view, "list"], [form_view, "form"]],
- "view_mode": "list",
+ "view_mode": "list,form",
"context": ctx,
- "domain": [("id", "in", items and items.ids or [])],
+ "domain": [("id", "in", self.transfer_item_ids.ids)],
}
@@ -534,17 +540,15 @@ class BudgetControlLine(models.Model):
index=True,
required=True,
)
- name = fields.Char(compute="_compute_name", required=False, readonly=True)
date_range_id = fields.Many2one(
comodel_name="date.range",
- string="Date range",
)
date_from = fields.Date(required=True, string="From")
date_to = fields.Date(required=True, string="To")
- analytic_account_id = fields.Many2one(
- comodel_name="account.analytic.account", string="Analytic account"
+ analytic_account_id = fields.Many2one(comodel_name="account.analytic.account")
+ amount = fields.Float(
+ digits="Budget Precision",
)
- amount = fields.Float()
template_line_id = fields.Many2one(
comodel_name="budget.template.line",
index=True,
@@ -559,28 +563,12 @@ class BudgetControlLine(models.Model):
readonly=True,
store=True,
)
- state = fields.Selection(
- [
- ("draft", "Draft"),
- ("submit", "Submitted"),
- ("done", "Controlled"),
- ("cancel", "Cancelled"),
- ],
- string="Status",
- compute="_compute_budget_control_state",
- store=True,
+ currency_id = fields.Many2one(
+ comodel_name="res.currency",
+ related="budget_control_id.currency_id",
index=True,
)
-
- @api.depends("kpi_id")
- def _compute_name(self):
- for rec in self:
- rec.name = rec.kpi_id.display_name
-
- @api.depends("budget_control_id.state")
- def _compute_budget_control_state(self):
- for rec in self:
- rec.state = rec.budget_control_id.state
+ state = fields.Selection(related="budget_control_id.state", store=True)
@api.depends("budget_control_id.active")
def _compute_active(self):
diff --git a/budget_control/models/budget_kpi.py b/budget_control/models/budget_kpi.py
index 04766897..4fb37f88 100644
--- a/budget_control/models/budget_kpi.py
+++ b/budget_control/models/budget_kpi.py
@@ -7,5 +7,6 @@
class BudgetKPI(models.Model):
_name = "budget.kpi"
_description = "Budget KPI"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char(required=True)
diff --git a/budget_control/models/budget_move_adjustment.py b/budget_control/models/budget_move_adjustment.py
index 3574e7cf..b60cd9ef 100644
--- a/budget_control/models/budget_move_adjustment.py
+++ b/budget_control/models/budget_move_adjustment.py
@@ -1,13 +1,13 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, fields, models
+from odoo import api, fields, models
from odoo.exceptions import UserError
class BudgetMoveAdjustment(models.Model):
_name = "budget.move.adjustment"
- _inherit = ["mail.thread"]
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Budget Moves Adjustment"
budget_move_ids = fields.One2many(
@@ -23,37 +23,27 @@ class BudgetMoveAdjustment(models.Model):
readonly=True,
)
description = fields.Text(
- readonly=True,
- states={"draft": [("readonly", False)]},
tracking=True,
)
adjust_item_ids = fields.One2many(
comodel_name="budget.move.adjustment.item",
inverse_name="adjust_id",
- readonly=True,
- states={"draft": [("readonly", False)]},
- tracking=True,
)
date_commit = fields.Date(
string="Budget Commit Date",
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
tracking=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
default=lambda self: self.env.company,
required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
index=True,
)
currency_id = fields.Many2one(
comodel_name="res.currency",
related="company_id.currency_id",
store=True,
- states={"draft": [("readonly", False)]},
)
state = fields.Selection(
[
@@ -81,15 +71,17 @@ def unlink(self):
"""Check that only records with state 'draft' can be deleted."""
if any(rec.state != "draft" for rec in self):
raise UserError(
- _("You are trying to delete a record that is still referenced!")
+ self.env._(
+ "You are trying to delete a record that is still referenced!"
+ )
)
return super().unlink()
def action_draft(self):
- self.write({"state": "draft"})
+ return self.write({"state": "draft"})
def action_cancel(self):
- self.write({"state": "cancel"})
+ return self.write({"state": "cancel"})
def action_adjust(self):
res = self.write({"state": "done"})
@@ -177,8 +169,9 @@ def _onchange_product_id(self):
@api.depends("amount")
def _compute_amount_balance(self):
- if self.filtered(lambda l: l.amount <= 0):
- raise UserError(_("Given amount must be positive"))
+ if any(rec.amount <= 0 for rec in self):
+ raise UserError(self.env._("Given amount must be positive"))
+
for rec in self:
# If the adjust type is 'release', negate the amount, else leave it as is
rec.amount = -rec.amount if rec.adjust_type == "release" else rec.amount
@@ -191,7 +184,7 @@ def recompute_budget_move(self):
def _init_docline_budget_vals(self, budget_vals, analytic_id):
self.ensure_one()
percent_analytic = self[self._budget_analytic_field].get(str(analytic_id))
- amount_budget = self.amount * percent_analytic / 100
+ amount_budget = self.amount * (percent_analytic / 100)
budget_vals["amount_currency"] = (
-amount_budget if self.adjust_type == "release" else amount_budget
)
diff --git a/budget_control/models/budget_period.py b/budget_control/models/budget_period.py
index 5da72418..607b9de3 100644
--- a/budget_control/models/budget_period.py
+++ b/budget_control/models/budget_period.py
@@ -1,50 +1,58 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from psycopg2 import sql
+# from psycopg2 import sql
from odoo import _, api, fields, models
-from odoo.exceptions import RedirectWarning, UserError, ValidationError
-from odoo.tools import float_compare, format_amount
+from odoo.exceptions import UserError, ValidationError
+from odoo.tools import SQL, float_compare, format_amount
class BudgetPeriod(models.Model):
_name = "budget.period"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "For each fiscal year, manage how budget is controlled"
- name = fields.Char(required=True)
+ name = fields.Char(required=True, tracking=True)
bm_date_from = fields.Date(
string="Date From",
required=True,
+ tracking=True,
)
bm_date_to = fields.Date(
string="Date To",
required=True,
+ tracking=True,
)
template_id = fields.Many2one(
comodel_name="budget.template",
string="Budget Template",
ondelete="restrict",
required=True,
+ tracking=True,
)
control_budget = fields.Boolean(
help="Block document transaction if budget is not enough",
+ tracking=True,
)
account = fields.Boolean(
string="On Account",
compute="_compute_control_account",
store=True,
readonly=False,
+ tracking=True,
help="Control budget on journal document(s), i.e., vendor bill",
)
control_all_analytic_accounts = fields.Boolean(
string="Control All Analytics",
default=True,
+ tracking=True,
)
control_analytic_account_ids = fields.Many2many(
comodel_name="account.analytic.account",
relation="budget_period_analytic_account_rel",
string="Controlled Analytics",
+ tracking=True,
)
control_level = fields.Selection(
selection=[
@@ -54,6 +62,7 @@ class BudgetPeriod(models.Model):
string="Level of Control",
required=True,
default="analytic",
+ tracking=True,
help="Level of budget check.\n"
"1. Based on Analytic Account only\n"
"2. Based on Analytic Account & KPI (more fine granied)",
@@ -62,17 +71,18 @@ class BudgetPeriod(models.Model):
comodel_name="date.range.type",
string="Plan Date Range",
required=True,
+ tracking=True,
help="Budget control sheet in this budget control year, will use this "
"data range to plan the budget.",
)
- analytic_ids = fields.One2many(
- comodel_name="account.analytic.account",
- inverse_name="budget_period_id",
- )
+ # analytic_ids = fields.One2many(
+ # comodel_name="account.analytic.account",
+ # inverse_name="budget_period_id",
+ # )
@api.model
- def default_get(self, field_list):
- res = super().default_get(field_list)
+ def default_get(self, default_fields):
+ res = super().default_get(default_fields)
res["template_id"] = self.env.company.budget_template_id.id
return res
@@ -81,47 +91,6 @@ def _compute_control_account(self):
for rec in self:
rec.account = rec.control_budget
- def _check_budget_period_date_range(self):
- self.ensure_one()
- range_from = self.env["date.range"].search(
- [
- ("date_start", "<=", self.bm_date_from),
- ("date_end", ">=", self.bm_date_from),
- ]
- )
- range_to = self.env["date.range"].search(
- [
- ("date_start", "<=", self.bm_date_to),
- ("date_end", ">=", self.bm_date_to),
- ]
- )
- if not range_from or not range_to:
- action = self.env.ref("date_range.date_range_generator_action")
- msg = (
- _(
- "There are no date ranges for the budget period, %s, yet.\n"
- "Please create date ranges that will cover this budget period."
- )
- % self.display_name
- )
- raise RedirectWarning(msg, action.id, _("Generate date range now"))
-
- def action_view_budget_control(self):
- """View all budget.control sharing same budget period."""
- self.ensure_one()
- action = self.env["ir.actions.act_window"]._for_xml_id(
- "budget_control.budget_control_action"
- )
- budget_controls = self.env["budget.control"].search(
- [("budget_period_id", "=", self.id)]
- )
- action.update(
- {
- "domain": [("id", "in", budget_controls.ids)],
- }
- )
- return action
-
@api.model
def check_budget_constraint(self, budget_constraints, doclines):
error_messages = []
@@ -156,8 +125,10 @@ def check_budget(self, doclines, doc_type="account"):
"""
Check the budget based on the input budget moves, i.e., account_move_line.
1. Get a valid budget period (how budget is being controlled).
- 2. Determine which account (KPI) and analytic to control based on (1) and doclines.
- 3. Check for negative budget and return warnings based on (2) and the KPI matrix.
+ 2. Determine which account (KPI) and analytic
+ to control based on (1) and doclines.
+ 3. Check for negative budget and return warnings
+ based on (2) and the KPI matrix.
"""
if self._context.get("force_no_budget_check"):
return
@@ -182,7 +153,7 @@ def check_budget(self, doclines, doc_type="account"):
!= 0
):
raise UserError(
- _(
+ self.env._(
"The total sum percent of Analytic Account must 100%. "
"Please check again."
)
@@ -194,11 +165,16 @@ def check_budget(self, doclines, doc_type="account"):
for aa in all_analytic_ids:
if isinstance(aa, int):
doclines = doclines.filtered(
- lambda l: l[doclines._budget_analytic_field].get(str(aa))
+ lambda line, aa=aa, doclines=doclines: line[
+ doclines._budget_analytic_field
+ ].get(str(aa))
)
else:
doclines = doclines.filtered(
- lambda l: l[doclines._budget_analytic_field] == aa
+ lambda line, aa=aa, doclines=doclines: line[
+ doclines._budget_analytic_field
+ ]
+ == aa
)
# Find active budget.period based on latest doclines date_commit
date_commit = doclines.filtered("date_commit").mapped("date_commit")
@@ -269,9 +245,9 @@ def check_budget_precommit(self, doclines, doc_type="account"):
for budget_move in budget_moves:
budget_move.unlink()
# Delete date commit from system create auto only
- doclines.filtered(lambda l: l.id in vals_date_commit).write(
- {"date_commit": False}
- )
+ doclines.filtered(
+ lambda line, vals_date_commit=vals_date_commit: line.id in vals_date_commit
+ ).write({"date_commit": False})
# Remove uncommit budget
if budget_moves_uncommit:
budget_moves_uncommit.unlink()
@@ -289,8 +265,9 @@ def check_over_returned_budget(self, docline, reverse=False):
if float_compare(amount_credit, amount_debit, 2) == 1:
docline.with_context(
use_amount_commit=True,
- commit_note=_("Over returned auto adjustment, %s")
- % docline.display_name,
+ commit_note=self.env._(
+ f"Over returned auto adjustment, {docline.display_name}"
+ ),
adj_commit=True,
).commit_budget(reverse=True)
@@ -307,11 +284,10 @@ def _get_eligible_budget_period(self, date=False, doc_type=False):
)
if budget_period and len(budget_period) > 1:
raise ValidationError(
- _(
- "Multiple Budget Periods found for date %s.\nPlease ensure "
+ self.env._(
+ f"Multiple Budget Periods found for date {date}.\nPlease ensure "
"there is only one Budget Period valid for this date."
)
- % date
)
if not doc_type:
return budget_period
@@ -319,8 +295,8 @@ def _get_eligible_budget_period(self, date=False, doc_type=False):
# if doctype is account, check special control too.
if doc_type == "account":
return budget_period.filtered(
- lambda l: (l.control_budget and l.account)
- or (not l.control_budget and l.account)
+ lambda bp: (bp.control_budget and bp.account)
+ or (not bp.control_budget and bp.account)
)
# Other module control budget must hook it for filter
return budget_period
@@ -332,8 +308,9 @@ def _prepare_controls(self, budget_period, doclines):
budget_moves = doclines.mapped(doclines._budget_field())
# Get budget moves from the period only
budget_moves_period = budget_moves.filtered(
- lambda l: l.date >= budget_period.bm_date_from
- and l.date <= budget_period.bm_date_to
+ lambda move, budget_period=budget_period: move.date
+ >= budget_period.bm_date_from
+ and move.date <= budget_period.bm_date_to
)
budget_control_key = self.env.company.budget_control_key
need_control = self.env.context.get("need_control")
@@ -365,7 +342,7 @@ def _get_filter_template_line(self, all_template_lines, control):
if budget_control_key == "account_id":
control_id = control[budget_control_key]
template_lines = all_template_lines.filtered(
- lambda l: control_id in l.account_ids.ids
+ lambda line, control_id=control_id: control_id in line.account_ids.ids
)
return template_lines
@@ -390,22 +367,23 @@ def _get_kpi_by_control_key(self, template_lines, control):
control, control_name = self._get_control_key_obj(control_key, control_id)
if not template_line:
raise UserError(
- _("Chosen %(name)s %(display_name)s is not valid in template")
- % ({"name": control_name, "display_name": control.display_name})
+ self.env._(
+ f"Chosen {control_name} {control.display_name} is not valid "
+ "in template"
+ )
)
raise UserError(
- _(
- "Template Lines has more than one KPI being "
- "referenced by the same %(name)s %(display_name)s"
+ self.env._(
+ f"Template Lines has more than one KPI being "
+ f"referenced by the same {control_name} {control.display_name}"
)
- % ({"name": control_name, "display_name": control.display_name})
)
def _get_where_domain(self, analytic_id, template_lines):
"""Return the WHERE clause for the budget monitoring query."""
if (
not template_lines
- or self._context.get("control_level", False) == "analytic"
+ or self.env.context.get("control_level", False) == "analytic"
):
return f"analytic_account_id = {analytic_id}"
kpi_domain = (
@@ -420,10 +398,14 @@ def _get_budget_monitor_report(self):
return self.env["budget.monitor.report"]
def _get_budget_avaiable(self, analytic_id, template_lines):
- self._cr.execute(
- sql.SQL(
- f"""SELECT * FROM ({self._get_budget_monitor_report()._table_query}) report
- WHERE {self._get_where_domain(analytic_id, template_lines)}"""
+ self.env.cr.execute(
+ SQL(
+ f"""
+ SELECT *
+ FROM (%s) report
+ WHERE {self._get_where_domain(analytic_id, template_lines)}
+ """,
+ SQL(self._get_budget_monitor_report()._table_query),
)
)
return self.env.cr.dictfetchall()
@@ -499,16 +481,22 @@ def get_budget_info_from_dataset(self, query, dataset):
budget_info = {col: 0 for col in query["info_cols"].keys()}
budget_info["amount_commit"] = 0
for col, (amount_type, is_commit) in query["info_cols"].items():
- info = list(filter(lambda l: l["amount_type"] == amount_type, dataset))
+ info = list(
+ filter(
+ lambda dataset, amount_type=amount_type: dataset["amount_type"]
+ == amount_type,
+ dataset,
+ )
+ )
if len(info) > 1:
- raise ValidationError(_("Error retrieving budget info!"))
+ raise ValidationError(self.env._("Error retrieving budget info!"))
if not info:
continue
amount = info[0]["amount"]
if is_commit:
budget_info[col] = -amount # Negate
budget_info["amount_commit"] += budget_info[col]
- elif amount_type == "8_actual": # Negate consumed
+ elif amount_type == "80_actual": # Negate consumed
budget_info[col] = -amount
else:
budget_info[col] = amount
@@ -524,10 +512,10 @@ def _budget_info_query(self):
query = {
"info_cols": {
"amount_budget": (
- "1_budget",
+ "10_budget",
False,
), # (amount_type, is_commit)
- "amount_actual": ("8_actual", False),
+ "amount_actual": ("80_actual", False),
},
"fields": [
"analytic_account_id",
diff --git a/budget_control/models/budget_plan.py b/budget_control/models/budget_plan.py
new file mode 100644
index 00000000..40e9606f
--- /dev/null
+++ b/budget_control/models/budget_plan.py
@@ -0,0 +1,298 @@
+from odoo import Command, api, fields, models
+from odoo.exceptions import UserError
+from odoo.tools import float_compare
+
+
+class BudgetPlan(models.Model):
+ _name = "budget.plan"
+ _description = "Budget Plan"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
+ _order = "id desc"
+
+ name = fields.Char(
+ required=True,
+ tracking=True,
+ )
+ budget_period_id = fields.Many2one(
+ comodel_name="budget.period",
+ required=True,
+ )
+ date_from = fields.Date(related="budget_period_id.bm_date_from")
+ date_to = fields.Date(related="budget_period_id.bm_date_to")
+ budget_control_ids = fields.One2many(
+ comodel_name="budget.control",
+ compute="_compute_budget_control",
+ )
+ budget_control_count = fields.Integer(
+ string="# of Budget Control",
+ compute="_compute_budget_control",
+ help="Count budget control in Plan",
+ )
+ total_amount = fields.Monetary(compute="_compute_total_amount")
+ company_id = fields.Many2one(
+ comodel_name="res.company",
+ default=lambda self: self.env.user.company_id,
+ required=False,
+ )
+ currency_id = fields.Many2one(
+ comodel_name="res.currency", related="company_id.currency_id"
+ )
+ line_ids = fields.One2many(
+ comodel_name="budget.plan.line",
+ inverse_name="plan_id",
+ copy=True,
+ context={"active_test": False},
+ )
+ active = fields.Boolean(default=True)
+ state = fields.Selection(
+ [
+ ("draft", "Draft"),
+ ("confirm", "Confirmed"),
+ ("done", "Done"),
+ ("cancel", "Cancelled"),
+ ],
+ default="draft",
+ tracking=True,
+ )
+
+ @api.depends("line_ids")
+ def _compute_total_amount(self):
+ for rec in self:
+ rec.total_amount = sum(rec.line_ids.mapped("amount"))
+
+ @api.depends("line_ids")
+ def _compute_budget_control(self):
+ """Find all budget controls of the same period"""
+ for rec in self.with_context(active_test=False).sudo():
+ rec.budget_control_ids = rec.line_ids.mapped("budget_control_ids")
+ rec.budget_control_count = len(rec.line_ids.mapped("budget_control_ids"))
+
+ def button_open_budget_control(self):
+ self.ensure_one()
+ # Get budget controls in one query with proper context
+ budget_controls = self.with_context(
+ create=False,
+ active_test=False,
+ search_default_current_period=False,
+ ).budget_control_ids
+
+ action = {
+ "name": self.env._("Budget Control Sheet"),
+ "type": "ir.actions.act_window",
+ "res_model": "budget.control",
+ "view_mode": "list,form",
+ "domain": [("id", "in", budget_controls.ids)],
+ }
+ return action
+
+ def _prepare_budget_control_sheet(self, analytic_plan, **kwargs):
+ self.ensure_one()
+ plan_date_range_id = self.budget_period_id.plan_date_range_type_id.id
+ currency_id = self.currency_id.id
+ budget_period = self.budget_period_id
+ # Additional params
+ template_lines = kwargs.get("template_lines", [])
+ use_all_kpis = kwargs.get("use_all_kpis", False)
+ return [
+ {
+ "analytic_account_id": x.id,
+ "name": f"{budget_period.name} :: {x.name}",
+ "plan_date_range_type_id": plan_date_range_id,
+ "use_all_kpis": use_all_kpis,
+ "template_line_ids": template_lines,
+ "budget_period_id": budget_period.id,
+ "currency_id": currency_id,
+ }
+ for x in analytic_plan
+ ]
+
+ def _create_budget_controls(self, vals):
+ return self.env["budget.control"].create(vals)
+
+ def _update_budget_control_values(self):
+ plan_line = self.line_ids.with_context(active_test=False)
+ dp = self.currency_id.decimal_places
+ for line in plan_line:
+ budget_control = line.budget_control_ids.filtered("active")
+ if not budget_control:
+ budget_control = line.budget_control_ids.sorted("id")[-1:]
+ if (
+ float_compare(
+ budget_control.allocated_amount,
+ line.allocated_amount,
+ precision_digits=dp,
+ )
+ != 0
+ or budget_control.active != line.active_status
+ ):
+ budget_control.action_draft()
+ budget_control.write(
+ {
+ "allocated_amount": line.allocated_amount,
+ "active": line.active_status,
+ }
+ )
+ return True
+
+ def action_create_update_budget_control(self):
+ self.ensure_one()
+ analytic_plan = self.line_ids.mapped("analytic_account_id")
+ # Skip if budget control already exists
+ existing_budget_controls = self.with_context(
+ active_test=False
+ ).budget_control_ids
+ existing_analytics = existing_budget_controls.mapped("analytic_account_id")
+ new_analytic = analytic_plan - existing_analytics
+
+ # Create new budget control if new plan line is added
+ if new_analytic:
+ # Prepare budget control
+ value_bc = self._prepare_budget_control_sheet(new_analytic)
+ # Create budget controls that are not already exists
+ new_budget_controls = self._create_budget_controls(value_bc)
+
+ new_budget_controls.prepare_budget_control_matrix()
+
+ # Update budget control values
+ self._update_budget_control_values()
+
+ return {
+ "type": "ir.actions.client",
+ "tag": "display_notification",
+ "params": {
+ "type": "success",
+ "message": self.env._("Budget Control has been updated!"),
+ "next": {"type": "ir.actions.act_window_close"},
+ },
+ }
+
+ def check_plan_consumed(self):
+ prec_digits = self.currency_id.decimal_places
+ for line in self.mapped("line_ids"):
+ # Check amount + transferred is less than the amount consumed
+ if (
+ float_compare(
+ line.amount + line.budget_control_ids.transferred_amount,
+ line.amount_consumed,
+ precision_digits=prec_digits,
+ )
+ == -1
+ ):
+ raise UserError(
+ self.env._(
+ f"{line.analytic_account_id.display_name} "
+ f"has amount less than consumed."
+ )
+ )
+ line.allocated_amount = line.released_amount = line.amount
+
+ def action_update_amount_consumed(self):
+ """Update amount consumed and released from budget control"""
+ for rec in self:
+ for line in rec.line_ids:
+ # find consumed amount from budget control
+ active_control = line.budget_control_ids
+ if not active_control:
+ continue
+
+ if len(active_control) > 1:
+ raise UserError(
+ self.env._(
+ f"{line.analytic_account_id.display_name} should have "
+ f"only 1 active budget control"
+ )
+ )
+ line.amount_consumed = active_control.amount_consumed
+ line.released_amount = active_control.released_amount
+
+ def action_update_plan(self):
+ """Update plan line is not in plan line"""
+ Analytic = self.env["account.analytic.account"]
+ for rec in self:
+ plan_analytic = rec.line_ids.mapped("analytic_account_id")
+ # search analytic is not add in plan line
+ new_analytic = Analytic.search(
+ [
+ ("bm_date_from", "<=", rec.date_to),
+ ("bm_date_to", ">=", rec.date_from),
+ ("id", "not in", plan_analytic.ids),
+ ]
+ )
+ lines = []
+ for new_aa in new_analytic:
+ active_control = new_aa.budget_control_ids.filtered(
+ lambda control, rec=rec: control.budget_period_id
+ == rec.budget_period_id
+ )
+ lines.append(
+ Command.create(
+ {
+ "analytic_account_id": new_aa.id,
+ "amount_consumed": active_control.amount_consumed,
+ "released_amount": active_control.released_amount,
+ },
+ )
+ )
+ rec.write({"line_ids": lines})
+
+ def action_confirm(self):
+ # Update amount consumed and released
+ self.action_update_amount_consumed()
+ # Update plan line
+ self.action_update_plan()
+ # Check plan consumed
+ self.check_plan_consumed()
+ return self.write({"state": "confirm"})
+
+ def action_done(self):
+ return self.write({"state": "done"})
+
+ def action_cancel(self):
+ return self.write({"state": "cancel"})
+
+ def action_draft(self):
+ return self.write({"state": "draft"})
+
+
+class BudgetPlanLine(models.Model):
+ _name = "budget.plan.line"
+ _description = "Budget Plan Line"
+
+ plan_id = fields.Many2one(
+ comodel_name="budget.plan",
+ index=True,
+ ondelete="cascade",
+ )
+ budget_control_ids = fields.Many2many(
+ comodel_name="budget.control",
+ string="Related Budget Control(s)",
+ compute="_compute_budget_control_ids",
+ help="Note: It is intention for this field to compute in realtime",
+ )
+ budget_period_id = fields.Many2one(
+ comodel_name="budget.period", related="plan_id.budget_period_id"
+ )
+ date_from = fields.Date(related="plan_id.date_from")
+ date_to = fields.Date(related="plan_id.date_to")
+ analytic_account_id = fields.Many2one(
+ comodel_name="account.analytic.account",
+ required=True,
+ )
+ allocated_amount = fields.Monetary(string="Allocated")
+ released_amount = fields.Monetary(string="Released")
+ amount = fields.Monetary(string="New Amount")
+ amount_consumed = fields.Monetary(string="Consumed")
+ company_id = fields.Many2one(
+ comodel_name="res.company", related="plan_id.company_id"
+ )
+ currency_id = fields.Many2one(
+ comodel_name="res.currency", related="plan_id.currency_id"
+ )
+ active_status = fields.Boolean(
+ default=True, help="Activate/Deactivate when create/Update Budget Control"
+ )
+
+ @api.depends("analytic_account_id.budget_control_ids")
+ def _compute_budget_control_ids(self):
+ for rec in self.sudo():
+ rec.budget_control_ids = rec.analytic_account_id.budget_control_ids
diff --git a/budget_control/models/budget_template.py b/budget_control/models/budget_template.py
index 52cc9f2f..8346bb46 100644
--- a/budget_control/models/budget_template.py
+++ b/budget_control/models/budget_template.py
@@ -7,6 +7,7 @@
class BudgetTemplate(models.Model):
_name = "budget.template"
_description = "Budget Template"
+ _inherit = ["mail.thread", "mail.activity.mixin"]
name = fields.Char(required=True)
line_ids = fields.One2many(
diff --git a/budget_control/models/budget_transfer.py b/budget_control/models/budget_transfer.py
index ed0c56cd..598716e5 100644
--- a/budget_control/models/budget_transfer.py
+++ b/budget_control/models/budget_transfer.py
@@ -1,13 +1,14 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import _, api, fields, models
+from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
+from odoo.tools.float_utils import float_compare
class BudgetTransfer(models.Model):
_name = "budget.transfer"
- _inherit = ["mail.thread"]
+ _inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Budget Transfer"
name = fields.Char(
@@ -15,21 +16,16 @@ class BudgetTransfer(models.Model):
index=True,
copy=False,
required=True,
- readonly=True,
)
budget_period_id = fields.Many2one(
comodel_name="budget.period",
- string="Budget Year",
default=lambda self: self.env["budget.period"]._get_eligible_budget_period(),
required=True,
- readonly=True,
)
transfer_item_ids = fields.One2many(
comodel_name="budget.transfer.item",
inverse_name="transfer_id",
- readonly=True,
copy=True,
- states={"draft": [("readonly", False)]},
)
state = fields.Selection(
[
@@ -57,30 +53,33 @@ def unlink(self):
"""Check state draft can delete only."""
if any(rec.state != "draft" for rec in self):
raise UserError(
- _("You are trying to delete a record that is still referenced!")
+ self.env._(
+ "You are trying to delete a record that is still referenced!"
+ )
)
return super().unlink()
def action_cancel(self):
- self.write({"state": "cancel"})
+ return self.write({"state": "cancel"})
def action_submit(self):
item_ids = self.mapped("transfer_item_ids")
if not item_ids:
- raise UserError(_("You need to add a line before submit."))
+ raise UserError(self.env._("You need to add a line before submit."))
+
for transfer in item_ids:
transfer._check_constraint_transfer()
- self.write({"state": "submit"})
+ return self.write({"state": "submit"})
def action_transfer(self):
self.mapped("transfer_item_ids").transfer()
self._check_budget_control()
- self.write({"state": "transfer"})
+ return self.write({"state": "transfer"})
def action_reverse(self):
self.mapped("transfer_item_ids").reverse()
self._check_budget_control()
- self.write({"state": "reverse"})
+ return self.write({"state": "reverse"})
def _check_budget_available_analytic(self, budget_controls):
BudgetPeriod = self.env["budget.period"]
@@ -89,9 +88,18 @@ def _check_budget_available_analytic(self, budget_controls):
budget_ctrl.analytic_account_id.id, budget_ctrl.template_line_ids
)
balance = sum(q["amount"] for q in query_data if q["amount"] is not None)
- if balance < 0.0:
+ if (
+ float_compare(
+ balance,
+ 0.0,
+ precision_rounding=budget_ctrl.currency_id.rounding,
+ )
+ == -1
+ ):
raise ValidationError(
- _("This transfer will result in negative budget balance for %s")
+ self.env._(
+ "This transfer will result in negative budget balance for %s"
+ )
% budget_ctrl.name
)
return True
diff --git a/budget_control/models/budget_transfer_item.py b/budget_control/models/budget_transfer_item.py
index d522614c..f298f811 100644
--- a/budget_control/models/budget_transfer_item.py
+++ b/budget_control/models/budget_transfer_item.py
@@ -22,35 +22,29 @@ class BudgetTransferItem(models.Model):
budget_control_from_id = fields.Many2one(
comodel_name="budget.control",
string="From",
- domain="[('budget_period_id', '=', budget_period_id)]",
required=True,
index=True,
)
budget_control_to_id = fields.Many2one(
comodel_name="budget.control",
string="To",
- domain="[('budget_period_id', '=', budget_period_id)]",
required=True,
index=True,
)
amount_from_available = fields.Float(
compute="_compute_amount_available",
store="True",
- readonly=True,
)
amount_to_available = fields.Float(
compute="_compute_amount_available",
store="True",
- readonly=True,
)
state_from = fields.Selection(
related="budget_control_from_id.state",
- string="State From",
store=True,
)
state_to = fields.Selection(
related="budget_control_to_id.state",
- string="State To",
store=True,
)
amount = fields.Float(
@@ -62,26 +56,35 @@ class BudgetTransferItem(models.Model):
)
state = fields.Selection(related="transfer_id.state", store=True)
- def _get_budget_control_transfer(self):
- from_budget_ctrl = self.budget_control_from_id
- to_budget_ctrl = self.budget_control_to_id
- return from_budget_ctrl, to_budget_ctrl
+ def _add_context(self):
+ """Add context to self"""
+ return self
- @api.depends("budget_control_from_id", "budget_control_to_id")
+ @api.depends(
+ "budget_control_from_id.amount_balance", "budget_control_to_id.amount_balance"
+ )
def _compute_amount_available(self):
+ """Compute available amounts for budget transfer"""
+ budget_controls = self.mapped("budget_control_from_id") + self.mapped(
+ "budget_control_to_id"
+ )
+ budget_balances = {bc.id: bc.amount_balance for bc in budget_controls}
+
for transfer in self:
- (
- from_budget_ctrl,
- to_budget_ctrl,
- ) = transfer._get_budget_control_transfer()
- transfer.amount_from_available = from_budget_ctrl.amount_balance
- transfer.amount_to_available = to_budget_ctrl.amount_balance
+ from_budget_ctrl, to_budget_ctrl = (
+ transfer.budget_control_from_id,
+ transfer.budget_control_to_id,
+ )
+ transfer.amount_from_available = budget_balances.get(
+ from_budget_ctrl.id, 0.0
+ )
+ transfer.amount_to_available = budget_balances.get(to_budget_ctrl.id, 0.0)
def _check_constraint_transfer(self):
self.ensure_one()
if self.budget_control_from_id == self.budget_control_to_id:
raise UserError(
- _("You can not transfer from the same budget control sheet!")
+ self.env._("You can not transfer from the same budget control sheet!")
)
# check amount transfer must be positive
if (
@@ -90,9 +93,9 @@ def _check_constraint_transfer(self):
0.0,
precision_rounding=self.currency_id.rounding,
)
- != 1
+ <= 0
):
- raise UserError(_("Transfer amount must be positive!"))
+ raise UserError(self.env._("Transfer amount must be positive!"))
# check amount transfer must less than amount available (source budget)
if (
float_compare(
@@ -100,10 +103,10 @@ def _check_constraint_transfer(self):
self.amount_from_available,
precision_rounding=self.currency_id.rounding,
)
- == 1
+ > 0
):
raise UserError(
- _("Transfer amount can not be exceeded {:,.2f}").format(
+ self.env._("Transfer amount can not be exceeded {:,.2f}").format(
self.amount_from_available
)
)
@@ -111,12 +114,17 @@ def _check_constraint_transfer(self):
def transfer(self):
for transfer in self:
transfer._check_constraint_transfer()
+ # Update released amounts
transfer.budget_control_from_id.released_amount -= transfer.amount
+ # Check if released amount is negative
+ if transfer.budget_control_from_id.released_amount < 0:
+ raise ValidationError(
+ _("Negative balance for {} after transfer!").format(
+ transfer.budget_control_from_id.display_name
+ )
+ )
+
transfer.budget_control_to_id.released_amount += transfer.amount
- # Final check
- from_amounts = self.mapped("budget_control_from_id.released_amount")
- if list(filter(lambda a: a < 0, from_amounts)):
- raise ValidationError(_("Negative from amount after transfer!"))
def reverse(self):
for transfer in self:
@@ -147,7 +155,7 @@ def _check_state(self):
budget_not_draft = ", ".join(budget_not_draft.mapped("name"))
if is_state_transfer_valid and budget_not_draft:
raise UserError(
- _(
+ self.env._(
"Following budget controls must be in state 'Draft', "
"before transferring.\n{}"
).format(budget_not_draft)
diff --git a/budget_control/models/res_company.py b/budget_control/models/res_company.py
index a43f11bd..4f00b4c6 100644
--- a/budget_control/models/res_company.py
+++ b/budget_control/models/res_company.py
@@ -8,7 +8,6 @@ class ResCompany(models.Model):
_inherit = "res.company"
budget_include_tax = fields.Boolean(
- string="Budget Included Tax",
help="If checked, all budget moves amount will include tax",
)
budget_include_tax_method = fields.Selection(
@@ -38,7 +37,7 @@ class ResCompany(models.Model):
)
budget_template_id = fields.Many2one(
comodel_name="budget.template",
- string="Budget Template",
+ help="Budget Template to be used for this company",
)
budget_control_key = fields.Selection(
selection=[("account_id", "Account")],
diff --git a/budget_control/models/res_config_settings.py b/budget_control/models/res_config_settings.py
index 99cf98fe..fe80086a 100644
--- a/budget_control/models/res_config_settings.py
+++ b/budget_control/models/res_config_settings.py
@@ -51,4 +51,3 @@ class ResConfigSettings(models.TransientModel):
module_budget_control_purchase = fields.Boolean(string="Purchase")
module_budget_control_expense = fields.Boolean(string="Expense")
module_budget_control_advance_clearing = fields.Boolean(string="Advance/Clearing")
- module_budget_plan = fields.Boolean(string="Budget Plan")
diff --git a/budget_control/report/budget_monitor_report.py b/budget_control/report/budget_monitor_report.py
index 73936445..b3041a8d 100644
--- a/budget_control/report/budget_monitor_report.py
+++ b/budget_control/report/budget_monitor_report.py
@@ -1,7 +1,8 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import fields, models
+from odoo import api, fields, models
+from odoo.tools import SQL
class BudgetMonitorReport(models.Model):
@@ -31,7 +32,7 @@ class BudgetMonitorReport(models.Model):
date = fields.Date()
amount = fields.Float()
amount_type = fields.Selection(
- selection=lambda self: [("1_budget", "Budget")]
+ selection=lambda self: [("10_budget", "Budget")]
+ self._get_budget_amount_type(),
string="Type",
)
@@ -56,22 +57,61 @@ class BudgetMonitorReport(models.Model):
active = fields.Boolean()
@property
- def _table_query(self):
- return f"""
- select a.*, p.id as budget_period_id
- from ({self._get_sql()}) a
- left outer join date_range d
- on a.date between d.date_start and d.date_end
- left outer join budget_period p
- on a.date between p.bm_date_from and p.bm_date_to
- {self._get_where_clause()}
- """
+ def _table_query(self) -> SQL:
+ return SQL("%s %s %s", self._select(), self._from(), self._where())
+
+ @api.model
+ def _select(self) -> SQL:
+ return SQL(
+ """SELECT a.*, p.id AS budget_period_id""",
+ )
+
+ @api.model
+ def _from(self) -> SQL:
+ return SQL(
+ """
+ FROM (%(table)s) a
+ LEFT JOIN budget_period p ON a.date between p.bm_date_from AND p.bm_date_to
+ LEFT JOIN date_range d ON a.date between d.date_start AND d.date_end
+ AND d.type_id = p.plan_date_range_type_id
+ """,
+ table=self._get_sql(),
+ )
+
+ @api.model
+ def _where(self) -> SQL:
+ return SQL("")
+
+ @api.model
+ def _get_sql(self) -> SQL:
+ select_budget_query = self._select_budget()
+ key_select_budget_list = sorted(select_budget_query.keys())
+ select_budget = ", ".join(
+ select_budget_query[x] for x in key_select_budget_list
+ )
+ select_actual_query = self._select_statement("80_actual")
+ key_select_actual_list = sorted(select_budget_query.keys())
+ select_actual = ", ".join(
+ select_actual_query[x] for x in key_select_actual_list
+ )
+ return SQL(
+ """
+ (SELECT %(select_budget)s %(from_budget)s)
+ UNION ALL
+ (SELECT %(select_actual)s %(from_actual)s %(where_actual)s)
+ """,
+ select_budget=SQL(select_budget),
+ from_budget=self._from_budget(),
+ select_actual=SQL(select_actual),
+ from_actual=self._from_statement("80_actual"),
+ where_actual=self._where_actual(),
+ )
def _get_consumed_sources(self):
return [
{
"model": ("account.move.line", "Account Move Line"),
- "type": ("8_actual", "Actual"),
+ "type": ("80_actual", "Actual"),
"budget_move": ("account_budget_move", "move_line_id"),
"source_doc": ("account_move", "move_id"),
}
@@ -89,17 +129,17 @@ def _get_select_amount_types(self):
sql_select = {}
for source in self._get_consumed_sources():
res_model = source["model"][0] # i.e., account.move.line
- amount_type = source["type"][0] # i.e., 8_actual
+ amount_type = source["type"][0] # i.e., 80_actual
res_field = source["budget_move"][1] # i.e., move_line_id
sql_select[amount_type] = {
- 0: """
- %s000000000 + a.id as id,
- '%s,' || a.%s as res_id,
+ 0: f"""
+ {amount_type[:1]}000000000 + a.id as id,
+ '{res_model},' || a.{res_field} as res_id,
a.kpi_id,
a.analytic_account_id,
a.analytic_plan,
a.date as date,
- '%s' as amount_type,
+ '{amount_type}' as amount_type,
a.credit-a.debit as amount,
a.product_id,
a.account_id,
@@ -109,7 +149,6 @@ def _get_select_amount_types(self):
a.fwd_commit,
1::boolean as active
"""
- % (amount_type[:1], res_model, res_field, amount_type)
}
return sql_select
@@ -119,10 +158,10 @@ def _get_from_amount_types(self):
budget_table = source["budget_move"][0] # i.e., account_budget_move
doc_table = source["source_doc"][0] # i.e., account_move
doc_field = source["source_doc"][1] # i.e., move_id
- amount_type = source["type"][0] # i.e., 8_actual
+ amount_type = source["type"][0] # i.e., 80_actual
sql_from[amount_type] = f"""
- from {budget_table} a
- left outer join {doc_table} b on a.{doc_field} = b.id
+ FROM {budget_table} a
+ LEFT OUTER JOIN {doc_table} b ON a.{doc_field} = b.id
"""
return sql_from
@@ -135,7 +174,7 @@ def _select_budget(self):
a.analytic_account_id,
b.analytic_plan,
a.date_to as date, -- approx date
- '1_budget' as amount_type,
+ '10_budget' as amount_type,
a.amount as amount,
null::integer as product_id,
null::integer as account_id,
@@ -147,40 +186,23 @@ def _select_budget(self):
"""
}
- def _from_budget(self):
- return """
- from budget_control_line a
- join budget_control b on a.budget_control_id = b.id
- and b.active = true
- """
+ @api.model
+ def _from_budget(self) -> SQL:
+ return SQL(
+ """
+ FROM budget_control_line a
+ INNER JOIN budget_control b ON a.budget_control_id = b.id
+ WHERE b.active = TRUE
+ """,
+ )
def _select_statement(self, amount_type):
return self._get_select_amount_types()[amount_type]
- def _from_statement(self, amount_type):
- return self._get_from_amount_types()[amount_type]
-
- def _where_actual(self):
- return ""
-
- def _get_sql(self):
- select_budget_query = self._select_budget()
- key_select_budget_list = sorted(select_budget_query.keys())
- select_budget = ", ".join(
- select_budget_query[x] for x in key_select_budget_list
- )
- select_actual_query = self._select_statement("8_actual")
- key_select_actual_list = sorted(select_budget_query.keys())
- select_actual = ", ".join(
- select_actual_query[x] for x in key_select_actual_list
- )
- return "(select {} {}) union (select {} {} {})".format(
- select_budget,
- self._from_budget(),
- select_actual,
- self._from_statement("8_actual"),
- self._where_actual(),
- )
+ @api.model
+ def _from_statement(self, amount_type) -> SQL:
+ return SQL(self._get_from_amount_types()[amount_type])
- def _get_where_clause(self):
- return "where d.type_id = p.plan_date_range_type_id"
+ @api.model
+ def _where_actual(self) -> SQL:
+ return SQL("")
diff --git a/budget_control/report/budget_monitor_report_view.xml b/budget_control/report/budget_monitor_report_view.xml
index eeef1f5b..351a4065 100644
--- a/budget_control/report/budget_monitor_report_view.xml
+++ b/budget_control/report/budget_monitor_report_view.xml
@@ -1,10 +1,10 @@
-
- budget.monitor.report.tree
+
+ budget.monitor.report.listbudget.monitor.report
-
+
@@ -13,7 +13,7 @@
-
+
@@ -113,7 +113,7 @@
Budget Monitoringbudget.monitor.report
- pivot,tree,graph
+ pivot,list,graph{
'group_by':[],
'group_by_no_leaf':1,
diff --git a/budget_control/report/budget_move_views.xml b/budget_control/report/budget_move_views.xml
index 90b5bdc7..82f3282c 100644
--- a/budget_control/report/budget_move_views.xml
+++ b/budget_control/report/budget_move_views.xml
@@ -1,10 +1,10 @@
-
- account.budget.move.tree
+
+ account.budget.move.listaccount.budget.move
-
+
@@ -15,7 +15,7 @@
-
+
@@ -82,7 +82,7 @@
Actual Budget Commitmentaccount.budget.move
- pivot,tree
+ pivot,list
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.
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.