import 'dart:convert'; import 'school_identity.dart'; import 'school_term.dart'; class ExamRecord { const ExamRecord({ required this.code, required this.name, this.assessment = '', this.candidate = '', this.sequence = '', this.room = '', this.seat = '', this.time = '', this.date = '', this.status = '', this.notes = '', this.examTypeId, }); final String code, name, assessment, candidate, sequence, room, seat, time; final String date, status, notes; final int? examTypeId; String get undergraduateKey => jsonEncode([examTypeId, code, date, time, room, seat]); Map toJson() => { 'code': code, 'name': name, 'assessment': assessment, 'candidate': candidate, 'sequence': sequence, 'room': room, 'seat': seat, 'time': time, 'date': date, 'status': status, 'notes': notes, 'examTypeId': examTypeId, }; factory ExamRecord.fromJson(Map json) { String field(String key, {bool optional = false}) { final value = optional && !json.containsKey(key) ? '' : json[key]; if (value is! String || value.length > 1000) { throw const FormatException('考试信息无效'); } return value; } final record = ExamRecord( code: field('code'), name: field('name'), assessment: field('assessment'), candidate: field('candidate'), sequence: field('sequence'), room: field('room'), seat: field('seat'), time: field('time'), date: field('date', optional: true), status: field('status', optional: true), notes: field('notes', optional: true), examTypeId: json['examTypeId'] as int?, ); if (record.code.isEmpty || record.name.isEmpty) { throw const FormatException('考试课程无效'); } return record; } } class ExamSchedule { ExamSchedule({ required this.semesterLabel, required this.termId, required List records, this.school = SupportedSchool.uestcGraduate, Set requestedExamTypes = const {}, }) : records = List.unmodifiable(records), requestedExamTypes = Set.unmodifiable(requestedExamTypes); final String semesterLabel, termId; final List records; final SupportedSchool school; final Set requestedExamTypes; Map toJson() => { 'semesterLabel': semesterLabel, 'termId': termId, 'records': records.map((r) => r.toJson()).toList(), 'schoolId': school.id, 'requestedExamTypes': requestedExamTypes.toList()..sort(), }; factory ExamSchedule.fromJson(Map json) { try { final label = json['semesterLabel'], id = json['termId'], rows = json['records']; if (label is! String || label.trim().isEmpty || id is! String || !RegExp(r'^\d+$').hasMatch(id) || rows is! List || rows.length > 500) { throw const FormatException('考试列表无效'); } final records = rows .map((r) => ExamRecord.fromJson(Map.from(r as Map))) .toList(); final school = json['schoolId'] == null ? SupportedSchool.uestcGraduate : SupportedSchool.fromId(json['schoolId'] as String); final rawTypes = json['requestedExamTypes'] ?? []; if (rawTypes is! List || rawTypes.any((t) => t is! int || t < 1 || t > 3)) { throw const FormatException('考试类型无效'); } final types = rawTypes.cast().toSet(); SchoolTerm(int.parse(id), school: school); if (school == SupportedSchool.uestcUndergraduate && (types.isEmpty || records.any((r) => !types.contains(r.examTypeId)))) { throw const FormatException('本科考试类型无效'); } if (records .map( (r) => school == SupportedSchool.uestcGraduate ? r.code : r.undergraduateKey, ) .toSet() .length != records.length) { throw const FormatException('考试课程重复'); } return ExamSchedule( semesterLabel: label, termId: id, records: records, school: school, requestedExamTypes: types, ); } catch (_) { throw const FormatException('考试列表无效'); } } }