feat(fusion_accounting_reports): reports_service.js reactive frontend service

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 16:00:29 -04:00
parent 1f94927f12
commit 1ffa86b532
2 changed files with 149 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
{
'name': 'Fusion Accounting Reports',
'version': '19.0.1.0.22',
'version': '19.0.1.0.23',
'category': 'Accounting/Accounting',
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
'description': """
@@ -42,6 +42,7 @@ menu hides; the engine and AI tools remain available for the chat.
'fusion_accounting_reports/static/src/scss/_variables.scss',
'fusion_accounting_reports/static/src/scss/reports.scss',
'fusion_accounting_reports/static/src/scss/dark_mode.scss',
'fusion_accounting_reports/static/src/services/reports_service.js',
],
},
'installable': True,

View File

@@ -0,0 +1,147 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { reactive } from "@odoo/owl";
const ENDPOINT_BASE = "/fusion/reports";
export class ReportsService {
constructor(env, services) {
this.env = env;
this.rpc = services.rpc;
this.notification = services.notification;
this.state = reactive({
availableReports: [],
currentReportType: null,
currentResult: null,
currentAnomalies: [],
currentCommentary: null,
isLoading: false,
isGeneratingCommentary: false,
dateFrom: null,
dateTo: null,
comparison: 'none',
companyId: null,
drillDown: null,
});
}
async loadAvailableReports(companyId = null) {
this.state.companyId = companyId;
this.state.isLoading = true;
try {
const result = await this.rpc(`${ENDPOINT_BASE}/list_available`,
{ company_id: companyId });
this.state.availableReports = result.reports;
} finally {
this.state.isLoading = false;
}
}
async runReport(reportType, dateFrom, dateTo, comparison = 'none') {
this.state.isLoading = true;
this.state.currentReportType = reportType;
this.state.dateFrom = dateFrom;
this.state.dateTo = dateTo;
this.state.comparison = comparison;
try {
this.state.currentResult = await this.rpc(`${ENDPOINT_BASE}/run`, {
report_type: reportType,
date_from: dateFrom,
date_to: dateTo,
comparison: comparison,
company_id: this.state.companyId,
});
if (comparison && comparison !== 'none') {
this.fetchAnomalies();
} else {
this.state.currentAnomalies = [];
}
this.state.currentCommentary = null;
return this.state.currentResult;
} catch (err) {
this.notification.add(`Run failed: ${err.message || err}`, { type: 'danger' });
throw err;
} finally {
this.state.isLoading = false;
}
}
async fetchAnomalies() {
if (!this.state.currentReportType) return;
try {
const result = await this.rpc(`${ENDPOINT_BASE}/get_anomalies`, {
report_type: this.state.currentReportType,
date_from: this.state.dateFrom,
date_to: this.state.dateTo,
comparison: this.state.comparison,
company_id: this.state.companyId,
});
this.state.currentAnomalies = result.anomalies || [];
} catch (err) {
this.state.currentAnomalies = [];
}
}
async generateCommentary({ forceRegenerate = false } = {}) {
if (!this.state.currentReportType) return;
this.state.isGeneratingCommentary = true;
try {
this.state.currentCommentary = await this.rpc(`${ENDPOINT_BASE}/get_commentary`, {
report_type: this.state.currentReportType,
date_from: this.state.dateFrom,
date_to: this.state.dateTo,
comparison: this.state.comparison,
company_id: this.state.companyId,
force_regenerate: forceRegenerate,
});
return this.state.currentCommentary;
} catch (err) {
this.notification.add(`Commentary failed: ${err.message || err}`, { type: 'danger' });
throw err;
} finally {
this.state.isGeneratingCommentary = false;
}
}
async drillDown(accountId, label = null) {
try {
const result = await this.rpc(`${ENDPOINT_BASE}/drill_down`, {
account_id: accountId,
date_from: this.state.dateFrom,
date_to: this.state.dateTo,
company_id: this.state.companyId,
});
this.state.drillDown = {
accountId, label, rows: result.rows || [],
count: result.count, isOpen: true,
};
return result;
} catch (err) {
this.notification.add(`Drill failed: ${err.message || err}`, { type: 'danger' });
throw err;
}
}
closeDrillDown() {
if (this.state.drillDown) {
this.state.drillDown.isOpen = false;
}
}
setComparison(mode) {
this.state.comparison = mode;
if (this.state.currentReportType) {
return this.runReport(this.state.currentReportType,
this.state.dateFrom, this.state.dateTo, mode);
}
}
}
export const reportsService = {
dependencies: ["rpc", "notification"],
start(env, services) { return new ReportsService(env, services); },
};
registry.category("services").add("fusion_reports", reportsService);