Task 13 Step 10 of phase-0 plan.
- month_end.get_period_summary → ReportsAdapter.run_report(...) with
Community fallback to the trial_balance() aggregator.
- hst_management.get_tax_report → ReportsAdapter.run_report(...).
Other tools in these files (get_unreconciled_counts, find_entries_in_locked_period,
get_accrual_status, run_hash_integrity_check, calculate_hst_balance,
find_missing_tax_invoices, find_missing_itc_bills, create_expense_entry) touch
pure-Community models (account.move, account.move.line, account.account,
account.payment) directly and are tri-mode safe.
account.return tools in hst_management (get_tax_return_status, generate_tax_return,
validate_tax_return) and account.audit.account.status tools in audit.py already
handle the missing-model case gracefully. They fall outside this task's target
set of {account.report, account.followup.line, account.asset} and are left
as-is per plan.
All 12 data-adapter tests pass on westin-v19.
Made-with: Cursor
140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
import logging
|
|
from odoo import fields
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_close_checklist(env, params):
|
|
from .bank_reconciliation import get_unreconciled_bank_lines
|
|
from .journal_review import find_draft_entries, find_sequence_gaps
|
|
from .hst_management import calculate_hst_balance
|
|
|
|
period = params.get('period', str(fields.Date.today())[:7])
|
|
date_from = f'{period}-01'
|
|
import calendar
|
|
year, month = int(period[:4]), int(period[5:7])
|
|
last_day = calendar.monthrange(year, month)[1]
|
|
date_to = f'{period}-{last_day:02d}'
|
|
|
|
p = {'date_from': date_from, 'date_to': date_to}
|
|
|
|
bank = get_unreconciled_bank_lines(env, p)
|
|
drafts = find_draft_entries(env, {'min_age_days': '0'})
|
|
gaps = find_sequence_gaps(env, p)
|
|
hst = calculate_hst_balance(env, p)
|
|
|
|
checklist = [
|
|
{'item': 'Bank Reconciliation', 'status': 'ok' if bank['count'] == 0 else 'attention', 'detail': f"{bank['count']} unreconciled lines"},
|
|
{'item': 'Draft Entries', 'status': 'ok' if drafts['count'] == 0 else 'attention', 'detail': f"{drafts['count']} draft entries"},
|
|
{'item': 'Sequence Gaps', 'status': 'ok' if gaps['count'] == 0 else 'warning', 'detail': f"{gaps['count']} gaps found"},
|
|
{'item': 'HST Balance', 'status': 'info', 'detail': f"Net HST: ${hst['net_hst']:.2f}"},
|
|
]
|
|
return {'period': period, 'checklist': checklist}
|
|
|
|
|
|
def get_unreconciled_counts(env, params):
|
|
accounts = env['account.account'].search([
|
|
('reconcile', '=', True),
|
|
('company_ids', 'in', env.company.id),
|
|
])
|
|
result = []
|
|
for acct in accounts:
|
|
count = env['account.move.line'].search_count([
|
|
('account_id', '=', acct.id),
|
|
('reconciled', '=', False),
|
|
('parent_state', '=', 'posted'),
|
|
])
|
|
if count > 0:
|
|
result.append({
|
|
'account_id': acct.id,
|
|
'code': acct.code,
|
|
'name': acct.name,
|
|
'unreconciled_count': count,
|
|
})
|
|
return {'accounts': sorted(result, key=lambda x: -x['unreconciled_count'])}
|
|
|
|
|
|
def find_entries_in_locked_period(env, params):
|
|
company = env.company
|
|
lock_date = company.fiscalyear_lock_date
|
|
if not lock_date:
|
|
return {'status': 'no_lock_date', 'entries': []}
|
|
entries = env['account.move'].search([
|
|
('date', '<=', lock_date),
|
|
('state', '=', 'draft'),
|
|
('company_id', '=', company.id),
|
|
])
|
|
return {
|
|
'lock_date': str(lock_date),
|
|
'count': len(entries),
|
|
'entries': [{'id': e.id, 'name': e.name, 'date': str(e.date)} for e in entries[:20]],
|
|
}
|
|
|
|
|
|
def get_accrual_status(env, params):
|
|
accrual_codes = params.get('account_codes', ['2100', '2110', '2120'])
|
|
result = []
|
|
for code in accrual_codes:
|
|
accounts = env['account.account'].search([
|
|
('code', '=like', f'{code}%'),
|
|
('company_ids', 'in', env.company.id),
|
|
])
|
|
for acct in accounts:
|
|
balance = sum(env['account.move.line'].search([
|
|
('account_id', '=', acct.id),
|
|
('parent_state', '=', 'posted'),
|
|
]).mapped('balance'))
|
|
result.append({'code': acct.code, 'name': acct.name, 'balance': balance})
|
|
return {'accruals': result}
|
|
|
|
|
|
def run_hash_integrity_check(env, params):
|
|
try:
|
|
result = env.company._check_hash_integrity()
|
|
return {
|
|
'status': 'completed',
|
|
'results': result.get('results', []),
|
|
'printing_date': result.get('printing_date', ''),
|
|
}
|
|
except Exception as e:
|
|
return {'error': str(e)}
|
|
|
|
|
|
def get_period_summary(env, params):
|
|
"""Period summary via trial-balance. Routed through ReportsAdapter so the
|
|
Enterprise-only account_reports.trial_balance_report path is isolated;
|
|
Community installs fall back to the adapter's trial_balance() aggregation."""
|
|
from ..data_adapters import get_adapter
|
|
adapter = get_adapter(env, 'reports')
|
|
date_from = params.get('date_from')
|
|
date_to = params.get('date_to')
|
|
result = adapter.run_report(
|
|
ref_id='account_reports.trial_balance_report',
|
|
date_from=date_from, date_to=date_to,
|
|
)
|
|
if isinstance(result, dict) and result.get('error'):
|
|
rows = adapter.trial_balance(
|
|
date_to=date_to, company_ids=[env.company.id],
|
|
)
|
|
return {
|
|
'period': f'{date_from} to {date_to}',
|
|
'lines': [{
|
|
'name': f"{r['account_code']} {r['account_name']}",
|
|
'columns': [r['debit'], r['credit'], r['balance']],
|
|
} for r in rows[:100]],
|
|
}
|
|
return {
|
|
'period': f'{date_from} to {date_to}',
|
|
'lines': result.get('lines', []),
|
|
}
|
|
|
|
|
|
TOOLS = {
|
|
'get_close_checklist': get_close_checklist,
|
|
'get_unreconciled_counts': get_unreconciled_counts,
|
|
'find_entries_in_locked_period': find_entries_in_locked_period,
|
|
'get_accrual_status': get_accrual_status,
|
|
'run_hash_integrity_check': run_hash_integrity_check,
|
|
'get_period_summary': get_period_summary,
|
|
}
|