update review api.

This commit is contained in:
Phuoc Nguyen
2025-11-17 17:54:32 +07:00
parent 0798b28db5
commit 0841e3bf3d
23 changed files with 4856 additions and 209 deletions

View File

@@ -0,0 +1,221 @@
/// 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<String, dynamic> json) {
// Handle nested user object if present
String? ownerEmail;
String? fullName;
if (json.containsKey('user') && json['user'] is Map<String, dynamic>) {
final user = json['user'] as Map<String, dynamic>;
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<String, dynamic> 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)';
}
}