add feature to show total amount selected

This commit is contained in:
Suherdy Yacob 2025-10-27 21:57:59 +07:00
parent b7aa68ec8e
commit 5dfc280ed6
15 changed files with 451 additions and 66 deletions

288
README.md
View File

@ -1,94 +1,262 @@
# Bank Statement Reconciliation Module # Bank Statement Reconciliation Module
## Overview ## Overview
This Odoo 17 module enhances the bank reconciliation process by providing a streamlined interface for matching bank statement lines with journal entries. The module allows users to select multiple bank lines and reconcile them with existing journal entries, with enhanced filtering capabilities to hide already reconciled lines.
This module enhances Odoo's bank statement reconciliation functionality by providing an intuitive interface for reconciling bank statement lines with journal entries.
## Features ## Features
### 1. Bank Statement Line Management ### Core Functionality
- Dedicated menu for accessing bank statement lines - **Menu Access**: Direct menu to access bank statement lines
- Journal selection wizard to filter lines by bank journal - **Bank Journal Filtering**: Filter statement lines by specific bank journals
- Multi-line selection for batch reconciliation - **Batch Reconciliation**: Select and reconcile multiple bank lines simultaneously
- **Journal Entry Matching**: Wizard to select appropriate journal entries for reconciliation
- **Automatic Reconciliation**: Automatic creation of reconciliation journal entries
### 2. Enhanced Reconciliation Process ### New Features (v17.0.1.0.0)
- Wizard-based interface for selecting journal entries to reconcile with
- Individual line processing for precise control
- Automatic journal entry creation for reconciliation transactions
- Intelligent automatic reconciliation of journal entry lines with created entries
- Proper account matching for accurate reconciliation
### 3. Smart Filtering #### 1. Total Selected Amount Widget
- "Hide Reconciled" filter to focus on unreconciled lines
- Computed `is_reconciled` field for accurate status tracking
- Toggle between filtered and unfiltered views
### 4. Visual Indicators A dynamic header widget that displays real-time totals when you select multiple bank statement lines in the list view.
- Color-coded lines for negative amounts (red danger decoration)
- Clean interface without visual muting of reconciled lines **Features:**
- Consistent styling with Odoo's standard UI patterns - **Real-time Calculation**: Automatically calculates the sum of selected lines
- **Visual Indicators**:
- Green badge for positive amounts
- Red badge for negative amounts
- **Selection Counter**: Shows how many lines are currently selected
- **Currency Display**: Shows the currency of selected lines
- **Modern UI**: Clean, Bootstrap-styled interface with icons
**How to Use:**
1. Navigate to: `Accounting > Bank Statement Reconciliation > Bank Statement Lines`
2. Select one or more bank statement lines by clicking the checkboxes
3. The header widget will automatically appear showing:
- Number of selected lines
- Total amount of selected lines
- Currency information
**Visual Layout:**
```
┌─────────────────────────────────────────────────────────────┐
│ ☑ Selected Lines: [3] │ 🧮 Total Amount: [1,234.56] USD │
└─────────────────────────────────────────────────────────────┘
```
## Installation ## Installation
1. Copy the `bank_statement_reconciliation` folder to your Odoo addons directory 1. Copy the module to your Odoo addons directory:
2. Update the apps list in Odoo ```bash
3. Search for "Bank Statement Reconciliation" in the Apps menu cp -r bank_statement_reconciliation /path/to/odoo/addons/
4. Install the module ```
2. Update the addons list:
- Go to `Apps` menu
- Click `Update Apps List`
- Search for "Bank Statement Reconciliation"
3. Install the module:
- Click `Install` button
## Configuration ## Configuration
No additional configuration is required after installation. The module works with standard Odoo accounting settings.
No additional configuration is required. The module works out of the box after installation.
## Usage ## Usage
### Accessing Bank Statement Lines ### Reconciling Bank Statement Lines
1. Navigate to Accounting > Bank Reconciliation > Bank Statement Lines
2. Select a bank journal using the wizard
3. View the bank statement lines for the selected journal
### Reconciling Bank Lines 1. **Access Bank Statement Lines**
1. Select one or more bank lines from the list view - Navigate to: `Accounting > Bank Statement Reconciliation > Bank Statement Lines`
2. Click "Action" and select "Reconcile Selected Lines"
3. In the wizard, select the journal entry to reconcile with
4. Choose the specific journal entry line
5. Click "Reconcile" to complete the process
### Filtering Reconciled Lines 2. **Filter Lines**
1. Click the "Filters" button in the search bar - Use the search bar to filter by:
2. Select "Hide Reconciled" to show only unreconciled lines - Date
3. To show all lines again, select "Show All" or remove the filter - Partner
- Journal
- Amount (Income/Expense)
3. **Select Lines for Reconciliation**
- Check the boxes next to the lines you want to reconcile
- The total amount widget will show the sum automatically
4. **Initiate Reconciliation**
- Click the `Action` menu
- Select `Reconcile Selected Lines`
- OR click the `Reconcile` button in the form view
5. **Match Journal Entries**
- The wizard will open showing available journal entries
- Select the appropriate journal entry to match
- Confirm the reconciliation
## Technical Details ## Technical Details
### Computed Fields ### Module Structure
- `is_reconciled`: Boolean field that computes reconciliation status based on the presence of "Reconciliation:" in linked journal entry names
### Search Filters ```
- "Hide Reconciled": `domain="[('is_reconciled', '=', False)]"` bank_statement_reconciliation/
- "Show All": `domain="[]"` ├── __init__.py
├── __manifest__.py
├── README.md
├── models/
│ ├── __init__.py
│ ├── account_bank_statement_line.py
│ ├── bank_statement_line.py
│ └── bank_statement_selector.py
├── views/
│ ├── bank_statement_line_views.xml
│ ├── bank_statement_selector_views.xml
│ └── menu.xml
├── wizards/
│ ├── __init__.py
│ ├── bank_reconcile_wizard.py
│ └── bank_reconcile_wizard_views.xml
├── security/
│ └── ir.model.access.csv
└── static/
└── src/
├── js/
│ ├── bank_statement_total_widget.js
│ ├── bank_statement_list_controller.js
│ └── bank_statement_list_view.js
└── xml/
├── bank_statement_total_widget.xml
└── bank_statement_list_view.xml
```
### JavaScript Components
#### BankStatementTotalWidget
- **Type**: OWL Component
- **Purpose**: Displays total amount of selected lines
- **Props**:
- `resIds`: Array of selected record IDs
- **Features**:
- Reactive state management
- Automatic updates on selection change
- Currency formatting
- Visual styling based on amount (positive/negative)
#### BankStatementListController
- **Type**: List Controller Extension
- **Purpose**: Extends standard list controller with custom functionality
- **Features**:
- Provides selected records to the widget
- Integrates widget into list view layout
### XML Views
#### Tree View Enhancement
- Added `js_class="bank_statement_list"` attribute
- Enabled multi-edit mode
- Added monetary widget for amount field
- Added currency field for proper formatting
- Includes sum totals in footer
### Dependencies ### Dependencies
- `account`: Standard Odoo accounting module
- `base`: Standard Odoo base module - `account`: Core accounting module
- `base`: Odoo base module
- `web`: Web framework for JavaScript components
## Customization
### Modifying the Widget Appearance
Edit [`bank_statement_total_widget.xml`](static/src/xml/bank_statement_total_widget.xml) to customize:
- Layout
- Colors
- Icons
- Styling
### Extending Functionality
To add more calculations or features:
1. Edit [`bank_statement_total_widget.js`](static/src/js/bank_statement_total_widget.js)
2. Add new computed properties or methods
3. Update the template to display new information
Example - Add average calculation:
```javascript
get averageAmount() {
if (this.state.selectedCount === 0) return 0;
return this.state.totalAmount / this.state.selectedCount;
}
```
## Troubleshooting ## Troubleshooting
### Common Issues ### Widget Not Appearing
1. **Lines not hiding when filter applied**: Ensure the `is_reconciled` computed field is properly stored in the database
2. **Reconciliation errors**: Verify that journal entries have proper account mappings
3. **Performance issues**: For large datasets, use the filter to reduce the number of displayed lines
4. **Automatic reconciliation not working**: Check that journal entry lines have matching accounts and sufficient balances for reconciliation
5. **Reconciliation journal created but not properly linked**: Verify that the reconciliation process is correctly matching accounts between the bank line and journal entry line
### Support 1. **Clear Browser Cache**
For issues or feature requests, please contact your Odoo administrator or module developer. - Hard refresh: `Ctrl+Shift+R` (Windows/Linux) or `Cmd+Shift+R` (Mac)
## Version 2. **Update Assets**
1.0.0 - Initial release for Odoo 17 - Navigate to: `Settings > Technical > User Interface > Views`
- Click `Regenerate Assets Bundles`
3. **Check JavaScript Console**
- Press `F12` to open developer tools
- Look for any JavaScript errors
### Incorrect Totals
1. **Verify Currency Settings**
- Ensure all selected lines use the same currency
- Check currency rounding settings
2. **Check Data Integrity**
- Verify amount fields are populated correctly
- Check for null or undefined values
## Support
For issues, questions, or contributions:
- **Author**: Suherdy Yacob
- **Version**: 17.0.1.0.0
- **Odoo Version**: 17.0
## Changelog
### Version 17.0.1.0.0
- Added total selected amount widget in list view header
- Enhanced tree view with monetary formatting
- Improved user experience with visual indicators
- Added real-time calculation of selected lines
- Implemented modern OWL-based JavaScript components
### Initial Version
- Basic bank statement reconciliation functionality
- Wizard-based reconciliation process
- Multi-line selection support
- Journal entry matching
## License ## License
This module is released under the Odoo Enterprise License.
## Author This module follows the same license as Odoo.
Custom Development Team
## Credits ## Screenshots
Developed for Odoo 17 accounting enhancements.
### Total Amount Widget in Action
When you select multiple bank statement lines, the widget appears at the top showing:
- Number of selected items
- Total sum with currency
- Color-coded amount (green for positive, red for negative)
### List View Enhancements
- Checkboxes for multi-selection
- Amount column with monetary formatting
- Footer totals for all visible records
- Clean, modern interface
## Future Enhancements
Planned features for future releases:
- [ ] Support for multi-currency reconciliation
- [ ] Advanced filtering options
- [ ] Reconciliation history tracking
- [ ] Export selected lines to Excel
- [ ] Batch operations for common reconciliation patterns
- [ ] AI-powered suggestion for matching entries

View File

@ -11,11 +11,13 @@
- Select multiple bank lines to reconcile - Select multiple bank lines to reconcile
- Wizard to select journal entries for reconciliation - Wizard to select journal entries for reconciliation
- Automatic creation of reconciliation journal entries - Automatic creation of reconciliation journal entries
- Total selected amount widget in list view header
""", """,
'author': 'Suherdy Yacob', 'author': 'Suherdy Yacob',
'depends': [ 'depends': [
'account', 'account',
'base', 'base',
'web',
], ],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
@ -24,6 +26,7 @@
'wizards/bank_reconcile_wizard_views.xml', 'wizards/bank_reconcile_wizard_views.xml',
'views/menu.xml', 'views/menu.xml',
], ],
# Tree view includes sum="Total Amount" footer for displaying totals
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,
} }

View File

@ -0,0 +1,29 @@
/** @odoo-module **/
import { ListController } from "@web/views/list/list_controller";
import { BankStatementTotalWidget } from "./bank_statement_total_widget";
import { patch } from "@web/core/utils/patch";
patch(ListController.prototype, {
get selectedRecords() {
if (this.props.resModel === "account.bank.statement.line") {
return this.model.root.selection.map(record => record.resId);
}
return [];
}
});
export class BankStatementListController extends ListController {
setup() {
super.setup();
}
get selectedRecords() {
return this.model.root.selection.map(record => record.resId);
}
}
BankStatementListController.components = {
...ListController.components,
BankStatementTotalWidget,
};

View File

@ -0,0 +1,12 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { listView } from "@web/views/list/list_view";
import { BankStatementListController } from "./bank_statement_list_controller";
export const bankStatementListView = {
...listView,
Controller: BankStatementListController,
};
registry.category("views").add("bank_statement_list", bankStatementListView);

View File

@ -0,0 +1,81 @@
/** @odoo-module **/
import { Component, useState, onWillStart, onWillUpdateProps } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
export class BankStatementTotalWidget extends Component {
setup() {
this.orm = useService("orm");
this.state = useState({
totalAmount: 0,
selectedCount: 0,
currency: null,
});
onWillStart(async () => {
await this.updateTotals();
});
onWillUpdateProps(async (nextProps) => {
await this.updateTotals(nextProps);
});
}
async updateTotals(props = this.props) {
const resIds = props.resIds || [];
if (resIds.length === 0) {
this.state.totalAmount = 0;
this.state.selectedCount = 0;
this.state.currency = null;
return;
}
try {
const records = await this.orm.searchRead(
"account.bank.statement.line",
[["id", "in", resIds]],
["amount", "currency_id"]
);
let total = 0;
let currency = null;
records.forEach(record => {
total += record.amount || 0;
if (!currency && record.currency_id) {
currency = record.currency_id;
}
});
this.state.totalAmount = total;
this.state.selectedCount = records.length;
this.state.currency = currency;
} catch (error) {
console.error("Error fetching bank statement totals:", error);
this.state.totalAmount = 0;
this.state.selectedCount = 0;
this.state.currency = null;
}
}
get formattedTotal() {
if (!this.state.currency) {
return this.state.totalAmount.toFixed(2);
}
// Format with currency symbol if available
return this.state.totalAmount.toFixed(2);
}
get displayClass() {
return this.state.totalAmount < 0 ? 'text-danger' : 'text-success';
}
}
BankStatementTotalWidget.template = "bank_statement_reconciliation.BankStatementTotalWidget";
BankStatementTotalWidget.props = {
resIds: { type: Array, optional: true },
};
registry.category("view_widgets").add("bank_statement_total_widget", BankStatementTotalWidget);

View File

@ -0,0 +1,32 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { ListController } from "@web/views/list/list_controller";
patch(ListController.prototype, {
/**
* Compute total amount of selected bank statement lines
*/
get selectedBankLinesTotal() {
if (this.props.resModel !== "account.bank.statement.line") {
return null;
}
const selection = this.model.root.selection;
if (!selection || selection.length === 0) {
return null;
}
let total = 0;
selection.forEach(record => {
const amount = record.data.amount || 0;
total += amount;
});
return {
count: selection.length,
total: total.toFixed(2),
currency: selection[0]?.data.currency_id?.[1] || 'IDR'
};
}
});

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="bank_statement_reconciliation.BankStatementListController" t-inherit="web.ListView" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_content')]" position="before">
<div class="o_bank_statement_header_widget p-3 mx-3" t-if="props.resModel === 'account.bank.statement.line' and selectedRecords.length > 0">
<BankStatementTotalWidget resIds="selectedRecords"/>
</div>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="bank_statement_reconciliation.SelectionInfo" t-inherit="web.ListView" t-inherit-mode="extension">
<xpath expr="//Layout" position="inside">
<t t-set-slot="layout-actions">
<div t-if="model.root.selection.length > 0" class="alert alert-info d-flex align-items-center gap-3 m-2" role="alert">
<i class="fa fa-info-circle fa-lg"/>
<strong t-esc="model.root.selection.length"/> lines selected
<span class="mx-2">|</span>
Total: <strong t-esc="model.root.selection.reduce((sum, rec) => sum + (rec.data.amount || 0), 0).toFixed(2)"/>
</div>
</t>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="bank_statement_reconciliation.BankStatementTotalWidget">
<div class="o_bank_statement_total_widget d-flex align-items-center gap-3 px-3 py-2 bg-light border rounded">
<div class="d-flex align-items-center gap-2">
<i class="fa fa-check-square-o text-primary" style="font-size: 1.2em;"/>
<span class="fw-bold">Selected Lines:</span>
<span class="badge bg-primary" t-esc="state.selectedCount"/>
</div>
<div class="vr"/>
<div class="d-flex align-items-center gap-2">
<i class="fa fa-calculator text-info" style="font-size: 1.2em;"/>
<span class="fw-bold">Total Amount:</span>
<span t-attf-class="badge {{displayClass}} fs-6 px-3" t-esc="formattedTotal"/>
<span t-if="state.currency" class="text-muted small" t-esc="state.currency[1]"/>
</div>
</div>
</t>
</templates>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="bank_statement_reconciliation.SelectedAmountBadge" t-inherit="web.ListController" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_cp_action_menus')]" position="before">
<div t-if="selectedBankLinesTotal" class="badge bg-info text-dark fs-5 px-3 py-2 me-2">
<i class="fa fa-calculator me-1"/>
Total: Rp <t t-esc="selectedBankLinesTotal.total"/>
</div>
</xpath>
</t>
</templates>

View File

@ -5,6 +5,7 @@
<field name="name">Bank Statement Lines</field> <field name="name">Bank Statement Lines</field>
<field name="res_model">account.bank.statement.line</field> <field name="res_model">account.bank.statement.line</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="context">{'tree_view_ref': 'bank_statement_reconciliation.view_account_bank_statement_line_tree'}</field>
<field name="help" type="html"> <field name="help" type="html">
<p class="o_view_nocontent_smiling_face"> <p class="o_view_nocontent_smiling_face">
Select a bank journal to view its statement lines Select a bank journal to view its statement lines
@ -37,11 +38,14 @@
<field name="name">account.bank.statement.line.tree</field> <field name="name">account.bank.statement.line.tree</field>
<field name="model">account.bank.statement.line</field> <field name="model">account.bank.statement.line</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Bank Statement Lines" create="0" delete="0" decoration-danger="amount &lt; 0" decoration-muted="move_id and 'Reconciliation:' in move_id.name"> <tree string="Bank Statement Lines" create="0" delete="0" decoration-danger="amount &lt; 0" decoration-muted="move_id and 'Reconciliation:' in move_id.name" multi_edit="1">
<field name="company_id" column_invisible="True"/>
<field name="currency_id" column_invisible="True"/>
<field name="suitable_journal_ids" column_invisible="True"/>
<field name="date"/> <field name="date"/>
<field name="name"/> <field name="name"/>
<field name="partner_id"/> <field name="partner_id"/>
<field name="amount"/> <field name="amount" sum="Total Amount" widget="monetary"/>
<field name="journal_id"/> <field name="journal_id"/>
<field name="statement_id"/> <field name="statement_id"/>
<field name="move_id"/> <field name="move_id"/>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<!-- Menu item for Bank Statement Reconciliation --> <!-- Top level menu for Bank Statement Reconciliation -->
<menuitem id="menu_bank_statement_root" <menuitem id="menu_bank_statement_root"
name="Bank Reconciliation" name="Bank Statement Reconciliation"
sequence="10" sequence="50"
web_icon="account,static/description/icon.png"
groups="account.group_account_user"/> groups="account.group_account_user"/>
<menuitem id="menu_bank_statement_lines" <menuitem id="menu_bank_statement_lines"

View File

@ -42,7 +42,7 @@ class BankReconcileWizard(models.TransientModel):
# Check if any of the selected bank lines are already reconciled # Check if any of the selected bank lines are already reconciled
for bank_line in self.bank_line_ids: for bank_line in self.bank_line_ids:
if bank_line.move_id and 'Reconciliation:' in bank_line.move_id.ref: if bank_line.move_id and bank_line.move_id.ref and 'Reconciliation:' in bank_line.move_id.ref:
raise UserError(f"Bank statement line '{bank_line.ref}' has already been reconciled and cannot be reconciled again.") raise UserError(f"Bank statement line '{bank_line.ref}' has already been reconciled and cannot be reconciled again.")
# Process each selected bank line individually # Process each selected bank line individually