diff --git a/models/answer.py b/models/answer.py index e66bc16..fc7dd0e 100644 --- a/models/answer.py +++ b/models/answer.py @@ -5,6 +5,7 @@ from typing import TypeAlias, Annotated from pydantic import field_validator, field_serializer, Field from models import BaseModel, form +from utils import validate_tin, validate_snils class AnswerError(Enum): @@ -13,7 +14,10 @@ class AnswerError(Enum): TOO_FEW_SELECTED = "The number of selected items is less than the minimum required." TOO_MANY_SELECTED = "The number of selected items is more than the maximum allowed." DUPLICATE_QUESTIONS = "Each value must correspond to a different question." + REQUIRED_QUIESTION_NOT_ANSWERED = "The required question was not answered." INCORRECT_IDS = "The ids for some questions are incorrect." + TIN_VALIDATION_FAILED = "The TIN validation process failed." + SNILS_VALIDATION_FAILED = "The SNILS validation process failed." class BaseValue(BaseModel): @@ -35,6 +39,17 @@ class TextValue(BaseValue): if question.max_length and len(self.value) > question.max_length: raise ValueError(AnswerError.TOO_LONG.value) + if ( + question.validator == form.TextValidator.tin + and validate_tin(self.value) is False + ): + raise ValueError(AnswerError.TIN_VALIDATION_FAILED.value) + if ( + question.validator == form.TextValidator.snils + and validate_snils(self.value) is False + ): + raise ValueError(AnswerError.SNILS_VALIDATION_FAILED.value) + class SelectorValue(BaseValue): question_type: form.QuestionType = form.QuestionType.selector diff --git a/models/form.py b/models/form.py index 01da847..400f5c7 100644 --- a/models/form.py +++ b/models/form.py @@ -21,6 +21,11 @@ class QuestionType(IntEnum): selector = 2 +class TextValidator(IntEnum): + tin = 1 + snils = 2 + + class BaseQuestion(BaseModel): id: UUID = Field(default_factory=uuid4) question_type: QuestionType @@ -39,25 +44,33 @@ class Option(BaseModel): class TextQuestion(BaseQuestion): question_type: QuestionType = QuestionType.text + validator: TextValidator | None = None min_length: int | None = None max_length: int | None = None @field_validator("min_length") @classmethod def validate_min_length(cls, v, info): - if v is not None and v < 0: - raise ValueError(FormError.MIN_LENGTH_ERR.value) + validator = info.data.get("validator") + if v is not None: + if v < 0: + raise ValueError(FormError.MIN_LENGTH_ERR.value) + if validator is not None: + return None return v @field_validator("max_length") @classmethod def validate_max_length(cls, v, info): min_length = info.data.get("min_length") + validator = info.data.get("validator") if v is not None: if v <= 0: raise ValueError(FormError.MAX_LENGTH_TOO_SMALL.value) if min_length is not None and v < min_length: raise ValueError(FormError.MAX_LENGTH_LESS_THAN_MIN_LENGTH.value) + if validator is not None: + return None return v @@ -89,9 +102,8 @@ class SelectorQuestion(BaseQuestion): Question: TypeAlias = SelectorQuestion | TextQuestion -class FormData(BaseModel): - name: str = Field(min_length=1) - description: str | None = Field(None, min_length=1) +class Page(BaseModel): + text: str | None = Field(None, min_length=1) questions: list[Question] = [] @field_validator("questions") @@ -107,6 +119,18 @@ class FormData(BaseModel): return v +class FormData(BaseModel): + name: str = Field(min_length=1) + pages: list[Page] = [] + + @property + def questions(self) -> list[Question]: + questions = [] + for page in self.pages: + questions.extend(page.questions) + return questions + + class Form(BaseModel): id: int data: FormData diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..2c71abe --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +from .validator import * diff --git a/utils/validator.py b/utils/validator.py new file mode 100644 index 0000000..e9fc4e2 --- /dev/null +++ b/utils/validator.py @@ -0,0 +1,76 @@ +def validate_tin(value: str) -> bool: + if len(value) != 10 and len(value) != 12: + return False + + value = list(map(int, value)) + + if len(value) == 10: + checksum = ( + ( + 2 * value[0] + + 4 * value[1] + + 10 * value[2] + + 3 * value[3] + + 5 * value[4] + + 9 * value[5] + + 4 * value[6] + + 6 * value[7] + + 8 * value[8] + ) + % 11 + % 10 + ) + return value[9] == checksum + if len(value) == 12: + checksum1 = ( + ( + 7 * value[0] + + 2 * value[1] + + 4 * value[2] + + 10 * value[3] + + 3 * value[4] + + 5 * value[5] + + 9 * value[6] + + 4 * value[7] + + 6 * value[8] + + 8 * value[9] + ) + % 11 + % 10 + ) + checksum2 = ( + ( + 3 * value[0] + + 7 * value[1] + + 2 * value[2] + + 4 * value[3] + + 10 * value[4] + + 3 * value[5] + + 5 * value[6] + + 9 * value[7] + + 4 * value[8] + + 6 * value[9] + + 8 * value[10] + ) + % 11 + % 10 + ) + return value[10] == checksum1 and value[11] == checksum2 + + return False + + +def validate_snils(value: str) -> bool: + if len(value) == 11: + checksum = 0 + for i in range(9): + checksum += int(value[i]) * (9 - i) + + if checksum > 101: + checksum = checksum % 101 + if checksum == 100 or checksum == 101: + checksum = 0 + + return checksum == int(value[9:]) + + return False