diff --git a/base_accounting_kit/README.rst b/base_accounting_kit/README.rst new file mode 100644 index 000000000..e85d30080 --- /dev/null +++ b/base_accounting_kit/README.rst @@ -0,0 +1,44 @@ +Accounting Kit +============================= +* Full accounting kit for Odoo 13 community editions + +Installation +============ + - www.odoo.com/documentation/12.0/setup/install.html + - Install our custom addon + - You need 'report_xlsx' module in order to get XLSX report. + - Install 'report_xlsx' from https://apps.odoo.com/apps/modules/12.0/report_xlsx/ + +License +------- +Odoo Proprietary License v1.0 (OPL-1) +(https://www.odoo.com/documentation/user/12.0/legal/licenses/licenses.html) + +Company +------- +* 'Cybrosys Techno Solutions `__ + +Credits +------- +* Developer: +(v12) Milind Mohan @ Cybrosys, Contact: milind@cybrosys.in +(v12) Mashhood K U @ Cybrosys, Contact: mashood@cybrosys.in + +Contacts +-------- +* Mail Contact : odoo@cybrosys.com + +Bug Tracker +----------- +Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. + +Maintainer +========== +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com + +Further information +=================== +HTML Description: ``__ + diff --git a/base_accounting_kit/__init__.py b/base_accounting_kit/__init__.py new file mode 100644 index 000000000..3808b32d2 --- /dev/null +++ b/base_accounting_kit/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import models +from . import report +from . import wizard diff --git a/base_accounting_kit/__manifest__.py b/base_accounting_kit/__manifest__.py new file mode 100644 index 000000000..a4f50081c --- /dev/null +++ b/base_accounting_kit/__manifest__.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +{ + 'name': 'Odoo 13 Accounting', + 'version': '13.0.1.0.0', + 'category': 'Accounting', + 'summary': """ Complete accounting kit for Odoo 13 """, + 'description': "This Module will bring back the accounting features such " + "as Account Reports, Asset Management, " + "Customer Credit Limit, Recurring Payment, " + "PDC Management, Customer Follow Up and " + "Lock Dates into Odoo 13 Community Edition", + 'author': 'Cybrosys Techno Solutions, Odoo SA', + 'website': "https://www.cybrosys.com", + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'depends': ['base', 'account', 'sale', 'account_check_printing'], + 'data': [ + 'security/ir.model.access.csv', + 'security/account_asset_security.xml', + 'data/account_financial_report_data.xml', + 'data/cash_flow_data.xml', + 'data/account_pdc_data.xml', + 'data/followup_levels.xml', + 'data/account_asset_data.xml', + 'views/reports_config_view.xml', + 'views/accounting_menu.xml', + 'views/credit_limit_view.xml', + 'views/account_configuration.xml', + 'views/account_payment_view.xml', + 'views/res_config_view.xml', + 'views/recurring_payments_view.xml', + 'views/account_followup.xml', + 'views/followup_report.xml', + 'wizard/asset_depreciation_confirmation_wizard_views.xml', + 'wizard/asset_modify_views.xml', + 'views/account_asset_views.xml', + 'views/account_invoice_views.xml', + 'views/account_asset_templates.xml', + 'views/product_template_views.xml', + 'wizard/financial_report.xml', + 'wizard/general_ledger.xml', + 'wizard/partner_ledger.xml', + 'wizard/tax_report.xml', + 'wizard/account_lock_date.xml', + 'wizard/trial_balance.xml', + 'wizard/aged_partner.xml', + 'wizard/journal_audit.xml', + 'wizard/cash_flow_report.xml', + 'wizard/account_bank_book_wizard_view.xml', + 'wizard/account_cash_book_wizard_view.xml', + 'wizard/account_day_book_wizard_view.xml', + 'wizard/recurring_payments_wizard.xml', + 'report/report_financial.xml', + 'report/general_ledger_report.xml', + 'report/report_journal_audit.xml', + 'report/report_aged_partner.xml', + 'report/report_trial_balance.xml', + 'report/report_tax.xml', + 'report/report_partner_ledger.xml', + 'report/cash_flow_report.xml', + 'report/account_bank_book_view.xml', + 'report/account_cash_book_view.xml', + 'report/account_day_book_view.xml', + 'report/account_asset_report_views.xml', + 'report/report.xml', + ], + 'qweb': ['static/src/xml/*.xml'], + 'license': 'AGPL-3', + 'images': ['static/description/banner.png'], + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/base_accounting_kit/data/account_asset_data.xml b/base_accounting_kit/data/account_asset_data.xml new file mode 100644 index 000000000..28f3491ba --- /dev/null +++ b/base_accounting_kit/data/account_asset_data.xml @@ -0,0 +1,15 @@ + + + + + Account Asset: Generate asset entries + + code + model._cron_generate_entries() + 1 + months + -1 + + + + \ No newline at end of file diff --git a/base_accounting_kit/data/account_financial_report_data.xml b/base_accounting_kit/data/account_financial_report_data.xml new file mode 100644 index 000000000..0b5a3c143 --- /dev/null +++ b/base_accounting_kit/data/account_financial_report_data.xml @@ -0,0 +1,87 @@ + + + + + + Profit and Loss + + sum + + + + Income + + + detail_with_hierarchy + account_type + + + + + Expense + + 1 + + detail_with_hierarchy + account_type + + + + + Balance Sheet + sum + + + + Assets + + detail_with_hierarchy + account_type + + + + + Liability + 1 + + no_detail + sum + + + + Liability + + detail_with_hierarchy + account_type + + + + + Profit (Loss) to report + + no_detail + account_report + + + + + diff --git a/base_accounting_kit/data/account_pdc_data.xml b/base_accounting_kit/data/account_pdc_data.xml new file mode 100755 index 000000000..54ec4ca7f --- /dev/null +++ b/base_accounting_kit/data/account_pdc_data.xml @@ -0,0 +1,19 @@ + + + + + + PDC + pdc + inbound + + + PDC + pdc + outbound + + + + + + \ No newline at end of file diff --git a/base_accounting_kit/data/cash_flow_data.xml b/base_accounting_kit/data/cash_flow_data.xml new file mode 100644 index 000000000..bf5918a78 --- /dev/null +++ b/base_accounting_kit/data/cash_flow_data.xml @@ -0,0 +1,73 @@ + + + + + Cash Flow Statement + sum + + + + Operations + 1 + + detail_with_hierarchy + sum + + + Cash In + 1 + + detail_with_hierarchy + accounts + + + Cash Out + 2 + + detail_with_hierarchy + accounts + + + + Investing Activities + 2 + + detail_with_hierarchy + sum + + + Cash In + + detail_with_hierarchy + accounts + + + Cash Out + + detail_with_hierarchy + accounts + + + + Financing Activities + 3 + + detail_with_hierarchy + sum + + + + + Cash In + + detail_with_hierarchy + accounts + + + Cash Out + + detail_with_hierarchy + accounts + + + diff --git a/base_accounting_kit/data/followup_levels.xml b/base_accounting_kit/data/followup_levels.xml new file mode 100644 index 000000000..f645b1521 --- /dev/null +++ b/base_accounting_kit/data/followup_levels.xml @@ -0,0 +1,12 @@ + + + + + Reminder + 5 + + + + + + \ No newline at end of file diff --git a/base_accounting_kit/models/__init__.py b/base_accounting_kit/models/__init__.py new file mode 100644 index 000000000..46cc59b9e --- /dev/null +++ b/base_accounting_kit/models/__init__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +############################################################################# +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import account_account +from . import account_asset +from . import account_followup +from . import account_journal +from . import account_move +from . import account_payment +from . import credit_limit +from . import product_template +from . import recurring_payments +from . import res_config_settings +from . import res_partner diff --git a/base_accounting_kit/models/account_account.py b/base_accounting_kit/models/account_account.py new file mode 100644 index 000000000..236926023 --- /dev/null +++ b/base_accounting_kit/models/account_account.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time +from odoo import api, models, fields, _ +from odoo.exceptions import UserError + + +class CashFlow(models.Model): + _inherit = 'account.account' + + def get_cash_flow_ids(self): + cash_flow_id = self.env.ref('base_accounting_kit.account_financial_report_cash_flow0') + if cash_flow_id: + return [('parent_id.id', '=', cash_flow_id.id)] + + cash_flow_type = fields.Many2one('account.financial.report', string="Cash Flow type", domain=get_cash_flow_ids) + + @api.onchange('cash_flow_type') + def onchange_cash_flow_type(self): + for rec in self.cash_flow_type: + # update new record + rec.write({ + 'account_ids': [(4, self._origin.id)] + }) + + if self._origin.cash_flow_type.ids: + for rec in self._origin.cash_flow_type: + # remove old record + rec.write({ + 'account_ids': [(3, self._origin.id)] + }) diff --git a/base_accounting_kit/models/account_asset.py b/base_accounting_kit/models/account_asset.py new file mode 100644 index 000000000..2db439da3 --- /dev/null +++ b/base_accounting_kit/models/account_asset.py @@ -0,0 +1,656 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import calendar +from datetime import date, datetime +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare, float_is_zero + + +class AccountAssetCategory(models.Model): + _name = 'account.asset.category' + _description = 'Asset category' + + active = fields.Boolean(default=True) + name = fields.Char(required=True, index=True, string="Asset Type") + account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') + analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag') + account_asset_id = fields.Many2one('account.account', string='Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used to record the purchase of the asset at its original price.") + account_depreciation_id = fields.Many2one('account.account', string='Depreciation Entries: Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used in the depreciation entries, to decrease the asset value.") + account_depreciation_expense_id = fields.Many2one('account.account', string='Depreciation Entries: Expense Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], oldname='account_income_recognition_id', help="Account used in the periodical entries, to record a part of the asset as expense.") + journal_id = fields.Many2one('account.journal', string='Journal', required=True) + company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('account.asset.category')) + method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, default='linear', + help="Choose the method to use to compute the amount of depreciation lines.\n" + " * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" + " * Degressive: Calculated on basis of: Residual Value * Degressive Factor") + method_number = fields.Integer(string='Number of Depreciations', default=5, help="The number of depreciations needed to depreciate your asset") + method_period = fields.Integer(string='Period Length', default=1, help="State here the time between 2 depreciations, in months", required=True) + method_progress_factor = fields.Float('Degressive Factor', default=0.3) + method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')], string='Time Method', required=True, default='number', + help="Choose the method to use to compute the dates and number of entries.\n" + " * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n" + " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") + method_end = fields.Date('Ending date') + prorata = fields.Boolean(string='Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January') + open_asset = fields.Boolean(string='Auto-Confirm Assets', help="Check this if you want to automatically confirm the assets of this category when created by invoices.") + group_entries = fields.Boolean(string='Group Journal Entries', help="Check this if you want to group the generated entries by categories.") + type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase') + date_first_depreciation = fields.Selection([ + ('last_day_period', 'Based on Last Day of Purchase Period'), + ('manual', 'Manual (Defaulted on Purchase Date)')], + string='Depreciation Dates', default='manual', required=True, + help='The way to compute the date of the first depreciation.\n' + ' * Based on last day of purchase period: The depreciation dates will be based on the last day of the purchase month or the purchase year (depending on the periodicity of the depreciations).\n' + ' * Based on purchase date: The depreciation dates will be based on the purchase date.') + + @api.onchange('account_asset_id') + def onchange_account_asset(self): + if self.type == "purchase": + self.account_depreciation_id = self.account_asset_id + elif self.type == "sale": + self.account_depreciation_expense_id = self.account_asset_id + + @api.onchange('type') + def onchange_type(self): + if self.type == 'sale': + self.prorata = True + self.method_period = 1 + else: + self.method_period = 12 + + @api.onchange('method_time') + def _onchange_method_time(self): + if self.method_time != 'number': + self.prorata = False + + +class AccountAssetAsset(models.Model): + _name = 'account.asset.asset' + _description = 'Asset/Revenue Recognition' + _inherit = ['mail.thread'] + + entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries') + name = fields.Char(string='Asset Name', required=True, readonly=True, states={'draft': [('readonly', False)]}) + code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}) + value = fields.Float(string='Gross Value', required=True, readonly=True, digits=0, states={'draft': [('readonly', False)]}, oldname='purchase_value') + currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]}, + default=lambda self: self.env.user.company_id.currency_id.id) + company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]}, + default=lambda self: self.env['res.company']._company_default_get('account.asset.asset')) + note = fields.Text() + category_id = fields.Many2one('account.asset.category', string='Category', required=True, change_default=True, readonly=True, states={'draft': [('readonly', False)]}) + date = fields.Date(string='Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, oldname="purchase_date") + state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], 'Status', required=True, copy=False, default='draft', + help="When an asset is created, the status is 'Draft'.\n" + "If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n" + "You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.") + active = fields.Boolean(default=True) + partner_id = fields.Many2one('res.partner', string='Partner', readonly=True, states={'draft': [('readonly', False)]}) + method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='linear', + help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" + " * Degressive: Calculated on basis of: Residual Value * Degressive Factor") + method_number = fields.Integer(string='Number of Depreciations', readonly=True, states={'draft': [('readonly', False)]}, default=5, help="The number of depreciations needed to depreciate your asset") + method_period = fields.Integer(string='Number of Months in a Period', required=True, readonly=True, default=12, states={'draft': [('readonly', False)]}, + help="The amount of time between two depreciations, in months") + method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]}) + method_progress_factor = fields.Float(string='Degressive Factor', readonly=True, default=0.3, states={'draft': [('readonly', False)]}) + value_residual = fields.Float(compute='_amount_residual', method=True, digits=0, string='Residual Value') + method_time = fields.Selection([('number', 'Number of Entries'), ('end', 'Ending Date')], string='Time Method', required=True, readonly=True, default='number', states={'draft': [('readonly', False)]}, + help="Choose the method to use to compute the dates and number of entries.\n" + " * Number of Entries: Fix the number of entries and the time between 2 depreciations.\n" + " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") + prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]}, + help='Indicates that the first depreciation entry for this asset have to be done from the asset date (purchase date) instead of the first January / Start date of fiscal year') + depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id', string='Depreciation Lines', readonly=True, states={'draft': [('readonly', False)], 'open': [('readonly', False)]}) + salvage_value = fields.Float(string='Salvage Value', digits=0, readonly=True, states={'draft': [('readonly', False)]}, + help="It is the amount you plan to have that you cannot depreciate.") + invoice_id = fields.Many2one('account.move', string='Invoice', states={'draft': [('readonly', False)]}, copy=False) + type = fields.Selection(related="category_id.type", string='Type', required=True) + account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') + analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tag') + date_first_depreciation = fields.Selection([ + ('last_day_period', 'Based on Last Day of Purchase Period'), + ('manual', 'Manual')], + string='Depreciation Dates', default='manual', + readonly=True, states={'draft': [('readonly', False)]}, required=True, + help='The way to compute the date of the first depreciation.\n' + ' * Based on last day of purchase period: The depreciation dates will be based on the last day of the purchase month or the purchase year (depending on the periodicity of the depreciations).\n' + ' * Based on purchase date: The depreciation dates will be based on the purchase date.\n') + first_depreciation_manual_date = fields.Date( + string='First Depreciation Date', + readonly=True, states={'draft': [('readonly', False)]}, + help='Note that this date does not alter the computation of the first journal entry in case of prorata temporis assets. It simply changes its accounting date' + ) + + def unlink(self): + for asset in self: + if asset.state in ['open', 'close']: + raise UserError(_('You cannot delete a document that is in %s state.') % (asset.state,)) + for depreciation_line in asset.depreciation_line_ids: + if depreciation_line.move_id: + raise UserError(_('You cannot delete a document that contains posted entries.')) + return super(AccountAssetAsset, self).unlink() + + @api.model + def _cron_generate_entries(self): + self.compute_generated_entries(datetime.today()) + + @api.model + def compute_generated_entries(self, date, asset_type=None): + # Entries generated : one by grouped category and one by asset from ungrouped category + created_move_ids = [] + type_domain = [] + if asset_type: + type_domain = [('type', '=', asset_type)] + + ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)]) + created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False) + + for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]): + assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)]) + created_move_ids += assets._compute_entries(date, group_entries=True) + return created_move_ids + + def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date): + for record in self: + amount = 0 + if sequence == undone_dotation_number: + amount = residual_amount + else: + if record.method == 'linear': + amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids)) + if record.prorata: + amount = amount_to_depr / record.method_number + if sequence == 1: + date = record.date + if record.method_period % 12 != 0: + month_days = calendar.monthrange(date.year, date.month)[1] + days = month_days - date.day + 1 + amount = (amount_to_depr / record.method_number) / month_days * days + else: + days = (record.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1 + amount = (amount_to_depr / record.method_number) / total_days * days + elif record.method == 'degressive': + amount = residual_amount * record.method_progress_factor + if record.prorata: + if sequence == 1: + date = record.date + if record.method_period % 12 != 0: + month_days = calendar.monthrange(date.year, date.month)[1] + days = month_days - date.day + 1 + amount = (residual_amount * record.method_progress_factor) / month_days * days + else: + days = (record.company_id.compute_fiscalyear_dates(date)['date_to'] - date).days + 1 + amount = (residual_amount * record.method_progress_factor) / total_days * days + return amount + + def _compute_board_undone_dotation_nb(self, depreciation_date, total_days): + for record in self: + undone_dotation_number = record.method_number + if record.method_time == 'end': + end_date = record.method_end + undone_dotation_number = 0 + while depreciation_date <= end_date: + depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+record.method_period) + undone_dotation_number += 1 + if record.prorata: + undone_dotation_number += 1 + return undone_dotation_number + + def compute_depreciation_board(self): + self.ensure_one() + for record in self: + posted_depreciation_line_ids = record.depreciation_line_ids.filtered(lambda x: x.move_check).sorted(key=lambda l: l.depreciation_date) + unposted_depreciation_line_ids = record.depreciation_line_ids.filtered(lambda x: not x.move_check) + + # Remove old unposted depreciation lines. We cannot use unlink() with One2many field + commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] + + if record.value_residual != 0.0: + amount_to_depr = residual_amount = record.value_residual + + # if we already have some previous validated entries, starting date is last entry + method period + if posted_depreciation_line_ids and posted_depreciation_line_ids[-1].depreciation_date: + last_depreciation_date = fields.Date.from_string(posted_depreciation_line_ids[-1].depreciation_date) + depreciation_date = last_depreciation_date + relativedelta(months=+record.method_period) + else: + # depreciation_date computed from the purchase date + depreciation_date = record.date + if record.date_first_depreciation == 'last_day_period': + # depreciation_date = the last day of the month + depreciation_date = depreciation_date + relativedelta(day=31) + # ... or fiscalyear depending the number of period + if record.method_period == 12: + depreciation_date = depreciation_date + relativedelta(month=record.company_id.fiscalyear_last_month) + depreciation_date = depreciation_date + relativedelta(day=record.company_id.fiscalyear_last_day) + if depreciation_date < record.date: + depreciation_date = depreciation_date + relativedelta(years=1) + elif record.first_depreciation_manual_date and record.first_depreciation_manual_date != record.date: + # depreciation_date set manually from the 'first_depreciation_manual_date' field + depreciation_date = record.first_depreciation_manual_date + + total_days = (depreciation_date.year % 4) and 365 or 366 + month_day = depreciation_date.day + undone_dotation_number = record._compute_board_undone_dotation_nb(depreciation_date, total_days) + + for x in range(len(posted_depreciation_line_ids), undone_dotation_number): + sequence = x + 1 + amount = record._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date) + amount = record.currency_id.round(amount) + if float_is_zero(amount, precision_rounding=record.currency_id.rounding): + continue + residual_amount -= amount + vals = { + 'amount': amount, + 'asset_id': record.id, + 'sequence': sequence, + 'name': (record.code or '') + '/' + str(sequence), + 'remaining_value': residual_amount, + 'depreciated_value': record.value - (record.salvage_value + residual_amount), + 'depreciation_date': depreciation_date, + } + commands.append((0, False, vals)) + + depreciation_date = depreciation_date + relativedelta(months=+record.method_period) + + if month_day > 28 and record.date_first_depreciation == 'manual': + max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1] + depreciation_date = depreciation_date.replace(day=min(max_day_in_month, month_day)) + + # datetime doesn't take into account that the number of days is not the same for each month + if not record.prorata and record.method_period % 12 != 0 and record.date_first_depreciation == 'last_day_period': + max_day_in_month = calendar.monthrange(depreciation_date.year, depreciation_date.month)[1] + depreciation_date = depreciation_date.replace(day=max_day_in_month) + + record.write({'depreciation_line_ids': commands}) + + return True + + def validate(self): + self.write({'state': 'open'}) + fields = [ + 'method', + 'method_number', + 'method_period', + 'method_end', + 'method_progress_factor', + 'method_time', + 'salvage_value', + 'invoice_id', + ] + ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields) + for asset in self: + tracked_fields = ref_tracked_fields.copy() + if asset.method == 'linear': + del(tracked_fields['method_progress_factor']) + if asset.method_time != 'end': + del(tracked_fields['method_end']) + else: + del(tracked_fields['method_number']) + dummy, tracking_value_ids = asset._message_track(tracked_fields, dict.fromkeys(fields)) + asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids) + + def _return_disposal_view(self, move_ids): + name = _('Disposal Move') + view_mode = 'form' + if len(move_ids) > 1: + name = _('Disposal Moves') + view_mode = 'tree,form' + return { + 'name': name, + 'view_type': 'form', + 'view_mode': view_mode, + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'target': 'current', + 'res_id': move_ids[0], + } + + def _get_disposal_moves(self): + move_ids = [] + for asset in self: + unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check) + if unposted_depreciation_line_ids: + old_values = { + 'method_end': asset.method_end, + 'method_number': asset.method_number, + } + + # Remove all unposted depr. lines + commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] + + # Create a new depr. line with the residual amount and post it + sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1 + today = fields.Datetime.today() + vals = { + 'amount': asset.value_residual, + 'asset_id': asset.id, + 'sequence': sequence, + 'name': (asset.code or '') + '/' + str(sequence), + 'remaining_value': 0, + 'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated + 'depreciation_date': today, + } + commands.append((0, False, vals)) + asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence}) + tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end']) + changes, tracking_value_ids = asset._message_track(tracked_fields, old_values) + if changes: + asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids) + move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False) + + return move_ids + + def set_to_close(self): + move_ids = self._get_disposal_moves() + if move_ids: + return self._return_disposal_view(move_ids) + # Fallback, as if we just clicked on the smartbutton + return self.open_entries() + + def set_to_draft(self): + self.write({'state': 'draft'}) + + @api.depends('value', 'salvage_value', 'depreciation_line_ids.move_check', 'depreciation_line_ids.amount') + def _amount_residual(self): + for record in self: + total_amount = 0.0 + for line in record.depreciation_line_ids: + if line.move_check: + total_amount += line.amount + record.value_residual = record.value - total_amount - record.salvage_value + + @api.onchange('company_id') + def onchange_company_id(self): + self.currency_id = self.company_id.currency_id.id + + @api.onchange('date_first_depreciation') + def onchange_date_first_depreciation(self): + if self.date_first_depreciation == 'manual': + self.first_depreciation_manual_date = self.date + + @api.depends('depreciation_line_ids.move_id') + def _entry_count(self): + for asset in self: + res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)]) + asset.entry_count = res or 0 + + @api.constrains('prorata', 'method_time') + def _check_prorata(self): + if self.prorata and self.method_time != 'number': + raise ValidationError(_('Prorata temporis can be applied only for the "number of depreciations" time method.')) + + @api.onchange('category_id') + def onchange_category_id(self): + vals = self.onchange_category_id_values(self.category_id.id) + # We cannot use 'write' on an object that doesn't exist yet + if vals: + for k, v in vals['value'].items(): + setattr(self, k, v) + + def onchange_category_id_values(self, category_id): + if category_id: + category = self.env['account.asset.category'].browse(category_id) + return { + 'value': { + 'method': category.method, + 'method_number': category.method_number, + 'method_time': category.method_time, + 'method_period': category.method_period, + 'method_progress_factor': category.method_progress_factor, + 'method_end': category.method_end, + 'prorata': category.prorata, + 'date_first_depreciation': category.date_first_depreciation, + 'account_analytic_id': category.account_analytic_id.id, + 'analytic_tag_ids': [(6, 0, category.analytic_tag_ids.ids)], + } + } + + @api.onchange('method_time') + def onchange_method_time(self): + if self.method_time != 'number': + self.prorata = False + + def copy_data(self, default=None): + if default is None: + default = {} + default['name'] = self.name + _(' (copy)') + return super(AccountAssetAsset, self).copy_data(default) + + def _compute_entries(self, date, group_entries=False): + depreciation_ids = self.env['account.asset.depreciation.line'].search([ + ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), + ('move_check', '=', False)]) + if group_entries: + return depreciation_ids.create_grouped_move() + return depreciation_ids.create_move() + + @api.model + def create(self, vals): + asset = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals) + asset.sudo().compute_depreciation_board() + return asset + + def write(self, vals): + for record in self: + res = super(AccountAssetAsset, record).write(vals) + if 'depreciation_line_ids' not in vals and 'state' not in vals: + for rec in record: + rec.compute_depreciation_board() + return res + + def open_entries(self): + move_ids = [] + for asset in self: + for depreciation_line in asset.depreciation_line_ids: + if depreciation_line.move_id: + move_ids.append(depreciation_line.move_id.id) + return { + 'name': _('Journal Entries'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'account.move', + 'view_id': False, + 'type': 'ir.actions.act_window', + 'domain': [('id', 'in', move_ids)], + } + + +class AccountAssetDepreciationLine(models.Model): + _name = 'account.asset.depreciation.line' + _description = 'Asset depreciation line' + + name = fields.Char(string='Depreciation Name', required=True, index=True) + sequence = fields.Integer(required=True) + asset_id = fields.Many2one('account.asset.asset', string='Asset', required=True, ondelete='cascade') + parent_state = fields.Selection(related='asset_id.state', string='State of Asset') + amount = fields.Float(string='Current Depreciation', digits=0, required=True) + remaining_value = fields.Float(string='Next Period Depreciation', digits=0, required=True) + depreciated_value = fields.Float(string='Cumulative Depreciation', required=True) + depreciation_date = fields.Date('Depreciation Date', index=True) + move_id = fields.Many2one('account.move', string='Depreciation Entry') + move_check = fields.Boolean(compute='_get_move_check', string='Linked', track_visibility='always', store=True) + move_posted_check = fields.Boolean(compute='_get_move_posted_check', string='Posted', track_visibility='always', store=True) + + @api.depends('move_id') + def _get_move_check(self): + for line in self: + line.move_check = bool(line.move_id) + + @api.depends('move_id.state') + def _get_move_posted_check(self): + for line in self: + line.move_posted_check = True if line.move_id and line.move_id.state == 'posted' else False + + def create_move(self, post_move=True): + created_moves = self.env['account.move'] + for line in self: + if line.move_id: + raise UserError(_('This depreciation is already linked to a journal entry. Please post or delete it.')) + move_vals = self._prepare_move(line) + move = self.env['account.move'].create(move_vals) + line.write({'move_id': move.id, 'move_check': True}) + created_moves |= move + + if post_move and created_moves: + created_moves.filtered(lambda m: any(m.asset_depreciation_ids.mapped('asset_id.category_id.open_asset'))).post() + return [x.id for x in created_moves] + + def _prepare_move(self, line): + category_id = line.asset_id.category_id + account_analytic_id = line.asset_id.account_analytic_id + analytic_tag_ids = line.asset_id.analytic_tag_ids + depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) + company_currency = line.asset_id.company_id.currency_id + current_currency = line.asset_id.currency_id + prec = company_currency.decimal_places + amount = current_currency._convert( + line.amount, company_currency, line.asset_id.company_id, depreciation_date) + asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) + move_line_1 = { + 'name': asset_name, + 'account_id': category_id.account_depreciation_id.id, + 'debit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, + 'credit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, + 'partner_id': line.asset_id.partner_id.id, + 'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False, + 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False, + 'currency_id': company_currency != current_currency and current_currency.id or False, + 'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0, + } + move_line_2 = { + 'name': asset_name, + 'account_id': category_id.account_depreciation_expense_id.id, + 'credit': 0.0 if float_compare(amount, 0.0, precision_digits=prec) > 0 else -amount, + 'debit': amount if float_compare(amount, 0.0, precision_digits=prec) > 0 else 0.0, + 'partner_id': line.asset_id.partner_id.id, + 'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False, + 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False, + 'currency_id': company_currency != current_currency and current_currency.id or False, + 'amount_currency': company_currency != current_currency and line.amount or 0.0, + } + move_vals = { + 'ref': line.asset_id.code, + 'date': depreciation_date or False, + 'journal_id': category_id.journal_id.id, + 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], + } + return move_vals + + def _prepare_move_grouped(self): + asset_id = self[0].asset_id + category_id = asset_id.category_id # we can suppose that all lines have the same category + account_analytic_id = asset_id.account_analytic_id + analytic_tag_ids = asset_id.analytic_tag_ids + depreciation_date = self.env.context.get('depreciation_date') or fields.Date.context_today(self) + amount = 0.0 + for line in self: + # Sum amount of all depreciation lines + company_currency = line.asset_id.company_id.currency_id + current_currency = line.asset_id.currency_id + company = line.asset_id.company_id + amount += current_currency._convert(line.amount, company_currency, company, fields.Date.today()) + + name = category_id.name + _(' (grouped)') + move_line_1 = { + 'name': name, + 'account_id': category_id.account_depreciation_id.id, + 'debit': 0.0, + 'credit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': account_analytic_id.id if category_id.type == 'sale' else False, + 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'sale' else False, + } + move_line_2 = { + 'name': name, + 'account_id': category_id.account_depreciation_expense_id.id, + 'credit': 0.0, + 'debit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': account_analytic_id.id if category_id.type == 'purchase' else False, + 'analytic_tag_ids': [(6, 0, analytic_tag_ids.ids)] if category_id.type == 'purchase' else False, + } + move_vals = { + 'ref': category_id.name, + 'date': depreciation_date or False, + 'journal_id': category_id.journal_id.id, + 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], + } + + return move_vals + + def create_grouped_move(self, post_move=True): + if not self.exists(): + return [] + + created_moves = self.env['account.move'] + move = self.env['account.move'].create(self._prepare_move_grouped()) + self.write({'move_id': move.id, 'move_check': True}) + created_moves |= move + + if post_move and created_moves: + self.post_lines_and_close_asset() + created_moves.post() + return [x.id for x in created_moves] + + def post_lines_and_close_asset(self): + # we re-evaluate the assets to determine whether we can close them + for line in self: + line.log_message_when_posted() + asset = line.asset_id + if asset.currency_id.is_zero(asset.value_residual): + asset.message_post(body=_("Document closed.")) + asset.write({'state': 'close'}) + + def log_message_when_posted(self): + def _format_message(message_description, tracked_values): + message = '' + if message_description: + message = '%s' % message_description + for name, values in tracked_values.items(): + message += '
    • %s: ' % name + message += '%s
' % values + return message + + for line in self: + if line.move_id and line.move_id.state == 'draft': + partner_name = line.asset_id.partner_id.name + currency_name = line.asset_id.currency_id.name + msg_values = {_('Currency'): currency_name, _('Amount'): line.amount} + if partner_name: + msg_values[_('Partner')] = partner_name + msg = _format_message(_('Depreciation line posted.'), msg_values) + line.asset_id.message_post(body=msg) + + def unlink(self): + for record in self: + if record.move_check: + if record.asset_id.category_id.type == 'purchase': + msg = _("You cannot delete posted depreciation lines.") + else: + msg = _("You cannot delete posted installment lines.") + raise UserError(msg) + return super(AccountAssetDepreciationLine, self).unlink() diff --git a/base_accounting_kit/models/account_followup.py b/base_accounting_kit/models/account_followup.py new file mode 100644 index 000000000..0e7bc51b8 --- /dev/null +++ b/base_accounting_kit/models/account_followup.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import fields, api, models, _ + + +class Followup(models.Model): + _name = 'account.followup' + _description = 'Account Follow-up' + _rec_name = 'name' + + followup_line_ids = fields.One2many('followup.line', 'followup_id', 'Follow-up', copy=True) + company_id = fields.Many2one('res.company', 'Company', + default=lambda self: self.env['res.company']._company_default_get( + 'account_followup.followup')) + name = fields.Char(related='company_id.name', readonly=True) + + _sql_constraints = [('company_uniq', 'unique(company_id)', 'Only one follow-up per company is allowed')] + + +class FollowupLine(models.Model): + _name = 'followup.line' + _description = 'Follow-up Criteria' + _order = 'delay' + + name = fields.Char('Follow-Up Action', required=True, translate=True) + sequence = fields.Integer(help="Gives the sequence order when displaying a list of follow-up lines.") + delay = fields.Integer('Due Days', required=True, + help="The number of days after the due date of the invoice" + " to wait before sending the reminder." + " Could be negative if you want to send a polite alert beforehand.") + followup_id = fields.Many2one('account.followup', 'Follow Ups', ondelete="cascade") + + _sql_constraints = [('days_uniq', 'unique(followup_id, delay)', 'Days of the follow-up levels must be different')] + + @api.constrains('description') + def _check_description(self): + for line in self: + if line.description: + try: + line.description % {'partner_name': '', 'date': '', 'user_signature': '', 'company_name': ''} + except: + raise Warning(_( + 'Your description is invalid, use the right legend' + ' or %% if you want to use the percent character.')) diff --git a/base_accounting_kit/models/account_journal.py b/base_accounting_kit/models/account_journal.py new file mode 100755 index 000000000..9b11017c6 --- /dev/null +++ b/base_accounting_kit/models/account_journal.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import models, api + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + @api.depends('outbound_payment_method_ids') + def _compute_check_printing_payment_method_selected(self): + self.check_printing_payment_method_selected = any( + pm.code in ['check_printing', 'pdc'] for pm in + self.outbound_payment_method_ids) + + @api.model + def _enable_pdc_on_bank_journals(self): + """ Enables check printing payment method and add a check + sequence on bank journals. Called upon module installation + via data file. + """ + pdcin = self.env.ref('base_accounting_kit.account_payment_method_pdc_in') + pdcout = self.env.ref('base_accounting_kit.account_payment_method_pdc_out') + bank_journals = self.search([('type', '=', 'bank')]) + for bank_journal in bank_journals: + # bank_journal._create_check_sequence() + bank_journal.write({ + 'inbound_payment_method_ids': [(4, pdcin.id, None)], + 'outbound_payment_method_ids': [(4, pdcout.id, None)], + }) diff --git a/base_accounting_kit/models/account_move.py b/base_accounting_kit/models/account_move.py new file mode 100644 index 000000000..36e8e2ece --- /dev/null +++ b/base_accounting_kit/models/account_move.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models, _ +from odoo.addons.base.models import decimal_precision as dp +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = 'account.move' + + asset_depreciation_ids = fields.One2many('account.asset.depreciation.line', + 'move_id', + string='Assets Depreciation Lines', + ondelete="restrict") + + @api.model + def _refund_cleanup_lines(self, lines): + result = super(AccountMove, self)._refund_cleanup_lines(lines) + for i, line in enumerate(lines): + for name, field in line._fields.items(): + if name == 'asset_category_id': + result[i][2][name] = False + break + return result + + def action_cancel(self): + res = super(AccountMove, self).action_cancel() + self.env['account.asset.asset'].sudo().search( + [('invoice_id', 'in', self.ids)]).write({'active': False}) + return res + + def action_post(self): + result = super(AccountMove, self).action_post() + for inv in self: + context = dict(self.env.context) + # Within the context of an invoice, + # this default value is for the type of the invoice, not the type of the asset. + # This has to be cleaned from the context before creating the asset, + # otherwise it tries to create the asset with the type of the invoice. + context.pop('default_type', None) + inv.invoice_line_ids.with_context(context).asset_create() + return result + + def button_cancel(self): + for move in self: + for line in move.asset_depreciation_ids: + line.move_posted_check = False + return super(AccountMove, self).button_cancel() + + def post(self): + for move in self: + for depreciation_line in move.asset_depreciation_ids: + depreciation_line.post_lines_and_close_asset() + return super(AccountMove, self).post() + + +class AccountMoveLine(models.Model): + _inherit = 'account.move.line' + + asset_category_id = fields.Many2one('account.asset.category', + string='Asset Category') + type_rel = fields.Selection(related='move_id.type') + move_asset_category_id = fields.Many2one('account.move') + asset_start_date = fields.Date(string='Asset Start Date', + compute='_get_asset_date', readonly=True, + store=True) + asset_end_date = fields.Date(string='Asset End Date', + compute='_get_asset_date', readonly=True, + store=True) + asset_mrr = fields.Float(string='Monthly Recurring Revenue', + compute='_get_asset_date', readonly=True, + digits=dp.get_precision('Account'), store=True) + + @api.depends('asset_category_id', 'move_id.invoice_date') + def _get_asset_date(self): + for record in self: + record.asset_mrr = 0 + record.asset_start_date = False + record.asset_end_date = False + cat = record.asset_category_id + if cat: + if cat.method_number == 0 or cat.method_period == 0: + raise UserError(_('The number of depreciations or ' + 'the period length of your asset category cannot be 0.')) + months = cat.method_number * cat.method_period + if record.move_id.type in ['out_invoice', 'out_refund']: + record.asset_mrr = record.price_subtotal / months + if record.move_id.invoice_date: + start_date = record.move_id.invoice_date.replace(day=1) + end_date = (start_date + relativedelta(months=months, + days=-1)) + record.asset_start_date = start_date + record.asset_end_date = end_date + + def asset_create(self): + for record in self: + if record.asset_category_id: + vals = { + 'name': record.name, + 'code': record.move_id.name or False, + 'category_id': record.asset_category_id.id, + 'value': record.price_subtotal, + 'partner_id': record.move_id.partner_id.id, + 'company_id': record.move_id.company_id.id, + 'currency_id': record.move_id.company_currency_id.id, + 'date': record.move_id.invoice_date, + 'invoice_id': record.move_id.id, + } + changed_vals = record.env[ + 'account.asset.asset'].onchange_category_id_values( + vals['category_id']) + vals.update(changed_vals['value']) + asset = record.env['account.asset.asset'].create(vals) + if record.asset_category_id.open_asset: + asset.validate() + return True + + @api.onchange('asset_category_id') + def onchange_asset_category_id(self): + if self.move_id.type == 'out_invoice' and self.asset_category_id: + self.account_id = self.asset_category_id.account_asset_id.id + elif self.move_id.type == 'in_invoice' and self.asset_category_id: + self.account_id = self.asset_category_id.account_asset_id.id + + @api.onchange('uom_id') + def _onchange_uom_id(self): + result = super(AccountMoveLine, self)._onchange_uom_id() + self.onchange_asset_category_id() + return result + + @api.onchange('product_id') + def _onchange_product_id(self): + vals = super(AccountMoveLine, self)._onchange_product_id() + if self.product_id: + if self.move_id.type == 'out_invoice': + self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id + elif self.move_id.type == 'in_invoice': + self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id + return vals + + def _set_additional_fields(self, invoice): + if not self.asset_category_id: + if invoice.type == 'out_invoice': + self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id + elif invoice.type == 'in_invoice': + self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id + self.onchange_asset_category_id() + super(AccountMoveLine, self)._set_additional_fields(invoice) + + def get_invoice_line_account(self, type, product, fpos, company): + return product.asset_category_id.account_asset_id or super( + AccountMoveLine, self).get_invoice_line_account(type, + product, + fpos, + company) diff --git a/base_accounting_kit/models/account_payment.py b/base_accounting_kit/models/account_payment.py new file mode 100755 index 000000000..85647c4c6 --- /dev/null +++ b/base_accounting_kit/models/account_payment.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import models, fields, _ +from odoo.exceptions import UserError + + +class AccountRegisterPayments(models.TransientModel): + _inherit = "account.payment.register" + + bank_reference = fields.Char(copy=False) + cheque_reference = fields.Char(copy=False) + effective_date = fields.Date('Effective Date', + help='Effective date of PDC', copy=False, + default=False) + + def get_payments_vals(self): + res = super(AccountRegisterPayments, self).get_payment_vals() + if self.payment_method_id == self.env.ref( + 'account_check_printing.account_payment_method_check'): + res.update({ + 'check_amount_in_words': self.check_amount_in_words, + 'check_manual_sequencing': self.check_manual_sequencing, + 'effective_date': self.effective_date, + }) + return res + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + bank_reference = fields.Char(copy=False) + cheque_reference = fields.Char(copy=False) + effective_date = fields.Date('Effective Date', + help='Effective date of PDC', copy=False, + default=False) + + def print_checks(self): + """ Check that the recordset is valid, set the payments state to + sent and call print_checks() """ + # Since this method can be called via a client_action_multi, we + # need to make sure the received records are what we expect + self = self.filtered(lambda r: + r.payment_method_id.code + in ['check_printing', 'pdc'] + and r.state != 'reconciled') + if len(self) == 0: + raise UserError(_( + "Payments to print as a checks must have 'Check' " + "or 'PDC' selected as payment method and " + "not have already been reconciled")) + if any(payment.journal_id != self[0].journal_id for payment in self): + raise UserError(_( + "In order to print multiple checks at once, they " + "must belong to the same bank journal.")) + + if not self[0].journal_id.check_manual_sequencing: + # The wizard asks for the number printed on the first + # pre-printed check so payments are attributed the + # number of the check the'll be printed on. + last_printed_check = self.search([ + ('journal_id', '=', self[0].journal_id.id), + ('check_number', '!=', "0")], order="check_number desc", + limit=1) + next_check_number = last_printed_check and int( + last_printed_check.check_number) + 1 or 1 + return { + 'name': _('Print Pre-numbered Checks'), + 'type': 'ir.actions.act_window', + 'res_model': 'print.prenumbered.checks', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'payment_ids': self.ids, + 'default_next_check_number': next_check_number, + } + } + else: + self.filtered(lambda r: r.state == 'draft').post() + self.write({'state': 'sent'}) + return self.do_print_checks() + + def _prepare_payment_moves(self): + """ supered function to set effective date """ + res = super(AccountPayment, self)._prepare_payment_moves() + inbound_pdc_id = self.env.ref( + 'base_accounting_kit.account_payment_method_pdc_in').id + outbound_pdc_id = self.env.ref( + 'base_accounting_kit.account_payment_method_pdc_out').id + if self.payment_method_id.id == inbound_pdc_id or \ + self.payment_method_id.id == outbound_pdc_id \ + and self.effective_date: + res[0]['date'] = self.effective_date + for line in res[0]['line_ids']: + line[2]['date_maturity'] = self.effective_date + return res diff --git a/base_accounting_kit/models/credit_limit.py b/base_accounting_kit/models/credit_limit.py new file mode 100644 index 000000000..c69a2e600 --- /dev/null +++ b/base_accounting_kit/models/credit_limit.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import models, fields, api +from odoo.exceptions import UserError +from odoo.tools.translate import _ + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + warning_stage = fields.Float(string='Warning Amount', + help="A warning message will appear once the " + "selected customer is crossed warning " + "amount. Set its value to 0.00 to" + " disable this feature") + blocking_stage = fields.Float(string='Blocking Amount', + help="Cannot make sales once the selected " + "customer is crossed blocking amount." + "Set its value to 0.00 to disable " + "this feature") + due_amount = fields.Float(string="Total Sale", + compute="compute_due_amount") + active_limit = fields.Boolean("Active Credit Limit", default=False) + + enable_credit_limit = fields.Boolean(string="Credit Limit Enabled", + compute="_compute_enable_credit_limit") + + def compute_due_amount(self): + for rec in self: + if not rec.id: + continue + rec.due_amount = rec.credit - rec.debit + + def _compute_enable_credit_limit(self): + """ Check credit limit is enabled in account settings """ + params = self.env['ir.config_parameter'].sudo() + customer_credit_limit = params.get_param('customer_credit_limit', + default=False) + for rec in self: + rec.enable_credit_limit = True if customer_credit_limit else False + + @api.constrains('warning_stage', 'blocking_stage') + def constrains_warning_stage(self): + if self.active_limit and self.enable_credit_limit: + if self.warning_stage >= self.blocking_stage: + if self.blocking_stage > 0: + raise UserError(_( + "Warning amount should be less than Blocking amount")) + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + has_due = fields.Boolean() + is_warning = fields.Boolean() + due_amount = fields.Float(related='partner_id.due_amount') + + def _action_confirm(self): + """To check the selected customers due amount is exceed than + blocking stage""" + pay_type = ['out_invoice', 'out_refund', 'out_receipt'] + if self.partner_id.active_limit and self.type in pay_type \ + and self.partner_id.enable_credit_limit: + if self.due_amount >= self.partner_id.blocking_stage: + if self.partner_id.blocking_stage != 0: + raise UserError(_( + "%s is in Blocking Stage and " + "has a due amount of %s %s to pay") % ( + self.partner_id.name, self.due_amount, + self.currency_id.symbol)) + return super(SaleOrder, self)._action_confirm() + + @api.onchange('partner_id') + def check_due(self): + """To show the due amount and warning stage""" + if self.partner_id and self.partner_id.due_amount > 0 \ + and self.partner_id.active_limit \ + and self.partner_id.enable_credit_limit: + self.has_due = True + else: + self.has_due = False + if self.partner_id and self.partner_id.active_limit\ + and self.partner_id.enable_credit_limit: + if self.due_amount >= self.partner_id.warning_stage: + if self.partner_id.warning_stage != 0: + self.is_warning = True + else: + self.is_warning = False + + +class AccountMove(models.Model): + _inherit = 'account.move' + + has_due = fields.Boolean() + is_warning = fields.Boolean() + due_amount = fields.Float(related='partner_id.due_amount') + + def action_post(self): + """To check the selected customers due amount is exceed than + blocking stage""" + pay_type = ['out_invoice', 'out_refund', 'out_receipt'] + if self.partner_id.active_limit and self.type in pay_type \ + and self.partner_id.enable_credit_limit: + if self.due_amount >= self.partner_id.blocking_stage: + if self.partner_id.blocking_stage != 0: + raise UserError(_( + "%s is in Blocking Stage and " + "has a due amount of %s %s to pay") % ( + self.partner_id.name, self.due_amount, + self.currency_id.symbol)) + return super(AccountMove, self).action_post() + + @api.onchange('partner_id') + def check_due(self): + """To show the due amount and warning stage""" + if self.partner_id and self.partner_id.due_amount > 0 \ + and self.partner_id.active_limit \ + and self.partner_id.enable_credit_limit: + self.has_due = True + else: + self.has_due = False + if self.partner_id and self.partner_id.active_limit \ + and self.partner_id.enable_credit_limit: + if self.due_amount >= self.partner_id.warning_stage: + if self.partner_id.warning_stage != 0: + self.is_warning = True + else: + self.is_warning = False diff --git a/base_accounting_kit/models/product_template.py b/base_accounting_kit/models/product_template.py new file mode 100644 index 000000000..a7fdf011a --- /dev/null +++ b/base_accounting_kit/models/product_template.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + asset_category_id = fields.Many2one('account.asset.category', + string='Asset Type', + company_dependent=True, + ondelete="restrict") + deferred_revenue_category_id = fields.Many2one('account.asset.category', + string='Deferred Revenue Type', + company_dependent=True, + ondelete="restrict") + + def _get_asset_accounts(self): + res = super(ProductTemplate, self)._get_asset_accounts() + if self.asset_category_id: + res['stock_input'] = self.property_account_expense_id + if self.deferred_revenue_category_id: + res['stock_output'] = self.property_account_income_id + return res diff --git a/base_accounting_kit/models/recurring_payments.py b/base_accounting_kit/models/recurring_payments.py new file mode 100644 index 000000000..80b67967f --- /dev/null +++ b/base_accounting_kit/models/recurring_payments.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from datetime import datetime, date + +from dateutil.relativedelta import relativedelta + +from odoo import models, fields, api + + +class FilterRecurringEntries(models.Model): + _inherit = 'account.move' + + recurring_ref = fields.Char() + + +class RecurringPayments(models.Model): + _name = 'account.recurring.payments' + + def _get_next_schedule(self): + if self.date: + recurr_dates = [] + today = datetime.today() + start_date = datetime.strptime(str(self.date), '%Y-%m-%d') + while start_date <= today: + recurr_dates.append(str(start_date.date())) + if self.recurring_period == 'days': + start_date += relativedelta(days=self.recurring_interval) + elif self.recurring_period == 'weeks': + start_date += relativedelta(weeks=self.recurring_interval) + elif self.recurring_period == 'months': + start_date += relativedelta(months=self.recurring_interval) + else: + start_date += relativedelta(years=self.recurring_interval) + self.next_date = start_date.date() + + name = fields.Char('Name') + debit_account = fields.Many2one('account.account', 'Debit Account', + required=True, + domain="['|', ('company_id', '=', False), " + "('company_id', '=', company_id)]") + credit_account = fields.Many2one('account.account', 'Credit Account', + required=True, + domain="['|', ('company_id', '=', False), " + "('company_id', '=', company_id)]") + journal_id = fields.Many2one('account.journal', 'Journal', required=True) + analytic_account_id = fields.Many2one('account.analytic.account', + 'Analytic Account') + date = fields.Date('Starting Date', required=True, default=date.today()) + next_date = fields.Date('Next Schedule', compute=_get_next_schedule, + readonly=True, copy=False) + recurring_period = fields.Selection(selection=[('days', 'Days'), + ('weeks', 'Weeks'), + ('months', 'Months'), + ('years', 'Years')], + store=True, required=True) + amount = fields.Float('Amount') + description = fields.Text('Description') + state = fields.Selection(selection=[('draft', 'Draft'), + ('running', 'Running')], + default='draft', string='Status') + journal_state = fields.Selection(selection=[('draft', 'Unposted'), + ('posted', 'Posted')], + required=True, default='draft', + string='Generate Journal As') + recurring_interval = fields.Integer('Recurring Interval', default=1) + partner_id = fields.Many2one('res.partner', 'Partner') + pay_time = fields.Selection(selection=[('pay_now', 'Pay Directly'), + ('pay_later', 'Pay Later')], + store=True, required=True) + company_id = fields.Many2one('res.company', + default=lambda l: l.env.user.company_id.id) + + @api.onchange('partner_id') + def onchange_partner_id(self): + if self.partner_id.property_account_receivable_id: + self.credit_account = self.partner_id.property_account_payable_id diff --git a/base_accounting_kit/models/res_config_settings.py b/base_accounting_kit/models/res_config_settings.py new file mode 100644 index 000000000..a01b9f660 --- /dev/null +++ b/base_accounting_kit/models/res_config_settings.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import models, fields, api + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + customer_credit_limit = fields.Boolean(string="Customer Credit Limit") + + @api.model + def get_values(self): + res = super(ResConfigSettings, self).get_values() + params = self.env['ir.config_parameter'].sudo() + customer_credit_limit = params.get_param('customer_credit_limit', + default=False) + res.update(customer_credit_limit=customer_credit_limit) + return res + + def set_values(self): + super(ResConfigSettings, self).set_values() + self.env['ir.config_parameter'].sudo().set_param( + "customer_credit_limit", + self.customer_credit_limit) diff --git a/base_accounting_kit/models/res_partner.py b/base_accounting_kit/models/res_partner.py new file mode 100644 index 000000000..ad164b221 --- /dev/null +++ b/base_accounting_kit/models/res_partner.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from datetime import date, timedelta + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + invoice_list = fields.One2many('account.move', 'partner_id', + string="Invoice Details", + readonly=True, + domain=( + [('invoice_payment_state', '=', 'not_paid'), + ('type', '=', 'out_invoice')])) + total_due = fields.Monetary(compute='_compute_for_followup', store=False, + readonly=True) + next_reminder_date = fields.Date(compute='_compute_for_followup', + store=False, readonly=True) + total_overdue = fields.Monetary(compute='_compute_for_followup', + store=False, readonly=True) + followup_status = fields.Selection( + [('in_need_of_action', 'In need of action'), + ('with_overdue_invoices', 'With overdue invoices'), + ('no_action_needed', 'No action needed')], + string='Followup status', + ) + + def _compute_for_followup(self): + """ + Compute the fields 'total_due', 'total_overdue' , 'next_reminder_date' and 'followup_status' + """ + for record in self: + total_due = 0 + total_overdue = 0 + today = fields.Date.today() + for am in record.invoice_list: + if am.company_id == self.env.user.company_id: + amount = am.amount_residual + total_due += amount + is_overdue = today > am.invoice_date_due if am.invoice_date_due else today > am.date + if is_overdue: + total_overdue += not am.invoice_sent and amount or 0 + min_date = record.get_min_date() + action = record.action_after() + if min_date: + date_reminder = min_date + timedelta(days=action) + if date_reminder: + record.next_reminder_date = date_reminder + else: + date_reminder = today + record.next_reminder_date = date_reminder + if total_overdue > 0 and date_reminder > today: + followup_status = "with_overdue_invoices" + elif total_due > 0 and date_reminder <= today: + followup_status = "in_need_of_action" + else: + followup_status = "no_action_needed" + record.total_due = total_due + record.total_overdue = total_overdue + record.followup_status = followup_status + + def get_min_date(self): + today = date.today() + for this in self: + if this.invoice_list: + min_list = this.invoice_list.mapped('invoice_date_due') + while False in min_list: + min_list.remove(False) + return min(min_list) + else: + return today + + def get_delay(self): + delay = """select id,delay from followup_line where followup_id = + (select id from account_followup where company_id = %s) + order by delay limit 1""" + self._cr.execute(delay, [self.env.user.company_id.id]) + record = self.env.cr.dictfetchall() + return record + + def action_after(self): + lines = self.env['followup.line'].search([( + 'followup_id.company_id', '=', self.env.user.company_id.id)]) + if lines: + record = self.get_delay() + for i in record: + return i['delay'] diff --git a/base_accounting_kit/report/__init__.py b/base_accounting_kit/report/__init__.py new file mode 100644 index 000000000..60ca24801 --- /dev/null +++ b/base_accounting_kit/report/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import general_ledger_report +from . import account_report_common_account +from . import report_partner_ledger +from . import report_tax +from . import report_trial_balance +from . import report_aged_partner +from . import report_journal_audit +from . import report_financial +from . import cash_flow_report +from . import account_bank_book +from . import account_cash_book +from . import account_day_book +from . import account_asset_report + diff --git a/base_accounting_kit/report/account_asset_report.py b/base_accounting_kit/report/account_asset_report.py new file mode 100644 index 000000000..aaff1991a --- /dev/null +++ b/base_accounting_kit/report/account_asset_report.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import fields, models, tools + + +class AssetAssetReport(models.Model): + _name = "asset.asset.report" + _description = "Assets Analysis" + _auto = False + + name = fields.Char(string='Year', required=False, readonly=True) + date = fields.Date(readonly=True) + depreciation_date = fields.Date(string='Depreciation Date', readonly=True) + asset_id = fields.Many2one('account.asset.asset', string='Asset', + readonly=True) + asset_category_id = fields.Many2one('account.asset.category', + string='Asset category', readonly=True) + partner_id = fields.Many2one('res.partner', string='Partner', + readonly=True) + state = fields.Selection( + [('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], + string='Status', readonly=True) + depreciation_value = fields.Float(string='Amount of Depreciation Lines', + readonly=True) + installment_value = fields.Float(string='Amount of Installment Lines', + readonly=True) + move_check = fields.Boolean(string='Posted', readonly=True) + installment_nbr = fields.Integer(string='Installment Count', readonly=True) + depreciation_nbr = fields.Integer(string='Depreciation Count', + readonly=True) + gross_value = fields.Float(string='Gross Amount', readonly=True) + posted_value = fields.Float(string='Posted Amount', readonly=True) + unposted_value = fields.Float(string='Unposted Amount', readonly=True) + company_id = fields.Many2one('res.company', string='Company', + readonly=True) + + def init(self): + tools.drop_view_if_exists(self._cr, 'asset_asset_report') + self._cr.execute(""" + create or replace view asset_asset_report as ( + select + min(dl.id) as id, + dl.name as name, + dl.depreciation_date as depreciation_date, + a.date as date, + (CASE WHEN dlmin.id = min(dl.id) + THEN a.value + ELSE 0 + END) as gross_value, + dl.amount as depreciation_value, + dl.amount as installment_value, + (CASE WHEN dl.move_check + THEN dl.amount + ELSE 0 + END) as posted_value, + (CASE WHEN NOT dl.move_check + THEN dl.amount + ELSE 0 + END) as unposted_value, + dl.asset_id as asset_id, + dl.move_check as move_check, + a.category_id as asset_category_id, + a.partner_id as partner_id, + a.state as state, + count(dl.*) as installment_nbr, + count(dl.*) as depreciation_nbr, + a.company_id as company_id + from account_asset_depreciation_line dl + left join account_asset_asset a on (dl.asset_id=a.id) + left join (select min(d.id) as id,ac.id as ac_id from account_asset_depreciation_line as d inner join account_asset_asset as ac ON (ac.id=d.asset_id) group by ac_id) as dlmin on dlmin.ac_id=a.id + where a.active is true + group by + dl.amount,dl.asset_id,dl.depreciation_date,dl.name, + a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id, + a.value, a.id, a.salvage_value, dlmin.id + )""") diff --git a/base_accounting_kit/report/account_asset_report_views.xml b/base_accounting_kit/report/account_asset_report_views.xml new file mode 100644 index 000000000..af8370a63 --- /dev/null +++ b/base_accounting_kit/report/account_asset_report_views.xml @@ -0,0 +1,80 @@ + + + + + asset.asset.report.pivot + asset.asset.report + + + + + + + + + + + asset.asset.report.graph + asset.asset.report + + + + + + + + + + + asset.asset.report.search + asset.asset.report + + + + + + + + + + + + + + + + + + + + + + + + + + + + Assets Analysis + asset.asset.report + graph,pivot + [('asset_category_id.type', '=', 'purchase')] + {} + +

+ No content +

+ From this report, you can have an overview on all depreciations. The + search bar can also be used to personalize your assets depreciation reporting. +

+
+
+ + +
diff --git a/base_accounting_kit/report/account_bank_book.py b/base_accounting_kit/report/account_bank_book.py new file mode 100644 index 000000000..45c36d1c0 --- /dev/null +++ b/base_accounting_kit/report/account_bank_book.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from datetime import time + +from odoo import models, api, _ +from odoo.exceptions import UserError + + +class ReportBankBook(models.AbstractModel): + _name = 'report.base_accounting_kit.report_bank_book' + + def _get_account_move_entry(self, accounts, init_balance, sortby, + display_account): + cr = self.env.cr + move_line = self.env['account.move.line'] + move_lines = {x: [] for x in accounts.ids} + + # Prepare initial sql query and Get the initial move lines + if init_balance: + init_tables, init_where_clause, init_where_params = move_line.with_context( + date_from=self.env.context.get('date_from'), date_to=False, + initial_bal=True)._query_get() + init_wheres = [""] + if init_where_clause.strip(): + init_wheres.append(init_where_clause.strip()) + init_filters = " AND ".join(init_wheres) + filters = init_filters.replace('account_move_line__move_id', + 'm').replace('account_move_line', + 'l') + sql = ("""SELECT 0 AS lid, l.account_id AS account_id, \ + '' AS ldate, '' AS lcode, 0.0 AS amount_currency, \ + '' AS lref, 'Initial Balance' AS lname, \ + COALESCE(SUM(l.debit),0.0) AS debit, \ + COALESCE(SUM(l.credit),0.0) AS credit, \ + COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, \ + '' AS lpartner_id,\ + '' AS move_name, '' AS mmove_id, '' AS currency_code,\ + NULL AS currency_id,\ + '' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\ + '' AS partner_name\ + FROM account_move_line l\ + LEFT JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + WHERE l.account_id IN %s""" + filters + ' GROUP BY l.account_id') + params = (tuple(accounts.ids),) + tuple(init_where_params) + cr.execute(sql, params) + for row in cr.dictfetchall(): + move_lines[row.pop('account_id')].append(row) + sql_sort = 'l.date, l.move_id' + if sortby == 'sort_journal_partner': + sql_sort = 'j.code, p.name, l.move_id' + + # Prepare sql query base on selected parameters from wizard + tables, where_clause, where_params = move_line._query_get() + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + filters = " AND ".join(wheres) + filters = filters.replace('account_move_line__move_id', 'm').replace( + 'account_move_line', 'l') + + # Get move lines base on sql query and Calculate the total + # balance of move lines + sql = ('''SELECT l.id AS lid, l.account_id \ + AS account_id, l.date AS ldate, j.code AS lcode,\ + l.currency_id, l.amount_currency, l.ref AS lref, l.name AS lname,\ + COALESCE(l.debit,0) AS debit, \ + COALESCE(l.credit,0) AS credit, \ + COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,\ + m.name AS move_name, c.symbol AS \ + currency_code, p.name AS partner_name\ + FROM account_move_line l\ + JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + JOIN account_account acc ON (l.account_id = acc.id) \ + WHERE l.account_id IN %s ''' + filters + ''' GROUP BY \ + l.id, l.account_id, l.date, j.code, l.currency_id, \ + l.amount_currency, l.ref, l.name, m.name, \ + c.symbol, p.name ORDER BY ''' + sql_sort) + params = (tuple(accounts.ids),) + tuple(where_params) + cr.execute(sql, params) + + for row in cr.dictfetchall(): + balance = 0 + for line in move_lines.get(row['account_id']): + balance += line['debit'] - line['credit'] + row['balance'] += balance + move_lines[row.pop('account_id')].append(row) + + # Calculate the debit, credit and balance for Accounts + account_res = [] + for account in accounts: + currency = account.currency_id and \ + account.currency_id or account.company_id.currency_id + res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance']) + res['code'] = account.code + res['name'] = account.name + res['move_lines'] = move_lines[account.id] + for line in res.get('move_lines'): + res['debit'] += line['debit'] + res['credit'] += line['credit'] + res['balance'] = line['balance'] + if display_account == 'all': + account_res.append(res) + if display_account == 'movement' and res.get('move_lines'): + account_res.append(res) + if display_account == 'not_zero' and not currency.is_zero( + res['balance']): + account_res.append(res) + + return account_res + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get('active_model'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse( + self.env.context.get('active_ids', [])) + init_balance = data['form'].get('initial_balance', True) + sortby = data['form'].get('sortby', 'sort_date') + display_account = 'movement' + codes = [] + if data['form'].get('journal_ids', False): + codes = [journal.code for journal in + self.env['account.journal'].search( + [('id', 'in', data['form']['journal_ids'])])] + account_ids = data['form']['account_ids'] + accounts = self.env['account.account'].search( + [('id', 'in', account_ids)]) + accounts_res = self.with_context( + data['form'].get('used_context', {}))._get_account_move_entry( + accounts, + init_balance, + sortby, + display_account) + return { + 'doc_ids': docids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'Accounts': accounts_res, + 'print_journal': codes, + } diff --git a/base_accounting_kit/report/account_bank_book_view.xml b/base_accounting_kit/report/account_bank_book_view.xml new file mode 100644 index 000000000..425dfb12f --- /dev/null +++ b/base_accounting_kit/report/account_bank_book_view.xml @@ -0,0 +1,133 @@ + + + + diff --git a/base_accounting_kit/report/account_cash_book.py b/base_accounting_kit/report/account_cash_book.py new file mode 100644 index 000000000..49b94e4a4 --- /dev/null +++ b/base_accounting_kit/report/account_cash_book.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from datetime import time + +from odoo import models, api, _ +from odoo.exceptions import UserError + + +class ReportCashBook(models.AbstractModel): + _name = 'report.base_accounting_kit.report_cash_book' + + def _get_account_move_entry(self, accounts, init_balance, sortby, + display_account): + + cr = self.env.cr + move_line = self.env['account.move.line'] + move_lines = {x: [] for x in accounts.ids} + + # Prepare initial sql query and Get the initial move lines + if init_balance: + init_tables, init_where_clause, init_where_params = move_line.with_context( + date_from=self.env.context.get('date_from'), date_to=False, + initial_bal=True)._query_get() + init_wheres = [""] + if init_where_clause.strip(): + init_wheres.append(init_where_clause.strip()) + init_filters = " AND ".join(init_wheres) + filters = init_filters.replace('account_move_line__move_id', + 'm').replace('account_move_line', + 'l') + sql = ("""SELECT 0 AS lid, l.account_id AS account_id, '' AS ldate, '' AS lcode, 0.0 AS amount_currency, '' AS lref, 'Initial Balance' AS lname, COALESCE(SUM(l.debit),0.0) AS debit, COALESCE(SUM(l.credit),0.0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, '' AS lpartner_id,\ + '' AS move_name, '' AS mmove_id, '' AS currency_code,\ + NULL AS currency_id,\ + '' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\ + '' AS partner_name\ + FROM account_move_line l\ + LEFT JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + WHERE l.account_id IN %s""" + filters + ' GROUP BY l.account_id') + params = (tuple(accounts.ids),) + tuple(init_where_params) + cr.execute(sql, params) + for row in cr.dictfetchall(): + move_lines[row.pop('account_id')].append(row) + sql_sort = 'l.date, l.move_id' + if sortby == 'sort_journal_partner': + sql_sort = 'j.code, p.name, l.move_id' + + # Prepare sql query base on selected parameters from wizard + tables, where_clause, where_params = move_line._query_get() + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + filters = " AND ".join(wheres) + filters = filters.replace('account_move_line__move_id', 'm').replace( + 'account_move_line', 'l') + + # Get move lines base on sql query and Calculate the total balance of move lines + sql = ('''SELECT l.id AS lid, l.account_id AS account_id, l.date AS ldate, j.code AS lcode, l.currency_id, l.amount_currency, l.ref AS lref, l.name AS lname, COALESCE(l.debit,0) AS debit, COALESCE(l.credit,0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,\ + m.name AS move_name, c.symbol AS currency_code, p.name AS partner_name\ + FROM account_move_line l\ + JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + JOIN account_account acc ON (l.account_id = acc.id) \ + WHERE l.account_id IN %s ''' + filters + ''' GROUP BY l.id, l.account_id, l.date, j.code, l.currency_id, l.amount_currency, l.ref, l.name, m.name, c.symbol, p.name ORDER BY ''' + sql_sort) + params = (tuple(accounts.ids),) + tuple(where_params) + cr.execute(sql, params) + + for row in cr.dictfetchall(): + balance = 0 + for line in move_lines.get(row['account_id']): + balance += line['debit'] - line['credit'] + row['balance'] += balance + move_lines[row.pop('account_id')].append(row) + + # Calculate the debit, credit and balance for Accounts + account_res = [] + for account in accounts: + currency = account.currency_id and account.currency_id or account.company_id.currency_id + res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance']) + res['code'] = account.code + res['name'] = account.name + res['move_lines'] = move_lines[account.id] + for line in res.get('move_lines'): + res['debit'] += line['debit'] + res['credit'] += line['credit'] + res['balance'] = line['balance'] + if display_account == 'all': + account_res.append(res) + if display_account == 'movement' and res.get('move_lines'): + account_res.append(res) + if display_account == 'not_zero' and not currency.is_zero( + res['balance']): + account_res.append(res) + + return account_res + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get('active_model'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse( + self.env.context.get('active_ids', [])) + init_balance = data['form'].get('initial_balance', True) + sortby = data['form'].get('sortby', 'sort_date') + display_account = 'movement' + codes = [] + if data['form'].get('journal_ids', False): + codes = [journal.code for journal in + self.env['account.journal'].search( + [('id', 'in', data['form']['journal_ids'])])] + account_ids = data['form']['account_ids'] + accounts = self.env['account.account'].search( + [('id', 'in', account_ids)]) + accounts_res = self.with_context( + data['form'].get('used_context', {}))._get_account_move_entry( + accounts, + init_balance, + sortby, + display_account) + return { + 'doc_ids': docids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'Accounts': accounts_res, + 'print_journal': codes, + } diff --git a/base_accounting_kit/report/account_cash_book_view.xml b/base_accounting_kit/report/account_cash_book_view.xml new file mode 100644 index 000000000..7a6af7ac1 --- /dev/null +++ b/base_accounting_kit/report/account_cash_book_view.xml @@ -0,0 +1,108 @@ + + + + + diff --git a/base_accounting_kit/report/account_day_book.py b/base_accounting_kit/report/account_day_book.py new file mode 100644 index 000000000..f3c9958bb --- /dev/null +++ b/base_accounting_kit/report/account_day_book.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +import time +from datetime import timedelta, datetime + +from odoo import models, api, _ +from odoo.exceptions import UserError + + +class DayBookPdfReport(models.AbstractModel): + _name = 'report.base_accounting_kit.day_book_report_template' + + def _get_account_move_entry(self, accounts, form_data, pass_date): + cr = self.env.cr + move_line = self.env['account.move.line'] + tables, where_clause, where_params = move_line._query_get() + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + if form_data['target_move'] == 'posted': + target_move = "AND m.state = 'posted'" + else: + target_move = '' + sql = (''' + SELECT l.id AS lid, acc.name as accname, l.account_id AS account_id, l.date AS ldate, j.code AS lcode, l.currency_id, + l.amount_currency, l.ref AS lref, l.name AS lname, COALESCE(l.debit,0) AS debit, COALESCE(l.credit,0) AS credit, + COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance, + m.name AS move_name, c.symbol AS currency_code, p.name AS partner_name + FROM account_move_line l + JOIN account_move m ON (l.move_id=m.id) + LEFT JOIN res_currency c ON (l.currency_id=c.id) + LEFT JOIN res_partner p ON (l.partner_id=p.id) + JOIN account_journal j ON (l.journal_id=j.id) + JOIN account_account acc ON (l.account_id = acc.id) + WHERE l.account_id IN %s AND l.journal_id IN %s ''' + target_move + ''' AND l.date = %s + GROUP BY l.id, l.account_id, l.date, + j.code, l.currency_id, l.amount_currency, l.ref, l.name, m.name, c.symbol, p.name , acc.name + ORDER BY l.date DESC + ''') + params = ( + tuple(accounts.ids), tuple(form_data['journal_ids']), pass_date) + cr.execute(sql, params) + data = cr.dictfetchall() + res = {} + debit = credit = balance = 0.00 + for line in data: + debit += line['debit'] + credit += line['credit'] + balance += line['balance'] + res['debit'] = debit + res['credit'] = credit + res['balance'] = balance + res['lines'] = data + return res + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get('active_model'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse( + self.env.context.get('active_ids', [])) + form_data = data['form'] + codes = [] + if data['form'].get('journal_ids', False): + codes = [journal.code for journal in + self.env['account.journal'].search( + [('id', 'in', data['form']['journal_ids'])])] + active_acc = data['form']['account_ids'] + accounts = self.env['account.account'].search( + [('id', 'in', active_acc)]) if data['form']['account_ids'] else \ + self.env['account.account'].search([]) + + date_start = datetime.strptime(form_data['date_from'], + '%Y-%m-%d').date() + date_end = datetime.strptime(form_data['date_to'], '%Y-%m-%d').date() + days = date_end - date_start + dates = [] + record = [] + for i in range(days.days + 1): + dates.append(date_start + timedelta(days=i)) + for head in dates: + pass_date = str(head) + accounts_res = self.with_context( + data['form'].get('used_context', {}))._get_account_move_entry( + accounts, form_data, pass_date) + if accounts_res['lines']: + record.append({ + 'date': head, + 'debit': accounts_res['debit'], + 'credit': accounts_res['credit'], + 'balance': accounts_res['balance'], + 'child_lines': accounts_res['lines'] + }) + return { + 'doc_ids': docids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'Accounts': record, + 'print_journal': codes, + } diff --git a/base_accounting_kit/report/account_day_book_view.xml b/base_accounting_kit/report/account_day_book_view.xml new file mode 100644 index 000000000..82f1ebb7a --- /dev/null +++ b/base_accounting_kit/report/account_day_book_view.xml @@ -0,0 +1,115 @@ + + + + + diff --git a/base_accounting_kit/report/account_report_common_account.py b/base_accounting_kit/report/account_report_common_account.py new file mode 100644 index 000000000..879186641 --- /dev/null +++ b/base_accounting_kit/report/account_report_common_account.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from odoo import api, fields, models + + +class AccountCommonAccountReport(models.TransientModel): + _name = 'account.common.account.report' + _description = 'Account Common Account Report' + _inherit = "account.common.report" + + display_account = fields.Selection( + [('all', 'All'), ('movement', 'With movements'), + ('not_zero', 'With balance is not equal to 0'), ], + string='Display Accounts', required=True, default='movement') + + @api.model + def pre_print_report(self, data): + data['form'].update(self.read(['display_account'])[0]) + return data diff --git a/base_accounting_kit/report/cash_flow_report.py b/base_accounting_kit/report/cash_flow_report.py new file mode 100644 index 000000000..054f52b36 --- /dev/null +++ b/base_accounting_kit/report/cash_flow_report.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportFinancial(models.AbstractModel): + _name = 'report.base_accounting_kit.report_cash_flow' + + def _compute_account_balance(self, accounts): + mapping = { + 'balance': "COALESCE(SUM(debit),0) - COALESCE(SUM(credit), 0) as balance", + 'debit': "COALESCE(SUM(debit), 0) as debit", + 'credit': "COALESCE(SUM(credit), 0) as credit", + } + + res = {} + for account in accounts: + res[account.id] = dict.fromkeys(mapping, 0.0) + if accounts: + tables, where_clause, where_params = self.env[ + 'account.move.line']._query_get() + tables = tables.replace('"', '') if tables else "account_move_line" + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + filters = " AND ".join(wheres) + request = "SELECT account_id as id, " + ', '.join( + mapping.values()) + \ + " FROM " + tables + \ + " WHERE account_id IN %s " \ + + filters + \ + " GROUP BY account_id" + params = (tuple(accounts._ids),) + tuple(where_params) + self.env.cr.execute(request, params) + for row in self.env.cr.dictfetchall(): + res[row['id']] = row + return res + + def _compute_report_balance(self, reports): + + res = {} + fields = ['credit', 'debit', 'balance'] + for report in reports: + if report.id in res: + continue + res[report.id] = dict((fn, 0.0) for fn in fields) + if report.type == 'accounts': + # it's the sum of credit or debit + res2 = self._compute_report_balance(report.parent_id) + for key, value in res2.items(): + cash_in_operation = self.env.ref( + 'base_accounting_kit.cash_in_from_operation0') + cash_out_operation = self.env.ref( + 'base_accounting_kit.cash_out_operation1') + cash_in_financial = self.env.ref( + 'base_accounting_kit.cash_in_financial0') + cash_out_financial = self.env.ref( + 'base_accounting_kit.cash_out_financial1') + cash_in_investing = self.env.ref( + 'base_accounting_kit.cash_in_investing0') + cash_out_investing = self.env.ref( + 'base_accounting_kit.cash_out_investing1') + if report == cash_in_operation or report == cash_in_financial or report == cash_in_investing: + res[report.id]['debit'] += value['debit'] + res[report.id]['balance'] += value['debit'] + elif report == cash_out_operation or report == cash_out_financial or report == cash_out_investing: + res[report.id]['credit'] += value['credit'] + res[report.id]['balance'] += -(value['credit']) + elif report.type == 'account_type': + # it's the sum the leaf accounts with such an account type + accounts = self.env['account.account'].search( + [('user_type_id', 'in', report.account_type_ids.ids)]) + res[report.id]['account'] = self._compute_account_balance( + accounts) + for value in res[report.id]['account'].values(): + for field in fields: + res[report.id][field] += value.get(field) + elif report.type == 'account_report' and report.account_report_id: + # it's the amount of the linked + res[report.id]['account'] = self._compute_account_balance( + report.account_ids) + for value in res[report.id]['account'].values(): + for field in fields: + res[report.id][field] += value.get(field) + + elif report.type == 'sum': + # it's the sum of the linked accounts + res[report.id]['account'] = self._compute_account_balance( + report.account_ids) + for values in res[report.id]['account'].values(): + for field in fields: + res[report.id][field] += values.get(field) + return res + + def get_account_lines(self, data): + lines = [] + account_report = self.env['account.financial.report'].search( + [('id', '=', data['account_report_id'][0])]) + child_reports = account_report._get_children_by_order() + res = self.with_context( + data.get('used_context'))._compute_report_balance(child_reports) + if data['enable_filter']: + comparison_res = self.with_context( + data.get('comparison_context'))._compute_report_balance( + child_reports) + for report_id, value in comparison_res.items(): + res[report_id]['comp_bal'] = value['balance'] + report_acc = res[report_id].get('account') + if report_acc: + for account_id, val in comparison_res[report_id].get( + 'account').items(): + report_acc[account_id]['comp_bal'] = val['balance'] + + for report in child_reports: + vals = { + 'name': report.name, + 'balance': res[report.id]['balance'] * int(report.sign), + 'type': 'report', + 'level': bool(report.style_overwrite) and int( + report.style_overwrite) or report.level, + 'account_type': report.type or False, + # used to underline the financial report balances + } + if data['debit_credit']: + vals['debit'] = res[report.id]['debit'] + vals['credit'] = res[report.id]['credit'] + + if data['enable_filter']: + vals['balance_cmp'] = res[report.id]['comp_bal'] * int( + report.sign) + + lines.append(vals) + if report.display_detail == 'no_detail': + # the rest of the loop is used to display the details of the financial report, so it's not needed here. + continue + if res[report.id].get('account'): + # if res[report.id].get('debit'): + sub_lines = [] + for account_id, value in res[report.id]['account'].items(): + # if there are accounts to display, we add them to the lines with a level equals to their level in + # the COA + 1 (to avoid having them with a too low level that would conflicts with the level of data + # financial reports for Assets, liabilities...) + flag = False + account = self.env['account.account'].browse(account_id) + + vals = { + 'name': account.code + ' ' + account.name, + 'balance': value['balance'] * int(report.sign) or 0.0, + 'type': 'account', + 'level': report.display_detail == 'detail_with_hierarchy' and 4, + 'account_type': account.internal_type, + } + if data['debit_credit']: + vals['debit'] = value['debit'] + vals['credit'] = value['credit'] + if not account.company_id.currency_id.is_zero( + vals[ + 'debit']) or not account.company_id.currency_id.is_zero( + vals['credit']): + flag = True + if not account.company_id.currency_id.is_zero( + vals['balance']): + flag = True + if data['enable_filter']: + vals['balance_cmp'] = value['comp_bal'] * int( + report.sign) + if not account.company_id.currency_id.is_zero( + vals['balance_cmp']): + flag = True + if flag: + sub_lines.append(vals) + lines += sorted(sub_lines, + key=lambda sub_line: sub_line['name']) + return lines + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get( + 'active_model') or not self.env.context.get('active_id'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse(self.env.context.get('active_id')) + report_lines = self.get_account_lines(data.get('form')) + return { + 'doc_ids': self.ids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'get_account_lines': report_lines, + } diff --git a/base_accounting_kit/report/cash_flow_report.xml b/base_accounting_kit/report/cash_flow_report.xml new file mode 100644 index 000000000..d0b7a849a --- /dev/null +++ b/base_accounting_kit/report/cash_flow_report.xml @@ -0,0 +1,108 @@ + + + + diff --git a/base_accounting_kit/report/general_ledger_report.py b/base_accounting_kit/report/general_ledger_report.py new file mode 100644 index 000000000..b81c6655e --- /dev/null +++ b/base_accounting_kit/report/general_ledger_report.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportGeneralLedger(models.AbstractModel): + _name = 'report.base_accounting_kit.report_general_ledger' + + def _get_account_move_entry(self, accounts, init_balance, sortby, + display_account): + """ + :param: + accounts: the recordset of accounts + init_balance: boolean value of initial_balance + sortby: sorting by date or partner and journal + display_account: type of account(receivable, payable and both) + + Returns a dictionary of accounts with following key and value { + 'code': account code, + 'name': account name, + 'debit': sum of total debit amount, + 'credit': sum of total credit amount, + 'balance': total balance, + 'amount_currency': sum of amount_currency, + 'move_lines': list of move line + } + """ + cr = self.env.cr + MoveLine = self.env['account.move.line'] + move_lines = {x: [] for x in accounts.ids} + + # Prepare initial sql query and Get the initial move lines + if init_balance: + init_tables, init_where_clause, init_where_params = MoveLine.with_context( + date_from=self.env.context.get('date_from'), date_to=False, + initial_bal=True)._query_get() + init_wheres = [""] + if init_where_clause.strip(): + init_wheres.append(init_where_clause.strip()) + init_filters = " AND ".join(init_wheres) + filters = init_filters.replace('account_move_line__move_id', + 'm').replace('account_move_line', + 'l') + sql = ("""SELECT 0 AS lid, l.account_id AS account_id, '' AS ldate, '' AS lcode, 0.0 AS amount_currency, '' AS lref, 'Initial Balance' AS lname, COALESCE(SUM(l.debit),0.0) AS debit, COALESCE(SUM(l.credit),0.0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance, '' AS lpartner_id,\ + '' AS move_name, '' AS mmove_id, '' AS currency_code,\ + NULL AS currency_id,\ + '' AS invoice_id, '' AS invoice_type, '' AS invoice_number,\ + '' AS partner_name\ + FROM account_move_line l\ + LEFT JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + LEFT JOIN account_move i ON (m.id =i.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + WHERE l.account_id IN %s""" + filters + ' GROUP BY l.account_id') + params = (tuple(accounts.ids),) + tuple(init_where_params) + cr.execute(sql, params) + for row in cr.dictfetchall(): + move_lines[row.pop('account_id')].append(row) + + sql_sort = 'l.date, l.move_id' + if sortby == 'sort_journal_partner': + sql_sort = 'j.code, p.name, l.move_id' + + # Prepare sql query base on selected parameters from wizard + tables, where_clause, where_params = MoveLine._query_get() + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + filters = " AND ".join(wheres) + filters = filters.replace('account_move_line__move_id', 'm').replace( + 'account_move_line', 'l') + + # Get move lines base on sql query and Calculate the total balance of move lines + sql = ('''SELECT l.id AS lid, l.account_id AS account_id, l.date AS ldate, j.code AS lcode, l.currency_id, l.amount_currency, l.ref AS lref, l.name AS lname, COALESCE(l.debit,0) AS debit, COALESCE(l.credit,0) AS credit, COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) AS balance,\ + m.name AS move_name, c.symbol AS currency_code, p.name AS partner_name\ + FROM account_move_line l\ + JOIN account_move m ON (l.move_id=m.id)\ + LEFT JOIN res_currency c ON (l.currency_id=c.id)\ + LEFT JOIN res_partner p ON (l.partner_id=p.id)\ + JOIN account_journal j ON (l.journal_id=j.id)\ + JOIN account_account acc ON (l.account_id = acc.id) \ + WHERE l.account_id IN %s ''' + filters + ''' GROUP BY l.id, l.account_id, l.date, j.code, l.currency_id, l.amount_currency, l.ref, l.name, m.name, c.symbol, p.name ORDER BY ''' + sql_sort) + params = (tuple(accounts.ids),) + tuple(where_params) + cr.execute(sql, params) + + for row in cr.dictfetchall(): + balance = 0 + for line in move_lines.get(row['account_id']): + balance += line['debit'] - line['credit'] + row['balance'] += balance + move_lines[row.pop('account_id')].append(row) + + # Calculate the debit, credit and balance for Accounts + account_res = [] + for account in accounts: + currency = account.currency_id and account.currency_id or account.company_id.currency_id + res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance']) + res['code'] = account.code + res['name'] = account.name + res['move_lines'] = move_lines[account.id] + for line in res.get('move_lines'): + res['debit'] += line['debit'] + res['credit'] += line['credit'] + res['balance'] = line['balance'] + if display_account == 'all': + account_res.append(res) + if display_account == 'movement' and res.get('move_lines'): + account_res.append(res) + if display_account == 'not_zero' and not currency.is_zero( + res['balance']): + account_res.append(res) + + return account_res + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get('active_model'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse( + self.env.context.get('active_ids', [])) + + init_balance = data['form'].get('initial_balance', True) + sortby = data['form'].get('sortby', 'sort_date') + display_account = data['form']['display_account'] + codes = [] + if data['form'].get('journal_ids', False): + codes = [journal.code for journal in + self.env['account.journal'].search( + [('id', 'in', data['form']['journal_ids'])])] + + accounts = docs if self.model == 'account.account' else self.env[ + 'account.account'].search([]) + accounts_res = self.with_context( + data['form'].get('used_context', {}))._get_account_move_entry( + accounts, init_balance, sortby, display_account) + return { + 'doc_ids': docids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'Accounts': accounts_res, + 'print_journal': codes, + } diff --git a/base_accounting_kit/report/general_ledger_report.xml b/base_accounting_kit/report/general_ledger_report.xml new file mode 100644 index 000000000..c027e5ef7 --- /dev/null +++ b/base_accounting_kit/report/general_ledger_report.xml @@ -0,0 +1,105 @@ + + + + diff --git a/base_accounting_kit/report/report.xml b/base_accounting_kit/report/report.xml new file mode 100644 index 000000000..2d6474d73 --- /dev/null +++ b/base_accounting_kit/report/report.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/base_accounting_kit/report/report_aged_partner.py b/base_accounting_kit/report/report_aged_partner.py new file mode 100644 index 000000000..f9eda5d77 --- /dev/null +++ b/base_accounting_kit/report/report_aged_partner.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time +from datetime import datetime + +from dateutil.relativedelta import relativedelta + +from odoo import api, models, _ +from odoo.exceptions import UserError +from odoo.tools import float_is_zero + + +class ReportAgedPartnerBalance(models.AbstractModel): + _name = 'report.base_accounting_kit.report_agedpartnerbalance' + + def _get_partner_move_lines(self, account_type, date_from, target_move, + period_length): + # This method can receive the context key 'include_nullified_amount' {Boolean} + # Do an invoice and a payment and unreconcile. The amount will be nullified + # By default, the partner wouldn't appear in this report. + # The context key allow it to appear + # In case of a period_length of 30 days as of 2019-02-08, we want the following periods: + # Name Stop Start + # 1 - 30 : 2019-02-07 - 2019-01-09 + # 31 - 60 : 2019-01-08 - 2018-12-10 + # 61 - 90 : 2018-12-09 - 2018-11-10 + # 91 - 120 : 2018-11-09 - 2018-10-11 + # +120 : 2018-10-10 + periods = {} + start = datetime.strptime(date_from, "%Y-%m-%d") + date_from = datetime.strptime(date_from, "%Y-%m-%d").date() + for i in range(5)[::-1]: + stop = start - relativedelta(days=period_length) + period_name = str((5 - (i + 1)) * period_length + 1) + '-' + str( + (5 - i) * period_length) + period_stop = (start - relativedelta(days=1)).strftime('%Y-%m-%d') + if i == 0: + period_name = '+' + str(4 * period_length) + periods[str(i)] = { + 'name': period_name, + 'stop': period_stop, + 'start': (i != 0 and stop.strftime('%Y-%m-%d') or False), + } + start = stop + + res = [] + total = [] + cr = self.env.cr + user_company = self.env.user.company_id + user_currency = user_company.currency_id + ResCurrency = self.env['res.currency'].with_context(date=date_from) + company_ids = self._context.get('company_ids') or [user_company.id] + move_state = ['draft', 'posted'] + if target_move == 'posted': + move_state = ['posted'] + arg_list = (tuple(move_state), tuple(account_type)) + # build the reconciliation clause to see what partner needs to be printed + reconciliation_clause = '(l.reconciled IS FALSE)' + cr.execute( + 'SELECT debit_move_id, credit_move_id FROM account_partial_reconcile where max_date > %s', + (date_from,)) + reconciled_after_date = [] + for row in cr.fetchall(): + reconciled_after_date += [row[0], row[1]] + if reconciled_after_date: + reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)' + arg_list += (tuple(reconciled_after_date),) + arg_list += (date_from, tuple(company_ids)) + query = ''' + SELECT DISTINCT l.partner_id, UPPER(res_partner.name) + FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, account_account, account_move am + WHERE (l.account_id = account_account.id) + AND (l.move_id = am.id) + AND (am.state IN %s) + AND (account_account.internal_type IN %s) + AND ''' + reconciliation_clause + ''' + AND (l.date <= %s) + AND l.company_id IN %s + ORDER BY UPPER(res_partner.name)''' + cr.execute(query, arg_list) + + partners = cr.dictfetchall() + # put a total of 0 + for i in range(7): + total.append(0) + + # Build a string like (1,2,3) for easy use in SQL query + partner_ids = [partner['partner_id'] for partner in partners if + partner['partner_id']] + lines = dict( + (partner['partner_id'] or False, []) for partner in partners) + if not partner_ids: + return [], [], {} + + # This dictionary will store the not due amount of all partners + undue_amounts = {} + query = '''SELECT l.id + FROM account_move_line AS l, account_account, account_move am + WHERE (l.account_id = account_account.id) AND (l.move_id = am.id) + AND (am.state IN %s) + AND (account_account.internal_type IN %s) + AND (COALESCE(l.date_maturity,l.date) >= %s)\ + AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) + AND (l.date <= %s) + AND l.company_id IN %s''' + cr.execute(query, ( + tuple(move_state), tuple(account_type), date_from, + tuple(partner_ids), date_from, tuple(company_ids))) + aml_ids = cr.fetchall() + aml_ids = aml_ids and [x[0] for x in aml_ids] or [] + for line in self.env['account.move.line'].browse(aml_ids): + partner_id = line.partner_id.id or False + if partner_id not in undue_amounts: + undue_amounts[partner_id] = 0.0 + line_amount = ResCurrency._compute(line.company_id.currency_id, + user_currency, line.balance) + if user_currency.is_zero(line_amount): + continue + for partial_line in line.matched_debit_ids: + if partial_line.max_date <= date_from: + line_amount += ResCurrency._compute( + partial_line.company_id.currency_id, user_currency, + partial_line.amount) + for partial_line in line.matched_credit_ids: + if partial_line.max_date <= date_from: + line_amount -= ResCurrency._compute( + partial_line.company_id.currency_id, user_currency, + partial_line.amount) + if not self.env.user.company_id.currency_id.is_zero(line_amount): + undue_amounts[partner_id] += line_amount + lines[partner_id].append({ + 'line': line, + 'amount': line_amount, + 'period': 6, + }) + + # Use one query per period and store results in history (a list variable) + # Each history will contain: history[1] = {'': } + history = [] + for i in range(5): + args_list = ( + tuple(move_state), tuple(account_type), tuple(partner_ids),) + dates_query = '(COALESCE(l.date_maturity,l.date)' + + if periods[str(i)]['start'] and periods[str(i)]['stop']: + dates_query += ' BETWEEN %s AND %s)' + args_list += ( + periods[str(i)]['start'], periods[str(i)]['stop']) + elif periods[str(i)]['start']: + dates_query += ' >= %s)' + args_list += (periods[str(i)]['start'],) + else: + dates_query += ' <= %s)' + args_list += (periods[str(i)]['stop'],) + args_list += (date_from, tuple(company_ids)) + + query = '''SELECT l.id + FROM account_move_line AS l, account_account, account_move am + WHERE (l.account_id = account_account.id) AND (l.move_id = am.id) + AND (am.state IN %s) + AND (account_account.internal_type IN %s) + AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) + AND ''' + dates_query + ''' + AND (l.date <= %s) + AND l.company_id IN %s''' + cr.execute(query, args_list) + partners_amount = {} + aml_ids = cr.fetchall() + aml_ids = aml_ids and [x[0] for x in aml_ids] or [] + for line in self.env['account.move.line'].browse(aml_ids): + partner_id = line.partner_id.id or False + if partner_id not in partners_amount: + partners_amount[partner_id] = 0.0 + line_amount = ResCurrency._compute(line.company_id.currency_id, + user_currency, line.balance) + if user_currency.is_zero(line_amount): + continue + for partial_line in line.matched_debit_ids: + if partial_line.max_date <= date_from: + line_amount += ResCurrency._compute( + partial_line.company_id.currency_id, user_currency, + partial_line.amount) + for partial_line in line.matched_credit_ids: + if partial_line.max_date <= date_from: + line_amount -= ResCurrency._compute( + partial_line.company_id.currency_id, user_currency, + partial_line.amount) + + if not self.env.user.company_id.currency_id.is_zero( + line_amount): + partners_amount[partner_id] += line_amount + lines[partner_id].append({ + 'line': line, + 'amount': line_amount, + 'period': i + 1, + }) + history.append(partners_amount) + + for partner in partners: + if partner['partner_id'] is None: + partner['partner_id'] = False + at_least_one_amount = False + values = {} + undue_amt = 0.0 + if partner[ + 'partner_id'] in undue_amounts: # Making sure this partner actually was found by the query + undue_amt = undue_amounts[partner['partner_id']] + + total[6] = total[6] + undue_amt + values['direction'] = undue_amt + if not float_is_zero(values['direction'], + precision_rounding=self.env.user.company_id.currency_id.rounding): + at_least_one_amount = True + + for i in range(5): + during = False + if partner['partner_id'] in history[i]: + during = [history[i][partner['partner_id']]] + # Adding counter + total[(i)] = total[(i)] + (during and during[0] or 0) + values[str(i)] = during and during[0] or 0.0 + if not float_is_zero(values[str(i)], + precision_rounding=self.env.user.company_id.currency_id.rounding): + at_least_one_amount = True + values['total'] = sum( + [values['direction']] + [values[str(i)] for i in range(5)]) + ## Add for total + total[(i + 1)] += values['total'] + values['partner_id'] = partner['partner_id'] + if partner['partner_id']: + browsed_partner = self.env['res.partner'].browse( + partner['partner_id']) + values['name'] = browsed_partner.name and len( + browsed_partner.name) >= 45 and browsed_partner.name[ + 0:40] + '...' or browsed_partner.name + values['trust'] = browsed_partner.trust + else: + values['name'] = _('Unknown Partner') + values['trust'] = False + + if at_least_one_amount or ( + self._context.get('include_nullified_amount') and lines[ + partner['partner_id']]): + res.append(values) + + return res, total, lines + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get( + 'active_model') or not self.env.context.get('active_id'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + total = [] + model = self.env.context.get('active_model') + docs = self.env[model].browse(self.env.context.get('active_id')) + + target_move = data['form'].get('target_move', 'all') + date_from = data['form'].get('date_from', time.strftime('%Y-%m-%d')) + + if data['form']['result_selection'] == 'customer': + account_type = ['receivable'] + elif data['form']['result_selection'] == 'supplier': + account_type = ['payable'] + else: + account_type = ['payable', 'receivable'] + + movelines, total, dummy = self._get_partner_move_lines(account_type, + date_from, + target_move, + data['form'][ + 'period_length']) + return { + 'doc_ids': self.ids, + 'doc_model': model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'get_partner_lines': movelines, + 'get_direction': total, + } diff --git a/base_accounting_kit/report/report_aged_partner.xml b/base_accounting_kit/report/report_aged_partner.xml new file mode 100644 index 000000000..efdb4eaf9 --- /dev/null +++ b/base_accounting_kit/report/report_aged_partner.xml @@ -0,0 +1,98 @@ + + + + diff --git a/base_accounting_kit/report/report_financial.py b/base_accounting_kit/report/report_financial.py new file mode 100644 index 000000000..819bb9841 --- /dev/null +++ b/base_accounting_kit/report/report_financial.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import api, fields, models + + +# --------------------------------------------------------- +# Account Financial Report +# --------------------------------------------------------- + + +class AccountFinancialReport(models.Model): + _name = "account.financial.report" + _description = "Account Report" + _rec_name = 'name' + + @api.depends('parent_id', 'parent_id.level') + def _get_level(self): + """Returns a dictionary with key=the ID of a record and + value = the level of this + record in the tree structure.""" + for report in self: + level = 0 + if report.parent_id: + level = report.parent_id.level + 1 + report.level = level + + def _get_children_by_order(self): + """returns a recordset of all the children computed recursively, + and sorted by sequence. Ready for the printing""" + res = self + children = self.search([('parent_id', 'in', self.ids)], + order='sequence ASC') + if children: + for child in children: + res += child._get_children_by_order() + return res + + name = fields.Char('Report Name', required=True, translate=True) + parent_id = fields.Many2one('account.financial.report', 'Parent') + children_ids = fields.One2many( + 'account.financial.report', + 'parent_id', + 'Account Report') + sequence = fields.Integer('Sequence') + level = fields.Integer(compute='_get_level', string='Level', store=True) + type = fields.Selection( + [('sum', 'View'), + ('accounts', 'Accounts'), + ('account_type', 'Account Type'), + ('account_report', 'Report Value')], + 'Type', + default='sum') + account_ids = fields.Many2many( + 'account.account', + 'account_account_financial_report', + 'report_line_id', + 'account_id', + 'Accounts') + account_report_id = fields.Many2one( + 'account.financial.report', + 'Report Value') + account_type_ids = fields.Many2many( + 'account.account.type', + 'account_account_financial_report_type', + 'report_id', 'account_type_id', + 'Account Types') + sign = fields.Selection( + [("1", 'Reverse balance sign'), ("-1", 'Preserve balance sign')], + 'Sign on Reports', required=True, default="1", + help='For accounts that are typically more' + ' debited than credited and that you' + ' would like to print as negative' + ' amounts in your reports, you should' + ' reverse the sign of the balance;' + ' e.g.: Expense account. The same applies' + ' for accounts that are typically more' + ' credited than debited and that you would' + ' like to print as positive amounts in' + ' your reports; e.g.: Income account.') + display_detail = fields.Selection( + [('no_detail', 'No detail'), + ('detail_flat', 'Display children flat'), + ('detail_with_hierarchy', 'Display children with hierarchy')], + 'Display details', + default='detail_flat') + style_overwrite = fields.Selection( + [('0', 'Automatic formatting'), + ('1', 'Main Title 1 (bold, underlined)'), + ('2', 'Title 2 (bold)'), + ('3', 'Title 3 (bold, smaller)'), + ('4', 'Normal Text'), + ('5', 'Italic Text (smaller)'), + ('6', 'Smallest Text')], + 'Financial Report Style', + default='0', + help="You can set up here the format you want this" + " record to be displayed. If you leave the" + " automatic formatting, it will be computed" + " based on the financial reports hierarchy " + "(auto-computed field 'level').") diff --git a/base_accounting_kit/report/report_financial.xml b/base_accounting_kit/report/report_financial.xml new file mode 100644 index 000000000..fb7d4d027 --- /dev/null +++ b/base_accounting_kit/report/report_financial.xml @@ -0,0 +1,146 @@ + + + + \ No newline at end of file diff --git a/base_accounting_kit/report/report_journal_audit.py b/base_accounting_kit/report/report_journal_audit.py new file mode 100644 index 000000000..90aaf4fed --- /dev/null +++ b/base_accounting_kit/report/report_journal_audit.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportJournal(models.AbstractModel): + _name = 'report.base_accounting_kit.report_journal_audit' + + def lines(self, target_move, journal_ids, sort_selection, data): + if isinstance(journal_ids, int): + journal_ids = [journal_ids] + + move_state = ['draft', 'posted'] + if target_move == 'posted': + move_state = ['posted'] + + query_get_clause = self._get_query_get_clause(data) + params = [tuple(move_state), tuple(journal_ids)] + query_get_clause[2] + query = 'SELECT "account_move_line".id FROM ' + query_get_clause[ + 0] + ', account_move am, account_account acc WHERE "account_move_line".account_id = acc.id AND "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + \ + query_get_clause[1] + ' ORDER BY ' + if sort_selection == 'date': + query += '"account_move_line".date' + else: + query += 'am.name' + query += ', "account_move_line".move_id, acc.code' + self.env.cr.execute(query, tuple(params)) + ids = (x[0] for x in self.env.cr.fetchall()) + return self.env['account.move.line'].browse(ids) + + def _sum_debit(self, data, journal_id): + move_state = ['draft', 'posted'] + if data['form'].get('target_move', 'all') == 'posted': + move_state = ['posted'] + + query_get_clause = self._get_query_get_clause(data) + params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[ + 2] + self.env.cr.execute('SELECT SUM(debit) FROM ' + query_get_clause[ + 0] + ', account_move am ' + 'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + + query_get_clause[1] + ' ', + tuple(params)) + return self.env.cr.fetchone()[0] or 0.0 + + def _sum_credit(self, data, journal_id): + move_state = ['draft', 'posted'] + if data['form'].get('target_move', 'all') == 'posted': + move_state = ['posted'] + + query_get_clause = self._get_query_get_clause(data) + params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[ + 2] + self.env.cr.execute('SELECT SUM(credit) FROM ' + query_get_clause[ + 0] + ', account_move am ' + 'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + + query_get_clause[1] + ' ', + tuple(params)) + return self.env.cr.fetchone()[0] or 0.0 + + def _get_taxes(self, data, journal_id): + move_state = ['draft', 'posted'] + if data['form'].get('target_move', 'all') == 'posted': + move_state = ['posted'] + + query_get_clause = self._get_query_get_clause(data) + params = [tuple(move_state), tuple(journal_id.ids)] + query_get_clause[ + 2] + query = """ + SELECT rel.account_tax_id, SUM("account_move_line".balance) AS base_amount + FROM account_move_line_account_tax_rel rel, """ + query_get_clause[ + 0] + """ + LEFT JOIN account_move am ON "account_move_line".move_id = am.id + WHERE "account_move_line".id = rel.account_move_line_id + AND am.state IN %s + AND "account_move_line".journal_id IN %s + AND """ + query_get_clause[1] + """ + GROUP BY rel.account_tax_id""" + self.env.cr.execute(query, tuple(params)) + ids = [] + base_amounts = {} + for row in self.env.cr.fetchall(): + ids.append(row[0]) + base_amounts[row[0]] = row[1] + + res = {} + for tax in self.env['account.tax'].browse(ids): + self.env.cr.execute( + 'SELECT sum(debit - credit) FROM ' + query_get_clause[ + 0] + ', account_move am ' + 'WHERE "account_move_line".move_id=am.id AND am.state IN %s AND "account_move_line".journal_id IN %s AND ' + + query_get_clause[1] + ' AND tax_line_id = %s', + tuple(params + [tax.id])) + res[tax] = { + 'base_amount': base_amounts[tax.id], + 'tax_amount': self.env.cr.fetchone()[0] or 0.0, + } + if journal_id.type == 'sale': + # sales operation are credits + res[tax]['base_amount'] = res[tax]['base_amount'] * -1 + res[tax]['tax_amount'] = res[tax]['tax_amount'] * -1 + return res + + def _get_query_get_clause(self, data): + return self.env['account.move.line'].with_context( + data['form'].get('used_context', {}))._query_get() + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + target_move = data['form'].get('target_move', 'all') + sort_selection = data['form'].get('sort_selection', 'date') + + res = {} + for journal in data['form']['journal_ids']: + res[journal] = self.with_context( + data['form'].get('used_context', {})).lines(target_move, + journal, + sort_selection, + data) + return { + 'doc_ids': data['form']['journal_ids'], + 'doc_model': self.env['account.journal'], + 'data': data, + 'docs': self.env['account.journal'].browse( + data['form']['journal_ids']), + 'time': time, + 'lines': res, + 'sum_credit': self._sum_credit, + 'sum_debit': self._sum_debit, + 'get_taxes': self._get_taxes, + } diff --git a/base_accounting_kit/report/report_journal_audit.xml b/base_accounting_kit/report/report_journal_audit.xml new file mode 100644 index 000000000..6c2a2c52c --- /dev/null +++ b/base_accounting_kit/report/report_journal_audit.xml @@ -0,0 +1,150 @@ + + + + + + diff --git a/base_accounting_kit/report/report_partner_ledger.py b/base_accounting_kit/report/report_partner_ledger.py new file mode 100644 index 000000000..7dd0df971 --- /dev/null +++ b/base_accounting_kit/report/report_partner_ledger.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportPartnerLedger(models.AbstractModel): + _name = 'report.base_accounting_kit.report_partnerledger' + + def _lines(self, data, partner): + full_account = [] + currency = self.env['res.currency'] + query_get_data = self.env['account.move.line'].with_context( + data['form'].get('used_context', {}))._query_get() + reconcile_clause = "" if data['form'][ + 'reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' + params = [partner.id, tuple(data['computed']['move_state']), + tuple(data['computed']['account_ids'])] + \ + query_get_data[2] + query = """ + SELECT "account_move_line".id, "account_move_line".date, j.code, acc.code as a_code, acc.name as a_name, "account_move_line".ref, m.name as move_name, "account_move_line".name, "account_move_line".debit, "account_move_line".credit, "account_move_line".amount_currency,"account_move_line".currency_id, c.symbol AS currency_code + FROM """ + query_get_data[0] + """ + LEFT JOIN account_journal j ON ("account_move_line".journal_id = j.id) + LEFT JOIN account_account acc ON ("account_move_line".account_id = acc.id) + LEFT JOIN res_currency c ON ("account_move_line".currency_id=c.id) + LEFT JOIN account_move m ON (m.id="account_move_line".move_id) + WHERE "account_move_line".partner_id = %s + AND m.state IN %s + AND "account_move_line".account_id IN %s AND """ + \ + query_get_data[1] + reconcile_clause + """ + ORDER BY "account_move_line".date""" + self.env.cr.execute(query, tuple(params)) + res = self.env.cr.dictfetchall() + sum = 0.0 + lang_code = self.env.context.get('lang') or 'en_US' + lang = self.env['res.lang'] + lang_id = lang._lang_get(lang_code) + date_format = lang_id.date_format + for r in res: + r['date'] = r['date'] + r['displayed_name'] = '-'.join( + r[field_name] for field_name in ('move_name', 'ref', 'name') + if r[field_name] not in (None, '', '/') + ) + sum += r['debit'] - r['credit'] + r['progress'] = sum + r['currency_id'] = currency.browse(r.get('currency_id')) + full_account.append(r) + return full_account + + def _sum_partner(self, data, partner, field): + if field not in ['debit', 'credit', 'debit - credit']: + return + result = 0.0 + query_get_data = self.env['account.move.line'].with_context( + data['form'].get('used_context', {}))._query_get() + reconcile_clause = "" if data['form'][ + 'reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' + + params = [partner.id, tuple(data['computed']['move_state']), + tuple(data['computed']['account_ids'])] + \ + query_get_data[2] + query = """SELECT sum(""" + field + """) + FROM """ + query_get_data[0] + """, account_move AS m + WHERE "account_move_line".partner_id = %s + AND m.id = "account_move_line".move_id + AND m.state IN %s + AND account_id IN %s + AND """ + query_get_data[1] + reconcile_clause + self.env.cr.execute(query, tuple(params)) + + contemp = self.env.cr.fetchone() + if contemp is not None: + result = contemp[0] or 0.0 + return result + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + data['computed'] = {} + + obj_partner = self.env['res.partner'] + query_get_data = self.env['account.move.line'].with_context( + data['form'].get('used_context', {}))._query_get() + data['computed']['move_state'] = ['draft', 'posted'] + if data['form'].get('target_move', 'all') == 'posted': + data['computed']['move_state'] = ['posted'] + result_selection = data['form'].get('result_selection', 'customer') + if result_selection == 'supplier': + data['computed']['ACCOUNT_TYPE'] = ['payable'] + elif result_selection == 'customer': + data['computed']['ACCOUNT_TYPE'] = ['receivable'] + else: + data['computed']['ACCOUNT_TYPE'] = ['payable', 'receivable'] + + self.env.cr.execute(""" + SELECT a.id + FROM account_account a + WHERE a.internal_type IN %s + AND NOT a.deprecated""", + (tuple(data['computed']['ACCOUNT_TYPE']),)) + data['computed']['account_ids'] = [a for (a,) in + self.env.cr.fetchall()] + params = [tuple(data['computed']['move_state']), + tuple(data['computed']['account_ids'])] + query_get_data[2] + reconcile_clause = "" if data['form'][ + 'reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' + query = """ + SELECT DISTINCT "account_move_line".partner_id + FROM """ + query_get_data[0] + """, account_account AS account, account_move AS am + WHERE "account_move_line".partner_id IS NOT NULL + AND "account_move_line".account_id = account.id + AND am.id = "account_move_line".move_id + AND am.state IN %s + AND "account_move_line".account_id IN %s + AND NOT account.deprecated + AND """ + query_get_data[1] + reconcile_clause + self.env.cr.execute(query, tuple(params)) + partner_ids = [res['partner_id'] for res in self.env.cr.dictfetchall()] + partners = obj_partner.browse(partner_ids) + partners = sorted(partners, key=lambda x: (x.ref or '', x.name or '')) + return { + 'doc_ids': partner_ids, + 'doc_model': self.env['res.partner'], + 'data': data, + 'docs': partners, + 'time': time, + 'lines': self._lines, + 'sum_partner': self._sum_partner, + } diff --git a/base_accounting_kit/report/report_partner_ledger.xml b/base_accounting_kit/report/report_partner_ledger.xml new file mode 100644 index 000000000..9024bea47 --- /dev/null +++ b/base_accounting_kit/report/report_partner_ledger.xml @@ -0,0 +1,107 @@ + + + + diff --git a/base_accounting_kit/report/report_tax.py b/base_accounting_kit/report/report_tax.py new file mode 100644 index 000000000..479f677b0 --- /dev/null +++ b/base_accounting_kit/report/report_tax.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +from _datetime import datetime + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportTax(models.AbstractModel): + _name = 'report.base_accounting_kit.report_tax' + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + return { + 'data': data['form'], + 'lines': self.get_lines(data.get('form')), + } + + def _sql_from_amls_one(self): + sql = """SELECT "account_move_line".tax_line_id, COALESCE(SUM("account_move_line".debit-"account_move_line".credit), 0) + FROM %s + WHERE %s AND "account_move_line".tax_exigible GROUP BY "account_move_line".tax_line_id""" + return sql + + def _sql_from_amls_two(self): + sql = """SELECT r.account_tax_id, COALESCE(SUM("account_move_line".debit-"account_move_line".credit), 0) + FROM %s + INNER JOIN account_move_line_account_tax_rel r ON ("account_move_line".id = r.account_move_line_id) + INNER JOIN account_tax t ON (r.account_tax_id = t.id) + WHERE %s AND "account_move_line".tax_exigible GROUP BY r.account_tax_id""" + return sql + + def _compute_from_amls(self, options, taxes): + # compute the tax amount + sql = self._sql_from_amls_one() + tables, where_clause, where_params = self.env[ + 'account.move.line']._query_get() + query = sql % (tables, where_clause) + self.env.cr.execute(query, where_params) + results = self.env.cr.fetchall() + for result in results: + if result[0] in taxes: + taxes[result[0]]['tax'] = abs(result[1]) + + # compute the net amount + sql2 = self._sql_from_amls_two() + query = sql2 % (tables, where_clause) + self.env.cr.execute(query, where_params) + results = self.env.cr.fetchall() + for result in results: + if result[0] in taxes: + taxes[result[0]]['net'] = abs(result[1]) + + @api.model + def get_lines(self, options): + taxes = {} + for tax in self.env['account.tax'].search( + [('type_tax_use', '!=', 'none')]): + if tax.children_tax_ids: + for child in tax.children_tax_ids: + if child.type_tax_use != 'none': + continue + taxes[child.id] = {'tax': 0, 'net': 0, 'name': child.name, + 'type': tax.type_tax_use} + else: + taxes[tax.id] = {'tax': 0, 'net': 0, 'name': tax.name, + 'type': tax.type_tax_use} + if options['date_from']: + self.with_context(date_from=options['date_from'], + strict_range=True)._compute_from_amls(options, + taxes) + elif options['date_to']: + self.with_context(date_to=options['date_to'], + strict_range=True)._compute_from_amls(options, + taxes) + elif options['date_from'] and options['date_to']: + self.with_context(date_from=options['date_from'], + date_to=options['date_to'], + strict_range=True)._compute_from_amls(options, + taxes) + else: + date_to = str(datetime.today().date()) + self.with_context(date_to=date_to, + strict_range=True)._compute_from_amls(options, + taxes) + + groups = dict((tp, []) for tp in ['sale', 'purchase']) + for tax in taxes.values(): + if tax['tax']: + groups[tax['type']].append(tax) + return groups diff --git a/base_accounting_kit/report/report_tax.xml b/base_accounting_kit/report/report_tax.xml new file mode 100644 index 000000000..33beb00c6 --- /dev/null +++ b/base_accounting_kit/report/report_tax.xml @@ -0,0 +1,75 @@ + + + + diff --git a/base_accounting_kit/report/report_trial_balance.py b/base_accounting_kit/report/report_trial_balance.py new file mode 100644 index 000000000..235fad7a0 --- /dev/null +++ b/base_accounting_kit/report/report_trial_balance.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL 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 AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# + +import time + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class ReportTrialBalance(models.AbstractModel): + _name = 'report.base_accounting_kit.report_trial_balance' + + def _get_accounts(self, accounts, display_account): + """ compute the balance, debit and credit for the provided accounts + :Arguments: + `accounts`: list of accounts record, + `display_account`: it's used to display either all accounts or those accounts which balance is > 0 + :Returns a list of dictionary of Accounts with following key and value + `name`: Account name, + `code`: Account code, + `credit`: total amount of credit, + `debit`: total amount of debit, + `balance`: total amount of balance, + """ + + account_result = {} + # Prepare sql query base on selected parameters from wizard + tables, where_clause, where_params = self.env[ + 'account.move.line']._query_get() + tables = tables.replace('"', '') + if not tables: + tables = 'account_move_line' + wheres = [""] + if where_clause.strip(): + wheres.append(where_clause.strip()) + filters = " AND ".join(wheres) + # compute the balance, debit and credit for the provided accounts + request = ( + "SELECT account_id AS id, SUM(debit) AS debit, SUM(credit) AS credit, (SUM(debit) - SUM(credit)) AS balance" + \ + " FROM " + tables + " WHERE account_id IN %s " + filters + " GROUP BY account_id") + params = (tuple(accounts.ids),) + tuple(where_params) + self.env.cr.execute(request, params) + for row in self.env.cr.dictfetchall(): + account_result[row.pop('id')] = row + + account_res = [] + for account in accounts: + res = dict((fn, 0.0) for fn in ['credit', 'debit', 'balance']) + currency = account.currency_id and account.currency_id or account.company_id.currency_id + res['code'] = account.code + res['name'] = account.name + if account.id in account_result: + res['debit'] = account_result[account.id].get('debit') + res['credit'] = account_result[account.id].get('credit') + res['balance'] = account_result[account.id].get('balance') + if display_account == 'all': + account_res.append(res) + if display_account == 'not_zero' and not currency.is_zero( + res['balance']): + account_res.append(res) + if display_account == 'movement' and ( + not currency.is_zero(res['debit']) or not currency.is_zero( + res['credit'])): + account_res.append(res) + return account_res + + @api.model + def _get_report_values(self, docids, data=None): + if not data.get('form') or not self.env.context.get('active_model'): + raise UserError( + _("Form content is missing, this report cannot be printed.")) + + self.model = self.env.context.get('active_model') + docs = self.env[self.model].browse( + self.env.context.get('active_ids', [])) + display_account = data['form'].get('display_account') + accounts = docs if self.model == 'account.account' else self.env[ + 'account.account'].search([]) + account_res = self.with_context( + data['form'].get('used_context'))._get_accounts(accounts, + display_account) + return { + 'doc_ids': self.ids, + 'doc_model': self.model, + 'data': data['form'], + 'docs': docs, + 'time': time, + 'Accounts': account_res, + } diff --git a/base_accounting_kit/report/report_trial_balance.xml b/base_accounting_kit/report/report_trial_balance.xml new file mode 100644 index 000000000..d7bfd0c15 --- /dev/null +++ b/base_accounting_kit/report/report_trial_balance.xml @@ -0,0 +1,71 @@ + + + + diff --git a/base_accounting_kit/security/account_asset_security.xml b/base_accounting_kit/security/account_asset_security.xml new file mode 100644 index 000000000..df01a4a26 --- /dev/null +++ b/base_accounting_kit/security/account_asset_security.xml @@ -0,0 +1,26 @@ + + + + + Account Asset Category multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + Account Asset multi-company + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + + Accountant + + + + + + + diff --git a/base_accounting_kit/security/ir.model.access.csv b/base_accounting_kit/security/ir.model.access.csv new file mode 100644 index 000000000..13c83e941 --- /dev/null +++ b/base_accounting_kit/security/ir.model.access.csv @@ -0,0 +1,21 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_financial_report_user,account_fin_rep_name_user,model_account_financial_report,account.group_account_user,1,1,1,1 +access_financial_report_manager,account_fin_rep_name_manager,model_account_financial_report,account.group_account_manager,1,1,1,1 +access_account_report_general_ledger_user,account_account_report_general_ledger_id_user,model_account_report_general_ledger,account.group_account_user,1,1,1,1 +access_account_report_general_ledger_manager,account_account_report_general_ledger_id_manager,model_account_report_general_ledger,account.group_account_manager,1,1,1,1 +access_generate_recurring_entries,generate.recurring.entries.user,model_account_recurring_payments,account.group_account_user,1,1,1,1 +access_generate_recurring_entries_wizard,generate.recurring.entries.user.wizard,model_recurring_payments_wizard,account.group_account_user,1,1,1,1 +access_account_followup,account.followup,model_account_followup,account.group_account_manager,1,1,1,1 +access_followup_line,followup.line,model_followup_line,account.group_account_manager,1,1,0,0 + +access_account_asset_category,account.asset.category,model_account_asset_category,account.group_account_user,1,0,0,0 +access_account_asset_asset,account.asset.asset,model_account_asset_asset,account.group_account_user,1,0,0,0 +access_account_asset_category_manager,account.asset.category,model_account_asset_category,account.group_account_manager,1,1,1,1 +access_account_asset_asset_manager,account.asset.asset,model_account_asset_asset,account.group_account_manager,1,1,1,1 +access_account_asset_depreciation_line,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_user,1,0,0,0 +access_account_asset_depreciation_line_manager,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_manager,1,1,1,1 +access_asset_asset_report,asset.asset.report,model_asset_asset_report,account.group_account_user,1,0,0,0 +access_asset_asset_report_manager,asset.asset.report,model_asset_asset_report,account.group_account_manager,1,1,1,1 +access_account_asset_category_invoicing_payment,account.asset.category,model_account_asset_category,account.group_account_invoice,1,0,0,0 +access_account_asset_asset_invoicing_payment,account.asset.asset,model_account_asset_asset,account.group_account_invoice,1,0,1,0 +access_account_asset_depreciation_line_invoicing_payment,account.asset.depreciation.line,model_account_asset_depreciation_line,account.group_account_invoice,1,0,1,0 \ No newline at end of file diff --git a/base_accounting_kit/static/description/banner.png b/base_accounting_kit/static/description/banner.png new file mode 100644 index 000000000..fb3ea07b9 Binary files /dev/null and b/base_accounting_kit/static/description/banner.png differ diff --git a/base_accounting_kit/static/description/cybro_logo.png b/base_accounting_kit/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/base_accounting_kit/static/description/cybro_logo.png differ diff --git a/base_accounting_kit/static/description/icon.png b/base_accounting_kit/static/description/icon.png new file mode 100644 index 000000000..11df8744c Binary files /dev/null and b/base_accounting_kit/static/description/icon.png differ diff --git a/base_accounting_kit/static/description/images/account_dynamic_report_banner.gif b/base_accounting_kit/static/description/images/account_dynamic_report_banner.gif new file mode 100644 index 000000000..d4530e466 Binary files /dev/null and b/base_accounting_kit/static/description/images/account_dynamic_report_banner.gif differ diff --git a/base_accounting_kit/static/description/images/accounting_kit_window.png b/base_accounting_kit/static/description/images/accounting_kit_window.png new file mode 100644 index 000000000..025dffae8 Binary files /dev/null and b/base_accounting_kit/static/description/images/accounting_kit_window.png differ diff --git a/base_accounting_kit/static/description/images/checked.png b/base_accounting_kit/static/description/images/checked.png new file mode 100644 index 000000000..578cedb80 Binary files /dev/null and b/base_accounting_kit/static/description/images/checked.png differ diff --git a/base_accounting_kit/static/description/images/crm_dashboard_banner.gif b/base_accounting_kit/static/description/images/crm_dashboard_banner.gif new file mode 100644 index 000000000..b80a0bfc9 Binary files /dev/null and b/base_accounting_kit/static/description/images/crm_dashboard_banner.gif differ diff --git a/base_accounting_kit/static/description/images/cybrosys.png b/base_accounting_kit/static/description/images/cybrosys.png new file mode 100644 index 000000000..d76b5bafb Binary files /dev/null and b/base_accounting_kit/static/description/images/cybrosys.png differ diff --git a/base_accounting_kit/static/description/images/mobile_service_shop_pro_banner.jpg b/base_accounting_kit/static/description/images/mobile_service_shop_pro_banner.jpg new file mode 100644 index 000000000..7a2e022ca Binary files /dev/null and b/base_accounting_kit/static/description/images/mobile_service_shop_pro_banner.jpg differ diff --git a/base_accounting_kit/static/description/images/odoo11_magento_banner.jpg b/base_accounting_kit/static/description/images/odoo11_magento_banner.jpg new file mode 100644 index 000000000..b2d9b33ce Binary files /dev/null and b/base_accounting_kit/static/description/images/odoo11_magento_banner.jpg differ diff --git a/base_accounting_kit/static/description/images/project_custome_gantt_banner.gif b/base_accounting_kit/static/description/images/project_custome_gantt_banner.gif new file mode 100755 index 000000000..3ee64f1c5 Binary files /dev/null and b/base_accounting_kit/static/description/images/project_custome_gantt_banner.gif differ diff --git a/base_accounting_kit/static/description/images/report_maker_banner.gif b/base_accounting_kit/static/description/images/report_maker_banner.gif new file mode 100644 index 000000000..db6305f39 Binary files /dev/null and b/base_accounting_kit/static/description/images/report_maker_banner.gif differ diff --git a/base_accounting_kit/static/description/index.html b/base_accounting_kit/static/description/index.html new file mode 100644 index 000000000..cb38cb5dd --- /dev/null +++ b/base_accounting_kit/static/description/index.html @@ -0,0 +1,464 @@ +
+ cybrosys-logo
+ +
+
+
+

Odoo 13 Accounting

+

A complete accounting kit for Odoo 13.

+
+

Key Highlights

+
    +
  • + check + Asset management. +
  • +
  • + check + PDC management. +
  • +
  • + check + Accounting lock dates. +
  • +
  • + check + Customer credit limit. +
  • +
  • + check + Recurring payments. +
  • +
  • + check + Day book, Bank book and Cash book reports. +
  • +
  • + check + Customer follow up. +
  • +
  • + check + Financial reports. +
  • +
  • + check + Trial balance report. +
  • +
  • + check + Journal audit report. +
  • +
  • + check + Cash flow statements. +
  • +
  • + check + Partner ledger. +
  • +
  • + check + Aged partner balance. +
  • +
  • + check + Tax reports. +
  • +
+ +
+
+
+
+
+ +
+
+
+ + + +
+
+ +

Overview

+
+

+ This Module will bring back the accounting features such as Account Reports, Asset Management + and Customer Follow Up into Odoo 13 Community Edition. Also new features are added to the + accounting module such as Customer Credit Limit, Recurring Payment, PDC Management and Lock Dates + are introduced into Odoo 13 Community Edition. +

+
+ + +
+
    +
    +
    +
    +
    +
    + +
    +

    Suggested Products

    +
    + +
    + + +
    +

    Our Service

    +
    + +
    +
    +
    +

    Our Industries

    +
    + +
    +
    +
    +
    + Odoo Industry
    +
    +
    +

    + + Trading

    +

    + Easily procure and sell your products.

    +
    +
    +
    +
    +
    + Odoo Industry +
    +
    +
    +

    + + Manufacturing

    +

    + Plan, track and schedule your operations.

    +
    +
    +
    +
    +
    + + Odoo Industry
    +
    +
    +

    + + Restaurant

    +

    + Run your bar or restaurant methodical.

    +
    +
    +
    +
    +
    + Odoo Industry
    +
    +
    +

    + + POS

    +

    + Easy configuring and convivial selling.

    +
    +
    +
    +
    +
    + Odoo Industry
    +
    +
    +

    + + E-commerce & Website

    +

    + Mobile friendly, awe-inspiring product pages.

    +
    +
    +
    +
    +
    + + Odoo Industry
    +
    +
    +

    + + Hotel Management

    +

    + An all-inclusive hotel management application.

    +
    +
    +
    +
    +
    + + Odoo Industry
    +
    +
    +

    + + Education

    +

    + A Collaborative platform for educational management.

    +
    +
    +
    +
    +
    + Odoo Industry
    +
    +
    +

    + + Service Management

    +

    + Keep track of services and invoice accordingly.

    +
    +
    +
    +
    + +
    + + +
    +
    +
    +

    Need Any Help?

    +
    + +

    If you have anything to share with us based on your use of this module, + please let us know. We are ready to offer our support.

    +
    +

    Email us

    +

    odoo@cybrosys.com / info@cybrosys.com

    + +
    +
    +

    Contact Us

    + www.cybrosys.com +
    +
    + +
    +
    + + +
    +
    +
    + + +
    +
    + +
    + + + + + + + + +
    +
    +
    +
    + diff --git a/base_accounting_kit/views/account_asset_templates.xml b/base_accounting_kit/views/account_asset_templates.xml new file mode 100644 index 000000000..7f8f3925e --- /dev/null +++ b/base_accounting_kit/views/account_asset_templates.xml @@ -0,0 +1,9 @@ + + +