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

# -*- 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()