add price policy

This commit is contained in:
Phuoc Nguyen
2025-11-03 11:20:09 +07:00
parent c0527a086c
commit 21c1c3372c
53 changed files with 7160 additions and 2361 deletions

View File

@@ -0,0 +1,185 @@
/// Price Policy Local DataSource
///
/// Handles all local data operations for price policy documents.
/// Currently provides mock data for development and testing.
/// Will be extended to use Hive cache when backend API is available.
library;
import 'package:worker/features/price_policy/data/models/price_document_model.dart';
/// Price Policy Local Data Source
///
/// Provides mock data for price policy documents.
/// In production, this will cache data from the remote API.
class PricePolicyLocalDataSource {
/// Get all price policy documents
///
/// Returns a list of all documents from mock data.
/// In production, this will fetch from Hive cache.
Future<List<PriceDocumentModel>> getAllDocuments() async {
// Simulate network delay
await Future<void>.delayed(const Duration(milliseconds: 300));
return _mockDocuments;
}
/// Get documents by category
///
/// Returns filtered list of documents matching the [category].
Future<List<PriceDocumentModel>> getDocumentsByCategory(
String category,
) async {
// Simulate network delay
await Future<void>.delayed(const Duration(milliseconds: 200));
return _mockDocuments
.where((doc) => doc.category.toLowerCase() == category.toLowerCase())
.toList();
}
/// Get a specific document by ID
///
/// Returns the document if found, null otherwise.
Future<PriceDocumentModel?> getDocumentById(String documentId) async {
// Simulate network delay
await Future<void>.delayed(const Duration(milliseconds: 100));
try {
return _mockDocuments.firstWhere((doc) => doc.id == documentId);
} catch (e) {
return null;
}
}
/// Check if cache is valid
///
/// Returns true if cached data is still valid.
/// Currently always returns false since we're using mock data.
Future<bool> isCacheValid() async {
// TODO: Implement cache validation when using Hive
return false;
}
/// Cache documents locally
///
/// Saves documents to Hive for offline access.
/// Currently not implemented (using mock data).
Future<void> cacheDocuments(List<PriceDocumentModel> documents) async {
// TODO: Implement Hive caching when backend API is ready
}
/// Clear cached documents
///
/// Removes all cached documents from Hive.
/// Currently not implemented (using mock data).
Future<void> clearCache() async {
// TODO: Implement cache clearing when using Hive
}
/// Mock documents matching HTML design
///
/// This data will be replaced with real API data in production.
static final List<PriceDocumentModel> _mockDocuments = [
// Policy documents (Chính sách giá)
const PriceDocumentModel(
id: 'policy-eurotile-10-2025',
title: 'Chính sách giá Eurotile T10/2025',
description:
'Chính sách giá mới nhất cho sản phẩm gạch Eurotile, áp dụng từ tháng 10/2025',
publishedDate: '2025-10-01T00:00:00.000Z',
documentType: 'pdf',
category: 'policy',
downloadUrl: '/documents/policy-eurotile-10-2025.pdf',
fileSize: '2.5 MB',
),
const PriceDocumentModel(
id: 'policy-vasta-10-2025',
title: 'Chính sách giá Vasta Stone T10/2025',
description:
'Chính sách giá đá tự nhiên Vasta Stone, hiệu lực từ tháng 10/2025',
publishedDate: '2025-10-01T00:00:00.000Z',
documentType: 'pdf',
category: 'policy',
downloadUrl: '/documents/policy-vasta-10-2025.pdf',
fileSize: '1.8 MB',
),
const PriceDocumentModel(
id: 'policy-dealer-2025',
title: 'Chính sách chiết khấu đại lý 2025',
description:
'Chương trình chiết khấu và ưu đãi dành cho đại lý, thầu thợ',
publishedDate: '2025-09-15T00:00:00.000Z',
documentType: 'pdf',
category: 'policy',
downloadUrl: '/documents/policy-dealer-2025.pdf',
fileSize: '3.2 MB',
),
const PriceDocumentModel(
id: 'policy-payment-2025',
title: 'Điều kiện thanh toán & giao hàng',
description:
'Điều khoản thanh toán, chính sách giao hàng và bảo hành sản phẩm',
publishedDate: '2025-08-01T00:00:00.000Z',
documentType: 'pdf',
category: 'policy',
downloadUrl: '/documents/policy-payment-2025.pdf',
fileSize: '1.5 MB',
),
// Price list documents (Bảng giá)
const PriceDocumentModel(
id: 'pricelist-granite-2025',
title: 'Bảng giá Gạch Granite Eurotile 2025',
description:
'Bảng giá chi tiết toàn bộ sản phẩm gạch granite, kích thước 60x60, 80x80, 120x120',
publishedDate: '2025-10-01T00:00:00.000Z',
documentType: 'excel',
category: 'priceList',
downloadUrl: '/documents/pricelist-granite-2025.xlsx',
fileSize: '850 KB',
),
const PriceDocumentModel(
id: 'pricelist-ceramic-2025',
title: 'Bảng giá Gạch Ceramic Eurotile 2025',
description: 'Bảng giá gạch ceramic vân gỗ, vân đá, vân xi măng các loại',
publishedDate: '2025-10-01T00:00:00.000Z',
documentType: 'excel',
category: 'priceList',
downloadUrl: '/documents/pricelist-ceramic-2025.xlsx',
fileSize: '720 KB',
),
const PriceDocumentModel(
id: 'pricelist-stone-2025',
title: 'Bảng giá Đá tự nhiên Vasta Stone 2025',
description:
'Bảng giá đá marble, granite tự nhiên nhập khẩu, kích thước tấm lớn',
publishedDate: '2025-10-01T00:00:00.000Z',
documentType: 'excel',
category: 'priceList',
downloadUrl: '/documents/pricelist-stone-2025.xlsx',
fileSize: '950 KB',
),
const PriceDocumentModel(
id: 'pricelist-accessories-2025',
title: 'Bảng giá Phụ kiện & Vật liệu 2025',
description:
'Giá keo dán, chà ron, nẹp nhựa, nẹp inox và các phụ kiện thi công',
publishedDate: '2025-09-15T00:00:00.000Z',
documentType: 'excel',
category: 'priceList',
downloadUrl: '/documents/pricelist-accessories-2025.xlsx',
fileSize: '640 KB',
),
const PriceDocumentModel(
id: 'pricelist-outdoor-2025',
title: 'Bảng giá Gạch Outdoor & Chống trơn 2025',
description:
'Bảng giá sản phẩm outdoor, gạch chống trơn dành cho ngoại thất',
publishedDate: '2025-09-01T00:00:00.000Z',
documentType: 'excel',
category: 'priceList',
downloadUrl: '/documents/pricelist-outdoor-2025.xlsx',
fileSize: '780 KB',
),
];
}

View File

@@ -0,0 +1,158 @@
/// Data Model: Price Document Model
///
/// Data layer model for price policy documents.
/// Handles JSON serialization and conversion to/from domain entity.
library;
import 'package:worker/features/price_policy/domain/entities/price_document.dart';
/// Price Document Model
///
/// Used in the data layer for:
/// - JSON serialization/deserialization from API
/// - Conversion to domain entity
/// - Local storage (if needed)
class PriceDocumentModel {
/// Unique document ID
final String id;
/// Document title
final String title;
/// Document description
final String description;
/// Date the document was published (ISO 8601 string)
final String publishedDate;
/// Type of document (pdf or excel)
final String documentType;
/// Category (policy or priceList)
final String category;
/// URL to download the document
final String downloadUrl;
/// Optional file size display string
final String? fileSize;
/// Constructor
const PriceDocumentModel({
required this.id,
required this.title,
required this.description,
required this.publishedDate,
required this.documentType,
required this.category,
required this.downloadUrl,
this.fileSize,
});
/// Create model from JSON
factory PriceDocumentModel.fromJson(Map<String, dynamic> json) {
return PriceDocumentModel(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String,
publishedDate: json['published_date'] as String,
documentType: json['document_type'] as String,
category: json['category'] as String,
downloadUrl: json['download_url'] as String,
fileSize: json['file_size'] as String?,
);
}
/// Convert model to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
'published_date': publishedDate,
'document_type': documentType,
'category': category,
'download_url': downloadUrl,
'file_size': fileSize,
};
}
/// Convert model to domain entity
PriceDocument toEntity() {
return PriceDocument(
id: id,
title: title,
description: description,
publishedDate: DateTime.parse(publishedDate),
documentType: _parseDocumentType(documentType),
category: _parseCategory(category),
downloadUrl: downloadUrl,
fileSize: fileSize,
);
}
/// Create model from domain entity
factory PriceDocumentModel.fromEntity(PriceDocument entity) {
return PriceDocumentModel(
id: entity.id,
title: entity.title,
description: entity.description,
publishedDate: entity.publishedDate.toIso8601String(),
documentType: _documentTypeToString(entity.documentType),
category: _categoryToString(entity.category),
downloadUrl: entity.downloadUrl,
fileSize: entity.fileSize,
);
}
/// Parse document type from string
static DocumentType _parseDocumentType(String type) {
switch (type.toLowerCase()) {
case 'pdf':
return DocumentType.pdf;
case 'excel':
return DocumentType.excel;
default:
return DocumentType.pdf;
}
}
/// Parse category from string
static DocumentCategory _parseCategory(String category) {
switch (category.toLowerCase()) {
case 'policy':
return DocumentCategory.policy;
case 'pricelist':
case 'price_list':
return DocumentCategory.priceList;
default:
return DocumentCategory.policy;
}
}
/// Convert document type to string
static String _documentTypeToString(DocumentType type) {
switch (type) {
case DocumentType.pdf:
return 'pdf';
case DocumentType.excel:
return 'excel';
}
}
/// Convert category to string
static String _categoryToString(DocumentCategory category) {
switch (category) {
case DocumentCategory.policy:
return 'policy';
case DocumentCategory.priceList:
return 'priceList';
}
}
@override
String toString() {
return 'PriceDocumentModel(id: $id, title: $title, category: $category, '
'documentType: $documentType, publishedDate: $publishedDate)';
}
}

View File

@@ -0,0 +1,134 @@
/// Repository Implementation: Price Policy Repository
///
/// Concrete implementation of the PricePolicyRepository interface.
/// Coordinates between local and remote data sources to provide price policy data.
///
/// Currently uses mock data from local datasource.
/// Will implement offline-first strategy when backend API is available.
library;
import 'package:worker/features/price_policy/data/datasources/price_policy_local_datasource.dart';
import 'package:worker/features/price_policy/domain/entities/price_document.dart';
import 'package:worker/features/price_policy/domain/repositories/price_policy_repository.dart';
/// Price Policy Repository Implementation
///
/// Responsibilities:
/// - Coordinate between local cache and remote API (when available)
/// - Convert data models to domain entities
/// - Handle errors gracefully
/// - Manage cache invalidation
class PricePolicyRepositoryImpl implements PricePolicyRepository {
/// Local data source
final PricePolicyLocalDataSource localDataSource;
/// Remote data source (API) - TODO: Add when API is ready
// final PricePolicyRemoteDataSource remoteDataSource;
/// Constructor
PricePolicyRepositoryImpl({
required this.localDataSource,
// required this.remoteDataSource, // TODO: Add when API ready
});
@override
Future<List<PriceDocument>> getAllDocuments() async {
try {
// TODO: Implement offline-first strategy
// 1. Check if cache is valid
// 2. Return cached data if valid
// 3. If cache invalid, fetch from remote
// For now, get from local datasource (mock data)
final models = await localDataSource.getAllDocuments();
// Convert models to entities
final entities = models.map((model) => model.toEntity()).toList();
// Sort by published date (newest first)
entities.sort((a, b) => b.publishedDate.compareTo(a.publishedDate));
return entities;
} catch (e) {
// Log error and return empty list
// In production, this should throw proper domain failures
print('[PricePolicyRepository] Error getting documents: $e');
return [];
}
}
@override
Future<List<PriceDocument>> getDocumentsByCategory(
DocumentCategory category,
) async {
try {
// Convert category to string for datasource
final categoryString = _categoryToString(category);
// Get documents from local datasource
final models = await localDataSource.getDocumentsByCategory(
categoryString,
);
// Convert models to entities
final entities = models.map((model) => model.toEntity()).toList();
// Sort by published date (newest first)
entities.sort((a, b) => b.publishedDate.compareTo(a.publishedDate));
return entities;
} catch (e) {
print('[PricePolicyRepository] Error getting documents by category: $e');
return [];
}
}
@override
Future<PriceDocument?> getDocumentById(String documentId) async {
try {
// Get document from local datasource
final model = await localDataSource.getDocumentById(documentId);
// Convert model to entity
return model?.toEntity();
} catch (e) {
print('[PricePolicyRepository] Error getting document by id: $e');
return null;
}
}
@override
Future<List<PriceDocument>> refreshDocuments() async {
try {
// TODO: Implement remote fetch when API is available
// 1. Fetch from remote API
// 2. Cache the results locally
// 3. Return fresh data
// For now, just clear and refetch from local
await localDataSource.clearCache();
final models = await localDataSource.getAllDocuments();
// Convert models to entities
final entities = models.map((model) => model.toEntity()).toList();
// Sort by published date (newest first)
entities.sort((a, b) => b.publishedDate.compareTo(a.publishedDate));
return entities;
} catch (e) {
print('[PricePolicyRepository] Error refreshing documents: $e');
return [];
}
}
/// Helper method to convert category enum to string
String _categoryToString(DocumentCategory category) {
switch (category) {
case DocumentCategory.policy:
return 'policy';
case DocumentCategory.priceList:
return 'priceList';
}
}
}