319 lines
9.6 KiB
Dart
319 lines
9.6 KiB
Dart
/// User Info Model
|
|
///
|
|
/// Data model for user information from API.
|
|
/// Handles JSON serialization and conversion to domain entity.
|
|
library;
|
|
|
|
import 'package:worker/core/database/models/enums.dart';
|
|
import 'package:worker/features/account/domain/entities/user_info.dart';
|
|
|
|
/// User Info Model
|
|
///
|
|
/// Maps API response from get_user_info endpoint to UserInfo entity.
|
|
class UserInfoModel {
|
|
const UserInfoModel({
|
|
required this.userId,
|
|
required this.fullName,
|
|
this.email,
|
|
this.phoneNumber,
|
|
required this.role,
|
|
required this.status,
|
|
required this.loyaltyTier,
|
|
this.totalPoints = 0,
|
|
this.availablePoints = 0,
|
|
this.expiringPoints = 0,
|
|
this.avatarUrl,
|
|
this.companyName,
|
|
this.taxId,
|
|
this.address,
|
|
this.cccd,
|
|
this.dateOfBirth,
|
|
this.gender,
|
|
this.idCardFront,
|
|
this.idCardBack,
|
|
this.certificates = const [],
|
|
this.membershipStatus,
|
|
this.membershipStatusColor,
|
|
this.isVerified = false,
|
|
this.credentialDisplay = false,
|
|
this.referralCode,
|
|
this.erpnextCustomerId,
|
|
this.createdAt,
|
|
this.updatedAt,
|
|
});
|
|
|
|
final String userId;
|
|
final String fullName;
|
|
final String? email;
|
|
final String? phoneNumber;
|
|
final UserRole role;
|
|
final UserStatus status;
|
|
final LoyaltyTier loyaltyTier;
|
|
final int totalPoints;
|
|
final int availablePoints;
|
|
final int expiringPoints;
|
|
final String? avatarUrl;
|
|
final String? companyName;
|
|
final String? taxId;
|
|
final String? address;
|
|
final String? cccd;
|
|
final DateTime? dateOfBirth;
|
|
final String? gender;
|
|
final String? idCardFront;
|
|
final String? idCardBack;
|
|
final List<String> certificates;
|
|
final String? membershipStatus;
|
|
final String? membershipStatusColor;
|
|
final bool isVerified;
|
|
final bool credentialDisplay;
|
|
final String? referralCode;
|
|
final String? erpnextCustomerId;
|
|
final DateTime? createdAt;
|
|
final DateTime? updatedAt;
|
|
|
|
// =========================================================================
|
|
// JSON SERIALIZATION
|
|
// =========================================================================
|
|
|
|
/// Create UserInfoModel from API JSON response
|
|
///
|
|
/// Expected API response structure:
|
|
/// ```json
|
|
/// {
|
|
/// "message": {
|
|
/// "user_id": "...",
|
|
/// "full_name": "...",
|
|
/// "email": "...",
|
|
/// "phone_number": "...",
|
|
/// "role": "customer",
|
|
/// "status": "active",
|
|
/// "loyalty_tier": "gold",
|
|
/// "total_points": 1000,
|
|
/// "available_points": 800,
|
|
/// "expiring_points": 200,
|
|
/// "avatar_url": "...",
|
|
/// "company_name": "...",
|
|
/// "tax_id": "...",
|
|
/// "address": "...",
|
|
/// "cccd": "...",
|
|
/// "referral_code": "...",
|
|
/// "erpnext_customer_id": "...",
|
|
/// "created_at": "...",
|
|
/// "updated_at": "...",
|
|
/// "last_login_at": "..."
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
factory UserInfoModel.fromJson(Map<String, dynamic> json) {
|
|
// API response structure: { "message": { "full_name": "...", ... } }
|
|
// Data is directly under 'message', not nested in 'data'
|
|
final data = json['message'] as Map<String, dynamic>? ?? json;
|
|
|
|
return UserInfoModel(
|
|
// Use email as userId since API doesn't provide user_id
|
|
userId: data['email'] as String? ?? data['phone'] as String? ?? 'unknown',
|
|
fullName: data['full_name'] as String? ?? '',
|
|
email: data['email'] as String?,
|
|
phoneNumber: data['phone'] as String?,
|
|
// Default values for fields not in this API
|
|
role: UserRole.customer, // Default to customer
|
|
status: UserStatus.active, // Default to active
|
|
loyaltyTier: LoyaltyTier.bronze, // Default to bronze
|
|
totalPoints: 0,
|
|
availablePoints: 0,
|
|
expiringPoints: 0,
|
|
avatarUrl: data['avatar'] as String?,
|
|
companyName: data['company_name'] as String?,
|
|
taxId: data['tax_code'] as String?,
|
|
address: data['address'] as String?,
|
|
cccd: null, // CCCD number not in API
|
|
dateOfBirth: _parseDateTime(data['date_of_birth'] as String?),
|
|
gender: data['gender'] as String?,
|
|
idCardFront: data['id_card_front'] as String?,
|
|
idCardBack: data['id_card_back'] as String?,
|
|
certificates: (data['certificates'] as List<dynamic>?)
|
|
?.map((e) => e.toString())
|
|
.toList() ?? const [],
|
|
membershipStatus: data['membership_status'] as String?,
|
|
membershipStatusColor: data['membership_status_color'] as String?,
|
|
isVerified: data['is_verified'] as bool? ?? false,
|
|
credentialDisplay: data['credential_display'] as bool? ?? false,
|
|
referralCode: null,
|
|
erpnextCustomerId: null,
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
);
|
|
}
|
|
|
|
/// Convert UserInfoModel to JSON
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'user_id': userId,
|
|
'full_name': fullName,
|
|
'email': email,
|
|
'phone_number': phoneNumber,
|
|
'role': role.name,
|
|
'status': status.name,
|
|
'loyalty_tier': loyaltyTier.name,
|
|
'total_points': totalPoints,
|
|
'available_points': availablePoints,
|
|
'expiring_points': expiringPoints,
|
|
'avatar_url': avatarUrl,
|
|
'company_name': companyName,
|
|
'tax_id': taxId,
|
|
'address': address,
|
|
'cccd': cccd,
|
|
'referral_code': referralCode,
|
|
'erpnext_customer_id': erpnextCustomerId,
|
|
'created_at': createdAt?.toIso8601String(),
|
|
'updated_at': updatedAt?.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
// =========================================================================
|
|
// DOMAIN CONVERSION
|
|
// =========================================================================
|
|
|
|
/// Convert to domain entity
|
|
UserInfo toEntity() {
|
|
final now = DateTime.now();
|
|
return UserInfo(
|
|
userId: userId,
|
|
fullName: fullName,
|
|
email: email,
|
|
phoneNumber: phoneNumber,
|
|
role: role,
|
|
status: status,
|
|
loyaltyTier: loyaltyTier,
|
|
totalPoints: totalPoints,
|
|
availablePoints: availablePoints,
|
|
expiringPoints: expiringPoints,
|
|
avatarUrl: avatarUrl,
|
|
companyName: companyName,
|
|
taxId: taxId,
|
|
address: address,
|
|
cccd: cccd,
|
|
dateOfBirth: dateOfBirth,
|
|
gender: gender,
|
|
idCardFront: idCardFront,
|
|
idCardBack: idCardBack,
|
|
certificates: certificates,
|
|
membershipStatus: membershipStatus,
|
|
membershipStatusColor: membershipStatusColor,
|
|
isVerified: isVerified,
|
|
credentialDisplay: credentialDisplay,
|
|
referralCode: referralCode,
|
|
erpnextCustomerId: erpnextCustomerId,
|
|
createdAt: createdAt ?? now,
|
|
updatedAt: updatedAt ?? now,
|
|
);
|
|
}
|
|
|
|
/// Create model from domain entity
|
|
factory UserInfoModel.fromEntity(UserInfo entity) {
|
|
return UserInfoModel(
|
|
userId: entity.userId,
|
|
fullName: entity.fullName,
|
|
email: entity.email,
|
|
phoneNumber: entity.phoneNumber,
|
|
role: entity.role,
|
|
status: entity.status,
|
|
loyaltyTier: entity.loyaltyTier,
|
|
totalPoints: entity.totalPoints,
|
|
availablePoints: entity.availablePoints,
|
|
expiringPoints: entity.expiringPoints,
|
|
avatarUrl: entity.avatarUrl,
|
|
companyName: entity.companyName,
|
|
taxId: entity.taxId,
|
|
address: entity.address,
|
|
cccd: entity.cccd,
|
|
referralCode: entity.referralCode,
|
|
erpnextCustomerId: entity.erpnextCustomerId,
|
|
createdAt: entity.createdAt,
|
|
updatedAt: entity.updatedAt,
|
|
);
|
|
}
|
|
|
|
// =========================================================================
|
|
// HELPER METHODS
|
|
// =========================================================================
|
|
|
|
/// Parse user role from string
|
|
static UserRole _parseUserRole(String? role) {
|
|
if (role == null) return UserRole.customer;
|
|
|
|
return UserRole.values.firstWhere(
|
|
(e) => e.name.toLowerCase() == role.toLowerCase(),
|
|
orElse: () => UserRole.customer,
|
|
);
|
|
}
|
|
|
|
/// Parse user status from string
|
|
static UserStatus _parseUserStatus(String? status) {
|
|
if (status == null) return UserStatus.pending;
|
|
|
|
// Handle ERPNext status values
|
|
final normalizedStatus = status.toLowerCase();
|
|
if (normalizedStatus == 'enabled' || normalizedStatus == 'active') {
|
|
return UserStatus.active;
|
|
}
|
|
if (normalizedStatus == 'disabled' || normalizedStatus == 'inactive') {
|
|
return UserStatus.suspended;
|
|
}
|
|
|
|
return UserStatus.values.firstWhere(
|
|
(e) => e.name.toLowerCase() == normalizedStatus,
|
|
orElse: () => UserStatus.pending,
|
|
);
|
|
}
|
|
|
|
/// Parse loyalty tier from string
|
|
static LoyaltyTier _parseLoyaltyTier(String? tier) {
|
|
if (tier == null) return LoyaltyTier.bronze;
|
|
|
|
return LoyaltyTier.values.firstWhere(
|
|
(e) => e.name.toLowerCase() == tier.toLowerCase(),
|
|
orElse: () => LoyaltyTier.bronze,
|
|
);
|
|
}
|
|
|
|
/// Parse integer from dynamic value
|
|
static int _parseInt(dynamic value) {
|
|
if (value == null) return 0;
|
|
if (value is int) return value;
|
|
if (value is double) return value.toInt();
|
|
if (value is String) return int.tryParse(value) ?? 0;
|
|
return 0;
|
|
}
|
|
|
|
/// Parse DateTime from string
|
|
static DateTime? _parseDateTime(String? dateString) {
|
|
if (dateString == null || dateString.isEmpty) return null;
|
|
try {
|
|
return DateTime.parse(dateString);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// EQUALITY
|
|
// =========================================================================
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
|
|
return other is UserInfoModel && other.userId == userId;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => userId.hashCode;
|
|
|
|
@override
|
|
String toString() {
|
|
return 'UserInfoModel(userId: $userId, fullName: $fullName, '
|
|
'tier: $loyaltyTier, points: $totalPoints)';
|
|
}
|
|
}
|