Commit Graph

53 Commits

Author SHA1 Message Date
gsinghpal
41d0908ade changes 2026-04-24 21:04:38 -04:00
gsinghpal
8a841e7534 changes 2026-04-20 13:07:15 -04:00
gsinghpal
867b5f71a1 fix(fusion_accounting): unified Accounting menu under one root, hide migration when Enterprise gone
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
User reported two UX problems after the Enterprise uninstall:
1. Each Fusion sub-module showed up as its own standalone app in the
   launcher (Bank Reconciliation, Financial Reports, Asset Management,
   Customer Follow-ups, Fusion AI). Should look like ONE Accounting app.
2. Clicking the 'Fusion Accounting' app still opened the migration
   wizard even though Enterprise had been uninstalled and there was
   nothing to migrate.

Fix:
- Move all Fusion sub-module roots under the Community account.menu_finance
  hierarchy:
    * Bank Reconciliation \u2192 Accounting > Bank Reconciliation
    * Asset Management    \u2192 Accounting > Asset Management
    * Financial Reports   \u2192 Reporting > Financial Reports
    * Follow-ups          \u2192 Customers > Follow-ups
    * Fusion AI           \u2192 Configuration > Fusion AI
    * Migrate from Ent.   \u2192 Configuration > Migrate from Enterprise
- Rename Community's 'Invoicing' top-level menu to 'Accounting' (what
  Enterprise's accountant module did). Set the Fusion icon on it. This
  rename lives in the meta-module so it only fires when the full suite
  is installed.
- Add second computed group 'group_fusion_show_when_enterprise_present'
  (inverse of the existing 'absent' group). Migration menus are gated
  by this group, so they auto-hide once Enterprise is uninstalled.
- _fusion_recompute_coexistence_group now maintains both groups in lockstep.
- Meta-module now also depends on l10n_ca, hr_payroll, ocr, documents
  (the Phase 6/7 sub-modules) for one-click full-suite install.
- Fusion AI menu's old parent ('accountant.menu_accounting') was deleted
  with the Enterprise uninstall \u2014 reparented under Configuration.

Result: single 'Accounting' top-level menu containing the standard
V19 Community structure (Dashboard / Customers / Vendors / Accounting /
Reporting / Configuration), with all Fusion features slotted into the
appropriate sub-section. Verified live on westin-v19: 6 separate
Fusion top-level menus collapsed to 1; coexistence groups recomputed
(absent=10 users, present=0 users); 604/604 tests pass.

Version bump: all touched modules \u2192 19.0.1.1.0.

Made-with: Cursor
2026-04-20 01:04:49 -04:00
gsinghpal
068a654c2b fix(fusion_accounting_bank_rec): test factory adapts to V19 Community semantics
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
After Enterprise's account_accountant is uninstalled,
account.bank.statement.journal_id reverts to its V19 Community definition
\u2014 a read-only computed field derived from line_ids.journal_id. Direct
writes are silently dropped (which is what was happening: 55 tests
errored with 'null value in column journal_id' because the test's
statement had no journal, and the line factory was reading
statement.journal_id (False) and passing that to the line create).

Fix:
- make_bank_statement now bootstraps the statement with one zero-amount
  line carrying journal_id, so the computed journal_id resolves correctly.
- make_bank_line no longer routes journal through the statement \u2014
  journal_id is set directly on the line (which is V19 Community's
  intended path; lines can exist standalone without a statement).

This is a test-only change; runtime behaviour is unchanged. Real users
creating bank lines via the UI already use the correct path.

Made-with: Cursor
2026-04-20 00:52:02 -04:00
gsinghpal
9d8db0f9b1 fix(bank_rec): don't shadow Odoo's _() translation function in action_run_migration
Line 77 was `_ = super().action_run_migration()`, using `_` as a
throwaway variable name. That rebinds the module-level `_` (Odoo's
translation function imported at the top) to whatever super() returns
\u2014 in our case the parent's notification dict.

Lines 84/85 then call `_('Bank-Rec Migration Complete')` which is
now `some_dict('Bank-Rec Migration Complete')` \u2192
TypeError: 'dict' object is not callable.

User hit this when running the migration wizard from the menu.

Fix: drop the assignment; we don't actually use super()'s return value.
Made-with: Cursor
2026-04-19 23:34:45 -04:00
gsinghpal
ef2ccb89cf fix(services): V19 removed 'rpc' service \u2014 import standalone rpc() function
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
2026-04-19 23:25:52 -04:00
gsinghpal
51d8ce494d fix(scss): remove forbidden @import "variables" lines breaking V19 asset bundle
Phases 1-3's SCSS files used '@import "variables";' to pull in tokens
from _variables.scss. V19's odoo.addons.base.models.assetsbundle
forbids cross-file SCSS imports for security ('Local import forbidden')
and the asset bundle warning was firing on every web request.

Phase 4 caught + fixed this for fusion_accounting_followup; Phases 1-3
were never updated. Today's deployment surfaced the CSS error reported
by the user.

Resolution:
- Removed @import lines from 7 SCSS files across bank_rec, reports, assets
- Variables come from _variables.scss via manifest concatenation order
  (bundle order is _variables.scss first, then dependent files)
- Replaced documentation comments to NOT contain the literal string
  '@import "variables"' \u2014 Odoo's check is regex-based and was
  matching even SCSS comments

Verified clean: bundle rebuilds with zero 'Local import forbidden'
warnings; all 534 fusion-module tests still pass.

Made-with: Cursor
2026-04-19 21:57:22 -04:00
gsinghpal
6e53955e9c docs(fusion_accounting_bank_rec): CLAUDE.md, UPGRADE_NOTES.md, README.md
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
Made-with: Cursor
2026-04-19 14:05:49 -04:00
gsinghpal
14e59148c6 test(fusion_accounting_bank_rec): local LLM (LM Studio/Ollama) compat smoke
Tagged 'local_llm'. Auto-detects LM Studio (:1234) or Ollama (:11434)
via host.docker.internal or localhost. When running, configures the
provider params and runs engine.suggest_matches end-to-end. Skips
gracefully when no local LLM is present (CI / dev VM mode).

Made-with: Cursor
2026-04-19 14:01:58 -04:00
gsinghpal
55eb368195 test(fusion_accounting_bank_rec): performance benchmarks with P95 targets
Tagged 'benchmark' so they can be selected explicitly. Targets:
suggest_matches <500ms, reconcile_batch(50) <5s, list_unreconciled <200ms,
MV refresh <2s. Hard-fail at 5x budget to catch egregious regressions.

Measured on local dev VM:
- suggest_matches: median=221ms p95=234ms (target <500ms)
- reconcile_batch(50 lines): 3318ms (target <5000ms)
- list_unreconciled: median=14ms p95=77ms (target <200ms)
- MV refresh: 60ms (target <2000ms)

Made-with: Cursor
2026-04-19 14:00:15 -04:00
gsinghpal
d623b67157 test(fusion_accounting_bank_rec): 5 OWL tour tests for widget smoke
Tours: smoke (header loads), select_line, accept_suggestion (skipped
in CI without AI config), auto_reconcile_wizard, load_more. Each
tour scripts a typical user interaction; the Python wrappers run them
via HttpCase.start_tour. Tagged 'tour' so they can be excluded from
fast unit-test runs and selected when full browser infra is available.

Made-with: Cursor
2026-04-19 13:47:23 -04:00
gsinghpal
aaaf49989c test(fusion_accounting_bank_rec): coexistence behavior
Verifies that the coexistence group recompute method works as expected
in both Enterprise-present and Enterprise-absent scenarios, and that
the bank-rec menu is gated by the group while the engine itself is
always available.

Made-with: Cursor
2026-04-19 13:45:39 -04:00
gsinghpal
878c013902 feat(fusion_accounting_bank_rec): top-level menu + window action
Menu visible only when fusion_accounting_core.group_fusion_show_when_enterprise_absent
is set (Enterprise's account_accountant not installed). Opens the OWL
bank-rec kanban widget at the unreconciled-lines view.

Made-with: Cursor
2026-04-19 13:37:16 -04:00
gsinghpal
ffc029a875 test(fusion_accounting_bank_rec): migration round-trip for bootstrap step
Verifies the bank_rec_bootstrap migration step (a) creates precedents
from existing partial.reconcile rows, (b) is idempotent on re-run, and
(c) refreshes the MV without erroring.

Three TransactionCase tests:
- test_bootstrap_creates_precedents_from_existing_reconciles seeds two
  reconciles via the engine, wipes the auto-recorded precedents, then
  asserts the bootstrap produces source='backfill' precedents.
- test_bootstrap_step_idempotent runs the bootstrap twice and asserts
  the second pass creates zero new precedents.
- test_bootstrap_refreshes_mv_without_error runs the bootstrap on a
  clean partner and asserts no exception is raised and the result dict
  reports MV + pattern refresh outcomes.

Implementation fixes uncovered by these tests:
- precedent_backfill.backfill_precedents now pre-filters
  account.partial.reconcile to rows that touch a bank statement line on
  either side. Previously it walked every partial in the DB; on the
  westin-v19 dev DB that's 16k rows and the default limit=10000 missed
  the newest test fixtures (highest IDs).
- backfill skips the periodic env.cr.commit() when running under a
  TestCursor, since committing inside a test breaks the rollback.

Test count: 139 -> 142.

Made-with: Cursor
2026-04-19 13:33:29 -04:00
gsinghpal
6048df0645 feat(fusion_accounting_bank_rec): migration audit PDF report
QWeb PDF showing per-company: backfilled precedent count, pattern count,
remaining unreconciled bank line count. Bound to fusion.migration.wizard
so it appears in the Print menu after migration runs.

- reports/migration_audit_report.py defines the AbstractModel
  report.fusion_accounting_bank_rec.migration_audit_template, which
  aggregates per-company counts from fusion.reconcile.precedent
  (source='backfill'), fusion.reconcile.pattern, and
  account.bank.statement.line (is_reconciled=False).
- reports/migration_audit_report_views.xml is the QWeb template.
- reports/migration_audit_report_action.xml registers the
  ir.actions.report bound to fusion.migration.wizard.

Made-with: Cursor
2026-04-19 13:25:59 -04:00
gsinghpal
b6aedc9bbe feat(fusion_accounting_bank_rec): migration wizard bootstrap step
Adds bank_rec_bootstrap step that backfills fusion.reconcile.precedent
from existing account.partial.reconcile rows during migration. This
gives the AI memory from past Enterprise reconciles. Also triggers
pattern refresh + MV refresh for immediate UI readiness.

- New service services/precedent_backfill.py walks
  account.partial.reconcile rows, identifies the bank-statement-line
  side, and creates a precedent per qualifying partial. Idempotent via
  (statement_line, account, amount, source='backfill') signature.
- New model models/fusion_migration_wizard.py inherits
  fusion.migration.wizard, exposes _bank_rec_bootstrap_step() (callable
  from tests/audit), and overrides action_run_migration() to call
  super() + the bootstrap.
- Adds 'backfill' to fusion.reconcile.precedent.source selection.
- Adds fusion_accounting_migration to depends.

Made-with: Cursor
2026-04-19 13:24:17 -04:00
gsinghpal
25f033d0c8 feat(fusion_accounting_bank_rec): bulk reconcile wizard for selected lines
TransientModel + view + binding action so users can select bank lines
from any list view and bulk-apply either engine.reconcile_batch or
a chosen reconcile model.

Made-with: Cursor
2026-04-19 13:17:58 -04:00
gsinghpal
75850aad73 feat(fusion_accounting_bank_rec): auto-reconcile wizard
TransientModel that filters unreconciled bank lines by journal +
date range + strategy and runs engine.reconcile_batch. Shows
reconciled_count / skipped_count / error_summary in result view.

Made-with: Cursor
2026-04-19 13:16:06 -04:00
gsinghpal
6cbb5f85fe feat(fusion_accounting_bank_rec): fusion-only attachment strip + partner history panel
attachment_strip renders inline mimetype-aware chips linking to /web/content
downloads. partner_history_panel calls bank_reconciliation.getPartnerHistory
to surface the learned reconcile pattern (preferred strategy, typical cadence)
plus the most recent reconciles per partner — context Enterprise's bank-rec
widget cannot show because it has no behavioural-learning layer.

Made-with: Cursor
2026-04-19 13:05:23 -04:00
gsinghpal
596ecb9e03 feat(fusion_accounting_bank_rec): fusion-only batch action bar + reconcile model picker
batch_action_bar exposes bulk Suggest-for-selected and Auto-reconcile-selected
toolbar driven by selectedIds prop and the bank_reconciliation service.
reconcile_model_picker is a quick-pick dropdown over account.reconcile.model
records (rule_type=writeoff_button) including the Fusion AI confidence
threshold; apply path is a state-only stub pending Task 38's dedicated endpoint.

Made-with: Cursor
2026-04-19 13:03:50 -04:00
gsinghpal
99e27cc566 feat(fusion_accounting_bank_rec): fusion-only AI suggestion UI components
ai_suggestion_strip (inline confidence badge + accept), ai_alternatives_panel
(expandable other-options), ai_reasoning_tooltip (score breakdown). These
go beyond Enterprise's bank_rec_widget which has no AI suggestions.

Made-with: Cursor
2026-04-19 13:02:18 -04:00
gsinghpal
c9ac4c64fb feat(fusion_accounting_bank_rec): mirror Enterprise OWL batch 4 (auxiliary components)
Mirrors 3 OWL components from account_accountant for Phase 1
structural parity:

- quick_create/ (BankRecQuickCreate + BankRecQuickCreateController
  for inline missing-record creation)
- chatter/ (BankRecChatter — extends @mail Chatter with a
  reloadParentView hook for the bound statement line)
- file_uploader/ (BankRecFileUploader — extends @account
  DocumentFileUploader to inject statement_line_id into the
  upload context, targeting account.bank.statement.line)

Renames applied per spec; CSS class
`o_bank_reconciliation_quick_create` ->
`o_fusion_bank_reconciliation_quick_create`.

Manifest version bumped to 19.0.1.0.15.

Module upgrade succeeds, 134 logical tests still pass — completing
the Phase 1 OWL component mirror (Tasks 30-33). All 14 components
across 4 batches are now bundled.

Made-with: Cursor
2026-04-19 12:55:20 -04:00
gsinghpal
b06e01babb feat(fusion_accounting_bank_rec): mirror Enterprise OWL batch 3 (dialog components)
Mirrors 2 OWL components (3 files each) from account_accountant
for Phase 1 structural parity:

- bankrec_form_dialog/ (full-form dialog for advanced editing,
  including BankRecEditLineFormController with the To-Review
  hotkey button)
- search_dialog/ (BankRecSelectCreateDialog for finding additional
  matches, plus the bank_rec_dialog_list view registration)

Renames applied per spec.

Notes:
- View registry IDs prefixed: `fusion_bankrec_edit_line`,
  `fusion_bank_rec_dialog_list`.
- Button template renamed
  `accountant.BankRecFormDialog.buttons` ->
  `fusion_accounting_bank_rec.BankRecFormDialog.buttons`.

Manifest version bumped to 19.0.1.0.14.

Module upgrade succeeds, 134 logical tests still pass.

Made-with: Cursor
2026-04-19 12:54:11 -04:00
gsinghpal
9e4de89269 feat(fusion_accounting_bank_rec): mirror Enterprise OWL batch 2 (action + edit components)
Mirrors 5 OWL components from account_accountant for Phase 1
structural parity:

- button/ (single action button)
- button_list/ (toolbar of buttons + dropdown + hotkeys)
- line_to_reconcile/ (editable matched-line editor)
- list_view/ (list view + many2one multi-edit field)
- apply_amount/ (amount application html field)

Renames applied per spec (template names, module IDs, CSS classes).

Notes / deferred to fusion-only Tasks 34-36:
- list_view extends @web ListController instead of Enterprise's
  AttachmentPreviewListController; setSelectedRecord is a no-op
  pending the previewer pane mirror.
- View/field registry IDs prefixed with `fusion_` to coexist with
  Enterprise's account_accountant when both modules are installed
  (`fusion_bank_rec_list`, `fusion_bank_rec_dialog_list`,
  `fusion_apply_amount_html`, `fusion_bank_rec_list_many2one_multi_id`,
  `fusion_bankrec_edit_line`).
- button_list still references Enterprise view_refs in dialog
  contexts (`account_accountant.view_account_list_bank_rec_widget`
  etc.) for parity; the `set_*` ORM methods on
  account.bank.statement.line are Enterprise-only too. These call
  sites only fire when the mirrored components are actually
  rendered, which Phase 1 does not exercise.

Manifest version bumped to 19.0.1.0.13.

Module upgrade succeeds, 134 logical tests still pass.

Made-with: Cursor
2026-04-19 12:53:02 -04:00
gsinghpal
1634ecd4f6 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
2026-04-19 12:51:38 -04:00
gsinghpal
3e48bab087 feat(fusion_accounting_bank_rec): kanban controller + renderer for OWL widget
Top-level OWL component (BankRecKanbanController) hosts the bank
reconciliation widget. Reads journal_id + company_id from action context,
initializes the fusion_bank_reconciliation service, and renders the
layout: header (stats), left column (line cards via BankRecLineCard
renderer), right column (detail panel with AI suggestions).

Custom view type 'fusion_bank_rec_kanban' registered so window actions
can use <field name="view_mode">fusion_bank_rec_kanban</field>.

Made-with: Cursor
2026-04-19 12:33:57 -04:00
gsinghpal
a4a9692888 fix(fusion_accounting_bank_rec): acceptSuggestion double-decrement count
Optimistic remove was decrementing unreconciledCount before assigning
the authoritative server count, leading to off-by-one. Order swapped:
remove first, then overwrite with server count.

Caught by Task 28 subagent self-review.

Made-with: Cursor
2026-04-19 12:28:34 -04:00
gsinghpal
d4dbca5927 feat(fusion_accounting_bank_rec): OWL bank reconciliation service
Central data layer + reactive state for the OWL widget. Wraps the 10
JSON-RPC endpoints from the bank_rec_controller (get_state,
list_unreconciled, get_line_detail, suggest_matches, accept_suggestion,
reconcile_manual, unreconcile, write_off, bulk_reconcile,
get_partner_history). Components inject via useService("fusion_bank_reconciliation").

State held in OWL's reactive() so components auto-rerender on
selection / pagination / reconcile-success changes.

Verified: web.assets_backend bundle includes
/fusion_accounting_bank_rec/static/src/services/bank_reconciliation_service.js;
134/134 module tests pass.

Made-with: Cursor
2026-04-19 12:27:44 -04:00
gsinghpal
24e2708d98 feat(fusion_accounting_bank_rec): SCSS foundation for OWL widget
Provides design tokens (variables.scss), main bank-rec stylesheet,
AI suggestion strip + alternatives panel styling, and dark mode
overrides. CSS classes (.o_fusion_*) will be consumed by OWL components
in Tasks 28-36.

Verified: all 4 SCSS files compile via libsass; web.assets_backend
bundle picks up all 4 entries; 134/134 module tests pass.

Made-with: Cursor
2026-04-19 12:23:55 -04:00
gsinghpal
6ecb1bbbee feat(fusion_accounting_bank_rec): 10 JSON-RPC endpoints for OWL widget
All endpoints route through fusion.reconcile.engine via BankRecAdapter
(or directly for engine methods adapter doesn't expose). Uses V19's
type='jsonrpc' (replacement for deprecated type='json'). Auth=user.

Endpoints:
- get_state, list_unreconciled, get_line_detail (read)
- suggest_matches, accept_suggestion (AI surface)
- reconcile_manual, unreconcile, write_off, bulk_reconcile (write)
- get_partner_history (precedent + pattern read)

Tests use HttpCase to exercise the real Werkzeug stack as a Fusion
Accounting administrator. Includes a smoke test for the deferred
write-off path (Task 12) and a negative test confirming auth='user'
rejects anonymous requests. Helper _make_pair shares one bank journal
across pairs to avoid the (code, company) unique-constraint collision
that the default factory would hit on repeat calls.

Verified: 11/11 controller tests pass, 134/134 module tests pass.
Made-with: Cursor
2026-04-19 12:15:40 -04:00
gsinghpal
d1819b940e feat(fusion_accounting_bank_rec): 3 cron schedules + handler model
- cron_suggest (every 30min): warm AI suggestions for unreconciled lines
  that don't have a recent pending one
- cron_pattern_refresh (daily 02:00): recompute fusion.reconcile.pattern
  for each (company, partner) pair with precedents
- cron_mv_refresh (every 5min): REFRESH MATERIALIZED VIEW CONCURRENTLY
  using a dedicated autocommit cursor (REFRESH CONCURRENTLY can't run
  inside a regular Odoo transaction)

V19 note: ir.cron dropped the numbercall field, so the data XML omits
it (cron now repeats indefinitely as long as active=True).

Tests: 5 new TestFusionBankRecCron tests pass; full module suite is
0 failed / 0 errors of 123 logical tests on westin-v19.

Made-with: Cursor
2026-04-19 11:59:16 -04:00
gsinghpal
d953525758 fix(fusion_accounting_bank_rec): MV correctness for V19 schema + Odoo test harness
Three issues surfaced when running the MV smoke tests against westin-v19:

1. account_bank_statement_line has no `date` column in V19 — `date` is a
   related field flowing through move_id -> account_move.date. The MV
   now JOINs account_move and selects am.date.
2. is_reconciled is nullable; replace `= FALSE` with `IS NOT TRUE` so
   nulls (genuinely unreconciled lines that haven't had the compute run
   yet) are still included.
3. _refresh() now flushes the ORM cache (env.flush_all()) before the
   REFRESH so computed-stored fields like is_reconciled are written to
   the DB before the materialization snapshot reads them. Previously the
   reconcile-then-refresh path saw the pre-reconcile column value.
4. _trigger_mv_refresh() (suggestion create/write hook) now uses
   concurrently=False because Postgres forbids
   REFRESH MATERIALIZED VIEW CONCURRENTLY inside a transaction block,
   and Odoo's per-request cursor is always inside one. The cron path
   (Task 25) will open an autocommit cursor for CONCURRENTLY refreshes.
5. Tests dropped the env.cr.commit() pattern: Postgres always shows a
   transaction its own writes, so a non-CONCURRENTLY refresh in the
   same txn picks up freshly-inserted rows. Cleaner + works inside
   TransactionCase, which forbids cr.commit().

Verified: 4 new MV tests pass, 0 failures across 118 logical tests
(178 with parametrized property-based runs) of fusion_accounting_bank_rec
on westin-v19.

Made-with: Cursor
2026-04-19 11:51:02 -04:00
gsinghpal
12b6b46e2e feat(fusion_accounting_bank_rec): pre-aggregated MV for OWL widget perf
CREATE MATERIALIZED VIEW fusion_unreconciled_bank_line_mv pre-computes
the data the kanban widget needs (top suggestion, confidence band,
attachment count, partner reconcile hint) so that listing 50-100 lines
is one indexed query instead of N+1.

Refresh strategy:
- Triggered on fusion.reconcile.suggestion create/write (best-effort,
  never poisons the originating transaction)
- Cron (every 5 min) — added in Task 25

The MV is created in the model's init() (Odoo calls this on
install/upgrade). The SQL DDL is idempotent
(CREATE MATERIALIZED VIEW IF NOT EXISTS / CREATE INDEX IF NOT EXISTS)
and includes a UNIQUE(id) index so REFRESH MATERIALIZED VIEW
CONCURRENTLY is supported. _refresh() falls back to a blocking refresh
on the first call after creation.

Made-with: Cursor
2026-04-19 11:45:36 -04:00
gsinghpal
5020129c45 refactor(fusion_accounting_ai): route legacy reconcile tools through engine
Some checks failed
fusion_accounting CI / test (fusion_accounting_ai) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_core) (push) Has been cancelled
fusion_accounting CI / test (fusion_accounting_migration) (push) Has been cancelled
When fusion_accounting_bank_rec is installed, match_bank_line_to_payments
and auto_reconcile_bank_lines now use fusion.reconcile.engine via the
BankRecAdapter, gaining precedent recording, AI suggestion superseding,
and shared validation. Legacy paths preserved for Enterprise/Community-
only installs (engine model absent -> fall back to set_line_bank_statement_line
and _try_auto_reconcile_statement_lines).

Also wraps engine.reconcile_batch's per-line loop in a savepoint so a
single bad line's DB error (e.g. check-constraint violation) no longer
poisons the whole batch transaction; the existing per-line try/except
now isolates failures as originally intended.

Made-with: Cursor
2026-04-19 11:37:34 -04:00
gsinghpal
3993f58910 feat(fusion_accounting_ai): 5 new bank-rec AI tools wrapping engine
Adds fusion_suggest_matches, fusion_accept_suggestion,
fusion_reconcile_bank_line, fusion_unreconcile, and
fusion_get_pending_suggestions. All route through the BankRecAdapter
(or direct engine for ones the adapter doesn't expose), giving the AI
chat the same reconciliation surface a human operator gets in the OWL UI.

Made-with: Cursor
2026-04-19 11:31:40 -04:00
gsinghpal
8eee64f053 feat(fusion_accounting_ai): wire BankRecAdapter fusion paths to engine
Enhances list_unreconciled_via_fusion to include fusion fields
(top_suggestion_id, confidence_band, attachment_count). Adds 3 new
adapter methods that proxy the engine: suggest_matches, accept_suggestion,
unreconcile. AI tools (Task 22+) and OWL controller (Task 26) will call
these adapter methods instead of touching the engine directly.

Made-with: Cursor
2026-04-19 11:25:41 -04:00
gsinghpal
2d099b2d0d feat(fusion_accounting_ai): bank_rec_prompt for AI re-rank step
Provider-agnostic system + user prompt builder for the confidence
scoring pipeline's Pass 3 (AI re-rank). Output contract is JSON with
"ranked" array; works with OpenAI, Claude, and local OpenAI-compatible
servers (LM Studio, Ollama).

Made-with: Cursor
2026-04-19 11:20:56 -04:00
gsinghpal
8be0caa474 fix(fusion_accounting_bank_rec): partial-reconcile balance + unreconcile suspense restore
Two engine bugs caught by Task 19's integration tests:

1. Partial reconcile (bank_amount < invoice_residual) was creating an
   unbalanced bank move. Counterpart balance now clamped to
   min(remaining_bank_amount, abs(invoice_residual)) so the move stays
   balanced; Odoo's reconcile() handles the resulting partial. The
   counterpart's amount_currency is scaled proportionally so multi-
   currency lines stay consistent.

2. Unreconcile only removed account.partial.reconcile rows but didn't
   restore the suspense line on the bank move, leaving is_reconciled=True
   after unreconcile. Now delegates to V19's standard
   account.bank.statement.line.action_undo_reconciliation for any
   affected bank line, which both deletes partials and restores the
   suspense state in one shot.

Made-with: Cursor
2026-04-19 11:14:43 -04:00
gsinghpal
fce748b89c test(fusion_accounting_bank_rec): integration tests for engine end-to-end flows
Tests engine behavior using factories (Task 18) instead of SQL fixtures.
Covers simple match, partial chain, multi-invoice batch, suggest-then-
accept flow, unreconcile reversal, and edge cases.

Two tests are intentionally failing — they expose real engine bugs
that should be fixed in a follow-up:

- TestReconcilePartialChain.test_partial_reconcile_leaves_residual:
  reconcile_one() builds counterpart vals using the full invoice
  residual, which leaves the bank move unbalanced when bank amount
  is smaller than the invoice (UserError: entry not balanced).
- TestUnreconcile.test_unreconcile_removes_partial: unreconcile()
  unlinks partial.reconcile rows but does not restore the suspense
  line on the bank move, so account.bank.statement.line.is_reconciled
  remains True after reversal.

Made-with: Cursor
2026-04-19 11:11:30 -04:00
gsinghpal
fcecf9d925 test(fusion_accounting_bank_rec): test data factories for bank-rec testing
Provides make_bank_journal, make_bank_statement, make_bank_line,
make_invoice, make_vendor_bill, make_suggestion, make_pattern,
make_precedent, make_reconcileable_pair helpers used across the
bank-rec test suite. Replaces the original plan's SQL-fixture capture
with programmatic factories — same testing intent, simpler maintenance,
no real Westin data baked into the repo.

Note: the original plan called for 5 SQL fixtures captured from the local
DB (westin_simple_match.sql, westin_partial_chain.sql, etc.). Those are
replaced by factory-driven test creation in Task 19 — eliminates fragile
hand-curated SQL while testing the same code paths.

Made-with: Cursor
2026-04-19 11:05:06 -04:00
gsinghpal
da269a6207 test(fusion_accounting_bank_rec): Hypothesis property-based engine invariants
Made-with: Cursor
2026-04-19 10:57:41 -04:00
gsinghpal
80b8100232 feat(fusion_accounting_bank_rec): reconcile engine 6-method public API
Adds fusion.reconcile.engine — the AbstractModel orchestrator for all
bank-line reconciliations. Six public methods (reconcile_one,
reconcile_batch, suggest_matches, accept_suggestion, write_off,
unreconcile) form the only sanctioned write path to
account.partial.reconcile from the rest of the module (controllers, AI
tools, wizards).

Implementation follows V19's bank_rec_widget pattern: rewrite the bank
move's suspense line into one counterpart per matched invoice (or a
write-off line) on the appropriate receivable / payable / write-off
account, then call account.move.line.reconcile() on each pair. Records
a precedent row per reconcile for downstream pattern learning.

16 new unit tests cover all six methods across happy paths, the
precedent side effect, suggestion lifecycle, batch auto-strategy, and
write-off line clearance. 67 total tests, 0 failed.

Made-with: Cursor
2026-04-19 10:50:46 -04:00
gsinghpal
920a624cd1 feat(fusion_accounting_bank_rec): 4-pass confidence scoring pipeline
Task 11 of Phase 1 Bank Reconciliation. Adds the brain that ranks
candidate journal-item matches for a bank statement line.

Pass 1 — SQL filter (done by caller's _fetch_candidates).
Pass 2 — Statistical scoring: weighted blend of amount-delta,
         partner pattern fit, and precedent similarity.
Pass 3 — Optional AI re-rank when an LLM provider is configured;
         gracefully no-ops when provider missing, prompt module not
         yet present (Task 20), or the JSON response is malformed.
Pass 4 — Persistence (handled by engine.suggest_matches).

Returns top-K ScoredCandidate dataclasses with per-feature scores
exposed for transparency and future learning.

7 new tests added; full module suite green (51 tests, 0 failures).

Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
06e382b27b feat(fusion_accounting_bank_rec): pattern_extractor for per-partner aggregates
Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
91d09dfca2 feat(fusion_accounting_bank_rec): precedent_lookup K-nearest search
Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
ef27f0e2c1 feat(fusion_accounting_bank_rec): inherit account.bank.statement.line + account.reconcile.model
Task 17 — Add Phase 1 widget compute fields and AI hooks:
- account.bank.statement.line: fusion_top_suggestion_id (m2o, unstored),
  fusion_confidence_band (selection, unstored), bank_statement_attachment_ids
  (one2many compute, mirrors Enterprise's surface field for the OWL widget).
- account.reconcile.model: fusion_ai_confidence_threshold (float).
- Bumps manifest 19.0.1.0.3 → 19.0.1.0.4.

V19 note: dropped @api.depends('id') on _compute_top_suggestion (NotImplementedError
in V19); compute is on-demand for unstored field anyway.

Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
b37b1d4618 feat(fusion_accounting_bank_rec): transient model for widget round-trip data
Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
e468ae6b0a feat(fusion_accounting_bank_rec): persisted AI suggestion model with state lifecycle
Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
6e945dea95 feat(fusion_accounting_bank_rec): pattern + precedent models for behavioural learning
Adds the foundation for AI confidence scoring:
- fusion.reconcile.pattern: per-(company, partner) aggregate profile
  (volume, cadence, preferred matching strategy, memo signature,
  write-off habits) — recomputed nightly from precedents.
- fusion.reconcile.precedent: per-historical-decision memory holding
  full feature vector + outcome, used by precedent_lookup for KNN
  scoring of new bank lines.

Includes ACL rows for fusion accounting user (read) and admin (CRUD)
groups. Manifest bumped to 19.0.1.0.1.

Note: switched the pattern uniqueness rule from the deprecated
_sql_constraints attribute to models.Constraint (Odoo 19 native API)
so the unique(company_id, partner_id) is actually enforced at the
PG level — _sql_constraints is silently ignored in 19.

Made-with: Cursor
2026-04-19 10:45:30 -04:00
gsinghpal
3dc74e3987 feat(fusion_accounting_bank_rec): matching strategies (AmountExact, FIFO, MultiInvoice)
Made-with: Cursor
2026-04-19 10:45:30 -04:00