38682-vm/api/daily_report_cron.php
2026-03-20 09:48:45 +00:00

210 lines
8.2 KiB
PHP

<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/WablasService.php';
session_write_close();
header("Content-Type: application/json");
// Enable error logging for cron
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/../storage/cron_errors.log');
try {
$settings = get_company_settings();
if (empty($settings['whatsapp_report_enabled']) || empty($settings['whatsapp_report_number']) || empty($settings['whatsapp_report_time'])) {
echo json_encode(['status' => '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 IN ('pending', 'preparing', 'ready', 'completed')"
);
$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()]);
}