diff --git a/account_statement_import_required_fields/README.rst b/account_statement_import_required_fields/README.rst new file mode 100644 index 0000000000..3f6d4fc66f --- /dev/null +++ b/account_statement_import_required_fields/README.rst @@ -0,0 +1,186 @@ +======================================== +Account Statement Import Required Fields +======================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3759e889c444ee30225568d494d864a150c07386c44f6796f73d3063477a2ca7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github + :target: https://github.com/OCA/bank-statement-import/tree/18.0/account_statement_import_required_fields + :alt: OCA/bank-statement-import +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/bank-statement-import-18-0/bank-statement-import-18-0-account_statement_import_required_fields + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/bank-statement-import&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to enforce mandatory columns when importing bank +statements via TXT/CSV/XLSX in Odoo. It extends the mapping profiles +from the Odoo Community Association (OCA) module +``account_statement_import_sheet_file``. + +-------------- + +Key Features +------------ + +1. **Mandatory Column Configuration** + + - In your Bank Statement Sheet Mappings, you can designate specific + mapped columns as "Mandatory Mapping Columns". + - Only columns that you have mapped (e.g., Date, Description, Amount) + will be available to select. + - If a column is cleared from the mapping, it is automatically + deselected from the mandatory fields list. + +2. **Pre-Import Data Validation** + + - When importing a file, the parser intercepts the raw TXT/CSV/XLSX + cells before Odoo processes them. + - If any of the mandatory columns are completely empty or blank in + the file for a particular line, the import is halted immediately. + - Generates a clear, user-friendly error message detailing exactly + which required columns are missing data. + +3. **Smart Debit/Credit Amount Handling** + + - Bank statements often represent amounts by placing values + exclusively in either a Debit column or a Credit column. + - If you set **both** the *Debit amount column* and *Credit amount + column* as mandatory, the module intelligently requires that **at + least one** of them must have a value on any given row. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +This document explains how to configure mandatory columns for your bank +statement imports. + +-------------- + +Configuring Mandatory Columns +----------------------------- + +To enforce that specific mapped columns are present during bank +statement imports: + +1. Go to **Accounting > Configuration > Statement Sheet Mappings** (or + **Invoicing > Configuration > Statement Sheet Mappings** depending on + your installed apps). +2. Open an existing mapping profile or create a new one according to + your bank's file format. +3. Under the **Columns** section, fill in the names/indexes of the + columns you are mapping (e.g., ``Timestamp Column`` = Date, + ``Amount column`` = Amount). +4. Locate the **Required Fields** (or **Mandatory Mapping Columns**) + section on the form. +5. Click to add fields. *Note: Only columns that you have mapped in step + 3 will appear as available options.* +6. Select all the columns that you want to enforce as mandatory for + every row in the imported file. + +-------------- + +Special Configuration: Debit and Credit Columns +----------------------------------------------- + +If your bank statement format splits amounts into separate Debit and +Credit columns rather than a single Amount column, you can enforce them +intelligently: + +1. Map both **Debit amount column** and **Credit amount column** in the + Columns section. +2. Add **both** of them to the **Mandatory Mapping Columns** field. +3. **Result**: The module understands that a transaction is usually + either a debit or a credit. During import, the parser will verify + that **at least one** of these two fields has a value on every single + row. If a row is entirely blank for both debit and credit, the import + will be safely halted. + +Usage +===== + +To use this module, follow these steps: + +1. Export your bank statement from your online banking portal in **CSV** + or **XLSX** or **TXT** format. +2. In Odoo, navigate to your Accounting Dashboard and click **Import + Statement** on the relevant bank journal. +3. Upload your statement file and select the **Sheet Mapping** profile + you configured with your Mandatory Columns. +4. Click **Import**. +5. **Validation in Action**: Odoo will scan the file before creating any + records. If any row is missing data in one of the columns you marked + as mandatory, the import will be safely halted. +6. An error message will appear detailing exactly which required columns + are missing data. Correct your file (or remove the junk row) and + retry the import! + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Heliconia Solutions Pvt. Ltd. + +Contributors +------------ + +- `Heliconia Solutions Pvt. Ltd. `__ + + - Bhavesh Heliconia + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-Bhavesh Heliconia| image:: https://github.com/Bhavesh Heliconia.png?size=40px + :target: https://github.com/Bhavesh Heliconia + :alt: Bhavesh Heliconia + +Current `maintainer `__: + +|maintainer-Bhavesh Heliconia| + +This module is part of the `OCA/bank-statement-import `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_statement_import_required_fields/__init__.py b/account_statement_import_required_fields/__init__.py new file mode 100644 index 0000000000..39b73f082b --- /dev/null +++ b/account_statement_import_required_fields/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/account_statement_import_required_fields/__manifest__.py b/account_statement_import_required_fields/__manifest__.py new file mode 100644 index 0000000000..23eafb2973 --- /dev/null +++ b/account_statement_import_required_fields/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Account Statement Import Required Fields", + "summary": "Enforce mandatory columns during bank statement import (TXT/CSV/XLSX)", + "version": "18.0.1.0.0", + "category": "Accounting", + "author": "Heliconia Solutions Pvt. Ltd., Odoo Community Association (OCA)", + "website": "https://github.com/OCA/bank-statement-import", + "license": "AGPL-3", + "depends": [ + "account", + "account_statement_import_sheet_file", + ], + "data": [ + "views/account_statement_import_sheet_mapping_view.xml", + ], + "maintainers": ["Bhavesh Heliconia"], + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/account_statement_import_required_fields/models/__init__.py b/account_statement_import_required_fields/models/__init__.py new file mode 100644 index 0000000000..155436a75c --- /dev/null +++ b/account_statement_import_required_fields/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_statement_import_sheet_mapping +from . import account_statement_import_sheet_parser diff --git a/account_statement_import_required_fields/models/account_statement_import_sheet_mapping.py b/account_statement_import_required_fields/models/account_statement_import_sheet_mapping.py new file mode 100644 index 0000000000..15029bfd17 --- /dev/null +++ b/account_statement_import_required_fields/models/account_statement_import_sheet_mapping.py @@ -0,0 +1,92 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountStatementImportRequiredField(models.Model): + _inherit = "account.statement.import.sheet.mapping" + + available_field_ids = fields.Many2many( + comodel_name="ir.model.fields", + compute="_compute_available_field_ids", + ) + + required_field_ids = fields.Many2many( + comodel_name="ir.model.fields", + relation="account_stmt_mapping_req_fld_rel", + column1="mapping_id", + column2="field_id", + string="Mandatory Mapping Columns", + help="Select which mapped columns must have a value in every row of " + "the imported file. " + "\nSpecial case: If you select both Debit and Credit columns, " + "at least one of them must be present in a given row.", + domain="[('id', 'in', available_field_ids)]", + ) + + @api.depends( + "timestamp_column", + "currency_column", + "amount_column", + "amount_debit_column", + "amount_credit_column", + "balance_column", + "original_currency_column", + "original_amount_column", + "debit_credit_column", + "transaction_id_column", + "description_column", + "notes_column", + "reference_column", + "partner_name_column", + "bank_name_column", + "bank_account_column", + ) + def _compute_available_field_ids(self): + column_fields = [ + "timestamp_column", + "currency_column", + "amount_column", + "amount_debit_column", + "amount_credit_column", + "balance_column", + "original_currency_column", + "original_amount_column", + "debit_credit_column", + "transaction_id_column", + "description_column", + "notes_column", + "reference_column", + "partner_name_column", + "bank_name_column", + "bank_account_column", + ] + for record in self: + valid_field_names = [] + for field_name in column_fields: + if getattr(record, field_name): + valid_field_names.append(field_name) + + if valid_field_names: + fields_records = ( + self.env["ir.model.fields"] + .sudo() + .search( + [ + ("model", "=", "account.statement.import.sheet.mapping"), + ("name", "in", valid_field_names), + ] + ) + ) + record.available_field_ids = fields_records + else: + record.available_field_ids = False + + @api.onchange("available_field_ids") + def _onchange_available_field_ids(self): + for record in self: + if record.required_field_ids: + record.required_field_ids = ( + record.required_field_ids & record.available_field_ids + ) diff --git a/account_statement_import_required_fields/models/account_statement_import_sheet_parser.py b/account_statement_import_required_fields/models/account_statement_import_sheet_parser.py new file mode 100644 index 0000000000..c8b760d4c7 --- /dev/null +++ b/account_statement_import_required_fields/models/account_statement_import_sheet_parser.py @@ -0,0 +1,79 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.exceptions import ValidationError + + +class AccountStatementImportSheetParser(models.TransientModel): + _inherit = "account.statement.import.sheet.parser" + + def _parse_rows(self, mapping, currency_code, data, columns): + missing_cols = set() + parser = self + if mapping.required_field_ids: + required_field_names = mapping.required_field_ids.mapped("name") + parser = self.with_context( + mapping_required_fields=required_field_names, + missing_required_columns=missing_cols, + ) + + rows = super(AccountStatementImportSheetParser, parser)._parse_rows( + mapping, currency_code, data, columns + ) + + if not rows: + return rows + + if missing_cols: + missing_field_labels = [ + f"• {getattr(mapping, col)}" for col in missing_cols + ] + raise ValidationError( + self.env._( + "The bank statement could not be imported. " + "The following required columns are missing data in one or " + "more rows:\n\n" + "%(missing_field_names)s\n\n" + "Please verify your file and ensure these columns have values " + "where expected.", + missing_field_names="\n".join(missing_field_labels), + ) + ) + return rows + + def _get_values_from_column(self, values, columns, column_name): + res = super()._get_values_from_column(values, columns, column_name) + + required_fields = self.env.context.get("mapping_required_fields", []) + if required_fields and column_name == "timestamp_column": + # 'timestamp_column' is an anchor, evaluated once per row. + row_missing = [] + for req_col in required_fields: + if not columns.get(req_col): + continue + val = super()._get_values_from_column(values, columns, req_col) + if val is None or (isinstance(val, str) and str(val).strip() == ""): + row_missing.append(req_col) + + # Special case for debit and credit amount columns + if ( + "amount_debit_column" in required_fields + and "amount_credit_column" in required_fields + ): + has_debit = "amount_debit_column" not in row_missing + has_credit = "amount_credit_column" not in row_missing + if has_debit or has_credit: + # At least one has a value, so remove both from the missing list + if "amount_debit_column" in row_missing: + row_missing.remove("amount_debit_column") + if "amount_credit_column" in row_missing: + row_missing.remove("amount_credit_column") + + if row_missing: + missing_set = self.env.context.get("missing_required_columns") + if missing_set is not None: + for col in row_missing: + missing_set.add(col) + + return res diff --git a/account_statement_import_required_fields/pyproject.toml b/account_statement_import_required_fields/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/account_statement_import_required_fields/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_statement_import_required_fields/readme/CONFIGURE.md b/account_statement_import_required_fields/readme/CONFIGURE.md new file mode 100644 index 0000000000..fb6acfe3b2 --- /dev/null +++ b/account_statement_import_required_fields/readme/CONFIGURE.md @@ -0,0 +1,24 @@ +This document explains how to configure mandatory columns for your bank statement imports. + +--- + +## Configuring Mandatory Columns + +To enforce that specific mapped columns are present during bank statement imports: + +1. Go to **Accounting > Configuration > Statement Sheet Mappings** (or **Invoicing > Configuration > Statement Sheet Mappings** depending on your installed apps). +2. Open an existing mapping profile or create a new one according to your bank's file format. +3. Under the **Columns** section, fill in the names/indexes of the columns you are mapping (e.g., `Timestamp Column` = Date, `Amount column` = Amount). +4. Locate the **Required Fields** (or **Mandatory Mapping Columns**) section on the form. +5. Click to add fields. *Note: Only columns that you have mapped in step 3 will appear as available options.* +6. Select all the columns that you want to enforce as mandatory for every row in the imported file. + +--- + +## Special Configuration: Debit and Credit Columns + +If your bank statement format splits amounts into separate Debit and Credit columns rather than a single Amount column, you can enforce them intelligently: + +1. Map both **Debit amount column** and **Credit amount column** in the Columns section. +2. Add **both** of them to the **Mandatory Mapping Columns** field. +3. **Result**: The module understands that a transaction is usually either a debit or a credit. During import, the parser will verify that **at least one** of these two fields has a value on every single row. If a row is entirely blank for both debit and credit, the import will be safely halted. diff --git a/account_statement_import_required_fields/readme/CONTRIBUTORS.md b/account_statement_import_required_fields/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..2f6060845e --- /dev/null +++ b/account_statement_import_required_fields/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Heliconia Solutions Pvt. Ltd.](https://www.heliconia.io) + - Bhavesh Heliconia diff --git a/account_statement_import_required_fields/readme/DESCRIPTION.md b/account_statement_import_required_fields/readme/DESCRIPTION.md new file mode 100644 index 0000000000..51fb5248e2 --- /dev/null +++ b/account_statement_import_required_fields/readme/DESCRIPTION.md @@ -0,0 +1,19 @@ +This module allows you to enforce mandatory columns when importing bank statements via TXT/CSV/XLSX in Odoo. It extends the mapping profiles from the Odoo Community Association (OCA) module `account_statement_import_sheet_file`. + +--- + +## Key Features + +1. **Mandatory Column Configuration** + - In your Bank Statement Sheet Mappings, you can designate specific mapped columns as "Mandatory Mapping Columns". + - Only columns that you have mapped (e.g., Date, Description, Amount) will be available to select. + - If a column is cleared from the mapping, it is automatically deselected from the mandatory fields list. + +2. **Pre-Import Data Validation** + - When importing a file, the parser intercepts the raw TXT/CSV/XLSX cells before Odoo processes them. + - If any of the mandatory columns are completely empty or blank in the file for a particular line, the import is halted immediately. + - Generates a clear, user-friendly error message detailing exactly which required columns are missing data. + +3. **Smart Debit/Credit Amount Handling** + - Bank statements often represent amounts by placing values exclusively in either a Debit column or a Credit column. + - If you set **both** the *Debit amount column* and *Credit amount column* as mandatory, the module intelligently requires that **at least one** of them must have a value on any given row. diff --git a/account_statement_import_required_fields/readme/USAGE.md b/account_statement_import_required_fields/readme/USAGE.md new file mode 100644 index 0000000000..8191848bf6 --- /dev/null +++ b/account_statement_import_required_fields/readme/USAGE.md @@ -0,0 +1,8 @@ +To use this module, follow these steps: + +1. Export your bank statement from your online banking portal in **CSV** or **XLSX** or **TXT** format. +2. In Odoo, navigate to your Accounting Dashboard and click **Import Statement** on the relevant bank journal. +3. Upload your statement file and select the **Sheet Mapping** profile you configured with your Mandatory Columns. +4. Click **Import**. +5. **Validation in Action**: Odoo will scan the file before creating any records. If any row is missing data in one of the columns you marked as mandatory, the import will be safely halted. +6. An error message will appear detailing exactly which required columns are missing data. Correct your file (or remove the junk row) and retry the import! diff --git a/account_statement_import_required_fields/static/description/icon.png b/account_statement_import_required_fields/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/account_statement_import_required_fields/static/description/icon.png differ diff --git a/account_statement_import_required_fields/static/description/index.html b/account_statement_import_required_fields/static/description/index.html new file mode 100644 index 0000000000..c79c54e41b --- /dev/null +++ b/account_statement_import_required_fields/static/description/index.html @@ -0,0 +1,520 @@ + + + + + +Account Statement Import Required Fields + + + +
+

Account Statement Import Required Fields

+ + +

Beta License: AGPL-3 OCA/bank-statement-import Translate me on Weblate Try me on Runboat

+

This module allows you to enforce mandatory columns when importing bank +statements via TXT/CSV/XLSX in Odoo. It extends the mapping profiles +from the Odoo Community Association (OCA) module +account_statement_import_sheet_file.

+
+
+

Key Features

+
    +
  1. Mandatory Column Configuration
      +
    • In your Bank Statement Sheet Mappings, you can designate specific +mapped columns as “Mandatory Mapping Columns”.
    • +
    • Only columns that you have mapped (e.g., Date, Description, Amount) +will be available to select.
    • +
    • If a column is cleared from the mapping, it is automatically +deselected from the mandatory fields list.
    • +
    +
  2. +
  3. Pre-Import Data Validation
      +
    • When importing a file, the parser intercepts the raw TXT/CSV/XLSX +cells before Odoo processes them.
    • +
    • If any of the mandatory columns are completely empty or blank in +the file for a particular line, the import is halted immediately.
    • +
    • Generates a clear, user-friendly error message detailing exactly +which required columns are missing data.
    • +
    +
  4. +
  5. Smart Debit/Credit Amount Handling
      +
    • Bank statements often represent amounts by placing values +exclusively in either a Debit column or a Credit column.
    • +
    • If you set both the Debit amount column and Credit amount +column as mandatory, the module intelligently requires that at +least one of them must have a value on any given row.
    • +
    +
  6. +
+

Table of contents

+ +
+

Configuration

+

This document explains how to configure mandatory columns for your bank +statement imports.

+
+
+
+
+

Configuring Mandatory Columns

+

To enforce that specific mapped columns are present during bank +statement imports:

+
    +
  1. Go to Accounting > Configuration > Statement Sheet Mappings (or +Invoicing > Configuration > Statement Sheet Mappings depending on +your installed apps).
  2. +
  3. Open an existing mapping profile or create a new one according to +your bank’s file format.
  4. +
  5. Under the Columns section, fill in the names/indexes of the +columns you are mapping (e.g., Timestamp Column = Date, +Amount column = Amount).
  6. +
  7. Locate the Required Fields (or Mandatory Mapping Columns) +section on the form.
  8. +
  9. Click to add fields. Note: Only columns that you have mapped in step +3 will appear as available options.
  10. +
  11. Select all the columns that you want to enforce as mandatory for +every row in the imported file.
  12. +
+
+
+
+

Special Configuration: Debit and Credit Columns

+

If your bank statement format splits amounts into separate Debit and +Credit columns rather than a single Amount column, you can enforce them +intelligently:

+
    +
  1. Map both Debit amount column and Credit amount column in the +Columns section.
  2. +
  3. Add both of them to the Mandatory Mapping Columns field.
  4. +
  5. Result: The module understands that a transaction is usually +either a debit or a credit. During import, the parser will verify +that at least one of these two fields has a value on every single +row. If a row is entirely blank for both debit and credit, the import +will be safely halted.
  6. +
+
+

Usage

+

To use this module, follow these steps:

+
    +
  1. Export your bank statement from your online banking portal in CSV +or XLSX or TXT format.
  2. +
  3. In Odoo, navigate to your Accounting Dashboard and click Import +Statement on the relevant bank journal.
  4. +
  5. Upload your statement file and select the Sheet Mapping profile +you configured with your Mandatory Columns.
  6. +
  7. Click Import.
  8. +
  9. Validation in Action: Odoo will scan the file before creating any +records. If any row is missing data in one of the columns you marked +as mandatory, the import will be safely halted.
  10. +
  11. An error message will appear detailing exactly which required columns +are missing data. Correct your file (or remove the junk row) and +retry the import!
  12. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+
+
+

Authors

+
    +
  • Heliconia Solutions Pvt. Ltd.
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

Bhavesh Heliconia

+

This module is part of the OCA/bank-statement-import project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+ + diff --git a/account_statement_import_required_fields/tests/__init__.py b/account_statement_import_required_fields/tests/__init__.py new file mode 100644 index 0000000000..f0685eeb63 --- /dev/null +++ b/account_statement_import_required_fields/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_import_required_fields diff --git a/account_statement_import_required_fields/tests/test_import_required_fields.py b/account_statement_import_required_fields/tests/test_import_required_fields.py new file mode 100644 index 0000000000..a313b9c928 --- /dev/null +++ b/account_statement_import_required_fields/tests/test_import_required_fields.py @@ -0,0 +1,159 @@ +# Copyright 2026 Heliconia Solutions Pvt. Ltd. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from csv import reader +from io import StringIO + +from odoo import Command +from odoo.exceptions import ValidationError +from odoo.tests import common + + +class TestImportRequiredFields(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.mapping = cls.env["account.statement.import.sheet.mapping"].create( + { + "name": "Test Mapping", + "timestamp_format": "%Y-%m-%d", + "timestamp_column": "Date", + "description_column": "Description", + "amount_type": "simple_value", + "amount_column": "Amount", + "header_lines_skip_count": 1, + } + ) + # Set required fields + date_field = cls.env["ir.model.fields"].search( + [ + ("model", "=", "account.statement.import.sheet.mapping"), + ("name", "=", "timestamp_column"), + ], + limit=1, + ) + cls.mapping.required_field_ids = [Command.set([date_field.id])] + cls.parser = cls.env["account.statement.import.sheet.parser"].create({}) + + def _get_columns(self, extra=None): + cols = { + "timestamp_column": [], + "currency_column": [], + "amount_column": [], + "amount_debit_column": [], + "amount_credit_column": [], + "balance_column": [], + "original_currency_column": [], + "original_amount_column": [], + "debit_credit_column": [], + "transaction_id_column": [], + "description_column": [], + "notes_column": [], + "reference_column": [], + "partner_name_column": [], + "bank_name_column": [], + "bank_account_column": [], + } + if extra: + cols.update(extra) + return cols + + def test_import_missing_required_field(self): + """Test import with a missing required field (Description)""" + raw_data = b"Date,Description,Amount\n2026-03-25,,100.0" + csv_reader = reader(StringIO(raw_data.decode())) + next(csv_reader) # Skip header + columns = self._get_columns( + {"timestamp_column": [0], "description_column": [1], "amount_column": [2]} + ) + desc_field = self.env["ir.model.fields"].search( + [ + ("model", "=", "account.statement.import.sheet.mapping"), + ("name", "=", "description_column"), + ], + limit=1, + ) + self.mapping.required_field_ids = [Command.set([desc_field.id])] + with self.assertRaises(ValidationError) as e: + self.parser._parse_rows( + self.mapping, "EUR", (csv_reader, raw_data), columns + ) + + self.assertIn( + "The following required columns are missing data", e.exception.args[0] + ) + self.assertIn("• Description", e.exception.args[0]) + + def test_import_with_required_field(self): + """Test import with all required fields present""" + raw_data = b"Date,Description,Amount\n2026-03-25,Test line,100.0" + csv_reader = reader(StringIO(raw_data.decode())) + next(csv_reader) # Skip header + columns = self._get_columns( + {"timestamp_column": [0], "description_column": [1], "amount_column": [2]} + ) + rows = self.parser._parse_rows( + self.mapping, "EUR", (csv_reader, raw_data), columns + ) + self.assertEqual(len(rows), 1) + + def test_debit_credit_smart_handling(self): + """Test smart handling for Debit and Credit columns""" + self.mapping.write( + { + "amount_type": "distinct_credit_debit", + "amount_debit_column": "Debit", + "amount_credit_column": "Credit", + } + ) + debit_field = self.env["ir.model.fields"].search( + [ + ("model", "=", "account.statement.import.sheet.mapping"), + ("name", "=", "amount_debit_column"), + ], + limit=1, + ) + credit_field = self.env["ir.model.fields"].search( + [ + ("model", "=", "account.statement.import.sheet.mapping"), + ("name", "=", "amount_credit_column"), + ], + limit=1, + ) + self.mapping.required_field_ids = [ + Command.set([debit_field.id, credit_field.id]) + ] + columns = self._get_columns( + { + "timestamp_column": [0], + "amount_debit_column": [1], + "amount_credit_column": [2], + } + ) + + # Case 1: Both missing -> Should fail + raw_missing = b"Date,Debit,Credit\n2026-03-25,," + csv_missing = reader(StringIO(raw_missing.decode())) + next(csv_missing) # Skip header + with self.assertRaises(ValidationError): + self.parser._parse_rows( + self.mapping, "EUR", (csv_missing, raw_missing), columns + ) + + # Case 2: Only Debit present -> Should pass + raw_debit = b"Date,Debit,Credit\n2026-03-25,100.0," + csv_debit = reader(StringIO(raw_debit.decode())) + next(csv_debit) # Skip header + rows = self.parser._parse_rows( + self.mapping, "EUR", (csv_debit, raw_debit), columns + ) + self.assertEqual(len(rows), 1) + + # Case 3: Only Credit present -> Should pass + raw_credit = b"Date,Debit,Credit\n2026-03-25,,100.0" + csv_credit = reader(StringIO(raw_credit.decode())) + next(csv_credit) # Skip header + rows = self.parser._parse_rows( + self.mapping, "EUR", (csv_credit, raw_credit), columns + ) + self.assertEqual(len(rows), 1) diff --git a/account_statement_import_required_fields/views/account_statement_import_sheet_mapping_view.xml b/account_statement_import_required_fields/views/account_statement_import_sheet_mapping_view.xml new file mode 100644 index 0000000000..b1d3b4752b --- /dev/null +++ b/account_statement_import_required_fields/views/account_statement_import_sheet_mapping_view.xml @@ -0,0 +1,29 @@ + + + + account.statement.import.sheet.mapping.form.inherit.required.fields + account.statement.import.sheet.mapping + + + + + + + + + + +