feat(fusion_accounting_bank_rec): mirror Enterprise OWL batch 1 (display components)
Mirrors 4 OWL components from account_accountant for Phase 1
structural parity:
- statement_line/ (display + interactivity for one bank line)
- statement_summary/ (header summary card per statement)
- line_info_pop_over/ (popover with extra info on hover)
- reconciled_line_name/ (label for already-reconciled lines)
Plus the Enterprise-compat surface added to
fusion_bank_reconciliation service:
- useBankReconciliation() hook export
- chatterState reactive (visible, statementLine)
- reconcileCountPerPartnerId / reconcileModelPerStatementLineId
- selectStatementLine, openChatter, toggleChatter, reloadChatter
- computeReconcileLineCountPerPartnerId (no-op stub)
- computeAvailableReconcileModels (no-op stub)
- updateAvailableReconcileModels (no-op stub)
- reloadRecords helper
- statementLine{,MoveId,Move,Id} getters
Service now also depends on `orm`. A
components/bank_reconciliation/bank_reconciliation_service.js
re-export shim lets mirrored components keep their relative
`../bank_reconciliation_service` imports verbatim.
Renames applied per spec:
- account_accountant.* -> fusion_accounting_bank_rec.* (template names)
- @account_accountant/... -> @fusion_accounting_bank_rec/... (module IDs)
- useService("bank_reconciliation_service")
-> useService("fusion_bank_reconciliation")
Forward imports to batch 2 components (button_list,
line_to_reconcile) resolve lazily — files are on disk and bundled
in subsequent batches. Phase 1 prioritizes structural parity;
behaviour wired up in fusion-only Tasks 34-36.
Manifest version bumped to 19.0.1.0.12.
Module upgrade succeeds, 134 logical tests still pass.
Made-with: Cursor
This commit is contained in:
@@ -11,7 +11,9 @@
|
||||
*/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { reactive, useState, EventBus } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
const ENDPOINT_BASE = "/fusion/bank_rec";
|
||||
|
||||
@@ -20,6 +22,22 @@ export class BankReconciliationService {
|
||||
this.env = env;
|
||||
this.rpc = services.rpc;
|
||||
this.notification = services.notification;
|
||||
this.orm = services.orm;
|
||||
|
||||
// ============================================================
|
||||
// Enterprise-compat surface (mirrored OWL components rely on this)
|
||||
// ============================================================
|
||||
// Mirrored components from account_accountant expect these
|
||||
// attributes/methods on the service. Most are implemented as
|
||||
// stubs that no-op or return sensible defaults; structural
|
||||
// parity now, behaviour wired up in fusion-only Tasks 34-36.
|
||||
this.bus = new EventBus();
|
||||
this.chatterState = reactive({
|
||||
visible: this._readChatterPref(),
|
||||
statementLine: null,
|
||||
});
|
||||
this.reconcileCountPerPartnerId = reactive({});
|
||||
this.reconcileModelPerStatementLineId = reactive({});
|
||||
|
||||
// Reactive state — components depend on it via useState/reactive
|
||||
this.state = reactive({
|
||||
@@ -265,13 +283,138 @@ export class BankReconciliationService {
|
||||
getBandClass(line) {
|
||||
return `band-${line.fusion_confidence_band || "none"}`;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Enterprise-compat methods (stubs — wired up later)
|
||||
// ============================================================
|
||||
// The following surface is required by mirrored components from
|
||||
// account_accountant. They are primarily no-ops or thin wrappers
|
||||
// around the legacy/V19 ORM. Phase 1 prioritizes structural parity;
|
||||
// fusion-only Tasks 34-36 will replace these with native
|
||||
// implementations driven by our JSON-RPC endpoints.
|
||||
|
||||
_readChatterPref() {
|
||||
try {
|
||||
return (
|
||||
JSON.parse(
|
||||
browser.sessionStorage.getItem("isFusionBankRecChatterOpened")
|
||||
) ?? false
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
toggleChatter() {
|
||||
this.chatterState.visible = !this.chatterState.visible;
|
||||
try {
|
||||
browser.sessionStorage.setItem(
|
||||
"isFusionBankRecChatterOpened",
|
||||
this.chatterState.visible
|
||||
);
|
||||
} catch {
|
||||
// Session storage unavailable — non-fatal.
|
||||
}
|
||||
}
|
||||
|
||||
openChatter() {
|
||||
this.chatterState.visible = true;
|
||||
}
|
||||
|
||||
selectStatementLine(statementLine) {
|
||||
this.chatterState.statementLine = statementLine;
|
||||
}
|
||||
|
||||
reloadChatter() {
|
||||
this.bus.trigger("MAIL:RELOAD-THREAD", {
|
||||
model: "account.move",
|
||||
id: this.statementLineMoveId,
|
||||
});
|
||||
}
|
||||
|
||||
async computeReconcileLineCountPerPartnerId(records) {
|
||||
// Stub: real impl to be added in fusion-only task.
|
||||
// Components call this after partner edits to refresh the per-partner
|
||||
// count badge. Returning empty here keeps the badge silent.
|
||||
if (!this.orm) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const partnerIds = (records || [])
|
||||
.map((r) => r?.data?.partner_id?.id)
|
||||
.filter(Boolean);
|
||||
if (!partnerIds.length) {
|
||||
this.reconcileCountPerPartnerId = {};
|
||||
return;
|
||||
}
|
||||
// Best-effort: keep a zero map so templates don't blow up.
|
||||
const out = {};
|
||||
for (const pid of partnerIds) {
|
||||
out[pid] = this.reconcileCountPerPartnerId[pid] ?? 0;
|
||||
}
|
||||
this.reconcileCountPerPartnerId = out;
|
||||
} catch {
|
||||
// Non-fatal; templates fall back to defaults.
|
||||
}
|
||||
}
|
||||
|
||||
async computeAvailableReconcileModels(records) {
|
||||
// Stub: components show these as quick-action buttons. Empty for now.
|
||||
const out = {};
|
||||
for (const r of records || []) {
|
||||
const id = r?.data?.id;
|
||||
if (id) {
|
||||
out[id] = [];
|
||||
}
|
||||
}
|
||||
this.reconcileModelPerStatementLineId = out;
|
||||
}
|
||||
|
||||
async updateAvailableReconcileModels(recordId) {
|
||||
if (recordId) {
|
||||
this.reconcileModelPerStatementLineId[recordId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
async reloadRecords(records) {
|
||||
await Promise.all(
|
||||
(records || []).map((record) => record?.load ? record.load() : null)
|
||||
);
|
||||
}
|
||||
|
||||
get statementLineMove() {
|
||||
return this.chatterState.statementLine?.data?.move_id;
|
||||
}
|
||||
|
||||
get statementLineMoveId() {
|
||||
return this.statementLineMove?.id;
|
||||
}
|
||||
|
||||
get statementLine() {
|
||||
return this.chatterState.statementLine;
|
||||
}
|
||||
|
||||
get statementLineId() {
|
||||
return this.statementLine?.data?.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const bankReconciliationService = {
|
||||
dependencies: ["rpc", "notification"],
|
||||
dependencies: ["rpc", "notification", "orm"],
|
||||
start(env, services) {
|
||||
return new BankReconciliationService(env, services);
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("fusion_bank_reconciliation", bankReconciliationService);
|
||||
|
||||
/**
|
||||
* Hook for OWL components mirrored from Enterprise.
|
||||
*
|
||||
* Enterprise's components import `useBankReconciliation` from
|
||||
* `../bank_reconciliation_service`; we expose the same hook here so
|
||||
* mirrored code works unmodified after the relative-import rewrite.
|
||||
*/
|
||||
export function useBankReconciliation() {
|
||||
return useState(useService("fusion_bank_reconciliation"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user