diff --git a/sage_validation/file_validator/forms.py b/sage_validation/file_validator/forms.py index 0442892bb4842d37a09e17ef4693ef7b6e9bacbc..a8c43eabc3b91671d805b6c97d6d571d8cd7f2c6 100644 --- a/sage_validation/file_validator/forms.py +++ b/sage_validation/file_validator/forms.py @@ -7,7 +7,7 @@ from typing import ClassVar from django import forms from django.core.files.uploadedfile import UploadedFile -from sage_validation.file_validator.models import PlAccountCodes +from sage_validation.file_validator.models import Meocostcentres, Meovalidsageaccounts, PlAccountCodes, XxData class CSVUploadForm(forms.Form): @@ -76,6 +76,7 @@ class CSVUploadForm(forms.Form): 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) @@ -135,8 +136,7 @@ class CSVUploadForm(forms.Form): @staticmethod def _validate_nominal_analysis_account(data: Iterable[dict]) -> list[str]: - """ - Validate that 'AccountNumber' matches the name in 'NominalAnalysisNominalAnalysisNarrative/1'. + """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). @@ -146,6 +146,7 @@ class CSVUploadForm(forms.Form): Returns: List[str]: A list of error messages, if any. + """ errors = [] @@ -171,4 +172,57 @@ class CSVUploadForm(forms.Form): f"'NominalAnalysisNominalAnalysisNarrative/1', but found '{nominal}'." ) - return errors \ No newline at end of file + 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