[IMP] account_financial_report: analytic info + filters in the

open items and aged partner balance reports.

This is useful in project based companies where it is important
the assets & liabilities by projects. Long term projects my have
outstanding invoices for months.
pull/1138/head
AaronHForgeFlow 2024-03-25 16:49:27 +01:00
parent f37d54516d
commit 85ead5cd59
10 changed files with 272 additions and 49 deletions

View File

@ -6,6 +6,7 @@ import operator
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from odoo import api, models from odoo import api, models
from odoo.osv import expression
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
@ -148,10 +149,38 @@ class AgedPartnerBalanceReport(models.AbstractModel):
date_from, date_from,
only_posted_moves, only_posted_moves,
show_move_line_details, show_move_line_details,
analytic_account_ids,
no_analytic,
): ):
domain = self._get_move_lines_domain_not_reconciled( domain = self._get_move_lines_domain_not_reconciled(
company_id, account_ids, partner_ids, only_posted_moves, date_from company_id, account_ids, partner_ids, only_posted_moves, date_from
) )
if no_analytic:
domain = expression.AND(
[
domain,
[
(
"analytic_account_id",
"=",
False,
)
],
]
)
elif analytic_account_ids:
domain = expression.AND(
[
domain,
[
(
"analytic_account_id",
"in",
analytic_account_ids,
)
],
]
)
ml_fields = self._get_ml_fields() ml_fields = self._get_ml_fields()
line_model = self.env["account.move.line"] line_model = self.env["account.move.line"]
move_lines = line_model.search_read(domain=domain, fields=ml_fields) move_lines = line_model.search_read(domain=domain, fields=ml_fields)
@ -224,6 +253,10 @@ class AgedPartnerBalanceReport(models.AbstractModel):
ref_label = move_line["ref"] ref_label = move_line["ref"]
else: else:
ref_label = move_line["ref"] + str(" - ") + move_line["name"] ref_label = move_line["ref"] + str(" - ") + move_line["name"]
if move_line["analytic_account_id"]:
analytic = move_line["analytic_account_id"][1]
else:
analytic = False
move_line_data.update( move_line_data.update(
{ {
"line_rec": line_model.browse(move_line["id"]), "line_rec": line_model.browse(move_line["id"]),
@ -235,6 +268,7 @@ class AgedPartnerBalanceReport(models.AbstractModel):
"ref_label": ref_label, "ref_label": ref_label,
"due_date": move_line["date_maturity"], "due_date": move_line["date_maturity"],
"residual": move_line["amount_residual"], "residual": move_line["amount_residual"],
"analytic_account_id": analytic,
} }
) )
ag_pb_data[acc_id][prt_id]["move_lines"].append(move_line_data) ag_pb_data[acc_id][prt_id]["move_lines"].append(move_line_data)
@ -416,6 +450,8 @@ class AgedPartnerBalanceReport(models.AbstractModel):
aged_partner_configuration = self.env[ aged_partner_configuration = self.env[
"account.age.report.configuration" "account.age.report.configuration"
].browse(data["age_partner_config_id"]) ].browse(data["age_partner_config_id"])
analytic_account_ids = data["analytic_account_ids"]
no_analytic = data["no_analytic"]
(ag_pb_data, accounts_data, partners_data, journals_data,) = self.with_context( (ag_pb_data, accounts_data, partners_data, journals_data,) = self.with_context(
age_partner_config=aged_partner_configuration age_partner_config=aged_partner_configuration
)._get_move_lines_data( )._get_move_lines_data(
@ -426,6 +462,8 @@ class AgedPartnerBalanceReport(models.AbstractModel):
date_from, date_from,
only_posted_moves, only_posted_moves,
show_move_line_details, show_move_line_details,
analytic_account_ids,
no_analytic,
) )
aged_partner_data = self.with_context( aged_partner_data = self.with_context(
age_partner_config=aged_partner_configuration age_partner_config=aged_partner_configuration
@ -458,4 +496,5 @@ class AgedPartnerBalanceReport(models.AbstractModel):
"amount_residual", "amount_residual",
"reconciled", "reconciled",
"date_maturity", "date_maturity",
"analytic_account_id",
] ]

View File

@ -104,9 +104,14 @@ class AgedPartnerBalanceXslx(models.AbstractModel):
2: {"header": _("Journal"), "field": "journal", "width": 8}, 2: {"header": _("Journal"), "field": "journal", "width": 8},
3: {"header": _("Account"), "field": "account", "width": 9}, 3: {"header": _("Account"), "field": "account", "width": 9},
4: {"header": _("Partner"), "field": "partner", "width": 25}, 4: {"header": _("Partner"), "field": "partner", "width": 25},
5: {"header": _("Ref - Label"), "field": "ref_label", "width": 40}, 5: {
6: {"header": _("Due date"), "field": "due_date", "width": 11}, "header": _("Analytic Account"),
7: { "field": "analytic_account_id",
"width": 25,
},
6: {"header": _("Ref - Label"), "field": "ref_label", "width": 40},
7: {"header": _("Due date"), "field": "due_date", "width": 11},
8: {
"header": _("Residual"), "header": _("Residual"),
"field": "residual", "field": "residual",
"field_footer_total": "residual", "field_footer_total": "residual",
@ -114,7 +119,7 @@ class AgedPartnerBalanceXslx(models.AbstractModel):
"type": "amount", "type": "amount",
"width": 14, "width": 14,
}, },
8: { 9: {
"header": _("Current"), "header": _("Current"),
"field": "current", "field": "current",
"field_footer_total": "current", "field_footer_total": "current",

View File

@ -7,6 +7,7 @@ import operator
from datetime import date, datetime from datetime import date, datetime
from odoo import _, api, models from odoo import _, api, models
from odoo.osv import expression
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
@ -68,10 +69,14 @@ class OpenItemsReport(models.AbstractModel):
company_id, company_id,
date_from, date_from,
grouped_by, grouped_by,
analytic_account_ids,
no_analytic,
): ):
domain = self._get_move_lines_domain_not_reconciled( domain = self._get_move_lines_domain_not_reconciled(
company_id, account_ids, partner_ids, only_posted_moves, date_from company_id, account_ids, partner_ids, only_posted_moves, date_from
) )
# moved out to avoid too complex method error
domain = self.get_analytic_domain(domain, analytic_account_ids, no_analytic)
ml_fields = self._get_ml_fields() ml_fields = self._get_ml_fields()
move_lines = self.env["account.move.line"].search_read( move_lines = self.env["account.move.line"].search_read(
domain=domain, fields=ml_fields domain=domain, fields=ml_fields
@ -148,7 +153,10 @@ class OpenItemsReport(models.AbstractModel):
ref_label = move_line["ref"] ref_label = move_line["ref"]
else: else:
ref_label = move_line["ref"] + str(" - ") + move_line["name"] ref_label = move_line["ref"] + str(" - ") + move_line["name"]
if analytic_account_ids and move_line["analytic_account_id"]:
analytic_account_id = move_line["analytic_account_id"][1]
else:
analytic_account_id = False
move_line.update( move_line.update(
{ {
"date": move_line["date"], "date": move_line["date"],
@ -167,6 +175,7 @@ class OpenItemsReport(models.AbstractModel):
"currency_name": move_line["currency_id"][1] "currency_name": move_line["currency_id"][1]
if move_line["currency_id"] if move_line["currency_id"]
else False, else False,
"analytic_account_id": analytic_account_id,
} }
) )
@ -247,6 +256,9 @@ class OpenItemsReport(models.AbstractModel):
only_posted_moves = data["only_posted_moves"] only_posted_moves = data["only_posted_moves"]
show_partner_details = data["show_partner_details"] show_partner_details = data["show_partner_details"]
grouped_by = data["grouped_by"] grouped_by = data["grouped_by"]
analytic_account_ids = data["analytic_account_ids"]
no_analytic = data["no_analytic"]
( (
move_lines_data, move_lines_data,
partners_data, partners_data,
@ -261,6 +273,8 @@ class OpenItemsReport(models.AbstractModel):
company_id, company_id,
date_from, date_from,
grouped_by, grouped_by,
analytic_account_ids,
no_analytic,
) )
total_amount = self._calculate_amounts(open_items_move_lines_data) total_amount = self._calculate_amounts(open_items_move_lines_data)
@ -288,6 +302,7 @@ class OpenItemsReport(models.AbstractModel):
def _get_ml_fields(self): def _get_ml_fields(self):
return self.COMMON_ML_FIELDS + [ return self.COMMON_ML_FIELDS + [
"analytic_account_id",
"amount_residual", "amount_residual",
"reconciled", "reconciled",
"currency_id", "currency_id",
@ -297,3 +312,32 @@ class OpenItemsReport(models.AbstractModel):
"debit", "debit",
"amount_currency", "amount_currency",
] ]
def get_analytic_domain(self, domain, analytic_account_ids, no_analytic):
if no_analytic:
domain = expression.AND(
[
domain,
[
(
"analytic_account_id",
"=",
False,
)
],
]
)
elif analytic_account_ids:
domain = expression.AND(
[
domain,
[
(
"analytic_account_id",
"in",
analytic_account_ids,
)
],
]
)
return domain

View File

@ -27,15 +27,20 @@ class OpenItemsXslx(models.AbstractModel):
2: {"header": _("Journal"), "field": "journal", "width": 8}, 2: {"header": _("Journal"), "field": "journal", "width": 8},
3: {"header": _("Account"), "field": "account", "width": 9}, 3: {"header": _("Account"), "field": "account", "width": 9},
4: {"header": _("Partner"), "field": "partner_name", "width": 25}, 4: {"header": _("Partner"), "field": "partner_name", "width": 25},
5: {"header": _("Ref - Label"), "field": "ref_label", "width": 40}, 5: {
6: {"header": _("Due date"), "field": "date_maturity", "width": 11}, "header": _("Analytic Account"),
7: { "field": "analytic_account_id",
"width": 25,
},
6: {"header": _("Ref - Label"), "field": "ref_label", "width": 40},
7: {"header": _("Due date"), "field": "date_maturity", "width": 11},
8: {
"header": _("Original"), "header": _("Original"),
"field": "original", "field": "original",
"type": "amount", "type": "amount",
"width": 14, "width": 14,
}, },
8: { 9: {
"header": _("Residual"), "header": _("Residual"),
"field": "amount_residual", "field": "amount_residual",
"field_final_balance": "residual", "field_final_balance": "residual",
@ -45,21 +50,21 @@ class OpenItemsXslx(models.AbstractModel):
} }
if report.foreign_currency: if report.foreign_currency:
foreign_currency = { foreign_currency = {
9: { 10: {
"header": _("Cur."), "header": _("Cur."),
"field": "currency_name", "field": "currency_name",
"field_currency_balance": "currency_name", "field_currency_balance": "currency_name",
"type": "currency_name", "type": "currency_name",
"width": 7, "width": 7,
}, },
10: { 11: {
"header": _("Cur. Original"), "header": _("Cur. Original"),
"field": "amount_currency", "field": "amount_currency",
"field_final_balance": "amount_currency", "field_final_balance": "amount_currency",
"type": "amount_currency", "type": "amount_currency",
"width": 14, "width": 14,
}, },
11: { 12: {
"header": _("Cur. Residual"), "header": _("Cur. Residual"),
"field": "amount_residual_currency", "field": "amount_residual_currency",
"field_final_balance": "amount_currency", "field_final_balance": "amount_currency",

View File

@ -218,13 +218,29 @@
<div class="act_as_cell" style="width: 5.00%;">Journal</div> <div class="act_as_cell" style="width: 5.00%;">Journal</div>
<!--## account code--> <!--## account code-->
<div class="act_as_cell" style="width: 6.00%;">Account</div> <div class="act_as_cell" style="width: 6.00%;">Account</div>
<!--## partner--> <t t-if="analytic_account_ids">
<div class="act_as_cell" style="width: 10.50%;">Partner</div> <!--## partner-->
<!--## ref - label--> <div class="act_as_cell" style="width: 9.50%;">Partner</div>
<div class="act_as_cell" style="width: 18.00%;"> <!--## analytic account -->
Ref - <div
Label class="act_as_cell"
</div> style="width: 9.50%;"
>Analytic Account</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 9.50%;">
Ref -
Label
</div>
</t>
<t t-else="">
<!--## partner-->
<div class="act_as_cell" style="width: 10.50%;">Partner</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 18.00%;">
Ref -
Label
</div>
</t>
<!--## date_due--> <!--## date_due-->
<div class="act_as_cell" style="width: 6.00%;"> <div class="act_as_cell" style="width: 6.00%;">
Due Due
@ -311,26 +327,60 @@
<t t-out="line['account']" /> <t t-out="line['account']" />
</span> </span>
</div> </div>
<!--## partner--> <t t-if="analytic_account_ids">
<div class="act_as_cell left"> <!--## partner-->
<span <div class="act_as_cell left">
t-att-res-id="line['line_rec'].partner_id.id" <span
res-model="res.partner" t-att-res-id="line['line_rec'].partner_id.id"
view-type="form" res-model="res.partner"
> view-type="form"
<t t-out="line['partner']" /> >
</span> <t t-out="line['partner']" />
</div> </span>
<!--## ref - label--> </div>
<div class="act_as_cell left"> <!-- ANAL -->
<span <div class="act_as_cell left">
t-att-res-id="line['line_rec'].id" <span
res-model="account.move.line" t-att-res-id="line['line_rec'].analytic_account_id.id"
view-type="form" res-model="analytic_account_id"
> view-type="form"
<t t-out="line['ref_label']" /> >
</span> <t t-out="line['analytic_account_id']" />
</div> </span>
</div>
<!--## ref - label-->
<div class="act_as_cell left">
<span
t-att-res-id="line['line_rec'].id"
res-model="account.move.line"
view-type="form"
>
<t t-out="line['ref_label']" />
</span>
</div>
</t>
<t t-else="">
<!--## partner-->
<div class="act_as_cell left">
<span
t-att-res-id="line['line_rec'].partner_id.id"
res-model="res.partner"
view-type="form"
>
<t t-out="line['partner']" />
</span>
</div>
<!--## ref - label-->
<div class="act_as_cell left">
<span
t-att-res-id="line['line_rec'].id"
res-model="account.move.line"
view-type="form"
>
<t t-out="line['ref_label']" />
</span>
</div>
</t>
<!--## date_due--> <!--## date_due-->
<div class="act_as_cell left"> <div class="act_as_cell left">
<span <span

View File

@ -245,13 +245,30 @@
<div class="act_as_cell" style="width: 4.78%;">Journal</div> <div class="act_as_cell" style="width: 4.78%;">Journal</div>
<!--## account code--> <!--## account code-->
<div class="act_as_cell" style="width: 5.38%;">Account</div> <div class="act_as_cell" style="width: 5.38%;">Account</div>
<!--## partner-->
<div class="act_as_cell" style="width: 15.07%;">Partner</div> <t t-if="analytic_account_ids">
<!--## ref - label--> <!--## partner-->
<div class="act_as_cell" style="width: 24.5%;"> <div class="act_as_cell" style="width: 13.19%;">Partner</div>
Ref - <!--## analytic_account-->
Label <div
</div> class="act_as_cell"
style="width: 13.19%;"
>Analytic Account</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 13.19%;">
Ref -
Label
</div>
</t>
<t t-else="">
<!--## partner-->
<div class="act_as_cell" style="width: 15.07%;">Partner</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 24.5%;">
Ref -
Label
</div>
</t>
<!--## date_due--> <!--## date_due-->
<div class="act_as_cell" style="width: 6.47%;"> <div class="act_as_cell" style="width: 6.47%;">
Due Due
@ -324,6 +341,20 @@
<t t-esc="line['partner_name']" /> <t t-esc="line['partner_name']" />
</span> </span>
</div> </div>
<!--## cost_center-->
<t t-if="analytic_account_ids">
<div class="act_as_cell left">
<t t-if="line['analytic_account_id']">
<span
t-att-res-id="line['analytic_account_id']"
res-model="account.analytic.account"
view-type="form"
>
<t t-raw="line['analytic_account_id']" />
</span>
</t>
</div>
</t>
<!--## ref - label--> <!--## ref - label-->
<div class="act_as_cell left"> <div class="act_as_cell left">
<span t-esc="line['ref_label']" /> <span t-esc="line['ref_label']" />

View File

@ -43,6 +43,10 @@ class AgedPartnerBalanceWizard(models.TransientModel):
age_partner_config_id = fields.Many2one( age_partner_config_id = fields.Many2one(
"account.age.report.configuration", string="Intervals configuration" "account.age.report.configuration", string="Intervals configuration"
) )
analytic_account_ids = fields.Many2many(
comodel_name="account.analytic.account", string="Filter analytic accounts"
)
no_analytic = fields.Boolean("Only no analytic items")
@api.onchange("account_code_from", "account_code_to") @api.onchange("account_code_from", "account_code_to")
def on_change_account_range(self): def on_change_account_range(self):
@ -86,17 +90,27 @@ class AgedPartnerBalanceWizard(models.TransientModel):
self.account_ids = self.account_ids.filtered( self.account_ids = self.account_ids.filtered(
lambda a: a.company_id == self.company_id lambda a: a.company_id == self.company_id
) )
res = {"domain": {"account_ids": [], "partner_ids": []}} res = {
"domain": {"account_ids": [], "partner_ids": [], "analytic_account_ids": []}
}
if not self.company_id: if not self.company_id:
return res return res
else: else:
res["domain"]["account_ids"] += [("company_id", "=", self.company_id.id)] res["domain"]["account_ids"] += [("company_id", "=", self.company_id.id)]
res["domain"]["partner_ids"] += self._get_partner_ids_domain() res["domain"]["partner_ids"] += self._get_partner_ids_domain()
res["domain"]["analytic_account_ids"] += [
("company_id", "=", self.company_id.id)
]
return res return res
@api.onchange("account_ids") @api.onchange("account_ids")
def onchange_account_ids(self): def onchange_account_ids(self):
return {"domain": {"account_ids": [("reconcile", "=", True)]}} return {
"domain": {
"account_ids": [("reconcile", "=", True)],
"analytic_account_ids": [],
}
}
@api.onchange("receivable_accounts_only", "payable_accounts_only") @api.onchange("receivable_accounts_only", "payable_accounts_only")
def onchange_type_accounts_only(self): def onchange_type_accounts_only(self):
@ -142,6 +156,8 @@ class AgedPartnerBalanceWizard(models.TransientModel):
"show_move_line_details": self.show_move_line_details, "show_move_line_details": self.show_move_line_details,
"account_financial_report_lang": self.env.lang, "account_financial_report_lang": self.env.lang,
"age_partner_config_id": self.age_partner_config_id.id, "age_partner_config_id": self.age_partner_config_id.id,
"analytic_account_ids": self.analytic_account_ids.ids or [],
"no_analytic": self.no_analytic,
} }
def _export(self, report_type): def _export(self, report_type):

View File

@ -62,6 +62,17 @@
colspan="4" colspan="4"
/> />
</group> </group>
<group
name="Filter analytic accounts"
groups="analytic.group_analytic_accounting"
>
<field
name="analytic_account_ids"
widget="many2many_tags"
options="{'no_create': True}"
/>
<field name="no_analytic" />
</group>
<footer> <footer>
<button <button
name="button_export_html" name="button_export_html"

View File

@ -63,6 +63,10 @@ class OpenItemsReportWizard(models.TransientModel):
selection=[("partners", "Partners"), ("salesperson", "Partner Salesperson")], selection=[("partners", "Partners"), ("salesperson", "Partner Salesperson")],
default="partners", default="partners",
) )
analytic_account_ids = fields.Many2many(
comodel_name="account.analytic.account", string="Filter analytic accounts"
)
no_analytic = fields.Boolean("Only no analytic items")
@api.onchange("account_code_from", "account_code_to") @api.onchange("account_code_from", "account_code_to")
def on_change_account_range(self): def on_change_account_range(self):
@ -109,12 +113,17 @@ class OpenItemsReportWizard(models.TransientModel):
self.account_ids = self.account_ids.filtered( self.account_ids = self.account_ids.filtered(
lambda a: a.company_id == self.company_id lambda a: a.company_id == self.company_id
) )
res = {"domain": {"account_ids": [], "partner_ids": []}} res = {
"domain": {"account_ids": [], "partner_ids": [], "analytic_account_ids": []}
}
if not self.company_id: if not self.company_id:
return res return res
else: else:
res["domain"]["account_ids"] += [("company_id", "=", self.company_id.id)] res["domain"]["account_ids"] += [("company_id", "=", self.company_id.id)]
res["domain"]["partner_ids"] += self._get_partner_ids_domain() res["domain"]["partner_ids"] += self._get_partner_ids_domain()
res["domain"]["analytic_account_ids"] += [
("company_id", "=", self.company_id.id)
]
return res return res
@api.onchange("account_ids") @api.onchange("account_ids")
@ -179,8 +188,10 @@ class OpenItemsReportWizard(models.TransientModel):
"target_move": self.target_move, "target_move": self.target_move,
"account_ids": self.account_ids.ids, "account_ids": self.account_ids.ids,
"partner_ids": self.partner_ids.ids or [], "partner_ids": self.partner_ids.ids or [],
"analytic_account_ids": self.analytic_account_ids.ids or [],
"account_financial_report_lang": self.env.lang, "account_financial_report_lang": self.env.lang,
"grouped_by": self.grouped_by, "grouped_by": self.grouped_by,
"no_analytic": self.no_analytic,
} }
def _export(self, report_type): def _export(self, report_type):

View File

@ -62,6 +62,17 @@
colspan="4" colspan="4"
/> />
</group> </group>
<group
name="Filter analytic accounts"
groups="analytic.group_analytic_accounting"
>
<field
name="analytic_account_ids"
widget="many2many_tags"
options="{'no_create': True}"
/>
<field name="no_analytic" />
</group>
<footer> <footer>
<button <button
name="button_export_html" name="button_export_html"