|
|
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
|
|
|
|
|
|
[<AllowNullLiteral>]
|
|
|
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<Document> 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<Document>).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<Document>).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 ""
|
|
|
|
|
|
[<EntryPoint>]
|
|
|
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 |