diff --git a/fusion_claims/__manifest__.py b/fusion_claims/__manifest__.py index f873963f..12201f20 100644 --- a/fusion_claims/__manifest__.py +++ b/fusion_claims/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Claims', - 'version': '19.0.9.0.1', + 'version': '19.0.9.1.0', 'category': 'Sales', 'summary': 'Complete ADP Claims Management with Dashboard, Sales Integration, Billing Automation, and Two-Stage Verification.', 'description': """ diff --git a/fusion_claims/models/dashboard.py b/fusion_claims/models/dashboard.py index 0db3f61a..1e36d9d6 100644 --- a/fusion_claims/models/dashboard.py +++ b/fusion_claims/models/dashboard.py @@ -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 = ( + '
No exports yet.
' + ) + 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'