307 lines
9.0 KiB
Dart
307 lines
9.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:odoo_rpc/odoo_rpc.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../main.dart';
|
|
import '../screens/login_screen.dart';
|
|
import 'config.dart';
|
|
import 'theme_manager.dart';
|
|
|
|
class OdooService {
|
|
static final OdooService _instance = OdooService._internal();
|
|
factory OdooService() => _instance;
|
|
OdooService._internal();
|
|
|
|
OdooClient? client;
|
|
|
|
void connect(String url, {OdooSession? session}) {
|
|
client = OdooClient(url, sessionId: session);
|
|
}
|
|
|
|
/// Returns the session cookie header value for authenticated image loading.
|
|
String get sessionCookie {
|
|
final sessionId = client?.sessionId?.id ?? '';
|
|
return 'session_id=$sessionId';
|
|
}
|
|
|
|
/// Returns the URL for the full notification image.
|
|
String notificationImageUrl(int notifId) =>
|
|
'${AppConfig.odooUrl}/web/image/mapan.app.notification/$notifId/image';
|
|
|
|
/// Returns the URL for a carousel image (uploaded).
|
|
String carouselImageUrl(int slideId) =>
|
|
'${AppConfig.odooUrl}/web/image/mapan.app.carousel/$slideId/image';
|
|
|
|
/// Returns the URL for a promo image (uploaded).
|
|
String promoImageUrl(int promoId) =>
|
|
'${AppConfig.odooUrl}/web/image/mapan.app.promo/$promoId/image';
|
|
|
|
Future<void> _handleSessionExpired() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove('odoo_session');
|
|
final state = navigatorKey.currentState;
|
|
if (state != null) {
|
|
state.pushAndRemoveUntil(
|
|
MaterialPageRoute(
|
|
builder: (_) => const LoginScreen(sessionExpired: true),
|
|
),
|
|
(route) => false,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<T> _performWithSessionCheck<T>(Future<T> Function() operation) async {
|
|
try {
|
|
return await operation();
|
|
} on OdooSessionExpiredException {
|
|
await _handleSessionExpired();
|
|
rethrow;
|
|
} catch (e) {
|
|
if (e.toString().toLowerCase().contains('session expired') ||
|
|
e.toString().toLowerCase().contains('session_expired')) {
|
|
await _handleSessionExpired();
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<dynamic> callKw(Map<String, dynamic> params) async {
|
|
if (client == null) throw Exception("Connect to Odoo first");
|
|
return _performWithSessionCheck(() => client!.callKw(params));
|
|
}
|
|
|
|
Future<dynamic> callRPC(String path, String method, dynamic params) async {
|
|
if (client == null) throw Exception("Connect to Odoo first");
|
|
return _performWithSessionCheck(() => client!.callRPC(path, method, params));
|
|
}
|
|
|
|
Future<OdooSession> login(String db, String username, String password) async {
|
|
if (client == null) throw Exception("Connect to Odoo first");
|
|
return await client!.authenticate(db, username, password);
|
|
}
|
|
|
|
Future<List<dynamic>> getLoyaltyCards(int partnerId) async {
|
|
// Only fetch cards from 'loyalty' type programs (multi-tier: Silver/Gold/Platinum).
|
|
// Excludes subscriptions, coupons, gift cards, promotions, eWallets, etc.
|
|
return await callKw({
|
|
'model': 'loyalty.card',
|
|
'method': 'search_read',
|
|
'args': [
|
|
[
|
|
['partner_id', '=', partnerId],
|
|
['program_id.program_type', '=', 'loyalty'],
|
|
['program_id.active', '=', true],
|
|
],
|
|
],
|
|
'kwargs': {
|
|
'fields': [
|
|
'points',
|
|
'program_id',
|
|
'code',
|
|
'subscription_start_date',
|
|
'subscription_end_date',
|
|
]
|
|
}
|
|
}) as List<dynamic>;
|
|
}
|
|
|
|
/// Fetch subscription cards only — displayed as a "My Subscriptions" list.
|
|
Future<List<dynamic>> getSubscriptionCards(int partnerId) async {
|
|
return await callKw({
|
|
'model': 'loyalty.card',
|
|
'method': 'search_read',
|
|
'args': [
|
|
[
|
|
['partner_id', '=', partnerId],
|
|
['program_id.program_type', '=', 'subscription'],
|
|
['program_id.active', '=', true],
|
|
],
|
|
],
|
|
'kwargs': {
|
|
'fields': [
|
|
'points',
|
|
'program_id',
|
|
'code',
|
|
'subscription_start_date',
|
|
'subscription_end_date',
|
|
]
|
|
}
|
|
}) as List<dynamic>;
|
|
}
|
|
|
|
/// Fetch carousel slides and promo highlights for the home screen.
|
|
Future<Map<String, dynamic>> getCmsContent() async {
|
|
final response = await callRPC(
|
|
'/api/loyalty/cms_content',
|
|
'call',
|
|
{},
|
|
);
|
|
if (response != null && response['status'] == 'success') {
|
|
return response as Map<String, dynamic>;
|
|
}
|
|
return {'carousel': [], 'promos': []};
|
|
}
|
|
|
|
/// Fetch app configuration (About Us URL, Contact Us URL, Branding & Theme).
|
|
static Future<Map<String, String>> getAppConfig() async {
|
|
final activeClient = OdooService().client;
|
|
final clientToUse = activeClient ?? OdooClient(AppConfig.odooUrl);
|
|
try {
|
|
final res = await clientToUse.callRPC('/api/loyalty/app_config', 'call', {});
|
|
if (res != null && res['status'] == 'success') {
|
|
final configMap = {
|
|
'about_us_url': (res['about_us_url'] as String?) ?? '',
|
|
'contact_us_url': (res['contact_us_url'] as String?) ?? '',
|
|
'brand_logo': (res['brand_logo'] as String?) ?? '',
|
|
'primary_color': (res['primary_color'] as String?) ?? '#C62828',
|
|
'secondary_color': (res['secondary_color'] as String?) ?? '#FF8F00',
|
|
'tertiary_color': (res['tertiary_color'] as String?) ?? '#4A7C59',
|
|
'background_color': (res['background_color'] as String?) ?? '#FAF6EE',
|
|
'background_gradient_color': (res['background_gradient_color'] as String?) ?? '#F3EAD3',
|
|
};
|
|
// Save and apply new branding and theme colors dynamically
|
|
await ThemeManager.instance.updateConfig(
|
|
primaryHex: configMap['primary_color']!,
|
|
secondaryHex: configMap['secondary_color']!,
|
|
tertiaryHex: configMap['tertiary_color']!,
|
|
backgroundHex: configMap['background_color']!,
|
|
backgroundGradientHex: configMap['background_gradient_color']!,
|
|
brandLogoB64: configMap['brand_logo']!,
|
|
);
|
|
return configMap;
|
|
}
|
|
return {'about_us_url': '', 'contact_us_url': ''};
|
|
} catch (_) {
|
|
return {'about_us_url': '', 'contact_us_url': ''};
|
|
} finally {
|
|
if (activeClient == null) {
|
|
clientToUse.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Fetch loyalty point history for the current user.
|
|
Future<List<dynamic>> getOrderHistory() async {
|
|
final response = await callRPC(
|
|
'/api/loyalty/order_history',
|
|
'call',
|
|
{},
|
|
);
|
|
if (response != null && response['status'] == 'success') {
|
|
return response['data'] as List<dynamic>;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
Future<dynamic> sendOtp({
|
|
String? email,
|
|
String? phone,
|
|
String? phoneOrEmail,
|
|
required String type,
|
|
}) async {
|
|
return await callRPC(
|
|
'/api/loyalty/send_otp',
|
|
'call',
|
|
{
|
|
'email': ?email,
|
|
'phone': ?phone,
|
|
'phone_or_email': ?phoneOrEmail,
|
|
'type': type,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<dynamic> signUpMember({
|
|
required String name,
|
|
required String phone,
|
|
required String email,
|
|
required String birthDate,
|
|
required String gender,
|
|
required String password,
|
|
required String otp,
|
|
}) async {
|
|
return await callRPC(
|
|
'/api/loyalty/signup_member',
|
|
'call',
|
|
{
|
|
'name': name,
|
|
'phone': phone,
|
|
'email': email,
|
|
'birth_date': birthDate,
|
|
'gender': gender,
|
|
'password': password,
|
|
'otp': otp,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<dynamic> activateAccount({
|
|
required String phone,
|
|
required String email,
|
|
required String birthDate,
|
|
required String password,
|
|
required String otp,
|
|
}) async {
|
|
return await callRPC(
|
|
'/api/loyalty/activate_account',
|
|
'call',
|
|
{
|
|
'phone': phone,
|
|
'email': email,
|
|
'birth_date': birthDate,
|
|
'password': password,
|
|
'otp': otp,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<dynamic> resetPassword({
|
|
required String phoneOrEmail,
|
|
required String otp,
|
|
required String password,
|
|
}) async {
|
|
return await callRPC(
|
|
'/api/loyalty/reset_password',
|
|
'call',
|
|
{
|
|
'phone_or_email': phoneOrEmail,
|
|
'otp': otp,
|
|
'password': password,
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<dynamic> deleteAccount(String password) async {
|
|
return await callRPC(
|
|
'/api/loyalty/delete_account',
|
|
'call',
|
|
{
|
|
'password': password,
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Fetch public branch information (includes lat/lng for geolocation sorting).
|
|
static Future<List<dynamic>> getPublicBranches() async {
|
|
final activeClient = OdooService().client;
|
|
final clientToUse = activeClient ?? OdooClient(AppConfig.odooUrl);
|
|
try {
|
|
final res = await clientToUse.callRPC(
|
|
'/api/loyalty/branches',
|
|
'call',
|
|
{}
|
|
);
|
|
if (res != null && res['status'] == 'success') {
|
|
return res['data'] as List<dynamic>;
|
|
} else {
|
|
throw Exception(res?['message'] ?? 'Failed to load branches');
|
|
}
|
|
} catch (e) {
|
|
rethrow;
|
|
} finally {
|
|
if (activeClient == null) {
|
|
clientToUse.close();
|
|
}
|
|
}
|
|
}
|
|
}
|