Evaluation Mode
Overview
When evaluationMode=true is set at the school level, all teacher modules except Exam Management become read-only. This allows teachers to focus solely on exam operations during evaluation periods without modifying other records.
Module Impact
| Module | Read | Write | Notes |
|---|---|---|---|
| Announcement | ✅ | ❌ | Read-only |
| Assignment | ✅ | ❌ | Read-only |
| Attendance | ✅ | ❌ | Cannot mark attendance |
| Class Management | ✅ | ❌ | Read-only |
| Competition | ✅ | ❌ | Read-only |
| Events | ✅ | ❌ | Read-only |
| Exam | ✅ | ✅ | Full write access |
| Fee | ✅ | ❌ | Read-only |
| Leave | ✅ | ❌ | Read-only |
| Results | ✅ | ❌ | Read-only (exam results managed via Exam) |
| Subjects | ✅ | ❌ | Read-only |
| Teachers | ✅ | ❌ | Read-only |
| Time Table | ✅ | ❌ | Read-only |
| Messaging | ✅ | ❌ | Read-only |
Implementation
Server-Side Enforcement
The NestJS API checks school.evaluationMode before allowing write operations:
@Injectable()
export class EvaluationModeGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const school = getCurrentSchool(); // from JWT schoolId
if (school.evaluationMode && !isExamModule(context.getClass())) {
throw new ForbiddenException('EVALUATION_MODE_READONLY');
}
return true;
}
}
App-Side Handling — EvaluationInterceptor
// dio_client.dart
class EvaluationInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 403 &&
err.response?.data['error'] == 'EVALUATION_MODE_READONLY') {
// Emit event to trigger permission sync
EventBus.instance.emit('EVALUATION_MODE_TRIGGERED');
// Show snackbar
Get.snackbar(
'Evaluation Mode',
'Write operations are disabled during the evaluation period.',
snackPosition: SnackPosition.BOTTOM,
);
}
handler.next(err);
}
}
Permission Re-Sync
// auth_viewmodel.dart
class AuthViewModel extends BaseProvider {
Future<void> syncPermissions() async {
try {
final response = await _authService.getPermissions();
_permissions = response.data['permissions'];
// Persist to shared_preferences for offline access
await storage.write('permissions', jsonEncode(_permissions));
} catch (e) {
setError(e.toString());
}
}
}
// Called when EvaluationInterceptor emits event
EventBus.instance.on('EVALUATION_MODE_TRIGGERED', (_) {
context.read<AuthViewModel>().syncPermissions();
});
UI Disable Pattern
Each ViewModel exposes a canWrite check:
bool get canWriteAttendance {
final hasWrite = _permissions.any(
(p) => p['moduleName'] == 'attendance' && p['canWrite'] == true,
);
return hasWrite && !_isEvaluationMode;
}
bool get isEvaluationMode {
// Fetched from school config on login
return _authService.isEvaluationMode;
}
Write buttons are conditionally rendered:
ElevatedButton(
onPressed: vm.canWriteAttendance
? () => _submitAttendance()
: null, // Disabled in evaluation mode
child: const Text('Submit Attendance'),
)
Detecting Evaluation Mode
Evaluation mode is detected:
- On 403 response —
EvaluationInterceptorcatchesEVALUATION_MODE_READONLY - On app lifecycle resume —
AppLifecycleObserverre-fetches permissions from server - On login — Permissions returned in auth response include
evaluationModeflag