diff --git a/budget_control/__manifest__.py b/budget_control/__manifest__.py
index 6728d923..0061906f 100644
--- a/budget_control/__manifest__.py
+++ b/budget_control/__manifest__.py
@@ -17,6 +17,7 @@
"data": [
"data/budget_data.xml",
"data/sequence_data.xml",
+ "security/analytic_security.xml",
"security/budget_control_security_groups.xml",
"security/budget_control_rules.xml",
"security/ir.model.access.csv",
diff --git a/budget_control/models/analytic_account.py b/budget_control/models/analytic_account.py
index ba5259e4..b6c9a4c6 100644
--- a/budget_control/models/analytic_account.py
+++ b/budget_control/models/analytic_account.py
@@ -42,6 +42,14 @@ class AccountAnalyticAccount(models.Model):
tracking=True,
help="Budget commit date must conform with this date",
)
+ budget_company_ids = fields.Many2many(
+ comodel_name="res.company",
+ compute="_compute_budget_company",
+ store=True,
+ readonly=False,
+ string="Allowed Budget Companies",
+ help="Companies that this analytic account is allowed to use",
+ )
auto_adjust_date_commit = fields.Boolean(
string="Auto Adjust Commit Date",
default=True,
@@ -79,6 +87,26 @@ class AccountAnalyticAccount(models.Model):
help="Initial Balance from carry forward commitment",
)
+ @api.depends("company_id")
+ def _compute_budget_company(self):
+ for rec in self:
+ rec.budget_company_ids = rec.company_id
+
+ @api.constrains("company_id", "budget_company_ids")
+ def _check_budget_company(self):
+ """
+ If analytic account is in company,
+ then it must be in Allowed Budget Companies only
+ """
+ for rec in self:
+ if not rec.company_id:
+ continue
+
+ if rec.company_id and rec.company_id != rec.budget_company_ids:
+ raise UserError(
+ _("Analytic Account Company must be in Allowed Budget Companies")
+ )
+
@api.depends("name", "budget_period_id")
def _compute_name_with_budget_period(self):
for rec in self:
diff --git a/budget_control/models/base_budget_move.py b/budget_control/models/base_budget_move.py
index 6774762f..d5ccc190 100644
--- a/budget_control/models/base_budget_move.py
+++ b/budget_control/models/base_budget_move.py
@@ -341,7 +341,7 @@ def _update_budget_commitment(self, budget_vals, reverse=False):
"amount_currency": budget_vals["amount_currency"],
"debit": not reverse and amount or 0,
"credit": reverse and amount or 0,
- "company_id": company.id,
+ "company_id": self[self._doc_rel].company_id.id, # Document company
}
if sum([res["debit"], res["credit"]]) < 0:
res["debit"], res["credit"] = abs(res["credit"]), abs(res["debit"])
diff --git a/budget_control/models/budget_control.py b/budget_control/models/budget_control.py
index f98d3843..493bc3b7 100644
--- a/budget_control/models/budget_control.py
+++ b/budget_control/models/budget_control.py
@@ -86,23 +86,28 @@ class BudgetControl(models.Model):
help="If checked, the newly created budget control sheet will has "
"initial budget equal to current budget commitment of its year.",
)
- company_id = fields.Many2one(
+ company_ids = fields.Many2many(
comodel_name="res.company",
- string="Company",
- default=lambda self: self.env.company,
- required=True,
- readonly=True,
- states={"draft": [("readonly", False)]},
+ 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,
+ readonly=True,
)
allocated_amount = fields.Monetary(
string="Allocated",
- help="Initial total amount for plan",
tracking=True,
readonly=True,
states={"draft": [("readonly", False)]},
+ help="Initial total amount for plan",
)
released_amount = fields.Monetary(
string="Released",
diff --git a/budget_control/models/budget_move_adjustment.py b/budget_control/models/budget_move_adjustment.py
index 722718e5..5ba54445 100644
--- a/budget_control/models/budget_move_adjustment.py
+++ b/budget_control/models/budget_move_adjustment.py
@@ -41,6 +41,11 @@ 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,
+ )
currency_id = fields.Many2one(
comodel_name="res.currency",
default=lambda self: self.env.user.company_id.currency_id,
diff --git a/budget_control/report/budget_monitor_report.py b/budget_control/report/budget_monitor_report.py
index cef6fa20..e4d778dc 100644
--- a/budget_control/report/budget_monitor_report.py
+++ b/budget_control/report/budget_monitor_report.py
@@ -53,6 +53,7 @@ class BudgetMonitorReport(models.Model):
],
)
fwd_commit = fields.Boolean()
+ companies = fields.Char()
active = fields.Boolean()
@property
@@ -109,6 +110,7 @@ def _get_select_amount_types(self):
a.source_document as source_document,
null::char as budget_state,
a.fwd_commit,
+ c.name::text AS companies,
1::boolean as active
"""
% (amount_type[:2], res_model, res_field, amount_type)
@@ -127,6 +129,7 @@ def _get_from_amount_types(self):
] = """
from {} a
left outer join {} b on a.{} = b.id
+ left outer join res_company c on b.company_id = c.id
""".format(
budget_table,
doc_table,
@@ -151,6 +154,7 @@ def _select_budget(self):
null::char as source_document,
b.state as budget_state,
0::boolean as fwd_commit,
+ string_agg(d.name::text, ', ') AS companies,
a.active as active
"""
}
@@ -158,8 +162,38 @@ 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
+ join budget_control b
+ on a.budget_control_id = b.id
+ left join budget_control_company_rel c
+ on b.id = c.budget_control_id
+ left join res_company d on d.id = c.company_id
+ """
+
+ def _where_budget(self):
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return "where b.active = true"
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where b.active = true and (c.company_id in {} or c.company_id is null)".format(
+ companies
+ )
+
+ def _groupby_budget(self):
+ return """
+ group by
+ a.id,
+ a.kpi_id,
+ a.analytic_account_id,
+ b.analytic_group,
+ a.date_to,
+ a.amount,
+ b.name,
+ b.state,
+ a.active
"""
def _select_statement(self, amount_type):
@@ -169,7 +203,15 @@ def _from_statement(self, amount_type):
return self._get_from_amount_types()[amount_type]
def _where_actual(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_budget_query = self._select_budget()
@@ -182,9 +224,11 @@ def _get_sql(self):
select_actual = ", ".join(
select_actual_query[x] for x in key_select_actual_list
)
- return "(select {} {}) union (select {} {} {})".format(
+ return "(select {} {} {} {}) union (select {} {} {})".format(
select_budget,
self._from_budget(),
+ self._where_budget(),
+ self._groupby_budget(),
select_actual,
self._from_statement("80_actual"),
self._where_actual(),
diff --git a/budget_control/report/budget_monitor_report_view.xml b/budget_control/report/budget_monitor_report_view.xml
index 8b79481b..372d9214 100644
--- a/budget_control/report/budget_monitor_report_view.xml
+++ b/budget_control/report/budget_monitor_report_view.xml
@@ -106,6 +106,12 @@
name="group_by_date"
context="{'group_by':'date'}"
/>
+
+
diff --git a/budget_control/security/analytic_security.xml b/budget_control/security/analytic_security.xml
new file mode 100644
index 00000000..0d9b9aac
--- /dev/null
+++ b/budget_control/security/analytic_security.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/budget_control/security/budget_control_rules.xml b/budget_control/security/budget_control_rules.xml
index 746fddea..cff5fa4f 100644
--- a/budget_control/security/budget_control_rules.xml
+++ b/budget_control/security/budget_control_rules.xml
@@ -28,4 +28,24 @@
+
+ Budget Control multi-company
+
+
+
+ [
+ '|',
+ ('company_ids', '=', False),
+ ('company_ids', 'in', company_ids)
+ ]
+
+
+
+ Budget Adjustment multi-company
+
+
+
+ ['|',('company_id','=',False),('company_id','in',company_ids)]
+
+
diff --git a/budget_control/views/analytic_account_views.xml b/budget_control/views/analytic_account_views.xml
index 3ffe6ad4..ea7540b4 100644
--- a/budget_control/views/analytic_account_views.xml
+++ b/budget_control/views/analytic_account_views.xml
@@ -49,6 +49,13 @@
+
+
+
@@ -89,6 +96,12 @@
groups="base.group_multi_company"
optional="show"
/>
+
+
+
+
@@ -212,8 +225,9 @@
+
@@ -39,6 +45,11 @@
+
+
1:
+ raise UserError(
+ _("All companies must have the same currency for budgeting.")
+ )
+
values = super().default_get(default_fields)
period_id = self.env.context.get("active_id")
period = self.env["budget.period"].browse(period_id)
@@ -137,6 +151,7 @@ def _prepare_value_duplicate(self, vals):
use_all_kpis = self.use_all_kpis
budget_period_id = self.budget_period_id.id
template_lines = self.template_line_ids.ids
+ currency_id = self.budget_currency_id.id
return list(
map(
lambda l: {
@@ -146,6 +161,7 @@ def _prepare_value_duplicate(self, vals):
or l["analytic_account_id"].name
),
"analytic_account_id": l["analytic_account_id"].id,
+ "currency_id": currency_id,
"plan_date_range_type_id": plan_date_range_id,
"use_all_kpis": use_all_kpis,
"template_line_ids": template_lines,
diff --git a/budget_control/wizards/generate_budget_control_view.xml b/budget_control/wizards/generate_budget_control_view.xml
index 014b0679..c5ab8d09 100644
--- a/budget_control/wizards/generate_budget_control_view.xml
+++ b/budget_control/wizards/generate_budget_control_view.xml
@@ -17,6 +17,7 @@
+
diff --git a/budget_control_advance_clearing/report/budget_monitor_report.py b/budget_control_advance_clearing/report/budget_monitor_report.py
index 8d93f38d..d9143c9c 100644
--- a/budget_control_advance_clearing/report/budget_monitor_report.py
+++ b/budget_control_advance_clearing/report/budget_monitor_report.py
@@ -18,7 +18,15 @@ def _get_consumed_sources(self):
]
def _where_advance_clearing(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_av_query = self._select_statement("40_av_commit")
diff --git a/budget_control_contract/report/budget_monitor_report.py b/budget_control_contract/report/budget_monitor_report.py
index 07633083..89e3b313 100644
--- a/budget_control_contract/report/budget_monitor_report.py
+++ b/budget_control_contract/report/budget_monitor_report.py
@@ -17,7 +17,15 @@ def _get_consumed_sources(self):
]
def _where_contract(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_ct_query = self._select_statement("60_ct_commit")
diff --git a/budget_control_expense/report/budget_monitor_report.py b/budget_control_expense/report/budget_monitor_report.py
index d28e59da..66f6e529 100644
--- a/budget_control_expense/report/budget_monitor_report.py
+++ b/budget_control_expense/report/budget_monitor_report.py
@@ -17,7 +17,15 @@ def _get_consumed_sources(self):
]
def _where_expense(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_ex_query = self._select_statement("50_ex_commit")
diff --git a/budget_control_purchase/report/budget_monitor_report.py b/budget_control_purchase/report/budget_monitor_report.py
index a00d0e83..eaae7c50 100644
--- a/budget_control_purchase/report/budget_monitor_report.py
+++ b/budget_control_purchase/report/budget_monitor_report.py
@@ -17,7 +17,15 @@ def _get_consumed_sources(self):
]
def _where_purchase(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_po_query = self._select_statement("30_po_commit")
diff --git a/budget_control_purchase_request/report/budget_monitor_report.py b/budget_control_purchase_request/report/budget_monitor_report.py
index 51cac66b..6bb8797e 100644
--- a/budget_control_purchase_request/report/budget_monitor_report.py
+++ b/budget_control_purchase_request/report/budget_monitor_report.py
@@ -20,7 +20,15 @@ def _get_consumed_sources(self):
]
def _where_purchase_request(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_pr_query = self._select_statement("20_pr_commit")
diff --git a/budget_control_request_document/report/budget_monitor_report.py b/budget_control_request_document/report/budget_monitor_report.py
index 63ef67b9..1145856e 100644
--- a/budget_control_request_document/report/budget_monitor_report.py
+++ b/budget_control_request_document/report/budget_monitor_report.py
@@ -17,7 +17,15 @@ def _get_consumed_sources(self):
]
def _where_expense(self):
- return ""
+ visible_company = self.env.context.get("allowed_company_ids")
+ if not visible_company:
+ return ""
+
+ if len(visible_company) > 1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+ return "where a.company_id in {}".format(companies)
def _get_sql(self):
select_ex_query = self._select_statement("15_rq_commit")
diff --git a/budget_control_revision/report/budget_monitor_report.py b/budget_control_revision/report/budget_monitor_report.py
index 0efd7da3..838bb38f 100644
--- a/budget_control_revision/report/budget_monitor_report.py
+++ b/budget_control_revision/report/budget_monitor_report.py
@@ -15,6 +15,11 @@ def _select_budget(self):
select_budget_query[70] = "b.revision_number::char as revision_number"
return select_budget_query
+ def _groupby_budget(self):
+ groupby_budget = super()._groupby_budget()
+ groupby_budget += ", b.revision_number"
+ return groupby_budget
+
# All consumed
def _select_statement(self, amount_type):
select_statement = super()._select_statement(amount_type)
diff --git a/budget_control_revision/report/budget_monitor_revision_report.py b/budget_control_revision/report/budget_monitor_revision_report.py
index 18df9b6b..3b8364f7 100644
--- a/budget_control_revision/report/budget_monitor_revision_report.py
+++ b/budget_control_revision/report/budget_monitor_revision_report.py
@@ -24,7 +24,9 @@ def _get_sql(self):
select_budget = ", ".join(
select_budget_query[x] for x in key_select_budget_list
)
- return "(select {} {})".format(
+ return "(select {} {} {} {})".format(
select_budget,
self._from_budget(),
+ self._where_budget(),
+ self._groupby_budget(),
)
diff --git a/budget_plan/__manifest__.py b/budget_plan/__manifest__.py
index 1e9dc667..2c021ae4 100644
--- a/budget_plan/__manifest__.py
+++ b/budget_plan/__manifest__.py
@@ -11,6 +11,7 @@
"depends": ["budget_control"],
"data": [
"security/ir.model.access.csv",
+ "security/budget_plan_rules.xml",
"views/budget_menuitem.xml",
"views/budget_control_view.xml",
"views/budget_plan_view.xml",
diff --git a/budget_plan/models/budget_plan.py b/budget_plan/models/budget_plan.py
index 3bc9ed7a..13b723e8 100644
--- a/budget_plan/models/budget_plan.py
+++ b/budget_plan/models/budget_plan.py
@@ -36,17 +36,30 @@ class BudgetPlan(models.Model):
help="Count budget control in Plan",
)
total_amount = fields.Monetary(compute="_compute_total_amount")
- company_id = fields.Many2one(
+ # company_id = fields.Many2one(
+ # comodel_name="res.company",
+ # default=lambda self: self.env.user.company_id,
+ # required=False,
+ # string="Company",
+ # readonly=True,
+ # states={"draft": [("readonly", False)]},
+ # )
+ # currency_id = fields.Many2one(
+ # comodel_name="res.currency", related="company_id.currency_id"
+ # )
+ company_ids = fields.Many2many(
comodel_name="res.company",
- default=lambda self: self.env.user.company_id,
- required=False,
- string="Company",
- readonly=True,
- states={"draft": [("readonly", False)]},
+ relation="budget_plan_company_rel",
+ column1="budget_plan_id",
+ column2="company_id",
+ string="Companies",
+ default=lambda self: self.env.context.get("allowed_company_ids"),
+ tracking=True,
)
currency_id = fields.Many2one(
- comodel_name="res.currency", related="company_id.currency_id"
+ comodel_name="res.currency", compute="_compute_currency_id"
)
+
line_ids = fields.One2many(
comodel_name="budget.plan.line",
inverse_name="plan_id",
@@ -67,6 +80,18 @@ class BudgetPlan(models.Model):
tracking=True,
)
+ @api.depends("company_ids")
+ def _compute_currency_id(self):
+ for rec in self:
+ currencies = rec.company_ids.mapped(
+ "currency_id"
+ ) # Get all currencies from companies
+ unique_currencies = set(currencies.ids) # Get unique currency IDs
+ if len(unique_currencies) > 1:
+ raise UserError(_("Selected companies have different currencies!"))
+
+ rec.currency_id = next(iter(currencies), self.env.company.currency_id)
+
@api.depends("line_ids")
def _compute_total_amount(self):
for rec in self:
@@ -106,6 +131,9 @@ def action_update_plan(self):
("bm_date_from", "<=", rec.date_to),
("bm_date_to", ">=", rec.date_from),
("id", "not in", plan_analytic.ids),
+ "|",
+ ("budget_company_ids", "=", False),
+ ("budget_company_ids", "in", rec.company_ids.ids),
]
)
lines = []
@@ -269,6 +297,15 @@ class BudgetPlanLine(models.Model):
active_status = fields.Boolean(
default=True, help="Activate/Deactivate when create/Update Budget Control"
)
+ company_ids = fields.Many2many(
+ comodel_name="res.company",
+ relation="budget_plan_line_company_rel",
+ column1="budget_plan_line_id",
+ column2="company_id",
+ string="Companies",
+ related="plan_id.company_ids",
+ store=True,
+ )
def _domain_budget_control(self):
self.ensure_one()
diff --git a/budget_plan/security/budget_plan_rules.xml b/budget_plan/security/budget_plan_rules.xml
new file mode 100644
index 00000000..295dd592
--- /dev/null
+++ b/budget_plan/security/budget_plan_rules.xml
@@ -0,0 +1,25 @@
+
+
+
+ Budget Plan multi-company
+
+
+ [
+ '|',
+ ('company_ids', '=', False),
+ ('company_ids', 'in', company_ids)
+ ]
+
+
+
+ Budget Plan Line multi-company
+
+
+ [
+ '|',
+ ('company_ids', '=', False),
+ ('company_ids', 'in', company_ids)
+ ]
+
+
+
diff --git a/budget_plan/static/description/index.html b/budget_plan/static/description/index.html
index 82425b9a..dd2569da 100644
--- a/budget_plan/static/description/index.html
+++ b/budget_plan/static/description/index.html
@@ -1,4 +1,3 @@
-
@@ -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 {
diff --git a/budget_plan/views/budget_plan_view.xml b/budget_plan/views/budget_plan_view.xml
index c1d0740f..25d21744 100644
--- a/budget_plan/views/budget_plan_view.xml
+++ b/budget_plan/views/budget_plan_view.xml
@@ -10,7 +10,19 @@
+
+
@@ -103,6 +122,12 @@
+
+
1:
+ companies = tuple(visible_company)
+ else:
+ companies = "({})".format(tuple(visible_company)[0])
+
+ where_purchase += " AND a.company_id in {}".format(companies)
return where_purchase
def _get_sql(self):