"""Materialized view of per-account-per-month balances. Created lazily by init() (called by Odoo on install/upgrade). Refresh via the model's _refresh() method or via cron (Task 25).""" import logging import os from odoo import api, fields, models _logger = logging.getLogger(__name__) class FusionAccountBalanceMV(models.Model): _name = "fusion.account.balance.mv" _description = "MV of per-account per-month aggregated balances" _auto = False _table = "fusion_account_balance_mv" _order = "period_month desc, account_id" account_id = fields.Many2one('account.account', readonly=True) company_id = fields.Many2one('res.company', readonly=True) period_month = fields.Date(readonly=True) debit = fields.Float(readonly=True) credit = fields.Float(readonly=True) balance = fields.Float(readonly=True) line_count = fields.Integer(readonly=True) def init(self): # If the MV exists but is missing the synthetic `id` column (e.g. from # an earlier dev install), drop it so the new schema applies cleanly. self.env.cr.execute( """ SELECT 1 FROM pg_matviews mv JOIN pg_attribute a ON a.attrelid = (mv.schemaname || '.' || mv.matviewname)::regclass AND a.attname = 'id' WHERE mv.matviewname = 'fusion_account_balance_mv' """ ) if not self.env.cr.fetchone(): self.env.cr.execute( "DROP MATERIALIZED VIEW IF EXISTS fusion_account_balance_mv" ) sql_path = os.path.join( os.path.dirname(__file__), '..', 'data', 'sql', 'create_mv_account_balance.sql', ) with open(sql_path, 'r') as f: self.env.cr.execute(f.read()) _logger.info( "fusion_account_balance_mv: created/verified MV + indexes") @api.model def _refresh(self, *, concurrently=True): """Refresh the MV. Falls back to non-concurrent if CONCURRENTLY fails. REFRESH MATERIALIZED VIEW CONCURRENTLY requires the MV to be already populated and an autocommit-capable cursor; the cron path in Task 25 opens a dedicated cursor for that. This helper keeps callers safe by retrying without CONCURRENTLY on failure.""" keyword = "CONCURRENTLY" if concurrently else "" try: self.env.cr.execute( f"REFRESH MATERIALIZED VIEW {keyword} fusion_account_balance_mv" ) _logger.debug( "fusion_account_balance_mv refreshed (%s)", 'concurrent' if concurrently else 'blocking', ) except Exception as e: if concurrently: _logger.warning( "Concurrent MV refresh failed (%s); falling back", e) self.env.cr.execute( "REFRESH MATERIALIZED VIEW fusion_account_balance_mv" ) else: raise