bharat_erp/lib/shared/models/user_model.dart

204 lines
6.7 KiB
Dart

import 'package:freezed_annotation/freezed_annotation.dart';
import '../../core/constants/enums.dart';
part 'user_model.freezed.dart';
part 'user_model.g.dart';
@freezed
class UserModel with _$UserModel {
const factory UserModel({
required String id,
@JsonKey(name: 'employee_id') required String employeeId,
required String name,
required String email,
required String mobile,
required String role,
String? department,
@Default('active') String status,
@JsonKey(name: 'company_id') String? companyId,
@JsonKey(name: 'branch_id') String? branchId,
String? avatarUrl,
@Default([]) List<String> permissions,
DateTime? createdAt,
DateTime? updatedAt,
}) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
/// Parses user objects returned by auth endpoints (`/auth/login`, `/auth/me`).
factory UserModel.fromLoginJson(Map<String, dynamic> json) {
final roleRaw = json['role_name'] ??
json['role'] ??
json['role_slug'] ??
_firstRoleFromList(json['roles']) ??
'employee';
final role = roleRaw is Map
? (roleRaw['name'] as String? ?? roleRaw['slug'] as String? ?? 'employee')
: roleRaw.toString();
final name = json['full_name'] ??
json['name'] ??
[
json['first_name'],
json['last_name'],
].whereType<String>().where((s) => s.isNotEmpty).join(' ');
return UserModel(
id: json['id']?.toString() ?? '',
employeeId: (json['employee_code'] ?? json['employee_id'] ?? '').toString(),
name: name is String && name.isNotEmpty ? name : (json['email'] as String? ?? ''),
email: json['email'] as String? ?? '',
mobile: json['mobile']?.toString() ?? '',
role: role,
department: _nestedLabel(json['department_name']) ??
_nestedLabel(json['department']),
status: json['status'] as String? ?? 'active',
companyId: json['company_id']?.toString(),
branchId: json['branch_id']?.toString(),
avatarUrl: json['avatar_url'] as String? ?? json['avatarUrl'] as String?,
permissions: (json['permissions'] as List<dynamic>?)
?.map((e) => e.toString())
.toList() ??
const [],
createdAt: json['created_at'] != null
? DateTime.tryParse(json['created_at'].toString())
: null,
updatedAt: json['updated_at'] != null
? DateTime.tryParse(json['updated_at'].toString())
: null,
);
}
}
Object? _firstRoleFromList(Object? roles) {
if (roles is! List || roles.isEmpty) return null;
return roles.first;
}
String? _nestedLabel(Object? value) {
if (value == null) return null;
if (value is String) return value.isEmpty ? null : value;
if (value is Map) {
final name = value['name'];
if (name is String && name.isNotEmpty) return name;
final code = value['code'];
if (code is String && code.isNotEmpty) return code;
}
return null;
}
extension UserModelX on UserModel {
UserRole get userRole => UserRole.fromValue(role);
EntityStatus get entityStatus => EntityStatus.fromValue(status);
bool get isActive => status == EntityStatus.active.value;
}
@freezed
class AuthTokens with _$AuthTokens {
const factory AuthTokens({
@JsonKey(name: 'access_token') required String accessToken,
@JsonKey(name: 'refresh_token') required String refreshToken,
@JsonKey(name: 'expires_in') int? expiresIn,
}) = _AuthTokens;
factory AuthTokens.fromJson(Map<String, dynamic> json) => _$AuthTokensFromJson(json);
}
@freezed
class LoginRequest with _$LoginRequest {
const factory LoginRequest({
required String email,
required String password,
@JsonKey(name: 'company_code') String? companyCode,
}) = _LoginRequest;
factory LoginRequest.fromJson(Map<String, dynamic> json) => _$LoginRequestFromJson(json);
}
@freezed
class LoginResponse with _$LoginResponse {
const factory LoginResponse({
required AuthTokens tokens,
required UserModel user,
@JsonKey(name: 'requires_otp') @Default(false) bool requiresOtp,
}) = _LoginResponse;
factory LoginResponse.fromJson(Map<String, dynamic> json) => _$LoginResponseFromJson(json);
/// Supports API `data` shapes:
/// - `{ access_token, refresh_token, user? }`
/// - `{ tokens: { access_token, refresh_token }, user }`
factory LoginResponse.fromApiData(Map<String, dynamic> data) {
final Map<String, dynamic> tokenSource;
if (data['tokens'] is Map<String, dynamic>) {
tokenSource = data['tokens'] as Map<String, dynamic>;
} else {
tokenSource = data;
}
final accessToken = tokenSource['access_token'] as String? ??
tokenSource['accessToken'] as String? ??
tokenSource['token'] as String?;
final refreshToken = tokenSource['refresh_token'] as String? ??
tokenSource['refreshToken'] as String? ??
'';
if (accessToken == null || accessToken.isEmpty) {
throw StateError('Login response missing access_token');
}
final userJson = data['user'];
final user = userJson is Map<String, dynamic>
? UserModel.fromLoginJson(userJson)
: UserModel.fromLoginJson({
if (data['email'] != null) 'email': data['email'],
if (data['full_name'] != null) 'full_name': data['full_name'],
if (data['name'] != null) 'name': data['name'],
if (data['id'] != null) 'id': data['id'],
});
return LoginResponse(
tokens: AuthTokens(
accessToken: accessToken,
refreshToken: refreshToken,
expiresIn: (tokenSource['expires_in'] as num?)?.toInt(),
),
user: user,
requiresOtp: data['requires_otp'] as bool? ?? false,
);
}
}
@freezed
class OtpVerifyRequest with _$OtpVerifyRequest {
const factory OtpVerifyRequest({
required String email,
required String otp,
}) = _OtpVerifyRequest;
factory OtpVerifyRequest.fromJson(Map<String, dynamic> json) =>
_$OtpVerifyRequestFromJson(json);
}
@freezed
class ChangePasswordRequest with _$ChangePasswordRequest {
const factory ChangePasswordRequest({
@JsonKey(name: 'current_password') required String currentPassword,
@JsonKey(name: 'new_password') required String newPassword,
@JsonKey(name: 'confirm_password') required String confirmPassword,
}) = _ChangePasswordRequest;
factory ChangePasswordRequest.fromJson(Map<String, dynamic> json) =>
_$ChangePasswordRequestFromJson(json);
}
@freezed
class ForgotPasswordRequest with _$ForgotPasswordRequest {
const factory ForgotPasswordRequest({
required String email,
}) = _ForgotPasswordRequest;
factory ForgotPasswordRequest.fromJson(Map<String, dynamic> json) =>
_$ForgotPasswordRequestFromJson(json);
}