You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
22 KiB
453 lines
22 KiB
# -*- coding: utf-8 -*-
|
|
################################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
|
# Author: Bhagyadev KP (<https://www.cybrosys.com>)
|
|
#
|
|
# You can modify it under the terms of the GNU LESSER
|
|
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
|
#
|
|
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
|
# (LGPL v3) along with this program.
|
|
# If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
import calendar
|
|
import io
|
|
import json
|
|
from datetime import datetime
|
|
import xlsxwriter
|
|
from odoo import api, fields, models
|
|
from odoo.tools.date_utils import get_month, get_fiscal_year, \
|
|
get_quarter_number, subtract
|
|
|
|
|
|
class AccountTrialBalance(models.TransientModel):
|
|
"""For creating Trial Balance report"""
|
|
_name = 'account.trial.balance'
|
|
_description = 'Trial Balance Report'
|
|
|
|
@api.model
|
|
def view_report(self):
|
|
"""
|
|
Generates a trial balance report for multiple accounts.
|
|
Retrieves account information and calculates total debit and credit
|
|
amounts for each account within the specified date range. Returns a list
|
|
of dictionaries containing account details and transaction totals.
|
|
|
|
:return: List of dictionaries representing the trial balance report.
|
|
:rtype: list
|
|
"""
|
|
account_ids = self.env['account.move.line'].search([]).mapped(
|
|
'account_id')
|
|
today = fields.Date.today()
|
|
move_line_list = []
|
|
for account_id in account_ids:
|
|
initial_move_line_ids = self.env['account.move.line'].search(
|
|
[('date', '<', get_month(today)[0]),
|
|
('account_id', '=', account_id.id),
|
|
('parent_state', '=', 'posted')])
|
|
initial_total_debit = round(
|
|
sum(initial_move_line_ids.mapped('debit')), 2)
|
|
initial_total_credit = round(
|
|
sum(initial_move_line_ids.mapped('credit')), 2)
|
|
move_line_ids = self.env['account.move.line'].search(
|
|
[('date', '>=', get_month(today)[0]),
|
|
('account_id', '=', account_id.id),
|
|
('date', '<=', get_month(today)[1]),
|
|
('parent_state', '=', 'posted')])
|
|
total_debit = round(sum(move_line_ids.mapped('debit')), 2)
|
|
total_credit = round(sum(move_line_ids.mapped('credit')), 2)
|
|
sum_debit = initial_total_debit + total_debit
|
|
sum_credit = initial_total_credit + total_credit
|
|
diff_credit_debit = sum_debit - sum_credit
|
|
if diff_credit_debit > 0:
|
|
end_total_debit = diff_credit_debit
|
|
end_total_credit = 0.0
|
|
else:
|
|
end_total_debit = 0.0
|
|
end_total_credit = abs(diff_credit_debit)
|
|
data = {
|
|
'account': account_id.display_name,
|
|
'account_id': account_id.id,
|
|
'journal_ids': self.env['account.journal'].search_read([], [
|
|
'name']),
|
|
'initial_total_debit': initial_total_debit,
|
|
'initial_total_credit': initial_total_credit,
|
|
'total_debit': total_debit,
|
|
'total_credit': total_credit,
|
|
'end_total_debit': end_total_debit,
|
|
'end_total_credit': end_total_credit
|
|
}
|
|
move_line_list.append(data)
|
|
journal = {
|
|
'journal_ids': self.env['account.journal'].search_read([], [
|
|
'name'])
|
|
}
|
|
return move_line_list, journal
|
|
|
|
@api.model
|
|
def get_filter_values(self, start_date, end_date, comparison_number,
|
|
comparison_type, journal_list, analytic, options,
|
|
method):
|
|
"""
|
|
Retrieves and calculates filtered values for generating a financial
|
|
report.
|
|
Retrieves and processes account movement data based on the provided
|
|
filters. Calculates initial, dynamic, and end total debit and credit
|
|
amounts for each account,considering date range, comparison type, and
|
|
other filter criteria.
|
|
|
|
:param str start_date: Start date of the reporting period.
|
|
:param str end_date: End date of the reporting period.
|
|
:param int comparison_number: Number of periods for comparison.
|
|
:param str comparison_type: Type of comparison (month, year, quarter).
|
|
:param list[int] journal_list: List of selected journal IDs.
|
|
:param list[int] analytic: List of selected analytic line IDs.
|
|
:param dict options: Additional filtering options (e.g., 'draft').
|
|
:param dict method: Find the method.
|
|
:return: List of dictionaries representing the financial report.
|
|
:rtype: list
|
|
"""
|
|
if options == {}:
|
|
options = None
|
|
if options is None:
|
|
option_domain = ['posted']
|
|
elif 'draft' in options:
|
|
option_domain = ['posted', 'draft']
|
|
if method == {}:
|
|
method = None
|
|
dynamic_total_debit = {}
|
|
dynamic_date_num = {}
|
|
dynamic_total_credit = {}
|
|
account_ids = self.env['account.move.line'].search([]).mapped(
|
|
'account_id')
|
|
move_line_list = []
|
|
start_date_first = \
|
|
get_fiscal_year(datetime.strptime(start_date, "%Y-%m-%d").date())[
|
|
0] if comparison_type == 'year' else datetime.strptime(
|
|
start_date, "%Y-%m-%d").date()
|
|
end_date_first = \
|
|
get_fiscal_year(datetime.strptime(end_date, "%Y-%m-%d").date())[
|
|
1] if comparison_type == 'year' else datetime.strptime(end_date,
|
|
"%Y-%m-%d").date()
|
|
for account_id in account_ids:
|
|
start_date = start_date_first
|
|
end_date = end_date_first
|
|
if comparison_number:
|
|
if comparison_type == 'month':
|
|
initial_start_date = subtract(start_date, months=eval(
|
|
comparison_number))
|
|
elif comparison_type == 'year':
|
|
initial_start_date = subtract(start_date, years=eval(
|
|
comparison_number))
|
|
else:
|
|
initial_start_date = subtract(start_date, months=eval(
|
|
comparison_number) * 3)
|
|
else:
|
|
initial_start_date = start_date
|
|
domain = [('date', '<', initial_start_date),
|
|
('account_id', '=', account_id.id),
|
|
('parent_state', 'in', option_domain), ]
|
|
if journal_list:
|
|
domain.append(
|
|
('journal_id', 'in', journal_list), )
|
|
if analytic:
|
|
domain.append(
|
|
('analytic_line_ids', 'in', analytic))
|
|
if method is not None and 'cash' in method:
|
|
domain.append(('journal_id', 'in',
|
|
self.env.company.tax_cash_basis_journal_id.ids))
|
|
initial_move_line_ids = self.env['account.move.line'].search(
|
|
domain)
|
|
initial_total_debit = round(
|
|
sum(initial_move_line_ids.mapped('debit')), 2)
|
|
initial_total_credit = round(
|
|
sum(initial_move_line_ids.mapped('credit')), 2)
|
|
if comparison_number:
|
|
if comparison_type == 'year':
|
|
for i in range(1, eval(comparison_number) + 1):
|
|
com_start_date = subtract(start_date, years=i)
|
|
com_end_date = subtract(end_date, years=i)
|
|
domain = [('date', '>=', com_start_date),
|
|
('account_id', '=', account_id.id),
|
|
('date', '<=', com_end_date),
|
|
('parent_state', 'in', option_domain), ]
|
|
if journal_list:
|
|
domain.append(
|
|
('journal_id', 'in', journal_list), )
|
|
if analytic:
|
|
domain.append(
|
|
('analytic_line_ids', 'in', analytic))
|
|
if method is not None and 'cash' in method:
|
|
domain.append(('journal_id', 'in',
|
|
self.env.company.tax_cash_basis_journal_id.ids))
|
|
move_lines = self.env['account.move.line'].search(
|
|
domain)
|
|
dynamic_total_debit[
|
|
f"dynamic_total_debit_{i}"] = round(
|
|
sum(move_lines.mapped('debit')), 2)
|
|
dynamic_total_credit[
|
|
f"dynamic_total_credit_{i}"] = round(
|
|
sum(move_lines.mapped('credit')), 2)
|
|
if comparison_type == 'month':
|
|
dynamic_date_num[
|
|
f"dynamic_date_num{0}"] = self.get_month_name(
|
|
start_date) + ' ' + str(
|
|
start_date.year)
|
|
for i in range(1, eval(comparison_number) + 1):
|
|
com_start_date = subtract(start_date, months=i)
|
|
com_end_date = subtract(end_date, months=i)
|
|
domain = [('date', '>=', com_start_date),
|
|
('account_id', '=', account_id.id),
|
|
('date', '<=', com_end_date),
|
|
('parent_state', 'in', option_domain), ]
|
|
if journal_list:
|
|
domain.append(
|
|
('journal_id', 'in', journal_list), )
|
|
if analytic:
|
|
domain.append(
|
|
('analytic_line_ids', 'in', analytic))
|
|
if method is not None and 'cash' in method:
|
|
domain.append(('journal_id', 'in',
|
|
self.env.company.tax_cash_basis_journal_id.ids), )
|
|
move_lines = self.env['account.move.line'].search(
|
|
domain)
|
|
dynamic_date_num[
|
|
f"dynamic_date_num{i}"] = self.get_month_name(
|
|
com_start_date) + ' ' + str(
|
|
com_start_date.year)
|
|
dynamic_total_debit[
|
|
f"dynamic_total_debit_{i}"] = round(
|
|
sum(move_lines.mapped('debit')), 2)
|
|
dynamic_total_credit[
|
|
f"dynamic_total_credit_{i}"] = round(
|
|
sum(move_lines.mapped('credit')), 2)
|
|
if comparison_type == 'quarter':
|
|
dynamic_date_num[
|
|
f"dynamic_date_num{0}"] = 'Q' + ' ' + str(
|
|
get_quarter_number(start_date)) + ' ' + str(
|
|
start_date.year)
|
|
for i in range(1, eval(comparison_number) + 1):
|
|
com_start_date = subtract(start_date, months=i * 3)
|
|
com_end_date = subtract(end_date, months=i * 3)
|
|
domain = [('date', '>=', com_start_date),
|
|
('account_id', '=', account_id.id),
|
|
('date', '<=', com_end_date),
|
|
('parent_state', 'in', option_domain), ]
|
|
if journal_list:
|
|
domain.append(
|
|
('journal_id', 'in', journal_list), )
|
|
if analytic:
|
|
domain.append(
|
|
('analytic_line_ids', 'in', analytic))
|
|
if method is not None and 'cash' in method:
|
|
domain.append(('journal_id', 'in',
|
|
self.env.company.tax_cash_basis_journal_id.ids))
|
|
move_lines = self.env['account.move.line'].search(
|
|
domain)
|
|
dynamic_date_num[
|
|
f"dynamic_date_num{i}"] = 'Q' + ' ' + str(
|
|
get_quarter_number(com_start_date)) + ' ' + str(
|
|
com_start_date.year)
|
|
dynamic_total_debit[
|
|
f"dynamic_total_debit_{i}"] = round(
|
|
sum(move_lines.mapped('debit')), 2)
|
|
dynamic_total_credit[
|
|
f"dynamic_total_credit_{i}"] = round(
|
|
sum(move_lines.mapped('credit')), 2)
|
|
domain = [('date', '>=', start_date),
|
|
('account_id', '=', account_id.id),
|
|
('date', '<=', end_date),
|
|
('parent_state', 'in', option_domain), ]
|
|
if journal_list:
|
|
domain.append(
|
|
('journal_id', 'in', journal_list), )
|
|
if analytic:
|
|
domain.append(
|
|
('analytic_line_ids', 'in', analytic))
|
|
if method is not None and 'cash' in method:
|
|
domain.append(('journal_id', 'in',
|
|
self.env.company.tax_cash_basis_journal_id.ids))
|
|
move_line_ids = self.env['account.move.line'].search(domain)
|
|
total_debit = round(sum(move_line_ids.mapped('debit')), 2)
|
|
total_credit = round(sum(move_line_ids.mapped('credit')), 2)
|
|
sum_debit = initial_total_debit + sum(
|
|
dynamic_total_debit.values()) + total_debit
|
|
sum_credit = initial_total_credit + sum(
|
|
dynamic_total_credit.values()) + total_credit
|
|
diff_credit_debit = sum_debit - sum_credit
|
|
if diff_credit_debit > 0:
|
|
end_total_debit = diff_credit_debit
|
|
end_total_credit = 0.0
|
|
else:
|
|
end_total_debit = 0.0
|
|
end_total_credit = abs(diff_credit_debit)
|
|
data = {
|
|
'account': account_id.display_name,
|
|
'account_id': account_id.id,
|
|
'journal_ids': self.env['account.journal'].search_read([], [
|
|
'name']),
|
|
'initial_total_debit': initial_total_debit,
|
|
'initial_total_credit': initial_total_credit,
|
|
'total_debit': total_debit,
|
|
'total_credit': total_credit,
|
|
'end_total_debit': end_total_debit,
|
|
'end_total_credit': end_total_credit
|
|
}
|
|
if comparison_number:
|
|
if dynamic_date_num:
|
|
data['dynamic_date_num'] = dynamic_date_num
|
|
for i in range(1, eval(comparison_number) + 1):
|
|
data[f'dynamic_total_debit_{i}'] = dynamic_total_debit.get(
|
|
f"dynamic_total_debit_{eval(comparison_number) + 1 - i}",
|
|
0.0)
|
|
data[
|
|
f'dynamic_total_credit_{i}'] = dynamic_total_credit.get(
|
|
f"dynamic_total_credit_{eval(comparison_number) + 1 - i}",
|
|
0.0)
|
|
move_line_list.append(data)
|
|
return move_line_list
|
|
|
|
@api.model
|
|
def get_month_name(self, date):
|
|
"""
|
|
Retrieve the abbreviated name of the month for a given date.
|
|
:param date: The date for which to retrieve the month's abbreviated name.
|
|
:type date: datetime.date
|
|
:return: Abbreviated name of the month (e.g., 'Jan', 'Feb', ..., 'Dec').
|
|
:rtype: str
|
|
"""
|
|
month_names = calendar.month_abbr
|
|
return month_names[date.month]
|
|
|
|
@api.model
|
|
def get_xlsx_report(self, data, response, report_name, report_action):
|
|
"""
|
|
Generate an XLSX report based on provided data and response stream.
|
|
Generates an Excel workbook with specified report format, including
|
|
subheadings,column headers, and row data for the given financial report
|
|
data.
|
|
:param str data: JSON-encoded data for the report.
|
|
:param response: Response object to stream the generated report.
|
|
:param str report_name: Name of the financial report.
|
|
"""
|
|
data = json.loads(data)
|
|
output = io.BytesIO()
|
|
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
|
|
start_date = data['filters']['start_date'] if \
|
|
data['filters']['start_date'] else ''
|
|
end_date = data['filters']['end_date'] if \
|
|
data['filters']['end_date'] else ''
|
|
head = workbook.add_format(
|
|
{'font_size': 15, 'align': 'center', 'bold': True})
|
|
sheet = workbook.add_worksheet()
|
|
sub_heading = workbook.add_format(
|
|
{'align': 'center', 'bold': True, 'font_size': '10px',
|
|
'border': 1, 'bg_color': '#D3D3D3',
|
|
'border_color': 'black'})
|
|
filter_head = workbook.add_format(
|
|
{'align': 'center', 'bold': True, 'font_size': '10px',
|
|
'border': 1, 'bg_color': '#D3D3D3',
|
|
'border_color': 'black'})
|
|
filter_body = workbook.add_format(
|
|
{'align': 'center', 'bold': True, 'font_size': '10px'})
|
|
side_heading_sub = workbook.add_format(
|
|
{'align': 'left', 'bold': True, 'font_size': '10px',
|
|
'border': 1,
|
|
'border_color': 'black'})
|
|
side_heading_sub.set_indent(1)
|
|
txt_name = workbook.add_format({'font_size': '10px', 'border': 1})
|
|
txt_name.set_indent(2)
|
|
sheet.set_column(0, 0, 30)
|
|
sheet.set_column(1, 1, 20)
|
|
sheet.set_column(2, 2, 15)
|
|
sheet.set_column(3, 3, 15)
|
|
col = 0
|
|
sheet.write('A1:b1', report_name, head)
|
|
sheet.write('B3:b4', 'Date Range', filter_head)
|
|
sheet.write('B4:b4', 'Comparison', filter_head)
|
|
sheet.write('B5:b4', 'Journal', filter_head)
|
|
sheet.write('B6:b4', 'Account', filter_head)
|
|
sheet.write('B7:b4', 'Option', filter_head)
|
|
if start_date or end_date:
|
|
sheet.merge_range('C3:G3', f"{start_date} to {end_date}",
|
|
filter_body)
|
|
if data['filters']['comparison_number_range']:
|
|
sheet.merge_range('C4:G4',
|
|
f"{data['filters']['comparison_type']} : {data['filters']['comparison_number_range']}",
|
|
filter_body)
|
|
if data['filters']['journal']:
|
|
display_names = [journal for
|
|
journal in data['filters']['journal']]
|
|
display_names_str = ', '.join(display_names)
|
|
sheet.merge_range('C5:G5', display_names_str, filter_body)
|
|
if data['filters']['account']:
|
|
account_keys = [account.get('display_name', 'undefined') for
|
|
account in data['filters']['account']]
|
|
account_keys_str = ', '.join(account_keys)
|
|
sheet.merge_range('C6:G6', account_keys_str, filter_body)
|
|
if data['filters']['options']:
|
|
option_keys = list(data['filters']['options'].keys())
|
|
option_keys_str = ', '.join(option_keys)
|
|
sheet.merge_range('C7:G7', option_keys_str, filter_body)
|
|
sheet.write(9, col, '', sub_heading)
|
|
sheet.merge_range(9, col + 1, 9, col + 2, 'Initial Balance',
|
|
sub_heading)
|
|
i = 3
|
|
for date_view in data['date_viewed']:
|
|
sheet.merge_range(9, col + i, 9, col + i + 1, date_view,
|
|
sub_heading)
|
|
i += 2
|
|
sheet.merge_range(9, col + i, 9, col + i + 1, 'End Balance',
|
|
sub_heading)
|
|
sheet.write(10, col, '', sub_heading)
|
|
sheet.write(10, col + 1, 'Debit', sub_heading)
|
|
sheet.write(10, col + 2, 'Credit', sub_heading)
|
|
i = 3
|
|
for date_views in data['date_viewed']:
|
|
sheet.write(10, col + i, 'Debit', sub_heading)
|
|
i += 1
|
|
sheet.write(10, col + i, 'Credit', sub_heading)
|
|
i += 1
|
|
sheet.write(10, col + i, 'Debit', sub_heading)
|
|
sheet.write(10, col + (i + 1), 'Credit', sub_heading)
|
|
if data:
|
|
if report_action == 'dynamic_accounts_report.action_trial_balance':
|
|
row = 11
|
|
for move_line in data['data']:
|
|
sheet.write(row, col, move_line['account'],
|
|
side_heading_sub)
|
|
sheet.write(row, col + 1, move_line['initial_total_debit'],
|
|
txt_name)
|
|
sheet.write(row, col + 2,
|
|
move_line['initial_total_credit'], txt_name)
|
|
j = 3
|
|
if data['apply_comparison']:
|
|
number_of_periods = data['comparison_number_range']
|
|
for num in number_of_periods:
|
|
sheet.write(row, col + j, move_line[
|
|
'dynamic_total_debit_' + str(num)], txt_name)
|
|
sheet.write(row, col + j + 1, move_line[
|
|
'dynamic_total_credit_' + str(num)], txt_name)
|
|
j += 2
|
|
sheet.write(row, col + j, move_line['total_debit'],
|
|
txt_name)
|
|
sheet.write(row, col + j + 1, move_line['total_credit'],
|
|
txt_name)
|
|
sheet.write(row, col + j + 2, move_line['end_total_debit'],
|
|
txt_name)
|
|
sheet.write(row, col + j + 3,
|
|
move_line['end_total_credit'], txt_name)
|
|
row += 1
|
|
workbook.close()
|
|
output.seek(0)
|
|
response.stream.write(output.read())
|
|
output.close()
|
|
|