diff --git a/src/organisation/router.py b/src/organisation/router.py index 5c862ea..10f3521 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -16,6 +16,7 @@ Endpoints: - [PATCH](/org/contact): [root user]: Updates the (contact_type) contact for an org(id). Any number of details can be changed. """ +from datetime import datetime, timezone from typing import Annotated from fastapi import APIRouter, status @@ -29,6 +30,7 @@ from src.contact.models import Contact from src.contact.schemas import ContactAddress from src.contact.exceptions import ContactNotFoundException from src.database import db_dependency +from src.organisation.schemas_questionnaires import QuestionnaireQuestionsVersion0 from src.user.dependencies import ( user_model_body_dependency, user_model_claims_dependency, @@ -64,6 +66,7 @@ from src.organisation.schemas import ( OrgPatchRootResponse, Questionnaire, OrgPatchContactResponse, + QuestionnaireMetadata, ) router = APIRouter( @@ -88,7 +91,9 @@ router = APIRouter( }, }, ) -async def get_org_by_id(org_model: org_model_root_claim_query_dependency): +async def get_org_by_id( + db: db_dependency, org_model: org_model_root_claim_query_dependency +): """ Returns organisation details including key member email addresses """ @@ -143,10 +148,21 @@ async def create_org( ALl organisations are given the "partial" status on creation. See update_questionnaire() for more details. """ if request_model.intake_questionnaire: - intake_questionnaire = request_model.intake_questionnaire.model_dump() + questionnaire_questions = request_model.intake_questionnaire.model_dump() else: - intake_questionnaire = None - org_model = Org(name=request_model.name, intake_questionnaire=intake_questionnaire) + questionnaire_questions = QuestionnaireQuestionsVersion0().model_dump() + + questionnaire_metadata = QuestionnaireMetadata(version=0, submission_date=None) + + intake_questionnaire = Questionnaire( + metadata=questionnaire_metadata, + questions=questionnaire_questions, + ) + + org_model = Org( + name=request_model.name, + intake_questionnaire=intake_questionnaire.model_dump(mode="json"), + ) org_model.status = "partial" @@ -204,18 +220,27 @@ async def update_questionnaire( final "are you sure" check before setting the org to be in "submitted" status, awaiting admin approval. """ update_data = request_model.intake_questionnaire.model_dump(exclude_none=True) - questionnaire_model = Questionnaire(**org_model.intake_questionnaire) + questionnaire = org_model.intake_questionnaire + questions_model = QuestionnaireQuestionsVersion0(**questionnaire["questions"]) for key, value in update_data.items(): - if hasattr(questionnaire_model, key): - setattr(questionnaire_model, key, value) + if hasattr(questions_model, key): + setattr(questions_model, key, value) else: raise UnprocessableContentException("Invalid keys in update request") + metadata = QuestionnaireMetadata(version=questionnaire["metadata"]["version"]) + # Allows for partially completed questionnaires to be saved without being submitted for review if not request_model.partial: org_model.status = "submitted" + metadata.submission_date = datetime.now(timezone.utc) - org_model.intake_questionnaire = questionnaire_model.model_dump() + questionnaire_model = Questionnaire( + metadata=metadata, + questions=questions_model, + ) + + org_model.intake_questionnaire = questionnaire_model.model_dump(mode="json") db.flush() response = OrgPatchQuestionnaireResponse(**org_model.__dict__) db.commit() diff --git a/src/organisation/schemas.py b/src/organisation/schemas.py index b4e9f23..2ea91c1 100644 --- a/src/organisation/schemas.py +++ b/src/organisation/schemas.py @@ -7,6 +7,7 @@ Models follow the nomenclature of: """ from typing import Optional +from datetime import datetime from pydantic import EmailStr, ConfigDict @@ -21,12 +22,17 @@ from src.schemas import ( from src.contact.schemas import ContactModel from src.organisation.constants import Status, ContactType +from src.organisation.schemas_questionnaires import QuestionnaireQuestionsVersion0 + + +class QuestionnaireMetadata(CustomBaseModel): + version: int + submission_date: Optional[datetime] = None class Questionnaire(CustomBaseModel): - question_one: Optional[str] = None - question_two: Optional[str] = None - question_three: Optional[str] = None + metadata: QuestionnaireMetadata + questions: QuestionnaireQuestionsVersion0 class ContactSummary(CustomBaseModel): @@ -47,7 +53,7 @@ class OrgSchema(OrgIDMixin): class OrgPostOrgRequest(CustomBaseModel): name: str - intake_questionnaire: Optional[Questionnaire] = None + intake_questionnaire: Optional[QuestionnaireQuestionsVersion0] = None class OrgPostOrgResponse(CustomBaseModel): @@ -57,7 +63,7 @@ class OrgPostOrgResponse(CustomBaseModel): class OrgPatchQuestionnaireRequest(OrgIDMixin): - intake_questionnaire: Questionnaire + intake_questionnaire: QuestionnaireQuestionsVersion0 partial: bool diff --git a/src/organisation/schemas_questionnaires.py b/src/organisation/schemas_questionnaires.py new file mode 100644 index 0000000..e9f7cfb --- /dev/null +++ b/src/organisation/schemas_questionnaires.py @@ -0,0 +1,13 @@ +from typing import Optional + +from src.schemas import CustomBaseModel + + +class QuestionnaireQuestions(CustomBaseModel): + pass + + +class QuestionnaireQuestionsVersion0(QuestionnaireQuestions): + question_one: Optional[str] = None + question_two: Optional[str] = None + question_three: Optional[str] = None diff --git a/test/conftest.py b/test/conftest.py index dbd63d7..1db0ad5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -103,7 +103,10 @@ def _seed(db): owner_contact_id=2, security_contact_id=3, status="approved", - intake_questionnaire={"question_two": "answer two"}, + intake_questionnaire={ + "metadata": {"version": 0, "submission_date": None}, + "questions": {"question_two": "answer two"}, + }, ) ) db.add(Service(name="Test Service", api_key="123456789")) diff --git a/test/test_auth_general.py b/test/test_auth_general.py index 4cc9ba5..a7f73a6 100644 --- a/test/test_auth_general.py +++ b/test/test_auth_general.py @@ -32,7 +32,10 @@ async def test_get_org_auth_root_su(default_client: AsyncClient, db_session): owner_contact_id=2, security_contact_id=3, status="approved", - intake_questionnaire={}, + intake_questionnaire={ + "metadata": {"version": 0, "submission_date": None}, + "questions": {}, + }, ) ) db_session.flush() diff --git a/test/test_organisation.py b/test/test_organisation.py index 364c753..ca7183c 100644 --- a/test/test_organisation.py +++ b/test/test_organisation.py @@ -103,9 +103,13 @@ async def test_patch_org_questionnaire_partial_success( assert data["status"] == "partial" assert "intake_questionnaire" in data assert isinstance(data["intake_questionnaire"], dict) - assert data["intake_questionnaire"]["question_one"] == "new answer one" - assert data["intake_questionnaire"]["question_two"] == "answer two" - assert data["intake_questionnaire"]["question_three"] is None + metadata = data["intake_questionnaire"]["metadata"] + assert metadata["version"] == 0 + assert metadata["submission_date"] is None + questions = data["intake_questionnaire"]["questions"] + assert questions["question_one"] == "new answer one" + assert questions["question_two"] == "answer two" + assert questions["question_three"] is None @pytest.mark.parametrize( @@ -172,9 +176,13 @@ async def test_patch_org_questionnaire_submit_success( assert data["status"] == "submitted" assert "intake_questionnaire" in data assert isinstance(data["intake_questionnaire"], dict) - assert data["intake_questionnaire"]["question_one"] == "new answer one" - assert data["intake_questionnaire"]["question_two"] == "answer two" - assert data["intake_questionnaire"]["question_three"] is None + metadata = data["intake_questionnaire"]["metadata"] + assert metadata["version"] == 0 + assert metadata["submission_date"] is not None + questions = data["intake_questionnaire"]["questions"] + assert questions["question_one"] == "new answer one" + assert questions["question_two"] == "answer two" + assert questions["question_three"] is None @pytest.mark.parametrize( diff --git a/test/test_user.py b/test/test_user.py index ad924f7..148ecae 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -161,9 +161,12 @@ async def test_get_self_orgs_dynamic(default_client: AsyncClient): "security_contact": {"email": "security@test.org", "id": 3}, "billing_contact": {"email": "billing@test.org", "id": 1}, "intake_questionnaire": { - "question_one": None, - "question_three": None, - "question_two": "answer two", + "questions": { + "question_one": None, + "question_three": None, + "question_two": "answer two", + }, + "metadata": {"version": 0, "submission_date": None}, }, } ]