feat(fusion_accounting_assets): top-level asset_dashboard component

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 17:37:34 -04:00
parent 6051ef22a0
commit bddd22cabd
4 changed files with 121 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
{
'name': 'Fusion Accounting Assets',
'version': '19.0.1.0.20',
'version': '19.0.1.0.21',
'category': 'Accounting/Accounting',
'summary': 'AI-augmented asset management with depreciation schedules.',
'description': """
@@ -41,6 +41,9 @@ menu hides; the engine + AI tools remain available for the chat.
'fusion_accounting_assets/static/src/scss/assets.scss',
'fusion_accounting_assets/static/src/scss/dark_mode.scss',
'fusion_accounting_assets/static/src/services/assets_service.js',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard.js',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard.xml',
'fusion_accounting_assets/static/src/views/asset_dashboard/asset_dashboard_view.js',
],
},
'installable': True,

View File

@@ -0,0 +1,47 @@
/** @odoo-module **/
import { Component, useState, onWillStart } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { AssetCard } from "../../components/asset_card/asset_card";
import { AssetDetailPanel } from "../../components/asset_detail_panel/asset_detail_panel";
import { AnomalyStrip } from "../../components/anomaly_strip/anomaly_strip";
export class AssetDashboard extends Component {
static template = "fusion_accounting_assets.AssetDashboard";
static props = { "*": true };
static components = { AssetCard, AssetDetailPanel, AnomalyStrip };
setup() {
this.assets = useService("fusion_assets");
this.state = useState(this.assets.state);
const companyId = this.env.services.user?.context?.allowed_company_ids?.[0];
onWillStart(async () => {
await this.assets.loadAssets(companyId);
await this.assets.fetchAnomalies();
});
}
onSelectAsset(id) {
this.assets.selectAsset(id);
}
onStateFilter(state) {
this.assets.setStateFilter(state || null);
}
formatCurrency(amount) {
return new Intl.NumberFormat(undefined, {
minimumFractionDigits: 2, maximumFractionDigits: 2,
}).format(amount || 0);
}
get totalCost() {
return this.state.assets.reduce((sum, a) => sum + a.cost, 0);
}
get totalBookValue() {
return this.state.assets.reduce((sum, a) => sum + a.book_value, 0);
}
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_accounting_assets.AssetDashboard">
<div class="o_fusion_assets">
<div class="o_fusion_assets_header">
<div>
<h1>Asset Management</h1>
<div class="text-muted">
<t t-esc="state.count"/> of <t t-esc="state.total"/> assets
</div>
</div>
<div class="o_fusion_assets_summary">
<div>Cost: <span class="summary-value">$<t t-esc="formatCurrency(totalCost)"/></span></div>
<div>Book Value: <span class="summary-value">$<t t-esc="formatCurrency(totalBookValue)"/></span></div>
</div>
</div>
<div class="d-flex" style="gap: 0.5rem; padding: 0.75rem;">
<button class="btn_asset" t-on-click="() => onStateFilter(null)"
t-att-class="state.stateFilter === null ? 'primary' : ''">All</button>
<button class="btn_asset" t-on-click="() => onStateFilter('draft')"
t-att-class="state.stateFilter === 'draft' ? 'primary' : ''">Draft</button>
<button class="btn_asset" t-on-click="() => onStateFilter('running')"
t-att-class="state.stateFilter === 'running' ? 'primary' : ''">Running</button>
<button class="btn_asset" t-on-click="() => onStateFilter('paused')"
t-att-class="state.stateFilter === 'paused' ? 'primary' : ''">Paused</button>
<button class="btn_asset" t-on-click="() => onStateFilter('disposed')"
t-att-class="state.stateFilter === 'disposed' ? 'primary' : ''">Disposed</button>
</div>
<AnomalyStrip t-foreach="state.anomalies" t-as="anomaly"
t-key="anomaly.id" anomaly="anomaly"/>
<div class="d-flex" style="gap: 1rem; padding: 1rem;">
<div style="flex: 1 1 60%;">
<div t-if="state.isLoading" class="text-center p-4 text-muted">Loading...</div>
<div t-elif="state.assets.length === 0" class="text-center p-4 text-muted">No assets found.</div>
<div t-else="">
<AssetCard t-foreach="state.assets" t-as="asset" t-key="asset.id"
asset="asset" selected="state.selectedAssetId === asset.id"
onSelect="() => onSelectAsset(asset.id)"
formatCurrency="formatCurrency.bind(this)"/>
</div>
</div>
<div style="flex: 1 1 40%;">
<AssetDetailPanel t-if="state.selectedDetail"
detail="state.selectedDetail"
formatCurrency="formatCurrency.bind(this)"/>
<div t-else="" class="p-4 text-muted">Select an asset to see details.</div>
</div>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,14 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { AssetDashboard } from "./asset_dashboard";
export const fusionAssetDashboardView = {
type: "fusion_assets",
Controller: AssetDashboard,
display_name: "Fusion Asset Management",
icon: "fa-cubes",
multiRecord: true,
};
registry.category("views").add("fusion_assets", fusionAssetDashboardView);