diff --git a/.gitignore b/.gitignore
index 7ea6ec68ec2c5a6b4e552fddc0405ec2159ddadf..d5c686e955ffb227ad37e6fc7d239434469bf341 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,8 @@ venv
 *.pyc
 sage_validation/staticfiles
 sage_validation/frontend/node_modules
-.env
\ No newline at end of file
+.env
+docs/build
+db.sqlite3
+coverage.xml
+.coverage
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 5cb62b06f7730cd2c493e068b4f21a68c1df7e37..38af583cffad5bb6cc39c40f8c68f91a06da1544 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -12,17 +12,21 @@
 #
 import os
 import sys
-sys.path.insert(0, os.path.abspath('../sage-validation'))
 
+import django
+
+sys.path.insert(0, os.path.abspath("../../"))
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sage_validation.settings")
+django.setup()
 
 # -- Project information -----------------------------------------------------
 
-project = 'Sage Validation'
-copyright = '2024, GÉANT'
-author = 'GÉANT'
+project = "Sage Validation"
+copyright = "2024, GÉANT"
+author = "GÉANT"
 
 # The full version, including alpha/beta/rc tags
-release = '0.1'
+release = "0.1"
 
 
 # -- General configuration ---------------------------------------------------
@@ -31,13 +35,13 @@ release = '0.1'
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.napoleon',
-    'sphinx_autodoc_typehints',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+    "sphinx_autodoc_typehints",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -50,9 +54,9 @@ exclude_patterns = []
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'alabaster'
+html_theme = "alabaster"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
\ No newline at end of file
+html_static_path = ["_static"]
diff --git a/docs/source/file_validator/forms.rst b/docs/source/file_validator/forms.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4898ae2f3027a9df4686d04c3a4748af40d0d940
--- /dev/null
+++ b/docs/source/file_validator/forms.rst
@@ -0,0 +1,8 @@
+==========================================
+Sage Validation - File Validator Forms
+==========================================
+
+.. automodule:: sage_validation.file_validator.forms
+   :members:
+   :undoc-members:
+   :show-inheritance:
\ No newline at end of file
diff --git a/docs/source/file_validator/index.rst b/docs/source/file_validator/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2d169ebfccd7efcc772a84ad29b43ef165804cb6
--- /dev/null
+++ b/docs/source/file_validator/index.rst
@@ -0,0 +1,10 @@
+==========================================
+File Validator Module
+==========================================
+
+ .. toctree::
+   :maxdepth: 2
+   :caption: Components:
+
+   views
+   forms
\ No newline at end of file
diff --git a/docs/source/file_validator/views.rst b/docs/source/file_validator/views.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5d27fc39a43085f0b3b4802668b07c0a73613b27
--- /dev/null
+++ b/docs/source/file_validator/views.rst
@@ -0,0 +1,8 @@
+==========================================
+Sage Validation - File Validator Views
+==========================================
+
+.. automodule:: sage_validation.file_validator.views
+   :members:
+   :undoc-members:
+   :show-inheritance:
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 2af1b6c9e73ac03dce2472f8a752e770c08ea653..a050f1caa539762e6b3e00d3a3a887b0c9efdc0d 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -10,11 +10,5 @@ Welcome to Sage Validation's documentation!
    :maxdepth: 2
    :caption: Contents:
 
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+   validations
+   file_validator/index
\ No newline at end of file
diff --git a/docs/source/validations.rst b/docs/source/validations.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5f2042ced17773463685c99c6ca99a4beaafc69b
--- /dev/null
+++ b/docs/source/validations.rst
@@ -0,0 +1,67 @@
+====================================
+CSV Upload Validation Rules
+====================================
+
+The following table describes the validation rules applied when uploading a CSV file.
+
+.. list-table::
+   :header-rows: 1
+   :widths: 20 20 40 20
+
+   * - **Type**
+     - **Field**
+     - **Logic**
+     - **Comments**
+
+   * - Structural
+     - *(All Fields)*
+     - Ensure that all required field names are present.
+     - -
+   * - Update
+     - `...NominalAccountNumber` (*n)
+     - If value starts with `xx-`, map `NominalCostCentre` using `dbo.meoCostCentres` and retrieve `NominalAccountNumber` from `dbo.vw_xx-data`.
+     - Ensure the correct nominal code is assigned.
+
+   * - Checks
+     - AccountNumber
+     - Validate against `dbo.meoValidSuppliers`.
+     - Ensure the account exists in the valid suppliers list.
+
+   * - Checks
+     - Source
+     - Should always be `80`.
+     - Reject rows where `Source != 80`.
+
+   * - Checks
+     - SYSTraderTranType
+     - Should always be `4`.
+     - Reject rows where `SYSTraderTranType != 4`.
+
+   * - Update
+     - TransactionDate
+     - Set to today’s date (`dd/mm/yyyy`).
+     - Ensure all rows have a consistent transaction date.
+
+   * - Checks
+     - `...NominalAccountNumber` (*n)
+     - Validate against `[Nom]` in `dbo.meoNominal`.
+     - Ensure all nominal account numbers exist.
+
+   * - Checks
+     - `...NominalCostCentre` (*n)
+     - Validate against `dbo.meoCostCentres`.
+     - Ensure cost centres exist.
+
+   * - Checks
+     - Combination of `...NominalAccountNumber`, `...NominalCostCentre`, and `...NominalDepartment`
+     - Validate against `dbo.meoValidSageAccounts`.
+     - The combination of the three fields must be valid.
+
+   * - Checks
+     - `...TaxRate`
+     - Validate against `[Tax code]` in `dbo.meoValidVAT`.
+     - Ensure valid tax rates.
+
+    * - Checks
+    - `ChequeCurrencyName`, `ChequeToBankExchangeRate`, `ChequeValueInChequeCurrency`
+    - Cheque fields must be empty.
\ No newline at end of file
diff --git a/sage_validation/file_validator/forms.py b/sage_validation/file_validator/forms.py
index 6c243d2efe673bb8a4fe661a1852dfa6bb7cf776..ef6e9ec49fccd201adcc9a8ec30a53fbfee2e566 100644
--- a/sage_validation/file_validator/forms.py
+++ b/sage_validation/file_validator/forms.py
@@ -1,6 +1,7 @@
 """Forms for the file_validator app."""
 
 import csv
+import re
 from collections.abc import Sequence
 from typing import ClassVar
 
@@ -44,6 +45,9 @@ class CSVUploadForm(forms.Form):
         "TaxValue",
         "SYSTraderGenerationReasonType",
         "GoodsValueInBaseCurrency",
+        "ChequeCurrencyName",
+        "ChequeToBankExchangeRate",
+        "ChequeValueInChequeCurrency",
     ]
 
     repeating_columns: ClassVar[dict] = {
@@ -82,6 +86,7 @@ class CSVUploadForm(forms.Form):
         error_list.extend(self._validate_source_and_trader_type(data))
         error_list.extend(self._validate_nominal_analysis_account(data))
         error_list.extend(self._validate_nc_cc_dep_combination_against_meo_sage_account(data))
+        error_list.extend(self._cheque_fields_must_be_empty(data))
         if error_list:
             raise forms.ValidationError(error_list)
 
@@ -125,17 +130,20 @@ class CSVUploadForm(forms.Form):
             msg = f"Missing required columns: {', '.join(missing_columns)}"
             raise forms.ValidationError(msg)
 
-    @staticmethod
-    def _validate_source_and_trader_type(data: list[dict]) -> list:
+    def _validate_source_and_trader_type(self, data: list[dict]) -> list:
         """Validate that 'Source' is always 80 and 'SYSTraderTranType' is always 4."""
         errors = []
 
         for index, row in enumerate(data, start=1):
+            claimant_name = self.get_account_name_from_code(row.get("AccountNumber"))
+            claim_number = row.get("SecondReference")
             if row.get("Source") != "80":
-                errors.append(f"Row {index}: 'Source' must be 80, but found {row.get('Source')}.")
+                errors.append(f"Row {index}, claimant: {claimant_name} with claim number: {claim_number}: "
+                              f"'Source' must be 80, but found {row.get('Source')}.")
 
             if row.get("SYSTraderTranType") != "4":
-                errors.append(f"Row {index}: 'SYSTraderTranType' must be 4, but found {row.get('SYSTraderTranType')}.")
+                errors.append(f"Row {index}, claimant: {claimant_name} with claim number: {claim_number}: "
+                              f"'SYSTraderTranType' must be 4, but found {row.get('SYSTraderTranType')}.")
 
         return errors
 
@@ -169,16 +177,31 @@ class CSVUploadForm(forms.Form):
                 continue
 
             pl_account_name = account_code_map.get(account_code)
+
             if pl_account_name is None:
                 errors.append(f"Row {index}: 'AccountNumber' {account_code} does not exist in PL Account Codes.")
-            elif pl_account_name not in nominal:
-                errors.append(
-                    f"Row {index}: 'AccountNumber' must match '{pl_account_name}' in "
-                    f"'NominalAnalysisNominalAnalysisNarrative/1', but found '{nominal}'."
-                )
+            else:
+                # Remove 'Soldo' and any hyphens from the PL account name. This is for credit card accounts.
+                revised_pl_account_name = re.sub(
+                    r"\bSoldo\b|\s*-\s*", "", pl_account_name, flags=re.IGNORECASE).strip()
+                if revised_pl_account_name not in nominal:
+                    errors.append(
+                        f"Row {index}: 'AccountNumber' must match '{revised_pl_account_name}' in "
+                        f"'NominalAnalysisNominalAnalysisNarrative/1', but found '{nominal}'."
+                    )
 
         return errors
 
+    @staticmethod
+    def get_account_name_from_code(account_code: str| None) -> str | None:
+        """Get the account name from the PL Account Codes table."""
+        if account_code is None:
+            return None
+        try:
+            return MeoValidSuppliers.objects.using("meo").get(
+                supplier_account_number=account_code).supplier_account_name
+        except MeoValidSuppliers.DoesNotExist:
+            return None
 
     def _validate_nc_cc_dep_combination_against_meo_sage_account(self, data: list[dict]) -> list[str]:
         """Validate that all nominal analysis fields exist in MEO.
@@ -208,6 +231,8 @@ class CSVUploadForm(forms.Form):
         max_repeat = self._get_max_repeat(fieldnames, "NominalAnalysisNominalCostCentre")
 
         for index, row in enumerate(data, start=1):
+            claimant_name = self.get_account_name_from_code(row.get("AccountNumber"))
+            claim_number = row.get("SecondReference")
             for repeat in range(1, max_repeat + 1):
                 cc_field = f"NominalAnalysisNominalCostCentre/{repeat}"
                 dep_field = f"NominalAnalysisNominalDepartment/{repeat}"
@@ -241,7 +266,28 @@ class CSVUploadForm(forms.Form):
                     errors.append(
                         f"Row {index}: The combination of '{cc_field}' ({cc}), "
                         f"'{dep_field}' ({dep}), and '{nominal_account_field}' "
-                        f"({nominal_account_name}) does not exist in MEO valid Sage accounts."
+                        f"({nc}) for claimant '{claimant_name}' and claim number '{claim_number}' "
+                        f"does not exist in MEO valid Sage accounts."
                     )
 
         return errors
+
+    def _cheque_fields_must_be_empty(self, data: list[dict]) -> list[str]:
+        """Validate that cheque fields are empty.
+
+        The cheque fields are 'ChequeCurrencyName', 'ChequeToBankExchangeRate', and 'ChequeValueInChequeCurrency'.
+        """
+        errors = []
+        for index, row in enumerate(data, start=1):
+            cheque_currency_name = row.get("ChequeCurrencyName")
+            cheque_to_bank_exchange_rate = row.get("ChequeToBankExchangeRate")
+            cheque_value_in_cheque_currency = row.get("ChequeValueInChequeCurrency")
+            claimant_name = self.get_account_name_from_code(row.get("AccountNumber"))
+            claim_number = row.get("SecondReference")
+            if any([cheque_currency_name, cheque_to_bank_exchange_rate, cheque_value_in_cheque_currency]):
+                errors.append(
+                    f"Row {index}: Unexpected values in the Cheque columns for {claimant_name} with claim number: "
+                    f"{claim_number}. All cheque columns must be empty."
+                )
+
+        return errors
diff --git a/sage_validation/file_validator/models.py b/sage_validation/file_validator/models.py
index 99c2e0f7485ede4bea0112eb3bac3b46630f1676..c2bd157589984bc6b80d66d8a0bc69b9e9d70ba4 100644
--- a/sage_validation/file_validator/models.py
+++ b/sage_validation/file_validator/models.py
@@ -38,6 +38,7 @@ class MeoNominal(models.Model):
 class MeoValidSageAccounts(models.Model):
     """View for MEO valid Sage accounts."""
 
+    id = models.UUIDField(db_column="ID", primary_key=True)
     account_name = models.CharField(db_column="AccountName", max_length=60)
     account_number = models.CharField(db_column="AccountNumber", max_length=8, blank=True, null=True)
     account_cost_centre = models.CharField(db_column="AccountCostCentre", max_length=3, blank=True, null=True)
diff --git a/sage_validation/file_validator/templates/upload.html b/sage_validation/file_validator/templates/upload.html
index a9428905a06c54a7c936c2d18c6bcddee3c6d32a..6ea180792d0f747d9cc090265a91cafe7a9f18bc 100644
--- a/sage_validation/file_validator/templates/upload.html
+++ b/sage_validation/file_validator/templates/upload.html
@@ -3,7 +3,7 @@
 {% block title %}File Upload{% endblock %}
 
 {% block content %}
-    <div class="bg-white p-10 rounded-lg shadow-lg w-11/12 md:w-1/2 lg:w-1/3 mx-auto">
+    <div class="bg-white p-10 rounded-lg shadow-lg w-11/12 md:w-3/4 lg:w-3/4 mx-auto">
         <h2 class="text-2xl font-bold mb-6 text-gray-800 text-center">Upload CSV File</h2>
 
         <form id="uploadForm" enctype="multipart/form-data" class="space-y-6">
@@ -13,10 +13,21 @@
                        class="w-full border border-gray-300 p-3 rounded-lg focus:outline-none focus:ring focus:ring-blue-400">
             </div>
 
-            <button type="submit"
+            <button id="submitBtn" type="submit"
                     class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring focus:ring-blue-400">
                 Upload
             </button>
+
+            <!-- Loading Spinner -->
+            <div id="loadingSpinner" class="hidden flex justify-center">
+                <svg class="animate-spin h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none"
+                     viewBox="0 0 24 24">
+                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
+                            stroke-width="4"></circle>
+                    <path class="opacity-75" fill="currentColor"
+                          d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
+                </svg>
+            </div>
         </form>
 
         <!-- Error Display -->
@@ -45,6 +56,8 @@
             e.preventDefault();
 
             const fileInput = document.getElementById('fileInput');
+            const submitBtn = document.getElementById('submitBtn');
+            const loadingSpinner = document.getElementById('loadingSpinner');
             const errorSection = document.getElementById('errorSection');
             const errorList = document.getElementById('errorList');
             const successSection = document.getElementById('successSection');
@@ -58,6 +71,9 @@
             successSection.classList.add('hidden');
             downloadSection.classList.add('hidden');
 
+            submitBtn.disabled = true;
+            loadingSpinner.classList.remove('hidden');
+
             const formData = new FormData();
             formData.append('file', fileInput.files[0]);
 
@@ -85,10 +101,10 @@
                         li.textContent = 'You are not authorized to perform this action.';
                         errorList.appendChild(li);
                     } else if (response.status === 400 && result.status === 'error') {
-                        for (const [field, messages] of Object.entries(result.errors)) {
+                        for (const messages of Object.values(result.errors)) {
                             messages.forEach(message => {
                                 const li = document.createElement('li');
-                                li.textContent = `${field}: ${message}`;
+                                li.textContent = message;
                                 errorList.appendChild(li);
                             });
                         }
@@ -105,6 +121,9 @@
                 li.textContent = 'Failed to connect to the server. Please check your internet connection.';
                 errorList.appendChild(li);
                 errorSection.classList.remove('hidden');
+            } finally {
+                loadingSpinner.classList.add('hidden');
+                submitBtn.disabled = false;
             }
         });
     </script>
diff --git a/setup.py b/setup.py
index 1dfbad104ea1c331d62789edcfe596035d05dcbd..d7bc1fd9dd0ff8491cda2e4eddbaa9405a62dfbf 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="sage-validation",
-    version="0.6",
+    version="0.7",
     packages=find_packages(),
     include_package_data=True,
     install_requires=[
diff --git a/test/conftest.py b/test/conftest.py
index 94a62ed9ada92e49af102a8213b29e3866eb9b90..1207568f8954c9f8cff2a5dfa862ff356cd880f8 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -37,6 +37,9 @@ def sample_input_file() -> SimpleUploadedFile:
         "SYSTraderGenerationReasonType",
         "GoodsValueInBaseCurrency",
         "TransactionReference",
+        "ChequeCurrencyName",
+        "ChequeToBankExchangeRate",
+        "ChequeValueInChequeCurrency",
 
         # NominalAnalysis repeating columns (Example: /1 for first occurrence)
         "NominalAnalysisTransactionValue/1",
@@ -79,6 +82,9 @@ def sample_input_file() -> SimpleUploadedFile:
         "1000",  # SYSTraderGenerationReasonType
         "1200",  # GoodsValueInBaseCurrency
         "BK123",  # TransactionReference(Batch Number)
+        "",  # ChequeCurrencyName
+        "",  # ChequeToBankExchangeRate
+        "",  # ChequeValueInChequeCurrency
 
         # NominalAnalysis repeating values (Example: /1)
         "500.75",  # NominalAnalysisTransactionValue/1
diff --git a/test/test_file_validator/test_forms.py b/test/test_file_validator/test_forms.py
index b5df1089c1430e9a898b62cd8449ef849415f422..d32963027934d3464746ed22734d87b7d737739a 100644
--- a/test/test_file_validator/test_forms.py
+++ b/test/test_file_validator/test_forms.py
@@ -61,8 +61,8 @@ def test_source_and_trader_type_validation(sample_input_file: SimpleUploadedFile
     modified_file = create_modified_csv(sample_input_file, {"Source": "90", "SYSTraderTranType": "5"})
     form = CSVUploadForm(files={"file": modified_file})
     assert not form.is_valid()
-    assert "Row 1: 'Source' must be 80" in form.errors["file"][0]
-    assert "Row 1: 'SYSTraderTranType' must be 4" in form.errors["file"][1]
+    assert "'Source' must be 80" in form.errors["file"][0]
+    assert "'SYSTraderTranType' must be 4" in form.errors["file"][1]
 
 
 def test_validate_nominal_analysis_account(sample_input_file: SimpleUploadedFile, mock_meo_database: MagicMock) -> None:
@@ -92,3 +92,11 @@ def test_validate_nc_cc_dep_combination_against_meo_sage_account(
     assert ("Row 1: 'NominalAnalysisNominalCostCentre/1' (Invalid_CC) is not a valid cost centre."
             in str(form.errors["file"][0]))
 
+
+def test_cheque_fields_must_be_empty(sample_input_file: SimpleUploadedFile, mock_meo_database: MagicMock) -> None:
+    """Test that cheque fields must be empty."""
+    modified_file = create_modified_csv(sample_input_file,
+                                        {"ChequeCurrencyName": "USD", "ChequeToBankExchangeRate": "1"})
+    form = CSVUploadForm(files={"file": modified_file})
+    assert not form.is_valid()
+    assert "All cheque columns must be empty." in form.errors["file"][0]