'skipped', 'reason' => 'Not enabled or missing settings']); exit; } $timezone = !empty($settings['timezone']) ? $settings['timezone'] : 'UTC'; date_default_timezone_set($timezone); $reportTime = $settings['whatsapp_report_time']; // e.g. "23:59:00" or "23:59" $lastReportFile = __DIR__ . '/../storage/last_daily_report.txt'; if (!is_dir(dirname($lastReportFile))) { mkdir(dirname($lastReportFile), 0775, true); } $lastReportDate = file_exists($lastReportFile) ? trim(file_get_contents($lastReportFile)) : ''; $nowDt = new DateTime('now', new DateTimeZone($timezone)); $todayDateStr = $nowDt->format('Y-m-d'); $targetTodayDt = clone $nowDt; $timeParts = explode(':', $reportTime); // Ignore seconds from the configuration, start matching exactly at the given minute $targetTodayDt->setTime((int)$timeParts[0], (int)($timeParts[1] ?? 0), 0); $targetYesterdayDt = clone $targetTodayDt; $targetYesterdayDt->modify('-1 day'); $yesterdayDateStr = $targetYesterdayDt->format('Y-m-d'); // Calculate time difference in seconds between now and the target scheduled times $diffToday = $nowDt->getTimestamp() - $targetTodayDt->getTimestamp(); $diffYesterday = $nowDt->getTimestamp() - $targetYesterdayDt->getTimestamp(); $reportDateToRun = null; // We allow a strict 15-minute window (900 seconds) to catch delayed cron executions. // If the diff is negative, the target time hasn't happened yet. // If the diff is > 900, we missed it by a lot (e.g. system was deployed or turned on hours later), // so we simply ignore it and wait for the NEXT proper scheduled time. if ($diffToday >= 0 && $diffToday <= 900 && $lastReportDate !== $todayDateStr) { $reportDateToRun = $todayDateStr; } elseif ($diffYesterday >= 0 && $diffYesterday <= 900 && $lastReportDate !== $yesterdayDateStr && $lastReportDate !== $todayDateStr) { $reportDateToRun = $yesterdayDateStr; } // Optional: write a small heartbeat log so user/admin knows cron is checking correctly $hb = "Timezone: $timezone | Now: " . $nowDt->format('Y-m-d H:i:s') . " | TargetToday: " . $targetTodayDt->format('Y-m-d H:i:s') . " | TargetYest: " . $targetYesterdayDt->format('Y-m-d H:i:s') . " | DiffToday: $diffToday | DiffYest: $diffYesterday | LastSent: $lastReportDate\n"; file_put_contents(__DIR__ . '/../storage/cron_heartbeat.log', $hb); if (!$reportDateToRun) { echo json_encode(['status' => 'skipped', 'reason' => 'No report due within the allowed window or already sent', 'now' => $nowDt->format('Y-m-d H:i:s')]); exit; } $currentDate = $reportDateToRun; // GENERATE REPORT $pdo = db(); // Get all non-cancelled orders for the report date $stmt = $pdo->prepare( "SELECT o.id, o.total_amount as total, o.payment_type_id, o.user_id, o.outlet_id, pt.name as payment_type_name, u.username, u.full_name, outl.name as outlet_name FROM orders o LEFT JOIN payment_types pt ON o.payment_type_id = pt.id LEFT JOIN users u ON o.user_id = u.id LEFT JOIN outlets outl ON o.outlet_id = outl.id WHERE DATE(o.created_at) = ? AND o.status != 'cancelled'" ); $stmt->execute([$currentDate]); $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); $totalOrders = count($orders); $totalAmount = 0; $outletsData = []; $stmtOut = $pdo->query("SELECT name FROM outlets WHERE is_deleted = 0"); $allOutlets = $stmtOut->fetchAll(PDO::FETCH_ASSOC); foreach ($allOutlets as $o) { $outletsData[$o["name"]] = [ "total" => 0, "staff" => [], "payments" => [] ]; } foreach ($orders as $order) { $totalAmount += $order['total']; $outletName = $order['outlet_name'] ?? 'Unknown Outlet'; $staffName = !empty($order['full_name']) ? $order['full_name'] : ($order['username'] ?? 'Unknown Staff'); $paymentType = $order['payment_type_name'] ?? 'Unknown Payment'; $amount = (float)$order['total']; if (!isset($outletsData[$outletName])) { $outletsData[$outletName] = [ 'total' => 0, 'staff' => [], 'payments' => [] ]; } $outletsData[$outletName]['total'] += $amount; if (!isset($outletsData[$outletName]['staff'][$staffName])) { $outletsData[$outletName]['staff'][$staffName] = 0; } $outletsData[$outletName]['staff'][$staffName] += $amount; if (!isset($outletsData[$outletName]['payments'][$paymentType])) { $outletsData[$outletName]['payments'][$paymentType] = 0; } $outletsData[$outletName]['payments'][$paymentType] += $amount; } $companyName = $settings['company_name'] ?? 'Restaurant'; $currency = $settings['currency_symbol'] ?? '$'; $message = "📊 *Daily Summary Report* 📊\n"; $message .= "🏢 *" . $companyName . "*\n"; $message .= "📅 Date: " . $currentDate . "\n"; $message .= "--------------------------------\n"; $message .= "🛒 Total Orders: " . $totalOrders . "\n"; $message .= "💰 Total Revenue: " . format_currency($totalAmount) . "\n\n"; if (empty($outletsData)) { $message .= "No active branches found.\n"; } else { foreach ($outletsData as $outletName => $data) { $message .= "🏪 *" . $outletName . "*\n"; $message .= " Total: " . format_currency($data['total']) . "\n"; $message .= " 🧑‍🍳 *Staff Breakdown:* "; if (empty($data['staff'])) { $message .= " - No staff sales\n"; } else { foreach ($data['staff'] as $staff => $amt) { $message .= " - " . $staff . ": " . format_currency($amt) . "\n"; } } $message .= " 💳 *Payment Breakdown:* "; if (empty($data['payments'])) { $message .= " - No payments\n"; } else { foreach ($data['payments'] as $pt => $amt) { $message .= " - " . $pt . ": " . format_currency($amt) . "\n"; } } $message .= "\n"; } } $message .= "--------------------------------\n"; $message .= "Generated automatically at " . $nowDt->format('H:i:s'); // Write immediately to prevent duplicate triggers file_put_contents($lastReportFile, $currentDate); // Send via Wablas $wablasService = new WablasService($pdo); $numbers = array_map('trim', explode(',', $settings['whatsapp_report_number'])); $successCount = 0; $errors = []; foreach ($numbers as $number) { if (!empty($number)) { $result = $wablasService->sendMessage($number, $message); if ($result['success']) { $successCount++; } else { $errors[] = $number . ': ' . $result['message']; } } } if ($successCount > 0) { echo json_encode(['status' => 'success', 'message' => 'Report sent to ' . $successCount . ' number(s)']); } else { // Revert on failure so it tries again next minute if (file_exists($lastReportFile)) { unlink($lastReportFile); } echo json_encode(['status' => 'error', 'message' => implode(', ', $errors)]); } } catch (Exception $e) { error_log("Daily Report Cron Error: " . $e->getMessage()); echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); }