diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index aa8b4b4..2b8b611 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -33,7 +33,7 @@ class NotificationService $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND device_fingerprint = ? AND push_token IS NOT NULL"); $stmt->execute([$userId, $deviceId]); } else { - $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL AND is_active = 1"); + $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL"); $stmt->execute([$userId]); } @@ -74,7 +74,7 @@ class NotificationService $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND device_fingerprint = ? AND push_token IS NOT NULL"); $stmt->execute([$userId, $deviceId]); } else { - $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL AND is_active = 1"); + $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL"); $stmt->execute([$userId]); } @@ -130,6 +130,31 @@ class NotificationService ], ], ]; + } else { + // Silent push / Live Activity Update + $message['android'] = [ + 'priority' => 'high' + ]; + $message['apns'] = [ + 'headers' => [ + 'apns-priority' => '5', + 'apns-push-type' => 'background' + ], + 'payload' => [ + 'aps' => [ + 'content-available' => 1 + ] + ] + ]; + + // If the data contains live activity update markers, adjust headers for iOS ActivityKit + if (isset($data['type']) && $data['type'] === 'batch_progress') { + $message['apns']['headers']['apns-push-type'] = 'liveactivity'; + $message['apns']['headers']['apns-priority'] = '10'; + $message['apns']['payload']['aps']['content-state'] = $data; + $message['apns']['payload']['aps']['timestamp'] = time(); + $message['apns']['payload']['aps']['event'] = 'update'; + } } $payload = ['message' => $message]; diff --git a/app/cron/process_batches.php b/app/cron/process_batches.php index e51acfb..4f84cfe 100644 --- a/app/cron/process_batches.php +++ b/app/cron/process_batches.php @@ -126,6 +126,9 @@ try { // Update batch progress $db->prepare("UPDATE invoice_batches SET processed_images = processed_images + 1 WHERE id = ?")->execute([$batchId]); + // Increment Quota + QuotaMiddleware::incrementInvoiceUsage($tenantId); + $db->commit(); echo "Success: Created Invoice $invoiceId\n"; @@ -143,14 +146,17 @@ try { ]); } - // Increment Quota - QuotaMiddleware::incrementInvoiceUsage($tenantId); - - - } catch (Exception $e) { - $db->rollBack(); + } catch (\Exception $pushErr) { + echo "Push error: " . $pushErr->getMessage() . "\n"; + } + } catch (\Exception $e) { + if ($db->inTransaction()) { + $db->rollBack(); + } echo "DB Error: " . $e->getMessage() . "\n"; - $db->prepare("UPDATE invoice_processing_queue SET status = 'failed', error_message = ? WHERE id = ?")->execute([$e->getMessage(), $queueId]); + try { + $db->prepare("UPDATE invoice_processing_queue SET status = 'failed', error_message = ? WHERE id = ?")->execute([$e->getMessage(), $queueId]); + } catch (\Exception $e2) {} } // Check if batch is complete diff --git a/app/modules_app/batches/finalize.php b/app/modules_app/batches/finalize.php index ec75786..48faaa8 100644 --- a/app/modules_app/batches/finalize.php +++ b/app/modules_app/batches/finalize.php @@ -57,6 +57,12 @@ $stmt = $db->prepare(" "); $stmt->execute([$batchId]); +// 3. If it's a single invoice, try triggering the worker in the background immediately +// This helps if the Cron Job is delayed or failing. +$workerPath = ROOT_PATH . '/app/cron/process_batches.php'; +$logPath = STORAGE_PATH . '/logs/cron.log'; +exec("php " . escapeshellarg($workerPath) . " >> " . escapeshellarg($logPath) . " 2>&1 &"); + json_success([ 'batch_id' => $batchId, 'status' => 'processing', diff --git a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart index 49ae659..03abf03 100644 --- a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart +++ b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart @@ -447,7 +447,8 @@ class DashboardView extends GetView { InkWell( onTap: () => _showBadgesDialog(gamification), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(20), @@ -457,11 +458,13 @@ class DashboardView extends GetView { children: [ const Text( 'المكافآت ', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), ), Text( '$badgesCount/$availableBadges', - style: const TextStyle(color: Colors.white70, fontSize: 12), + style: const TextStyle( + color: Colors.white70, fontSize: 12), ), ], ), @@ -491,23 +494,29 @@ class DashboardView extends GetView { void _showBadgesDialog(Map gamification) { final badges = gamification['badges'] as List? ?? []; - + Get.dialog( AlertDialog( - title: const Text('شاراتك ومكافآتك', textAlign: TextAlign.center, style: TextStyle(color: Color(0xFF0F4C81))), + title: const Text('شاراتك ومكافآتك', + textAlign: TextAlign.center, + style: TextStyle(color: Color(0xFF0F4C81))), content: SizedBox( width: double.maxFinite, child: badges.isEmpty - ? const Text('لم تحصل على أي شارات بعد. قم برفع الفواتير لتبدأ!', textAlign: TextAlign.center) + ? const Text('لم تحصل على أي شارات بعد. قم برفع الفواتير لتبدأ!', + textAlign: TextAlign.center) : ListView.builder( shrinkWrap: true, itemCount: badges.length, itemBuilder: (context, index) { final b = badges[index]; return ListTile( - leading: Text(b['badge_icon'] ?? '🌟', style: const TextStyle(fontSize: 24)), - title: Text(b['badge_name'] ?? '', style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text('تم الحصول عليها: ${(b['earned_at'] ?? '').toString().split(' ')[0]}'), + leading: Text(b['badge_icon'] ?? '🌟', + style: const TextStyle(fontSize: 24)), + title: Text(b['badge_name'] ?? '', + style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text( + 'تم الحصول عليها: ${(b['earned_at'] ?? '').toString().split(' ')[0]}'), ); }, ),