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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user