create order
This commit is contained in:
@@ -23,7 +23,7 @@ class InvoicesLocalDataSource {
|
||||
throw Exception('Invalid JSON format: expected List');
|
||||
}
|
||||
|
||||
final invoices = (decoded as List<dynamic>)
|
||||
final invoices = decoded
|
||||
.map((json) => InvoiceModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/// Order Remote Data Source
|
||||
///
|
||||
/// Handles API calls for order-related data.
|
||||
library;
|
||||
|
||||
import 'package:worker/core/constants/api_constants.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/orders/data/models/order_status_model.dart';
|
||||
import 'package:worker/features/orders/data/models/payment_term_model.dart';
|
||||
|
||||
/// Order Remote Data Source
|
||||
class OrderRemoteDataSource {
|
||||
const OrderRemoteDataSource(this._dioClient);
|
||||
|
||||
final DioClient _dioClient;
|
||||
|
||||
/// Get order status list
|
||||
///
|
||||
/// Calls: POST /api/method/building_material.building_material.api.sales_order.get_order_status_list
|
||||
/// Returns: List of order statuses with labels and colors
|
||||
Future<List<OrderStatusModel>> getOrderStatusList() async {
|
||||
try {
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.getOrderStatusList}',
|
||||
data: <String, dynamic>{},
|
||||
);
|
||||
|
||||
final data = response.data;
|
||||
if (data == null) {
|
||||
throw Exception('No data received from getOrderStatusList API');
|
||||
}
|
||||
|
||||
// API returns: { "message": [...] }
|
||||
final message = data['message'];
|
||||
if (message == null) {
|
||||
throw Exception('No message field in getOrderStatusList response');
|
||||
}
|
||||
|
||||
final List<dynamic> statusList = message as List<dynamic>;
|
||||
return statusList
|
||||
.map((json) => OrderStatusModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to get order status list: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get payment terms list
|
||||
///
|
||||
/// Calls: POST /api/method/frappe.client.get_list
|
||||
/// Body: { "doctype": "Payment Terms Template", "fields": ["name","custom_description"], "limit_page_length": 0 }
|
||||
/// Returns: List of payment terms with names and descriptions
|
||||
Future<List<PaymentTermModel>> getPaymentTermsList() async {
|
||||
try {
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetList}',
|
||||
data: {
|
||||
'doctype': 'Payment Terms Template',
|
||||
'fields': ['name', 'custom_description'],
|
||||
'limit_page_length': 0,
|
||||
},
|
||||
);
|
||||
|
||||
final data = response.data;
|
||||
if (data == null) {
|
||||
throw Exception('No data received from getPaymentTermsList API');
|
||||
}
|
||||
|
||||
// API returns: { "message": [...] }
|
||||
final message = data['message'];
|
||||
if (message == null) {
|
||||
throw Exception('No message field in getPaymentTermsList response');
|
||||
}
|
||||
|
||||
final List<dynamic> termsList = message as List<dynamic>;
|
||||
return termsList
|
||||
.map((json) => PaymentTermModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to get payment terms list: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new order
|
||||
///
|
||||
/// Calls: POST /api/method/building_material.building_material.api.sales_order.save
|
||||
/// Body: {
|
||||
/// "transaction_date": "2025-11-20",
|
||||
/// "delivery_date": "2025-11-20",
|
||||
/// "shipping_address_name": "...",
|
||||
/// "customer_address": "...",
|
||||
/// "description": "...",
|
||||
/// "payment_terms": "...",
|
||||
/// "items": [{"item_id": "...", "qty_entered": 0, "primary_qty": 0, "price_entered": 0}]
|
||||
/// }
|
||||
/// Returns: { "message": { "name": "SAL-ORD-2025-00001", ... } }
|
||||
Future<Map<String, dynamic>> createOrder({
|
||||
required List<Map<String, dynamic>> items,
|
||||
required Map<String, dynamic> deliveryAddress,
|
||||
required String paymentMethod,
|
||||
bool needsInvoice = false,
|
||||
bool needsNegotiation = false,
|
||||
String? notes,
|
||||
}) async {
|
||||
try {
|
||||
// Get current date for transaction and delivery
|
||||
final now = DateTime.now();
|
||||
final dateStr = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||||
|
||||
// Format items for Frappe API
|
||||
final formattedItems = items.map((item) {
|
||||
return {
|
||||
'item_id': item['item_id'],
|
||||
'qty_entered': item['quantity'], // boxes/pieces (viên)
|
||||
'primary_qty': item['quantityConverted'], // m²
|
||||
'price_entered': item['price'],
|
||||
};
|
||||
}).toList();
|
||||
|
||||
// Prepare request body in Frappe format
|
||||
final requestBody = {
|
||||
'transaction_date': dateStr,
|
||||
'delivery_date': dateStr,
|
||||
'shipping_address_name': deliveryAddress['name'] ?? '',
|
||||
'customer_address': deliveryAddress['name'] ?? '',
|
||||
'description': notes ?? 'Order from mobile app',
|
||||
'payment_terms': paymentMethod,
|
||||
'items': formattedItems,
|
||||
};
|
||||
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'${ApiConstants.frappeApiMethod}${ApiConstants.createOrder}',
|
||||
data: requestBody,
|
||||
);
|
||||
|
||||
final data = response.data;
|
||||
if (data == null) {
|
||||
throw Exception('No data received from createOrder API');
|
||||
}
|
||||
|
||||
// Extract order info from Frappe response
|
||||
final message = data['message'] as Map<String, dynamic>?;
|
||||
if (message == null) {
|
||||
throw Exception('No message field in createOrder response');
|
||||
}
|
||||
|
||||
// Return standardized response
|
||||
return {
|
||||
'orderId': message['name'] ?? '',
|
||||
'orderNumber': message['name'] ?? '',
|
||||
'fullResponse': message,
|
||||
};
|
||||
} catch (e) {
|
||||
throw Exception('Failed to create order: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class OrdersLocalDataSource {
|
||||
throw Exception('Invalid JSON format: expected List');
|
||||
}
|
||||
|
||||
final orders = (decoded as List<dynamic>)
|
||||
final orders = decoded
|
||||
.map((json) => OrderModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
|
||||
65
lib/features/orders/data/models/order_status_model.dart
Normal file
65
lib/features/orders/data/models/order_status_model.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
/// Order Status Model
|
||||
///
|
||||
/// Data model for order status from API responses.
|
||||
library;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_status.dart';
|
||||
|
||||
/// Order Status Model
|
||||
class OrderStatusModel extends Equatable {
|
||||
final String status;
|
||||
final String label;
|
||||
final String color;
|
||||
final int index;
|
||||
|
||||
const OrderStatusModel({
|
||||
required this.status,
|
||||
required this.label,
|
||||
required this.color,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
/// Create from JSON
|
||||
factory OrderStatusModel.fromJson(Map<String, dynamic> json) {
|
||||
return OrderStatusModel(
|
||||
status: json['status'] as String,
|
||||
label: json['label'] as String,
|
||||
color: json['color'] as String,
|
||||
index: json['index'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'status': status,
|
||||
'label': label,
|
||||
'color': color,
|
||||
'index': index,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert to entity
|
||||
OrderStatus toEntity() {
|
||||
return OrderStatus(
|
||||
status: status,
|
||||
label: label,
|
||||
color: color,
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from entity
|
||||
factory OrderStatusModel.fromEntity(OrderStatus entity) {
|
||||
return OrderStatusModel(
|
||||
status: entity.status,
|
||||
label: entity.label,
|
||||
color: entity.color,
|
||||
index: entity.index,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, label, color, index];
|
||||
}
|
||||
53
lib/features/orders/data/models/payment_term_model.dart
Normal file
53
lib/features/orders/data/models/payment_term_model.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
/// Payment Term Model
|
||||
///
|
||||
/// Data model for payment term from API responses.
|
||||
library;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:worker/features/orders/domain/entities/payment_term.dart';
|
||||
|
||||
/// Payment Term Model
|
||||
class PaymentTermModel extends Equatable {
|
||||
final String name;
|
||||
final String? customDescription;
|
||||
|
||||
const PaymentTermModel({
|
||||
required this.name,
|
||||
this.customDescription,
|
||||
});
|
||||
|
||||
/// Create from JSON
|
||||
factory PaymentTermModel.fromJson(Map<String, dynamic> json) {
|
||||
return PaymentTermModel(
|
||||
name: json['name'] as String,
|
||||
customDescription: json['custom_description'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'custom_description': customDescription,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert to entity
|
||||
PaymentTerm toEntity() {
|
||||
return PaymentTerm(
|
||||
name: name,
|
||||
customDescription: customDescription ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from entity
|
||||
factory PaymentTermModel.fromEntity(PaymentTerm entity) {
|
||||
return PaymentTermModel(
|
||||
name: entity.name,
|
||||
customDescription: entity.customDescription,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, customDescription];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/// Order Repository Implementation
|
||||
///
|
||||
/// Implements the order repository interface.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/orders/data/datasources/order_remote_datasource.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_status.dart';
|
||||
import 'package:worker/features/orders/domain/entities/payment_term.dart';
|
||||
import 'package:worker/features/orders/domain/repositories/order_repository.dart';
|
||||
|
||||
/// Order Repository Implementation
|
||||
class OrderRepositoryImpl implements OrderRepository {
|
||||
const OrderRepositoryImpl(this._remoteDataSource);
|
||||
|
||||
final OrderRemoteDataSource _remoteDataSource;
|
||||
|
||||
@override
|
||||
Future<List<OrderStatus>> getOrderStatusList() async {
|
||||
try {
|
||||
final models = await _remoteDataSource.getOrderStatusList();
|
||||
return models.map((model) => model.toEntity()).toList();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to get order status list: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PaymentTerm>> getPaymentTermsList() async {
|
||||
try {
|
||||
final models = await _remoteDataSource.getPaymentTermsList();
|
||||
return models.map((model) => model.toEntity()).toList();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to get payment terms list: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> createOrder({
|
||||
required List<Map<String, dynamic>> items,
|
||||
required Map<String, dynamic> deliveryAddress,
|
||||
required String paymentMethod,
|
||||
bool needsInvoice = false,
|
||||
bool needsNegotiation = false,
|
||||
String? notes,
|
||||
}) async {
|
||||
try {
|
||||
return await _remoteDataSource.createOrder(
|
||||
items: items,
|
||||
deliveryAddress: deliveryAddress,
|
||||
paymentMethod: paymentMethod,
|
||||
needsInvoice: needsInvoice,
|
||||
needsNegotiation: needsNegotiation,
|
||||
notes: notes,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('Failed to create order: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
31
lib/features/orders/domain/entities/order_status.dart
Normal file
31
lib/features/orders/domain/entities/order_status.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
/// Order Status Entity
|
||||
///
|
||||
/// Represents an order status option from the API.
|
||||
library;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Order Status Entity
|
||||
class OrderStatus extends Equatable {
|
||||
/// Status value (e.g., "Pending approval", "Processing", "Completed")
|
||||
final String status;
|
||||
|
||||
/// Vietnamese label (e.g., "Chờ phê duyệt", "Đang xử lý", "Hoàn thành")
|
||||
final String label;
|
||||
|
||||
/// Color indicator (e.g., "Warning", "Success", "Danger")
|
||||
final String color;
|
||||
|
||||
/// Display order index
|
||||
final int index;
|
||||
|
||||
const OrderStatus({
|
||||
required this.status,
|
||||
required this.label,
|
||||
required this.color,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, label, color, index];
|
||||
}
|
||||
23
lib/features/orders/domain/entities/payment_term.dart
Normal file
23
lib/features/orders/domain/entities/payment_term.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
/// Payment Term Entity
|
||||
///
|
||||
/// Represents a payment term template option from the API.
|
||||
library;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Payment Term Entity
|
||||
class PaymentTerm extends Equatable {
|
||||
/// Payment term name (e.g., "Thanh toán hoàn toàn", "Thanh toán trả trước")
|
||||
final String name;
|
||||
|
||||
/// Custom description (e.g., "Thanh toán ngay được chiết khấu 2%")
|
||||
final String customDescription;
|
||||
|
||||
const PaymentTerm({
|
||||
required this.name,
|
||||
required this.customDescription,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, customDescription];
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/// Order Repository Interface
|
||||
///
|
||||
/// Defines the contract for order-related data operations.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/orders/domain/entities/order_status.dart';
|
||||
import 'package:worker/features/orders/domain/entities/payment_term.dart';
|
||||
|
||||
/// Order Repository Interface
|
||||
abstract class OrderRepository {
|
||||
/// Get list of available order statuses
|
||||
Future<List<OrderStatus>> getOrderStatusList();
|
||||
|
||||
/// Get list of available payment terms
|
||||
Future<List<PaymentTerm>> getPaymentTermsList();
|
||||
|
||||
/// Create new order
|
||||
Future<Map<String, dynamic>> createOrder({
|
||||
required List<Map<String, dynamic>> items,
|
||||
required Map<String, dynamic> deliveryAddress,
|
||||
required String paymentMethod,
|
||||
bool needsInvoice = false,
|
||||
bool needsNegotiation = false,
|
||||
String? notes,
|
||||
});
|
||||
}
|
||||
280
lib/features/orders/presentation/pages/order_success_page.dart
Normal file
280
lib/features/orders/presentation/pages/order_success_page.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
/// Order Success Page
|
||||
///
|
||||
/// Displays order confirmation after successful order placement.
|
||||
/// Features:
|
||||
/// - Success icon and message
|
||||
/// - Order information (order number, date, total, payment method, status)
|
||||
/// - Different message for negotiation requests
|
||||
/// - Navigation to order details or home
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Order Success Page
|
||||
class OrderSuccessPage extends StatelessWidget {
|
||||
const OrderSuccessPage({
|
||||
super.key,
|
||||
required this.orderNumber,
|
||||
this.total,
|
||||
this.paymentMethod,
|
||||
this.isNegotiation = false,
|
||||
});
|
||||
|
||||
final String orderNumber;
|
||||
final double? total;
|
||||
final String? paymentMethod;
|
||||
final bool isNegotiation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final now = DateTime.now();
|
||||
final dateFormat = DateFormat('dd/MM/yyyy HH:mm');
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Success Icon
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.success.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
FontAwesomeIcons.check,
|
||||
color: AppColors.success,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
|
||||
// Success Title
|
||||
Text(
|
||||
isNegotiation
|
||||
? 'Gửi yêu cầu thành công!'
|
||||
: 'Tạo đơn hàng thành công!',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Success Message
|
||||
Text(
|
||||
isNegotiation
|
||||
? 'Chúng tôi sẽ liên hệ với bạn để đàm phán giá trong vòng 24 giờ.'
|
||||
: 'Cảm ơn bạn đã đặt hàng. Chúng tôi sẽ liên hệ xác nhận trong vòng 24 giờ.',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// Order Info Card
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF4F6F8),
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Order Number
|
||||
Column(
|
||||
children: [
|
||||
const Text(
|
||||
'Mã đơn hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
orderNumber,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Order Date
|
||||
_buildInfoRow(
|
||||
'Ngày đặt',
|
||||
dateFormat.format(now),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Total Amount
|
||||
if (total != null)
|
||||
_buildInfoRow(
|
||||
'Tổng tiền',
|
||||
_formatCurrency(total!),
|
||||
valueStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
|
||||
if (total != null) const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Payment Method
|
||||
if (paymentMethod != null && !isNegotiation)
|
||||
_buildInfoRow(
|
||||
'Phương thức thanh toán',
|
||||
paymentMethod!,
|
||||
),
|
||||
|
||||
if (paymentMethod != null && !isNegotiation)
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Status
|
||||
_buildInfoRow(
|
||||
'Trạng thái',
|
||||
isNegotiation ? 'Chờ đàm phán' : 'Chờ xác nhận',
|
||||
valueStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isNegotiation
|
||||
? AppColors.warning
|
||||
: AppColors.warning,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// View Order Details Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Navigate to order details page
|
||||
context.pushReplacementNamed(
|
||||
RouteNames.orderDetail,
|
||||
pathParameters: {'orderId': orderNumber},
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.eye, size: 18),
|
||||
label: const Text(
|
||||
'Xem chi tiết đơn hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// Back to Home Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// Navigate to home page
|
||||
context.goNamed(RouteNames.home);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.house, size: 18),
|
||||
label: const Text(
|
||||
'Quay về trang chủ',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.grey900,
|
||||
side: BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 1.5,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build info row
|
||||
Widget _buildInfoRow(
|
||||
String label,
|
||||
String value, {
|
||||
TextStyle? valueStyle,
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: valueStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Format currency
|
||||
String _formatCurrency(double amount) {
|
||||
return '${amount.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d)(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}₫';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/// Order Data Providers
|
||||
///
|
||||
/// Riverpod providers for order data sources and repositories.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/orders/data/datasources/order_remote_datasource.dart';
|
||||
import 'package:worker/features/orders/data/repositories/order_repository_impl.dart';
|
||||
import 'package:worker/features/orders/domain/repositories/order_repository.dart';
|
||||
|
||||
part 'order_data_providers.g.dart';
|
||||
|
||||
/// Provider for Order Remote Data Source
|
||||
@riverpod
|
||||
Future<OrderRemoteDataSource> orderRemoteDataSource(Ref ref) async {
|
||||
final dioClient = await ref.watch(dioClientProvider.future);
|
||||
return OrderRemoteDataSource(dioClient);
|
||||
}
|
||||
|
||||
/// Provider for Order Repository
|
||||
@riverpod
|
||||
Future<OrderRepository> orderRepository(Ref ref) async {
|
||||
final remoteDataSource = await ref.watch(orderRemoteDataSourceProvider.future);
|
||||
return OrderRepositoryImpl(remoteDataSource);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'order_data_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for Order Remote Data Source
|
||||
|
||||
@ProviderFor(orderRemoteDataSource)
|
||||
const orderRemoteDataSourceProvider = OrderRemoteDataSourceProvider._();
|
||||
|
||||
/// Provider for Order Remote Data Source
|
||||
|
||||
final class OrderRemoteDataSourceProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<OrderRemoteDataSource>,
|
||||
OrderRemoteDataSource,
|
||||
FutureOr<OrderRemoteDataSource>
|
||||
>
|
||||
with
|
||||
$FutureModifier<OrderRemoteDataSource>,
|
||||
$FutureProvider<OrderRemoteDataSource> {
|
||||
/// Provider for Order Remote Data Source
|
||||
const OrderRemoteDataSourceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'orderRemoteDataSourceProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$orderRemoteDataSourceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<OrderRemoteDataSource> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<OrderRemoteDataSource> create(Ref ref) {
|
||||
return orderRemoteDataSource(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$orderRemoteDataSourceHash() =>
|
||||
r'f4e14afbd8ae9e4348cba8f9a983ff9cb5e2a4c7';
|
||||
|
||||
/// Provider for Order Repository
|
||||
|
||||
@ProviderFor(orderRepository)
|
||||
const orderRepositoryProvider = OrderRepositoryProvider._();
|
||||
|
||||
/// Provider for Order Repository
|
||||
|
||||
final class OrderRepositoryProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<OrderRepository>,
|
||||
OrderRepository,
|
||||
FutureOr<OrderRepository>
|
||||
>
|
||||
with $FutureModifier<OrderRepository>, $FutureProvider<OrderRepository> {
|
||||
/// Provider for Order Repository
|
||||
const OrderRepositoryProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'orderRepositoryProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$orderRepositoryHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<OrderRepository> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<OrderRepository> create(Ref ref) {
|
||||
return orderRepository(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$orderRepositoryHash() => r'985408a6667ab31427524f9b1981287c28f4f221';
|
||||
@@ -0,0 +1,44 @@
|
||||
/// Order Repository Provider
|
||||
///
|
||||
/// Provides the order repository instance and related providers.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/orders/data/datasources/order_remote_datasource.dart';
|
||||
import 'package:worker/features/orders/data/repositories/order_repository_impl.dart';
|
||||
import 'package:worker/features/orders/domain/repositories/order_repository.dart';
|
||||
|
||||
part 'order_repository_provider.g.dart';
|
||||
|
||||
/// Order Repository Provider
|
||||
@riverpod
|
||||
Future<OrderRepository> orderRepository(Ref ref) async {
|
||||
final dioClient = await ref.watch(dioClientProvider.future);
|
||||
final remoteDataSource = OrderRemoteDataSource(dioClient);
|
||||
return OrderRepositoryImpl(remoteDataSource);
|
||||
}
|
||||
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
@riverpod
|
||||
Future<Map<String, dynamic>> createOrder(
|
||||
Ref ref, {
|
||||
required List<Map<String, dynamic>> items,
|
||||
required Map<String, dynamic> deliveryAddress,
|
||||
required String paymentMethod,
|
||||
bool needsInvoice = false,
|
||||
bool needsNegotiation = false,
|
||||
String? notes,
|
||||
}) async {
|
||||
final repository = await ref.watch(orderRepositoryProvider.future);
|
||||
return await repository.createOrder(
|
||||
items: items,
|
||||
deliveryAddress: deliveryAddress,
|
||||
paymentMethod: paymentMethod,
|
||||
needsInvoice: needsInvoice,
|
||||
needsNegotiation: needsNegotiation,
|
||||
notes: notes,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'order_repository_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Order Repository Provider
|
||||
|
||||
@ProviderFor(orderRepository)
|
||||
const orderRepositoryProvider = OrderRepositoryProvider._();
|
||||
|
||||
/// Order Repository Provider
|
||||
|
||||
final class OrderRepositoryProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<OrderRepository>,
|
||||
OrderRepository,
|
||||
FutureOr<OrderRepository>
|
||||
>
|
||||
with $FutureModifier<OrderRepository>, $FutureProvider<OrderRepository> {
|
||||
/// Order Repository Provider
|
||||
const OrderRepositoryProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'orderRepositoryProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$orderRepositoryHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<OrderRepository> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<OrderRepository> create(Ref ref) {
|
||||
return orderRepository(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$orderRepositoryHash() => r'15efafcf3b545ea52fdc8d0acbd8192ba8f41546';
|
||||
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
|
||||
@ProviderFor(createOrder)
|
||||
const createOrderProvider = CreateOrderFamily._();
|
||||
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
|
||||
final class CreateOrderProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<Map<String, dynamic>>,
|
||||
Map<String, dynamic>,
|
||||
FutureOr<Map<String, dynamic>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<Map<String, dynamic>>,
|
||||
$FutureProvider<Map<String, dynamic>> {
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
const CreateOrderProvider._({
|
||||
required CreateOrderFamily super.from,
|
||||
required ({
|
||||
List<Map<String, dynamic>> items,
|
||||
Map<String, dynamic> deliveryAddress,
|
||||
String paymentMethod,
|
||||
bool needsInvoice,
|
||||
bool needsNegotiation,
|
||||
String? notes,
|
||||
})
|
||||
super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'createOrderProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$createOrderHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'createOrderProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<Map<String, dynamic>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<Map<String, dynamic>> create(Ref ref) {
|
||||
final argument =
|
||||
this.argument
|
||||
as ({
|
||||
List<Map<String, dynamic>> items,
|
||||
Map<String, dynamic> deliveryAddress,
|
||||
String paymentMethod,
|
||||
bool needsInvoice,
|
||||
bool needsNegotiation,
|
||||
String? notes,
|
||||
});
|
||||
return createOrder(
|
||||
ref,
|
||||
items: argument.items,
|
||||
deliveryAddress: argument.deliveryAddress,
|
||||
paymentMethod: argument.paymentMethod,
|
||||
needsInvoice: argument.needsInvoice,
|
||||
needsNegotiation: argument.needsNegotiation,
|
||||
notes: argument.notes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is CreateOrderProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$createOrderHash() => r'2d13526815e19a2bbef2f2974dad991d8ffcb594';
|
||||
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
|
||||
final class CreateOrderFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
FutureOr<Map<String, dynamic>>,
|
||||
({
|
||||
List<Map<String, dynamic>> items,
|
||||
Map<String, dynamic> deliveryAddress,
|
||||
String paymentMethod,
|
||||
bool needsInvoice,
|
||||
bool needsNegotiation,
|
||||
String? notes,
|
||||
})
|
||||
> {
|
||||
const CreateOrderFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'createOrderProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
/// Create Order Provider
|
||||
///
|
||||
/// Creates a new order with the given parameters.
|
||||
|
||||
CreateOrderProvider call({
|
||||
required List<Map<String, dynamic>> items,
|
||||
required Map<String, dynamic> deliveryAddress,
|
||||
required String paymentMethod,
|
||||
bool needsInvoice = false,
|
||||
bool needsNegotiation = false,
|
||||
String? notes,
|
||||
}) => CreateOrderProvider._(
|
||||
argument: (
|
||||
items: items,
|
||||
deliveryAddress: deliveryAddress,
|
||||
paymentMethod: paymentMethod,
|
||||
needsInvoice: needsInvoice,
|
||||
needsNegotiation: needsNegotiation,
|
||||
notes: notes,
|
||||
),
|
||||
from: this,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => r'createOrderProvider';
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Order Status Provider
|
||||
///
|
||||
/// Provides order status list from the API.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_status.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/order_data_providers.dart';
|
||||
|
||||
part 'order_status_provider.g.dart';
|
||||
|
||||
/// Provider for fetching order status list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<OrderStatus>> which handles loading/error states.
|
||||
@riverpod
|
||||
Future<List<OrderStatus>> orderStatusList(Ref ref) async {
|
||||
final repository = await ref.watch(orderRepositoryProvider.future);
|
||||
return repository.getOrderStatusList();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'order_status_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for fetching order status list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<OrderStatus>> which handles loading/error states.
|
||||
|
||||
@ProviderFor(orderStatusList)
|
||||
const orderStatusListProvider = OrderStatusListProvider._();
|
||||
|
||||
/// Provider for fetching order status list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<OrderStatus>> which handles loading/error states.
|
||||
|
||||
final class OrderStatusListProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<OrderStatus>>,
|
||||
List<OrderStatus>,
|
||||
FutureOr<List<OrderStatus>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<OrderStatus>>,
|
||||
$FutureProvider<List<OrderStatus>> {
|
||||
/// Provider for fetching order status list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<OrderStatus>> which handles loading/error states.
|
||||
const OrderStatusListProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'orderStatusListProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$orderStatusListHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<OrderStatus>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<OrderStatus>> create(Ref ref) {
|
||||
return orderStatusList(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$orderStatusListHash() => r'feb0d93e57f22e0c39c34e0a655c0972d874904e';
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Payment Terms Provider
|
||||
///
|
||||
/// Provides payment terms list from the API.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/orders/domain/entities/payment_term.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/order_data_providers.dart';
|
||||
|
||||
part 'payment_terms_provider.g.dart';
|
||||
|
||||
/// Provider for fetching payment terms list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<PaymentTerm>> which handles loading/error states.
|
||||
@riverpod
|
||||
Future<List<PaymentTerm>> paymentTermsList(Ref ref) async {
|
||||
final repository = await ref.watch(orderRepositoryProvider.future);
|
||||
return repository.getPaymentTermsList();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'payment_terms_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider for fetching payment terms list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<PaymentTerm>> which handles loading/error states.
|
||||
|
||||
@ProviderFor(paymentTermsList)
|
||||
const paymentTermsListProvider = PaymentTermsListProvider._();
|
||||
|
||||
/// Provider for fetching payment terms list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<PaymentTerm>> which handles loading/error states.
|
||||
|
||||
final class PaymentTermsListProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<PaymentTerm>>,
|
||||
List<PaymentTerm>,
|
||||
FutureOr<List<PaymentTerm>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<PaymentTerm>>,
|
||||
$FutureProvider<List<PaymentTerm>> {
|
||||
/// Provider for fetching payment terms list
|
||||
///
|
||||
/// This provider automatically fetches the list when accessed.
|
||||
/// Returns AsyncValue<List<PaymentTerm>> which handles loading/error states.
|
||||
const PaymentTermsListProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'paymentTermsListProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$paymentTermsListHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<PaymentTerm>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<PaymentTerm>> create(Ref ref) {
|
||||
return paymentTermsList(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$paymentTermsListHash() => r'6074016c04d947058b731c334b8f84fe85c36124';
|
||||
@@ -6,7 +6,6 @@ library;
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
Reference in New Issue
Block a user