feat(fusion_accounting_assets): assets_service.js reactive frontend service
Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting Assets',
|
||||
'version': '19.0.1.0.19',
|
||||
'version': '19.0.1.0.20',
|
||||
'category': 'Accounting/Accounting',
|
||||
'summary': 'AI-augmented asset management with depreciation schedules.',
|
||||
'description': """
|
||||
@@ -40,6 +40,7 @@ menu hides; the engine + AI tools remain available for the chat.
|
||||
'fusion_accounting_assets/static/src/scss/_variables.scss',
|
||||
'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',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
|
||||
149
fusion_accounting_assets/static/src/services/assets_service.js
Normal file
149
fusion_accounting_assets/static/src/services/assets_service.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { reactive } from "@odoo/owl";
|
||||
|
||||
const ENDPOINT_BASE = "/fusion/assets";
|
||||
|
||||
export class AssetsService {
|
||||
constructor(env, services) {
|
||||
this.env = env;
|
||||
this.rpc = services.rpc;
|
||||
this.notification = services.notification;
|
||||
|
||||
this.state = reactive({
|
||||
assets: [],
|
||||
count: 0,
|
||||
total: 0,
|
||||
stateFilter: null,
|
||||
categoryFilter: null,
|
||||
isLoading: false,
|
||||
isProcessing: false,
|
||||
selectedAssetId: null,
|
||||
selectedDetail: null,
|
||||
companyId: null,
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
anomalies: [],
|
||||
});
|
||||
}
|
||||
|
||||
async loadAssets(companyId = null) {
|
||||
this.state.companyId = companyId;
|
||||
this.state.isLoading = true;
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/list`, {
|
||||
state: this.state.stateFilter,
|
||||
category_id: this.state.categoryFilter,
|
||||
limit: this.state.limit,
|
||||
offset: this.state.offset,
|
||||
company_id: companyId,
|
||||
});
|
||||
this.state.assets = result.assets;
|
||||
this.state.count = result.count;
|
||||
this.state.total = result.total;
|
||||
} finally {
|
||||
this.state.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async selectAsset(assetId) {
|
||||
this.state.selectedAssetId = assetId;
|
||||
this.state.selectedDetail = null;
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/get_detail`, {
|
||||
asset_id: assetId,
|
||||
});
|
||||
this.state.selectedDetail = result;
|
||||
} catch (err) {
|
||||
this.notification.add(`Failed to load asset detail: ${err.message || err}`, { type: "danger" });
|
||||
}
|
||||
}
|
||||
|
||||
async computeSchedule(assetId, recompute = false) {
|
||||
this.state.isProcessing = true;
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/compute_schedule`, {
|
||||
asset_id: assetId, recompute: recompute,
|
||||
});
|
||||
this.notification.add(`Schedule computed (${result.lines_created} lines)`, { type: "success" });
|
||||
if (this.state.selectedAssetId === assetId) {
|
||||
await this.selectAsset(assetId);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.notification.add(`Compute failed: ${err.message || err}`, { type: "danger" });
|
||||
throw err;
|
||||
} finally {
|
||||
this.state.isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async postDepreciation(assetId) {
|
||||
this.state.isProcessing = true;
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/post_depreciation`, {
|
||||
asset_id: assetId,
|
||||
});
|
||||
this.notification.add(`Posted ${result.posted_count} period(s)`, { type: "success" });
|
||||
if (this.state.selectedAssetId === assetId) {
|
||||
await this.selectAsset(assetId);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.notification.add(`Post failed: ${err.message || err}`, { type: "danger" });
|
||||
throw err;
|
||||
} finally {
|
||||
this.state.isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async disposeAsset(assetId, { saleAmount = 0, saleDate = null, salePartnerId = null, disposalType = "sale" } = {}) {
|
||||
this.state.isProcessing = true;
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/dispose`, {
|
||||
asset_id: assetId, sale_amount: saleAmount,
|
||||
sale_date: saleDate, sale_partner_id: salePartnerId,
|
||||
disposal_type: disposalType,
|
||||
});
|
||||
this.notification.add(`Asset disposed: gain/loss $${result.gain_loss_amount.toFixed(2)}`, { type: "success" });
|
||||
await this.loadAssets(this.state.companyId);
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.notification.add(`Dispose failed: ${err.message || err}`, { type: "danger" });
|
||||
throw err;
|
||||
} finally {
|
||||
this.state.isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAnomalies(severity = null) {
|
||||
try {
|
||||
const result = await this.rpc(`${ENDPOINT_BASE}/get_anomalies`, {
|
||||
severity: severity, company_id: this.state.companyId,
|
||||
});
|
||||
this.state.anomalies = result.anomalies || [];
|
||||
} catch (err) {
|
||||
this.state.anomalies = [];
|
||||
}
|
||||
}
|
||||
|
||||
async suggestUsefulLife(description, amount = null, partnerName = null) {
|
||||
return await this.rpc(`${ENDPOINT_BASE}/suggest_useful_life`, {
|
||||
description: description, amount: amount, partner_name: partnerName,
|
||||
});
|
||||
}
|
||||
|
||||
setStateFilter(state) {
|
||||
this.state.stateFilter = state;
|
||||
this.state.offset = 0;
|
||||
this.loadAssets(this.state.companyId);
|
||||
}
|
||||
}
|
||||
|
||||
export const assetsService = {
|
||||
dependencies: ["rpc", "notification"],
|
||||
start(env, services) { return new AssetsService(env, services); },
|
||||
};
|
||||
|
||||
registry.category("services").add("fusion_assets", assetsService);
|
||||
Reference in New Issue
Block a user