# -*- coding: utf-8 -*- ################################################################################ # # Cybrosys Technologies Pvt. Ltd. # # Copyright (C) 2024-TODAY Cybrosys Technologies() # Author: Bhagyadev KP () # # 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 . # ################################################################################ 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()