Compare commits
3 Commits
ffc029a875
...
d623b67157
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d623b67157 | ||
|
|
aaaf49989c | ||
|
|
878c013902 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting — Bank Reconciliation',
|
||||
'version': '19.0.1.0.23',
|
||||
'version': '19.0.1.0.26',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 28,
|
||||
'summary': 'Native V19 bank reconciliation widget with AI confidence scoring + behavioural learning.',
|
||||
@@ -35,6 +35,7 @@ Built by Nexa Systems Inc.
|
||||
'wizards/bulk_reconcile_wizard_views.xml',
|
||||
'reports/migration_audit_report_views.xml',
|
||||
'reports/migration_audit_report_action.xml',
|
||||
'views/menu_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
@@ -102,6 +103,9 @@ Built by Nexa Systems Inc.
|
||||
'fusion_accounting_bank_rec/static/src/components/bank_reconciliation/partner_history_panel/partner_history_panel.js',
|
||||
'fusion_accounting_bank_rec/static/src/components/bank_reconciliation/partner_history_panel/partner_history_panel.xml',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'fusion_accounting_bank_rec/static/src/tours/bank_rec_tours.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': False,
|
||||
|
||||
109
fusion_accounting_bank_rec/static/src/tours/bank_rec_tours.js
Normal file
109
fusion_accounting_bank_rec/static/src/tours/bank_rec_tours.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
/**
|
||||
* 5 OWL tours for fusion_accounting_bank_rec smoke testing.
|
||||
*
|
||||
* Each tour scripts a user interaction with the bank-rec widget and
|
||||
* is invoked from Python via HttpCase.start_tour(). Useful for catching
|
||||
* UI regressions that asset-bundle compilation alone won't catch.
|
||||
*/
|
||||
|
||||
// Tour 1: Open the kanban widget and confirm it loads
|
||||
registry.category("web_tour.tours").add("fusion_bank_rec_smoke", {
|
||||
test: true,
|
||||
url: "/odoo/action-fusion_accounting_bank_rec.action_fusion_bank_rec_widget",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Wait for header to appear",
|
||||
trigger: ".o_fusion_bank_rec_header h1:contains(Bank Reconciliation)",
|
||||
},
|
||||
{
|
||||
content: "Confirm stats are visible",
|
||||
trigger: ".o_fusion_stats",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Tour 2: Select a line and confirm detail panel loads
|
||||
registry.category("web_tour.tours").add("fusion_bank_rec_select_line", {
|
||||
test: true,
|
||||
url: "/odoo/action-fusion_accounting_bank_rec.action_fusion_bank_rec_widget",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Wait for at least one line card",
|
||||
trigger: ".o_fusion_bank_rec_line:first",
|
||||
},
|
||||
{
|
||||
content: "Click the first line",
|
||||
trigger: ".o_fusion_bank_rec_line:first",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Detail panel shows selected line",
|
||||
trigger: ".o_fusion_bank_rec_detail h2",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Tour 3: Trigger AI suggestion and accept
|
||||
registry.category("web_tour.tours").add("fusion_bank_rec_accept_suggestion", {
|
||||
test: true,
|
||||
url: "/odoo/action-fusion_accounting_bank_rec.action_fusion_bank_rec_widget",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click first line with a partner",
|
||||
trigger: ".o_fusion_bank_rec_line:has(.o_fusion_partner):first",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click 'Get AI suggestions' button",
|
||||
trigger: ".o_fusion_bank_rec_detail .btn_fusion_primary:contains(Get AI)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait for at least one suggestion to appear",
|
||||
trigger: ".o_fusion_ai_suggestion",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Tour 4: Open auto-reconcile wizard
|
||||
registry.category("web_tour.tours").add("fusion_bank_rec_auto_reconcile_wizard", {
|
||||
test: true,
|
||||
url: "/odoo/action-fusion_accounting_bank_rec.action_fusion_auto_reconcile_wizard",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Wizard form opens",
|
||||
trigger: ".modal-dialog .o_form_view",
|
||||
},
|
||||
{
|
||||
content: "Strategy field exists",
|
||||
trigger: ".modal-dialog [name='strategy']",
|
||||
},
|
||||
{
|
||||
content: "Close wizard",
|
||||
trigger: ".modal-dialog .btn-secondary",
|
||||
run: "click",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Tour 5: Load more (pagination)
|
||||
registry.category("web_tour.tours").add("fusion_bank_rec_load_more", {
|
||||
test: true,
|
||||
url: "/odoo/action-fusion_accounting_bank_rec.action_fusion_bank_rec_widget",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Wait for kanban container",
|
||||
trigger: ".o_fusion_bank_rec",
|
||||
},
|
||||
// Pagination button only appears if there are more lines than `limit`.
|
||||
// This tour is a no-op if the dataset is small — that's fine for smoke.
|
||||
{
|
||||
content: "Confirm app loaded (regardless of pagination state)",
|
||||
trigger: ".o_fusion_bank_rec_header h1",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -19,3 +19,5 @@ from . import test_controller
|
||||
from . import test_auto_reconcile_wizard
|
||||
from . import test_bulk_reconcile_wizard
|
||||
from . import test_migration_round_trip
|
||||
from . import test_coexistence
|
||||
from . import test_bank_rec_tours
|
||||
|
||||
42
fusion_accounting_bank_rec/tests/test_bank_rec_tours.py
Normal file
42
fusion_accounting_bank_rec/tests/test_bank_rec_tours.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Python wrappers that run the OWL tours via HttpCase.start_tour.
|
||||
|
||||
Tours require an HTTP server + headless browser. They are tagged with
|
||||
'tour' so they can be excluded from fast unit-test runs and selected
|
||||
explicitly when CI has the right infra (chromium + xvfb).
|
||||
"""
|
||||
|
||||
from odoo.tests.common import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'tour')
|
||||
class TestBankRecTours(HttpCase):
|
||||
|
||||
def test_smoke_tour(self):
|
||||
# Just verify the smoke tour runs without crashing
|
||||
self.start_tour("/odoo", "fusion_bank_rec_smoke", login="admin")
|
||||
|
||||
def test_select_line_tour(self):
|
||||
# Need a bank line to select — create one
|
||||
partner = self.env['res.partner'].create({'name': 'Tour Partner'})
|
||||
journal = self.env['account.journal'].create({
|
||||
'name': 'Tour Bank', 'type': 'bank', 'code': 'TOURB',
|
||||
})
|
||||
statement = self.env['account.bank.statement'].create({
|
||||
'name': 'Tour Stmt', 'journal_id': journal.id,
|
||||
})
|
||||
self.env['account.bank.statement.line'].create({
|
||||
'statement_id': statement.id, 'journal_id': journal.id,
|
||||
'date': '2026-04-19', 'payment_ref': 'Tour line',
|
||||
'amount': 100, 'partner_id': partner.id,
|
||||
})
|
||||
self.start_tour("/odoo", "fusion_bank_rec_select_line", login="admin")
|
||||
|
||||
def test_accept_suggestion_tour(self):
|
||||
# Skip if too slow / dataset issues — tour itself is the smoke
|
||||
self.skipTest("Tour 3 requires AI provider config; skipping in CI smoke")
|
||||
|
||||
def test_auto_reconcile_wizard_tour(self):
|
||||
self.start_tour("/odoo", "fusion_bank_rec_auto_reconcile_wizard", login="admin")
|
||||
|
||||
def test_load_more_tour(self):
|
||||
self.start_tour("/odoo", "fusion_bank_rec_load_more", login="admin")
|
||||
86
fusion_accounting_bank_rec/tests/test_coexistence.py
Normal file
86
fusion_accounting_bank_rec/tests/test_coexistence.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Coexistence tests: fusion_accounting_bank_rec menus only visible
|
||||
when Enterprise's account_accountant is absent.
|
||||
|
||||
Strategy: mock the install state by toggling the group's user list directly,
|
||||
then verify the recompute method aligns it with module presence."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestCoexistence(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.group = self.env.ref(
|
||||
'fusion_accounting_core.group_fusion_show_when_enterprise_absent')
|
||||
|
||||
def _account_accountant_installed(self):
|
||||
return bool(self.env['ir.module.module'].sudo().search([
|
||||
('name', '=', 'account_accountant'),
|
||||
('state', '=', 'installed'),
|
||||
]))
|
||||
|
||||
def test_group_exists(self):
|
||||
self.assertTrue(self.group, "Coexistence group must exist")
|
||||
|
||||
def test_recompute_when_enterprise_present(self):
|
||||
"""When account_accountant is installed, group should be empty."""
|
||||
if not self._account_accountant_installed():
|
||||
self.skipTest(
|
||||
"Local DB doesn't have account_accountant installed; "
|
||||
"this test only meaningful in Enterprise-present scenario"
|
||||
)
|
||||
self.env['res.users']._fusion_recompute_coexistence_group()
|
||||
self.assertEqual(
|
||||
len(self.group.user_ids), 0,
|
||||
"Coexistence group should be empty when Enterprise is installed",
|
||||
)
|
||||
|
||||
def test_recompute_when_enterprise_absent(self):
|
||||
"""When account_accountant is uninstalled, all internal users get the group."""
|
||||
if self._account_accountant_installed():
|
||||
# Simulate by mocking the enterprise-installed check.
|
||||
with patch.object(
|
||||
type(self.env['ir.module.module']),
|
||||
'_fusion_is_enterprise_accounting_installed',
|
||||
return_value=False,
|
||||
):
|
||||
self.env['res.users']._fusion_recompute_coexistence_group()
|
||||
internal_users = self.env['res.users'].search([
|
||||
('share', '=', False),
|
||||
])
|
||||
self.assertGreater(
|
||||
len(self.group.user_ids & internal_users), 0,
|
||||
"Coexistence group should contain internal users when "
|
||||
"Enterprise is absent",
|
||||
)
|
||||
else:
|
||||
self.env['res.users']._fusion_recompute_coexistence_group()
|
||||
internal = self.env['res.users'].search([('share', '=', False)])
|
||||
self.assertGreater(len(self.group.user_ids & internal), 0)
|
||||
|
||||
def test_menu_has_coexistence_group(self):
|
||||
"""The fusion bank-rec root menu must have the coexistence group attached."""
|
||||
menu = self.env.ref(
|
||||
'fusion_accounting_bank_rec.menu_fusion_bank_rec_root',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not menu:
|
||||
self.skipTest("Menu not yet loaded — Task 42 must run first")
|
||||
# Odoo 19 renamed ir.ui.menu.groups_id -> group_ids; tolerate either.
|
||||
groups_field = getattr(menu, 'group_ids', None) or menu.groups_id
|
||||
self.assertIn(
|
||||
self.group, groups_field,
|
||||
"Menu must require the coexistence group",
|
||||
)
|
||||
|
||||
def test_engine_works_regardless_of_coexistence(self):
|
||||
"""The reconcile engine must work even when Enterprise is installed
|
||||
(it's the AI tools/menu that gate; the engine is always available)."""
|
||||
self.assertIn(
|
||||
'fusion.reconcile.engine', self.env.registry,
|
||||
"Engine must always be available when fusion_accounting_bank_rec "
|
||||
"is installed",
|
||||
)
|
||||
45
fusion_accounting_bank_rec/views/menu_views.xml
Normal file
45
fusion_accounting_bank_rec/views/menu_views.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Window action that opens the bank reconciliation kanban widget -->
|
||||
<record id="action_fusion_bank_rec_widget" model="ir.actions.act_window">
|
||||
<field name="name">Bank Reconciliation</field>
|
||||
<field name="res_model">account.bank.statement.line</field>
|
||||
<field name="view_mode">fusion_bank_rec_kanban</field>
|
||||
<field name="domain">[('is_reconciled', '=', False)]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Bank Reconciliation Widget
|
||||
</p>
|
||||
<p>
|
||||
AI-assisted bank reconciliation. Statement lines that haven't
|
||||
been matched yet appear here, with confidence-scored AI
|
||||
suggestions for matching.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Top-level menu — only visible when Enterprise's account_accountant is absent -->
|
||||
<menuitem id="menu_fusion_bank_rec_root"
|
||||
name="Bank Reconciliation"
|
||||
sequence="40"
|
||||
web_icon="fusion_accounting_bank_rec,static/description/icon.png"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<menuitem id="menu_fusion_bank_rec_main"
|
||||
name="Reconcile Bank Lines"
|
||||
parent="menu_fusion_bank_rec_root"
|
||||
action="action_fusion_bank_rec_widget"
|
||||
sequence="10"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
<!-- Sub-menu for the auto-reconcile wizard -->
|
||||
<menuitem id="menu_fusion_auto_reconcile_wizard"
|
||||
name="Auto-Reconcile…"
|
||||
parent="menu_fusion_bank_rec_root"
|
||||
action="action_fusion_auto_reconcile_wizard"
|
||||
sequence="20"
|
||||
groups="fusion_accounting_core.group_fusion_show_when_enterprise_absent"/>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user