/// Data Model: Review /// /// JSON serializable model for review data from API. library; import 'package:worker/features/reviews/domain/entities/review.dart'; /// Review data model /// /// Handles JSON serialization/deserialization for review data from the API. /// /// API Response format (assumed based on common Frappe patterns): /// ```json /// { /// "name": "ITEM-{item_id}-{user_email}", /// "item_id": "GIB20 G04", /// "rating": 0.5, /// "comment": "Good product", /// "owner": "user@example.com", /// "creation": "2024-11-17 10:30:00", /// "modified": "2024-11-17 10:30:00" /// } /// ``` class ReviewModel { const ReviewModel({ required this.name, required this.itemId, required this.rating, required this.comment, this.owner, this.ownerFullName, this.creation, this.modified, }); /// Unique review identifier (format: ITEM-{item_id}-{user_email}) final String name; /// Product item code final String itemId; /// Rating (0-1 scale from API) final double rating; /// Review comment text final String comment; /// Email of the review owner final String? owner; /// Full name of the review owner (if available) final String? ownerFullName; /// ISO 8601 timestamp when review was created final String? creation; /// ISO 8601 timestamp when review was last modified final String? modified; /// Create model from JSON factory ReviewModel.fromJson(Map json) { // Handle nested user object if present String? ownerEmail; String? fullName; if (json.containsKey('user') && json['user'] is Map) { final user = json['user'] as Map; ownerEmail = user['name'] as String?; fullName = user['full_name'] as String?; } else { ownerEmail = json['owner'] as String?; fullName = json['owner_full_name'] as String? ?? json['full_name'] as String?; } return ReviewModel( name: json['name'] as String, itemId: json['item_id'] as String? ?? '', rating: (json['rating'] as num).toDouble(), comment: json['comment'] as String? ?? '', owner: ownerEmail, ownerFullName: fullName, creation: json['creation'] as String?, modified: json['modified'] as String?, ); } /// Convert model to JSON Map toJson() { return { 'name': name, 'item_id': itemId, 'rating': rating, 'comment': comment, if (owner != null) 'owner': owner, if (ownerFullName != null) 'owner_full_name': ownerFullName, if (creation != null) 'creation': creation, if (modified != null) 'modified': modified, }; } /// Convert to domain entity Review toEntity() { return Review( id: name, itemId: itemId, rating: rating, comment: comment, reviewerName: ownerFullName ?? _extractNameFromEmail(owner), reviewerEmail: owner, reviewDate: creation != null ? _parseDateTime(creation!) : null, ); } /// Create model from domain entity factory ReviewModel.fromEntity(Review entity) { return ReviewModel( name: entity.id, itemId: entity.itemId, rating: entity.rating, comment: entity.comment, owner: entity.reviewerEmail, ownerFullName: entity.reviewerName, creation: entity.reviewDate?.toIso8601String(), ); } /// Extract name from email (fallback if full name not available) /// /// Example: "john.doe@example.com" -> "John Doe" String? _extractNameFromEmail(String? email) { if (email == null) return null; final username = email.split('@').first; final parts = username.split('.'); return parts .map((part) => part.isEmpty ? '' : part[0].toUpperCase() + part.substring(1).toLowerCase()) .join(' '); } /// Parse datetime string from API /// /// Handles common formats: /// - ISO 8601: "2024-11-17T10:30:00" /// - Frappe format: "2024-11-17 10:30:00" DateTime? _parseDateTime(String dateString) { try { // Try ISO 8601 first return DateTime.tryParse(dateString); } catch (e) { try { // Try replacing space with T for ISO 8601 compatibility final normalized = dateString.replaceFirst(' ', 'T'); return DateTime.tryParse(normalized); } catch (e) { return null; } } } /// Copy with method ReviewModel copyWith({ String? name, String? itemId, double? rating, String? comment, String? owner, String? ownerFullName, String? creation, String? modified, }) { return ReviewModel( name: name ?? this.name, itemId: itemId ?? this.itemId, rating: rating ?? this.rating, comment: comment ?? this.comment, owner: owner ?? this.owner, ownerFullName: ownerFullName ?? this.ownerFullName, creation: creation ?? this.creation, modified: modified ?? this.modified, ); } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is ReviewModel && other.name == name && other.itemId == itemId && other.rating == rating && other.comment == comment && other.owner == owner && other.ownerFullName == ownerFullName && other.creation == creation && other.modified == modified; } @override int get hashCode { return Object.hash( name, itemId, rating, comment, owner, ownerFullName, creation, modified, ); } @override String toString() { return 'ReviewModel(name: $name, itemId: $itemId, rating: $rating, ' 'comment: ${comment.substring(0, comment.length > 30 ? 30 : comment.length)}..., ' 'owner: $owner, creation: $creation)'; } }