diff --git a/Changelog.md b/Changelog.md
index 61d65ecde93212a164ac250e4caae1ce18ef02ba..db0c489bdf991d3207861f10cc9b121e4ec3407e 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## [0.2] - 2025-01-27
+- Added new validations
+
 ## [0.2] - 2025-01-09
 - Updated the setting related to the MEDIA ROOT and URL and also the CSRF settings
 
diff --git a/requirements.txt b/requirements.txt
index 791ff6078582a2142929c798f615301afd50f385..0fb6979ad1f0d490d1ea74e2384a40dd911b001c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
-Django==5.1.1
+Django==5.0.11
 ruff
 mypy
 tox
 sphinx
 sphinx-autodoc-typehints
+mssql-django
diff --git a/sage_validation/file_validator/apps.py b/sage_validation/file_validator/apps.py
index bd972d5fbb02b580411cd5dab63bceae1b5d2a04..0356f131109d7c09a63684b908706764368885f0 100644
--- a/sage_validation/file_validator/apps.py
+++ b/sage_validation/file_validator/apps.py
@@ -1,4 +1,5 @@
 """App configuration for file_validator app."""
+
 from django.apps import AppConfig
 
 
diff --git a/sage_validation/file_validator/forms.py b/sage_validation/file_validator/forms.py
index e3a2929a068a5d8337a695fb41f706a6f7a81619..e3f9850e13b5a1799ed1d344ad354363044152f1 100644
--- a/sage_validation/file_validator/forms.py
+++ b/sage_validation/file_validator/forms.py
@@ -1,9 +1,13 @@
 """Forms for the file_validator app."""
+
 import csv
-from collections.abc import Sequence
+from collections.abc import Iterable, 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
 
 
 class CSVUploadForm(forms.Form):
@@ -12,68 +16,74 @@ class CSVUploadForm(forms.Form):
     file = forms.FileField(label="Select a CSV file")
 
     required_columns: ClassVar[list] = [
-        "AccountNumber", "CBAccountNumber", "DaysDiscountValid", "DiscountValue",
-        "DiscountPercentage", "DueDate", "GoodsValueInAccountCurrency",
-        "PurControlValueInBaseCurrency", "DocumentToBaseCurrencyRate",
-        "DocumentToAccountCurrencyRate", "PostedDate", "QueryCode",
-        "TransactionReference", "SecondReference", "Source",
-        "SYSTraderTranType", "TransactionDate", "UniqueReferenceNumber",
-        "UserNumber", "TaxValue", "SYSTraderGenerationReasonType",
-        "GoodsValueInBaseCurrency"
+        "AccountNumber",
+        "CBAccountNumber",
+        "DaysDiscountValid",
+        "DiscountValue",
+        "DiscountPercentage",
+        "DueDate",
+        "GoodsValueInAccountCurrency",
+        "PurControlValueInBaseCurrency",
+        "DocumentToBaseCurrencyRate",
+        "DocumentToAccountCurrencyRate",
+        "PostedDate",
+        "QueryCode",
+        "TransactionReference",
+        "SecondReference",
+        "Source",
+        "SYSTraderTranType",
+        "TransactionDate",
+        "UniqueReferenceNumber",
+        "UserNumber",
+        "TaxValue",
+        "SYSTraderGenerationReasonType",
+        "GoodsValueInBaseCurrency",
     ]
 
     repeating_columns: ClassVar[dict] = {
         "NominalAnalysis": [
-            "NominalAnalysisTransactionValue", "NominalAnalysisNominalAccountNumber",
-            "NominalAnalysisNominalCostCentre", "NominalAnalysisNominalDepartment",
-            "NominalAnalysisNominalAnalysisNarrative", "NominalAnalysisTransactionAnalysisCode"
+            "NominalAnalysisTransactionValue",
+            "NominalAnalysisNominalAccountNumber",
+            "NominalAnalysisNominalCostCentre",
+            "NominalAnalysisNominalDepartment",
+            "NominalAnalysisNominalAnalysisNarrative",
+            "NominalAnalysisTransactionAnalysisCode",
         ],
         "TaxAnalysis": [
-            "TaxAnalysisTaxRate", "TaxAnalysisGoodsValueBeforeDiscount",
-            "TaxAnalysisDiscountValue", "TaxAnalysisDiscountPercentage",
-            "TaxAnalysisTaxOnGoodsValue"
-        ]
+            "TaxAnalysisTaxRate",
+            "TaxAnalysisGoodsValueBeforeDiscount",
+            "TaxAnalysisDiscountValue",
+            "TaxAnalysisDiscountPercentage",
+            "TaxAnalysisTaxOnGoodsValue",
+        ],
     }
 
-    def clean_file(self) -> str:
-        """Validate the uploaded file format and contents."""
+    def clean_file(self) -> UploadedFile:
+        """Validate the uploaded file."""
         file = self.cleaned_data["file"]
 
-        if not file.name.endswith(".csv"):
-            err_msg = "File must be in CSV format"
-            raise forms.ValidationError(err_msg)
-
-        try:
-            csv_file = file.read().decode("utf-8").splitlines()
-            reader = csv.DictReader(csv_file)
-            fieldnames = reader.fieldnames if reader.fieldnames is not None else []
-
-            missing_columns = [col for col in self.required_columns if col not in fieldnames]
+        # Step 1: Validate file type
+        self._validate_file_type(file)
 
-            for section_name, column_list in self.repeating_columns.items():
-                max_repeat = self.get_max_repeat(fieldnames, section_name)
+        # Step 2: Parse file and validate headers
+        csv_file = file.read().decode("utf-8").splitlines()
+        reader = csv.DictReader(csv_file, delimiter=",")
+        fieldnames = reader.fieldnames if reader.fieldnames is not None else []
+        self._validate_headers(fieldnames)
 
-                if max_repeat == 0:
-                    missing_columns.extend([f"{base_col}/1" for base_col in column_list])
-                else:
-                    for repeat in range(1, max_repeat + 1):
-                        missing_columns.extend(
-                            [f"{base_col}/{repeat}" for base_col in column_list if
-                             f"{base_col}/{repeat}" not in fieldnames]
-                        )
-
-            if missing_columns:
-                err_msg = f"Missing required columns: {', '.join(missing_columns)}"
-                raise forms.ValidationError(err_msg)
-
-        except (UnicodeDecodeError, csv.Error) as e:
-            err_msg = f"File could not be processed: {e!s}"
-            raise forms.ValidationError(err_msg) from e
+        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))
+        error_list.extend(self._validate_nc_cc_dep_combination_against_meo_sage_account(data))
+        if error_list:
+            raise forms.ValidationError(error_list)
 
         return file
 
     @staticmethod
-    def get_max_repeat(fieldnames: Sequence[str], section_prefix: str) -> int:
+    def _get_max_repeat(fieldnames: Sequence[str], section_prefix: str) -> int:
         """Identify the maximum number of repeats for a section."""
         max_repeat = 0
         for field in fieldnames:
@@ -84,3 +94,135 @@ class CSVUploadForm(forms.Form):
                 except ValueError:
                     continue
         return max_repeat
+
+    @staticmethod
+    def _validate_file_type(file: UploadedFile) -> None:
+        """Validate that the uploaded file is a CSV."""
+        if not file.name.endswith(".csv"):
+            msg = "File must be in CSV format."
+            raise forms.ValidationError(msg)
+
+    def _validate_headers(self, fieldnames: Sequence[str]) -> None:
+        """Validate required and repeating columns in the headers."""
+        missing_columns = [col for col in self.required_columns if col not in fieldnames]
+
+        for section_name, column_list in self.repeating_columns.items():
+            max_repeat = self._get_max_repeat(fieldnames, section_name)
+            if max_repeat == 0:
+                missing_columns.extend([f"{base_col}/1" for base_col in column_list])
+            else:
+                for repeat in range(1, max_repeat + 1):
+                    missing_columns.extend([
+                        f"{base_col}/{repeat}" for base_col in column_list if f"{base_col}/{repeat}" not in fieldnames
+                    ])
+
+        if missing_columns:
+            msg = f"Missing required columns: {', '.join(missing_columns)}"
+            raise forms.ValidationError(msg)
+
+    @staticmethod
+    def _validate_source_and_trader_type(data: Iterable[dict]) -> list:
+        """Validate that 'Source' is always 80 and 'SYSTraderTranType' is always 4."""
+        errors = []
+
+        for index, row in enumerate(data, start=1):
+            if row.get("Source") != "80":
+                errors.append(f"Row {index}: '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')}.")
+
+        return errors
+
+    @staticmethod
+    def _validate_nominal_analysis_account(data: Iterable[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.
+
+        Returns:
+            List[str]: A list of error messages, if any.
+
+        """
+        errors = []
+
+        account_code_map = {
+            obj.pl_account_code: obj.pl_account_name
+            for obj in PlAccountCodes.objects.using("meo").all()  # type: ignore[attr-defined]
+        }
+
+        for index, row in enumerate(data, start=1):
+            account_code = row.get("AccountNumber")
+            nominal = row.get("NominalAnalysisNominalAnalysisNarrative/1")
+
+            # Skip rows without 'AccountNumber' or 'NominalAnalysisNominalAnalysisNarrative/1'
+            if not account_code or not nominal:
+                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}'."
+                )
+
+        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.
+
+        Args:
+            data (Iterable[dict]): The rows of data to validate.
+
+        Returns:
+            List[str]: A list of error messages, if any.
+
+        """
+        errors = []
+
+        cost_centre_map = {
+            obj.cc: obj.cctype 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()
+        }
+
+        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")
+
+            if not cc or not dep or not nominal_account_name:
+                continue
+
+            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
+
+            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
+
+            nc = xx_data[0] if cc_type == "Project" else xx_data[1]
+
+            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."
+                )
+
+        return errors
diff --git a/sage_validation/file_validator/models.py b/sage_validation/file_validator/models.py
index 5fd99e9ade91d0858b759177ff8c997fea2ef30a..0bc48ec3eb16cc8dad934cb145068fb76e8935ab 100644
--- a/sage_validation/file_validator/models.py
+++ b/sage_validation/file_validator/models.py
@@ -1 +1,141 @@
-"""Models for the file_validator app."""
+"""Models for MEO DB tables and views."""
+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):
+    """Represents cost centres.
+
+    This model contains data related to cost centres,
+    including their names, types, and unique IDs.
+    """
+
+    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)
+    id = models.IntegerField(db_column="ID", primary_key=True)
+
+    class Meta:
+        """Metaclass for the Meocostcentres model."""
+
+        managed = False
+        db_table = "meoCostCentres"
+
+
+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")
+
+    class Meta:
+        """Metaclass for the Meonominal model."""
+
+        managed = False
+        db_table = "meoNominal"
+
+
+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)
+
+    class Meta:
+        """Metaclass for the Meovalidsageaccounts model."""
+
+        managed = False
+        db_table = "meoValidSageAccounts"
+
+
+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)
+
+    class Meta:
+        """Metaclass for the Meovalidsuppliers model."""
+
+        managed = False
+        db_table = "meoValidSuppliers"
+
+
+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)
+
+    class Meta:
+        """Metaclass for the Meovalidvat model."""
+
+        managed = False
+        db_table = "meoValidVAT"
+
+
+class VwPlAcctCodes(models.Model):
+    """View for profit and loss account codes."""
+
+    pl_account_name = models.TextField(db_column="PL_Account_Name")
+    pl_account_code = models.CharField(db_column="PL_Account_Code", max_length=50)
+
+    class Meta:
+        """Metaclass for the VwPlAcctCodes model."""
+
+        managed = False
+        db_table = "vw_PL_Acct_Codes"
+
+
+class VwXxData(models.Model):
+    """provides a read-only view of XX data entries.
+
+    The entries are typically used for categorizing costs and activities.
+    """
+
+    description = models.CharField(db_column="Description", max_length=50)
+    xx_value = models.CharField(db_column="xx_Value", max_length=50)
+    project = models.SmallIntegerField(db_column="Project")
+    overhead = models.IntegerField(db_column="Overhead")
+
+    class Meta:
+        """Meta class for the VwXxData model."""
+
+        managed = False
+        db_table = "vw_xx-data"
+
+
+class XxData(models.Model):
+    """Model for XX data entries."""
+
+    description = models.CharField(db_column="Description", max_length=50)
+    xx_value = models.CharField(db_column="xx_Value", primary_key=True, max_length=50)
+    project = models.SmallIntegerField(db_column="Project")
+    overhead = models.IntegerField(db_column="Overhead")
+
+    class Meta:
+        """Metaclass for the XxData model."""
+
+        managed = False
+        db_table = "xx-data"
diff --git a/sage_validation/file_validator/urls.py b/sage_validation/file_validator/urls.py
index 78e88de5f074f65eb3e0b268df9cee03ef8453c1..12e1520494e4f3bda7dfb0b9e7b2dd8b0ead954a 100644
--- a/sage_validation/file_validator/urls.py
+++ b/sage_validation/file_validator/urls.py
@@ -1,4 +1,5 @@
 """Urls for the file_validator app."""
+
 from django.urls import path
 
 from sage_validation.file_validator.views import CSVUploadView
diff --git a/sage_validation/file_validator/views.py b/sage_validation/file_validator/views.py
index 3f75b923b2c584e3576f3ea1d22f3bfc34ea0d55..33ec72f6cc93b6db8ad5aafa505629af0e2d6677 100644
--- a/sage_validation/file_validator/views.py
+++ b/sage_validation/file_validator/views.py
@@ -1,4 +1,5 @@
 """Views for the file_validator app."""
+
 from django.http import HttpRequest, HttpResponse, JsonResponse
 from django.shortcuts import render
 from django.urls import reverse_lazy
diff --git a/sage_validation/settings.py b/sage_validation/settings.py
index 5bf2163c0aa92aedb85e121d78796016554b88d2..9d508100b933598577dc26f8450229d7f1e13bcf 100644
--- a/sage_validation/settings.py
+++ b/sage_validation/settings.py
@@ -78,7 +78,19 @@ DATABASES = {
     "default": {
         "ENGINE": "django.db.backends.sqlite3",
         "NAME": BASE_DIR / "db.sqlite3",
-    }
+    },
+    "meo": {
+        'ENGINE': 'mssql',
+        'NAME': os.getenv("MSSQL_DB_NAME", ""),
+        'USER': os.getenv("MSSQL_DB_USER", ""),
+        'PASSWORD': os.getenv("MSSQL_DB_PASSWORD", ""),
+        'HOST': os.getenv("MSSQL_DB_HOST", "localhost"),
+        'PORT': os.getenv("MSSQL_DB_PORT", ""),
+
+        'OPTIONS': {
+            'driver': 'ODBC Driver 18 for SQL Server',
+        },
+    },
 }
 
 # Password validation
diff --git a/setup.py b/setup.py
index 5ce53c3ad5d888edad992252a14d5d7b0343fa81..bb1bd54466c3270083abeb5acc7b0dac98de5064 100644
--- a/setup.py
+++ b/setup.py
@@ -3,11 +3,12 @@ from setuptools import find_packages, setup
 
 setup(
     name="sage-validation",
-    version="0.2",
+    version="0.3",
     packages=find_packages(),
     include_package_data=True,
     install_requires=[
-        "Django==5.1.1",
+        "Django==5.0.11",
+        "mssql-django",
     ],
     extras_require={
         "prod": [