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

580
CLAUDE.md
View File

@@ -1314,321 +1314,132 @@ class Auth extends _$Auth {
}
```
#### Cart State
```dart
@riverpod
class Cart extends _$Cart {
@override
List<CartItem> build() {
return ref.watch(cartRepositoryProvider).getCartItems();
}
void addItem(Product product, int quantity) {
state = [
...state,
CartItem(
id: product.id,
productName: product.name,
price: product.price,
quantity: quantity,
imageUrl: product.images.first,
),
];
_persistCart();
}
void updateQuantity(String itemId, int quantity) {
state = state.map((item) {
if (item.id == itemId) {
return item.copyWith(quantity: quantity);
}
return item;
}).toList();
_persistCart();
}
void removeItem(String itemId) {
state = state.where((item) => item.id != itemId).toList();
_persistCart();
}
void clear() {
state = [];
_persistCart();
}
void _persistCart() {
ref.read(cartRepositoryProvider).saveCart(state);
}
}
@riverpod
double cartTotal(CartTotalRef ref) {
final items = ref.watch(cartProvider);
return items.fold(0, (sum, item) => sum + (item.price * item.quantity));
}
```
#### Loyalty Points State
```dart
@riverpod
class LoyaltyPoints extends _$LoyaltyPoints {
@override
Future<LoyaltyPointsData> build() async {
return await ref.read(loyaltyRepositoryProvider).getLoyaltyPoints();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await ref.read(loyaltyRepositoryProvider).getLoyaltyPoints();
});
}
Future<void> redeemReward(Reward reward) async {
final result = await ref.read(loyaltyRepositoryProvider).redeemReward(reward.id);
if (result.success) {
await refresh();
// Show success dialog with gift code
}
}
}
```
---
### Database Schema (Hive)
#### User Model
```dart
@HiveType(typeId: 0)
class UserModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String name;
@HiveField(2)
final String phone;
@HiveField(3)
final String email;
@HiveField(4)
final String? avatar;
@HiveField(5)
final MemberTier memberTier; // diamond, platinum, gold
@HiveField(6)
final int points;
@HiveField(7)
final String referralCode;
@HiveField(8)
final String? company;
@HiveField(9)
final UserType userType; // contractor, architect, distributor, broker
@HiveField(10)
final DateTime createdAt;
}
```
#### Product Model
```dart
@HiveType(typeId: 1)
class ProductModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String name;
@HiveField(2)
final String sku;
@HiveField(3)
final String description;
@HiveField(4)
final double price;
@HiveField(5)
final double? salePrice;
@HiveField(6)
final List<String> images;
@HiveField(7)
final String categoryId;
@HiveField(8)
final int stock;
@HiveField(9)
final bool isAvailable;
@HiveField(10)
final DateTime createdAt;
}
```
#### Cart Item Model
```dart
@HiveType(typeId: 2)
class CartItemModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String productId;
@HiveField(2)
final String productName;
@HiveField(3)
final double price;
@HiveField(4)
final int quantity;
@HiveField(5)
final String imageUrl;
@HiveField(6)
final DateTime addedAt;
}
```
#### Order Model
```dart
@HiveType(typeId: 3)
class OrderModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String orderNumber;
@HiveField(2)
final String customerId;
@HiveField(3)
final String customerName;
@HiveField(4)
final List<OrderItemModel> items;
@HiveField(5)
final double subtotal;
@HiveField(6)
final double discount;
@HiveField(7)
final double shipping;
@HiveField(8)
final double total;
@HiveField(9)
final OrderStatus status;
@HiveField(10)
final String paymentMethod;
@HiveField(11)
final AddressModel deliveryAddress;
@HiveField(12)
final DateTime createdAt;
@HiveField(13)
final DateTime? completedAt;
}
```
#### Project Model
```dart
@HiveType(typeId: 4)
class ProjectModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final String name;
@HiveField(2)
final String code;
@HiveField(3)
final String client;
@HiveField(4)
final String clientPhone;
@HiveField(5)
final String location;
@HiveField(6)
final DateTime startDate;
@HiveField(7)
final DateTime endDate;
@HiveField(8)
final int progress; // 0-100
@HiveField(9)
final ProjectStatus status; // planning, inProgress, completed
@HiveField(10)
final double budget;
@HiveField(11)
final String description;
@HiveField(12)
final ProjectType type; // residential, commercial, industrial
@HiveField(13)
final DateTime createdAt;
}
```
#### Loyalty Transaction Model
```dart
@HiveType(typeId: 5)
class LoyaltyTransactionModel extends HiveObject {
@HiveField(0)
final String id;
@HiveField(1)
final TransactionType type; // earn, redeem
@HiveField(2)
final int amount;
@HiveField(3)
final String description;
@HiveField(4)
final DateTime timestamp;
@HiveField(5)
final int newBalance;
@HiveField(6)
final String? orderId;
@HiveField(7)
final String? rewardId;
}
```
### Domain Entities & Data Models
The app follows clean architecture with domain entities and Hive data models based on the database schema in `database.md`.
#### **Domain Entities (28 files)**
Pure business logic entities with no external dependencies. All entities use freezed for immutability.
**Auth Feature** (`lib/features/auth/domain/entities/`)
- `user.dart` - User with role, status, loyalty tier, company info
- `user_session.dart` - User session with device tracking
**Products Feature** (`lib/features/products/domain/entities/`)
- `product.dart` - Product with images, specs, 360 view, ERPNext integration
- `stock_level.dart` - Inventory tracking (available/reserved/ordered)
- `category.dart` - Product categories
**Cart Feature** (`lib/features/cart/domain/entities/`)
- `cart.dart` - Shopping cart with sync status
- `cart_item.dart` - Cart line items
**Orders Feature** (`lib/features/orders/domain/entities/`)
- `order.dart` - Orders with status, addresses, ERPNext integration
- `order_item.dart` - Order line items with discounts
- `invoice.dart` - Invoices with type, status, payment tracking
- `payment_line.dart` - Payment transactions with method and status
**Loyalty Feature** (`lib/features/loyalty/domain/entities/`)
- `loyalty_point_entry.dart` - Points transactions with type, source, complaints
- `gift_catalog.dart` - Redeemable gifts with category, points cost
- `redeemed_gift.dart` - User's redeemed gifts with voucher codes
- `points_record.dart` - Invoice submissions for manual points
**Projects Feature** (`lib/features/projects/domain/entities/`)
- `project_submission.dart` - Completed project submissions with photos
- `design_request.dart` - Design consultation requests
**Quotes Feature** (`lib/features/quotes/domain/entities/`)
- `quote.dart` - Quotations with status, validity, conversion tracking
- `quote_item.dart` - Quote line items with price negotiation
**Chat Feature** (`lib/features/chat/domain/entities/`)
- `chat_room.dart` - Chat rooms with type, participants
- `message.dart` - Messages with content type, attachments, read status
**Notifications Feature** (`lib/features/notifications/domain/entities/`)
- `notification.dart` - User notifications with type-based data
**Showrooms Feature** (`lib/features/showrooms/domain/entities/`)
- `showroom.dart` - Virtual showrooms with 360 views, gallery
- `showroom_product.dart` - Products used in showrooms
**Account Feature** (`lib/features/account/domain/entities/`)
- `payment_reminder.dart` - Payment reminders with scheduling
- `audit_log.dart` - System audit trail
**Home Feature** (`lib/features/home/domain/entities/`)
- `member_card.dart` - Membership card display
- `promotion.dart` - Promotional campaigns
#### **Hive Data Models (25 files)**
Hive CE models for offline-first local storage. All models extend `HiveObject` with proper type adapters.
**Type ID Allocation:**
- Models: 0-24 (user_model=0, user_session_model=1, product_model=2, etc.)
- Enums: 30-50 (UserRole=30, UserStatus=31, LoyaltyTier=32, etc.)
**Model Features:**
- `@HiveType` with unique typeId
- `@HiveField` for all properties
- `fromJson()` / `toJson()` for API serialization
- `toEntity()` / `fromEntity()` for domain conversion
- Helper methods for JSONB fields
- Full documentation
**All models are located in:** `lib/features/*/data/models/`
- Auth: `user_model.dart`, `user_session_model.dart`
- Products: `product_model.dart`, `stock_level_model.dart`
- Cart: `cart_model.dart`, `cart_item_model.dart`
- Orders: `order_model.dart`, `order_item_model.dart`, `invoice_model.dart`, `payment_line_model.dart`
- Loyalty: `loyalty_point_entry_model.dart`, `gift_catalog_model.dart`, `redeemed_gift_model.dart`, `points_record_model.dart`
- Projects: `project_submission_model.dart`, `design_request_model.dart`
- Quotes: `quote_model.dart`, `quote_item_model.dart`
- Chat: `chat_room_model.dart`, `message_model.dart`
- Notifications: `notification_model.dart`
- Showrooms: `showroom_model.dart`, `showroom_product_model.dart`
- Account: `payment_reminder_model.dart`, `audit_log_model.dart`
#### **Enums (21 types)**
All enums are defined in `lib/core/database/models/enums.dart` with Hive type adapters:
**User & Auth:**
- `UserRole` - admin, customer, contractor, architect, distributor, broker
- `UserStatus` - active, inactive, pending, suspended, banned
- `LoyaltyTier` - diamond, platinum, gold
**Orders & Payments:**
- `OrderStatus` - draft, pending, confirmed, processing, shipped, delivered, completed, cancelled, returned, refunded
- `InvoiceType` - sales, proforma, credit_note, debit_note
- `InvoiceStatus` - draft, sent, viewed, overdue, paid, partially_paid, cancelled
- `PaymentMethod` - cash, bank_transfer, credit_card, debit_card, e_wallet, cod
- `PaymentStatus` - pending, processing, completed, failed, refunded
**Loyalty & Gifts:**
- `EntryType` - earn, spend, adjust, expire, reversal
- `EntrySource` - purchase, referral, promotion, bonus, manual, project_submission, design_request, points_record
- `ComplaintStatus` - pending, reviewing, approved, rejected, resolved
- `GiftCategory` - voucher, product, service, experience, discount
- `GiftStatus` - active, used, expired, cancelled
- `PointsStatus` - pending, approved, rejected
**Projects & Quotes:**
- `ProjectType` - residential, commercial, industrial, hospitality, retail, office, other
- `SubmissionStatus` - pending, reviewing, approved, rejected
- `DesignStatus` - submitted, assigned, in_progress, completed, delivered, revision_requested, approved, cancelled
- `QuoteStatus` - draft, sent, viewed, negotiating, accepted, rejected, expired, converted
**Chat:**
- `RoomType` - support, sales, quote, order
- `ContentType` - text, image, file, product, location
- `ReminderType` - payment_due, overdue, final_notice, custom
**Detailed Documentation:**
- See `DOMAIN_ENTITIES_SUMMARY.md` for entity details
- See `HIVE_MODELS_REFERENCE.md` for model templates and usage
- See `lib/core/constants/storage_constants.dart` for Type ID reference
---
@@ -1788,150 +1599,9 @@ class OfflineQueue {
}
```
---
## Error Handling
### Network Errors
```dart
class ApiClient {
Future<T> request<T>(String endpoint, {Map<String, dynamic>? data}) async {
try {
final response = await dio.post(endpoint, data: data);
return _parseResponse<T>(response);
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout) {
throw NetworkException('Connection timeout. Please check your internet.');
} else if (e.type == DioExceptionType.receiveTimeout) {
throw NetworkException('Server took too long to respond.');
} else if (e.response?.statusCode == 401) {
throw UnauthorizedException('Session expired. Please login again.');
} else if (e.response?.statusCode == 404) {
throw NotFoundException('Resource not found.');
} else if (e.response?.statusCode == 500) {
throw ServerException('Server error. Please try again later.');
} else {
throw NetworkException('Network error: ${e.message}');
}
} catch (e) {
throw UnknownException('Unexpected error: $e');
}
}
}
```
### Form Validation
```dart
class Validators {
static String? required(String? value) {
if (value == null || value.trim().isEmpty) {
return 'Trường này là bắt buộc';
}
return null;
}
static String? vietnamesePhone(String? value) {
if (value == null || value.isEmpty) {
return 'Vui lòng nhập số điện thoại';
}
final phoneRegex = RegExp(r'^(0|\+84)[3|5|7|8|9][0-9]{8}$');
if (!phoneRegex.hasMatch(value)) {
return 'Số điện thoại không hợp lệ';
}
return null;
}
static String? email(String? value) {
if (value == null || value.isEmpty) {
return 'Vui lòng nhập email';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Email không hợp lệ';
}
return null;
}
static String? password(String? value) {
if (value == null || value.isEmpty) {
return 'Vui lòng nhập mật khẩu';
}
if (value.length < 8) {
return 'Mật khẩu phải có ít nhất 8 ký tự';
}
if (!RegExp(r'[A-Z]').hasMatch(value)) {
return 'Mật khẩu phải có ít nhất 1 chữ hoa';
}
if (!RegExp(r'[a-z]').hasMatch(value)) {
return 'Mật khẩu phải có ít nhất 1 chữ thường';
}
if (!RegExp(r'[0-9]').hasMatch(value)) {
return 'Mật khẩu phải có ít nhất 1 số';
}
if (!RegExp(r'[!@#\$%^&*(),.?":{}|<>]').hasMatch(value)) {
return 'Mật khẩu phải có ít nhất 1 ký tự đặc biệt';
}
return null;
}
}
```
---
## Testing Strategy
### Unit Tests
```dart
void main() {
group('CartNotifier', () {
late CartNotifier cartNotifier;
late MockCartRepository mockRepository;
setUp(() {
mockRepository = MockCartRepository();
cartNotifier = CartNotifier(mockRepository);
});
test('addItem adds product to cart', () {
final product = Product(id: '1', name: 'Test', price: 100);
cartNotifier.addItem(product, 2);
expect(cartNotifier.state.length, 1);
expect(cartNotifier.state.first.quantity, 2);
verify(mockRepository.saveCart(any)).called(1);
});
test('cartTotal calculates correct total', () {
final items = [
CartItem(id: '1', price: 100, quantity: 2),
CartItem(id: '2', price: 50, quantity: 3),
];
final total = calculateCartTotal(items);
expect(total, 350); // (100 * 2) + (50 * 3)
});
});
}
```
---
## Localization (Vietnamese Primary)
### Setup