update address
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
/// Address Remote Data Source
|
||||
///
|
||||
/// Handles API calls to Frappe ERPNext address endpoints.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/features/account/data/models/address_model.dart';
|
||||
|
||||
/// Address Remote Data Source
|
||||
///
|
||||
/// Provides methods to interact with address API endpoints.
|
||||
/// Online-only approach - no offline caching.
|
||||
class AddressRemoteDataSource {
|
||||
final Dio _dio;
|
||||
|
||||
AddressRemoteDataSource(this._dio);
|
||||
|
||||
/// Get list of addresses
|
||||
///
|
||||
/// Fetches all addresses for the authenticated user.
|
||||
/// Optionally filter by default address.
|
||||
///
|
||||
/// API: GET /api/method/building_material.building_material.api.address.get_list
|
||||
Future<List<AddressModel>> getAddresses({
|
||||
int limitStart = 0,
|
||||
int limitPageLength = 0,
|
||||
bool? isDefault,
|
||||
}) async {
|
||||
try {
|
||||
_debugPrint('Fetching addresses list...');
|
||||
|
||||
final response = await _dio.post(
|
||||
'/api/method/building_material.building_material.api.address.get_list',
|
||||
data: {
|
||||
'limit_start': limitStart,
|
||||
'limit_page_length': limitPageLength,
|
||||
if (isDefault != null) 'is_default': isDefault,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
_debugPrint('Response data: $data');
|
||||
|
||||
// Extract addresses from response
|
||||
if (data is Map<String, dynamic> && data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
_debugPrint('Message type: ${message.runtimeType}');
|
||||
|
||||
// Handle array response
|
||||
if (message is List) {
|
||||
_debugPrint('Parsing ${message.length} addresses from list');
|
||||
final addresses = <AddressModel>[];
|
||||
for (var i = 0; i < message.length; i++) {
|
||||
try {
|
||||
final item = message[i] as Map<String, dynamic>;
|
||||
_debugPrint('Parsing address $i: $item');
|
||||
final address = AddressModel.fromJson(item);
|
||||
addresses.add(address);
|
||||
} catch (e) {
|
||||
_debugPrint('Error parsing address $i: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
_debugPrint('Fetched ${addresses.length} addresses');
|
||||
return addresses;
|
||||
}
|
||||
|
||||
// Handle object with data field
|
||||
if (message is Map<String, dynamic> && message.containsKey('data')) {
|
||||
final dataList = message['data'] as List;
|
||||
_debugPrint('Parsing ${dataList.length} addresses from data field');
|
||||
final addresses = <AddressModel>[];
|
||||
for (var i = 0; i < dataList.length; i++) {
|
||||
try {
|
||||
final item = dataList[i] as Map<String, dynamic>;
|
||||
_debugPrint('Parsing address $i: $item');
|
||||
final address = AddressModel.fromJson(item);
|
||||
addresses.add(address);
|
||||
} catch (e) {
|
||||
_debugPrint('Error parsing address $i: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
_debugPrint('Fetched ${addresses.length} addresses');
|
||||
return addresses;
|
||||
}
|
||||
}
|
||||
|
||||
throw const ServerException('Invalid response format');
|
||||
} else {
|
||||
throw ServerException(
|
||||
'Failed to fetch addresses: ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error fetching addresses: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create or update address
|
||||
///
|
||||
/// If name is provided (not empty), updates existing address.
|
||||
/// If name is null/empty, creates new address.
|
||||
///
|
||||
/// Per API docs: When name field is null/empty, the API creates a new address.
|
||||
/// When name has a value, the API updates the existing address.
|
||||
///
|
||||
/// API: POST /api/method/building_material.building_material.api.address.update
|
||||
Future<AddressModel> saveAddress(AddressModel address) async {
|
||||
try {
|
||||
final isUpdate = address.name.isNotEmpty;
|
||||
_debugPrint(
|
||||
isUpdate
|
||||
? 'Updating address: ${address.name}'
|
||||
: 'Creating new address',
|
||||
);
|
||||
|
||||
// toJson() already handles setting name to null for creation
|
||||
final data = address.toJson();
|
||||
_debugPrint('Request data: $data');
|
||||
|
||||
final response = await _dio.post(
|
||||
'/api/method/building_material.building_material.api.address.update',
|
||||
data: data,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
_debugPrint('Response data: $data');
|
||||
|
||||
// Check for API error response (even with 200 status)
|
||||
if (data is Map<String, dynamic> && data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
|
||||
// Check for error response format
|
||||
if (message is Map<String, dynamic> && message.containsKey('error')) {
|
||||
final error = message['error'] as String;
|
||||
_debugPrint('API error: $error');
|
||||
throw ServerException(error);
|
||||
}
|
||||
|
||||
// Handle direct address object
|
||||
if (message is Map<String, dynamic>) {
|
||||
final savedAddress = AddressModel.fromJson(message);
|
||||
_debugPrint('Address saved: ${savedAddress.name}');
|
||||
return savedAddress;
|
||||
}
|
||||
|
||||
// Handle nested data
|
||||
if (message is Map<String, dynamic> && message.containsKey('data')) {
|
||||
final savedAddress =
|
||||
AddressModel.fromJson(message['data'] as Map<String, dynamic>);
|
||||
_debugPrint('Address saved: ${savedAddress.name}');
|
||||
return savedAddress;
|
||||
}
|
||||
}
|
||||
|
||||
throw const ServerException('Invalid response format');
|
||||
} else {
|
||||
throw ServerException(
|
||||
'Failed to save address: ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error saving address: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete address
|
||||
///
|
||||
/// Note: API endpoint for delete not provided in docs.
|
||||
/// This is a placeholder - adjust when endpoint is available.
|
||||
Future<void> deleteAddress(String name) async {
|
||||
try {
|
||||
_debugPrint('Deleting address: $name');
|
||||
|
||||
// TODO: Update with actual delete endpoint when available
|
||||
final response = await _dio.post(
|
||||
'/api/method/building_material.building_material.api.address.delete',
|
||||
data: {'name': name},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_debugPrint('Address deleted: $name');
|
||||
return;
|
||||
} else {
|
||||
throw ServerException(
|
||||
'Failed to delete address: ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('Error deleting address: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug print helper
|
||||
void _debugPrint(String message) {
|
||||
// ignore: avoid_print
|
||||
print('[AddressRemoteDataSource] $message');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/// Location Local Data Source
|
||||
///
|
||||
/// Handles Hive caching for cities and wards.
|
||||
library;
|
||||
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/core/database/hive_service.dart';
|
||||
import 'package:worker/features/account/data/models/city_model.dart';
|
||||
import 'package:worker/features/account/data/models/ward_model.dart';
|
||||
|
||||
/// Location Local Data Source
|
||||
///
|
||||
/// Provides offline-first caching for cities and wards using Hive.
|
||||
class LocationLocalDataSource {
|
||||
final HiveService _hiveService;
|
||||
|
||||
LocationLocalDataSource(this._hiveService);
|
||||
|
||||
// ============================================================================
|
||||
// CITIES
|
||||
// ============================================================================
|
||||
|
||||
/// Get city box
|
||||
Box<dynamic> get _cityBox => _hiveService.getBox(HiveBoxNames.cityBox);
|
||||
|
||||
/// Get all cached cities
|
||||
List<CityModel> getCities() {
|
||||
try {
|
||||
final cities = _cityBox.values.whereType<CityModel>().toList();
|
||||
return cities;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Save cities to cache
|
||||
Future<void> saveCities(List<CityModel> cities) async {
|
||||
try {
|
||||
// Only clear if there are existing cities
|
||||
if (_cityBox.isNotEmpty) {
|
||||
await _cityBox.clear();
|
||||
}
|
||||
|
||||
for (final city in cities) {
|
||||
await _cityBox.put(city.code, city);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get city by code
|
||||
CityModel? getCityByCode(String code) {
|
||||
try {
|
||||
return _cityBox.get(code) as CityModel?;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if cities are cached
|
||||
bool hasCities() {
|
||||
return _cityBox.isNotEmpty;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WARDS
|
||||
// ============================================================================
|
||||
|
||||
/// Get ward box
|
||||
Box<dynamic> get _wardBox => _hiveService.getBox(HiveBoxNames.wardBox);
|
||||
|
||||
/// Get cached wards for a city
|
||||
///
|
||||
/// Wards are stored with key: "cityCode_wardCode"
|
||||
List<WardModel> getWards(String cityCode) {
|
||||
try {
|
||||
final wards = _wardBox.values
|
||||
.whereType<WardModel>()
|
||||
.where((ward) {
|
||||
// Check if this ward belongs to the city
|
||||
final key = '${cityCode}_${ward.code}';
|
||||
return _wardBox.containsKey(key);
|
||||
})
|
||||
.toList();
|
||||
|
||||
return wards;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Save wards for a specific city to cache
|
||||
Future<void> saveWards(String cityCode, List<WardModel> wards) async {
|
||||
try {
|
||||
// Remove old wards for this city (only if they exist)
|
||||
final keysToDelete = _wardBox.keys
|
||||
.where((key) => key.toString().startsWith('${cityCode}_'))
|
||||
.toList();
|
||||
|
||||
if (keysToDelete.isNotEmpty) {
|
||||
for (final key in keysToDelete) {
|
||||
await _wardBox.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Save new wards
|
||||
for (final ward in wards) {
|
||||
final key = '${cityCode}_${ward.code}';
|
||||
await _wardBox.put(key, ward);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if wards are cached for a city
|
||||
bool hasWards(String cityCode) {
|
||||
return _wardBox.keys.any((key) => key.toString().startsWith('${cityCode}_'));
|
||||
}
|
||||
|
||||
/// Clear all cached data
|
||||
Future<void> clearAll() async {
|
||||
try {
|
||||
// Only clear if boxes are not empty
|
||||
if (_cityBox.isNotEmpty) {
|
||||
await _cityBox.clear();
|
||||
}
|
||||
if (_wardBox.isNotEmpty) {
|
||||
await _wardBox.clear();
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/// Location Remote Data Source
|
||||
///
|
||||
/// Handles API calls for cities and wards using Frappe ERPNext client.get_list.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/features/account/data/models/city_model.dart';
|
||||
import 'package:worker/features/account/data/models/ward_model.dart';
|
||||
|
||||
/// Location Remote Data Source
|
||||
///
|
||||
/// Provides methods to fetch cities and wards from API.
|
||||
class LocationRemoteDataSource {
|
||||
final Dio _dio;
|
||||
|
||||
LocationRemoteDataSource(this._dio);
|
||||
|
||||
/// Get all cities
|
||||
///
|
||||
/// API: POST /api/method/frappe.client.get_list
|
||||
Future<List<CityModel>> getCities() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/api/method/frappe.client.get_list',
|
||||
data: {
|
||||
'doctype': 'City',
|
||||
'fields': ['city_name', 'name', 'code'],
|
||||
'limit_page_length': 0,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
|
||||
if (data is Map<String, dynamic> && data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
|
||||
if (message is List) {
|
||||
final cities = message
|
||||
.map((item) => CityModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
return cities;
|
||||
}
|
||||
}
|
||||
|
||||
throw const ServerException('Invalid response format');
|
||||
} else {
|
||||
throw ServerException('Failed to fetch cities: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get wards for a specific city
|
||||
///
|
||||
/// API: POST /api/method/frappe.client.get_list
|
||||
/// [cityCode] - The city code to filter wards
|
||||
Future<List<WardModel>> getWards(String cityCode) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
'/api/method/frappe.client.get_list',
|
||||
data: {
|
||||
'doctype': 'Ward',
|
||||
'fields': ['ward_name', 'name', 'code'],
|
||||
'filters': {'city': cityCode},
|
||||
'limit_page_length': 0,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
|
||||
if (data is Map<String, dynamic> && data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
|
||||
if (message is List) {
|
||||
final wards = message
|
||||
.map((item) => WardModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
return wards;
|
||||
}
|
||||
}
|
||||
|
||||
throw const ServerException('Invalid response format');
|
||||
} else {
|
||||
throw ServerException('Failed to fetch wards: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user