import 'package:odoo_rpc/odoo_rpc.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 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> getLoyaltyCards(int partnerId) async { if (client == null) throw Exception("Connect to Odoo first"); // Only fetch cards from 'loyalty' type programs (multi-tier: Silver/Gold/Platinum). // Excludes subscriptions, coupons, gift cards, promotions, eWallets, etc. return await client!.callKw({ 'model': 'loyalty.card', 'method': 'search_read', 'args': [ [ ['partner_id', '=', partnerId], ['program_id.program_type', '=', 'loyalty'], ], ], 'kwargs': {'fields': ['points', 'program_id', 'code']} }) as List; } /// Fetch subscription cards only — displayed as a "My Subscriptions" list. Future> getSubscriptionCards(int partnerId) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callKw({ 'model': 'loyalty.card', 'method': 'search_read', 'args': [ [ ['partner_id', '=', partnerId], ['program_id.program_type', '=', 'subscription'], ], ], 'kwargs': { 'fields': [ 'program_id', 'code', 'subscription_start_date', 'subscription_end_date', ] } }) as List; } /// Fetch carousel slides and promo highlights for the home screen. Future> getCmsContent() async { if (client == null) throw Exception("Connect to Odoo first"); final response = await client!.callRPC( '/api/loyalty/cms_content', 'call', {}, ); if (response != null && response['status'] == 'success') { return response as Map; } return {'carousel': [], 'promos': []}; } /// Fetch app configuration (About Us URL, Contact Us URL, Branding & Theme). static Future> getAppConfig() async { final tempClient = OdooClient(AppConfig.odooUrl); try { final res = await tempClient.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', }; // Save and apply new branding and theme colors dynamically await ThemeManager.instance.updateConfig( primaryHex: configMap['primary_color']!, secondaryHex: configMap['secondary_color']!, brandLogoB64: configMap['brand_logo']!, ); return configMap; } return {'about_us_url': '', 'contact_us_url': ''}; } catch (_) { return {'about_us_url': '', 'contact_us_url': ''}; } finally { tempClient.close(); } } /// Fetch loyalty point history for the current user. Future> getOrderHistory() async { if (client == null) throw Exception("Connect to Odoo first"); final response = await client!.callRPC( '/api/loyalty/order_history', 'call', {}, ); if (response != null && response['status'] == 'success') { return response['data'] as List; } return []; } Future sendOtp({ String? email, String? phone, String? phoneOrEmail, required String type, }) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callRPC( '/api/loyalty/send_otp', 'call', { if (email != null) 'email': email, if (phone != null) 'phone': phone, if (phoneOrEmail != null) 'phone_or_email': phoneOrEmail, 'type': type, }, ); } Future signUpMember({ required String name, required String phone, required String email, required String birthDate, required String gender, required String password, required String otp, }) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callRPC( '/api/loyalty/signup_member', 'call', { 'name': name, 'phone': phone, 'email': email, 'birth_date': birthDate, 'gender': gender, 'password': password, 'otp': otp, }, ); } Future activateAccount({ required String phone, required String email, required String birthDate, required String password, required String otp, }) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callRPC( '/api/loyalty/activate_account', 'call', { 'phone': phone, 'email': email, 'birth_date': birthDate, 'password': password, 'otp': otp, }, ); } Future resetPassword({ required String phoneOrEmail, required String otp, required String password, }) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callRPC( '/api/loyalty/reset_password', 'call', { 'phone_or_email': phoneOrEmail, 'otp': otp, 'password': password, }, ); } Future deleteAccount(String password) async { if (client == null) throw Exception("Connect to Odoo first"); return await client!.callRPC( '/api/loyalty/delete_account', 'call', { 'password': password, }, ); } /// Fetch public branch information (includes lat/lng for geolocation sorting). static Future> getPublicBranches() async { final tempClient = OdooClient(AppConfig.odooUrl); try { final res = await tempClient.callRPC( '/api/loyalty/branches', 'call', {} ); if (res != null && res['status'] == 'success') { return res['data'] as List; } else { throw Exception(res?['message'] ?? 'Failed to load branches'); } } catch (e) { rethrow; } finally { tempClient.close(); } } }