This commit is contained in:
Phuoc Nguyen
2025-11-27 14:59:48 +07:00
parent dc8e60f589
commit ba04576750
25 changed files with 931 additions and 721 deletions

View File

@@ -3,7 +3,7 @@
/// Represents a request for design consultation service.
library;
import 'project_submission.dart';
import 'package:worker/features/projects/domain/entities/project_type.dart';
/// Design status enum
enum DesignStatus {

View File

@@ -0,0 +1,33 @@
/// Project Status Entity
///
/// Represents a project status option from the API.
library;
import 'package:equatable/equatable.dart';
/// Project Status Entity
///
/// Similar to OrderStatus - represents status options for project submissions.
class ProjectStatus extends Equatable {
/// Status value (e.g., "Pending approval", "Approved", "Rejected", "Cancelled")
final String status;
/// Vietnamese label (e.g., "Chờ phê duyệt", "Đã được phê duyệt", "Từ chối", "HỦY BỎ")
final String label;
/// Color indicator (e.g., "Warning", "Success", "Danger")
final String color;
/// Display order index
final int index;
const ProjectStatus({
required this.status,
required this.label,
required this.color,
required this.index,
});
@override
List<Object?> get props => [status, label, color, index];
}

View File

@@ -1,242 +1,100 @@
/// Domain Entity: Project Submission
///
/// Represents a completed project submitted for loyalty points.
/// Based on API response from building_material.building_material.api.project.get_list
library;
/// Project type enum
enum ProjectType {
/// Residential project
residential,
/// Commercial project
commercial,
/// Industrial project
industrial,
/// Public infrastructure
infrastructure,
/// Other type
other;
/// Get display name for project type
String get displayName {
switch (this) {
case ProjectType.residential:
return 'Residential';
case ProjectType.commercial:
return 'Commercial';
case ProjectType.industrial:
return 'Industrial';
case ProjectType.infrastructure:
return 'Infrastructure';
case ProjectType.other:
return 'Other';
}
}
}
/// Submission status enum
enum SubmissionStatus {
/// Submitted, pending review
pending,
/// Under review
reviewing,
/// Approved, points awarded
approved,
/// Rejected
rejected;
/// Get display name for status
String get displayName {
switch (this) {
case SubmissionStatus.pending:
return 'Pending';
case SubmissionStatus.reviewing:
return 'Reviewing';
case SubmissionStatus.approved:
return 'Approved';
case SubmissionStatus.rejected:
return 'Rejected';
}
}
}
import 'package:equatable/equatable.dart';
/// Project Submission Entity
///
/// Contains information about a completed project:
/// - Project details
/// - Before/after photos
/// - Invoice documentation
/// - Review status
/// - Points earned
class ProjectSubmission {
/// Unique submission identifier
/// Contains information about a completed project submission.
/// Mapped from API response:
/// - name -> submissionId
/// - designed_area -> designedArea (project name/title)
/// - design_area -> designArea (area value in m²)
/// - request_date -> requestDate
/// - status -> status (Vietnamese label)
/// - reason_for_rejection -> reasonForRejection
/// - status_color -> statusColor
class ProjectSubmission extends Equatable {
/// Unique submission identifier (API: name)
final String submissionId;
/// User ID who submitted
final String userId;
/// Project name/title (API: designed_area)
final String designedArea;
/// Project name
final String projectName;
/// Design area value in square meters (API: design_area)
final double designArea;
/// Project address/location
final String? projectAddress;
/// Submission/request date (API: request_date)
final DateTime requestDate;
/// Project value/cost
final double projectValue;
/// Status label - Vietnamese (API: status)
/// e.g., "Chờ phê duyệt", "Đã được phê duyệt", "Từ chối", "HỦY BỎ"
final String status;
/// Project type
final ProjectType projectType;
/// Rejection reason if rejected (API: reason_for_rejection)
final String? reasonForRejection;
/// Before photos URLs
final List<String> beforePhotos;
/// After photos URLs
final List<String> afterPhotos;
/// Invoice/receipt URLs
final List<String> invoices;
/// Submission status
final SubmissionStatus status;
/// Review notes from admin
final String? reviewNotes;
/// Rejection reason (if rejected)
final String? rejectionReason;
/// Points earned (if approved)
final int? pointsEarned;
/// Submission timestamp
final DateTime submittedAt;
/// Review timestamp
final DateTime? reviewedAt;
/// ID of admin who reviewed
final String? reviewedBy;
/// Status color indicator (API: status_color)
/// Values: "Warning", "Success", "Danger"
final String statusColor;
const ProjectSubmission({
required this.submissionId,
required this.userId,
required this.projectName,
this.projectAddress,
required this.projectValue,
required this.projectType,
required this.beforePhotos,
required this.afterPhotos,
required this.invoices,
required this.designedArea,
required this.designArea,
required this.requestDate,
required this.status,
this.reviewNotes,
this.rejectionReason,
this.pointsEarned,
required this.submittedAt,
this.reviewedAt,
this.reviewedBy,
this.reasonForRejection,
required this.statusColor,
});
/// Check if submission is pending
bool get isPending => status == SubmissionStatus.pending;
/// Check if submission is under review
bool get isReviewing => status == SubmissionStatus.reviewing;
/// Check if submission is pending approval
bool get isPending => statusColor == 'Warning';
/// Check if submission is approved
bool get isApproved => status == SubmissionStatus.approved;
bool get isApproved => statusColor == 'Success';
/// Check if submission is rejected
bool get isRejected => status == SubmissionStatus.rejected;
/// Check if submission has been reviewed
bool get isReviewed =>
status == SubmissionStatus.approved ||
status == SubmissionStatus.rejected;
/// Check if submission has before photos
bool get hasBeforePhotos => beforePhotos.isNotEmpty;
/// Check if submission has after photos
bool get hasAfterPhotos => afterPhotos.isNotEmpty;
/// Check if submission has invoices
bool get hasInvoices => invoices.isNotEmpty;
/// Get total number of photos
int get totalPhotos => beforePhotos.length + afterPhotos.length;
/// Get review duration
Duration? get reviewDuration {
if (reviewedAt == null) return null;
return reviewedAt!.difference(submittedAt);
}
/// Check if submission is rejected or cancelled
bool get isRejected => statusColor == 'Danger';
/// Copy with method for immutability
ProjectSubmission copyWith({
String? submissionId,
String? userId,
String? projectName,
String? projectAddress,
double? projectValue,
ProjectType? projectType,
List<String>? beforePhotos,
List<String>? afterPhotos,
List<String>? invoices,
SubmissionStatus? status,
String? reviewNotes,
String? rejectionReason,
int? pointsEarned,
DateTime? submittedAt,
DateTime? reviewedAt,
String? reviewedBy,
String? designedArea,
double? designArea,
DateTime? requestDate,
String? status,
String? reasonForRejection,
String? statusColor,
}) {
return ProjectSubmission(
submissionId: submissionId ?? this.submissionId,
userId: userId ?? this.userId,
projectName: projectName ?? this.projectName,
projectAddress: projectAddress ?? this.projectAddress,
projectValue: projectValue ?? this.projectValue,
projectType: projectType ?? this.projectType,
beforePhotos: beforePhotos ?? this.beforePhotos,
afterPhotos: afterPhotos ?? this.afterPhotos,
invoices: invoices ?? this.invoices,
designedArea: designedArea ?? this.designedArea,
designArea: designArea ?? this.designArea,
requestDate: requestDate ?? this.requestDate,
status: status ?? this.status,
reviewNotes: reviewNotes ?? this.reviewNotes,
rejectionReason: rejectionReason ?? this.rejectionReason,
pointsEarned: pointsEarned ?? this.pointsEarned,
submittedAt: submittedAt ?? this.submittedAt,
reviewedAt: reviewedAt ?? this.reviewedAt,
reviewedBy: reviewedBy ?? this.reviewedBy,
reasonForRejection: reasonForRejection ?? this.reasonForRejection,
statusColor: statusColor ?? this.statusColor,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ProjectSubmission &&
other.submissionId == submissionId &&
other.userId == userId &&
other.projectName == projectName &&
other.projectValue == projectValue &&
other.status == status;
}
@override
int get hashCode {
return Object.hash(submissionId, userId, projectName, projectValue, status);
}
List<Object?> get props => [
submissionId,
designedArea,
designArea,
requestDate,
status,
reasonForRejection,
statusColor,
];
@override
String toString() {
return 'ProjectSubmission(submissionId: $submissionId, projectName: $projectName, '
'projectValue: $projectValue, projectType: $projectType, status: $status, '
'pointsEarned: $pointsEarned)';
return 'ProjectSubmission(submissionId: $submissionId, designedArea: $designedArea, '
'designArea: $designArea, status: $status, statusColor: $statusColor)';
}
}

View File

@@ -0,0 +1,38 @@
/// Project Type Enum
///
/// Represents the type of construction project.
library;
/// Project type enum
enum ProjectType {
/// Residential project
residential,
/// Commercial project
commercial,
/// Industrial project
industrial,
/// Public infrastructure
infrastructure,
/// Other type
other;
/// Get display name for project type
String get displayName {
switch (this) {
case ProjectType.residential:
return 'Residential';
case ProjectType.commercial:
return 'Commercial';
case ProjectType.industrial:
return 'Industrial';
case ProjectType.infrastructure:
return 'Infrastructure';
case ProjectType.other:
return 'Other';
}
}
}

View File

@@ -3,24 +3,26 @@
/// Repository interface for project submissions operations.
library;
import 'package:worker/features/projects/domain/entities/project_status.dart';
import 'package:worker/features/projects/domain/entities/project_submission.dart';
/// Submissions Repository
///
/// Defines contract for project submissions data operations.
abstract class SubmissionsRepository {
/// Get list of available project statuses
///
/// Uses cache-first pattern:
/// - Returns cached data if available
/// - Fetches from API and updates cache
/// - [forceRefresh] bypasses cache and fetches fresh data
Future<List<ProjectStatus>> getProjectStatusList({
bool forceRefresh = false,
});
/// Get all project submissions for the current user
Future<List<ProjectSubmission>> getSubmissions();
/// Get a single submission by ID
Future<ProjectSubmission> getSubmissionById(String submissionId);
/// Create a new project submission
Future<ProjectSubmission> createSubmission(ProjectSubmission submission);
/// Update an existing submission
Future<ProjectSubmission> updateSubmission(ProjectSubmission submission);
/// Delete a submission
Future<void> deleteSubmission(String submissionId);
Future<List<ProjectSubmission>> getSubmissions({
int limitStart = 0,
int limitPageLength = 0,
});
}

View File

@@ -1,23 +0,0 @@
/// Get Submissions Use Case
///
/// Retrieves all project submissions for the current user.
library;
import 'package:worker/features/projects/domain/entities/project_submission.dart';
import 'package:worker/features/projects/domain/repositories/submissions_repository.dart';
/// Get Submissions Use Case
///
/// Business logic for retrieving project submissions.
class GetSubmissions {
const GetSubmissions(this._repository);
final SubmissionsRepository _repository;
/// Execute the use case
///
/// Returns list of all project submissions for the current user.
Future<List<ProjectSubmission>> call() async {
return await _repository.getSubmissions();
}
}