V19 removed the 'rpc' service from the registry. All 4 fusion services
(bank_reconciliation, reports, assets, followup) declared dependencies:
['rpc', ...] and accessed services.rpc in their constructor. At runtime
this caused:
Error: Some services could not be started: fusion_bank_reconciliation,
fusion_reports, fusion_assets, fusion_followup. Missing dependencies: rpc
\u2014 which prevented the entire OWL backend from booting (blank screen).
Fix per V19 docs:
- Add 'import { rpc } from "@web/core/network/rpc";'
- Set 'this.rpc = rpc;' in constructor (instead of services.rpc)
- Remove 'rpc' from dependencies list
This is the workspace CLAUDE.md guidance Phase 4's subagent flagged
but didn't act on for backward consistency. V19 actually removed the
service entirely, so the consistency choice was wrong \u2014 fixing now.
All call sites still use this.rpc(...) so no per-method changes needed.
Bundle rebuilt clean; backend boots correctly.
Made-with: Cursor
152 lines
5.1 KiB
JavaScript
152 lines
5.1 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { registry } from "@web/core/registry";
|
|
import { reactive } from "@odoo/owl";
|
|
import { rpc } from "@web/core/network/rpc";
|
|
|
|
const ENDPOINT_BASE = "/fusion/assets";
|
|
|
|
export class AssetsService {
|
|
constructor(env, services) {
|
|
this.env = env;
|
|
// V19: rpc is a standalone import, not a service.
|
|
this.rpc = 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: ["notification"],
|
|
start(env, services) { return new AssetsService(env, services); },
|
|
};
|
|
|
|
registry.category("services").add("fusion_assets", assetsService);
|