changes
This commit is contained in:
@@ -209,59 +209,111 @@ class FusionAccountingDashboard(models.TransientModel):
|
||||
def _compute_action_centre(self):
|
||||
for rec in self:
|
||||
attention = []
|
||||
today = fields.Date.today()
|
||||
|
||||
unrecon = self.env['account.bank.statement.line'].search_count([
|
||||
('is_reconciled', '=', False),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if unrecon > 0:
|
||||
attention.append({
|
||||
'priority': 1,
|
||||
'title': f'{unrecon} unreconciled bank lines',
|
||||
'domain': 'bank_reconciliation',
|
||||
'action': 'Review and reconcile bank statement lines',
|
||||
})
|
||||
|
||||
overdue = self.env['account.move'].search_count([
|
||||
('move_type', '=', 'out_invoice'),
|
||||
('state', '=', 'posted'),
|
||||
('payment_state', 'in', ('not_paid', 'partial')),
|
||||
('invoice_date_due', '<', fields.Date.today()),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if overdue > 0:
|
||||
attention.append({
|
||||
'priority': 2,
|
||||
'title': f'{overdue} overdue customer invoices',
|
||||
'domain': 'accounts_receivable',
|
||||
'action': 'Send follow-up reminders',
|
||||
})
|
||||
|
||||
# Pending AI approvals (highest priority)
|
||||
pending = self.env['fusion.accounting.match.history'].search_count([
|
||||
('decision', '=', 'pending'),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if pending > 0:
|
||||
attention.append({
|
||||
'priority': 0,
|
||||
'title': f'{pending} AI actions awaiting approval',
|
||||
'priority': 0, 'severity': 'danger',
|
||||
'title': f'{pending} AI actions awaiting your approval',
|
||||
'domain': 'audit',
|
||||
'action': 'Review and approve/reject pending actions',
|
||||
'action': 'Review and approve or reject pending actions',
|
||||
'prompt': 'Show me all pending approval actions',
|
||||
})
|
||||
|
||||
# Unreconciled bank lines
|
||||
unrecon = self.env['account.bank.statement.line'].search_count([
|
||||
('is_reconciled', '=', False),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if unrecon > 0:
|
||||
attention.append({
|
||||
'priority': 1, 'severity': 'warning',
|
||||
'title': f'{unrecon} unreconciled bank lines',
|
||||
'domain': 'bank_reconciliation',
|
||||
'action': 'Review and reconcile bank statement lines',
|
||||
'prompt': 'Show me unreconciled bank lines across all journals with a breakdown by journal',
|
||||
})
|
||||
|
||||
# Overdue customer invoices
|
||||
overdue = self.env['account.move'].search_count([
|
||||
('move_type', '=', 'out_invoice'),
|
||||
('state', '=', 'posted'),
|
||||
('payment_state', 'in', ('not_paid', 'partial')),
|
||||
('invoice_date_due', '<', today),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if overdue > 0:
|
||||
attention.append({
|
||||
'priority': 2, 'severity': 'warning',
|
||||
'title': f'{overdue} overdue customer invoices',
|
||||
'domain': 'accounts_receivable',
|
||||
'action': 'Send follow-up reminders',
|
||||
'prompt': 'Show me overdue invoices sorted by amount',
|
||||
})
|
||||
|
||||
# Unpaid vendor bills due this week
|
||||
week_end = today + timedelta(days=7)
|
||||
due_bills = self.env['account.move'].search_count([
|
||||
('move_type', '=', 'in_invoice'),
|
||||
('state', '=', 'posted'),
|
||||
('payment_state', 'in', ('not_paid', 'partial')),
|
||||
('invoice_date_due', '<=', week_end),
|
||||
('invoice_date_due', '>=', today),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if due_bills > 0:
|
||||
attention.append({
|
||||
'priority': 3, 'severity': 'info',
|
||||
'title': f'{due_bills} vendor bills due this week',
|
||||
'domain': 'accounts_payable',
|
||||
'action': 'Review upcoming payments',
|
||||
'prompt': f'Show me vendor bills due between {today} and {week_end}',
|
||||
})
|
||||
|
||||
# Stale draft entries
|
||||
drafts = self.env['account.move'].search_count([
|
||||
('state', '=', 'draft'),
|
||||
('date', '<=', fields.Date.today() - timedelta(days=30)),
|
||||
('date', '<=', today - timedelta(days=30)),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if drafts > 0:
|
||||
attention.append({
|
||||
'priority': 3,
|
||||
'priority': 4, 'severity': 'muted',
|
||||
'title': f'{drafts} stale draft entries (30+ days)',
|
||||
'domain': 'journal_review',
|
||||
'action': 'Post or delete stale draft entries',
|
||||
'prompt': 'Find all stale draft entries older than 30 days',
|
||||
})
|
||||
|
||||
# Unmatched customer payments (on outstanding receipts accounts)
|
||||
try:
|
||||
outstanding_accts = self.env['account.account'].search([
|
||||
('name', 'ilike', 'outstanding receipt'),
|
||||
('company_ids', 'in', rec.company_id.id),
|
||||
])
|
||||
if outstanding_accts:
|
||||
unmatched_payments = self.env['account.move.line'].search_count([
|
||||
('account_id', 'in', outstanding_accts.ids),
|
||||
('parent_state', '=', 'posted'),
|
||||
('reconciled', '=', False),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
])
|
||||
if unmatched_payments > 0:
|
||||
attention.append({
|
||||
'priority': 5, 'severity': 'info',
|
||||
'title': f'{unmatched_payments} unmatched customer payments',
|
||||
'domain': 'accounts_receivable',
|
||||
'action': 'Match payments to invoices',
|
||||
'prompt': 'Show me unmatched customer payments that need to be applied to invoices',
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
attention.sort(key=lambda x: x['priority'])
|
||||
rec.needs_attention_json = json.dumps(attention)
|
||||
|
||||
|
||||
@@ -1,19 +1,83 @@
|
||||
import json
|
||||
import logging
|
||||
from odoo import models, fields, api
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TOOL_LABELS = {
|
||||
'get_unreconciled_bank_lines': 'Get Unreconciled Bank Lines',
|
||||
'get_unreconciled_receipts': 'Get Unreconciled Receipts',
|
||||
'match_bank_line_to_payments': 'Match Bank Line to Payments',
|
||||
'auto_reconcile_bank_lines': 'Auto-Reconcile Bank Lines',
|
||||
'apply_reconcile_model': 'Apply Reconcile Model',
|
||||
'unmatch_bank_line': 'Unmatch Bank Line',
|
||||
'get_reconcile_suggestions': 'Get Reconcile Suggestions',
|
||||
'sum_payments_by_date': 'Sum Payments by Date',
|
||||
'get_bank_line_details': 'Get Bank Line Details',
|
||||
'check_recurring_pattern': 'Check Recurring Pattern',
|
||||
'match_internal_transfers': 'Match Internal Transfers',
|
||||
'find_unreconciled_cheques': 'Find Unreconciled Cheques',
|
||||
'reconcile_payroll_cheques': 'Reconcile Payroll Cheques',
|
||||
'suggest_bank_line_matches': 'Suggest Bank Line Matches',
|
||||
'search_matching_entries': 'Search Matching Entries',
|
||||
'calculate_hst_balance': 'Calculate HST Balance',
|
||||
'create_expense_entry': 'Create Expense Entry',
|
||||
'find_missing_itc_bills': 'Find Missing ITC Bills',
|
||||
'find_missing_tax_invoices': 'Find Missing Tax Invoices',
|
||||
'get_tax_report': 'Get Tax Report',
|
||||
'get_ar_aging': 'Get AR Aging',
|
||||
'get_overdue_invoices': 'Get Overdue Invoices',
|
||||
'get_partner_balance': 'Get Partner Balance',
|
||||
'get_ap_aging': 'Get AP Aging',
|
||||
'get_unpaid_bills': 'Get Unpaid Bills',
|
||||
'find_duplicate_bills': 'Find Duplicate Bills',
|
||||
'create_vendor_bill': 'Create Vendor Bill',
|
||||
'register_bill_payment': 'Register Bill Payment',
|
||||
'get_profit_loss': 'Get Profit & Loss',
|
||||
'get_balance_sheet': 'Get Balance Sheet',
|
||||
'get_trial_balance': 'Get Trial Balance',
|
||||
'get_cash_flow': 'Get Cash Flow',
|
||||
'compare_periods': 'Compare Periods',
|
||||
'get_invoicing_summary': 'Get Invoicing Summary',
|
||||
'get_billing_summary': 'Get Billing Summary',
|
||||
'get_collections_summary': 'Get Collections Summary',
|
||||
'create_payroll_journal_entry': 'Create Payroll Journal Entry',
|
||||
'find_adp_without_payment': 'Find ADP Without Payment',
|
||||
'get_adp_receivable_aging': 'Get ADP Receivable Aging',
|
||||
'register_adp_batch_payment': 'Register ADP Batch Payment',
|
||||
'get_close_checklist': 'Get Month-End Checklist',
|
||||
'find_draft_entries': 'Find Draft Entries',
|
||||
'find_wrong_direction_balances': 'Find Wrong Direction Balances',
|
||||
'find_duplicate_entries': 'Find Duplicate Entries',
|
||||
'get_payroll_entries': 'Get Payroll Entries',
|
||||
'get_cra_remittance_status': 'Get CRA Remittance Status',
|
||||
}
|
||||
|
||||
|
||||
class FusionAccountingMatchHistory(models.Model):
|
||||
_name = 'fusion.accounting.match.history'
|
||||
_description = 'Fusion Accounting Match History'
|
||||
_order = 'proposed_at desc'
|
||||
_rec_name = 'display_label'
|
||||
|
||||
display_label = fields.Char(
|
||||
string='Label', compute='_compute_display_label', store=True,
|
||||
)
|
||||
session_id = fields.Many2one(
|
||||
'fusion.accounting.session', string='Session',
|
||||
index=True, ondelete='cascade',
|
||||
)
|
||||
tool_name = fields.Char(string='Tool Name', required=True, index=True)
|
||||
tool_display_name = fields.Char(
|
||||
string='Tool', compute='_compute_tool_display_name', store=True,
|
||||
)
|
||||
tool_params_pretty = fields.Text(
|
||||
string='Parameters', compute='_compute_pretty_json',
|
||||
)
|
||||
tool_result_pretty = fields.Text(
|
||||
string='Result', compute='_compute_pretty_json',
|
||||
)
|
||||
tool_params = fields.Text(string='Tool Parameters (JSON)')
|
||||
tool_result = fields.Text(string='Tool Result (JSON)')
|
||||
ai_reasoning = fields.Text(string='AI Reasoning')
|
||||
@@ -60,6 +124,30 @@ class FusionAccountingMatchHistory(models.Model):
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
@api.depends('tool_name')
|
||||
def _compute_tool_display_name(self):
|
||||
for rec in self:
|
||||
rec.tool_display_name = TOOL_LABELS.get(rec.tool_name, (rec.tool_name or '').replace('_', ' ').title())
|
||||
|
||||
@api.depends('tool_params', 'tool_result')
|
||||
def _compute_pretty_json(self):
|
||||
for rec in self:
|
||||
for src, dst in [('tool_params', 'tool_params_pretty'), ('tool_result', 'tool_result_pretty')]:
|
||||
raw = getattr(rec, src) or '{}'
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
setattr(rec, dst, json.dumps(parsed, indent=2, default=str, ensure_ascii=False))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
setattr(rec, dst, raw)
|
||||
|
||||
@api.depends('tool_name', 'proposed_at', 'decision')
|
||||
def _compute_display_label(self):
|
||||
for rec in self:
|
||||
label = TOOL_LABELS.get(rec.tool_name, (rec.tool_name or '').replace('_', ' ').title())
|
||||
date_str = rec.proposed_at.strftime('%b %d %H:%M') if rec.proposed_at else ''
|
||||
decision_str = dict(rec._fields['decision'].selection).get(rec.decision, '')
|
||||
rec.display_label = f"{label} — {decision_str} ({date_str})" if date_str else label
|
||||
|
||||
def action_approve(self):
|
||||
self.write({
|
||||
'decision': 'approved',
|
||||
|
||||
Reference in New Issue
Block a user