prodycrts
This commit is contained in:
71
lib/features/products/domain/entities/category.dart
Normal file
71
lib/features/products/domain/entities/category.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
/// Domain Entity: Category
|
||||
///
|
||||
/// Pure business entity representing a product category.
|
||||
library;
|
||||
|
||||
/// Category Entity
|
||||
///
|
||||
/// Represents a product category for filtering and organization.
|
||||
class Category {
|
||||
/// Unique identifier
|
||||
final String id;
|
||||
|
||||
/// Category name (Vietnamese)
|
||||
final String name;
|
||||
|
||||
/// Category description
|
||||
final String description;
|
||||
|
||||
/// Icon name or emoji
|
||||
final String icon;
|
||||
|
||||
/// Display order
|
||||
final int order;
|
||||
|
||||
const Category({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.order,
|
||||
});
|
||||
|
||||
/// Copy with method
|
||||
Category copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? description,
|
||||
String? icon,
|
||||
int? order,
|
||||
}) {
|
||||
return Category(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
icon: icon ?? this.icon,
|
||||
order: order ?? this.order,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Category(id: $id, name: $name, order: $order)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is Category &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
other.icon == icon &&
|
||||
other.order == order;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(id, name, description, icon, order);
|
||||
}
|
||||
}
|
||||
156
lib/features/products/domain/entities/product.dart
Normal file
156
lib/features/products/domain/entities/product.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
/// Domain Entity: Product
|
||||
///
|
||||
/// Pure business entity representing a product in the catalog.
|
||||
/// This entity is framework-independent and contains only business logic.
|
||||
library;
|
||||
|
||||
/// Product Entity
|
||||
///
|
||||
/// Represents a tile/construction product in the application.
|
||||
/// Used across all layers but originates in the domain layer.
|
||||
class Product {
|
||||
/// Unique identifier
|
||||
final String id;
|
||||
|
||||
/// Product name (Vietnamese)
|
||||
final String name;
|
||||
|
||||
/// Product SKU (Stock Keeping Unit)
|
||||
final String sku;
|
||||
|
||||
/// Product description
|
||||
final String description;
|
||||
|
||||
/// Price per unit (VND)
|
||||
final double price;
|
||||
|
||||
/// Unit of measurement (e.g., "m²", "viên", "hộp")
|
||||
final String unit;
|
||||
|
||||
/// Product image URL
|
||||
final String imageUrl;
|
||||
|
||||
/// Category ID
|
||||
final String categoryId;
|
||||
|
||||
/// Stock availability
|
||||
final bool inStock;
|
||||
|
||||
/// Stock quantity
|
||||
final int stockQuantity;
|
||||
|
||||
/// Created date
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Optional sale price
|
||||
final double? salePrice;
|
||||
|
||||
/// Optional brand name
|
||||
final String? brand;
|
||||
|
||||
const Product({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.sku,
|
||||
required this.description,
|
||||
required this.price,
|
||||
required this.unit,
|
||||
required this.imageUrl,
|
||||
required this.categoryId,
|
||||
required this.inStock,
|
||||
required this.stockQuantity,
|
||||
required this.createdAt,
|
||||
this.salePrice,
|
||||
this.brand,
|
||||
});
|
||||
|
||||
/// Get effective price (sale price if available, otherwise regular price)
|
||||
double get effectivePrice => salePrice ?? price;
|
||||
|
||||
/// Check if product is on sale
|
||||
bool get isOnSale => salePrice != null && salePrice! < price;
|
||||
|
||||
/// Get discount percentage
|
||||
int get discountPercentage {
|
||||
if (!isOnSale) return 0;
|
||||
return (((price - salePrice!) / price) * 100).round();
|
||||
}
|
||||
|
||||
/// Check if stock is low (less than 10 items)
|
||||
bool get isLowStock => inStock && stockQuantity < 10;
|
||||
|
||||
/// Copy with method for creating modified copies
|
||||
Product copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? sku,
|
||||
String? description,
|
||||
double? price,
|
||||
String? unit,
|
||||
String? imageUrl,
|
||||
String? categoryId,
|
||||
bool? inStock,
|
||||
int? stockQuantity,
|
||||
DateTime? createdAt,
|
||||
double? salePrice,
|
||||
String? brand,
|
||||
}) {
|
||||
return Product(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
sku: sku ?? this.sku,
|
||||
description: description ?? this.description,
|
||||
price: price ?? this.price,
|
||||
unit: unit ?? this.unit,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
categoryId: categoryId ?? this.categoryId,
|
||||
inStock: inStock ?? this.inStock,
|
||||
stockQuantity: stockQuantity ?? this.stockQuantity,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
salePrice: salePrice ?? this.salePrice,
|
||||
brand: brand ?? this.brand,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Product(id: $id, name: $name, sku: $sku, price: $price, unit: $unit, inStock: $inStock)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is Product &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
other.sku == sku &&
|
||||
other.description == description &&
|
||||
other.price == price &&
|
||||
other.unit == unit &&
|
||||
other.imageUrl == imageUrl &&
|
||||
other.categoryId == categoryId &&
|
||||
other.inStock == inStock &&
|
||||
other.stockQuantity == stockQuantity &&
|
||||
other.salePrice == salePrice &&
|
||||
other.brand == brand;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
id,
|
||||
name,
|
||||
sku,
|
||||
description,
|
||||
price,
|
||||
unit,
|
||||
imageUrl,
|
||||
categoryId,
|
||||
inStock,
|
||||
stockQuantity,
|
||||
salePrice,
|
||||
brand,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/// Domain Repository Interface: Products Repository
|
||||
///
|
||||
/// Defines the contract for product data operations.
|
||||
/// The data layer will implement this interface.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/products/domain/entities/category.dart';
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
|
||||
/// Products Repository Interface
|
||||
///
|
||||
/// Abstract repository defining product data operations.
|
||||
/// Implemented by the data layer to provide actual data access logic.
|
||||
abstract class ProductsRepository {
|
||||
/// Get all products
|
||||
///
|
||||
/// Returns a list of all available products.
|
||||
/// Throws an exception if the operation fails.
|
||||
Future<List<Product>> getAllProducts();
|
||||
|
||||
/// Search products by query
|
||||
///
|
||||
/// [query] - Search term to filter products
|
||||
/// Returns filtered list of products matching the query.
|
||||
Future<List<Product>> searchProducts(String query);
|
||||
|
||||
/// Get products by category
|
||||
///
|
||||
/// [categoryId] - Category ID to filter by
|
||||
/// Returns list of products in the specified category.
|
||||
Future<List<Product>> getProductsByCategory(String categoryId);
|
||||
|
||||
/// Get product by ID
|
||||
///
|
||||
/// [id] - Product ID
|
||||
/// Returns the product with the specified ID.
|
||||
/// Throws an exception if product not found.
|
||||
Future<Product> getProductById(String id);
|
||||
|
||||
/// Get all categories
|
||||
///
|
||||
/// Returns a list of all product categories.
|
||||
Future<List<Category>> getCategories();
|
||||
}
|
||||
28
lib/features/products/domain/usecases/get_categories.dart
Normal file
28
lib/features/products/domain/usecases/get_categories.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Use Case: Get Categories
|
||||
///
|
||||
/// Business logic for retrieving product categories.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/products/domain/entities/category.dart';
|
||||
import 'package:worker/features/products/domain/repositories/products_repository.dart';
|
||||
|
||||
/// Get Categories Use Case
|
||||
///
|
||||
/// Retrieves all product categories from the repository.
|
||||
class GetCategories {
|
||||
final ProductsRepository repository;
|
||||
|
||||
const GetCategories(this.repository);
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// Returns list of all categories sorted by order
|
||||
Future<List<Category>> call() async {
|
||||
final categories = await repository.getCategories();
|
||||
|
||||
// Sort by display order
|
||||
categories.sort((a, b) => a.order.compareTo(b.order));
|
||||
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
28
lib/features/products/domain/usecases/get_products.dart
Normal file
28
lib/features/products/domain/usecases/get_products.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Use Case: Get Products
|
||||
///
|
||||
/// Business logic for retrieving products with optional filtering.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
import 'package:worker/features/products/domain/repositories/products_repository.dart';
|
||||
|
||||
/// Get Products Use Case
|
||||
///
|
||||
/// Retrieves products from the repository with optional category filtering.
|
||||
class GetProducts {
|
||||
final ProductsRepository repository;
|
||||
|
||||
const GetProducts(this.repository);
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// [categoryId] - Optional category ID to filter products
|
||||
/// Returns list of products (all or filtered by category)
|
||||
Future<List<Product>> call({String? categoryId}) async {
|
||||
if (categoryId == null || categoryId == 'all') {
|
||||
return await repository.getAllProducts();
|
||||
} else {
|
||||
return await repository.getProductsByCategory(categoryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
lib/features/products/domain/usecases/search_products.dart
Normal file
29
lib/features/products/domain/usecases/search_products.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
/// Use Case: Search Products
|
||||
///
|
||||
/// Business logic for searching products by query string.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/products/domain/entities/product.dart';
|
||||
import 'package:worker/features/products/domain/repositories/products_repository.dart';
|
||||
|
||||
/// Search Products Use Case
|
||||
///
|
||||
/// Searches for products matching the given query string.
|
||||
class SearchProducts {
|
||||
final ProductsRepository repository;
|
||||
|
||||
const SearchProducts(this.repository);
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// [query] - Search query string
|
||||
/// Returns list of products matching the query
|
||||
Future<List<Product>> call(String query) async {
|
||||
// Return all products if query is empty
|
||||
if (query.trim().isEmpty) {
|
||||
return await repository.getAllProducts();
|
||||
}
|
||||
|
||||
return await repository.searchProducts(query);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user