update database
This commit is contained in:
45
lib/features/quotes/data/models/quote_item_model.dart
Normal file
45
lib/features/quotes/data/models/quote_item_model.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
|
||||
part 'quote_item_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.quoteItemModel)
|
||||
class QuoteItemModel extends HiveObject {
|
||||
QuoteItemModel({required this.quoteItemId, required this.quoteId, required this.productId, required this.quantity, required this.originalPrice, required this.negotiatedPrice, required this.discountPercent, required this.subtotal, this.notes});
|
||||
|
||||
@HiveField(0) final String quoteItemId;
|
||||
@HiveField(1) final String quoteId;
|
||||
@HiveField(2) final String productId;
|
||||
@HiveField(3) final double quantity;
|
||||
@HiveField(4) final double originalPrice;
|
||||
@HiveField(5) final double negotiatedPrice;
|
||||
@HiveField(6) final double discountPercent;
|
||||
@HiveField(7) final double subtotal;
|
||||
@HiveField(8) final String? notes;
|
||||
|
||||
factory QuoteItemModel.fromJson(Map<String, dynamic> json) => QuoteItemModel(
|
||||
quoteItemId: json['quote_item_id'] as String,
|
||||
quoteId: json['quote_id'] as String,
|
||||
productId: json['product_id'] as String,
|
||||
quantity: (json['quantity'] as num).toDouble(),
|
||||
originalPrice: (json['original_price'] as num).toDouble(),
|
||||
negotiatedPrice: (json['negotiated_price'] as num).toDouble(),
|
||||
discountPercent: (json['discount_percent'] as num).toDouble(),
|
||||
subtotal: (json['subtotal'] as num).toDouble(),
|
||||
notes: json['notes'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'quote_item_id': quoteItemId,
|
||||
'quote_id': quoteId,
|
||||
'product_id': productId,
|
||||
'quantity': quantity,
|
||||
'original_price': originalPrice,
|
||||
'negotiated_price': negotiatedPrice,
|
||||
'discount_percent': discountPercent,
|
||||
'subtotal': subtotal,
|
||||
'notes': notes,
|
||||
};
|
||||
|
||||
double get totalDiscount => originalPrice * quantity - subtotal;
|
||||
}
|
||||
65
lib/features/quotes/data/models/quote_item_model.g.dart
Normal file
65
lib/features/quotes/data/models/quote_item_model.g.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'quote_item_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class QuoteItemModelAdapter extends TypeAdapter<QuoteItemModel> {
|
||||
@override
|
||||
final typeId = 17;
|
||||
|
||||
@override
|
||||
QuoteItemModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return QuoteItemModel(
|
||||
quoteItemId: fields[0] as String,
|
||||
quoteId: fields[1] as String,
|
||||
productId: fields[2] as String,
|
||||
quantity: (fields[3] as num).toDouble(),
|
||||
originalPrice: (fields[4] as num).toDouble(),
|
||||
negotiatedPrice: (fields[5] as num).toDouble(),
|
||||
discountPercent: (fields[6] as num).toDouble(),
|
||||
subtotal: (fields[7] as num).toDouble(),
|
||||
notes: fields[8] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, QuoteItemModel obj) {
|
||||
writer
|
||||
..writeByte(9)
|
||||
..writeByte(0)
|
||||
..write(obj.quoteItemId)
|
||||
..writeByte(1)
|
||||
..write(obj.quoteId)
|
||||
..writeByte(2)
|
||||
..write(obj.productId)
|
||||
..writeByte(3)
|
||||
..write(obj.quantity)
|
||||
..writeByte(4)
|
||||
..write(obj.originalPrice)
|
||||
..writeByte(5)
|
||||
..write(obj.negotiatedPrice)
|
||||
..writeByte(6)
|
||||
..write(obj.discountPercent)
|
||||
..writeByte(7)
|
||||
..write(obj.subtotal)
|
||||
..writeByte(8)
|
||||
..write(obj.notes);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is QuoteItemModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
69
lib/features/quotes/data/models/quote_model.dart
Normal file
69
lib/features/quotes/data/models/quote_model.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'dart:convert';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
|
||||
part 'quote_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.quoteModel)
|
||||
class QuoteModel extends HiveObject {
|
||||
QuoteModel({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, this.updatedAt});
|
||||
|
||||
@HiveField(0) final String quoteId;
|
||||
@HiveField(1) final String quoteNumber;
|
||||
@HiveField(2) final String userId;
|
||||
@HiveField(3) final QuoteStatus status;
|
||||
@HiveField(4) final double totalAmount;
|
||||
@HiveField(5) final double discountAmount;
|
||||
@HiveField(6) final double finalAmount;
|
||||
@HiveField(7) final String? projectName;
|
||||
@HiveField(8) final String? deliveryAddress;
|
||||
@HiveField(9) final String? paymentTerms;
|
||||
@HiveField(10) final String? notes;
|
||||
@HiveField(11) final DateTime? validUntil;
|
||||
@HiveField(12) final String? convertedOrderId;
|
||||
@HiveField(13) final String? erpnextQuotation;
|
||||
@HiveField(14) final DateTime createdAt;
|
||||
@HiveField(15) final DateTime? updatedAt;
|
||||
|
||||
factory QuoteModel.fromJson(Map<String, dynamic> json) => QuoteModel(
|
||||
quoteId: json['quote_id'] as String,
|
||||
quoteNumber: json['quote_number'] as String,
|
||||
userId: json['user_id'] as String,
|
||||
status: QuoteStatus.values.firstWhere((e) => e.name == json['status']),
|
||||
totalAmount: (json['total_amount'] as num).toDouble(),
|
||||
discountAmount: (json['discount_amount'] as num).toDouble(),
|
||||
finalAmount: (json['final_amount'] as num).toDouble(),
|
||||
projectName: json['project_name'] as String?,
|
||||
deliveryAddress: json['delivery_address'] != null ? jsonEncode(json['delivery_address']) : null,
|
||||
paymentTerms: json['payment_terms'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
validUntil: json['valid_until'] != null ? DateTime.parse(json['valid_until']?.toString() ?? '') : null,
|
||||
convertedOrderId: json['converted_order_id'] as String?,
|
||||
erpnextQuotation: json['erpnext_quotation'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']?.toString() ?? '') : null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'quote_id': quoteId,
|
||||
'quote_number': quoteNumber,
|
||||
'user_id': userId,
|
||||
'status': status.name,
|
||||
'total_amount': totalAmount,
|
||||
'discount_amount': discountAmount,
|
||||
'final_amount': finalAmount,
|
||||
'project_name': projectName,
|
||||
'delivery_address': deliveryAddress != null ? jsonDecode(deliveryAddress!) : null,
|
||||
'payment_terms': paymentTerms,
|
||||
'notes': notes,
|
||||
'valid_until': validUntil?.toIso8601String(),
|
||||
'converted_order_id': convertedOrderId,
|
||||
'erpnext_quotation': erpnextQuotation,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
bool get isExpired => validUntil != null && DateTime.now().isAfter(validUntil!);
|
||||
bool get isConverted => convertedOrderId != null;
|
||||
}
|
||||
86
lib/features/quotes/data/models/quote_model.g.dart
Normal file
86
lib/features/quotes/data/models/quote_model.g.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'quote_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class QuoteModelAdapter extends TypeAdapter<QuoteModel> {
|
||||
@override
|
||||
final typeId = 16;
|
||||
|
||||
@override
|
||||
QuoteModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return QuoteModel(
|
||||
quoteId: fields[0] as String,
|
||||
quoteNumber: fields[1] as String,
|
||||
userId: fields[2] as String,
|
||||
status: fields[3] as QuoteStatus,
|
||||
totalAmount: (fields[4] as num).toDouble(),
|
||||
discountAmount: (fields[5] as num).toDouble(),
|
||||
finalAmount: (fields[6] as num).toDouble(),
|
||||
projectName: fields[7] as String?,
|
||||
deliveryAddress: fields[8] as String?,
|
||||
paymentTerms: fields[9] as String?,
|
||||
notes: fields[10] as String?,
|
||||
validUntil: fields[11] as DateTime?,
|
||||
convertedOrderId: fields[12] as String?,
|
||||
erpnextQuotation: fields[13] as String?,
|
||||
createdAt: fields[14] as DateTime,
|
||||
updatedAt: fields[15] as DateTime?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, QuoteModel obj) {
|
||||
writer
|
||||
..writeByte(16)
|
||||
..writeByte(0)
|
||||
..write(obj.quoteId)
|
||||
..writeByte(1)
|
||||
..write(obj.quoteNumber)
|
||||
..writeByte(2)
|
||||
..write(obj.userId)
|
||||
..writeByte(3)
|
||||
..write(obj.status)
|
||||
..writeByte(4)
|
||||
..write(obj.totalAmount)
|
||||
..writeByte(5)
|
||||
..write(obj.discountAmount)
|
||||
..writeByte(6)
|
||||
..write(obj.finalAmount)
|
||||
..writeByte(7)
|
||||
..write(obj.projectName)
|
||||
..writeByte(8)
|
||||
..write(obj.deliveryAddress)
|
||||
..writeByte(9)
|
||||
..write(obj.paymentTerms)
|
||||
..writeByte(10)
|
||||
..write(obj.notes)
|
||||
..writeByte(11)
|
||||
..write(obj.validUntil)
|
||||
..writeByte(12)
|
||||
..write(obj.convertedOrderId)
|
||||
..writeByte(13)
|
||||
..write(obj.erpnextQuotation)
|
||||
..writeByte(14)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(15)
|
||||
..write(obj.updatedAt);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is QuoteModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
313
lib/features/quotes/domain/entities/quote.dart
Normal file
313
lib/features/quotes/domain/entities/quote.dart
Normal 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)';
|
||||
}
|
||||
}
|
||||
133
lib/features/quotes/domain/entities/quote_item.dart
Normal file
133
lib/features/quotes/domain/entities/quote_item.dart
Normal file
@@ -0,0 +1,133 @@
|
||||
/// Domain Entity: Quote Item
|
||||
///
|
||||
/// Represents a single line item in a quotation.
|
||||
library;
|
||||
|
||||
/// Quote Item Entity
|
||||
///
|
||||
/// Contains item-level information in a quote:
|
||||
/// - Product reference
|
||||
/// - Quantity and pricing
|
||||
/// - Price negotiation
|
||||
/// - Discounts
|
||||
class QuoteItem {
|
||||
/// Unique quote item identifier
|
||||
final String quoteItemId;
|
||||
|
||||
/// Quote ID this item belongs to
|
||||
final String quoteId;
|
||||
|
||||
/// Product ID
|
||||
final String productId;
|
||||
|
||||
/// Quantity quoted
|
||||
final double quantity;
|
||||
|
||||
/// Original/list price per unit
|
||||
final double originalPrice;
|
||||
|
||||
/// Negotiated price per unit
|
||||
final double negotiatedPrice;
|
||||
|
||||
/// Discount percentage
|
||||
final double discountPercent;
|
||||
|
||||
/// Subtotal (quantity * negotiatedPrice)
|
||||
final double subtotal;
|
||||
|
||||
/// Item notes
|
||||
final String? notes;
|
||||
|
||||
const QuoteItem({
|
||||
required this.quoteItemId,
|
||||
required this.quoteId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.originalPrice,
|
||||
required this.negotiatedPrice,
|
||||
required this.discountPercent,
|
||||
required this.subtotal,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
/// Calculate subtotal at original price
|
||||
double get subtotalAtOriginalPrice => quantity * originalPrice;
|
||||
|
||||
/// Calculate discount amount per unit
|
||||
double get discountPerUnit => originalPrice - negotiatedPrice;
|
||||
|
||||
/// Calculate total discount amount
|
||||
double get totalDiscountAmount =>
|
||||
(quantity * originalPrice) - (quantity * negotiatedPrice);
|
||||
|
||||
/// Calculate effective discount percentage
|
||||
double get effectiveDiscountPercentage {
|
||||
if (originalPrice == 0) return 0;
|
||||
return ((originalPrice - negotiatedPrice) / originalPrice) * 100;
|
||||
}
|
||||
|
||||
/// Check if item has discount
|
||||
bool get hasDiscount => negotiatedPrice < originalPrice;
|
||||
|
||||
/// Check if price was negotiated
|
||||
bool get isNegotiated => negotiatedPrice != originalPrice;
|
||||
|
||||
/// Copy with method for immutability
|
||||
QuoteItem copyWith({
|
||||
String? quoteItemId,
|
||||
String? quoteId,
|
||||
String? productId,
|
||||
double? quantity,
|
||||
double? originalPrice,
|
||||
double? negotiatedPrice,
|
||||
double? discountPercent,
|
||||
double? subtotal,
|
||||
String? notes,
|
||||
}) {
|
||||
return QuoteItem(
|
||||
quoteItemId: quoteItemId ?? this.quoteItemId,
|
||||
quoteId: quoteId ?? this.quoteId,
|
||||
productId: productId ?? this.productId,
|
||||
quantity: quantity ?? this.quantity,
|
||||
originalPrice: originalPrice ?? this.originalPrice,
|
||||
negotiatedPrice: negotiatedPrice ?? this.negotiatedPrice,
|
||||
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 QuoteItem &&
|
||||
other.quoteItemId == quoteItemId &&
|
||||
other.quoteId == quoteId &&
|
||||
other.productId == productId &&
|
||||
other.quantity == quantity &&
|
||||
other.originalPrice == originalPrice &&
|
||||
other.negotiatedPrice == negotiatedPrice &&
|
||||
other.subtotal == subtotal;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
quoteItemId,
|
||||
quoteId,
|
||||
productId,
|
||||
quantity,
|
||||
originalPrice,
|
||||
negotiatedPrice,
|
||||
subtotal,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'QuoteItem(quoteItemId: $quoteItemId, productId: $productId, '
|
||||
'quantity: $quantity, originalPrice: $originalPrice, '
|
||||
'negotiatedPrice: $negotiatedPrice, subtotal: $subtotal)';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user