Nội dung:
-
DH001234 La Nguyen Quynh
+
DH001234
@@ -139,12 +139,15 @@
diff --git a/lib/core/constants/api_constants.dart b/lib/core/constants/api_constants.dart
index 9554687..f42f1d0 100644
--- a/lib/core/constants/api_constants.dart
+++ b/lib/core/constants/api_constants.dart
@@ -211,10 +211,15 @@ class ApiConstants {
// Order Endpoints
// ============================================================================
- /// Create new order
- /// POST /orders
- /// Body: { "items": [...], "deliveryAddress": {...}, "paymentMethod": "..." }
- static const String createOrder = '/orders';
+ /// Get order status list (requires sid and csrf_token)
+ /// POST /api/method/building_material.building_material.api.sales_order.get_order_status_list
+ /// Returns: { "message": [{ "status": "...", "label": "...", "color": "...", "index": 0 }] }
+ static const String getOrderStatusList = '/building_material.building_material.api.sales_order.get_order_status_list';
+
+ /// Create new order (requires sid and csrf_token)
+ /// POST /api/method/building_material.building_material.api.sales_order.save
+ /// Body: { "transaction_date": "...", "delivery_date": "...", "items": [...], ... }
+ static const String createOrder = '/building_material.building_material.api.sales_order.save';
/// Get user's orders
/// GET /orders?status={status}&page={page}&limit={limit}
diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart
index 51dbc44..57a32e8 100644
--- a/lib/core/router/app_router.dart
+++ b/lib/core/router/app_router.dart
@@ -32,6 +32,7 @@ import 'package:worker/features/main/presentation/pages/main_scaffold.dart';
import 'package:worker/features/news/presentation/pages/news_detail_page.dart';
import 'package:worker/features/news/presentation/pages/news_list_page.dart';
import 'package:worker/features/orders/presentation/pages/order_detail_page.dart';
+import 'package:worker/features/orders/presentation/pages/order_success_page.dart';
import 'package:worker/features/orders/presentation/pages/orders_page.dart';
import 'package:worker/features/orders/presentation/pages/payment_detail_page.dart';
import 'package:worker/features/orders/presentation/pages/payment_qr_page.dart';
@@ -327,6 +328,29 @@ final routerProvider = Provider
((ref) {
},
),
+ // Order Success Route
+ GoRoute(
+ path: RouteNames.orderSuccess,
+ name: RouteNames.orderSuccess,
+ pageBuilder: (context, state) {
+ final orderNumber = state.uri.queryParameters['orderNumber'] ?? '';
+ final totalStr = state.uri.queryParameters['total'];
+ final total = totalStr != null ? double.tryParse(totalStr) : null;
+ final paymentMethod = state.uri.queryParameters['paymentMethod'];
+ final isNegotiationStr = state.uri.queryParameters['isNegotiation'];
+ final isNegotiation = isNegotiationStr == 'true';
+ return MaterialPage(
+ key: state.pageKey,
+ child: OrderSuccessPage(
+ orderNumber: orderNumber,
+ total: total,
+ paymentMethod: paymentMethod,
+ isNegotiation: isNegotiation,
+ ),
+ );
+ },
+ ),
+
// Quotes Route
GoRoute(
path: RouteNames.quotes,
diff --git a/lib/features/auth/presentation/pages/login_page.dart b/lib/features/auth/presentation/pages/login_page.dart
index a248789..edeb69d 100644
--- a/lib/features/auth/presentation/pages/login_page.dart
+++ b/lib/features/auth/presentation/pages/login_page.dart
@@ -42,7 +42,7 @@ class _LoginPageState extends ConsumerState {
final _formKey = GlobalKey();
// Controllers
- final _phoneController = TextEditingController(text: "0978113710");
+ final _phoneController = TextEditingController(text: "0986788766");
final _passwordController = TextEditingController(text: "123456");
// Focus nodes
diff --git a/lib/features/cart/presentation/pages/checkout_page.dart b/lib/features/cart/presentation/pages/checkout_page.dart
index 54bffd8..1ec1954 100644
--- a/lib/features/cart/presentation/pages/checkout_page.dart
+++ b/lib/features/cart/presentation/pages/checkout_page.dart
@@ -26,6 +26,8 @@ import 'package:worker/features/cart/presentation/widgets/invoice_section.dart';
import 'package:worker/features/cart/presentation/widgets/order_summary_section.dart';
import 'package:worker/features/cart/presentation/widgets/payment_method_section.dart';
import 'package:worker/features/cart/presentation/widgets/price_negotiation_section.dart';
+import 'package:worker/features/orders/presentation/providers/order_status_provider.dart';
+import 'package:worker/features/orders/presentation/providers/payment_terms_provider.dart';
/// Checkout Page
///
@@ -51,12 +53,15 @@ class CheckoutPage extends HookConsumerWidget {
// Invoice section
final needsInvoice = useState(false);
- // Payment method
- final paymentMethod = useState('full_payment');
+ // Payment method (will be set to first payment term name from API)
+ final paymentMethod = useState('');
// Price negotiation
final needsNegotiation = useState(false);
+ // Watch API provider for payment terms
+ final paymentTermsListAsync = ref.watch(paymentTermsListProvider);
+
// Get CartItemData from navigation
final cartItemsData = checkoutData?['cartItems'] as List? ?? [];
@@ -85,7 +90,7 @@ class CheckoutPage extends HookConsumerWidget {
);
// TODO: Fetch member discount from user profile API
- const memberDiscountPercent = 15.0; // Diamond tier (temporary)
+ const memberDiscountPercent = 0.0; // Temporarily disabled (was 15.0 for Diamond tier)
final memberDiscount = subtotal * (memberDiscountPercent / 100);
// TODO: Fetch shipping fee from API based on address
@@ -140,7 +145,78 @@ class CheckoutPage extends HookConsumerWidget {
// Payment Method Section (hidden if negotiation is checked)
if (!needsNegotiation.value)
- PaymentMethodSection(paymentMethod: paymentMethod),
+ paymentTermsListAsync.when(
+ data: (paymentTerms) {
+ // Set default payment method to first term if not set
+ if (paymentMethod.value.isEmpty && paymentTerms.isNotEmpty) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ paymentMethod.value = paymentTerms.first.name;
+ });
+ }
+ return PaymentMethodSection(
+ paymentMethod: paymentMethod,
+ paymentTerms: paymentTerms,
+ );
+ },
+ loading: () => Container(
+ margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
+ padding: const EdgeInsets.all(AppSpacing.md),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(AppRadius.card),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withValues(alpha: 0.05),
+ blurRadius: 8,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: const Center(
+ child: CircularProgressIndicator(),
+ ),
+ ),
+ error: (error, stack) => Container(
+ margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
+ padding: const EdgeInsets.all(AppSpacing.md),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(AppRadius.card),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withValues(alpha: 0.05),
+ blurRadius: 8,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Column(
+ children: [
+ const Icon(
+ FontAwesomeIcons.triangleExclamation,
+ color: AppColors.danger,
+ size: 32,
+ ),
+ const SizedBox(height: 12),
+ Text(
+ 'Không thể tải phương thức thanh toán',
+ style: const TextStyle(
+ fontSize: 14,
+ color: AppColors.grey500,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 8),
+ TextButton(
+ onPressed: () {
+ ref.invalidate(paymentTermsListProvider);
+ },
+ child: const Text('Thử lại'),
+ ),
+ ],
+ ),
+ ),
+ ),
if (!needsNegotiation.value)
const SizedBox(height: AppSpacing.md),
@@ -164,6 +240,7 @@ class CheckoutPage extends HookConsumerWidget {
// Price Negotiation Section
PriceNegotiationSection(needsNegotiation: needsNegotiation),
+
const SizedBox(height: AppSpacing.md),
// Terms and Conditions
@@ -200,6 +277,8 @@ class CheckoutPage extends HookConsumerWidget {
selectedAddress: selectedAddress.value,
paymentMethod: paymentMethod.value,
total: total,
+ cartItems: checkoutItems,
+ notes: notesController.text.trim().isEmpty ? null : notesController.text.trim(),
),
const SizedBox(height: AppSpacing.lg),
diff --git a/lib/features/cart/presentation/providers/cart_state.dart b/lib/features/cart/presentation/providers/cart_state.dart
index a49e858..74b6feb 100644
--- a/lib/features/cart/presentation/providers/cart_state.dart
+++ b/lib/features/cart/presentation/providers/cart_state.dart
@@ -8,11 +8,7 @@ import 'package:worker/features/products/domain/entities/product.dart';
/// Cart Item Data
///
/// Represents a product in the cart with quantity.
-class CartItemData {
- final Product product;
- final double quantity;
- final double quantityConverted; // Rounded-up quantity for actual billing
- final int boxes; // Number of tiles/boxes needed
+class CartItemData { // Number of tiles/boxes needed
const CartItemData({
required this.product,
@@ -20,6 +16,10 @@ class CartItemData {
required this.quantityConverted,
required this.boxes,
});
+ final Product product;
+ final double quantity;
+ final double quantityConverted; // Rounded-up quantity for actual billing
+ final int boxes;
/// Calculate line total using CONVERTED quantity (important for accurate billing)
double get lineTotal => product.basePrice * quantityConverted;
@@ -43,19 +43,6 @@ class CartItemData {
///
/// Represents the complete state of the shopping cart.
class CartState {
- final List items;
- final Map selectedItems; // productId -> isSelected
- final String selectedWarehouse;
- final String? discountCode;
- final bool discountCodeApplied;
- final String memberTier;
- final double memberDiscountPercent;
- final double subtotal;
- final double memberDiscount;
- final double shippingFee;
- final double total;
- final bool isLoading;
- final String? errorMessage;
const CartState({
required this.items,
@@ -88,6 +75,19 @@ class CartState {
total: 0.0,
);
}
+ final List items;
+ final Map selectedItems; // productId -> isSelected
+ final String selectedWarehouse;
+ final String? discountCode;
+ final bool discountCodeApplied;
+ final String memberTier;
+ final double memberDiscountPercent;
+ final double subtotal;
+ final double memberDiscount;
+ final double shippingFee;
+ final double total;
+ final bool isLoading;
+ final String? errorMessage;
bool get isEmpty => items.isEmpty;
bool get isNotEmpty => items.isNotEmpty;
diff --git a/lib/features/cart/presentation/widgets/checkout_submit_button.dart b/lib/features/cart/presentation/widgets/checkout_submit_button.dart
index 2f1181c..11236de 100644
--- a/lib/features/cart/presentation/widgets/checkout_submit_button.dart
+++ b/lib/features/cart/presentation/widgets/checkout_submit_button.dart
@@ -5,16 +5,18 @@ library;
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:worker/core/constants/ui_constants.dart';
import 'package:worker/core/router/app_router.dart';
import 'package:worker/core/theme/colors.dart';
import 'package:worker/features/account/domain/entities/address.dart';
+import 'package:worker/features/orders/presentation/providers/order_repository_provider.dart';
/// Checkout Submit Button
///
/// Button that changes based on negotiation checkbox state.
-class CheckoutSubmitButton extends StatelessWidget {
+class CheckoutSubmitButton extends HookConsumerWidget {
const CheckoutSubmitButton({
super.key,
required this.formKey,
@@ -23,6 +25,8 @@ class CheckoutSubmitButton extends StatelessWidget {
required this.selectedAddress,
required this.paymentMethod,
required this.total,
+ required this.cartItems,
+ this.notes,
});
final GlobalKey formKey;
@@ -31,9 +35,11 @@ class CheckoutSubmitButton extends StatelessWidget {
final Address? selectedAddress;
final String paymentMethod;
final double total;
+ final List