# Fusion Accounting - Configuration Settings # Extends res.config.settings with accounting-specific options # including fiscal year, deferrals, tax periodicity, and stock accounts. from datetime import date from odoo import api, fields, models, _ from odoo.exceptions import ValidationError from odoo.tools import date_utils from odoo.tools.misc import format_date # Shared domain for stock-related account fields _ACCOUNT_FILTER_DOMAIN = [ ('account_type', 'not in', ( 'asset_receivable', 'liability_payable', 'asset_cash', 'liability_credit_card', 'off_balance', )), ] class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' # ===================================================================== # Fiscal Year # ===================================================================== fiscalyear_last_day = fields.Integer( related='company_id.fiscalyear_last_day', required=True, readonly=False, ) fiscalyear_last_month = fields.Selection( related='company_id.fiscalyear_last_month', required=True, readonly=False, ) group_fiscal_year = fields.Boolean( string='Fiscal Years', implied_group='fusion_accounting.group_fiscal_year', ) # ===================================================================== # General Accounting Options # ===================================================================== use_anglo_saxon = fields.Boolean( string='Anglo-Saxon Accounting', related='company_id.anglo_saxon_accounting', readonly=False, ) invoicing_switch_threshold = fields.Date( string="Invoicing Switch Threshold", related='company_id.invoicing_switch_threshold', readonly=False, ) predict_bill_product = fields.Boolean( string="Predict Bill Product", related='company_id.predict_bill_product', readonly=False, ) # ===================================================================== # Inter-Company Invoice Sync # ===================================================================== fusion_intercompany_invoice_enabled = fields.Boolean( string="Inter-Company Invoice Sync", related='company_id.fusion_intercompany_invoice_enabled', readonly=False, help="Automatically create matching bills/invoices between companies.", ) fusion_intercompany_invoice_journal_id = fields.Many2one( comodel_name='account.journal', string="Inter-Company Journal", related='company_id.fusion_intercompany_invoice_journal_id', readonly=False, help="Default journal for inter-company counter-documents.", ) # ===================================================================== # Invoice Signing # ===================================================================== sign_invoice = fields.Boolean( string='Authorized Signatory on invoice', related='company_id.sign_invoice', readonly=False, ) signing_user = fields.Many2one( comodel_name='res.users', string="Signature used to sign all the invoice", readonly=False, related='company_id.signing_user', help="Override every invoice signature with this user's signature.", ) module_sign = fields.Boolean( string='Sign', compute='_compute_module_sign_status', ) # ===================================================================== # Deferred Expenses # ===================================================================== deferred_expense_journal_id = fields.Many2one( comodel_name='account.journal', help='Journal for deferred expense entries.', readonly=False, related='company_id.deferred_expense_journal_id', ) deferred_expense_account_id = fields.Many2one( comodel_name='account.account', help='Account for deferred expense balances.', readonly=False, related='company_id.deferred_expense_account_id', ) generate_deferred_expense_entries_method = fields.Selection( related='company_id.generate_deferred_expense_entries_method', readonly=False, required=True, help='When to generate deferred expense entries.', ) deferred_expense_amount_computation_method = fields.Selection( related='company_id.deferred_expense_amount_computation_method', readonly=False, required=True, help='How to prorate deferred expense amounts.', ) # ===================================================================== # Deferred Revenue # ===================================================================== deferred_revenue_journal_id = fields.Many2one( comodel_name='account.journal', help='Journal for deferred revenue entries.', readonly=False, related='company_id.deferred_revenue_journal_id', ) deferred_revenue_account_id = fields.Many2one( comodel_name='account.account', help='Account for deferred revenue balances.', readonly=False, related='company_id.deferred_revenue_account_id', ) generate_deferred_revenue_entries_method = fields.Selection( related='company_id.generate_deferred_revenue_entries_method', readonly=False, required=True, help='When to generate deferred revenue entries.', ) deferred_revenue_amount_computation_method = fields.Selection( related='company_id.deferred_revenue_amount_computation_method', readonly=False, required=True, help='How to prorate deferred revenue amounts.', ) # ===================================================================== # Reporting & Tax Periodicity # ===================================================================== totals_below_sections = fields.Boolean( related='company_id.totals_below_sections', string='Add totals below sections', readonly=False, help='Display totals and subtotals beneath report sections.', ) account_tax_periodicity = fields.Selection( related='company_id.account_tax_periodicity', string='Periodicity', readonly=False, required=True, ) account_tax_periodicity_reminder_day = fields.Integer( related='company_id.account_tax_periodicity_reminder_day', string='Reminder', readonly=False, required=True, ) account_tax_periodicity_journal_id = fields.Many2one( related='company_id.account_tax_periodicity_journal_id', string='Journal', readonly=False, ) account_reports_show_per_company_setting = fields.Boolean( compute="_compute_account_reports_show_per_company_setting", ) # ===================================================================== # Stock Valuation Accounts (Product Category Defaults) # ===================================================================== property_stock_journal = fields.Many2one( comodel_name='account.journal', string="Stock Journal", check_company=True, compute='_compute_property_stock_account', inverse='_set_property_stock_journal', ) property_account_income_categ_id = fields.Many2one( comodel_name='account.account', string="Income Account", check_company=True, domain=_ACCOUNT_FILTER_DOMAIN, compute='_compute_property_stock_account', inverse='_set_property_account_income_categ_id', ) property_account_expense_categ_id = fields.Many2one( comodel_name='account.account', string="Expense Account", check_company=True, domain=_ACCOUNT_FILTER_DOMAIN, compute='_compute_property_stock_account', inverse='_set_property_account_expense_categ_id', ) property_stock_valuation_account_id = fields.Many2one( comodel_name='account.account', string="Stock Valuation Account", check_company=True, domain="[]", compute='_compute_property_stock_account', inverse='_set_property_stock_valuation_account_id', ) property_stock_account_input_categ_id = fields.Many2one( comodel_name='account.account', string="Stock Input Account", check_company=True, domain="[]", compute='_compute_property_stock_account', inverse='_set_property_stock_account_input_categ_id', ) property_stock_account_output_categ_id = fields.Many2one( comodel_name='account.account', string="Stock Output Account", check_company=True, domain="[]", compute='_compute_property_stock_account', inverse='_set_property_stock_account_output_categ_id', ) # ===================================================================== # Compute Methods # ===================================================================== @api.depends('sign_invoice') def _compute_module_sign_status(self): """Check whether the Sign module is installed or sign is enabled.""" is_sign_installed = 'sign' in self.env['ir.module.module']._installed() for cfg in self: cfg.module_sign = is_sign_installed or cfg.company_id.sign_invoice @api.depends('company_id') def _compute_account_reports_show_per_company_setting(self): """Show per-company tax report start date settings when the company operates in countries with custom start dates.""" custom_codes = self._get_country_codes_with_another_tax_closing_start_date() relevant_countries = ( self.env['account.fiscal.position'].search([ ('company_id', '=', self.env.company.id), ('foreign_vat', '!=', False), ]).mapped('country_id') + self.env.company.account_fiscal_country_id ) for cfg in self: cfg.account_reports_show_per_company_setting = bool( set(relevant_countries.mapped('code')) & custom_codes ) @api.depends('company_id') def _compute_property_stock_account(self): """Load stock account defaults from product.category properties.""" prop_names = self._get_account_stock_properties_names() ProdCat = self.env['product.category'] for cfg in self: scoped = cfg.with_company(cfg.company_id) for prop_name in prop_names: fld = ProdCat._fields[prop_name] scoped[prop_name] = fld.get_company_dependent_fallback(ProdCat) # ===================================================================== # Constraints # ===================================================================== @api.constrains('fiscalyear_last_day', 'fiscalyear_last_month') def _check_fiscalyear(self): """Validate that the fiscal year end date is a real calendar date.""" for cfg in self: try: date(2020, int(cfg.fiscalyear_last_month), cfg.fiscalyear_last_day) except ValueError: raise ValidationError(_( 'Invalid fiscal year date: day %(day)s is out of range ' 'for month %(month)s.', month=cfg.fiscalyear_last_month, day=cfg.fiscalyear_last_day, )) # ===================================================================== # Create Override # ===================================================================== @api.model_create_multi def create(self, vals_list): """Write fiscal year settings atomically to the company to avoid intermediate constraint violations from related fields.""" for vals in vals_list: fy_day = vals.pop('fiscalyear_last_day', False) or self.env.company.fiscalyear_last_day fy_month = vals.pop('fiscalyear_last_month', False) or self.env.company.fiscalyear_last_month company_updates = {} if fy_day != self.env.company.fiscalyear_last_day: company_updates['fiscalyear_last_day'] = fy_day if fy_month != self.env.company.fiscalyear_last_month: company_updates['fiscalyear_last_month'] = fy_month if company_updates: self.env.company.write(company_updates) return super().create(vals_list) # ===================================================================== # Actions # ===================================================================== def open_tax_group_list(self): """Open the tax group list filtered by the fiscal country.""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': 'Tax groups', 'res_model': 'account.tax.group', 'view_mode': 'list', 'context': { 'default_country_id': self.account_fiscal_country_id.id, 'search_default_country_id': self.account_fiscal_country_id.id, }, } def open_company_dependent_report_settings(self): """Open the report configuration view for per-company start dates.""" self.ensure_one() base_report = self.env.ref('account.generic_tax_report') variant_reports = base_report._get_variants(base_report.id) return { 'type': 'ir.actions.act_window', 'name': _('Configure your start dates'), 'res_model': 'account.report', 'domain': [('id', 'in', variant_reports.ids)], 'views': [( self.env.ref( 'fusion_accounting.account_report_tree_configure_start_dates', ).id, 'list', )], } # ===================================================================== # Hooks # ===================================================================== def _get_country_codes_with_another_tax_closing_start_date(self): """Hook for localization modules to declare countries with custom tax closing start date support. :return: Set of country code strings. """ return set() # ===================================================================== # Stock Property Setters # ===================================================================== def _set_property_stock_journal(self): for cfg in self: cfg._persist_product_category_default('property_stock_journal') def _set_property_account_income_categ_id(self): for cfg in self: cfg._persist_product_category_default('property_account_income_categ_id') def _set_property_account_expense_categ_id(self): for cfg in self: cfg._persist_product_category_default('property_account_expense_categ_id') def _set_property_stock_valuation_account_id(self): for cfg in self: cfg._persist_product_category_default('property_stock_valuation_account_id') def _set_property_stock_account_input_categ_id(self): for cfg in self: cfg._persist_product_category_default('property_stock_account_input_categ_id') def _set_property_stock_account_output_categ_id(self): for cfg in self: cfg._persist_product_category_default('property_stock_account_output_categ_id') def _persist_product_category_default(self, field_name): """Save a product category default value via ir.default.""" self.env['ir.default'].set( 'product.category', field_name, self[field_name].id, company_id=self.company_id.id, ) @api.model def _get_account_stock_properties_names(self): """Return the list of stock-related property field names.""" return [ 'property_stock_journal', 'property_account_income_categ_id', 'property_account_expense_categ_id', 'property_stock_valuation_account_id', 'property_stock_account_input_categ_id', 'property_stock_account_output_categ_id', ]