[IMP] partner_statement: add Detailed Activity report

pull/1106/head
Miquel Raïch 2022-12-16 10:29:17 +01:00
parent e51598fc1e
commit f833ce444c
22 changed files with 1537 additions and 345 deletions

View File

@ -7,6 +7,7 @@
"category": "Accounting & Finance",
"summary": "OCA Financial Reports",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"maintainers": ["MiquelRForgeFlow"],
"website": "https://github.com/OCA/account-financial-reporting",
"license": "AGPL-3",
"depends": ["account", "report_xlsx", "report_xlsx_helper"],
@ -15,6 +16,7 @@
"security/statement_security.xml",
"views/activity_statement.xml",
"views/outstanding_statement.xml",
"views/detailed_activity_statement.xml",
"views/aging_buckets.xml",
"views/res_config_settings.xml",
"wizard/statement_wizard.xml",

View File

@ -1,6 +1,6 @@
This module extends the functionality of Invoicing to support the printing of customer and vendor statements.
There are two types of statements, Activity and Outstanding. Aging details can be shown in the reports, expressed in aging buckets,
so the customer or vendor can review how much is open, due or overdue.
There are three types of statements: Activity, Detailed Activity, and Outstanding. Aging details can be shown
in the reports, expressed in aging buckets, so the customer or vendor can review how much is open, due or overdue.
The activity statement provides details of all activity on the partner receivables or payables
between two selected dates. This includes all invoices, refunds and payments.
@ -8,6 +8,9 @@ Any outstanding balance dated prior to the chosen statement period will appear
as a forward balance at the top of the statement. The list is displayed in chronological
order and is split by currencies.
The detailed activity statement is an extension of the previous statement, and intends to explain the transactions
that have happened during the period, also providing with a Prior Balance section and an Ending Balance section.
The outstanding statement provides details of all outstanding partner receivables or payables
up to a particular date. This includes all unpaid invoices, unclaimed refunds and
outstanding payments. The list is displayed in chronological order and is split by currencies.

View File

@ -5,3 +5,8 @@
* [ADD] New features.
* Age by months or days
* Filter negative balances
14.0.2.0.0 (2022-12-16)
~~~~~~~~~~~~~~~~~~~~~~~
* [ADD] Detailed Activity Statement.

View File

@ -1,5 +1,7 @@
from . import report_statement_common
from . import activity_statement
from . import detailed_activity_statement
from . import outstanding_statement
from . import activity_statement_xlsx
from . import detailed_activity_statement_xlsx
from . import outstanting_statement_xlsx

View File

@ -5,6 +5,8 @@ from collections import defaultdict
from odoo import api, models
from .outstanding_statement import OutstandingStatement
class ActivityStatement(models.AbstractModel):
"""Model of Activity Statement"""
@ -17,40 +19,74 @@ class ActivityStatement(models.AbstractModel):
return str(
self._cr.mogrify(
"""
SELECT l.partner_id, l.currency_id, l.company_id,
sum(CASE WHEN l.currency_id is not null AND l.amount_currency > 0.0
THEN l.amount_currency
ELSE l.debit
END) as debit,
sum(CASE WHEN l.currency_id is not null AND l.amount_currency < 0.0
THEN l.amount_currency * (-1)
ELSE l.credit
END) as credit
SELECT l.partner_id, l.currency_id, l.company_id, l.id,
CASE WHEN l.balance > 0.0
THEN l.balance - sum(coalesce(pd.amount, 0.0))
ELSE l.balance + sum(coalesce(pc.amount, 0.0))
END AS open_amount,
CASE WHEN l.balance > 0.0
THEN l.amount_currency - sum(coalesce(pd.debit_amount_currency, 0.0))
ELSE l.amount_currency + sum(coalesce(pc.credit_amount_currency, 0.0))
END AS open_amount_currency
FROM account_move_line l
JOIN account_account aa ON (aa.id = l.account_id)
JOIN account_account_type at ON (at.id = aa.user_type_id)
JOIN account_move m ON (l.move_id = m.id)
LEFT JOIN (SELECT pr.*
FROM account_partial_reconcile pr
INNER JOIN account_move_line l2
ON pr.credit_move_id = l2.id
WHERE l2.date < %(date_start)s
) as pd ON pd.debit_move_id = l.id
LEFT JOIN (SELECT pr.*
FROM account_partial_reconcile pr
INNER JOIN account_move_line l2
ON pr.debit_move_id = l2.id
WHERE l2.date < %(date_start)s
) as pc ON pc.credit_move_id = l.id
WHERE l.partner_id IN %(partners)s
AND at.type = %(account_type)s
AND l.date < %(date_start)s AND not l.blocked
AND m.state IN ('posted')
GROUP BY l.partner_id, l.currency_id, l.company_id
AND (
(pd.id IS NOT NULL AND
pd.max_date < %(date_start)s) OR
(pc.id IS NOT NULL AND
pc.max_date < %(date_start)s) OR
(pd.id IS NULL AND pc.id IS NULL)
)
GROUP BY l.partner_id, l.currency_id, l.company_id, l.balance, l.id
""",
locals(),
),
"utf-8",
)
def _initial_balance_sql_q2(self, company_id):
def _initial_balance_sql_q2(self, sub):
return str(
self._cr.mogrify(
"""
SELECT Q1.partner_id, debit-credit AS balance,
COALESCE(Q1.currency_id, c.currency_id) AS currency_id
FROM Q1
JOIN res_company c ON (c.id = Q1.company_id)
WHERE c.id = %(company_id)s
""",
f"""
SELECT {sub}.partner_id, {sub}.currency_id,
sum(CASE WHEN {sub}.currency_id is not null
THEN {sub}.open_amount_currency
ELSE {sub}.open_amount
END) as balance, {sub}.company_id
FROM {sub}
GROUP BY {sub}.partner_id, {sub}.currency_id, {sub}.company_id""",
locals(),
),
"utf-8",
)
def _initial_balance_sql_q3(self, sub, company_id):
return str(
self._cr.mogrify(
f"""
SELECT {sub}.partner_id, {sub}.balance,
COALESCE({sub}.currency_id, c.currency_id) AS currency_id
FROM {sub}
JOIN res_company c ON (c.id = {sub}.company_id)
WHERE c.id = %(company_id)s""",
locals(),
),
"utf-8",
@ -63,23 +99,30 @@ class ActivityStatement(models.AbstractModel):
partners = tuple(partner_ids)
# pylint: disable=E8103
self.env.cr.execute(
"""WITH Q1 AS (%s), Q2 AS (%s)
SELECT partner_id, currency_id, balance
FROM Q2"""
"""WITH Q1 AS (%s),
Q2 AS (%s),
Q3 AS (%s)
SELECT partner_id, currency_id, sum(balance) as balance
FROM Q3
GROUP BY partner_id, currency_id"""
% (
self._initial_balance_sql_q1(partners, date_start, account_type),
self._initial_balance_sql_q2(company_id),
self._initial_balance_sql_q2("Q1"),
self._initial_balance_sql_q3("Q2", company_id),
)
)
for row in self.env.cr.dictfetchall():
balance_start[row.pop("partner_id")].append(row)
return balance_start
def _display_lines_sql_q1(self, partners, date_start, date_end, account_type):
def _display_activity_lines_sql_q1(
self, partners, date_start, date_end, account_type
):
return str(
self._cr.mogrify(
"""
SELECT m.name AS move_id, l.partner_id, l.date,
array_agg(l.id ORDER BY l.id) as ids,
CASE WHEN (aj.type IN ('sale', 'purchase'))
THEN l.name
ELSE '/'
@ -92,7 +135,7 @@ class ActivityStatement(models.AbstractModel):
WHEN (aj.type in ('bank', 'cash'))
THEN 'Payment'
ELSE ''
END as ref,
END as case_ref,
l.blocked, l.currency_id, l.company_id,
sum(CASE WHEN (l.currency_id is not null AND l.amount_currency > 0.0)
THEN l.amount_currency
@ -120,33 +163,23 @@ class ActivityStatement(models.AbstractModel):
CASE WHEN (aj.type IN ('sale', 'purchase'))
THEN l.name
ELSE '/'
END,
CASE
WHEN (aj.type IN ('sale', 'purchase')) AND l.name IS NOT NULL
THEN l.ref
WHEN aj.type IN ('sale', 'purchase') AND l.name IS NULL
THEN m.ref
WHEN (aj.type in ('bank', 'cash'))
THEN 'Payment'
ELSE ''
END,
l.blocked, l.currency_id, l.company_id
END, case_ref, l.blocked, l.currency_id, l.company_id
""",
locals(),
),
"utf-8",
)
def _display_lines_sql_q2(self, company_id):
def _display_activity_lines_sql_q2(self, sub, company_id):
return str(
self._cr.mogrify(
"""
SELECT Q1.partner_id, Q1.move_id, Q1.date, Q1.date_maturity,
Q1.name, Q1.ref, Q1.debit, Q1.credit,
Q1.debit-Q1.credit as amount, Q1.blocked,
COALESCE(Q1.currency_id, c.currency_id) AS currency_id
FROM Q1
JOIN res_company c ON (c.id = Q1.company_id)
f"""
SELECT {sub}.partner_id, {sub}.move_id, {sub}.date, {sub}.date_maturity,
{sub}.name, {sub}.case_ref as ref, {sub}.debit, {sub}.credit, {sub}.ids,
{sub}.debit-{sub}.credit as amount, {sub}.blocked,
COALESCE({sub}.currency_id, c.currency_id) AS currency_id
FROM {sub}
JOIN res_company c ON (c.id = {sub}.company_id)
WHERE c.id = %(company_id)s
""",
locals(),
@ -165,21 +198,117 @@ class ActivityStatement(models.AbstractModel):
"""
WITH Q1 AS (%s),
Q2 AS (%s)
SELECT partner_id, move_id, date, date_maturity, name, ref, debit,
credit, amount, blocked, currency_id
SELECT partner_id, move_id, date, date_maturity, ids,
COALESCE(name, '') as name, COALESCE(ref, '') as ref,
debit, credit, amount, blocked, currency_id
FROM Q2
ORDER BY date, date_maturity, move_id"""
% (
self._display_lines_sql_q1(
self._display_activity_lines_sql_q1(
partners, date_start, date_end, account_type
),
self._display_lines_sql_q2(company_id),
self._display_activity_lines_sql_q2("Q1", company_id),
)
)
for row in self.env.cr.dictfetchall():
res[row.pop("partner_id")].append(row)
return res
def _display_activity_reconciled_lines_sql_q1(self, sub):
return str(
self._cr.mogrify(
f"""
SELECT unnest(ids) as id
FROM {sub}
""",
locals(),
),
"utf-8",
)
def _display_activity_reconciled_lines_sql_q2(self, sub, date_end):
return str(
self._cr.mogrify(
f"""
SELECT l.id as rel_id, m.name AS move_id, l.partner_id, l.date, l.name,
l.blocked, l.currency_id, l.company_id, {sub}.id,
CASE WHEN l.ref IS NOT NULL
THEN l.ref
ELSE m.ref
END as ref,
CASE WHEN (l.currency_id is not null AND l.amount_currency > 0.0)
THEN avg(l.amount_currency)
ELSE avg(l.debit)
END as debit,
CASE WHEN (l.currency_id is not null AND l.amount_currency < 0.0)
THEN avg(l.amount_currency * (-1))
ELSE avg(l.credit)
END as credit,
CASE WHEN l.balance > 0.0
THEN sum(coalesce(pc.amount, 0.0))
ELSE -sum(coalesce(pd.amount, 0.0))
END AS open_amount,
CASE WHEN l.balance > 0.0
THEN sum(coalesce(pc.debit_amount_currency, 0.0))
ELSE -sum(coalesce(pd.credit_amount_currency, 0.0))
END AS open_amount_currency,
CASE WHEN l.date_maturity is null
THEN l.date
ELSE l.date_maturity
END as date_maturity
FROM {sub}
LEFT JOIN account_partial_reconcile pd ON (
pd.debit_move_id = {sub}.id AND pd.max_date <= %(date_end)s)
LEFT JOIN account_partial_reconcile pc ON (
pc.credit_move_id = {sub}.id AND pc.max_date <= %(date_end)s)
LEFT JOIN account_move_line l ON (
pd.credit_move_id = l.id OR pc.debit_move_id = l.id)
LEFT JOIN account_move m ON (l.move_id = m.id)
WHERE l.date <= %(date_end)s AND m.state IN ('posted')
GROUP BY l.id, l.partner_id, m.name, l.date, l.date_maturity, l.name,
CASE WHEN l.ref IS NOT NULL
THEN l.ref
ELSE m.ref
END, {sub}.id,
l.blocked, l.currency_id, l.balance, l.amount_currency, l.company_id
""",
locals(),
),
"utf-8",
)
def _get_account_display_reconciled_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
partners = tuple(partner_ids)
# pylint: disable=E8103
self.env.cr.execute(
"""
WITH Q1 AS (%s),
Q2 AS (%s),
Q3 AS (%s),
Q4 AS (%s),
Q5 AS (%s),
Q6 AS (%s)
SELECT partner_id, currency_id, move_id, date, date_maturity, debit,
credit, amount, open_amount, COALESCE(name, '') as name,
COALESCE(ref, '') as ref, blocked, id
FROM Q6
ORDER BY date, date_maturity, move_id"""
% (
self._display_activity_lines_sql_q1(
partners, date_start, date_end, account_type
),
self._display_activity_lines_sql_q2("Q1", company_id),
self._display_activity_reconciled_lines_sql_q1("Q2"),
self._display_activity_reconciled_lines_sql_q2("Q3", date_end),
self._display_outstanding_lines_sql_q2("Q4"),
self._display_outstanding_lines_sql_q3("Q5", company_id),
)
)
return self.env.cr.dictfetchall()
@api.model
def _get_report_values(self, docids, data=None):
if not data:
@ -191,3 +320,11 @@ class ActivityStatement(models.AbstractModel):
data.update(wiz.create({})._prepare_statement())
data["amount_field"] = "amount"
return super()._get_report_values(docids, data)
ActivityStatement._display_outstanding_lines_sql_q2 = (
OutstandingStatement._display_outstanding_lines_sql_q2
)
ActivityStatement._display_outstanding_lines_sql_q3 = (
OutstandingStatement._display_outstanding_lines_sql_q3
)

View File

@ -2,11 +2,23 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, fields, models
from odoo import _, models
from odoo.addons.report_xlsx_helper.report.report_xlsx_format import FORMATS
def copy_format(book, fmt):
properties = [f[4:] for f in dir(fmt) if f[0:4] == "set_"]
dft_fmt = book.add_format()
return book.add_format(
{
k: v
for k, v in fmt.__dict__.items()
if k in properties and dft_fmt.__dict__[k] != v
}
)
class ActivityStatementXslx(models.AbstractModel):
_name = "report.p_s.report_activity_statement_xlsx"
_description = "Activity Statement XLSL Report"
@ -36,7 +48,7 @@ class ActivityStatementXslx(models.AbstractModel):
}
sheet.merge_range(
row_pos, 0, row_pos, 6, statement_header, FORMATS["format_right_bold"]
row_pos, 0, row_pos, 6, statement_header, FORMATS["format_left_bold"]
)
row_pos += 1
sheet.write(
@ -47,20 +59,25 @@ class ActivityStatementXslx(models.AbstractModel):
row_pos,
2,
row_pos,
4,
3,
_("Description"),
FORMATS["format_theader_yellow_center"],
)
sheet.write(
row_pos, 5, _("Open Amount"), FORMATS["format_theader_yellow_center"]
row_pos, 4, _("Original Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(
row_pos, 5, _("Applied Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(
row_pos, 6, _("Open Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 6, _("Balance"), FORMATS["format_theader_yellow_center"])
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("start"), FORMATS["format_tcell_date_left"]
row_pos, 1, partner_data.get("prior_day"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos, 2, row_pos, 4, _("Balance Forward"), FORMATS["format_tcell_left"]
row_pos, 2, row_pos, 5, _("Balance Forward"), FORMATS["format_tcell_left"]
)
sheet.write(
row_pos,
@ -68,12 +85,21 @@ class ActivityStatementXslx(models.AbstractModel):
currency_data.get("balance_forward"),
FORMATS["current_money_format"],
)
format_tcell_left = FORMATS["format_tcell_left"]
format_tcell_date_left = FORMATS["format_tcell_date_left"]
format_distributed = FORMATS["format_distributed"]
current_money_format = FORMATS["current_money_format"]
for line in currency_data.get("lines"):
if line.get("blocked"):
format_tcell_left = FORMATS["format_tcell_left_blocked"]
format_tcell_date_left = FORMATS["format_tcell_date_left_blocked"]
format_distributed = FORMATS["format_distributed_blocked"]
current_money_format = FORMATS["current_money_format_blocked"]
row_pos += 1
name_to_show = (
line.get("name", "") == "/" or not line.get("name", "")
) and line.get("ref", "")
if line.get("name", "") != "/":
if line.get("name", "") and line.get("name", "") != "/":
if not line.get("ref", ""):
name_to_show = line.get("name", "")
else:
@ -83,30 +109,26 @@ class ActivityStatementXslx(models.AbstractModel):
name_to_show = line.get("name", "")
elif line.get("ref", "") not in line.get("name", ""):
name_to_show = line.get("ref", "")
sheet.write(row_pos, 0, line.get("move_id", ""), format_tcell_left)
sheet.write(row_pos, 1, line.get("date", ""), format_tcell_date_left)
sheet.merge_range(row_pos, 2, row_pos, 3, name_to_show, format_distributed)
sheet.write(row_pos, 4, line.get("amount", ""), current_money_format)
sheet.write(
row_pos, 0, line.get("move_id", ""), FORMATS["format_tcell_left"]
)
sheet.write(
row_pos, 1, line.get("date", ""), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos, 2, row_pos, 4, name_to_show, FORMATS["format_distributed"]
)
sheet.write(
row_pos, 5, line.get("amount", ""), FORMATS["current_money_format"]
)
sheet.write(
row_pos, 6, line.get("balance", ""), FORMATS["current_money_format"]
row_pos, 5, line.get("applied_amount", ""), current_money_format
)
sheet.write(row_pos, 6, line.get("open_amount", ""), current_money_format)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("end"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos, 2, row_pos, 4, _("Ending Balance"), FORMATS["format_tcell_left"]
row_pos, 2, row_pos, 5, _("Ending Balance"), FORMATS["format_tcell_left"]
)
sheet.write(
row_pos, 6, currency_data.get("amount_due"), FORMATS["current_money_format"]
row_pos,
6,
currency_data.get("amount_due"),
FORMATS["current_money_format"],
)
return row_pos
@ -180,7 +202,7 @@ class ActivityStatementXslx(models.AbstractModel):
)
return row_pos
def _size_columns(self, sheet):
def _size_columns(self, sheet, data):
for i in range(7):
sheet.set_column(0, i, 20)
@ -203,7 +225,7 @@ class ActivityStatementXslx(models.AbstractModel):
0,
row_pos,
6,
_("Statement of Account from %s") % (company.display_name),
_("Statement of Account from %s") % (company.display_name,),
FORMATS["format_ws_title"],
)
row_pos += 1
@ -211,10 +233,10 @@ class ActivityStatementXslx(models.AbstractModel):
sheet.write(
row_pos,
2,
fields.Date.from_string(data.get("date_end")),
data.get("data", {}).get(partners.ids[0], {}).get("today"),
FORMATS["format_date_left"],
)
self._size_columns(sheet)
self._size_columns(sheet, data)
for partner in partners:
invoice_address = data.get(
"get_inv_addr", lambda x: self.env["res.partner"]
@ -286,6 +308,23 @@ class ActivityStatementXslx(models.AbstractModel):
FORMATS["current_money_format"] = workbook.add_format(
{"align": "right", "num_format": money_string}
)
bg_grey = "#CCCCCC"
FORMATS["format_tcell_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_left"]
)
FORMATS["format_tcell_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_tcell_date_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_date_left"]
)
FORMATS["format_tcell_date_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_distributed_blocked"] = copy_format(
workbook, FORMATS["format_distributed"]
)
FORMATS["format_distributed_blocked"].set_bg_color(bg_grey)
FORMATS["current_money_format_blocked"] = copy_format(
workbook, FORMATS["current_money_format"]
)
FORMATS["current_money_format_blocked"].set_bg_color(bg_grey)
row_pos = self._write_currency_lines(
row_pos, sheet, partner, currency, data
)

View File

@ -0,0 +1,43 @@
# Copyright 2022 ForgeFlow, S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models
from .outstanding_statement import OutstandingStatement
class DetailedActivityStatement(models.AbstractModel):
"""Model of Detailed Activity Statement"""
_inherit = "report.partner_statement.activity_statement"
_name = "report.partner_statement.detailed_activity_statement"
_description = "Partner Detailed Activity Statement"
def _get_account_display_prior_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
return self._get_account_display_lines2(
company_id, partner_ids, date_start, date_end, account_type
)
def _get_account_display_ending_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
return self._get_account_display_lines2(
company_id, partner_ids, date_start, date_end, account_type
)
def _add_currency_prior_line(self, line, currency):
return self._add_currency_line2(line, currency)
def _add_currency_ending_line(self, line, currency):
return self._add_currency_line2(line, currency)
DetailedActivityStatement._get_account_display_lines2 = (
OutstandingStatement._get_account_display_lines
)
DetailedActivityStatement._display_outstanding_lines_sql_q1 = (
OutstandingStatement._display_outstanding_lines_sql_q1
)
DetailedActivityStatement._add_currency_line2 = OutstandingStatement._add_currency_line

View File

@ -0,0 +1,568 @@
# Author: Miquel Raïch
# Copyright 2022 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
from odoo.addons.report_xlsx_helper.report.report_xlsx_format import FORMATS
def copy_format(book, fmt):
properties = [f[4:] for f in dir(fmt) if f[0:4] == "set_"]
dft_fmt = book.add_format()
return book.add_format(
{
k: v
for k, v in fmt.__dict__.items()
if k in properties and dft_fmt.__dict__[k] != v
}
)
class DetailedActivityStatementXslx(models.AbstractModel):
_name = "report.p_s.report_detailed_activity_statement_xlsx"
_description = "Detailed Activity Statement XLSL Report"
_inherit = "report.p_s.report_activity_statement_xlsx"
def _get_report_name(self, report, data=False):
company_id = data.get("company_id", False)
report_name = _("Detailed Activity Statement")
if company_id:
company = self.env["res.company"].browse(company_id)
suffix = " - {} - {}".format(company.name, company.currency_id.name)
report_name = report_name + suffix
return report_name
def _write_currency_lines(self, row_pos, sheet, partner, currency, data):
partner_data = data.get("data", {}).get(partner.id, {})
currency_data = partner_data.get("currencies", {}).get(currency.id)
account_type = data.get("account_type", False)
row_pos += 2
statement_header = _(
"Detailed %(payable)sStatement between %(start)s and %(end)s in %(currency)s"
) % {
"payable": account_type == "payable" and _("Supplier ") or "",
"start": partner_data.get("start"),
"end": partner_data.get("end"),
"currency": currency.display_name,
}
sheet.merge_range(
row_pos,
0,
row_pos,
6,
statement_header,
FORMATS["format_left_bold"],
)
row_pos += 1
sheet.write(
row_pos, 0, _("Reference Number"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 1, _("Date"), FORMATS["format_theader_yellow_center"])
sheet.merge_range(
row_pos,
2,
row_pos,
3,
_("Description"),
FORMATS["format_theader_yellow_center"],
)
sheet.write(
row_pos, 4, _("Original Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(
row_pos, 5, _("Applied Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(
row_pos, 6, _("Open Amount"), FORMATS["format_theader_yellow_center"]
)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("prior_day"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
5,
_("Initial Balance"),
FORMATS["format_tcell_left"],
)
sheet.write(
row_pos,
6,
currency_data.get("balance_forward"),
FORMATS["current_money_format"],
)
for line in currency_data.get("lines"):
if line.get("blocked") and not line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked"]
format_tcell_date_left = FORMATS["format_tcell_date_left_blocked"]
format_distributed = FORMATS["format_distributed_blocked"]
current_money_format = FORMATS["current_money_format_blocked"]
elif line.get("reconciled_line") and not line.get("blocked"):
format_tcell_left = FORMATS["format_tcell_left_reconciled"]
format_tcell_date_left = FORMATS["format_tcell_date_left_reconciled"]
format_distributed = FORMATS["format_distributed_reconciled"]
current_money_format = FORMATS["current_money_format_reconciled"]
elif line.get("blocked") and line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked_reconciled"]
format_tcell_date_left = FORMATS[
"format_tcell_date_left_blocked_reconciled"
]
format_distributed = FORMATS["format_distributed_blocked_reconciled"]
current_money_format = FORMATS[
"current_money_format_blocked_reconciled"
]
else:
format_tcell_left = FORMATS["format_tcell_left"]
format_tcell_date_left = FORMATS["format_tcell_date_left"]
format_distributed = FORMATS["format_distributed"]
current_money_format = FORMATS["current_money_format"]
row_pos += 1
name_to_show = (
line.get("name", "") == "/" or not line.get("name", "")
) and line.get("ref", "")
if line.get("name", "") and line.get("name", "") != "/":
if not line.get("ref", ""):
name_to_show = line.get("name", "")
else:
if (line.get("name", "") in line.get("ref", "")) or (
line.get("name", "") == line.get("ref", "")
):
name_to_show = line.get("name", "")
elif line.get("ref", "") not in line.get("name", ""):
name_to_show = line.get("ref", "")
sheet.write(row_pos, 0, line.get("move_id", ""), format_tcell_left)
sheet.write(row_pos, 1, line.get("date", ""), format_tcell_date_left)
sheet.merge_range(row_pos, 2, row_pos, 3, name_to_show, format_distributed)
sheet.write(
row_pos,
4,
line.get("amount", "") if not line.get("reconciled_line") else "",
current_money_format,
)
sheet.write(
row_pos, 5, line.get("applied_amount", ""), current_money_format
)
sheet.write(
row_pos,
6,
line.get("open_amount", "") if not line.get("reconciled_line") else "",
current_money_format,
)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("end"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
5,
_("Ending Balance"),
FORMATS["format_tcell_left"],
)
sheet.write(
row_pos,
6,
currency_data.get("amount_due"),
FORMATS["current_money_format"],
)
return row_pos
def _write_currency_prior_lines(self, row_pos, sheet, partner, currency, data):
partner_data = data.get("data", {}).get(partner.id, {})
currency_data = partner_data.get("currencies", {}).get(currency.id)
account_type = data.get("account_type", False)
row_pos += 2
statement_header = _(
"%(payable)sStatement up to %(prior_day)s in %(currency)s"
) % {
"payable": account_type == "payable" and _("Supplier ") or "",
"prior_day": partner_data.get("prior_day"),
"currency": currency.display_name,
}
sheet.merge_range(
row_pos,
0,
row_pos,
6,
statement_header,
FORMATS["format_left_bold"],
)
row_pos += 1
sheet.write(
row_pos, 0, _("Reference Number"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 1, _("Date"), FORMATS["format_theader_yellow_center"])
sheet.write(row_pos, 2, _("Due Date"), FORMATS["format_theader_yellow_center"])
sheet.write(
row_pos,
3,
_("Description"),
FORMATS["format_theader_yellow_center"],
)
sheet.write(row_pos, 4, _("Original"), FORMATS["format_theader_yellow_center"])
sheet.write(
row_pos, 5, _("Open Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 6, _("Balance"), FORMATS["format_theader_yellow_center"])
format_tcell_left = FORMATS["format_tcell_left"]
format_tcell_date_left = FORMATS["format_tcell_date_left"]
format_distributed = FORMATS["format_distributed"]
current_money_format = FORMATS["current_money_format"]
for line in currency_data.get("prior_lines"):
if line.get("blocked") and not line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked"]
format_tcell_date_left = FORMATS["format_tcell_date_left_blocked"]
format_distributed = FORMATS["format_distributed_blocked"]
current_money_format = FORMATS["current_money_format_blocked"]
elif line.get("reconciled_line") and not line.get("blocked"):
format_tcell_left = FORMATS["format_tcell_left_reconciled"]
format_tcell_date_left = FORMATS["format_tcell_date_left_reconciled"]
format_distributed = FORMATS["format_distributed_reconciled"]
current_money_format = FORMATS["current_money_format_reconciled"]
elif line.get("blocked") and line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked_reconciled"]
format_tcell_date_left = FORMATS[
"format_tcell_date_left_blocked_reconciled"
]
format_distributed = FORMATS["format_distributed_blocked_reconciled"]
current_money_format = FORMATS[
"current_money_format_blocked_reconciled"
]
row_pos += 1
name_to_show = (
line.get("name", "") == "/" or not line.get("name", "")
) and line.get("ref", "")
if line.get("name", "") and line.get("name", "") != "/":
if not line.get("ref", ""):
name_to_show = line.get("name", "")
else:
if (line.get("ref", "") in line.get("name", "")) or (
line.get("name", "") == line.get("ref", "")
):
name_to_show = line.get("name", "")
else:
name_to_show = line.get("ref", "")
sheet.write(row_pos, 0, line.get("move_id", ""), format_tcell_left)
sheet.write(row_pos, 1, line.get("date", ""), format_tcell_date_left)
sheet.write(
row_pos,
2,
line.get("date_maturity", ""),
format_tcell_date_left,
)
sheet.write(row_pos, 3, name_to_show, format_distributed)
sheet.write(row_pos, 4, line.get("amount", ""), current_money_format)
sheet.write(row_pos, 5, line.get("open_amount", ""), current_money_format)
sheet.write(row_pos, 6, line.get("balance", ""), current_money_format)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("prior_day"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
5,
_("Ending Balance"),
FORMATS["format_tcell_left"],
)
sheet.write(
row_pos,
6,
currency_data.get("balance_forward"),
FORMATS["current_money_format"],
)
return row_pos
def _write_currency_ending_lines(self, row_pos, sheet, partner, currency, data):
partner_data = data.get("data", {}).get(partner.id, {})
currency_data = partner_data.get("currencies", {}).get(currency.id)
account_type = data.get("account_type", False)
row_pos += 2
statement_header = _("%(payable)sStatement up to %(end)s in %(currency)s") % {
"payable": account_type == "payable" and _("Supplier ") or "",
"end": partner_data.get("end"),
"currency": currency.display_name,
}
sheet.merge_range(
row_pos,
0,
row_pos,
6,
statement_header,
FORMATS["format_left_bold"],
)
row_pos += 1
sheet.write(
row_pos, 0, _("Reference Number"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 1, _("Date"), FORMATS["format_theader_yellow_center"])
sheet.write(row_pos, 2, _("Due Date"), FORMATS["format_theader_yellow_center"])
sheet.write(
row_pos,
3,
_("Description"),
FORMATS["format_theader_yellow_center"],
)
sheet.write(row_pos, 4, _("Original"), FORMATS["format_theader_yellow_center"])
sheet.write(
row_pos, 5, _("Open Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 6, _("Balance"), FORMATS["format_theader_yellow_center"])
format_tcell_left = FORMATS["format_tcell_left"]
format_tcell_date_left = FORMATS["format_tcell_date_left"]
format_distributed = FORMATS["format_distributed"]
current_money_format = FORMATS["current_money_format"]
for line in currency_data.get("ending_lines"):
if line.get("blocked") and not line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked"]
format_tcell_date_left = FORMATS["format_tcell_date_left_blocked"]
format_distributed = FORMATS["format_distributed_blocked"]
current_money_format = FORMATS["current_money_format_blocked"]
elif line.get("reconciled_line") and not line.get("blocked"):
format_tcell_left = FORMATS["format_tcell_left_reconciled"]
format_tcell_date_left = FORMATS["format_tcell_date_left_reconciled"]
format_distributed = FORMATS["format_distributed_reconciled"]
current_money_format = FORMATS["current_money_format_reconciled"]
elif line.get("blocked") and line.get("reconciled_line"):
format_tcell_left = FORMATS["format_tcell_left_blocked_reconciled"]
format_tcell_date_left = FORMATS[
"format_tcell_date_left_blocked_reconciled"
]
format_distributed = FORMATS["format_distributed_blocked_reconciled"]
current_money_format = FORMATS[
"current_money_format_blocked_reconciled"
]
row_pos += 1
name_to_show = (
line.get("name", "") == "/" or not line.get("name", "")
) and line.get("ref", "")
if line.get("name", "") and line.get("name", "") != "/":
if not line.get("ref", ""):
name_to_show = line.get("name", "")
else:
if (line.get("ref", "") in line.get("name", "")) or (
line.get("name", "") == line.get("ref", "")
):
name_to_show = line.get("name", "")
else:
name_to_show = line.get("ref", "")
sheet.write(row_pos, 0, line.get("move_id", ""), format_tcell_left)
sheet.write(row_pos, 1, line.get("date", ""), format_tcell_date_left)
sheet.write(
row_pos,
2,
line.get("date_maturity", ""),
format_tcell_date_left,
)
sheet.write(row_pos, 3, name_to_show, format_distributed)
sheet.write(row_pos, 4, line.get("amount", ""), current_money_format)
sheet.write(row_pos, 5, line.get("open_amount", ""), current_money_format)
sheet.write(row_pos, 6, line.get("balance", ""), current_money_format)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("end"), FORMATS["format_tcell_date_left"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
5,
_("Ending Balance"),
FORMATS["format_tcell_left"],
)
sheet.write(
row_pos,
6,
currency_data.get("amount_due"),
FORMATS["current_money_format"],
)
return row_pos
def _size_columns(self, sheet, data):
for i in range(7):
sheet.set_column(0, i, 20)
def generate_xlsx_report(self, workbook, data, objects):
report_model = self.env["report.partner_statement.detailed_activity_statement"]
self._define_formats(workbook)
FORMATS["format_distributed"] = workbook.add_format({"align": "vdistributed"})
company_id = data.get("company_id", False)
if company_id:
company = self.env["res.company"].browse(company_id)
else:
company = self.env.user.company_id
data.update(report_model._get_report_values(data.get("partner_ids"), data))
partners = self.env["res.partner"].browse(data.get("partner_ids"))
sheet = workbook.add_worksheet(_("Detailed Activity Statement"))
sheet.set_landscape()
row_pos = 0
sheet.merge_range(
row_pos,
0,
row_pos,
6,
_("Statement of Account from %s") % (company.display_name,),
FORMATS["format_ws_title"],
)
row_pos += 1
sheet.write(row_pos, 1, _("Date:"), FORMATS["format_theader_yellow_right"])
sheet.write(
row_pos,
2,
data.get("data", {}).get(partners.ids[0], {}).get("today"),
FORMATS["format_date_left"],
)
self._size_columns(sheet, data)
for partner in partners:
invoice_address = data.get(
"get_inv_addr", lambda x: self.env["res.partner"]
)(partner)
row_pos += 3
sheet.write(
row_pos, 1, _("Statement to:"), FORMATS["format_theader_yellow_right"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
3,
invoice_address.display_name,
FORMATS["format_left"],
)
if invoice_address.vat:
sheet.write(
row_pos,
4,
_("VAT:"),
FORMATS["format_theader_yellow_right"],
)
sheet.write(
row_pos,
5,
invoice_address.vat,
FORMATS["format_left"],
)
row_pos += 1
sheet.write(
row_pos, 1, _("Statement from:"), FORMATS["format_theader_yellow_right"]
)
sheet.merge_range(
row_pos,
2,
row_pos,
3,
company.partner_id.display_name,
FORMATS["format_left"],
)
if company.vat:
sheet.write(
row_pos,
4,
_("VAT:"),
FORMATS["format_theader_yellow_right"],
)
sheet.write(
row_pos,
5,
company.vat,
FORMATS["format_left"],
)
partner_data = data.get("data", {}).get(partner.id)
currencies = partner_data.get("currencies", {}).keys()
if currencies:
row_pos += 1
for currency_id in currencies:
currency = self.env["res.currency"].browse(currency_id)
if currency.position == "after":
money_string = "#,##0.%s " % (
"0" * currency.decimal_places
) + "[${}]".format(currency.symbol)
elif currency.position == "before":
money_string = "[${}]".format(currency.symbol) + " #,##0.%s" % (
"0" * currency.decimal_places
)
FORMATS["current_money_format"] = workbook.add_format(
{"align": "right", "num_format": money_string}
)
bg_grey = "#CCCCCC"
FORMATS["format_tcell_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_left"]
)
FORMATS["format_tcell_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_tcell_date_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_date_left"]
)
FORMATS["format_tcell_date_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_distributed_blocked"] = copy_format(
workbook, FORMATS["format_distributed"]
)
FORMATS["format_distributed_blocked"].set_bg_color(bg_grey)
FORMATS["current_money_format_blocked"] = copy_format(
workbook, FORMATS["current_money_format"]
)
FORMATS["current_money_format_blocked"].set_bg_color(bg_grey)
FORMATS["format_tcell_left_reconciled"] = copy_format(
workbook, FORMATS["format_tcell_left"]
)
FORMATS["format_tcell_left_reconciled"].set_italic(True)
FORMATS["format_tcell_left_reconciled"].set_font_size(10)
FORMATS["format_tcell_left_reconciled"].set_indent(1)
FORMATS["format_tcell_date_left_reconciled"] = copy_format(
workbook, FORMATS["format_tcell_date_left"]
)
FORMATS["format_tcell_date_left_reconciled"].set_italic(True)
FORMATS["format_tcell_date_left_reconciled"].set_font_size(10)
FORMATS["format_distributed_reconciled"] = copy_format(
workbook, FORMATS["format_distributed"]
)
FORMATS["format_distributed_reconciled"].set_italic(True)
FORMATS["format_distributed_reconciled"].set_font_size(10)
FORMATS["current_money_format_reconciled"] = copy_format(
workbook, FORMATS["current_money_format"]
)
FORMATS["current_money_format_reconciled"].set_italic(True)
FORMATS["current_money_format_reconciled"].set_font_size(10)
FORMATS["format_tcell_left_blocked_reconciled"] = copy_format(
workbook, FORMATS["format_tcell_left"]
)
FORMATS["format_tcell_left_blocked_reconciled"].set_bg_color(bg_grey)
FORMATS["format_tcell_left_blocked_reconciled"].set_italic(True)
FORMATS["format_tcell_left_blocked_reconciled"].set_font_size(10)
FORMATS["format_tcell_left_blocked_reconciled"].set_indent(1)
FORMATS["format_tcell_date_left_blocked_reconciled"] = copy_format(
workbook, FORMATS["format_tcell_date_left"]
)
FORMATS["format_tcell_date_left_blocked_reconciled"].set_bg_color(
bg_grey
)
FORMATS["format_tcell_date_left_blocked_reconciled"].set_italic(True)
FORMATS["format_tcell_date_left_blocked_reconciled"].set_font_size(10)
FORMATS["format_distributed_blocked_reconciled"] = copy_format(
workbook, FORMATS["format_distributed"]
)
FORMATS["format_distributed_blocked_reconciled"].set_bg_color(bg_grey)
FORMATS["format_distributed_blocked_reconciled"].set_italic(True)
FORMATS["format_distributed_blocked_reconciled"].set_font_size(10)
FORMATS["current_money_format_blocked_reconciled"] = copy_format(
workbook, FORMATS["current_money_format"]
)
FORMATS["current_money_format_blocked_reconciled"].set_bg_color(bg_grey)
FORMATS["current_money_format_blocked_reconciled"].set_italic(True)
FORMATS["current_money_format_blocked_reconciled"].set_font_size(10)
row_pos = self._write_currency_prior_lines(
row_pos, sheet, partner, currency, data
)
row_pos = self._write_currency_lines(
row_pos, sheet, partner, currency, data
)
row_pos = self._write_currency_ending_lines(
row_pos, sheet, partner, currency, data
)
row_pos = self._write_currency_buckets(
row_pos, sheet, partner, currency, data
)

View File

@ -12,13 +12,13 @@ class OutstandingStatement(models.AbstractModel):
_name = "report.partner_statement.outstanding_statement"
_description = "Partner Outstanding Statement"
def _display_lines_sql_q1(self, partners, date_end, account_type):
def _display_outstanding_lines_sql_q1(self, partners, date_end, account_type):
partners = tuple(partners)
return str(
self._cr.mogrify(
"""
SELECT l.id, m.name AS move_id, l.partner_id, l.date, l.name,
l.blocked, l.currency_id, l.company_id,
l.blocked, l.currency_id, l.company_id,
CASE WHEN l.ref IS NOT NULL
THEN l.ref
ELSE m.ref
@ -60,13 +60,13 @@ class OutstandingStatement(models.AbstractModel):
WHERE l2.date <= %(date_end)s
) as pc ON pc.credit_move_id = l.id
WHERE l.partner_id IN %(partners)s AND at.type = %(account_type)s
AND (
(pd.id IS NOT NULL AND
pd.max_date <= %(date_end)s) OR
(pc.id IS NOT NULL AND
pc.max_date <= %(date_end)s) OR
(pd.id IS NULL AND pc.id IS NULL)
) AND l.date <= %(date_end)s AND m.state IN ('posted')
AND (
(pd.id IS NOT NULL AND
pd.max_date <= %(date_end)s) OR
(pc.id IS NOT NULL AND
pc.max_date <= %(date_end)s) OR
(pd.id IS NULL AND pc.id IS NULL)
) AND l.date <= %(date_end)s AND m.state IN ('posted')
GROUP BY l.id, l.partner_id, m.name, l.date, l.date_maturity, l.name,
CASE WHEN l.ref IS NOT NULL
THEN l.ref
@ -79,36 +79,36 @@ class OutstandingStatement(models.AbstractModel):
"utf-8",
)
def _display_lines_sql_q2(self):
def _display_outstanding_lines_sql_q2(self, sub):
return str(
self._cr.mogrify(
"""
SELECT Q1.partner_id, Q1.currency_id, Q1.move_id,
Q1.date, Q1.date_maturity, Q1.debit, Q1.credit,
Q1.name, Q1.ref, Q1.blocked, Q1.company_id,
CASE WHEN Q1.currency_id is not null
THEN Q1.open_amount_currency
ELSE Q1.open_amount
END as open_amount
FROM Q1
f"""
SELECT {sub}.partner_id, {sub}.currency_id, {sub}.move_id,
{sub}.date, {sub}.date_maturity, {sub}.debit, {sub}.credit,
{sub}.name, {sub}.ref, {sub}.blocked, {sub}.company_id,
CASE WHEN {sub}.currency_id is not null
THEN {sub}.open_amount_currency
ELSE {sub}.open_amount
END as open_amount, {sub}.id
FROM {sub}
""",
locals(),
),
"utf-8",
)
def _display_lines_sql_q3(self, company_id):
def _display_outstanding_lines_sql_q3(self, sub, company_id):
return str(
self._cr.mogrify(
"""
SELECT Q2.partner_id, Q2.move_id, Q2.date, Q2.date_maturity,
Q2.name, Q2.ref, Q2.debit, Q2.credit,
Q2.debit-Q2.credit AS amount, blocked,
COALESCE(Q2.currency_id, c.currency_id) AS currency_id,
Q2.open_amount
FROM Q2
JOIN res_company c ON (c.id = Q2.company_id)
WHERE c.id = %(company_id)s AND Q2.open_amount != 0.0
f"""
SELECT {sub}.partner_id, {sub}.move_id, {sub}.date,
{sub}.date_maturity, {sub}.name, {sub}.ref, {sub}.debit,
{sub}.credit, {sub}.debit-{sub}.credit AS amount,
COALESCE({sub}.currency_id, c.currency_id) AS currency_id,
{sub}.open_amount, {sub}.blocked, {sub}.id
FROM {sub}
JOIN res_company c ON (c.id = {sub}.company_id)
WHERE c.id = %(company_id)s AND {sub}.open_amount != 0.0
""",
locals(),
),
@ -127,13 +127,16 @@ class OutstandingStatement(models.AbstractModel):
Q2 AS (%s),
Q3 AS (%s)
SELECT partner_id, currency_id, move_id, date, date_maturity, debit,
credit, amount, open_amount, name, ref, blocked
credit, amount, open_amount, COALESCE(name, '') as name,
COALESCE(ref, '') as ref, blocked, id
FROM Q3
ORDER BY date, date_maturity, move_id"""
% (
self._display_lines_sql_q1(partners, date_end, account_type),
self._display_lines_sql_q2(),
self._display_lines_sql_q3(company_id),
self._display_outstanding_lines_sql_q1(
partners, date_end, account_type
),
self._display_outstanding_lines_sql_q2("Q1"),
self._display_outstanding_lines_sql_q3("Q2", company_id),
)
)
for row in self.env.cr.dictfetchall():

View File

@ -2,11 +2,23 @@
# Copyright 2021 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, fields, models
from odoo import _, models
from odoo.addons.report_xlsx_helper.report.report_xlsx_format import FORMATS
def copy_format(book, fmt):
properties = [f[4:] for f in dir(fmt) if f[0:4] == "set_"]
dft_fmt = book.add_format()
return book.add_format(
{
k: v
for k, v in fmt.__dict__.items()
if k in properties and dft_fmt.__dict__[k] != v
}
)
class OutstandingStatementXslx(models.AbstractModel):
_name = "report.p_s.report_outstanding_statement_xlsx"
_description = "Outstanding Statement XLSL Report"
@ -33,7 +45,7 @@ class OutstandingStatementXslx(models.AbstractModel):
}
sheet.merge_range(
row_pos, 0, row_pos, 6, statement_header, FORMATS["format_right_bold"]
row_pos, 0, row_pos, 6, statement_header, FORMATS["format_left_bold"]
)
row_pos += 1
sheet.write(
@ -49,12 +61,21 @@ class OutstandingStatementXslx(models.AbstractModel):
row_pos, 5, _("Open Amount"), FORMATS["format_theader_yellow_center"]
)
sheet.write(row_pos, 6, _("Balance"), FORMATS["format_theader_yellow_center"])
format_tcell_left = FORMATS["format_tcell_left"]
format_tcell_date_left = FORMATS["format_tcell_date_left"]
format_distributed = FORMATS["format_distributed"]
current_money_format = FORMATS["current_money_format"]
for line in currency_data.get("lines"):
if line.get("blocked"):
format_tcell_left = FORMATS["format_tcell_left_blocked"]
format_tcell_date_left = FORMATS["format_tcell_date_left_blocked"]
format_distributed = FORMATS["format_distributed_blocked"]
current_money_format = FORMATS["current_money_format_blocked"]
row_pos += 1
name_to_show = (
line.get("name", "") == "/" or not line.get("name", "")
) and line.get("ref", "")
if line.get("name", "") != "/":
if line.get("name", "") and line.get("name", "") != "/":
if not line.get("ref", ""):
name_to_show = line.get("name", "")
else:
@ -64,28 +85,18 @@ class OutstandingStatementXslx(models.AbstractModel):
name_to_show = line.get("name", "")
else:
name_to_show = line.get("ref", "")
sheet.write(
row_pos, 0, line.get("move_id", ""), FORMATS["format_tcell_left"]
)
sheet.write(
row_pos, 1, line.get("date", ""), FORMATS["format_tcell_date_left"]
)
sheet.write(row_pos, 0, line.get("move_id", ""), format_tcell_left)
sheet.write(row_pos, 1, line.get("date", ""), format_tcell_date_left)
sheet.write(
row_pos,
2,
line.get("date_maturity", ""),
FORMATS["format_tcell_date_left"],
)
sheet.write(row_pos, 3, name_to_show, FORMATS["format_distributed"])
sheet.write(
row_pos, 4, line.get("amount", ""), FORMATS["current_money_format"]
)
sheet.write(
row_pos, 5, line.get("open_amount", ""), FORMATS["current_money_format"]
)
sheet.write(
row_pos, 6, line.get("balance", ""), FORMATS["current_money_format"]
format_tcell_date_left,
)
sheet.write(row_pos, 3, name_to_show, format_distributed)
sheet.write(row_pos, 4, line.get("amount", ""), current_money_format)
sheet.write(row_pos, 5, line.get("open_amount", ""), current_money_format)
sheet.write(row_pos, 6, line.get("balance", ""), current_money_format)
row_pos += 1
sheet.write(
row_pos, 1, partner_data.get("end"), FORMATS["format_tcell_date_left"]
@ -169,7 +180,7 @@ class OutstandingStatementXslx(models.AbstractModel):
)
return row_pos
def _size_columns(self, sheet):
def _size_columns(self, sheet, data):
for i in range(7):
sheet.set_column(0, i, 20)
@ -192,7 +203,7 @@ class OutstandingStatementXslx(models.AbstractModel):
0,
row_pos,
6,
_("Statement of Account from %s") % (company.display_name),
_("Statement of Account from %s") % (company.display_name,),
FORMATS["format_ws_title"],
)
row_pos += 1
@ -200,10 +211,10 @@ class OutstandingStatementXslx(models.AbstractModel):
sheet.write(
row_pos,
2,
fields.Date.from_string(data.get("date_end")),
data.get("data", {}).get(partners.ids[0], {}).get("today"),
FORMATS["format_date_left"],
)
self._size_columns(sheet)
self._size_columns(sheet, data)
for partner in partners:
invoice_address = data.get(
"get_inv_addr", lambda x: self.env["res.partner"]
@ -275,6 +286,23 @@ class OutstandingStatementXslx(models.AbstractModel):
FORMATS["current_money_format"] = workbook.add_format(
{"align": "right", "num_format": money_string}
)
bg_grey = "#CCCCCC"
FORMATS["format_tcell_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_left"]
)
FORMATS["format_tcell_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_tcell_date_left_blocked"] = copy_format(
workbook, FORMATS["format_tcell_date_left"]
)
FORMATS["format_tcell_date_left_blocked"].set_bg_color(bg_grey)
FORMATS["format_distributed_blocked"] = copy_format(
workbook, FORMATS["format_distributed"]
)
FORMATS["format_distributed_blocked"].set_bg_color(bg_grey)
FORMATS["current_money_format_blocked"] = copy_format(
workbook, FORMATS["current_money_format"]
)
FORMATS["current_money_format_blocked"].set_bg_color(bg_grey)
row_pos = self._write_currency_lines(
row_pos, sheet, partner, currency, data
)

View File

@ -34,6 +34,21 @@ class ReportStatementCommon(models.AbstractModel):
):
return {}
def _get_account_display_prior_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
return {}
def _get_account_display_reconciled_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
return {}
def _get_account_display_ending_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
return {}
def _show_buckets_sql_q1(self, partners, date_end, account_type):
return str(
self._cr.mogrify(
@ -273,16 +288,21 @@ class ReportStatementCommon(models.AbstractModel):
_("Total"),
]
def _get_line_currency_defaults(self, currency_id, currencies, balance_forward):
def _get_line_currency_defaults(
self, currency_id, currencies, balance_forward, amount_due
):
if currency_id not in currencies:
# This will only happen if currency is inactive
currencies[currency_id] = self.env["res.currency"].browse(currency_id)
return (
{
"prior_lines": [],
"lines": [],
"ending_lines": [],
"buckets": [],
"balance_forward": balance_forward,
"amount_due": balance_forward,
"amount_due": amount_due,
"ending_balance": 0.0,
},
currencies,
)
@ -290,6 +310,12 @@ class ReportStatementCommon(models.AbstractModel):
def _add_currency_line(self, line, currency):
return [line]
def _add_currency_prior_line(self, line, currency):
return [line]
def _add_currency_ending_line(self, line, currency):
return [line]
@api.model
def _get_report_values(self, docids, data=None):
# flake8: noqa: C901
@ -325,6 +351,8 @@ class ReportStatementCommon(models.AbstractModel):
date_end = datetime.strptime(date_end, DEFAULT_SERVER_DATE_FORMAT).date()
account_type = data["account_type"]
aging_type = data["aging_type"]
is_activity = data.get("is_activity")
is_detailed = data.get("is_detailed")
today = fields.Date.today()
amount_field = data.get("amount_field", "amount")
@ -344,9 +372,31 @@ class ReportStatementCommon(models.AbstractModel):
res = {}
# get base data
prior_day = date_start - timedelta(days=1) if date_start else None
prior_lines = (
self._get_account_display_prior_lines(
company_id, partner_ids, prior_day, prior_day, account_type
)
if is_detailed
else {}
)
lines = self._get_account_display_lines(
company_id, partner_ids, date_start, date_end, account_type
)
ending_lines = (
self._get_account_display_ending_lines(
company_id, partner_ids, date_start, date_end, account_type
)
if is_detailed
else {}
)
reconciled_lines = (
self._get_account_display_reconciled_lines(
company_id, partner_ids, date_start, date_end, account_type
)
if is_activity
else {}
)
balances_forward = self._get_account_initial_balance(
company_id, partner_ids, date_start, account_type
)
@ -359,7 +409,7 @@ class ReportStatementCommon(models.AbstractModel):
else:
bucket_labels = {}
# organise and format for report
# organize and format for report
format_date = self._format_date_to_partner_lang
partners_to_remove = set()
for partner_id in partner_ids:
@ -369,6 +419,9 @@ class ReportStatementCommon(models.AbstractModel):
date_start, date_formats.get(partner_id, default_fmt)
),
"end": format_date(date_end, date_formats.get(partner_id, default_fmt)),
"prior_day": format_date(
prior_day, date_formats.get(partner_id, default_fmt)
),
"currencies": {},
}
currency_dict = res[partner_id]["currencies"]
@ -378,7 +431,32 @@ class ReportStatementCommon(models.AbstractModel):
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, line["balance"]
line["currency_id"],
currencies,
line["balance"],
0.0 if is_detailed else line["balance"],
)
for line in prior_lines.get(partner_id, []):
if line["currency_id"] not in currency_dict:
(
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, 0.0, 0.0
)
line_currency = currency_dict[line["currency_id"]]
if not line["blocked"]:
line_currency["amount_due"] += line["open_amount"]
line["balance"] = line_currency["amount_due"]
line["date"] = format_date(
line["date"], date_formats.get(partner_id, default_fmt)
)
line["date_maturity"] = format_date(
line["date_maturity"], date_formats.get(partner_id, default_fmt)
)
line_currency["prior_lines"].extend(
self._add_currency_prior_line(line, currencies[line["currency_id"]])
)
for line in lines[partner_id]:
@ -387,11 +465,59 @@ class ReportStatementCommon(models.AbstractModel):
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, 0.0
line["currency_id"], currencies, 0.0, 0.0
)
line_currency = currency_dict[line["currency_id"]]
if not line["blocked"]:
line_currency["amount_due"] += line[amount_field]
if not is_activity:
line_currency["amount_due"] += line[amount_field]
line["balance"] = line_currency["amount_due"]
else:
line_currency["ending_balance"] += line[amount_field]
line["balance"] = line_currency["ending_balance"]
line["date"] = format_date(
line["date"], date_formats.get(partner_id, default_fmt)
)
line["date_maturity"] = format_date(
line["date_maturity"], date_formats.get(partner_id, default_fmt)
)
line["reconciled_line"] = False
if is_activity:
line["open_amount"] = 0.0
line["applied_amount"] = 0.0
line_currency["lines"].extend(
self._add_currency_line(line, currencies[line["currency_id"]])
)
for line2 in reconciled_lines:
if line2["id"] in line["ids"]:
line2["date"] = format_date(
line2["date"], date_formats.get(partner_id, default_fmt)
)
line2["date_maturity"] = format_date(
line2["date_maturity"],
date_formats.get(partner_id, default_fmt),
)
line2["reconciled_line"] = True
line2["applied_amount"] = line2["open_amount"]
line["applied_amount"] += line2["open_amount"]
if is_detailed:
line_currency["lines"].extend(
self._add_currency_line(
line2, currencies[line["currency_id"]]
)
)
if is_activity:
line["open_amount"] = line["amount"] + line["applied_amount"]
line_currency["amount_due"] += line["open_amount"]
if is_detailed:
for line_currency in currency_dict.values():
line_currency["amount_due"] = 0.0
for line in ending_lines.get(partner_id, []):
line_currency = currency_dict[line["currency_id"]]
if not line["blocked"]:
line_currency["amount_due"] += line["open_amount"]
line["balance"] = line_currency["amount_due"]
line["date"] = format_date(
line["date"], date_formats.get(partner_id, default_fmt)
@ -399,8 +525,10 @@ class ReportStatementCommon(models.AbstractModel):
line["date_maturity"] = format_date(
line["date_maturity"], date_formats.get(partner_id, default_fmt)
)
line_currency["lines"].extend(
self._add_currency_line(line, currencies[line["currency_id"]])
line_currency["ending_lines"].extend(
self._add_currency_ending_line(
line, currencies[line["currency_id"]]
)
)
if data["show_aging_buckets"]:
@ -410,7 +538,7 @@ class ReportStatementCommon(models.AbstractModel):
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, 0.0
line["currency_id"], currencies, 0.0, 0.0
)
line_currency = currency_dict[line["currency_id"]]
line_currency["buckets"] = line
@ -439,6 +567,7 @@ class ReportStatementCommon(models.AbstractModel):
"company": self.env["res.company"].browse(company_id),
"Currencies": currencies,
"account_type": account_type,
"is_detailed": is_detailed,
"bucket_labels": bucket_labels,
"get_inv_addr": self._get_invoice_address,
}

View File

@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_activity_statement_wizard,access_activity_statement_wizard,model_activity_statement_wizard,account.group_account_invoice,1,1,1,0
access_outstanding_statement_wizard,access_outstanding_statement_wizard,model_outstanding_statement_wizard,account.group_account_invoice,1,1,1,0
access_detailed_activity_statement_wizard,access_detailed_activity_statement_wizard,model_detailed_activity_statement_wizard,account.group_account_invoice,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_activity_statement_wizard access_activity_statement_wizard model_activity_statement_wizard account.group_account_invoice 1 1 1 0
3 access_outstanding_statement_wizard access_outstanding_statement_wizard model_outstanding_statement_wizard account.group_account_invoice 1 1 1 0
4 access_detailed_activity_statement_wizard access_detailed_activity_statement_wizard model_detailed_activity_statement_wizard account.group_account_invoice 1 1 1 0

View File

@ -20,3 +20,11 @@
background-color: $gray-500 !important;
}
}
.statement-reconciled {
font-size: smaller;
font-style: italic !important;
td:last-child {
font-style: italic !important;
}
}

View File

@ -2,6 +2,119 @@
<!-- Copyright 2018 ForgeFlow, S.L. (https://www.forgeflow.com)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<template id="activity_balance">
<p>
<span t-if="is_detailed">Detailed </span>
<span
t-if="account_type == 'payable'"
>Supplier </span>Statement between <span t-esc="d['start']" /> and <span
t-esc="d['end']"
/> in <span t-esc="display_currency.name" />
</p>
<table class="table table-condensed table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Description</th>
<th class="amount">Original Amount</th>
<th class="amount">Applied Amount</th>
<th class="amount">Open Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td />
<td>
<span t-esc="d['prior_day']" />
</td>
<td t-if="is_detailed">Initial Balance</td>
<td t-else="">Balance Forward</td>
<td />
<td />
<td class="amount">
<span
t-esc="currency['balance_forward']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr
t-foreach="currency['lines']"
t-as="line"
t-att-class="('statement-blocked ' if line['blocked'] else '') + ('statement-reconciled' if line['reconciled_line'] else '')"
>
<td>
<span
t-if="line['reconciled_line']"
style="padding-left: 11px;"
/><span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref'] and line['name']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and not line['name']">
<span t-esc="line['ref']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref'] or line['name'] == line['ref']"
>
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] not in line['name']">
<span t-esc="line['ref']" />
</t>
</t>
</t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
t-if="not line['reconciled_line']"
/>
</td>
<td class="amount">
<span
t-esc="line['applied_amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['open_amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
t-if="not line['reconciled_line']"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']" />
</td>
<td>Ending Balance</td>
<td />
<td />
<td class="amount">
<span
t-esc="currency['amount_due']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
</template>
<template id="partner_statement.activity_statement_document">
<t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context({'lang': lang})" />
@ -35,109 +148,10 @@
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span
t-if="account_type == 'payable'"
>Supplier </span>Statement between <span
t-esc="d['start']"
/> and <span t-esc="d['end']" /> in <span
t-esc="display_currency.name"
/>
</p>
<table class="table table-condensed table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Description</th>
<th class="amount">Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr>
<td />
<td>
<span t-esc="d['start']" />
</td>
<td>
Balance Forward
</td>
<td />
<td class="amount">
<span
t-esc="currency['balance_forward']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr
t-foreach="currency['lines']"
t-as="line"
t-att-class="'statement-blocked' if line['blocked'] else ''"
>
<td>
<span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref'] and line['name']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and not line['name']">
<span t-esc="line['ref']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref'] or line['name'] == line['ref']"
>
<span t-esc="line['name']" />
</t>
<t
t-if="line['ref'] not in line['name']"
>
<span t-esc="line['ref']" />
</t>
</t>
</t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']" />
</td>
<td>
Ending Balance
</td>
<td />
<td class="amount">
<span
t-esc="currency['amount_due']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
<t
t-call="partner_statement.activity_balance"
name="activity"
/>
<t
t-call="partner_statement.aging_buckets"
t-if="currency['buckets']"

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2022 ForgeFlow, S.L. (https://www.forgeflow.com)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<template id="detailed_activity_statement_document">
<t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context({'lang': lang})" />
<t t-set="address">
<address
t-esc="get_inv_addr(o)"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
<div t-if="o.vat" class="mt16"><t
t-esc="company.country_id.vat_label or 'Tax ID'"
/>: <span t-field="o.vat" /></div>
</t>
<div class="page">
<h2>Statement of Account</h2>
<div id="informations" class="row mt32 mb32">
<div class="col-3 bm-2">
<strong>Date:</strong>
<p t-esc="d['today']" class="m-0" />
</div>
<div class="col-3 bm-2">
<strong>Partner Name:</strong>
<p t-field="o.name" class="m-0" />
</div>
<div t-if="o.ref" class="col-3 bm-2">
<strong>Partner Code:</strong>
<p t-field="o.ref" class="m-0" />
</div>
</div>
<t t-if="d['currencies']">
<br />
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<t t-set="line_type" t-value="'prior_lines'" />
<t t-set="title_name" t-value="'Prior Balance'" />
<t
t-set="ending_amount"
t-value="currency['balance_forward']"
/>
<t t-set="ending_date" t-value="d['prior_day']" />
<t
t-call="partner_statement.outstanding_balance"
name="prior_balance"
/>
<t
t-call="partner_statement.activity_balance"
name="detailed_activity"
/>
<t t-set="line_type" t-value="'ending_lines'" />
<t t-set="title_name" t-value="'Ending Balance'" />
<t t-set="ending_amount" t-value="currency['amount_due']" />
<t t-set="ending_date" t-value="d['end']" />
<t
t-call="partner_statement.outstanding_balance"
name="ending_balance"
/>
<t
t-call="partner_statement.aging_buckets"
t-if="currency['buckets']"
/>
</t>
</t>
<p t-if="d.get('no_entries')">
<strong>The partner doesn't have due entries.</strong>
</p>
</div>
</t>
</template>
<template id="detailed_activity_statement">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-set="d" t-value="data.get(o.id)" />
<t
t-call="partner_statement.detailed_activity_statement_document"
t-lang="o.lang"
/>
</t>
</t>
</template>
<record id="action_print_detailed_activity_statement" model="ir.actions.report">
<field name="name">Detailed Activity Statement</field>
<field name="model">res.partner</field>
<field name="report_name">partner_statement.detailed_activity_statement</field>
<field name="report_type">qweb-pdf</field>
<field name="report_file">partner_statement.detailed_activity_statement</field>
</record>
<record
id="action_print_detailed_activity_statement_html"
model="ir.actions.report"
>
<field name="name">Detailed Activity Statement</field>
<field name="model">res.partner</field>
<field name="report_name">partner_statement.detailed_activity_statement</field>
<field name="report_type">qweb-html</field>
<field name="report_file">partner_statement.detailed_activity_statement</field>
</record>
<record
id="action_print_detailed_activity_statement_xlsx"
model="ir.actions.report"
>
<field name="name">Detailed Activity Statement XLSX</field>
<field name="model">res.partner</field>
<field name="type">ir.actions.report</field>
<field name="report_name">p_s.report_detailed_activity_statement_xlsx</field>
<field name="report_type">xlsx</field>
<field name="report_file">report_detailed_activity_statement</field>
</record>
</odoo>

View File

@ -2,6 +2,104 @@
<!-- Copyright 2018 ForgeFlow, S.L. (https://www.forgeflow.com)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<template id="outstanding_balance">
<p>
<span t-esc="'' if account_type == 'receivable' else 'Supplier '" /><span
t-esc="title_name"
/> up to <span t-esc="ending_date" /> in <span
t-esc="display_currency.name"
/>
</p>
<table class="table table-sm table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Due Date</th>
<th>Description</th>
<th class="amount">Original</th>
<th class="amount">Open Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr
t-foreach="currency[line_type]"
t-as="line"
t-att-class="'statement-blocked' if line['blocked'] else ''"
>
<td>
<span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<span t-esc="line['date_maturity']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and not line['name']">
<span t-esc="line['ref']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref'] or line['name'] == line['ref']"
>
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] not in line['name']">
<span t-esc="line['ref']" />
</t>
</t>
</t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['open_amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="ending_date" />
</td>
<td>
Ending Balance
</td>
<td />
<td />
<td />
<td class="amount">
<span
t-esc="ending_amount"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
</template>
<template id="partner_statement.outstanding_statement_document">
<t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context({'lang': lang})" />
@ -36,104 +134,14 @@
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span
t-esc="'' if account_type == 'receivable' else 'Supplier '"
/>Statement up to <span t-esc="d['end']" /> in <span
t-esc="display_currency.name"
/>
</p>
<table class="table table-sm table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Due Date</th>
<th>Description</th>
<th class="amount">Original</th>
<th class="amount">Open Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr
t-foreach="currency['lines']"
t-as="line"
t-att-class="'statement-blocked' if line['blocked'] else ''"
>
<td>
<span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<span t-esc="line['date_maturity']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and not line['name']">
<span t-esc="line['ref']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref'] or line['name'] == line['ref']"
>
<span t-esc="line['name']" />
</t>
<t
t-if="line['ref'] not in line['name']"
>
<span t-esc="line['ref']" />
</t>
</t>
</t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['open_amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']" />
</td>
<td>
Ending Balance
</td>
<td />
<td />
<td />
<td class="amount">
<span
t-esc="currency['amount_due']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
<t t-set="line_type" t-value="'lines'" />
<t t-set="title_name" t-value="'Statement'" />
<t t-set="ending_amount" t-value="currency['amount_due']" />
<t t-set="ending_date" t-value="d['end']" />
<t
t-call="partner_statement.outstanding_balance"
name="outstanding"
/>
<t
t-call="partner_statement.aging_buckets"
t-if="currency['buckets']"

View File

@ -1,4 +1,5 @@
from . import statement_common
from . import activity_statement_wizard
from . import detailed_activity_statement_wizard
from . import outstanding_statement_wizard
from . import res_config_settings

View File

@ -32,7 +32,12 @@ class ActivityStatementWizard(models.TransientModel):
def _prepare_statement(self):
res = super()._prepare_statement()
res.update({"date_start": self.date_start})
res.update(
{
"date_start": self.date_start,
"is_activity": True,
}
)
return res
def _print_report(self, report_type):

View File

@ -0,0 +1,40 @@
# Copyright 2018 ForgeFlow, S.L. (http://www.forgeflow.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class DetailedActivityStatementWizard(models.TransientModel):
"""Detailed Activity Statement wizard."""
_inherit = "activity.statement.wizard"
_name = "detailed.activity.statement.wizard"
_description = "Detailed Activity Statement Wizard"
show_aging_buckets = fields.Boolean(default=False)
show_balance = fields.Boolean(string="Show Balance column")
def _prepare_statement(self):
res = super()._prepare_statement()
res.update(
{
"is_detailed": True,
}
)
return res
def _print_report(self, report_type):
self.ensure_one()
data = self._prepare_statement()
if report_type == "xlsx":
report_name = "p_s.report_detailed_activity_statement_xlsx"
else:
report_name = "partner_statement.detailed_activity_statement"
return (
self.env["ir.actions.report"]
.search(
[("report_name", "=", report_name), ("report_type", "=", report_type)],
limit=1,
)
.report_action(self, data=data)
)

View File

@ -11,6 +11,15 @@ class OutstandingStatementWizard(models.TransientModel):
_inherit = "statement.common.wizard"
_description = "Outstanding Statement Wizard"
def _prepare_statement(self):
res = super()._prepare_statement()
res.update(
{
"is_outstanding": True,
}
)
return res
def _print_report(self, report_type):
self.ensure_one()
data = self._prepare_statement()

View File

@ -5,7 +5,7 @@ class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
group_activity_statement = fields.Boolean(
"Enable OCA Activity Statements",
"Enable OCA Activity & Detailed Activity Statements",
group="account.group_account_invoice",
implied_group="partner_statement.group_activity_statement",
)
@ -55,4 +55,5 @@ class ResConfigSettings(models.TransientModel):
value = self[name]
IrDefault.set("activity.statement.wizard", name[8:], value)
IrDefault.set("outstanding.statement.wizard", name[8:], value)
IrDefault.set("detailed.activity.statement.wizard", name[8:], value)
return super().set_values()

View File

@ -25,6 +25,20 @@
/>
<field name="target">new</field>
</record>
<record
id="detailed_activity_statement_wizard_action"
model="ir.actions.act_window"
>
<field name="name">Partner Detailed Activity Statement</field>
<field name="binding_model_id" ref="base.model_res_partner" />
<field name="res_model">detailed.activity.statement.wizard</field>
<field name="view_mode">form</field>
<field
name="groups_id"
eval="[(4, ref('partner_statement.group_activity_statement'))]"
/>
<field name="target">new</field>
</record>
<!-- wizard view -->
<record id="statement_common_view" model="ir.ui.view">
<field name="name">Statement Common Wizard View</field>
@ -138,4 +152,24 @@
</xpath>
</field>
</record>
<record id="detailed_activity_statement_wizard_view" model="ir.ui.view">
<field name="name">Detailed Activity Statement Wizard</field>
<field name="model">detailed.activity.statement.wizard</field>
<field name="inherit_id" ref="partner_statement.statement_common_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//div[@name='info']/span" position="before">
<span
class="o_form_label"
>The detailed activity statement is an extension of the activity statement, and intends to explain the transactions
that have happened during the period, also providing with a Prior Balance section and an Ending Balance section.
</span>
<br />
<br />
</xpath>
<xpath expr="//field[@name='date_end']" position="before">
<field name="date_start" />
</xpath>
</field>
</record>
</odoo>