open System open System.Text.RegularExpressions type DocumentType = | Passport | DriverLicense | InternationalPassport | IdentityCard type ValidationResult = | Valid | InvalidSeries of string | InvalidNumber of string | InvalidIssueDate of string | InvalidExpiryDate of string | InvalidIssuedBy of string [] type Document(docType: DocumentType, series: string, number: string, issuedBy: string, issueDate: DateTime, expiryDate: DateTime option) = let mutable _series = series let mutable _number = number let mutable _issuedBy = issuedBy let mutable _issueDate = issueDate let mutable _expiryDate = expiryDate let seriesPattern = @"^\d{4}$" let numberPattern = @"^\d{6}$" let issuedByPattern = @"^[А-Яа-яA-Za-z\s\-\.]{5,100}$" let validateSeries (s: string) = if String.IsNullOrWhiteSpace(s) then false else Regex.IsMatch(s, seriesPattern) let validateNumber (n: string) = if String.IsNullOrWhiteSpace(n) then false else Regex.IsMatch(n, numberPattern) let validateIssuedBy (issuer: string) = if String.IsNullOrWhiteSpace(issuer) then false else Regex.IsMatch(issuer, issuedByPattern) let validateDates (issue: DateTime) (expiry: DateTime option) = let now = DateTime.Now match expiry with | Some exp -> issue <= now && issue < exp | None -> issue <= now let validate() = if not (validateSeries _series) then InvalidSeries "Series must be 4 digits" elif not (validateNumber _number) then InvalidNumber "Number must be 6 digits" elif not (validateIssuedBy _issuedBy) then InvalidIssuedBy "Invalid issuer format" elif not (validateDates _issueDate _expiryDate) then InvalidIssueDate "Invalid dates" else Valid do let validation = validate() match validation with | Valid -> () | _ -> failwithf "Document validation failed: %A" validation member this.DocumentType = docType member this.Series with get() = _series and set(value) = if validateSeries value then _series <- value else raise (ArgumentException("Invalid series format. Must be 4 digits.")) member this.Number with get() = _number and set(value) = if validateNumber value then _number <- value else raise (ArgumentException("Invalid number format. Must be 6 digits.")) member this.IssuedBy with get() = _issuedBy and set(value) = if validateIssuedBy value then _issuedBy <- value else raise (ArgumentException("Invalid issuer format.")) member this.IssueDate with get() = _issueDate and set(value) = if validateDates value _expiryDate then _issueDate <- value else raise (ArgumentException("Invalid issue date.")) member this.ExpiryDate with get() = _expiryDate and set(value) = if validateDates _issueDate value then _expiryDate <- value else raise (ArgumentException("Invalid expiry date.")) member this.Validate() = validate() member this.IsValid = this.Validate() = Valid member this.Display() = let docTypeStr = match docType with | Passport -> "Паспорт" | DriverLicense -> "Водительские права" | InternationalPassport -> "Загранпаспорт" | IdentityCard -> "Удостоверение личности" let expiryStr = match _expiryDate with | Some date -> date.ToString("dd.MM.yyyy") | None -> "Бессрочно" printfn "┌─────────────────────────────────────┐" printfn "│ %-35s │" docTypeStr printfn "├─────────────────────────────────────┤" printfn "│ Серия: %-28s │" _series printfn "│ Номер: %-28s │" _number printfn "│ Выдан: %-28s │" _issuedBy printfn "│ Дата выдачи: %-19s │" (_issueDate.ToString("dd.MM.yyyy")) printfn "│ Действителен до: %-15s │" expiryStr printfn "└─────────────────────────────────────┘" override this.ToString() = sprintf "%A %s-%s" docType _series _number interface IComparable with member this.CompareTo(obj) = match obj with | :? Document as other -> this.CompareTo(other) | _ -> invalidArg "obj" "Cannot compare with non-Document object" member this.CompareTo(other: Document) = if other = null then 1 else let seriesComparison = String.Compare(_series, other.Series) if seriesComparison <> 0 then seriesComparison else String.Compare(_number, other.Number) interface IComparable with member this.CompareTo(other) = this.CompareTo(other) override this.Equals(obj) = match obj with | :? Document as other -> _series = other.Series && _number = other.Number | _ -> false override this.GetHashCode() = hash (_series, _number) static member TryCreate(docType: DocumentType, series: string, number: string, issuedBy: string, issueDate: DateTime, expiryDate: DateTime option) = try let doc = Document(docType, series, number, issuedBy, issueDate, expiryDate) Some doc with | _ -> None static member op_Equality(left: Document, right: Document) = if left = null && right = null then true elif left = null || right = null then false else left.Equals(right) static member op_Inequality(left: Document, right: Document) = not (left = right) static member op_LessThan(left: Document, right: Document) = if left = null then right <> null elif right = null then false else (left :> IComparable).CompareTo(right) < 0 static member op_GreaterThan(left: Document, right: Document) = if right = null then left <> null elif left = null then false else (left :> IComparable).CompareTo(right) > 0 module DocumentTests = let testDocumentCreation() = printfn "=== Тест создания документов ===" let passport = Document(Passport, "1234", "567890", "УМВД России по г. Москве", DateTime(2020, 5, 15), None) let license = Document(DriverLicense, "5678", "123456", "ГИБДД УМВД России", DateTime(2021, 3, 10), Some(DateTime(2031, 3, 10))) passport.Display() printfn "" license.Display() printfn "" let testValidation() = printfn "=== Тест валидации ===" let validCases = [ ("Valid passport", Passport, "1234", "567890", "УМВД России по г. Москве", DateTime(2020, 5, 15), None) ("Valid license", DriverLicense, "5678", "123456", "ГИБДД УМВД России", DateTime(2021, 3, 10), Some(DateTime(2031, 3, 10))) ] let invalidCases = [ ("Invalid series", Passport, "12", "567890", "УМВД России", DateTime(2020, 5, 15), None) ("Invalid number", Passport, "1234", "56789", "УМВД России", DateTime(2020, 5, 15), None) ("Invalid issuer", Passport, "1234", "567890", "УМ", DateTime(2020, 5, 15), None) ] printfn "Валидные документы:" for (description, docType, series, number, issuedBy, issueDate, expiryDate) in validCases do match Document.TryCreate(docType, series, number, issuedBy, issueDate, expiryDate) with | Some doc -> printfn "✓ %s: %s" description (doc.ToString()) | None -> printfn "✗ %s: Ошибка создания" description printfn "\nНевалидные документы:" for (description, docType, series, number, issuedBy, issueDate, expiryDate) in invalidCases do match Document.TryCreate(docType, series, number, issuedBy, issueDate, expiryDate) with | Some doc -> printfn "✗ %s: Неожиданно валиден: %s" description (doc.ToString()) | None -> printfn "✓ %s: Корректно отклонен" description printfn "" let testComparison() = printfn "=== Тест сравнения документов ===" let doc1 = Document(Passport, "1234", "567890", "УМВД России", DateTime(2020, 5, 15), None) let doc2 = Document(Passport, "1234", "567890", "Другой орган", DateTime(2021, 1, 1), None) let doc3 = Document(DriverLicense, "5678", "123456", "ГИБДД", DateTime(2021, 3, 10), Some(DateTime(2031, 3, 10))) let doc4 = Document(Passport, "1235", "567890", "УМВД России", DateTime(2020, 5, 15), None) printfn "doc1 = doc2 (одинаковые серия и номер): %b" (doc1 = doc2) printfn "doc1 = doc3 (разные серия и номер): %b" (doc1 = doc3) printfn "doc1 < doc4 (сравнение по серии): %b" (doc1 < doc4) printfn "doc4 > doc1 (сравнение по серии): %b" (doc4 > doc1) let documents = [| doc1; doc3; doc4; doc2 |] printfn "\nДо сортировки:" documents |> Array.iter (fun doc -> printfn " %s" (doc.ToString())) let sortedDocs = documents |> Array.sort printfn "\nПосле сортировки:" sortedDocs |> Array.iter (fun doc -> printfn " %s" (doc.ToString())) printfn "" [] let main argv = printfn "Лабораторная работа 7: ООП - Класс Document" printfn "==========================================\n" DocumentTests.testDocumentCreation() DocumentTests.testValidation() DocumentTests.testComparison() printfn "=== Интерактивный тест ===" try printfn "Создание документа с некорректными данными..." let invalidDoc = Document(Passport, "123", "567890", "УМВД", DateTime(2020, 5, 15), None) printfn "Документ создан (не должно было произойти): %s" (invalidDoc.ToString()) with | ex -> printfn "✓ Корректно перехвачена ошибка: %s" ex.Message 0