feat(fusion_claims): expand dashboard with this-month, pipeline, aging, recent exports + full-width
Adds 4 new sections: - This Month rollup: submitted/approved/delivered/billed counts MTD - Pipeline $ by stage: pre-submit / submitted / approved / ready-to-bill amounts - Aging buckets: 30-59d, 60-89d, 90+ days - Recent ADP Exports: last 5 with totals Also overrides Odoo's form-sheet max-width on .o_fc_dashboard so the dashboard uses the full browser width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,10 @@ class FusionClaimsDashboard(models.TransientModel):
|
||||
return []
|
||||
return [('user_id', '=', self.env.user.id)]
|
||||
|
||||
def _month_start(self):
|
||||
from datetime import date
|
||||
return date.today().replace(day=1)
|
||||
|
||||
# =========================================================================
|
||||
# Header banner
|
||||
# =========================================================================
|
||||
@@ -301,6 +305,139 @@ class FusionClaimsDashboard(models.TransientModel):
|
||||
('x_fc_mod_status', '=', 'pod_submitted'),
|
||||
])
|
||||
|
||||
# =========================================================================
|
||||
# This Month rollup (4-up secondary KPI strip)
|
||||
# =========================================================================
|
||||
count_month_submitted = fields.Integer(compute='_compute_this_month')
|
||||
count_month_approved = fields.Integer(compute='_compute_this_month')
|
||||
count_month_delivered = fields.Integer(compute='_compute_this_month')
|
||||
count_month_billed = fields.Integer(compute='_compute_this_month')
|
||||
|
||||
def _compute_this_month(self):
|
||||
SO = self.env['sale.order'].sudo()
|
||||
for rec in self:
|
||||
base = rec._role_filter_domain()
|
||||
ms = rec._month_start()
|
||||
rec.count_month_submitted = SO.search_count(base + [
|
||||
('x_fc_claim_submission_date', '>=', ms),
|
||||
])
|
||||
rec.count_month_approved = SO.search_count(base + [
|
||||
('x_fc_claim_approval_date', '>=', ms),
|
||||
])
|
||||
rec.count_month_delivered = SO.search_count(base + [
|
||||
('x_fc_adp_delivery_date', '>=', ms),
|
||||
])
|
||||
rec.count_month_billed = SO.search_count(base + [
|
||||
('x_fc_billing_date', '>=', ms),
|
||||
])
|
||||
|
||||
# =========================================================================
|
||||
# Pipeline $ by stage (4-up money-in-motion strip)
|
||||
# =========================================================================
|
||||
pipeline_pre_amount = fields.Monetary(compute='_compute_pipeline',
|
||||
currency_field='currency_id')
|
||||
pipeline_submitted_amount = fields.Monetary(compute='_compute_pipeline',
|
||||
currency_field='currency_id')
|
||||
pipeline_approved_amount = fields.Monetary(compute='_compute_pipeline',
|
||||
currency_field='currency_id')
|
||||
pipeline_ready_bill_amount = fields.Monetary(compute='_compute_pipeline',
|
||||
currency_field='currency_id')
|
||||
|
||||
def _compute_pipeline(self):
|
||||
SO = self.env['sale.order'].sudo()
|
||||
for rec in self:
|
||||
base = rec._role_filter_domain()
|
||||
pre = SO.search(base + [
|
||||
('x_fc_adp_application_status', 'in',
|
||||
['waiting_for_application', 'assessment_completed',
|
||||
'application_received', 'ready_submission']),
|
||||
])
|
||||
sub = SO.search(base + [
|
||||
('x_fc_adp_application_status', 'in', ['submitted', 'resubmitted']),
|
||||
])
|
||||
app = SO.search(base + [
|
||||
('x_fc_adp_application_status', 'in', ['approved', 'approved_deduction']),
|
||||
])
|
||||
bill = SO.search(base + [
|
||||
('x_fc_adp_application_status', '=', 'ready_bill'),
|
||||
])
|
||||
rec.pipeline_pre_amount = sum(pre.mapped('amount_total'))
|
||||
rec.pipeline_submitted_amount = sum(sub.mapped('amount_total'))
|
||||
rec.pipeline_approved_amount = sum(app.mapped('amount_total'))
|
||||
rec.pipeline_ready_bill_amount = sum(bill.mapped('amount_total'))
|
||||
|
||||
# =========================================================================
|
||||
# Aging buckets (disjoint: 30-59d, 60-89d, 90+d)
|
||||
# =========================================================================
|
||||
aging_30_count = fields.Integer(compute='_compute_aging')
|
||||
aging_60_count = fields.Integer(compute='_compute_aging')
|
||||
aging_90_count = fields.Integer(compute='_compute_aging')
|
||||
|
||||
def _compute_aging(self):
|
||||
from datetime import date, timedelta
|
||||
SO = self.env['sale.order'].sudo()
|
||||
today = date.today()
|
||||
cut_30 = today - timedelta(days=30)
|
||||
cut_60 = today - timedelta(days=60)
|
||||
cut_90 = today - timedelta(days=90)
|
||||
# "Active" = SO not cancelled at order level, AND if it has an ADP
|
||||
# status, it's not in a terminal ADP state.
|
||||
terminal_adp = ['case_closed', 'cancelled', 'expired', 'withdrawn']
|
||||
for rec in self:
|
||||
base = rec._role_filter_domain() + [
|
||||
('state', '!=', 'cancel'),
|
||||
'|',
|
||||
('x_fc_adp_application_status', '=', False),
|
||||
('x_fc_adp_application_status', 'not in', terminal_adp),
|
||||
]
|
||||
rec.aging_30_count = SO.search_count(base + [
|
||||
('create_date', '<', cut_30),
|
||||
('create_date', '>=', cut_60),
|
||||
])
|
||||
rec.aging_60_count = SO.search_count(base + [
|
||||
('create_date', '<', cut_60),
|
||||
('create_date', '>=', cut_90),
|
||||
])
|
||||
rec.aging_90_count = SO.search_count(base + [
|
||||
('create_date', '<', cut_90),
|
||||
])
|
||||
|
||||
# =========================================================================
|
||||
# Recent ADP Exports (last 5)
|
||||
# =========================================================================
|
||||
recent_exports_html = fields.Html(compute='_compute_recent_exports',
|
||||
sanitize=False)
|
||||
recent_exports_count = fields.Integer(compute='_compute_recent_exports')
|
||||
|
||||
def _compute_recent_exports(self):
|
||||
Exp = self.env['fusion_claims.adp.export.record'].sudo()
|
||||
for rec in self:
|
||||
records = Exp.search([], order='export_date desc', limit=5)
|
||||
rec.recent_exports_count = Exp.search_count([])
|
||||
if not records:
|
||||
rec.recent_exports_html = (
|
||||
'<p class="o_fc_empty">No exports yet.</p>'
|
||||
)
|
||||
continue
|
||||
rows = []
|
||||
for r in records:
|
||||
total = sum(r.invoice_ids.mapped('amount_total'))
|
||||
date_str = (r.export_date.strftime('%b %d, %Y')
|
||||
if r.export_date else '—')
|
||||
label = r.posting_period_label or r.name or 'Export'
|
||||
inv_count = r.invoice_count or 0
|
||||
rows.append(
|
||||
f'<div class="o_fc_export_row" '
|
||||
f'data-export-id="{r.id}">'
|
||||
f'<div class="o_fc_export_label">'
|
||||
f'<b>{label}</b>'
|
||||
f'<br/><small>{date_str} · {inv_count} inv</small>'
|
||||
f'</div>'
|
||||
f'<div class="o_fc_export_amount">${total:,.0f}</div>'
|
||||
f'</div>'
|
||||
)
|
||||
rec.recent_exports_html = '\n'.join(rows)
|
||||
|
||||
# =========================================================================
|
||||
# Open-list action methods
|
||||
# =========================================================================
|
||||
@@ -545,3 +682,85 @@ class FusionClaimsDashboard(models.TransientModel):
|
||||
def action_create_private_so(self):
|
||||
return self._create_so_action('New Private Order',
|
||||
{'default_x_fc_sale_type': 'direct_private'})
|
||||
|
||||
# =========================================================================
|
||||
# Additional drill-downs (This Month, Pipeline, Aging, Exports)
|
||||
# =========================================================================
|
||||
def action_open_month_submitted(self):
|
||||
return self._so_list_action('Submitted This Month', [
|
||||
('x_fc_claim_submission_date', '>=', self._month_start()),
|
||||
])
|
||||
|
||||
def action_open_month_approved(self):
|
||||
return self._so_list_action('Approved This Month', [
|
||||
('x_fc_claim_approval_date', '>=', self._month_start()),
|
||||
])
|
||||
|
||||
def action_open_month_delivered(self):
|
||||
return self._so_list_action('Delivered This Month', [
|
||||
('x_fc_adp_delivery_date', '>=', self._month_start()),
|
||||
])
|
||||
|
||||
def action_open_month_billed(self):
|
||||
return self._so_list_action('Billed This Month', [
|
||||
('x_fc_billing_date', '>=', self._month_start()),
|
||||
])
|
||||
|
||||
def action_open_pipeline_pre(self):
|
||||
return self._so_list_action('Pipeline — Pre-Submission', [
|
||||
('x_fc_adp_application_status', 'in',
|
||||
['waiting_for_application', 'assessment_completed',
|
||||
'application_received', 'ready_submission']),
|
||||
])
|
||||
|
||||
def action_open_pipeline_submitted(self):
|
||||
return self._so_list_action('Pipeline — Submitted to ADP', [
|
||||
('x_fc_adp_application_status', 'in', ['submitted', 'resubmitted']),
|
||||
])
|
||||
|
||||
def action_open_aging_30(self):
|
||||
from datetime import date, timedelta
|
||||
today = date.today()
|
||||
terminal_adp = ['case_closed', 'cancelled', 'expired', 'withdrawn']
|
||||
return self._so_list_action('Aging — 30 to 59 Days', [
|
||||
('state', '!=', 'cancel'),
|
||||
'|',
|
||||
('x_fc_adp_application_status', '=', False),
|
||||
('x_fc_adp_application_status', 'not in', terminal_adp),
|
||||
('create_date', '<', today - timedelta(days=30)),
|
||||
('create_date', '>=', today - timedelta(days=60)),
|
||||
])
|
||||
|
||||
def action_open_aging_60(self):
|
||||
from datetime import date, timedelta
|
||||
today = date.today()
|
||||
terminal_adp = ['case_closed', 'cancelled', 'expired', 'withdrawn']
|
||||
return self._so_list_action('Aging — 60 to 89 Days', [
|
||||
('state', '!=', 'cancel'),
|
||||
'|',
|
||||
('x_fc_adp_application_status', '=', False),
|
||||
('x_fc_adp_application_status', 'not in', terminal_adp),
|
||||
('create_date', '<', today - timedelta(days=60)),
|
||||
('create_date', '>=', today - timedelta(days=90)),
|
||||
])
|
||||
|
||||
def action_open_aging_90(self):
|
||||
from datetime import date, timedelta
|
||||
today = date.today()
|
||||
terminal_adp = ['case_closed', 'cancelled', 'expired', 'withdrawn']
|
||||
return self._so_list_action('Aging — 90+ Days', [
|
||||
('state', '!=', 'cancel'),
|
||||
'|',
|
||||
('x_fc_adp_application_status', '=', False),
|
||||
('x_fc_adp_application_status', 'not in', terminal_adp),
|
||||
('create_date', '<', today - timedelta(days=90)),
|
||||
])
|
||||
|
||||
def action_open_recent_exports(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'ADP Export History',
|
||||
'res_model': 'fusion_claims.adp.export.record',
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user