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
148 lines
5.0 KiB
JavaScript
148 lines
5.0 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/followup";
|
|
|
|
export class FollowupService {
|
|
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({
|
|
partners: [],
|
|
count: 0,
|
|
total: 0,
|
|
statusFilter: null,
|
|
isLoading: false,
|
|
isProcessing: false,
|
|
selectedPartnerId: null,
|
|
selectedDetail: null,
|
|
companyId: null,
|
|
limit: 50,
|
|
offset: 0,
|
|
generatedText: null,
|
|
});
|
|
}
|
|
|
|
async loadOverdue(companyId = null) {
|
|
this.state.companyId = companyId;
|
|
this.state.isLoading = true;
|
|
try {
|
|
const result = await this.rpc(`${ENDPOINT_BASE}/list_overdue`, {
|
|
status: this.state.statusFilter,
|
|
limit: this.state.limit,
|
|
offset: this.state.offset,
|
|
company_id: companyId,
|
|
});
|
|
this.state.partners = result.partners;
|
|
this.state.count = result.count;
|
|
this.state.total = result.total;
|
|
} finally {
|
|
this.state.isLoading = false;
|
|
}
|
|
}
|
|
|
|
async selectPartner(partnerId) {
|
|
this.state.selectedPartnerId = partnerId;
|
|
this.state.selectedDetail = null;
|
|
this.state.generatedText = null;
|
|
try {
|
|
this.state.selectedDetail = await this.rpc(`${ENDPOINT_BASE}/get_partner_detail`, {
|
|
partner_id: partnerId,
|
|
});
|
|
} catch (err) {
|
|
this.notification.add(`Failed to load partner detail: ${err.message || err}`, { type: "danger" });
|
|
}
|
|
}
|
|
|
|
async generateText(partnerId, levelId = null, forceRegenerate = false) {
|
|
this.state.isProcessing = true;
|
|
try {
|
|
this.state.generatedText = await this.rpc(`${ENDPOINT_BASE}/generate_text`, {
|
|
partner_id: partnerId, level_id: levelId,
|
|
force_regenerate: forceRegenerate,
|
|
});
|
|
return this.state.generatedText;
|
|
} catch (err) {
|
|
this.notification.add(`Generate failed: ${err.message || err}`, { type: "danger" });
|
|
throw err;
|
|
} finally {
|
|
this.state.isProcessing = false;
|
|
}
|
|
}
|
|
|
|
async sendFollowup(partnerId, levelId = null, force = false) {
|
|
this.state.isProcessing = true;
|
|
try {
|
|
const result = await this.rpc(`${ENDPOINT_BASE}/send`, {
|
|
partner_id: partnerId, level_id: levelId, force: force,
|
|
});
|
|
const status = result.status || "unknown";
|
|
const type = status === "sent" ? "success" : status.startsWith("paused") ? "warning" : "info";
|
|
this.notification.add(`Send result: ${status}`, { type: type });
|
|
if (this.state.selectedPartnerId === partnerId) {
|
|
await this.selectPartner(partnerId);
|
|
}
|
|
await this.loadOverdue(this.state.companyId);
|
|
return result;
|
|
} catch (err) {
|
|
this.notification.add(`Send failed: ${err.message || err}`, { type: "danger" });
|
|
throw err;
|
|
} finally {
|
|
this.state.isProcessing = false;
|
|
}
|
|
}
|
|
|
|
async pausePartner(partnerId, untilDate = null) {
|
|
try {
|
|
const result = await this.rpc(`${ENDPOINT_BASE}/pause`, {
|
|
partner_id: partnerId, until_date: untilDate,
|
|
});
|
|
this.notification.add(`Paused until ${result.paused_until}`, { type: "info" });
|
|
if (this.state.selectedPartnerId === partnerId) {
|
|
await this.selectPartner(partnerId);
|
|
}
|
|
await this.loadOverdue(this.state.companyId);
|
|
return result;
|
|
} catch (err) {
|
|
this.notification.add(`Pause failed: ${err.message || err}`, { type: "danger" });
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async resetPartner(partnerId) {
|
|
try {
|
|
const result = await this.rpc(`${ENDPOINT_BASE}/reset`, {
|
|
partner_id: partnerId,
|
|
});
|
|
this.notification.add(`Reset`, { type: "info" });
|
|
if (this.state.selectedPartnerId === partnerId) {
|
|
await this.selectPartner(partnerId);
|
|
}
|
|
await this.loadOverdue(this.state.companyId);
|
|
return result;
|
|
} catch (err) {
|
|
this.notification.add(`Reset failed: ${err.message || err}`, { type: "danger" });
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
setStatusFilter(status) {
|
|
this.state.statusFilter = status;
|
|
this.state.offset = 0;
|
|
this.loadOverdue(this.state.companyId);
|
|
}
|
|
}
|
|
|
|
export const followupService = {
|
|
dependencies: ["notification"],
|
|
start(env, services) { return new FollowupService(env, services); },
|
|
};
|
|
|
|
registry.category("services").add("fusion_followup", followupService);
|