81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""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
|