Files
Odoo-18.0-20251222/account_financial_report/report/open_items.py
Fekete Mihai 2547753dcc Update links in report, add account group file, update trial balance with hierarchy.
Update indentation, remove empty lines from header.

Update test.

Update pylint.

Remove company_id on computing accounts, since account.group is not a company based model, filtering accounts is done on trial balance report.

Update account variables.

Improve condition in padding on accounts.

Add option to print hierarchy based on defined accounts/computed accounts.

Add VAT report, hierarchy from tax tags ans taxes.

Fix pylint, xlsx report generation header.

Update code to select code_prefix or name.

Update code to select code_prefix or name.

Update code to select code_prefix or name.

Fix domain in base amounts in vat report.

Change trial balance code_prefix or name.

Update trail balance, add tests for vat report.

Update pylint, amounts as monetary, many2one option on generation excels.

Update pulint.

Add VAT Report in readme.

Add VAT Report in readme.

Update array_agg.

Update array_agg.

Update array_agg.

Add option in VAT Report to be printed on Tax Tags - Tax Groups.

Add widget to hierarchy_on on trial balance.
2024-11-29 15:38:40 +07:00

777 lines
22 KiB
Python

# ?? 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
class OpenItemsReport(models.TransientModel):
""" Here, we just define class fields.
For methods, go more bottom at this file.
The class hierarchy is :
* OpenItemsReport
** OpenItemsReportAccount
*** OpenItemsReportPartner
**** OpenItemsReportMoveLine
"""
_name = 'report_open_items'
# Filters fields, used for data computation
date_at = fields.Date()
only_posted_moves = fields.Boolean()
hide_account_balance_at_0 = fields.Boolean()
company_id = fields.Many2one(comodel_name='res.company')
filter_account_ids = fields.Many2many(comodel_name='account.account')
filter_partner_ids = fields.Many2many(comodel_name='res.partner')
# Flag fields, used for report display
has_second_currency = fields.Boolean()
# Data fields, used to browse report data
account_ids = fields.One2many(
comodel_name='report_open_items_account',
inverse_name='report_id'
)
class OpenItemsReportAccount(models.TransientModel):
_name = 'report_open_items_account'
_order = 'code ASC'
report_id = fields.Many2one(
comodel_name='report_open_items',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
account_id = fields.Many2one(
'account.account',
index=True
)
# Data fields, used for report display
code = fields.Char()
name = fields.Char()
final_amount_residual = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
partner_ids = fields.One2many(
comodel_name='report_open_items_partner',
inverse_name='report_account_id'
)
class OpenItemsReportPartner(models.TransientModel):
_name = 'report_open_items_partner'
report_account_id = fields.Many2one(
comodel_name='report_open_items_account',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
partner_id = fields.Many2one(
'res.partner',
index=True
)
# Data fields, used for report display
name = fields.Char()
final_amount_residual = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
move_line_ids = fields.One2many(
comodel_name='report_open_items_move_line',
inverse_name='report_partner_id'
)
@api.model
def _generate_order_by(self, order_spec, query):
"""Custom order to display "No partner allocated" at last position."""
return """
ORDER BY
CASE
WHEN "report_open_items_partner"."partner_id" IS NOT NULL
THEN 0
ELSE 1
END,
"report_open_items_partner"."name"
"""
class OpenItemsReportMoveLine(models.TransientModel):
_name = 'report_open_items_move_line'
report_partner_id = fields.Many2one(
comodel_name='report_open_items_partner',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
move_line_id = fields.Many2one('account.move.line')
# Data fields, used for report display
date = fields.Date()
date_due = fields.Date()
entry = fields.Char()
journal = fields.Char()
account = fields.Char()
partner = fields.Char()
label = fields.Char()
amount_total_due = fields.Float(digits=(16, 2))
amount_residual = fields.Float(digits=(16, 2))
currency_id = fields.Many2one('res.currency')
amount_total_due_currency = fields.Float(digits=(16, 2))
amount_residual_currency = fields.Float(digits=(16, 2))
class OpenItemsReportCompute(models.TransientModel):
""" Here, we just define methods.
For class fields, go more top at this file.
"""
_inherit = 'report_open_items'
@api.multi
def print_report(self, report_type):
self.ensure_one()
if report_type == 'xlsx':
report_name = 'a_f_r.report_open_items_xlsx'
else:
report_name = 'account_financial_report.' \
'report_open_items_qweb'
return self.env['ir.actions.report'].search(
[('report_name', '=', report_name),
('report_type', '=', report_type)], limit=1).report_action(self)
def _get_html(self):
result = {}
rcontext = {}
context = dict(self.env.context)
report = self.browse(context.get('active_id'))
if report:
rcontext['o'] = report
result['html'] = self.env.ref(
'account_financial_report.report_open_items').render(
rcontext)
return result
@api.model
def get_html(self, given_context=None):
return self._get_html()
@api.multi
def compute_data_for_report(self):
self.ensure_one()
# Compute report data
self._inject_account_values()
self._inject_partner_values()
self._inject_line_values()
self._inject_line_values(only_empty_partner_line=True)
self._clean_partners_and_accounts()
self._compute_partners_and_accounts_cumul()
if self.hide_account_balance_at_0:
self._clean_partners_and_accounts(
only_delete_account_balance_at_0=True
)
# Compute display flag
self._compute_has_second_currency()
# Refresh cache because all data are computed with SQL requests
self.refresh()
def _inject_account_values(self):
"""Inject report values for report_open_items_account."""
query_inject_account = """
WITH
accounts AS
(
SELECT
a.id,
a.code,
a.name,
a.user_type_id
FROM
account_account a
INNER JOIN
account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
"""
if self.filter_partner_ids:
query_inject_account += """
INNER JOIN
res_partner p ON ml.partner_id = p.id
"""
if self.only_posted_moves:
query_inject_account += """
INNER JOIN
account_move m ON ml.move_id = m.id AND m.state = 'posted'
"""
query_inject_account += """
WHERE
a.company_id = %s
AND a.reconcile IS true
"""
if self.filter_account_ids:
query_inject_account += """
AND
a.id IN %s
"""
if self.filter_partner_ids:
query_inject_account += """
AND
p.id IN %s
"""
query_inject_account += """
GROUP BY
a.id
)
INSERT INTO
report_open_items_account
(
report_id,
create_uid,
create_date,
account_id,
code,
name
)
SELECT
%s AS report_id,
%s AS create_uid,
NOW() AS create_date,
a.id AS account_id,
a.code,
a.name
FROM
accounts a
"""
query_inject_account_params = (
self.date_at,
self.company_id.id,
)
if self.filter_account_ids:
query_inject_account_params += (
tuple(self.filter_account_ids.ids),
)
if self.filter_partner_ids:
query_inject_account_params += (
tuple(self.filter_partner_ids.ids),
)
query_inject_account_params += (
self.id,
self.env.uid,
)
self.env.cr.execute(query_inject_account, query_inject_account_params)
def _inject_partner_values(self):
""" Inject report values for report_open_items_partner. """
# pylint: disable=sql-injection
query_inject_partner = """
WITH
accounts_partners AS
(
SELECT
ra.id AS report_account_id,
a.id AS account_id,
at.include_initial_balance AS include_initial_balance,
p.id AS partner_id,
COALESCE(
CASE
WHEN
NULLIF(p.name, '') IS NOT NULL
AND NULLIF(p.ref, '') IS NOT NULL
THEN p.name || ' (' || p.ref || ')'
ELSE p.name
END,
'""" + _('No partner allocated') + """'
) AS partner_name
FROM
report_open_items_account ra
INNER JOIN
account_account a ON ra.account_id = a.id
INNER JOIN
account_account_type at ON a.user_type_id = at.id
INNER JOIN
account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
"""
if self.only_posted_moves:
query_inject_partner += """
INNER JOIN
account_move m ON ml.move_id = m.id AND m.state = 'posted'
"""
query_inject_partner += """
LEFT JOIN
res_partner p ON ml.partner_id = p.id
WHERE
ra.report_id = %s
"""
if self.filter_partner_ids:
query_inject_partner += """
AND
p.id IN %s
"""
query_inject_partner += """
GROUP BY
ra.id,
a.id,
p.id,
at.include_initial_balance
)
INSERT INTO
report_open_items_partner
(
report_account_id,
create_uid,
create_date,
partner_id,
name
)
SELECT
ap.report_account_id,
%s AS create_uid,
NOW() AS create_date,
ap.partner_id,
ap.partner_name
FROM
accounts_partners ap
"""
query_inject_partner_params = (
self.date_at,
self.id,
)
if self.filter_partner_ids:
query_inject_partner_params += (
tuple(self.filter_partner_ids.ids),
)
query_inject_partner_params += (
self.env.uid,
)
self.env.cr.execute(query_inject_partner, query_inject_partner_params)
def _get_line_sub_query_move_lines(self,
only_empty_partner_line=False,
positive_balance=True):
""" Return subquery used to compute sum amounts on lines """
sub_query = """
SELECT
ml.id,
ml.balance,
SUM(
CASE
WHEN ml_past.id IS NOT NULL
THEN pr.amount
ELSE NULL
END
) AS partial_amount,
ml.amount_currency,
SUM(
CASE
WHEN ml_past.id IS NOT NULL
THEN pr.amount_currency
ELSE NULL
END
) AS partial_amount_currency,
ml.currency_id
FROM
report_open_items_partner rp
INNER JOIN
report_open_items_account ra
ON rp.report_account_id = ra.id
INNER JOIN
account_move_line ml
ON ra.account_id = ml.account_id
"""
if not only_empty_partner_line:
sub_query += """
AND rp.partner_id = ml.partner_id
"""
elif only_empty_partner_line:
sub_query += """
AND ml.partner_id IS NULL
"""
if not positive_balance:
sub_query += """
LEFT JOIN
account_partial_reconcile pr
ON ml.balance < 0 AND pr.credit_move_id = ml.id
LEFT JOIN
account_move_line ml_future
ON ml.balance < 0 AND pr.debit_move_id = ml_future.id
AND ml_future.date > %s
LEFT JOIN
account_move_line ml_past
ON ml.balance < 0 AND pr.debit_move_id = ml_past.id
AND ml_past.date <= %s
"""
else:
sub_query += """
LEFT JOIN
account_partial_reconcile pr
ON ml.balance > 0 AND pr.debit_move_id = ml.id
LEFT JOIN
account_move_line ml_future
ON ml.balance > 0 AND pr.credit_move_id = ml_future.id
AND ml_future.date > %s
LEFT JOIN
account_move_line ml_past
ON ml.balance > 0 AND pr.credit_move_id = ml_past.id
AND ml_past.date <= %s
"""
sub_query += """
WHERE
ra.report_id = %s
GROUP BY
ml.id,
ml.balance,
ml.amount_currency
HAVING
(
ml.full_reconcile_id IS NULL
OR MAX(ml_future.id) IS NOT NULL
)
"""
return sub_query
def _inject_line_values(self, only_empty_partner_line=False):
""" Inject report values for report_open_items_move_line.
The "only_empty_partner_line" value is used
to compute data without partner.
"""
query_inject_move_line = """
WITH
move_lines_amount AS
(
"""
query_inject_move_line += self._get_line_sub_query_move_lines(
only_empty_partner_line=only_empty_partner_line,
positive_balance=True
)
query_inject_move_line += """
UNION
"""
query_inject_move_line += self._get_line_sub_query_move_lines(
only_empty_partner_line=only_empty_partner_line,
positive_balance=False
)
query_inject_move_line += """
),
move_lines AS
(
SELECT
id,
CASE
WHEN SUM(partial_amount) > 0
THEN
CASE
WHEN balance > 0
THEN balance - SUM(partial_amount)
ELSE balance + SUM(partial_amount)
END
ELSE balance
END AS amount_residual,
CASE
WHEN SUM(partial_amount_currency) > 0
THEN
CASE
WHEN amount_currency > 0
THEN amount_currency - SUM(partial_amount_currency)
ELSE amount_currency + SUM(partial_amount_currency)
END
ELSE amount_currency
END AS amount_residual_currency,
currency_id
FROM
move_lines_amount
GROUP BY
id,
balance,
amount_currency,
currency_id
)
INSERT INTO
report_open_items_move_line
(
report_partner_id,
create_uid,
create_date,
move_line_id,
date,
date_due,
entry,
journal,
account,
partner,
label,
amount_total_due,
amount_residual,
currency_id,
amount_total_due_currency,
amount_residual_currency
)
SELECT
rp.id AS report_partner_id,
%s AS create_uid,
NOW() AS create_date,
ml.id AS move_line_id,
ml.date,
ml.date_maturity,
m.name AS entry,
j.code AS journal,
a.code AS account,
"""
if not only_empty_partner_line:
query_inject_move_line += """
CASE
WHEN
NULLIF(p.name, '') IS NOT NULL
AND NULLIF(p.ref, '') IS NOT NULL
THEN p.name || ' (' || p.ref || ')'
ELSE p.name
END AS partner,
"""
elif only_empty_partner_line:
query_inject_move_line += """
'""" + _('No partner allocated') + """' AS partner,
"""
query_inject_move_line += """
CONCAT_WS(' - ', NULLIF(ml.ref, ''), NULLIF(ml.name, '')) AS label,
ml.balance,
ml2.amount_residual,
c.id AS currency_id,
ml.amount_currency,
ml2.amount_residual_currency
FROM
report_open_items_partner rp
INNER JOIN
report_open_items_account ra ON rp.report_account_id = ra.id
INNER JOIN
account_move_line ml ON ra.account_id = ml.account_id
INNER JOIN
move_lines ml2
ON ml.id = ml2.id
AND ml2.amount_residual IS NOT NULL
AND ml2.amount_residual != 0
INNER JOIN
account_move m ON ml.move_id = m.id
INNER JOIN
account_journal j ON ml.journal_id = j.id
INNER JOIN
account_account a ON ml.account_id = a.id
"""
if not only_empty_partner_line:
query_inject_move_line += """
INNER JOIN
res_partner p
ON ml.partner_id = p.id AND rp.partner_id = p.id
"""
query_inject_move_line += """
LEFT JOIN
account_full_reconcile fr ON ml.full_reconcile_id = fr.id
LEFT JOIN
res_currency c ON ml2.currency_id = c.id
WHERE
ra.report_id = %s
AND
ml.date <= %s
"""
if self.only_posted_moves:
query_inject_move_line += """
AND
m.state = 'posted'
"""
if only_empty_partner_line:
query_inject_move_line += """
AND
ml.partner_id IS NULL
AND
rp.partner_id IS NULL
"""
if not only_empty_partner_line:
query_inject_move_line += """
ORDER BY
a.code, p.name, ml.date, ml.id
"""
elif only_empty_partner_line:
query_inject_move_line += """
ORDER BY
a.code, ml.date, ml.id
"""
self.env.cr.execute(
query_inject_move_line,
(self.date_at,
self.date_at,
self.id,
self.date_at,
self.date_at,
self.id,
self.env.uid,
self.id,
self.date_at,)
)
def _compute_partners_and_accounts_cumul(self):
""" Compute cumulative amount for
report_open_items_partner and report_open_items_account.
"""
query_compute_partners_cumul = """
UPDATE
report_open_items_partner
SET
final_amount_residual =
(
SELECT
SUM(rml.amount_residual) AS final_amount_residual
FROM
report_open_items_move_line rml
WHERE
rml.report_partner_id = report_open_items_partner.id
)
WHERE
id IN
(
SELECT
rp.id
FROM
report_open_items_account ra
INNER JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
WHERE
ra.report_id = %s
)
"""
params_compute_partners_cumul = (self.id,)
self.env.cr.execute(query_compute_partners_cumul,
params_compute_partners_cumul)
query_compute_accounts_cumul = """
UPDATE
report_open_items_account
SET
final_amount_residual =
(
SELECT
SUM(rp.final_amount_residual) AS final_amount_residual
FROM
report_open_items_partner rp
WHERE
rp.report_account_id = report_open_items_account.id
)
WHERE
report_id = %s
"""
params_compute_accounts_cumul = (self.id,)
self.env.cr.execute(query_compute_accounts_cumul,
params_compute_accounts_cumul)
def _clean_partners_and_accounts(self,
only_delete_account_balance_at_0=False):
""" Delete empty data for
report_open_items_partner and report_open_items_account.
The "only_delete_account_balance_at_0" value is used
to delete also the data with cumulative amounts at 0.
"""
query_clean_partners = """
DELETE FROM
report_open_items_partner
WHERE
id IN
(
SELECT
DISTINCT rp.id
FROM
report_open_items_account ra
INNER JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
LEFT JOIN
report_open_items_move_line rml
ON rp.id = rml.report_partner_id
WHERE
ra.report_id = %s
"""
if not only_delete_account_balance_at_0:
query_clean_partners += """
AND rml.id IS NULL
"""
elif only_delete_account_balance_at_0:
query_clean_partners += """
AND (
rp.final_amount_residual IS NULL
OR rp.final_amount_residual = 0
)
"""
query_clean_partners += """
)
"""
params_clean_partners = (self.id,)
self.env.cr.execute(query_clean_partners, params_clean_partners)
query_clean_accounts = """
DELETE FROM
report_open_items_account
WHERE
id IN
(
SELECT
DISTINCT ra.id
FROM
report_open_items_account ra
LEFT JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
WHERE
ra.report_id = %s
"""
if not only_delete_account_balance_at_0:
query_clean_accounts += """
AND rp.id IS NULL
"""
elif only_delete_account_balance_at_0:
query_clean_accounts += """
AND (
ra.final_amount_residual IS NULL
OR ra.final_amount_residual = 0
)
"""
query_clean_accounts += """
)
"""
params_clean_accounts = (self.id,)
self.env.cr.execute(query_clean_accounts, params_clean_accounts)
def _compute_has_second_currency(self):
""" Compute "has_second_currency" flag which will used for display."""
query_update_has_second_currency = """
UPDATE
report_open_items
SET
has_second_currency =
(
SELECT
TRUE
FROM
report_open_items_move_line l
INNER JOIN
report_open_items_partner p
ON l.report_partner_id = p.id
INNER JOIN
report_open_items_account a
ON p.report_account_id = a.id
WHERE
a.report_id = %s
AND l.currency_id IS NOT NULL
LIMIT 1
)
WHERE id = %s
"""
params = (self.id,) * 2
self.env.cr.execute(query_update_has_second_currency, params)