From 21f6171162a774dceedae7c3b753d6eb29ac5200 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 21:18:59 -0400 Subject: [PATCH] feat(fusion_accounting_followup): top-level followup_dashboard component Made-with: Cursor --- fusion_accounting_followup/__manifest__.py | 5 +- .../followup_dashboard/followup_dashboard.js | 69 +++++++++++++++++++ .../followup_dashboard/followup_dashboard.xml | 65 +++++++++++++++++ .../followup_dashboard_view.js | 14 ++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.js create mode 100644 fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.xml create mode 100644 fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard_view.js diff --git a/fusion_accounting_followup/__manifest__.py b/fusion_accounting_followup/__manifest__.py index fae8c64b..7e8542f8 100644 --- a/fusion_accounting_followup/__manifest__.py +++ b/fusion_accounting_followup/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Fusion Accounting Follow-up', - 'version': '19.0.1.0.20', + 'version': '19.0.1.0.21', 'category': 'Accounting/Accounting', 'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.', 'description': """ @@ -41,6 +41,9 @@ menu hides; the engine + AI tools remain available for the chat. 'fusion_accounting_followup/static/src/scss/followup.scss', 'fusion_accounting_followup/static/src/scss/dark_mode.scss', 'fusion_accounting_followup/static/src/services/followup_service.js', + 'fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.js', + 'fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.xml', + 'fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard_view.js', ], }, 'installable': True, diff --git a/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.js b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.js new file mode 100644 index 00000000..274b16be --- /dev/null +++ b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.js @@ -0,0 +1,69 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { PartnerCard } from "../../components/partner_card/partner_card"; +import { AgingBucketStrip } from "../../components/aging_bucket_strip/aging_bucket_strip"; +import { RiskBadge } from "../../components/risk_badge/risk_badge"; +import { AiTextPanel } from "../../components/ai_text_panel/ai_text_panel"; +import { FollowupHistoryTable } from "../../components/followup_history_table/followup_history_table"; + +export class FollowupDashboard extends Component { + static template = "fusion_accounting_followup.FollowupDashboard"; + static props = { "*": true }; + static components = { PartnerCard, AgingBucketStrip, RiskBadge, AiTextPanel, FollowupHistoryTable }; + + setup() { + this.followup = useService("fusion_followup"); + this.state = useState(this.followup.state); + + const companyId = this.env.services.user?.context?.allowed_company_ids?.[0]; + + onWillStart(async () => { + await this.followup.loadOverdue(companyId); + }); + } + + onSelectPartner(partnerId) { + this.followup.selectPartner(partnerId); + } + + onStatusFilter(status) { + this.followup.setStatusFilter(status || null); + } + + async onGenerateText() { + if (!this.state.selectedPartnerId) return; + await this.followup.generateText(this.state.selectedPartnerId); + } + + async onSend() { + if (!this.state.selectedPartnerId) return; + await this.followup.sendFollowup(this.state.selectedPartnerId, null, true); + } + + async onPause() { + if (!this.state.selectedPartnerId) return; + const days = parseInt(prompt("Pause for how many days?", "30")); + if (isNaN(days)) return; + const until = new Date(); + until.setDate(until.getDate() + days); + await this.followup.pausePartner( + this.state.selectedPartnerId, until.toISOString().slice(0, 10)); + } + + async onReset() { + if (!this.state.selectedPartnerId) return; + await this.followup.resetPartner(this.state.selectedPartnerId); + } + + formatCurrency(amount) { + return new Intl.NumberFormat(undefined, { + minimumFractionDigits: 2, maximumFractionDigits: 2, + }).format(amount || 0); + } + + get totalOverdue() { + return this.state.partners.reduce((sum, p) => sum + (p.overdue_amount || 0), 0); + } +} diff --git a/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.xml b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.xml new file mode 100644 index 00000000..1af84ada --- /dev/null +++ b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard.xml @@ -0,0 +1,65 @@ + + + + +
+
+
+

Customer Follow-ups

+
of partners with overdue
+
+
+
Total overdue: $
+
+
+ +
+ + + + +
+ +
+
+
Loading...
+
No overdue partners.
+
+ +
+
+
+
+

+
+ +
+
+ +
+ +
+ + + + +
+ + +
+
Select a partner.
+
+
+
+
+ +
diff --git a/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard_view.js b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard_view.js new file mode 100644 index 00000000..5e22b538 --- /dev/null +++ b/fusion_accounting_followup/static/src/views/followup_dashboard/followup_dashboard_view.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { FollowupDashboard } from "./followup_dashboard"; + +export const fusionFollowupDashboardView = { + type: "fusion_followup", + Controller: FollowupDashboard, + display_name: "Fusion Customer Follow-ups", + icon: "fa-bell", + multiRecord: true, +}; + +registry.category("views").add("fusion_followup", fusionFollowupDashboardView);