update database

This commit is contained in:
Phuoc Nguyen
2025-10-24 11:31:48 +07:00
parent f95fa9d0a6
commit c4272f9a21
126 changed files with 23528 additions and 2234 deletions

View File

@@ -0,0 +1,313 @@
/// Domain Entity: Quote
///
/// Represents a price quotation for products/services.
library;
/// Quote status enum
enum QuoteStatus {
/// Quote is in draft state
draft,
/// Quote has been sent to customer
sent,
/// Customer has accepted the quote
accepted,
/// Quote has been rejected
rejected,
/// Quote has expired
expired,
/// Quote has been converted to order
converted;
/// Get display name for status
String get displayName {
switch (this) {
case QuoteStatus.draft:
return 'Draft';
case QuoteStatus.sent:
return 'Sent';
case QuoteStatus.accepted:
return 'Accepted';
case QuoteStatus.rejected:
return 'Rejected';
case QuoteStatus.expired:
return 'Expired';
case QuoteStatus.converted:
return 'Converted';
}
}
}
/// Delivery Address information
class DeliveryAddress {
/// Recipient name
final String? name;
/// Phone number
final String? phone;
/// Street address
final String? street;
/// Ward/commune
final String? ward;
/// District
final String? district;
/// City/province
final String? city;
/// Postal code
final String? postalCode;
const DeliveryAddress({
this.name,
this.phone,
this.street,
this.ward,
this.district,
this.city,
this.postalCode,
});
/// Get full address string
String get fullAddress {
final parts = [
street,
ward,
district,
city,
postalCode,
].where((part) => part != null && part.isNotEmpty).toList();
return parts.join(', ');
}
/// Create from JSON map
factory DeliveryAddress.fromJson(Map<String, dynamic> json) {
return DeliveryAddress(
name: json['name'] as String?,
phone: json['phone'] as String?,
street: json['street'] as String?,
ward: json['ward'] as String?,
district: json['district'] as String?,
city: json['city'] as String?,
postalCode: json['postal_code'] as String?,
);
}
/// Convert to JSON map
Map<String, dynamic> toJson() {
return {
'name': name,
'phone': phone,
'street': street,
'ward': ward,
'district': district,
'city': city,
'postal_code': postalCode,
};
}
}
/// Quote Entity
///
/// Contains complete quotation information:
/// - Quote identification
/// - Customer details
/// - Pricing and terms
/// - Conversion tracking
class Quote {
/// Unique quote identifier
final String quoteId;
/// Quote number (human-readable)
final String quoteNumber;
/// User ID who requested the quote
final String userId;
/// Quote status
final QuoteStatus status;
/// Total amount before discount
final double totalAmount;
/// Discount amount
final double discountAmount;
/// Final amount after discount
final double finalAmount;
/// Project name (if applicable)
final String? projectName;
/// Delivery address
final DeliveryAddress? deliveryAddress;
/// Payment terms
final String? paymentTerms;
/// Quote notes
final String? notes;
/// Valid until date
final DateTime? validUntil;
/// Converted order ID (if converted)
final String? convertedOrderId;
/// ERPNext quotation reference
final String? erpnextQuotation;
/// Quote creation timestamp
final DateTime createdAt;
/// Last update timestamp
final DateTime updatedAt;
const Quote({
required this.quoteId,
required this.quoteNumber,
required this.userId,
required this.status,
required this.totalAmount,
required this.discountAmount,
required this.finalAmount,
this.projectName,
this.deliveryAddress,
this.paymentTerms,
this.notes,
this.validUntil,
this.convertedOrderId,
this.erpnextQuotation,
required this.createdAt,
required this.updatedAt,
});
/// Check if quote is draft
bool get isDraft => status == QuoteStatus.draft;
/// Check if quote is sent
bool get isSent => status == QuoteStatus.sent;
/// Check if quote is accepted
bool get isAccepted => status == QuoteStatus.accepted;
/// Check if quote is rejected
bool get isRejected => status == QuoteStatus.rejected;
/// Check if quote is expired
bool get isExpired =>
status == QuoteStatus.expired ||
(validUntil != null && DateTime.now().isAfter(validUntil!));
/// Check if quote is converted to order
bool get isConverted => status == QuoteStatus.converted;
/// Check if quote can be edited
bool get canBeEdited => isDraft;
/// Check if quote can be sent
bool get canBeSent => isDraft;
/// Check if quote can be converted to order
bool get canBeConverted => isAccepted && !isExpired;
/// Get discount percentage
double get discountPercentage {
if (totalAmount == 0) return 0;
return (discountAmount / totalAmount) * 100;
}
/// Get days until expiry
int? get daysUntilExpiry {
if (validUntil == null) return null;
final days = validUntil!.difference(DateTime.now()).inDays;
return days > 0 ? days : 0;
}
/// Check if expiring soon (within 7 days)
bool get isExpiringSoon {
if (validUntil == null || isExpired) return false;
final days = daysUntilExpiry;
return days != null && days > 0 && days <= 7;
}
/// Copy with method for immutability
Quote copyWith({
String? quoteId,
String? quoteNumber,
String? userId,
QuoteStatus? status,
double? totalAmount,
double? discountAmount,
double? finalAmount,
String? projectName,
DeliveryAddress? deliveryAddress,
String? paymentTerms,
String? notes,
DateTime? validUntil,
String? convertedOrderId,
String? erpnextQuotation,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Quote(
quoteId: quoteId ?? this.quoteId,
quoteNumber: quoteNumber ?? this.quoteNumber,
userId: userId ?? this.userId,
status: status ?? this.status,
totalAmount: totalAmount ?? this.totalAmount,
discountAmount: discountAmount ?? this.discountAmount,
finalAmount: finalAmount ?? this.finalAmount,
projectName: projectName ?? this.projectName,
deliveryAddress: deliveryAddress ?? this.deliveryAddress,
paymentTerms: paymentTerms ?? this.paymentTerms,
notes: notes ?? this.notes,
validUntil: validUntil ?? this.validUntil,
convertedOrderId: convertedOrderId ?? this.convertedOrderId,
erpnextQuotation: erpnextQuotation ?? this.erpnextQuotation,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Quote &&
other.quoteId == quoteId &&
other.quoteNumber == quoteNumber &&
other.userId == userId &&
other.status == status &&
other.totalAmount == totalAmount &&
other.discountAmount == discountAmount &&
other.finalAmount == finalAmount;
}
@override
int get hashCode {
return Object.hash(
quoteId,
quoteNumber,
userId,
status,
totalAmount,
discountAmount,
finalAmount,
);
}
@override
String toString() {
return 'Quote(quoteId: $quoteId, quoteNumber: $quoteNumber, status: $status, '
'finalAmount: $finalAmount, validUntil: $validUntil)';
}
}