diff --git a/Changelog.md b/Changelog.md
index db0c489bdf991d3207861f10cc9b121e4ec3407e..abf10fcfcb23ac39ed7d51ac1655acb3b4b52ace 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## [0.3] - 2025-02-13
+- Added CSV Export and Modify Functionality
+
 ## [0.2] - 2025-01-27
 - Added new validations
 
diff --git a/sage_validation/file_validator/forms.py b/sage_validation/file_validator/forms.py
index e3f9850e13b5a1799ed1d344ad354363044152f1..6c243d2efe673bb8a4fe661a1852dfa6bb7cf776 100644
--- a/sage_validation/file_validator/forms.py
+++ b/sage_validation/file_validator/forms.py
@@ -1,13 +1,19 @@
 """Forms for the file_validator app."""
 
 import csv
-from collections.abc import Iterable, Sequence
+from collections.abc import Sequence
 from typing import ClassVar
 
 from django import forms
 from django.core.files.uploadedfile import UploadedFile
 
-from sage_validation.file_validator.models import Meocostcentres, Meovalidsageaccounts, PlAccountCodes, XxData
+from sage_validation.file_validator.models import (
+    MeoCostCentres,
+    MeoNominal,
+    MeoValidSageAccounts,
+    MeoValidSuppliers,
+    XxData,
+)
 
 
 class CSVUploadForm(forms.Form):
@@ -72,7 +78,6 @@ class CSVUploadForm(forms.Form):
         self._validate_headers(fieldnames)
 
         error_list = []
-        # Step 3: Validate 'Source' and 'SYSTraderTranType' values
         data = list(reader)
         error_list.extend(self._validate_source_and_trader_type(data))
         error_list.extend(self._validate_nominal_analysis_account(data))
@@ -121,7 +126,7 @@ class CSVUploadForm(forms.Form):
             raise forms.ValidationError(msg)
 
     @staticmethod
-    def _validate_source_and_trader_type(data: Iterable[dict]) -> list:
+    def _validate_source_and_trader_type(data: list[dict]) -> list:
         """Validate that 'Source' is always 80 and 'SYSTraderTranType' is always 4."""
         errors = []
 
@@ -135,14 +140,14 @@ class CSVUploadForm(forms.Form):
         return errors
 
     @staticmethod
-    def _validate_nominal_analysis_account(data: Iterable[dict]) -> list[str]:
+    def _validate_nominal_analysis_account(data: list[dict]) -> list[str]:
         """Validate that 'AccountNumber' matches the name in 'NominalAnalysisNominalAnalysisNarrative/1'.
 
         This only checks the first group of NominalAnalysis columns. A list of codes/names
         is fetched from the database for validation (from the 'PL Account Codes' table).
 
         Args:
-            data (Iterable[dict]): The rows of data to validate.
+            data (list[dict]): The rows of data to validate.
 
         Returns:
             List[str]: A list of error messages, if any.
@@ -151,8 +156,8 @@ class CSVUploadForm(forms.Form):
         errors = []
 
         account_code_map = {
-            obj.pl_account_code: obj.pl_account_name
-            for obj in PlAccountCodes.objects.using("meo").all()  # type: ignore[attr-defined]
+            obj.supplier_account_number: obj.supplier_account_name
+            for obj in MeoValidSuppliers.objects.using("meo").all()  # type: ignore[attr-defined]
         }
 
         for index, row in enumerate(data, start=1):
@@ -174,12 +179,15 @@ class CSVUploadForm(forms.Form):
 
         return errors
 
-    @staticmethod
-    def _validate_nc_cc_dep_combination_against_meo_sage_account(data: Iterable[dict]) -> list[str]:
-        """Validate that the combination of 'AccountCostCentre', 'AccountDepartment', and 'AccountNumber' exists in MEO.
+
+    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.
+
+        This includes 'NominalAnalysisNominalCostCentre/{N}', 'NominalAnalysisNominalDepartment/{N}',
+        and 'NominalAnalysisNominalAccountNumber/{N}'.
 
         Args:
-            data (Iterable[dict]): The rows of data to validate.
+            data (list[dict]): The rows of data to validate.
 
         Returns:
             List[str]: A list of error messages, if any.
@@ -188,41 +196,52 @@ class CSVUploadForm(forms.Form):
         errors = []
 
         cost_centre_map = {
-            obj.cc: obj.cctype for obj in Meocostcentres.objects.using("meo").all()
+            obj.cc: obj.cc_type for obj in MeoCostCentres.objects.using("meo").all()
         }
 
         xx_data_map = {
             obj.xx_value: (obj.project, obj.overhead) for obj in XxData.objects.using("meo").all()
         }
 
+
+        fieldnames = list(data[0].keys())
+        max_repeat = self._get_max_repeat(fieldnames, "NominalAnalysisNominalCostCentre")
+
         for index, row in enumerate(data, start=1):
-            cc = row.get("NominalAnalysisNominalCostCentre/1")
-            dep = row.get("NominalAnalysisNominalDepartment/1")
-            nominal_account_name = row.get("NominalAnalysisNominalAccountNumber/1")
+            for repeat in range(1, max_repeat + 1):
+                cc_field = f"NominalAnalysisNominalCostCentre/{repeat}"
+                dep_field = f"NominalAnalysisNominalDepartment/{repeat}"
+                nominal_account_field = f"NominalAnalysisNominalAccountNumber/{repeat}"
 
-            if not cc or not dep or not nominal_account_name:
-                continue
+                cc = row.get(cc_field)
+                dep = row.get(dep_field)
+                nominal_account_name = row.get(nominal_account_field)
 
-            cc_type = cost_centre_map.get(cc)
-            if not cc_type:
-                errors.append(f"Row {index}: 'NominalAnalysisNominalCostCentre/1' ({cc}) is not a valid cost centre.")
-                continue
+                if not cc or not dep or not nominal_account_name:
+                    continue
 
-            xx_data = xx_data_map.get(nominal_account_name)
-            if not xx_data:
-                errors.append(
-                    f"Row {index}: 'NominalAnalysisNominalAccountNumber/1' ({nominal_account_name}) is not valid.")
-                continue
+                cc_type = cost_centre_map.get(cc)
+                if not cc_type:
+                    errors.append(f"Row {index}: '{cc_field}' ({cc}) is not a valid cost centre.")
+                    continue
 
-            nc = xx_data[0] if cc_type == "Project" else xx_data[1]
+                xx_data = xx_data_map.get(nominal_account_name)
 
-            if not Meovalidsageaccounts.objects.using("meo").filter(
-                    accountcostcentre=cc, accountdepartment=dep, accountnumber=nc
-            ).exists():
-                errors.append(
-                    f"Row {index}: The combination of 'NominalAnalysisNominalCostCentre/1' ({cc}), "
-                    f"'NominalAnalysisNominalDepartment/1' ({dep}), and 'NominalAnalysisNominalAccountNumber/1' "
-                    f"({nominal_account_name}) does not exist in MEO valid Sage accounts."
-                )
+                if xx_data:
+                    nc = xx_data[0] if cc_type == "Project" else xx_data[1]
+                elif MeoNominal.objects.using("meo").filter(nom=nominal_account_name).exists():
+                    nc = nominal_account_name
+                else:
+                    errors.append(f"Row {index}: '{nominal_account_field}' ({nominal_account_name}) is not valid.")
+                    continue
+
+                if not MeoValidSageAccounts.objects.using("meo").filter(
+                        account_cost_centre=cc, account_department=dep, account_number=nc
+                ).exists():
+                    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."
+                    )
 
         return errors
diff --git a/sage_validation/file_validator/models.py b/sage_validation/file_validator/models.py
index 0bc48ec3eb16cc8dad934cb145068fb76e8935ab..99c2e0f7485ede4bea0112eb3bac3b46630f1676 100644
--- a/sage_validation/file_validator/models.py
+++ b/sage_validation/file_validator/models.py
@@ -2,24 +2,7 @@
 from django.db import models
 
 
-class PlAccountCodes(models.Model):
-    """Represents profit and loss (PL) account codes.
-
-    This model maps the PL account names to their respective codes.
-    """
-
-    objects = None
-    pl_account_name = models.TextField(db_column="PL_Account_Name")
-    pl_account_code = models.CharField(db_column="PL_Account_Code", primary_key=True, max_length=50)
-
-    class Meta:
-        """Metaclass for the PlAccountCodes model."""
-
-        managed = False
-        db_table = "PL_Account_Codes"
-
-
-class Meocostcentres(models.Model):
+class MeoCostCentres(models.Model):
     """Represents cost centres.
 
     This model contains data related to cost centres,
@@ -28,68 +11,68 @@ class Meocostcentres(models.Model):
 
     cc = models.CharField(db_column="CC", max_length=3)
     cc_name = models.CharField(db_column="CC_Name", max_length=50, blank=True, null=True)
-    cctype = models.CharField(db_column="CCType", max_length=8)
+    cc_type = models.CharField(db_column="CCType", max_length=8)
     id = models.IntegerField(db_column="ID", primary_key=True)
 
     class Meta:
-        """Metaclass for the Meocostcentres model."""
+        """Metaclass for the MeoCostCentres model."""
 
         managed = False
         db_table = "meoCostCentres"
 
 
-class Meonominal(models.Model):
+class MeoNominal(models.Model):
     """View for MEO nominal codes."""
 
     nom = models.CharField(db_column="Nom", max_length=5)
-    nomname = models.CharField(db_column="NomName", max_length=60, blank=True, null=True)
-    nomid = models.IntegerField(db_column="NomID")
+    nom_name = models.CharField(db_column="NomName", max_length=60, blank=True, null=True)
+    nom_id = models.IntegerField(db_column="NomID")
 
     class Meta:
-        """Metaclass for the Meonominal model."""
+        """Metaclass for the MeoNominal model."""
 
         managed = False
         db_table = "meoNominal"
 
 
-class Meovalidsageaccounts(models.Model):
+class MeoValidSageAccounts(models.Model):
     """View for MEO valid Sage accounts."""
 
-    accountname = models.CharField(db_column="AccountName", max_length=60)
-    accountnumber = models.CharField(db_column="AccountNumber", max_length=8, blank=True, null=True)
-    accountcostcentre = models.CharField(db_column="AccountCostCentre", max_length=3, blank=True, null=True)
-    accountdepartment = models.CharField(db_column="AccountDepartment", max_length=3, blank=True, null=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)
+    account_department = models.CharField(db_column="AccountDepartment", max_length=3, blank=True, null=True)
 
     class Meta:
-        """Metaclass for the Meovalidsageaccounts model."""
+        """Metaclass for the MeoValidSageAccounts model."""
 
         managed = False
         db_table = "meoValidSageAccounts"
 
 
-class Meovalidsuppliers(models.Model):
+class MeoValidSuppliers(models.Model):
     """View for MEO valid suppliers."""
 
-    supplieraccountnumber = models.CharField(db_column="SupplierAccountNumber", max_length=8)
-    supplieraccountname = models.CharField(db_column="SupplierAccountName", max_length=60)
+    supplier_account_number = models.CharField(db_column="SupplierAccountNumber", max_length=8, primary_key=True)
+    supplier_account_name = models.CharField(db_column="SupplierAccountName", max_length=60)
 
     class Meta:
-        """Metaclass for the Meovalidsuppliers model."""
+        """Metaclass for the MeoValidSuppliers model."""
 
         managed = False
         db_table = "meoValidSuppliers"
 
 
-class Meovalidvat(models.Model):
+class MeoValidVat(models.Model):
     """View for MEO valid VAT codes."""
 
     tax_code = models.SmallIntegerField(db_column="Tax code")
     tax_code_label = models.CharField(db_column="tax code label", max_length=60)
     rate = models.DecimalField(db_column="Rate", max_digits=18, decimal_places=2)
-    inputnominalaccount = models.CharField(db_column="InputNominalAccount", max_length=8, blank=True, null=True)
+    input_nominal_account = models.CharField(db_column="InputNominalAccount", max_length=8, blank=True, null=True)
 
     class Meta:
-        """Metaclass for the Meovalidvat model."""
+        """Metaclass for the MeoValidVat model."""
 
         managed = False
         db_table = "meoValidVAT"
diff --git a/sage_validation/file_validator/templates/upload.html b/sage_validation/file_validator/templates/upload.html
index 129121ef05336a83485f0533f17cf4cac7afc749..4a595da4794ec9281508073a490bc54daa4549f5 100644
--- a/sage_validation/file_validator/templates/upload.html
+++ b/sage_validation/file_validator/templates/upload.html
@@ -26,12 +26,21 @@
         </div>
 
         <!-- Success Message -->
-        <div id="successSection" class="hidden mt-4 bg-green-100 border border-green-400 text-green-700 p-4 rounded-lg">
-            <strong class="font-bold">Success:</strong>
-            <span id="successMessage" class="block mt-2"></span>
+        <div id="successSection"
+             class="hidden mt-4 mb-8 bg-green-100 border border-green-400 text-green-700 p-4 rounded-lg text-center">
+            <strong class="font-bold text-lg">Success!</strong>
+            <p id="successMessage" class="block mt-2 text-md"></p>
+        </div>
+        <div id="downloadSection" class="hidden mt-16 flex justify-center">
+            <a id="downloadLink" href="#"
+               class="w-full max-w-xs text-center bg-blue-600 text-white py-4 px-12 rounded-lg shadow-lg hover:bg-blue-700
+          focus:outline-none focus:ring focus:ring-blue-400 transition text-lg font-semibold block">
+                📥 Download Updated CSV
+            </a>
         </div>
     </div>
 
+
     <script>
         const form = document.getElementById('uploadForm');
         const fileInput = document.getElementById('fileInput');
@@ -39,6 +48,8 @@
         const errorList = document.getElementById('errorList');
         const successSection = document.getElementById('successSection');
         const successMessage = document.getElementById('successMessage');
+        const downloadSection = document.getElementById('downloadSection');
+        const downloadLink = document.getElementById('downloadLink');
 
         form.addEventListener('submit', async function (e) {
             e.preventDefault();
@@ -48,6 +59,7 @@
             successMessage.innerHTML = '';
             errorSection.classList.add('hidden');
             successSection.classList.add('hidden');
+            downloadSection.classList.add('hidden');
 
             const formData = new FormData();
             formData.append('file', fileInput.files[0]);
@@ -66,18 +78,35 @@
                 if (response.ok && result.status === 'success') {
                     successMessage.innerText = result.message;
                     successSection.classList.remove('hidden');
+                    downloadLink.href = result.download_url;
+                    downloadSection.classList.remove('hidden');
                 } else if (response.status === 400 && result.status === 'error') {
-                    // Handle form errors from the backend
+                    errorList.innerHTML = '';
+
                     if (Array.isArray(result.errors)) {
                         result.errors.forEach(errorObj => {
-                            for (const [field, messages] of Object.entries(errorObj)) {
-                                messages.forEach(message => {
-                                    const li = document.createElement('li');
-                                    li.textContent = `${field}: ${message}`;
-                                    errorList.appendChild(li);
-                                });
+                            if (typeof errorObj === 'string') {
+                                const li = document.createElement('li');
+                                li.textContent = errorObj;
+                                errorList.appendChild(li);
+                            } else {
+                                for (const [field, messages] of Object.entries(errorObj)) {
+                                    messages.forEach(message => {
+                                        const li = document.createElement('li');
+                                        li.textContent = `${field}: ${message}`;
+                                        errorList.appendChild(li);
+                                    });
+                                }
                             }
                         });
+                    } else if (typeof result.errors === 'object') {
+                        for (const [field, messages] of Object.entries(result.errors)) {
+                            messages.forEach(message => {
+                                const li = document.createElement('li');
+                                li.textContent = `${field}: ${message}`;
+                                errorList.appendChild(li);
+                            });
+                        }
                     } else {
                         const li = document.createElement('li');
                         li.textContent = result.errors;
@@ -87,7 +116,6 @@
                     errorSection.classList.remove('hidden');
                 }
             } catch (error) {
-                // Handle unexpected errors
                 const li = document.createElement('li');
                 li.textContent = 'An unexpected error occurred. Please try again.';
                 errorList.appendChild(li);
diff --git a/sage_validation/file_validator/urls.py b/sage_validation/file_validator/urls.py
index 12e1520494e4f3bda7dfb0b9e7b2dd8b0ead954a..a5c0d9fd7840097233aabed1184a392037d7bcc1 100644
--- a/sage_validation/file_validator/urls.py
+++ b/sage_validation/file_validator/urls.py
@@ -2,8 +2,9 @@
 
 from django.urls import path
 
-from sage_validation.file_validator.views import CSVUploadView
+from sage_validation.file_validator.views import CSVExportView, CSVUploadView
 
 urlpatterns = [
     path("upload/", CSVUploadView.as_view(), name="upload-file"),
+    path("export/", CSVExportView.as_view(), name="export-file"),
 ]
diff --git a/sage_validation/file_validator/views.py b/sage_validation/file_validator/views.py
index 33ec72f6cc93b6db8ad5aafa505629af0e2d6677..99efd175b0fa9498eb15127578cd85e272e21ac1 100644
--- a/sage_validation/file_validator/views.py
+++ b/sage_validation/file_validator/views.py
@@ -1,11 +1,17 @@
 """Views for the file_validator app."""
+import csv
+import io
+from typing import Any
 
 from django.http import HttpRequest, HttpResponse, JsonResponse
 from django.shortcuts import render
 from django.urls import reverse_lazy
+from django.utils import timezone
+from django.views.generic.base import View
 from django.views.generic.edit import FormView
 
 from sage_validation.file_validator.forms import CSVUploadForm
+from sage_validation.file_validator.models import MeoCostCentres, XxData
 
 
 def index_view(request: HttpRequest) -> HttpResponse:
@@ -20,17 +26,92 @@ class CSVUploadView(FormView):
     form_class = CSVUploadForm
     success_url = reverse_lazy("upload-file")
 
-    def get_context_data(self, **kwargs: dict) -> dict:
+    def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]:
         """Render the form with no error message on GET request."""
         context = super().get_context_data(**kwargs)
         context["error"] = None
         context["message"] = None
         return context
 
-    def form_valid(self, form: CSVUploadForm) -> JsonResponse:  # noqa: ARG002
-        """Handle the CSV validation and passes appropriate success messages to the template."""
-        return JsonResponse({"status": "success", "message": "File is valid"})
+    def form_valid(self, form: CSVUploadForm) -> JsonResponse:
+        """Handle the CSV validation, store valid data, and prepare for export."""
+        csv_file = form.cleaned_data["file"]
+        csv_file.seek(0)
+        decoded_file = csv_file.read().decode("utf-8").strip()
+
+        if not decoded_file:
+            return JsonResponse({"status": "error", "message": "Uploaded file is empty."}, status=400)
+
+        reader = csv.DictReader(io.StringIO(decoded_file))
+        csv_data: list[dict[str, str]] = list(reader)
+
+        updated_data = self.update_fields(csv_data)
+
+        self.request.session["validated_csv"] = updated_data
+        self.request.session.modified = True
+
+        return JsonResponse({
+            "status": "success",
+            "message": "File successfully uploaded and processed.",
+            "download_url": reverse_lazy("export-file")
+        })
 
     def form_invalid(self, form: CSVUploadForm) -> JsonResponse:
-        """Handle the form when it is invalid (e.g., wrong file type or validation errors)."""
-        return JsonResponse({"status": "error", "errors": [form.errors]}, status=400)
+        """Handle the form when it is invalid."""
+        return JsonResponse({"status": "error", "errors": form.errors}, status=400)
+
+    @staticmethod
+    def update_fields(csv_data: list[dict[str, str]]) -> list[dict[str, str]]:
+        """Automatically update specific fields before export."""
+        current_date: str = timezone.now().strftime("%d/%m/%Y")
+
+        xx_data_map: dict[str, tuple] = {
+            obj.xx_value: (obj.project, obj.overhead) for obj in XxData.objects.using("meo").all()
+        }
+        cost_centre_map: dict[str, str] = {
+            obj.cc: obj.cc_type for obj in MeoCostCentres.objects.using("meo").all()
+        }
+
+        for row in csv_data:
+            row["TransactionDate"] = current_date
+
+            repeat = 1
+            while f"NominalAnalysisNominalCostCentre/{repeat}" in row:
+                cc = row.get(f"NominalAnalysisNominalCostCentre/{repeat}", "")
+                nominal_account_name = row.get(f"NominalAnalysisNominalAccountNumber/{repeat}", "")
+
+                if not cc or not nominal_account_name:
+                    repeat += 1
+                    continue
+
+                cc_type = cost_centre_map.get(cc, "")
+                xx_data = xx_data_map.get(nominal_account_name)
+
+                if xx_data:
+                    row[f"NominalAnalysisNominalAccountNumber/{repeat}"] = (
+                        xx_data[0] if cc_type == "Project" else xx_data[1]
+                    )
+
+                repeat += 1
+
+        return csv_data
+
+
+class CSVExportView(View):
+    """View for exporting the updated CSV file."""
+
+    def get(self, request: HttpRequest) -> HttpResponse:
+        """Generate a downloadable CSV file with updated values."""
+        csv_data: list[dict[str, str]] = request.session.get("validated_csv", [])
+
+        if not csv_data:
+            return HttpResponse("No data available for export.", status=400)
+
+        response = HttpResponse(content_type="text/csv")
+        response["Content-Disposition"] = 'attachment; filename="updated_file.csv"'
+
+        writer = csv.DictWriter(response, fieldnames=csv_data[0].keys())
+        writer.writeheader()
+        writer.writerows(csv_data)
+
+        return response
diff --git a/sage_validation/settings.py b/sage_validation/settings.py
index 9d508100b933598577dc26f8450229d7f1e13bcf..1a00d60d4de5129a8f1a28d43bc7160f5cea9272 100644
--- a/sage_validation/settings.py
+++ b/sage_validation/settings.py
@@ -89,6 +89,8 @@ DATABASES = {
 
         'OPTIONS': {
             'driver': 'ODBC Driver 18 for SQL Server',
+            'extra_params': 'TrustServerCertificate=Yes;'
+
         },
     },
 }
diff --git a/setup.py b/setup.py
index bb1bd54466c3270083abeb5acc7b0dac98de5064..09c3d78cb76669e38b66c2433c81b9f3ece4497f 100644
--- a/setup.py
+++ b/setup.py
@@ -3,12 +3,12 @@ from setuptools import find_packages, setup
 
 setup(
     name="sage-validation",
-    version="0.3",
+    version="0.4",
     packages=find_packages(),
     include_package_data=True,
     install_requires=[
         "Django==5.0.11",
-        "mssql-django",
+        "mssql-django==1.5",
     ],
     extras_require={
         "prod": [
diff --git a/tox.ini b/tox.ini
index 74a6ea7a599a22889bc9271e231206a165c7cafb..91d0161e6590165ecf51bec47ab84ff68718aacd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py312
+envlist = py311
 
 [testenv]
 deps =