feat(fusion_accounting_followup): partner_card + aging_bucket_strip + risk_badge components

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 21:19:52 -04:00
parent 21f6171162
commit da746698c5
7 changed files with 118 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
{
'name': 'Fusion Accounting Follow-up',
'version': '19.0.1.0.21',
'version': '19.0.1.0.22',
'category': 'Accounting/Accounting',
'summary': 'AI-augmented customer follow-ups (dunning) for unpaid invoices.',
'description': """
@@ -44,6 +44,12 @@ menu hides; the engine + AI tools remain available for the chat.
'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',
'fusion_accounting_followup/static/src/components/risk_badge/risk_badge.js',
'fusion_accounting_followup/static/src/components/risk_badge/risk_badge.xml',
'fusion_accounting_followup/static/src/components/partner_card/partner_card.js',
'fusion_accounting_followup/static/src/components/partner_card/partner_card.xml',
'fusion_accounting_followup/static/src/components/aging_bucket_strip/aging_bucket_strip.js',
'fusion_accounting_followup/static/src/components/aging_bucket_strip/aging_bucket_strip.xml',
],
},
'installable': True,

View File

@@ -0,0 +1,15 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class AgingBucketStrip extends Component {
static template = "fusion_accounting_followup.AgingBucketStrip";
static props = {
aging: { type: Object },
};
bucketWidth(bucket) {
const total = this.props.aging.total_amount || 1;
return ((bucket.amount / total) * 100).toFixed(2) + "%";
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_accounting_followup.AgingBucketStrip">
<div class="mt-2">
<div class="fu-aging-strip">
<div t-foreach="props.aging.buckets" t-as="b" t-key="b.name"
class="bucket" t-att-data-name="b.name"
t-att-style="'width: ' + bucketWidth(b)"
t-att-title="b.name + ': $' + (b.amount or 0).toFixed(2)"/>
</div>
<div class="d-flex justify-content-between text-muted" style="font-size: 0.7rem;">
<span>Current</span>
<span>30</span>
<span>60</span>
<span>90</span>
<span>120+</span>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,15 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { RiskBadge } from "../risk_badge/risk_badge";
export class PartnerCard extends Component {
static template = "fusion_accounting_followup.PartnerCard";
static props = {
partner: { type: Object },
selected: { type: Boolean, optional: true },
onSelect: { type: Function },
formatCurrency: { type: Function },
};
static components = { RiskBadge };
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_accounting_followup.PartnerCard">
<div class="o_fusion_followup_card"
t-att-class="props.selected ? 'selected' : ''"
t-on-click="props.onSelect">
<div class="o_fusion_followup_card_header">
<div class="partner-name"><t t-esc="props.partner.partner_name"/></div>
<div>
<span class="fu-status-badge" t-att-data-status="props.partner.status">
<t t-esc="props.partner.status"/>
</span>
</div>
</div>
<div class="partner-numbers">
<div>
<span class="label">Overdue:</span>
<span class="value">$<t t-esc="props.formatCurrency(props.partner.overdue_amount)"/></span>
</div>
<div>
<span class="label">Lines:</span>
<span class="value"><t t-esc="props.partner.overdue_line_count or 0"/></span>
</div>
<div>
<span class="label">Risk:</span>
<RiskBadge band="props.partner.risk_band" score="props.partner.risk_score"/>
</div>
<div t-if="props.partner.last_level_name">
<span class="label">Last:</span>
<span class="value"><t t-esc="props.partner.last_level_name"/></span>
</div>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,11 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class RiskBadge extends Component {
static template = "fusion_accounting_followup.RiskBadge";
static props = {
band: { type: String, optional: true },
score: { type: Number, optional: true },
};
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_accounting_followup.RiskBadge">
<span class="fu-risk-badge" t-att-data-band="props.band || 'low'">
<t t-esc="props.band || 'low'"/>
<t t-if="props.score !== undefined"> (<t t-esc="props.score"/>)</t>
</span>
</t>
</templates>