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,273 @@
/// Domain Entity: Invoice
///
/// Represents an invoice for an order.
library;
/// Invoice type enum
enum InvoiceType {
/// Standard invoice
standard,
/// Proforma invoice
proforma,
/// Credit note
creditNote,
/// Debit note
debitNote;
}
/// Invoice status enum
enum InvoiceStatus {
/// Draft invoice
draft,
/// Invoice has been submitted
submitted,
/// Partially paid
partiallyPaid,
/// Fully paid
paid,
/// Overdue invoice
overdue,
/// Cancelled invoice
cancelled;
/// Get display name for status
String get displayName {
switch (this) {
case InvoiceStatus.draft:
return 'Draft';
case InvoiceStatus.submitted:
return 'Submitted';
case InvoiceStatus.partiallyPaid:
return 'Partially Paid';
case InvoiceStatus.paid:
return 'Paid';
case InvoiceStatus.overdue:
return 'Overdue';
case InvoiceStatus.cancelled:
return 'Cancelled';
}
}
}
/// Invoice Entity
///
/// Contains complete invoice information:
/// - Invoice identification
/// - Associated order
/// - Amounts and calculations
/// - Payment tracking
/// - Status and dates
class Invoice {
/// Unique invoice identifier
final String invoiceId;
/// Invoice number (human-readable)
final String invoiceNumber;
/// User ID
final String userId;
/// Order ID
final String? orderId;
/// Invoice type
final InvoiceType invoiceType;
/// Issue date
final DateTime issueDate;
/// Due date
final DateTime dueDate;
/// Currency code (e.g., VND, USD)
final String currency;
/// Subtotal amount
final double subtotalAmount;
/// Tax amount
final double taxAmount;
/// Discount amount
final double discountAmount;
/// Shipping amount
final double shippingAmount;
/// Total amount
final double totalAmount;
/// Amount paid so far
final double amountPaid;
/// Amount remaining to be paid
final double amountRemaining;
/// Invoice status
final InvoiceStatus status;
/// Payment terms
final String? paymentTerms;
/// Invoice notes
final String? notes;
/// ERPNext invoice reference
final String? erpnextInvoice;
/// Creation timestamp
final DateTime createdAt;
/// Last update timestamp
final DateTime updatedAt;
/// Last reminder sent timestamp
final DateTime? lastReminderSent;
const Invoice({
required this.invoiceId,
required this.invoiceNumber,
required this.userId,
this.orderId,
required this.invoiceType,
required this.issueDate,
required this.dueDate,
required this.currency,
required this.subtotalAmount,
required this.taxAmount,
required this.discountAmount,
required this.shippingAmount,
required this.totalAmount,
required this.amountPaid,
required this.amountRemaining,
required this.status,
this.paymentTerms,
this.notes,
this.erpnextInvoice,
required this.createdAt,
required this.updatedAt,
this.lastReminderSent,
});
/// Check if invoice is fully paid
bool get isPaid => status == InvoiceStatus.paid || amountRemaining <= 0;
/// Check if invoice is overdue
bool get isOverdue =>
status == InvoiceStatus.overdue ||
(!isPaid && DateTime.now().isAfter(dueDate));
/// Check if invoice is partially paid
bool get isPartiallyPaid =>
amountPaid > 0 && amountPaid < totalAmount;
/// Get payment percentage
double get paymentPercentage {
if (totalAmount == 0) return 0;
return (amountPaid / totalAmount) * 100;
}
/// Get days until due
int get daysUntilDue => dueDate.difference(DateTime.now()).inDays;
/// Get days overdue
int get daysOverdue {
if (!isOverdue) return 0;
return DateTime.now().difference(dueDate).inDays;
}
/// Copy with method for immutability
Invoice copyWith({
String? invoiceId,
String? invoiceNumber,
String? userId,
String? orderId,
InvoiceType? invoiceType,
DateTime? issueDate,
DateTime? dueDate,
String? currency,
double? subtotalAmount,
double? taxAmount,
double? discountAmount,
double? shippingAmount,
double? totalAmount,
double? amountPaid,
double? amountRemaining,
InvoiceStatus? status,
String? paymentTerms,
String? notes,
String? erpnextInvoice,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? lastReminderSent,
}) {
return Invoice(
invoiceId: invoiceId ?? this.invoiceId,
invoiceNumber: invoiceNumber ?? this.invoiceNumber,
userId: userId ?? this.userId,
orderId: orderId ?? this.orderId,
invoiceType: invoiceType ?? this.invoiceType,
issueDate: issueDate ?? this.issueDate,
dueDate: dueDate ?? this.dueDate,
currency: currency ?? this.currency,
subtotalAmount: subtotalAmount ?? this.subtotalAmount,
taxAmount: taxAmount ?? this.taxAmount,
discountAmount: discountAmount ?? this.discountAmount,
shippingAmount: shippingAmount ?? this.shippingAmount,
totalAmount: totalAmount ?? this.totalAmount,
amountPaid: amountPaid ?? this.amountPaid,
amountRemaining: amountRemaining ?? this.amountRemaining,
status: status ?? this.status,
paymentTerms: paymentTerms ?? this.paymentTerms,
notes: notes ?? this.notes,
erpnextInvoice: erpnextInvoice ?? this.erpnextInvoice,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
lastReminderSent: lastReminderSent ?? this.lastReminderSent,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Invoice &&
other.invoiceId == invoiceId &&
other.invoiceNumber == invoiceNumber &&
other.userId == userId &&
other.orderId == orderId &&
other.invoiceType == invoiceType &&
other.totalAmount == totalAmount &&
other.amountPaid == amountPaid &&
other.status == status;
}
@override
int get hashCode {
return Object.hash(
invoiceId,
invoiceNumber,
userId,
orderId,
invoiceType,
totalAmount,
amountPaid,
status,
);
}
@override
String toString() {
return 'Invoice(invoiceId: $invoiceId, invoiceNumber: $invoiceNumber, '
'status: $status, totalAmount: $totalAmount, amountPaid: $amountPaid, '
'amountRemaining: $amountRemaining)';
}
}

View File

@@ -0,0 +1,321 @@
/// Domain Entity: Order
///
/// Represents a customer order.
library;
/// Order status enum
enum OrderStatus {
/// Order has been created but not confirmed
draft,
/// Order has been confirmed
confirmed,
/// Order is being processed
processing,
/// Order is ready for shipping
ready,
/// Order has been shipped
shipped,
/// Order has been delivered
delivered,
/// Order has been completed
completed,
/// Order has been cancelled
cancelled,
/// Order has been returned
returned;
/// Get display name for status
String get displayName {
switch (this) {
case OrderStatus.draft:
return 'Draft';
case OrderStatus.confirmed:
return 'Confirmed';
case OrderStatus.processing:
return 'Processing';
case OrderStatus.ready:
return 'Ready';
case OrderStatus.shipped:
return 'Shipped';
case OrderStatus.delivered:
return 'Delivered';
case OrderStatus.completed:
return 'Completed';
case OrderStatus.cancelled:
return 'Cancelled';
case OrderStatus.returned:
return 'Returned';
}
}
}
/// Address information
class Address {
/// 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 Address({
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 Address.fromJson(Map<String, dynamic> json) {
return Address(
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,
};
}
}
/// Order Entity
///
/// Contains complete order information:
/// - Order identification
/// - Customer details
/// - Pricing and discounts
/// - Shipping information
/// - Status tracking
class Order {
/// Unique order identifier
final String orderId;
/// Human-readable order number
final String orderNumber;
/// User ID who placed the order
final String userId;
/// Current order status
final OrderStatus status;
/// Total order amount before discounts
final double totalAmount;
/// Discount amount applied
final double discountAmount;
/// Tax amount
final double taxAmount;
/// Shipping fee
final double shippingFee;
/// Final amount to pay
final double finalAmount;
/// Shipping address
final Address? shippingAddress;
/// Billing address
final Address? billingAddress;
/// Expected delivery date
final DateTime? expectedDeliveryDate;
/// Actual delivery date
final DateTime? actualDeliveryDate;
/// Order notes
final String? notes;
/// Cancellation reason
final String? cancellationReason;
/// ERPNext sales order reference
final String? erpnextSalesOrder;
/// Order creation timestamp
final DateTime createdAt;
/// Last update timestamp
final DateTime updatedAt;
const Order({
required this.orderId,
required this.orderNumber,
required this.userId,
required this.status,
required this.totalAmount,
required this.discountAmount,
required this.taxAmount,
required this.shippingFee,
required this.finalAmount,
this.shippingAddress,
this.billingAddress,
this.expectedDeliveryDate,
this.actualDeliveryDate,
this.notes,
this.cancellationReason,
this.erpnextSalesOrder,
required this.createdAt,
required this.updatedAt,
});
/// Check if order is active (not cancelled or completed)
bool get isActive =>
status != OrderStatus.cancelled &&
status != OrderStatus.completed &&
status != OrderStatus.returned;
/// Check if order can be cancelled
bool get canBeCancelled =>
status == OrderStatus.draft ||
status == OrderStatus.confirmed ||
status == OrderStatus.processing;
/// Check if order is delivered
bool get isDelivered =>
status == OrderStatus.delivered || status == OrderStatus.completed;
/// Check if order is cancelled
bool get isCancelled => status == OrderStatus.cancelled;
/// Get discount percentage
double get discountPercentage {
if (totalAmount == 0) return 0;
return (discountAmount / totalAmount) * 100;
}
/// Copy with method for immutability
Order copyWith({
String? orderId,
String? orderNumber,
String? userId,
OrderStatus? status,
double? totalAmount,
double? discountAmount,
double? taxAmount,
double? shippingFee,
double? finalAmount,
Address? shippingAddress,
Address? billingAddress,
DateTime? expectedDeliveryDate,
DateTime? actualDeliveryDate,
String? notes,
String? cancellationReason,
String? erpnextSalesOrder,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Order(
orderId: orderId ?? this.orderId,
orderNumber: orderNumber ?? this.orderNumber,
userId: userId ?? this.userId,
status: status ?? this.status,
totalAmount: totalAmount ?? this.totalAmount,
discountAmount: discountAmount ?? this.discountAmount,
taxAmount: taxAmount ?? this.taxAmount,
shippingFee: shippingFee ?? this.shippingFee,
finalAmount: finalAmount ?? this.finalAmount,
shippingAddress: shippingAddress ?? this.shippingAddress,
billingAddress: billingAddress ?? this.billingAddress,
expectedDeliveryDate: expectedDeliveryDate ?? this.expectedDeliveryDate,
actualDeliveryDate: actualDeliveryDate ?? this.actualDeliveryDate,
notes: notes ?? this.notes,
cancellationReason: cancellationReason ?? this.cancellationReason,
erpnextSalesOrder: erpnextSalesOrder ?? this.erpnextSalesOrder,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Order &&
other.orderId == orderId &&
other.orderNumber == orderNumber &&
other.userId == userId &&
other.status == status &&
other.totalAmount == totalAmount &&
other.discountAmount == discountAmount &&
other.taxAmount == taxAmount &&
other.shippingFee == shippingFee &&
other.finalAmount == finalAmount;
}
@override
int get hashCode {
return Object.hash(
orderId,
orderNumber,
userId,
status,
totalAmount,
discountAmount,
taxAmount,
shippingFee,
finalAmount,
);
}
@override
String toString() {
return 'Order(orderId: $orderId, orderNumber: $orderNumber, status: $status, '
'finalAmount: $finalAmount, createdAt: $createdAt)';
}
}

View File

@@ -0,0 +1,117 @@
/// Domain Entity: Order Item
///
/// Represents a single line item in an order.
library;
/// Order Item Entity
///
/// Contains item-level information in an order:
/// - Product reference
/// - Quantity and pricing
/// - Discounts
/// - Notes
class OrderItem {
/// Unique order item identifier
final String orderItemId;
/// Order ID this item belongs to
final String orderId;
/// Product ID
final String productId;
/// Quantity ordered
final double quantity;
/// Unit price at time of order
final double unitPrice;
/// Discount percentage applied
final double discountPercent;
/// Subtotal (quantity * unitPrice * (1 - discountPercent/100))
final double subtotal;
/// Item notes
final String? notes;
const OrderItem({
required this.orderItemId,
required this.orderId,
required this.productId,
required this.quantity,
required this.unitPrice,
required this.discountPercent,
required this.subtotal,
this.notes,
});
/// Calculate subtotal before discount
double get subtotalBeforeDiscount => quantity * unitPrice;
/// Calculate discount amount
double get discountAmount =>
subtotalBeforeDiscount * (discountPercent / 100);
/// Calculate subtotal after discount (for verification)
double get calculatedSubtotal => subtotalBeforeDiscount - discountAmount;
/// Check if item has discount
bool get hasDiscount => discountPercent > 0;
/// Copy with method for immutability
OrderItem copyWith({
String? orderItemId,
String? orderId,
String? productId,
double? quantity,
double? unitPrice,
double? discountPercent,
double? subtotal,
String? notes,
}) {
return OrderItem(
orderItemId: orderItemId ?? this.orderItemId,
orderId: orderId ?? this.orderId,
productId: productId ?? this.productId,
quantity: quantity ?? this.quantity,
unitPrice: unitPrice ?? this.unitPrice,
discountPercent: discountPercent ?? this.discountPercent,
subtotal: subtotal ?? this.subtotal,
notes: notes ?? this.notes,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is OrderItem &&
other.orderItemId == orderItemId &&
other.orderId == orderId &&
other.productId == productId &&
other.quantity == quantity &&
other.unitPrice == unitPrice &&
other.discountPercent == discountPercent &&
other.subtotal == subtotal;
}
@override
int get hashCode {
return Object.hash(
orderItemId,
orderId,
productId,
quantity,
unitPrice,
discountPercent,
subtotal,
);
}
@override
String toString() {
return 'OrderItem(orderItemId: $orderItemId, productId: $productId, '
'quantity: $quantity, unitPrice: $unitPrice, subtotal: $subtotal)';
}
}

View File

@@ -0,0 +1,237 @@
/// Domain Entity: Payment Line
///
/// Represents a payment transaction for an invoice.
library;
/// Payment method enum
enum PaymentMethod {
/// Cash payment
cash,
/// Bank transfer
bankTransfer,
/// Credit card
creditCard,
/// E-wallet (Momo, ZaloPay, etc.)
ewallet,
/// Check
check,
/// Other method
other;
/// Get display name for payment method
String get displayName {
switch (this) {
case PaymentMethod.cash:
return 'Cash';
case PaymentMethod.bankTransfer:
return 'Bank Transfer';
case PaymentMethod.creditCard:
return 'Credit Card';
case PaymentMethod.ewallet:
return 'E-Wallet';
case PaymentMethod.check:
return 'Check';
case PaymentMethod.other:
return 'Other';
}
}
}
/// Payment status enum
enum PaymentStatus {
/// Payment pending
pending,
/// Payment is being processed
processing,
/// Payment completed successfully
completed,
/// Payment failed
failed,
/// Payment refunded
refunded,
/// Payment cancelled
cancelled;
/// Get display name for status
String get displayName {
switch (this) {
case PaymentStatus.pending:
return 'Pending';
case PaymentStatus.processing:
return 'Processing';
case PaymentStatus.completed:
return 'Completed';
case PaymentStatus.failed:
return 'Failed';
case PaymentStatus.refunded:
return 'Refunded';
case PaymentStatus.cancelled:
return 'Cancelled';
}
}
}
/// Payment Line Entity
///
/// Contains payment transaction information:
/// - Payment details
/// - Payment method
/// - Bank information
/// - Status tracking
class PaymentLine {
/// Unique payment line identifier
final String paymentLineId;
/// Invoice ID this payment is for
final String invoiceId;
/// Payment number (human-readable)
final String paymentNumber;
/// Payment date
final DateTime paymentDate;
/// Payment amount
final double amount;
/// Payment method
final PaymentMethod paymentMethod;
/// Bank name (for bank transfer)
final String? bankName;
/// Bank account number (for bank transfer)
final String? bankAccount;
/// Reference number (transaction ID, check number, etc.)
final String? referenceNumber;
/// Payment notes
final String? notes;
/// Payment status
final PaymentStatus status;
/// Receipt URL
final String? receiptUrl;
/// ERPNext payment entry reference
final String? erpnextPaymentEntry;
/// Creation timestamp
final DateTime createdAt;
/// Processing timestamp
final DateTime? processedAt;
const PaymentLine({
required this.paymentLineId,
required this.invoiceId,
required this.paymentNumber,
required this.paymentDate,
required this.amount,
required this.paymentMethod,
this.bankName,
this.bankAccount,
this.referenceNumber,
this.notes,
required this.status,
this.receiptUrl,
this.erpnextPaymentEntry,
required this.createdAt,
this.processedAt,
});
/// Check if payment is completed
bool get isCompleted => status == PaymentStatus.completed;
/// Check if payment is pending
bool get isPending => status == PaymentStatus.pending;
/// Check if payment is being processed
bool get isProcessing => status == PaymentStatus.processing;
/// Check if payment failed
bool get isFailed => status == PaymentStatus.failed;
/// Check if payment has receipt
bool get hasReceipt => receiptUrl != null && receiptUrl!.isNotEmpty;
/// Copy with method for immutability
PaymentLine copyWith({
String? paymentLineId,
String? invoiceId,
String? paymentNumber,
DateTime? paymentDate,
double? amount,
PaymentMethod? paymentMethod,
String? bankName,
String? bankAccount,
String? referenceNumber,
String? notes,
PaymentStatus? status,
String? receiptUrl,
String? erpnextPaymentEntry,
DateTime? createdAt,
DateTime? processedAt,
}) {
return PaymentLine(
paymentLineId: paymentLineId ?? this.paymentLineId,
invoiceId: invoiceId ?? this.invoiceId,
paymentNumber: paymentNumber ?? this.paymentNumber,
paymentDate: paymentDate ?? this.paymentDate,
amount: amount ?? this.amount,
paymentMethod: paymentMethod ?? this.paymentMethod,
bankName: bankName ?? this.bankName,
bankAccount: bankAccount ?? this.bankAccount,
referenceNumber: referenceNumber ?? this.referenceNumber,
notes: notes ?? this.notes,
status: status ?? this.status,
receiptUrl: receiptUrl ?? this.receiptUrl,
erpnextPaymentEntry: erpnextPaymentEntry ?? this.erpnextPaymentEntry,
createdAt: createdAt ?? this.createdAt,
processedAt: processedAt ?? this.processedAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PaymentLine &&
other.paymentLineId == paymentLineId &&
other.invoiceId == invoiceId &&
other.paymentNumber == paymentNumber &&
other.amount == amount &&
other.paymentMethod == paymentMethod &&
other.status == status;
}
@override
int get hashCode {
return Object.hash(
paymentLineId,
invoiceId,
paymentNumber,
amount,
paymentMethod,
status,
);
}
@override
String toString() {
return 'PaymentLine(paymentLineId: $paymentLineId, paymentNumber: $paymentNumber, '
'amount: $amount, paymentMethod: $paymentMethod, status: $status)';
}
}