Autosave: 20260421-040526

This commit is contained in:
Flatlogic Bot 2026-04-21 04:05:16 +00:00
parent f6d94ddca5
commit c8919d7836
7 changed files with 898 additions and 102 deletions

View File

@ -15,6 +15,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'timezone', 'company_name_ar', 'company_name_en', 'vat_percentage',
'company_vat_number', 'company_phone', 'company_email', 'company_address',
'wablas_enabled', 'wablas_token', 'wablas_secret_key',
'wablas_invoice_recipients', 'wablas_report_recipients',
'wablas_daily_auto_send', 'wablas_daily_auto_time', 'wablas_daily_auto_last_date',
'wablas_template_invoice', 'wablas_template_daily_report',
'wablas_template_created', 'wablas_template_pending', 'wablas_template_accepted', 'wablas_template_completed', 'wablas_template_rejected',
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_secure', 'mail_from', 'mail_from_name'
];
@ -33,6 +36,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_POST['company_phone'] = $companyPhone;
}
foreach (['wablas_invoice_recipients', 'wablas_report_recipients'] as $phoneListKey) {
$parsed = wablas_parse_phone_list((string) ($_POST[$phoneListKey] ?? ''));
if (!empty($parsed['invalid'])) {
set_flash('danger', tr('يوجد رقم واتساب غير صالح في الحقل.', 'There is an invalid WhatsApp number in the field.') . ' ' . implode(', ', $parsed['invalid']));
$referer = $_SERVER['HTTP_REFERER'] ?? '../index.php';
header('Location: ' . $referer);
exit;
}
$_POST[$phoneListKey] = implode(',', $parsed['phones']);
}
$_POST['wablas_daily_auto_time'] = wablas_format_time_setting((string) ($_POST['wablas_daily_auto_time'] ?? '21:00'));
if (!isset($_POST['wablas_daily_auto_send'])) {
$_POST['wablas_daily_auto_send'] = '0';
}
unset($_POST['wablas_daily_auto_last_date']);
foreach ($keys as $key) {
if (isset($_POST[$key])) {
$value = is_string($_POST[$key]) ? trim($_POST[$key]) : $_POST[$key];

View File

@ -0,0 +1,39 @@
<?php
require_once __DIR__ . '/../includes/app.php';
require_permission('reports', 'show');
$user = current_user();
if (!in_array($user['role'], ['owner', 'manager'], true)) {
set_flash('danger', tr('غير مصرح لك.', 'Unauthorized.'));
redirect_to('../reports.php', ['tab' => 'daily']);
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
redirect_to('../reports.php', ['tab' => 'daily']);
}
$reportDate = trim((string) ($_POST['date'] ?? date('Y-m-d')));
$branch = trim((string) ($_POST['branch'] ?? ''));
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $reportDate)) {
set_flash('danger', tr('تاريخ التقرير غير صالح.', 'Invalid report date.'));
redirect_to('../reports.php', ['tab' => 'daily']);
}
try {
$result = wablas_send_daily_report($reportDate, $branch !== '' ? $branch : null);
if (!empty($result['success'])) {
set_flash('success', tr('تم إرسال ملخص التقرير اليومي عبر واتساب.', 'Daily summary report sent via WhatsApp.'));
} else {
$error = (string) ($result['error'] ?? tr('تعذر إرسال التقرير عبر واتساب.', 'Could not send the WhatsApp report.'));
set_flash('danger', tr('فشل إرسال التقرير عبر واتساب.', 'Failed to send the WhatsApp report.') . ' ' . $error);
}
} catch (Throwable $e) {
set_flash('danger', tr('فشل إرسال التقرير عبر واتساب.', 'Failed to send the WhatsApp report.') . ' ' . $e->getMessage());
}
redirect_to('../reports.php', [
'tab' => 'daily',
'date' => $reportDate,
'branch' => $branch,
]);

View File

@ -9,7 +9,7 @@ require_once __DIR__ . '/../db/config.php';
// Auto-migrate newly added columns
try {
$flagFile = sys_get_temp_dir() . '/.schema_migrated_v3_' . md5(__DIR__);
$flagFile = sys_get_temp_dir() . '/.schema_migrated_v4_' . md5(__DIR__);
if (!file_exists($flagFile)) {
$pdo = db();
$stmt = $pdo->query("SHOW COLUMNS FROM users LIKE 'avatar'");
@ -43,8 +43,23 @@ try {
$pdo->exec("UPDATE sales_orders SET paid_amount = CASE WHEN payment_status = 'unpaid' THEN 0 ELSE total_amount END WHERE paid_amount IS NULL OR paid_amount = 0");
$pdo->exec("UPDATE sales_orders SET due_amount = GREATEST(total_amount - paid_amount, 0)");
$pdo->exec("UPDATE sales_orders SET payment_status = CASE WHEN due_amount <= 0.0005 THEN 'paid' WHEN paid_amount > 0 THEN 'partial' ELSE 'unpaid' END");
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
('wablas_invoice_recipients', ''),
('wablas_report_recipients', ''),
('wablas_template_invoice', ''),
('wablas_template_daily_report', '')");
@file_put_contents($flagFile, '1');
}
$flagFileV5 = sys_get_temp_dir() . '/.schema_migrated_v5_' . md5(__DIR__);
if (!file_exists($flagFileV5)) {
$pdo = db();
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES
('wablas_daily_auto_send', '0'),
('wablas_daily_auto_time', '21:00'),
('wablas_daily_auto_last_date', '')");
@file_put_contents($flagFileV5, '1');
}
} catch (\Throwable $e) {}
@ -73,6 +88,41 @@ function get_setting(string $key, $default = '')
return $settings[$key] ?? $default;
}
function get_setting_non_empty(string $key, $default = '')
{
$value = get_setting($key, null);
if ($value === null) {
return $default;
}
if (is_string($value) && trim($value) === '') {
return $default;
}
return $value;
}
function save_setting_value(string $key, string $value): void
{
try {
$stmt = db()->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->bindValue(':key', $key);
$stmt->bindValue(':value', $value);
$stmt->execute();
} catch (Throwable $e) {
}
}
function wablas_format_time_setting(string $value, string $default = '21:00'): string
{
$value = trim($value);
if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $value)) {
return $default;
}
return $value;
}
$app_tz = get_setting('timezone', 'UTC');
if (empty($app_tz)) {
$app_tz = 'UTC';
@ -149,6 +199,40 @@ function phone_display(?string $value): string
return $local !== '' ? ('968 ' . $local) : $raw;
}
function wablas_parse_phone_list(string $value): array
{
$parts = preg_split('/[\s,;،]+/', trim($value)) ?: [];
$phones = [];
$invalid = [];
foreach ($parts as $part) {
$part = trim((string) $part);
if ($part === '') {
continue;
}
$normalized = normalize_oman_phone($part);
if ($normalized === '') {
$invalid[] = $part;
continue;
}
$phones[$normalized] = $normalized;
}
return [
'phones' => array_values($phones),
'invalid' => $invalid,
];
}
function wablas_phone_list_for_input(string $value): string
{
$parsed = wablas_parse_phone_list($value);
return implode("
", $parsed['phones']);
}
function qs_with_lang(array $params = []): string
{
if (!isset($params['lang']) || !in_array($params['lang'], ['ar', 'en'], true)) {
@ -526,6 +610,47 @@ function wablas_order_status_label(string $status): string
};
}
function wablas_default_invoice_template(): string
{
return "🧾 فاتورة جديدة #{receipt_no}
الفرع: {branch_name}
النوع: {sale_mode_label}
العميل: {customer_name}
الهاتف: {customer_phone}
الدفع: {payment_method_label} / {payment_status_label}
الأصناف:
{items_summary}
قبل الضريبة: {subtotal}
الضريبة: {vat_amount}
الإجمالي: {total_amount}
الكاشير: {cashier_name}
التاريخ: {sale_date}";
}
function wablas_default_daily_report_template(): string
{
return "📊 ملخص المبيعات اليومي
التاريخ: {report_date}
الفرع: {branch_name}
عدد الفواتير: {invoice_count}
إجمالي المبيعات: {total_sales}
حسب الموظف:
{seller_summary}
حسب الفرع:
{outlet_summary}
حسب الدفع:
{payment_summary}
وقت الإرسال: {generated_at}";
}
function wablas_default_order_template(string $event): string
{
return match ($event) {
@ -604,6 +729,336 @@ function wablas_order_template_vars(array $order): array
];
}
function wablas_sale_items_summary(array $sale): string
{
$items = $sale['items'] ?? null;
if (!is_array($items)) {
$items = json_decode((string) ($sale['items_json'] ?? '[]'), true);
}
if (!is_array($items) || $items === []) {
return '-';
}
$parts = [];
foreach ($items as $item) {
$name = (string) ($item['name'] ?? $item['name_ar'] ?? $item['name_en'] ?? $item['sku'] ?? '');
$qty = (int) ($item['qty'] ?? 0);
$lineTotal = isset($item['line_total']) ? currency((float) $item['line_total']) : '';
if ($name === '') {
continue;
}
$line = '- ' . $name;
if ($qty > 0) {
$line .= ' x' . $qty;
}
if ($lineTotal !== '') {
$line .= ' = ' . $lineTotal;
}
$parts[] = $line;
}
return $parts ? implode("
", $parts) : '-';
}
function wablas_payment_method_label(string $method): string
{
return match ($method) {
'cash' => tr('كاش', 'Cash'),
'card' => tr('بطاقة', 'Card'),
'transfer', 'bank' => tr('تحويل', 'Transfer'),
'pay_later' => tr('آجل', 'Pay later'),
default => $method,
};
}
function wablas_payment_status_label(string $status): string
{
return match ($status) {
'paid' => tr('مدفوع', 'Paid'),
'partial' => tr('مدفوع جزئياً', 'Partial'),
'unpaid' => tr('غير مدفوع', 'Unpaid'),
default => $status,
};
}
function wablas_customer_phone_by_id(?int $customerId): string
{
if (!$customerId) {
return '';
}
try {
$stmt = db()->prepare('SELECT phone FROM customers WHERE id = :id LIMIT 1');
$stmt->bindValue(':id', $customerId, PDO::PARAM_INT);
$stmt->execute();
$phone = (string) $stmt->fetchColumn();
return normalize_oman_phone($phone);
} catch (Throwable $e) {
return '';
}
}
function wablas_invoice_template_vars(array $sale): array
{
$customerName = trim((string) ($sale['customer_name'] ?? ''));
if ($customerName === '') {
$customerName = tr('عميل نقدي', 'Walk-in customer');
}
$customerPhone = wablas_customer_phone_by_id(isset($sale['customer_id']) ? (int) $sale['customer_id'] : null);
$paymentStatus = (string) ($sale['payment_status'] ?? 'paid');
$saleDateRaw = (string) ($sale['sale_date'] ?? $sale['created_at'] ?? '');
$saleDate = $saleDateRaw !== '' ? date('Y-m-d H:i', strtotime($saleDateRaw)) : '';
return [
'sale_id' => (string) ($sale['id'] ?? ''),
'receipt_no' => (string) ($sale['receipt_no'] ?? ''),
'branch_name' => branch_label((string) ($sale['branch_code'] ?? '')),
'sale_mode' => (string) ($sale['sale_mode'] ?? ''),
'sale_mode_label' => sale_mode_label((string) ($sale['sale_mode'] ?? '')),
'customer_name' => $customerName,
'customer_phone' => $customerPhone !== '' ? phone_display($customerPhone) : '-',
'payment_method' => (string) ($sale['payment_method'] ?? ''),
'payment_method_label' => wablas_payment_method_label((string) ($sale['payment_method'] ?? '')),
'payment_status' => $paymentStatus,
'payment_status_label' => wablas_payment_status_label($paymentStatus),
'cashier_name' => (string) ($sale['cashier_name'] ?? ''),
'subtotal' => currency((float) ($sale['subtotal'] ?? 0)),
'vat_amount' => currency((float) ($sale['vat_amount'] ?? 0)),
'total_amount' => currency((float) ($sale['total_amount'] ?? 0)),
'paid_amount' => currency((float) ($sale['paid_amount'] ?? 0)),
'due_amount' => currency((float) ($sale['due_amount'] ?? 0)),
'sale_date' => $saleDate,
'notes' => (string) ($sale['notes'] ?? ''),
'items_summary' => wablas_sale_items_summary($sale),
];
}
function daily_sales_breakdown(string $reportDate, ?string $branch = null): array
{
ensure_sales_table();
$params = [];
$where = base_sales_query_filters($params, null, $branch ?: null);
$where .= " AND DATE(sale_date) = :rdate AND status != 'order'";
$params[':rdate'] = $reportDate;
$dailyTotals = [
'seller' => [],
'outlet' => [],
'payment' => [],
'total' => 0.0,
'invoice_count' => 0,
'date' => $reportDate,
'branch' => $branch ?: '',
];
$sql = "SELECT cashier_name, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY cashier_name";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['seller'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR) ?: [];
$sql = "SELECT branch_code, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY branch_code";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['outlet'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR) ?: [];
$sql = "SELECT payment_method, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY payment_method";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['payment'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR) ?: [];
$sql = "SELECT COUNT(*) as invoice_count, COALESCE(SUM(total_amount), 0) as total FROM sales_orders" . $where;
$stmt = db()->prepare($sql);
$stmt->execute($params);
$row = $stmt->fetch() ?: [];
$dailyTotals['invoice_count'] = (int) ($row['invoice_count'] ?? 0);
$dailyTotals['total'] = (float) ($row['total'] ?? 0);
return $dailyTotals;
}
function wablas_format_summary_lines(array $rows, callable $formatter): string
{
if ($rows === []) {
return '- ' . tr('لا يوجد', 'None');
}
$lines = [];
foreach ($rows as $key => $amount) {
$lines[] = '- ' . $formatter((string) $key) . ': ' . currency((float) $amount);
}
return implode("
", $lines);
}
function wablas_daily_report_template_vars(array $dailyTotals): array
{
return [
'report_date' => (string) ($dailyTotals['date'] ?? date('Y-m-d')),
'branch_name' => !empty($dailyTotals['branch']) ? branch_label((string) $dailyTotals['branch']) : tr('جميع الفروع', 'All Branches'),
'invoice_count' => (string) ((int) ($dailyTotals['invoice_count'] ?? 0)),
'total_sales' => currency((float) ($dailyTotals['total'] ?? 0)),
'seller_summary' => wablas_format_summary_lines((array) ($dailyTotals['seller'] ?? []), static fn(string $value): string => $value !== '' ? $value : tr('غير محدد', 'Unknown')),
'outlet_summary' => wablas_format_summary_lines((array) ($dailyTotals['outlet'] ?? []), static fn(string $value): string => branch_label($value)),
'payment_summary' => wablas_format_summary_lines((array) ($dailyTotals['payment'] ?? []), static fn(string $value): string => wablas_payment_method_label($value)),
'generated_at' => date('Y-m-d H:i'),
];
}
function wablas_invoice_preview_vars(): array
{
try {
$sales = fetch_sales(null, null, 1);
if (!empty($sales[0]) && is_array($sales[0])) {
return wablas_invoice_template_vars($sales[0]);
}
} catch (Throwable $e) {
// Ignore preview lookup failures and use fallback sample data below.
}
return wablas_invoice_template_vars([
'id' => 1001,
'receipt_no' => 'INV-1001',
'branch_code' => 'main',
'sale_mode' => 'normal',
'customer_name' => tr('عميل تجريبي', 'Sample Customer'),
'payment_method' => 'cash',
'payment_status' => 'paid',
'cashier_name' => tr('الموظف', 'Cashier'),
'subtotal' => 25.000,
'vat_amount' => 1.250,
'total_amount' => 26.250,
'paid_amount' => 26.250,
'due_amount' => 0.000,
'sale_date' => date('Y-m-d H:i:s'),
'items' => [
['name' => tr('منتج 1', 'Item 1'), 'qty' => 2, 'line_total' => 10.000],
['name' => tr('منتج 2', 'Item 2'), 'qty' => 1, 'line_total' => 15.000],
],
]);
}
function wablas_daily_report_preview_vars(): array
{
try {
$dailyTotals = daily_sales_breakdown(date('Y-m-d'));
$hasData = (int) ($dailyTotals['invoice_count'] ?? 0) > 0
|| (float) ($dailyTotals['total'] ?? 0) > 0
|| !empty($dailyTotals['seller'])
|| !empty($dailyTotals['outlet'])
|| !empty($dailyTotals['payment']);
if ($hasData) {
return wablas_daily_report_template_vars($dailyTotals);
}
} catch (Throwable $e) {
// Ignore preview lookup failures and use fallback sample data below.
}
return wablas_daily_report_template_vars([
'date' => date('Y-m-d'),
'branch' => 'main',
'invoice_count' => 8,
'total' => 182.750,
'seller' => [
tr('الموظف 1', 'Cashier 1') => 95.500,
tr('الموظف 2', 'Cashier 2') => 87.250,
],
'outlet' => ['main' => 182.750],
'payment' => ['cash' => 120.000, 'card' => 62.750],
]);
}
function wablas_send_to_multiple_recipients(array $phones, string $message, array $options = []): array
{
$results = [];
$sent = 0;
$firstError = '';
foreach ($phones as $phone) {
$result = wablas_send_message($phone, $message, $options);
$results[] = $result;
if (!empty($result['success'])) {
$sent++;
} elseif ($firstError === '') {
$firstError = (string) ($result['error'] ?? ('HTTP ' . (string) ($result['status'] ?? '0')));
}
}
return [
'success' => $sent > 0 && $sent === count($phones),
'attempted' => count($phones),
'sent' => $sent,
'failed' => count($phones) - $sent,
'results' => $results,
'message' => $message,
'error' => $firstError,
];
}
function wablas_notify_sale_invoice(int $saleId): array
{
$sale = fetch_sale($saleId);
if (!$sale) {
return ['success' => false, 'attempted' => 0, 'error' => 'Sale not found'];
}
$customerPhone = wablas_customer_phone_by_id(isset($sale['customer_id']) ? (int) $sale['customer_id'] : null);
if ($customerPhone === '') {
return ['success' => false, 'attempted' => 0, 'error' => 'No customer WhatsApp phone on invoice'];
}
$template = trim((string) get_setting_non_empty('wablas_template_invoice', wablas_default_invoice_template()));
if ($template === '') {
$template = wablas_default_invoice_template();
}
$message = wablas_render_template($template, wablas_invoice_template_vars($sale));
$result = wablas_send_message($customerPhone, $message);
if (empty($result['success'])) {
error_log('Wablas invoice notify failed for sale #' . $saleId . ' customer ' . $customerPhone);
}
return [
'success' => !empty($result['success']),
'attempted' => 1,
'sent' => !empty($result['success']) ? 1 : 0,
'failed' => !empty($result['success']) ? 0 : 1,
'results' => [
[
'phone' => $customerPhone,
'success' => !empty($result['success']),
'response' => $result,
],
],
'customer_phone' => $customerPhone,
'error' => empty($result['success']) ? (string) ($result['error'] ?? 'Failed to send invoice message') : '',
];
}
function wablas_send_daily_report(string $reportDate, ?string $branch = null): array
{
$phones = wablas_parse_phone_list((string) get_setting('wablas_report_recipients', ''))['phones'];
if ($phones === []) {
return ['success' => false, 'attempted' => 0, 'error' => 'No daily report recipients configured'];
}
$dailyTotals = daily_sales_breakdown($reportDate, $branch);
$template = trim((string) get_setting_non_empty('wablas_template_daily_report', wablas_default_daily_report_template()));
if ($template === '') {
$template = wablas_default_daily_report_template();
}
$message = wablas_render_template($template, wablas_daily_report_template_vars($dailyTotals));
$result = wablas_send_to_multiple_recipients($phones, $message);
$result['report'] = $dailyTotals;
if (($result['failed'] ?? 0) > 0) {
error_log('Wablas daily report send failed for date ' . $reportDate . ' branch ' . (string) $branch);
}
return $result;
}
function wablas_send_message(string $phone, string $message, array $options = []): array
{
$localPhone = normalize_oman_phone($phone);
@ -1032,3 +1487,46 @@ function create_purchase(array $data): int
throw $e;
}
}
function wablas_daily_auto_is_enabled(): bool
{
return (string) get_setting('wablas_daily_auto_send', '0') === '1';
}
function wablas_auto_send_daily_report_if_due(): void
{
static $checked = false;
if ($checked) {
return;
}
$checked = true;
if (!wablas_daily_auto_is_enabled() || !wablas_is_configured()) {
return;
}
$phones = wablas_parse_phone_list((string) get_setting('wablas_report_recipients', ''))['phones'];
if ($phones === []) {
return;
}
$today = date('Y-m-d');
$currentTime = date('H:i');
$scheduledTime = wablas_format_time_setting((string) get_setting('wablas_daily_auto_time', '21:00'));
$lastSentDate = trim((string) get_setting('wablas_daily_auto_last_date', ''));
if ($lastSentDate === $today || $currentTime < $scheduledTime) {
return;
}
$result = wablas_send_daily_report($today, null);
if (!empty($result['success'])) {
save_setting_value('wablas_daily_auto_last_date', $today);
return;
}
error_log('Wablas scheduled daily report failed for date ' . $today . ' at ' . $scheduledTime . ': ' . (string) ($result['error'] ?? 'unknown error'));
}
wablas_auto_send_daily_report_if_due();

View File

@ -1,12 +1,31 @@
<?php if (isset($user) && $user && in_array($user['role'], ['owner', 'manager'])): ?>
<?php
$wablasInvoiceTemplate = (string) get_setting_non_empty('wablas_template_invoice', wablas_default_invoice_template());
$wablasInvoicePreviewVars = wablas_invoice_preview_vars();
$wablasDailyTemplate = (string) get_setting_non_empty('wablas_template_daily_report', wablas_default_daily_report_template());
$wablasDailyPreviewVars = wablas_daily_report_preview_vars();
$wablasReportRecipientsValue = (string) get_setting('wablas_report_recipients');
$wablasReportRecipientCount = count(wablas_parse_phone_list($wablasReportRecipientsValue)['phones']);
$wablasDailyAutoEnabled = (string) get_setting('wablas_daily_auto_send', '0') === '1';
$wablasDailyAutoTime = wablas_format_time_setting((string) get_setting('wablas_daily_auto_time', '21:00'));
$wablasDailyAutoLastDate = (string) get_setting('wablas_daily_auto_last_date', '');
?>
<!-- Settings Modal -->
<div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<form action="api/settings.php" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('إعدادات الشركة', 'Company Settings')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div>
<h5 class="modal-title mb-1"><?= h(tr('إعدادات الشركة', 'Company Settings')) ?></h5>
<div class="small text-muted"><?= h(tr('إعدادات واتساب أصبحت في نافذة مستقلة حتى تظهر جميع القوالب بوضوح.', 'WhatsApp settings now open in a separate popup so all templates stay visible.')) ?></div>
</div>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-success btn-sm" data-open-wablas-settings="1">
<i class="bi bi-whatsapp me-1"></i><?= h(tr('واتساب', 'WhatsApp')) ?>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body pb-2">
<ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
@ -89,75 +108,41 @@
</div>
</div>
</div>
<div class="tab-pane fade" id="settings-wablas-pane" role="tabpanel" aria-labelledby="settings-wablas-tab" tabindex="0">
<div class="row g-2">
<div class="col-12">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-2 border rounded-3 px-3 py-2">
<h6 class="mb-0 fw-bold"><?= h(tr('إعدادات واتساب Wablas', 'Wablas WhatsApp Settings')) ?></h6>
<div class="d-flex flex-column flex-sm-row align-items-sm-center gap-2">
<div class="form-check form-switch m-0">
<input type="hidden" name="wablas_enabled" value="0">
<input class="form-check-input" type="checkbox" role="switch" id="wablasEnabledSwitch" name="wablas_enabled" value="1" <?= wablas_is_enabled() ? 'checked' : '' ?>>
<label class="form-check-label fw-semibold" for="wablasEnabledSwitch"><?= h(tr('تفعيل', 'Enable')) ?></label>
</div>
<div class="rounded-4 border bg-body-tertiary p-4">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h5 class="mb-1 fw-bold"><?= h(tr('إعدادات واتساب في نافذة مستقلة', 'WhatsApp settings in a separate popup')) ?></h5>
<p class="text-muted mb-0"><?= h(tr('تم نقل جميع قوالب واتساب إلى نافذة مستقلة قابلة للتمرير حتى تظهر كل القوالب بوضوح بدون ازدحام داخل إعدادات الشركة.', 'All WhatsApp templates were moved into a separate scrollable popup so every template is clearly visible without being cramped inside Company Settings.')) ?></p>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-success" data-open-wablas-settings="1">
<i class="bi bi-whatsapp me-1"></i><?= h(tr('فتح إعدادات واتساب', 'Open WhatsApp Settings')) ?>
</button>
</div>
</div>
<div class="row g-3 mt-1">
<div class="col-md-4">
<div class="border rounded-3 bg-white p-3 h-100">
<div class="small text-muted mb-1"><?= h(tr('حالة الإرسال', 'Sending status')) ?></div>
<div class="fw-semibold"><?= h(wablas_is_enabled() ? tr('مفعل', 'Enabled') : tr('متوقف', 'Disabled')) ?></div>
</div>
</div>
<div class="col-md-4">
<div class="border rounded-3 bg-white p-3 h-100">
<div class="small text-muted mb-1"><?= h(tr('الفواتير', 'Invoices')) ?></div>
<div class="fw-semibold"><?= h(tr('للعميل فقط', 'Customer only')) ?></div>
</div>
</div>
<div class="col-md-4">
<div class="border rounded-3 bg-white p-3 h-100">
<div class="small text-muted mb-1"><?= h(tr('الملخص اليومي', 'Daily summary')) ?></div>
<div class="fw-semibold"><?= $wablasDailyAutoEnabled ? h($wablasDailyAutoTime . ' ' . tr('يومياً', 'daily')) : h(tr('يدوي فقط', 'Manual only')) ?></div>
<div class="small text-muted mt-1"><?= h((string) $wablasReportRecipientCount) ?> <?= h(tr('رقم مستلم', 'recipient numbers')) ?></div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('Wablas Token', 'Wablas Token')) ?></label>
<input type="text" class="form-control" name="wablas_token" value="<?= h(get_setting('wablas_token')) ?>">
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('Wablas Secret Key', 'Wablas Secret Key')) ?></label>
<input type="password" class="form-control" name="wablas_secret_key" value="<?= h(get_setting('wablas_secret_key')) ?>">
</div>
<div class="col-md-4">
<label class="form-label mb-1"><?= h(tr('رقم الاختبار', 'Test Phone')) ?></label>
<div class="input-group" dir="ltr">
<span class="input-group-text">968</span>
<input type="text" class="form-control" name="wablas_test_phone" value="<?= h(normalize_oman_phone((string) get_setting('company_phone'))) ?>" inputmode="numeric" maxlength="8" pattern="\d{8}" placeholder="91234567">
</div>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('رسالة الاختبار', 'Test Message')) ?></label>
<textarea class="form-control" name="wablas_test_message" rows="2"><?= h(tr('هذه رسالة واتساب تجريبية من النظام.', 'This is a WhatsApp test message from the system.')) ?></textarea>
</div>
<div class="col-md-2 d-grid align-self-end">
<button type="submit" class="btn btn-outline-success" formaction="api/wablas_test.php" formmethod="POST">
<i class="bi bi-whatsapp me-1"></i><?= h(tr('اختبار', 'Test')) ?>
</button>
</div>
<div class="col-12">
<div class="border rounded-3 p-3">
<div class="row g-2">
<div class="col-12">
<label class="form-label mb-1"><?= h(tr('قالب رسالة إنشاء الطلب', 'Order Created Template')) ?></label>
<textarea class="form-control" name="wablas_template_created" rows="3"><?= h(get_setting('wablas_template_created', wablas_default_order_template('created'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب قيد الانتظار', 'Pending Template')) ?></label>
<textarea class="form-control" name="wablas_template_pending" rows="3"><?= h(get_setting('wablas_template_pending', wablas_default_order_template('pending'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مقبول', 'Accepted Template')) ?></label>
<textarea class="form-control" name="wablas_template_accepted" rows="3"><?= h(get_setting('wablas_template_accepted', wablas_default_order_template('accepted'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مكتمل', 'Completed Template')) ?></label>
<textarea class="form-control" name="wablas_template_completed" rows="3"><?= h(get_setting('wablas_template_completed', wablas_default_order_template('completed'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مرفوض', 'Rejected Template')) ?></label>
<textarea class="form-control" name="wablas_template_rejected" rows="3"><?= h(get_setting('wablas_template_rejected', wablas_default_order_template('rejected'))) ?></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
@ -210,4 +195,276 @@
</div>
</div>
</div>
<style>
#wablasSettingsModal {
overflow-y: auto;
}
#wablasSettingsModal .modal-dialog {
margin: 0;
max-width: none;
min-height: 100%;
}
#wablasSettingsModal .modal-content {
min-height: 100vh;
border-radius: 0;
}
#wablasSettingsModal .modal-header,
#wablasSettingsModal .modal-footer {
position: sticky;
z-index: 3;
background: var(--bs-body-bg);
}
#wablasSettingsModal .modal-header {
top: 0;
border-bottom: 1px solid var(--bs-border-color);
}
#wablasSettingsModal .modal-footer {
bottom: 0;
border-top: 1px solid var(--bs-border-color);
}
#wablasSettingsModal .modal-body {
overflow: visible;
padding-bottom: 2rem;
}
#wablasSettingsModal .wablas-template-preview {
white-space: pre-wrap;
word-break: break-word;
}
</style>
<!-- WhatsApp Settings Modal -->
<div class="modal fade" id="wablasSettingsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content border-0 rounded-0">
<form action="api/settings.php" method="POST">
<div class="modal-header">
<div>
<h5 class="modal-title mb-1"><?= h(tr('إعدادات واتساب', 'WhatsApp Settings')) ?></h5>
<div class="small text-muted"><?= h(tr('نافذة مستقلة قابلة للتمرير لعرض جميع القوالب والحقول بشكل واضح.', 'A dedicated scrollable popup to show all WhatsApp fields and templates clearly.')) ?></div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<div class="border rounded-4 px-3 py-3 bg-body-tertiary">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h6 class="mb-1 fw-bold"><?= h(tr('ربط Wablas', 'Wablas connection')) ?></h6>
<div class="small text-muted"><?= h(tr('فعّل الإرسال ثم احفظ، ويمكنك إرسال رسالة اختبار مباشرة من هنا.', 'Enable sending, save the settings, and send a test message directly from here.')) ?></div>
</div>
<div class="form-check form-switch m-0">
<input type="hidden" name="wablas_enabled" value="0">
<input class="form-check-input" type="checkbox" role="switch" id="wablasEnabledSwitchModal" name="wablas_enabled" value="1" <?= wablas_is_enabled() ? 'checked' : '' ?>>
<label class="form-check-label fw-semibold" for="wablasEnabledSwitchModal"><?= h(tr('تفعيل واتساب', 'Enable WhatsApp')) ?></label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('Wablas Token', 'Wablas Token')) ?></label>
<input type="text" class="form-control" name="wablas_token" value="<?= h(get_setting('wablas_token')) ?>">
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('Wablas Secret Key', 'Wablas Secret Key')) ?></label>
<input type="password" class="form-control" name="wablas_secret_key" value="<?= h(get_setting('wablas_secret_key')) ?>">
</div>
<div class="col-md-4">
<label class="form-label mb-1"><?= h(tr('رقم الاختبار', 'Test Phone')) ?></label>
<div class="input-group" dir="ltr">
<span class="input-group-text">968</span>
<input type="text" class="form-control" name="wablas_test_phone" value="<?= h(normalize_oman_phone((string) get_setting('company_phone'))) ?>" inputmode="numeric" maxlength="8" pattern="\d{8}" placeholder="91234567">
</div>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('رسالة الاختبار', 'Test Message')) ?></label>
<textarea class="form-control" name="wablas_test_message" rows="2"><?= h(tr('هذه رسالة واتساب تجريبية من النظام.', 'This is a WhatsApp test message from the system.')) ?></textarea>
</div>
<div class="col-md-2 d-grid align-self-end">
<button type="submit" class="btn btn-outline-success" formaction="api/wablas_test.php" formmethod="POST">
<i class="bi bi-whatsapp me-1"></i><?= h(tr('إرسال اختبار', 'Send Test')) ?>
</button>
</div>
<div class="col-12">
<div class="border rounded-4 p-3 bg-white">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-start gap-2 mb-3">
<div>
<h6 class="mb-1 fw-semibold"><?= h(tr('قالب الفاتورة', 'Invoice template')) ?></h6>
<div class="small text-muted"><?= h(tr('يرسل تلقائياً إلى رقم العميل الموجود في الفاتورة فقط.', 'Sent automatically only to the customer phone saved on the invoice.')) ?></div>
</div>
<span class="badge text-bg-success-subtle border border-success-subtle text-success-emphasis"><?= h(tr('للعميل فقط', 'Customer only')) ?></span>
</div>
<div class="alert alert-light border small mb-3">
<strong><?= h(tr('مهم:', 'Important:')) ?></strong> <?= h(tr('لن يتم إرسال الفاتورة لأي رقم إداري. إذا لم يكن للعميل رقم واتساب محفوظ فلن يتم الإرسال.', 'The invoice will not be sent to any admin number. If the customer has no saved WhatsApp phone, nothing will be sent.')) ?>
</div>
<label class="form-label mb-1"><?= h(tr('قالب الفاتورة', 'Invoice Template')) ?></label>
<textarea class="form-control" id="wablasInvoiceTemplate" name="wablas_template_invoice" rows="12" data-wablas-preview-source="invoice"><?= h($wablasInvoiceTemplate) ?></textarea>
<div class="small text-muted mt-2"><?= h(tr('المتغيرات المتاحة: {receipt_no}, {customer_name}, {customer_phone}, {branch_name}, {payment_method_label}, {payment_status_label}, {total_amount}, {due_amount}, {sale_date}, {items_summary}, {notes}', 'Available placeholders: {receipt_no}, {customer_name}, {customer_phone}, {branch_name}, {payment_method_label}, {payment_status_label}, {total_amount}, {due_amount}, {sale_date}, {items_summary}, {notes}')) ?></div>
<div class="border rounded-3 bg-light-subtle p-3 mt-3">
<div class="small text-muted mb-2"><?= h(tr('معاينة مباشرة', 'Live preview')) ?></div>
<pre class="small mb-0 text-wrap wablas-template-preview" data-wablas-preview="invoice" data-preview-vars='<?= h(json_encode($wablasInvoicePreviewVars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>'><?= h(wablas_render_template($wablasInvoiceTemplate, $wablasInvoicePreviewVars)) ?></pre>
</div>
</div>
</div>
<div class="col-12">
<div class="border rounded-4 p-3 bg-white">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-start gap-2 mb-3">
<div>
<h6 class="mb-1 fw-semibold"><?= h(tr('قالب الملخص اليومي', 'Daily summary template')) ?></h6>
<div class="small text-muted"><?= h(tr('يرسل إلى أرقام الإدارة المحددة فقط، ويمكن تشغيله تلقائياً كل يوم في وقت محدد.', 'Sent only to the specified management numbers, and can run automatically every day at a chosen time.')) ?></div>
</div>
<span class="badge text-bg-light border"><?= h((string) $wablasReportRecipientCount) ?> <?= h(tr('مستلمين', 'recipients')) ?></span>
</div>
<div class="row g-3">
<div class="col-lg-6">
<label class="form-label mb-1"><?= h(tr('أرقام واتساب الملخص اليومي', 'Daily summary WhatsApp numbers')) ?></label>
<textarea class="form-control" name="wablas_report_recipients" rows="5" dir="ltr"><?= h(wablas_phone_list_for_input($wablasReportRecipientsValue)) ?></textarea>
<div class="form-text"><?= h(tr('هذه الأرقام فقط ستستلم الملخص اليومي.', 'Only these numbers will receive the daily summary.')) ?></div>
</div>
<div class="col-lg-6">
<div class="border rounded-4 bg-body-tertiary p-3 h-100">
<div class="form-check form-switch mb-3">
<input type="hidden" name="wablas_daily_auto_send" value="0">
<input class="form-check-input" type="checkbox" role="switch" id="wablasDailyAutoSend" name="wablas_daily_auto_send" value="1" <?= $wablasDailyAutoEnabled ? 'checked' : '' ?>>
<label class="form-check-label fw-semibold" for="wablasDailyAutoSend"><?= h(tr('تفعيل الإرسال التلقائي للملخص اليومي', 'Enable automatic daily summary sending')) ?></label>
</div>
<label class="form-label mb-1" for="wablasDailyAutoTime"><?= h(tr('وقت الإرسال اليومي', 'Daily send time')) ?></label>
<input type="time" class="form-control" id="wablasDailyAutoTime" name="wablas_daily_auto_time" value="<?= h($wablasDailyAutoTime) ?>">
<div class="form-text mt-2"><?= h(tr('سيتم الإرسال مرة واحدة يومياً بعد هذا الوقت عند أول استخدام للنظام.', 'It will send once per day after this time on the first app request that day.')) ?></div>
<?php if ($wablasDailyAutoLastDate !== ''): ?>
<div class="small text-muted mt-3"><?= h(tr('آخر إرسال تلقائي:', 'Last automatic send:')) ?> <?= h($wablasDailyAutoLastDate) ?></div>
<?php endif; ?>
</div>
</div>
</div>
<label class="form-label mb-1 mt-3"><?= h(tr('قالب الملخص اليومي', 'Daily Summary Template')) ?></label>
<textarea class="form-control" id="wablasDailyTemplate" name="wablas_template_daily_report" rows="12" data-wablas-preview-source="daily"><?= h($wablasDailyTemplate) ?></textarea>
<div class="small text-muted mt-2"><?= h(tr('المتغيرات المتاحة: {report_date}, {branch_name}, {invoice_count}, {total_sales}, {seller_summary}, {outlet_summary}, {payment_summary}, {generated_at}', 'Available placeholders: {report_date}, {branch_name}, {invoice_count}, {total_sales}, {seller_summary}, {outlet_summary}, {payment_summary}, {generated_at}')) ?></div>
<div class="border rounded-3 bg-light-subtle p-3 mt-3">
<div class="small text-muted mb-2"><?= h(tr('معاينة مباشرة', 'Live preview')) ?></div>
<pre class="small mb-0 text-wrap wablas-template-preview" data-wablas-preview="daily" data-preview-vars='<?= h(json_encode($wablasDailyPreviewVars, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?>'><?= h(wablas_render_template($wablasDailyTemplate, $wablasDailyPreviewVars)) ?></pre>
</div>
</div>
</div>
<div class="col-12">
<div class="border rounded-4 p-3 bg-white">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-2 mb-3">
<div>
<h6 class="mb-1 fw-semibold"><?= h(tr('قوالب طلبات المتجر الإلكتروني', 'Online order templates')) ?></h6>
<div class="small text-muted"><?= h(tr('هذه القوالب تُرسل لعميل الطلب حسب حالة الطلب.', 'These templates are sent to the customer depending on the order status.')) ?></div>
</div>
</div>
<div class="row g-3">
<div class="col-12">
<label class="form-label mb-1"><?= h(tr('قالب رسالة إنشاء الطلب', 'Order Created Template')) ?></label>
<textarea class="form-control" name="wablas_template_created" rows="4"><?= h(get_setting('wablas_template_created', wablas_default_order_template('created'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب قيد الانتظار', 'Pending Template')) ?></label>
<textarea class="form-control" name="wablas_template_pending" rows="4"><?= h(get_setting('wablas_template_pending', wablas_default_order_template('pending'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مقبول', 'Accepted Template')) ?></label>
<textarea class="form-control" name="wablas_template_accepted" rows="4"><?= h(get_setting('wablas_template_accepted', wablas_default_order_template('accepted'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مكتمل', 'Completed Template')) ?></label>
<textarea class="form-control" name="wablas_template_completed" rows="4"><?= h(get_setting('wablas_template_completed', wablas_default_order_template('completed'))) ?></textarea>
</div>
<div class="col-md-6">
<label class="form-label mb-1"><?= h(tr('قالب مرفوض', 'Rejected Template')) ?></label>
<textarea class="form-control" name="wablas_template_rejected" rows="4"><?= h(get_setting('wablas_template_rejected', wablas_default_order_template('rejected'))) ?></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-success"><?= h(tr('حفظ إعدادات واتساب', 'Save WhatsApp Settings')) ?></button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var settingsModalEl = document.getElementById('settingsModal');
var wablasModalEl = document.getElementById('wablasSettingsModal');
var reopenSettingsAfterWablas = false;
document.querySelectorAll('[data-open-wablas-settings]').forEach(function (trigger) {
trigger.addEventListener('click', function () {
if (!wablasModalEl || typeof bootstrap === 'undefined') {
return;
}
var wablasModal = bootstrap.Modal.getOrCreateInstance(wablasModalEl);
if (settingsModalEl && settingsModalEl.classList.contains('show')) {
reopenSettingsAfterWablas = true;
settingsModalEl.addEventListener('hidden.bs.modal', function handleSettingsHidden() {
settingsModalEl.removeEventListener('hidden.bs.modal', handleSettingsHidden);
wablasModal.show();
});
bootstrap.Modal.getOrCreateInstance(settingsModalEl).hide();
return;
}
wablasModal.show();
});
});
if (settingsModalEl && wablasModalEl && typeof bootstrap !== 'undefined') {
wablasModalEl.addEventListener('hidden.bs.modal', function () {
if (!reopenSettingsAfterWablas) {
return;
}
reopenSettingsAfterWablas = false;
bootstrap.Modal.getOrCreateInstance(settingsModalEl).show();
});
}
document.querySelectorAll('[data-wablas-preview-source]').forEach(function (field) {
var key = field.getAttribute('data-wablas-preview-source');
var preview = document.querySelector('[data-wablas-preview="' + key + '"]');
if (!preview) {
return;
}
var vars = {};
try {
vars = JSON.parse(preview.getAttribute('data-preview-vars') || '{}');
} catch (error) {
vars = {};
}
var render = function (template) {
return String(template || '').replace(/\{([a-z0-9_]+)\}/gi, function (match, token) {
return Object.prototype.hasOwnProperty.call(vars, token) ? String(vars[token]) : match;
});
};
var sync = function () {
preview.textContent = render(field.value);
};
field.addEventListener('input', sync);
sync();
});
});
</script>
<?php endif; ?>

View File

@ -121,6 +121,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'notes' => $notes !== '' ? $notes : null,
]);
wablas_notify_sale_invoice($saleId);
set_flash('success', $saleMode === 'normal'
? tr('تم حفظ الفاتورة بنجاح.', 'Invoice saved successfully.')
: tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));

View File

@ -100,6 +100,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'notes' => $notes !== '' ? $notes : null,
]);
wablas_notify_sale_invoice($saleId);
set_flash('success', tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));
redirect_to('print_receipt.php', ['id' => $saleId]);
}

View File

@ -56,44 +56,19 @@ if ($tab === 'sales') {
} elseif ($tab === 'daily') {
$reportDate = $_GET['date'] ?? date('Y-m-d');
$branchFilter = $_GET['branch'] ?? '';
$params = [];
$where = base_sales_query_filters($params, null, $branchFilter ?: null);
$where .= " AND DATE(sale_date) = :rdate AND status != 'order'";
$params[':rdate'] = $reportDate;
$dailyTotals = [
'seller' => [],
'outlet' => [],
'payment' => [],
'total' => 0
'total' => 0.0,
'invoice_count' => 0,
'date' => $reportDate,
'branch' => $branchFilter,
];
try {
// By Seller
$sql = "SELECT cashier_name, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY cashier_name";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['seller'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// By Outlet
$sql = "SELECT branch_code, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY branch_code";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['outlet'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// By Payment
$sql = "SELECT payment_method, SUM(total_amount) as total FROM sales_orders" . $where . " GROUP BY payment_method";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['payment'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// Grand total
$sql = "SELECT SUM(total_amount) as total FROM sales_orders" . $where;
$stmt = db()->prepare($sql);
$stmt->execute($params);
$dailyTotals['total'] = (float) $stmt->fetchColumn();
$dailyTotals = daily_sales_breakdown($reportDate, $branchFilter ?: null);
} catch(Throwable $e) {
$dbError = $e->getMessage();
}
@ -625,11 +600,11 @@ require __DIR__ . '/includes/header.php';
<div class="card-body">
<form method="GET" action="reports.php" class="row g-3 align-items-end">
<input type="hidden" name="tab" value="daily">
<div class="col-md-4">
<div class="col-md-3">
<label class="form-label"><?= h(tr('التاريخ', 'Date')) ?></label>
<input type="date" name="date" class="form-control" value="<?= h($reportDate) ?>" onchange="this.form.submit()">
</div>
<div class="col-md-4">
<div class="col-md-3">
<label class="form-label"><?= h(tr('الفرع', 'Branch')) ?></label>
<select name="branch" class="form-select" onchange="this.form.submit()">
<option value=""><?= h(tr('جميع الفروع', 'All Branches')) ?></option>
@ -644,6 +619,9 @@ require __DIR__ . '/includes/header.php';
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100"><?= h(tr('بحث', 'Search')) ?></button>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-success w-100" formaction="api/wablas_daily_report.php" formmethod="POST"><i class="bi bi-whatsapp"></i> <?= h(tr('إرسال واتساب', 'Send WhatsApp')) ?></button>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-outline-secondary w-100" onclick="window.print()"><i class="bi bi-printer"></i> <?= h(tr('طباعة', 'Print')) ?></button>
</div>