āĻ¸ā§āĻ•āĻŋāĻĒ āĻ•āϰ⧇ āĻŽā§‚āϞ āĻ•āĻ¨ā§āĻŸā§‡āĻ¨ā§āϟ āĻ āϝāĻžāύ

đŸ—ī¸ Model Code Samples

FLX CLI āĻĻāĻŋāϝāĻŧ⧇ āϤ⧈āϰāĻŋ āĻšāĻ“āϝāĻŧāĻž āĻŦāĻŋāĻ­āĻŋāĻ¨ā§āύ āϧāϰāύ⧇āϰ Model class-āĻāϰ comprehensive examplesāĨ¤

đŸŽ¯ Basic Model Pattern​

Simple User Model​

import '../../domain/entities/user_entity.dart';

class UserModel extends UserEntity {
const UserModel({
required super.id,
required super.name,
required super.email,
super.profilePicture,
required super.createdAt,
});

/// Create UserModel from JSON
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
profilePicture: json['profile_picture'] as String?,
createdAt: DateTime.parse(json['created_at'] as String),
);
}

/// Convert to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'profile_picture': profilePicture,
'created_at': createdAt.toIso8601String(),
};
}

/// Create copy with updated fields
UserModel copyWith({
String? id,
String? name,
String? email,
String? profilePicture,
DateTime? createdAt,
}) {
return UserModel(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
profilePicture: profilePicture ?? this.profilePicture,
createdAt: createdAt ?? this.createdAt,
);
}

@override
String toString() {
return 'UserModel(id: $id, name: $name, email: $email, profilePicture: $profilePicture, createdAt: $createdAt)';
}
}

đŸ”ĸ Complex Model with Nested Objects​

Product Model with Variants​

import '../../domain/entities/product_entity.dart';

class ProductModel extends ProductEntity {
const ProductModel({
required super.id,
required super.name,
required super.description,
required super.price,
required super.category,
required super.variants,
required super.inventory,
required super.images,
required super.isActive,
required super.createdAt,
required super.updatedAt,
});

factory ProductModel.fromJson(Map<String, dynamic> json) {
return ProductModel(
id: json['id'] as String,
name: json['name'] as String,
description: json['description'] as String,
price: (json['price'] as num).toDouble(),
category: ProductCategoryModel.fromJson(json['category']),
variants: (json['variants'] as List)
.map((variant) => ProductVariantModel.fromJson(variant))
.toList(),
inventory: ProductInventoryModel.fromJson(json['inventory']),
images: (json['images'] as List)
.map((image) => ProductImageModel.fromJson(image))
.toList(),
isActive: json['is_active'] as bool,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'price': price,
'category': (category as ProductCategoryModel).toJson(),
'variants': variants
.map((variant) => (variant as ProductVariantModel).toJson())
.toList(),
'inventory': (inventory as ProductInventoryModel).toJson(),
'images': images
.map((image) => (image as ProductImageModel).toJson())
.toList(),
'is_active': isActive,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}

ProductModel copyWith({
String? id,
String? name,
String? description,
double? price,
ProductCategory? category,
List<ProductVariant>? variants,
ProductInventory? inventory,
List<ProductImage>? images,
bool? isActive,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return ProductModel(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
price: price ?? this.price,
category: category ?? this.category,
variants: variants ?? this.variants,
inventory: inventory ?? this.inventory,
images: images ?? this.images,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

class ProductCategoryModel extends ProductCategory {
const ProductCategoryModel({
required super.id,
required super.name,
required super.slug,
super.parentId,
required super.description,
});

factory ProductCategoryModel.fromJson(Map<String, dynamic> json) {
return ProductCategoryModel(
id: json['id'] as String,
name: json['name'] as String,
slug: json['slug'] as String,
parentId: json['parent_id'] as String?,
description: json['description'] as String,
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'slug': slug,
'parent_id': parentId,
'description': description,
};
}
}

class ProductVariantModel extends ProductVariant {
const ProductVariantModel({
required super.id,
required super.name,
required super.sku,
required super.price,
required super.attributes,
required super.stockQuantity,
});

factory ProductVariantModel.fromJson(Map<String, dynamic> json) {
return ProductVariantModel(
id: json['id'] as String,
name: json['name'] as String,
sku: json['sku'] as String,
price: (json['price'] as num).toDouble(),
attributes: Map<String, String>.from(json['attributes']),
stockQuantity: json['stock_quantity'] as int,
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'sku': sku,
'price': price,
'attributes': attributes,
'stock_quantity': stockQuantity,
};
}
}

📅 Model with Date/Time Handling​

Order Model with Timestamps​

import '../../domain/entities/order_entity.dart';

class OrderModel extends OrderEntity {
const OrderModel({
required super.id,
required super.userId,
required super.items,
required super.totalAmount,
required super.discountAmount,
required super.taxAmount,
required super.shippingAddress,
required super.billingAddress,
required super.status,
required super.paymentStatus,
required super.orderDate,
super.shippedDate,
super.deliveredDate,
super.cancelledDate,
required super.createdAt,
required super.updatedAt,
});

factory OrderModel.fromJson(Map<String, dynamic> json) {
return OrderModel(
id: json['id'] as String,
userId: json['user_id'] as String,
items: (json['items'] as List)
.map((item) => OrderItemModel.fromJson(item))
.toList(),
totalAmount: (json['total_amount'] as num).toDouble(),
discountAmount: (json['discount_amount'] as num).toDouble(),
taxAmount: (json['tax_amount'] as num).toDouble(),
shippingAddress: AddressModel.fromJson(json['shipping_address']),
billingAddress: AddressModel.fromJson(json['billing_address']),
status: OrderStatus.values.firstWhere(
(status) => status.name == json['status'],
),
paymentStatus: PaymentStatus.values.firstWhere(
(status) => status.name == json['payment_status'],
),
orderDate: DateTime.parse(json['order_date'] as String),
shippedDate: json['shipped_date'] != null
? DateTime.parse(json['shipped_date'] as String)
: null,
deliveredDate: json['delivered_date'] != null
? DateTime.parse(json['delivered_date'] as String)
: null,
cancelledDate: json['cancelled_date'] != null
? DateTime.parse(json['cancelled_date'] as String)
: null,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'user_id': userId,
'items': items
.map((item) => (item as OrderItemModel).toJson())
.toList(),
'total_amount': totalAmount,
'discount_amount': discountAmount,
'tax_amount': taxAmount,
'shipping_address': (shippingAddress as AddressModel).toJson(),
'billing_address': (billingAddress as AddressModel).toJson(),
'status': status.name,
'payment_status': paymentStatus.name,
'order_date': orderDate.toIso8601String(),
'shipped_date': shippedDate?.toIso8601String(),
'delivered_date': deliveredDate?.toIso8601String(),
'cancelled_date': cancelledDate?.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}

/// Calculate final amount after discounts and taxes
double get finalAmount {
return (totalAmount - discountAmount) + taxAmount;
}

/// Check if order can be cancelled
bool get canBeCancelled {
return status == OrderStatus.pending || status == OrderStatus.confirmed;
}

/// Get order status in Bangla
String get statusInBangla {
switch (status) {
case OrderStatus.pending:
return 'āĻ…āĻĒ⧇āĻ•ā§āώāĻŽāĻžāĻŖ';
case OrderStatus.confirmed:
return 'āύāĻŋāĻļā§āϚāĻŋāϤ';
case OrderStatus.processing:
return 'āĻĒā§āϰāϏ⧇āϏāĻŋāĻ‚';
case OrderStatus.shipped:
return 'āĻĒāĻžāĻ āĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇';
case OrderStatus.delivered:
return 'āĻĄā§‡āϞāĻŋāĻ­āĻžāϰ āĻšāϝāĻŧ⧇āϛ⧇';
case OrderStatus.cancelled:
return 'āĻŦāĻžāϤāĻŋāϞ';
case OrderStatus.returned:
return 'āĻĢ⧇āϰāϤ';
}
}
}

🔐 Model with Validation​

Registration Model with Validation​

import '../../domain/entities/registration_entity.dart';

class RegistrationModel extends RegistrationEntity {
const RegistrationModel({
required super.firstName,
required super.lastName,
required super.email,
required super.phone,
required super.password,
required super.dateOfBirth,
required super.gender,
required super.address,
required super.acceptedTerms,
required super.marketingOptIn,
});

factory RegistrationModel.fromJson(Map<String, dynamic> json) {
return RegistrationModel(
firstName: json['first_name'] as String,
lastName: json['last_name'] as String,
email: json['email'] as String,
phone: json['phone'] as String,
password: json['password'] as String,
dateOfBirth: DateTime.parse(json['date_of_birth'] as String),
gender: Gender.values.firstWhere(
(gender) => gender.name == json['gender'],
),
address: AddressModel.fromJson(json['address']),
acceptedTerms: json['accepted_terms'] as bool,
marketingOptIn: json['marketing_opt_in'] as bool,
);
}

Map<String, dynamic> toJson() {
return {
'first_name': firstName,
'last_name': lastName,
'email': email,
'phone': phone,
'password': password, // Note: Never send password in real API
'date_of_birth': dateOfBirth.toIso8601String(),
'gender': gender.name,
'address': (address as AddressModel).toJson(),
'accepted_terms': acceptedTerms,
'marketing_opt_in': marketingOptIn,
};
}

/// Convert to API payload (without sensitive data)
Map<String, dynamic> toApiPayload() {
final payload = toJson();
payload.remove('password'); // Remove password from API payload
return payload;
}

/// Validate registration data
List<String> validate() {
final errors = <String>[];

// Name validation
if (firstName.trim().isEmpty) {
errors.add('āĻĒā§āϰāĻĨāĻŽ āύāĻžāĻŽ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
}
if (lastName.trim().isEmpty) {
errors.add('āĻļ⧇āώ āύāĻžāĻŽ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
}

// Email validation
if (email.trim().isEmpty) {
errors.add('āχāĻŽā§‡āχāϞ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
} else if (!_isValidEmail(email)) {
errors.add('āϏāĻ āĻŋāĻ• āχāĻŽā§‡āχāϞ āĻĢāϰāĻŽā§āϝāĻžāϟ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
}

// Phone validation
if (phone.trim().isEmpty) {
errors.add('āĻĢā§‹āύ āύāĻŽā§āĻŦāϰ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
} else if (!_isValidPhone(phone)) {
errors.add('āϏāĻ āĻŋāĻ• āĻĢā§‹āύ āύāĻŽā§āĻŦāϰ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
}

// Password validation
if (password.isEmpty) {
errors.add('āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĒā§āϰāϝāĻŧā§‹āϜāύ');
} else if (password.length < 8) {
errors.add('āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ•āĻŽāĻĒāĻ•ā§āώ⧇ ā§Ž āĻ…āĻ•ā§āώāϰ⧇āϰ āĻšāϤ⧇ āĻšāĻŦ⧇');
} else if (!_hasUppercase(password)) {
errors.add('āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄā§‡ āĻ•āĻŽāĻĒāĻ•ā§āώ⧇ āĻāĻ•āϟāĻŋ āĻŦāĻĄāĻŧ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇');
} else if (!_hasLowercase(password)) {
errors.add('āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄā§‡ āĻ•āĻŽāĻĒāĻ•ā§āώ⧇ āĻāĻ•āϟāĻŋ āϛ⧋āϟ āĻšāĻžāϤ⧇āϰ āĻ…āĻ•ā§āώāϰ āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇');
} else if (!_hasNumber(password)) {
errors.add('āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄā§‡ āĻ•āĻŽāĻĒāĻ•ā§āώ⧇ āĻāĻ•āϟāĻŋ āϏāĻ‚āĻ–ā§āϝāĻž āĻĨāĻžāĻ•āϤ⧇ āĻšāĻŦ⧇');
}

// Age validation
final age = DateTime.now().difference(dateOfBirth).inDays ~/ 365;
if (age < 13) {
errors.add('āĻ¨ā§āϝ⧂āύāϤāĻŽ āĻŦāϝāĻŧāϏ ā§§ā§Š āĻŦāĻ›āϰ āĻšāϤ⧇ āĻšāĻŦ⧇');
} else if (age > 120) {
errors.add('āĻ…āĻŦ⧈āϧ āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ–');
}

// Terms validation
if (!acceptedTerms) {
errors.add('āĻļāĻ°ā§āϤāĻžāĻŦāϞ⧀ āĻ¸ā§āĻŦā§€āĻ•āĻžāϰ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇');
}

return errors;
}

/// Check if registration is valid
bool get isValid => validate().isEmpty;

// Private validation methods
bool _isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}

bool _isValidPhone(String phone) {
// Bangladeshi phone number validation
return RegExp(r'^(\+88)?01[3-9]\d{8}$').hasMatch(phone);
}

bool _hasUppercase(String password) {
return RegExp(r'[A-Z]').hasMatch(password);
}

bool _hasLowercase(String password) {
return RegExp(r'[a-z]').hasMatch(password);
}

bool _hasNumber(String password) {
return RegExp(r'\d').hasMatch(password);
}
}

📊 Model with Calculations​

Invoice Model with Calculations​

import '../../domain/entities/invoice_entity.dart';

class InvoiceModel extends InvoiceEntity {
const InvoiceModel({
required super.id,
required super.invoiceNumber,
required super.customerId,
required super.customerName,
required super.customerEmail,
required super.items,
required super.subtotal,
required super.taxRate,
required super.discountAmount,
required super.shippingCost,
required super.currency,
required super.status,
required super.issueDate,
required super.dueDate,
super.paidDate,
required super.notes,
});

factory InvoiceModel.fromJson(Map<String, dynamic> json) {
return InvoiceModel(
id: json['id'] as String,
invoiceNumber: json['invoice_number'] as String,
customerId: json['customer_id'] as String,
customerName: json['customer_name'] as String,
customerEmail: json['customer_email'] as String,
items: (json['items'] as List)
.map((item) => InvoiceItemModel.fromJson(item))
.toList(),
subtotal: (json['subtotal'] as num).toDouble(),
taxRate: (json['tax_rate'] as num).toDouble(),
discountAmount: (json['discount_amount'] as num).toDouble(),
shippingCost: (json['shipping_cost'] as num).toDouble(),
currency: json['currency'] as String,
status: InvoiceStatus.values.firstWhere(
(status) => status.name == json['status'],
),
issueDate: DateTime.parse(json['issue_date'] as String),
dueDate: DateTime.parse(json['due_date'] as String),
paidDate: json['paid_date'] != null
? DateTime.parse(json['paid_date'] as String)
: null,
notes: json['notes'] as String,
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'invoice_number': invoiceNumber,
'customer_id': customerId,
'customer_name': customerName,
'customer_email': customerEmail,
'items': items
.map((item) => (item as InvoiceItemModel).toJson())
.toList(),
'subtotal': subtotal,
'tax_rate': taxRate,
'discount_amount': discountAmount,
'shipping_cost': shippingCost,
'currency': currency,
'status': status.name,
'issue_date': issueDate.toIso8601String(),
'due_date': dueDate.toIso8601String(),
'paid_date': paidDate?.toIso8601String(),
'notes': notes,
};
}

/// Calculate tax amount
double get taxAmount {
return (subtotal - discountAmount) * (taxRate / 100);
}

/// Calculate total amount
double get totalAmount {
return subtotal - discountAmount + taxAmount + shippingCost;
}

/// Calculate discount percentage
double get discountPercentage {
if (subtotal == 0) return 0;
return (discountAmount / subtotal) * 100;
}

/// Check if invoice is overdue
bool get isOverdue {
if (status == InvoiceStatus.paid) return false;
return DateTime.now().isAfter(dueDate);
}

/// Get days until due
int get daysUntilDue {
final difference = dueDate.difference(DateTime.now());
return difference.inDays;
}

/// Get days overdue
int get daysOverdue {
if (!isOverdue) return 0;
final difference = DateTime.now().difference(dueDate);
return difference.inDays;
}

/// Format currency amount
String formatAmount(double amount) {
switch (currency.toUpperCase()) {
case 'BDT':
return 'ā§ŗ${amount.toStringAsFixed(2)}';
case 'USD':
return '\$${amount.toStringAsFixed(2)}';
case 'EUR':
return 'â‚Ŧ${amount.toStringAsFixed(2)}';
default:
return '${amount.toStringAsFixed(2)} $currency';
}
}

/// Get formatted total
String get formattedTotal => formatAmount(totalAmount);

/// Get status in Bangla
String get statusInBangla {
switch (status) {
case InvoiceStatus.draft:
return 'āĻ–āϏāĻĄāĻŧāĻž';
case InvoiceStatus.sent:
return 'āĻĒāĻžāĻ āĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇';
case InvoiceStatus.viewed:
return 'āĻĻ⧇āĻ–āĻž āĻšāϝāĻŧ⧇āϛ⧇';
case InvoiceStatus.paid:
return 'āĻĒ⧇āĻŽā§‡āĻ¨ā§āϟ āϏāĻŽā§āĻĒāĻ¨ā§āύ';
case InvoiceStatus.overdue:
return 'āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ';
case InvoiceStatus.cancelled:
return 'āĻŦāĻžāϤāĻŋāϞ';
}
}

/// Generate payment reminder message
String generatePaymentReminder() {
if (isOverdue) {
return 'āφāĻĒāύāĻžāϰ āχāύāĻ­āϝāĻŧ⧇āϏ #$invoiceNumber ${daysOverdue} āĻĻāĻŋāύ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώāĨ¤ '
'āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ ${formattedTotal} āĻĒ⧇āĻŽā§‡āĻ¨ā§āϟ āĻ•āϰ⧁āύāĨ¤';
} else {
return 'āφāĻĒāύāĻžāϰ āχāύāĻ­āϝāĻŧ⧇āϏ #$invoiceNumber ${daysUntilDue} āĻĻāĻŋāύ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āĻĒ⧇āĻŽā§‡āĻ¨ā§āϟ āĻ•āϰ⧁āύāĨ¤ '
'āĻŽā§‹āϟ āĻĒāϰāĻŋāĻŽāĻžāĻŖ: ${formattedTotal}';
}
}
}

class InvoiceItemModel extends InvoiceItem {
const InvoiceItemModel({
required super.id,
required super.productId,
required super.productName,
required super.description,
required super.quantity,
required super.unitPrice,
required super.taxRate,
});

factory InvoiceItemModel.fromJson(Map<String, dynamic> json) {
return InvoiceItemModel(
id: json['id'] as String,
productId: json['product_id'] as String,
productName: json['product_name'] as String,
description: json['description'] as String,
quantity: json['quantity'] as int,
unitPrice: (json['unit_price'] as num).toDouble(),
taxRate: (json['tax_rate'] as num).toDouble(),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'product_id': productId,
'product_name': productName,
'description': description,
'quantity': quantity,
'unit_price': unitPrice,
'tax_rate': taxRate,
};
}

/// Calculate line total
double get lineTotal {
return quantity * unitPrice;
}

/// Calculate tax amount for this line
double get taxAmount {
return lineTotal * (taxRate / 100);
}

/// Calculate total with tax
double get totalWithTax {
return lineTotal + taxAmount;
}
}

🔄 Model with State Management​

Task Model with State Transitions​

import '../../domain/entities/task_entity.dart';

class TaskModel extends TaskEntity {
const TaskModel({
required super.id,
required super.title,
required super.description,
required super.assigneeId,
required super.assigneeName,
required super.projectId,
required super.priority,
required super.status,
required super.tags,
required super.dueDate,
super.startDate,
super.completedDate,
required super.estimatedHours,
required super.actualHours,
required super.createdAt,
required super.updatedAt,
});

factory TaskModel.fromJson(Map<String, dynamic> json) {
return TaskModel(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String,
assigneeId: json['assignee_id'] as String,
assigneeName: json['assignee_name'] as String,
projectId: json['project_id'] as String,
priority: TaskPriority.values.firstWhere(
(priority) => priority.name == json['priority'],
),
status: TaskStatus.values.firstWhere(
(status) => status.name == json['status'],
),
tags: List<String>.from(json['tags']),
dueDate: DateTime.parse(json['due_date'] as String),
startDate: json['start_date'] != null
? DateTime.parse(json['start_date'] as String)
: null,
completedDate: json['completed_date'] != null
? DateTime.parse(json['completed_date'] as String)
: null,
estimatedHours: (json['estimated_hours'] as num).toDouble(),
actualHours: (json['actual_hours'] as num).toDouble(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
'assignee_id': assigneeId,
'assignee_name': assigneeName,
'project_id': projectId,
'priority': priority.name,
'status': status.name,
'tags': tags,
'due_date': dueDate.toIso8601String(),
'start_date': startDate?.toIso8601String(),
'completed_date': completedDate?.toIso8601String(),
'estimated_hours': estimatedHours,
'actual_hours': actualHours,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}

/// Get allowed status transitions
List<TaskStatus> get allowedTransitions {
switch (status) {
case TaskStatus.todo:
return [TaskStatus.inProgress, TaskStatus.cancelled];
case TaskStatus.inProgress:
return [TaskStatus.todo, TaskStatus.review, TaskStatus.blocked];
case TaskStatus.review:
return [TaskStatus.inProgress, TaskStatus.done, TaskStatus.todo];
case TaskStatus.blocked:
return [TaskStatus.todo, TaskStatus.inProgress];
case TaskStatus.done:
return [TaskStatus.review]; // Can reopen for review
case TaskStatus.cancelled:
return [TaskStatus.todo]; // Can reactivate
}
}

/// Check if transition is allowed
bool canTransitionTo(TaskStatus newStatus) {
return allowedTransitions.contains(newStatus);
}

/// Transition to new status
TaskModel transitionTo(TaskStatus newStatus) {
if (!canTransitionTo(newStatus)) {
throw Exception('Invalid status transition from $status to $newStatus');
}

return copyWith(
status: newStatus,
startDate: newStatus == TaskStatus.inProgress && startDate == null
? DateTime.now()
: startDate,
completedDate: newStatus == TaskStatus.done
? DateTime.now()
: null,
updatedAt: DateTime.now(),
);
}

/// Calculate completion percentage
double get completionPercentage {
switch (status) {
case TaskStatus.todo:
return 0.0;
case TaskStatus.inProgress:
return 50.0;
case TaskStatus.review:
return 80.0;
case TaskStatus.blocked:
return 25.0;
case TaskStatus.done:
return 100.0;
case TaskStatus.cancelled:
return 0.0;
}
}

/// Check if task is overdue
bool get isOverdue {
if (status == TaskStatus.done || status == TaskStatus.cancelled) {
return false;
}
return DateTime.now().isAfter(dueDate);
}

/// Get priority in Bangla
String get priorityInBangla {
switch (priority) {
case TaskPriority.low:
return 'āĻ•āĻŽ';
case TaskPriority.medium:
return 'āĻŽāĻžāĻāĻžāϰāĻŋ';
case TaskPriority.high:
return 'āωāĻšā§āϚ';
case TaskPriority.urgent:
return 'āϜāϰ⧁āϰāĻŋ';
}
}

/// Get status in Bangla
String get statusInBangla {
switch (status) {
case TaskStatus.todo:
return 'āĻ•āϰāϤ⧇ āĻšāĻŦ⧇';
case TaskStatus.inProgress:
return 'āϚāϞāĻŽāĻžāύ';
case TaskStatus.review:
return 'āĻĒāĻ°ā§āϝāĻžāϞ⧋āϚāύāĻž';
case TaskStatus.blocked:
return 'āĻŦāĻžāϧāĻžāĻ—ā§āϰāĻ¸ā§āϤ';
case TaskStatus.done:
return 'āϏāĻŽā§āĻĒāĻ¨ā§āύ';
case TaskStatus.cancelled:
return 'āĻŦāĻžāϤāĻŋāϞ';
}
}

TaskModel copyWith({
String? id,
String? title,
String? description,
String? assigneeId,
String? assigneeName,
String? projectId,
TaskPriority? priority,
TaskStatus? status,
List<String>? tags,
DateTime? dueDate,
DateTime? startDate,
DateTime? completedDate,
double? estimatedHours,
double? actualHours,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return TaskModel(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
assigneeId: assigneeId ?? this.assigneeId,
assigneeName: assigneeName ?? this.assigneeName,
projectId: projectId ?? this.projectId,
priority: priority ?? this.priority,
status: status ?? this.status,
tags: tags ?? this.tags,
dueDate: dueDate ?? this.dueDate,
startDate: startDate ?? this.startDate,
completedDate: completedDate ?? this.completedDate,
estimatedHours: estimatedHours ?? this.estimatedHours,
actualHours: actualHours ?? this.actualHours,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

đŸŽ¯ Key Model Patterns​

  1. Basic CRUD Models: Simple JSON serialization
  2. Nested Object Models: Complex relationships
  3. Date/Time Models: Proper timestamp handling
  4. Validation Models: Built-in validation logic
  5. Calculation Models: Business logic calculations
  6. State Machine Models: Status transitions
  7. Localization Models: Multi-language support
  8. Currency Models: Financial calculations
  9. File/Media Models: Asset handling
  10. Audit Models: Change tracking

✅ Best Practices​

  1. Always extend Entity: Model extends corresponding Entity
  2. Proper JSON handling: Handle null values gracefully
  3. Validation logic: Include business rule validation
  4. Immutable models: Use copyWith for updates
  5. Type safety: Proper type casting from JSON
  6. Error handling: Graceful error handling in factories
  7. Documentation: Clear method documentation
  8. Performance: Efficient serialization/deserialization

āĻāχ examples āϗ⧁āϞ⧋ follow āĻ•āϰ⧇: āφāĻĒāύāĻŋ āϝ⧇āϕ⧋āύ⧋ āϧāϰāύ⧇āϰ Model āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ! đŸ—ī¸