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('考试列表无效'); } } }