Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,685 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/init_secretaire.php';
|
||||
|
||||
|
||||
require_once APP_HTDOCS . '/push/push_endpoint.php';
|
||||
|
||||
|
||||
// PHPMailer (config centralisée)
|
||||
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// ------- helpers JSON -------
|
||||
function out(bool $ok, string $message, array $extra = []): never {
|
||||
echo json_encode(['ok'=>$ok, 'message'=>$message] + $extra, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ------- helpers e-mail HTML/TXT -------
|
||||
function email_html_status(array $d, array $t): string {
|
||||
$brand = '<img src="https://uslugi.fr/public/uploads/img/logo_mail.png" width="150" alt="USLUGI.FR">';
|
||||
$brand_bas ='УСЛУГИ.ФР';
|
||||
return '<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>USLUGI.FR</title></head>
|
||||
<body style="margin:0;padding:0;background:#f4f6f8;font-family:Arial,Helvetica,sans-serif;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6f8;padding:24px 0;"><tr><td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:8px;overflow:hidden;">
|
||||
<tr><td style="background:#38378B;color:#fff;padding:20px 24px;font-size:18px;font-weight:600;">'.$brand.'</td></tr>
|
||||
<tr><td style="padding:24px 24px 8px 24px;color:#111;font-size:16px;line-height:1.5;">'.nl2br(htmlspecialchars($d['hello_line'])).'</td></tr>
|
||||
<tr><td style="padding:8px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($d['intro'])).'</td></tr>
|
||||
<tr><td style="padding:16px 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:8px;">
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;border-bottom:1px solid #e5e7eb;">'.htmlspecialchars($d['who_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['who_value']).'</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($d['date_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['when']).'</td></tr>
|
||||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($d['statut_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['statut_value']).'</td></tr>
|
||||
</table>
|
||||
</td></tr>'.
|
||||
(!empty($d['cta_url']) ? '<tr><td align="center" style="padding:8px 24px 24px 24px;"><a href="'.htmlspecialchars($d['cta_url']).'" target="_blank" style="display:inline-block;text-decoration:none;padding:12px 20px;border-radius:999px;background:#E03C3F;color:#fff;font-weight:600;">'.htmlspecialchars($d['cta_label']).'</a></td></tr>' : '').
|
||||
'<tr><td style="padding:8px 24px 24px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($d['footer'])).'</td></tr>
|
||||
</table>
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="margin-top:12px;"><tr><td style="text-align:center;color:#9ca3af;font-size:12px;padding:8px 0;">© '.date('Y').' '.$brand_bas.' — '.htmlspecialchars($t['merci_simple'] ?? 'Merci').'</td></tr></table>
|
||||
</td></tr></table></body></html>';
|
||||
}
|
||||
function email_text_status(array $d, array $t): string {
|
||||
$L=[]; $L[]=$d['hello_line']; $L[]=''; $L[]=$d['intro']; $L[]='';
|
||||
$L[]=$d['who_label'].': '.$d['who_value']; $L[]=$d['date_label'].': '.$d['when']; $L[]=$d['statut_label'].': '.$d['statut_value'];
|
||||
if (!empty($d['cta_url'])) { $L[]=''; $L[] = $d['cta_label'].': '.$d['cta_url']; }
|
||||
$L[]=''; $L[]=$d['footer']; return implode("\n",$L);
|
||||
}
|
||||
|
||||
// ------- ICS helpers -------
|
||||
function ics_escape(string $s): string {
|
||||
$s = str_replace("\\","\\\\",$s);
|
||||
$s = str_replace(";","\;",$s);
|
||||
$s = str_replace(",","\,",$s);
|
||||
return preg_replace("/\r\n|\r|\n/","\\n",$s);
|
||||
}
|
||||
function ics_build(
|
||||
string $uid, DateTime $start, DateTime $end, string $tz, string $summary, string $desc, string $loc,
|
||||
array $attendees, string $method='REQUEST', string $status='CONFIRMED'
|
||||
): string {
|
||||
$L=[];
|
||||
$L[]="BEGIN:VCALENDAR";
|
||||
$L[]="VERSION:2.0";
|
||||
$L[]="PRODID:-//УСЛУГИ.ФР//RDV//FR";
|
||||
$L[]="CALSCALE:GREGORIAN";
|
||||
$L[]="METHOD:{$method}";
|
||||
$L[]="BEGIN:VEVENT";
|
||||
$L[]="UID:{$uid}";
|
||||
$L[]="DTSTAMP:".gmdate('Ymd\THis\Z');
|
||||
$L[]="DTSTART;TZID={$tz}:".$start->format('Ymd\THis');
|
||||
$L[]="DTEND;TZID={$tz}:".$end->format('Ymd\THis');
|
||||
if ($summary) $L[]="SUMMARY:".ics_escape($summary);
|
||||
if ($desc) $L[]="DESCRIPTION:".ics_escape($desc);
|
||||
if ($loc) $L[]="LOCATION:".ics_escape($loc);
|
||||
$L[]="ORGANIZER;CN=УСЛУГИ.ФР:mailto:rdv@uslugi.fr";
|
||||
foreach ($attendees as $a) {
|
||||
if (empty($a['email'])) continue;
|
||||
$cn = ics_escape($a['cn'] ?? '');
|
||||
$L[]="ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN={$cn};RSVP=TRUE:mailto:".$a['email'];
|
||||
}
|
||||
$L[]="STATUS:{$status}";
|
||||
if ($method==='CANCEL') $L[]="SEQUENCE:2";
|
||||
$L[]="END:VEVENT";
|
||||
$L[]="END:VCALENDAR";
|
||||
return implode("\r\n",$L)."\r\n";
|
||||
}
|
||||
|
||||
// ------- SMTP send (PHPMailer) -------
|
||||
function smtp_send(
|
||||
string $to, string $subject, string $text, string $html,
|
||||
?string $icsName=null, ?string $icsContent=null, ?string $icsMethod=null
|
||||
): array {
|
||||
global $MAILCFG;
|
||||
$m = new PHPMailer(true);
|
||||
try {
|
||||
$m->isSMTP();
|
||||
$m->Host = $MAILCFG['smtp']['host'];
|
||||
$m->Port = $MAILCFG['smtp']['port'];
|
||||
$m->SMTPSecure = $MAILCFG['smtp']['secure'];
|
||||
$m->SMTPAuth = $MAILCFG['smtp']['auth'];
|
||||
$m->Username = $MAILCFG['smtp']['username'];
|
||||
$m->Password = $MAILCFG['smtp']['password'];
|
||||
$m->CharSet = 'UTF-8';
|
||||
$m->Encoding = 'base64';
|
||||
$m->XMailer = '';
|
||||
|
||||
if (!empty($_GET['debug_mail'])) {
|
||||
$m->SMTPDebug = 2;
|
||||
$m->Debugoutput = function($s,$lvl){ error_log("SMTP[$lvl] ".$s); };
|
||||
}
|
||||
|
||||
if (!empty($MAILCFG['bounce'])) $m->Sender = $MAILCFG['bounce'];
|
||||
$from = $MAILCFG['from'] ?? ['no-reply@uslugi.fr','USLUGI.FR'];
|
||||
$rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr','USLUGI.FR'];
|
||||
$m->setFrom($from[0], $from[1] ?? '');
|
||||
if (!empty($rpto[0])) $m->addReplyTo($rpto[0], $rpto[1] ?? '');
|
||||
|
||||
$m->addAddress($to);
|
||||
if (!empty($MAILCFG['suppress_auto'])) {
|
||||
$m->addCustomHeader('Auto-Submitted', 'auto-generated');
|
||||
$m->addCustomHeader('X-Auto-Response-Suppress', 'All');
|
||||
}
|
||||
|
||||
$m->isHTML(true);
|
||||
$m->Subject = $subject;
|
||||
$m->Body = $html;
|
||||
$m->AltBody = $text;
|
||||
|
||||
if ($icsName && $icsContent !== null && $icsMethod) {
|
||||
$m->addStringAttachment($icsContent, $icsName, 'base64', 'text/calendar; method='.$icsMethod.'; charset=UTF-8');
|
||||
}
|
||||
|
||||
$m->send();
|
||||
return [true, null];
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('PHPMailer changer_statut (cabinet) error: '.$e->getMessage().($m->ErrorInfo ? ' | '.$m->ErrorInfo : ''));
|
||||
// retry sans ICS
|
||||
if ($icsName && $icsContent !== null && $icsMethod) {
|
||||
try { $m->clearAttachments(); $m->send(); return [true, 'sent_without_ics']; }
|
||||
catch(Throwable $e2){ error_log('PHPMailer fallback: '.$e2->getMessage()); }
|
||||
}
|
||||
return [false, $e->getMessage()];
|
||||
}
|
||||
}
|
||||
// ===== Badge helpers =====
|
||||
function get_compte_id(PDO $pdo, string $role, int $reference_id): int {
|
||||
$st = $pdo->prepare("SELECT id FROM compte WHERE role = ? AND reference_id = ? LIMIT 1");
|
||||
$st->execute([$role, $reference_id]);
|
||||
return (int)($st->fetchColumn() ?: 0);
|
||||
}
|
||||
|
||||
function badge_rdv_inc(PDO $pdo, int $compte_id, int $delta = 1): void {
|
||||
if ($compte_id <= 0) return;
|
||||
$pdo->prepare("
|
||||
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread, updated_at)
|
||||
VALUES (:cid, 0, :d, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
rdv_unread = LEAST(99, GREATEST(0, rdv_unread + :d)),
|
||||
updated_at = NOW()
|
||||
")->execute([':cid' => $compte_id, ':d' => $delta]);
|
||||
}
|
||||
|
||||
// =================== Contrôles d’entrée ===================
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') out(false, 'Méthode invalide.');
|
||||
|
||||
$compteId = (int)($_SESSION['compte_id'] ?? 0);
|
||||
$userRole = strtolower((string)($_SESSION['role'] ?? ''));
|
||||
$praticien_id = (int)($_POST['praticien_id'] ?? 0); // <<< IMPORTANT côté secrétaire
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$statut = (string)($_POST['statut'] ?? '');
|
||||
|
||||
$whitelist = ['prévu','confirmé','annulé','terminé'];
|
||||
if ($userRole !== 'secretaire' || $compteId<=0 || $praticien_id<=0 || $id<=0 || !in_array($statut, $whitelist, true)) {
|
||||
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
|
||||
}
|
||||
|
||||
// =================== Vérif pivot secrétaire ===================
|
||||
$st = $pdo->prepare("
|
||||
SELECT can_manage_rdv
|
||||
FROM secretaire_praticien
|
||||
WHERE secretaire_user_id = :u
|
||||
AND praticien_id = :p
|
||||
AND is_active = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$st->execute(['u'=>$compteId,'p'=>$praticien_id]);
|
||||
$pivot = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$pivot || (int)$pivot['can_manage_rdv'] !== 1) {
|
||||
out(false, 'Forbidden');
|
||||
}
|
||||
|
||||
// =================== Update DB sécurisé ===================
|
||||
try {
|
||||
// =========================
|
||||
// SNAPSHOT AVANT UPDATE (secrétaire)
|
||||
// =========================
|
||||
$beforeStmt = $pdo->prepare("
|
||||
SELECT statut, date_rdv, duree_minutes, lieu, praticien_id, client_id
|
||||
FROM rendez_vous
|
||||
WHERE id = :id AND praticien_id = :pid
|
||||
LIMIT 1
|
||||
");
|
||||
$beforeStmt->execute([':id'=>$id, ':pid'=>$praticien_id]);
|
||||
$beforeRow = $beforeStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$beforeRow) {
|
||||
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
|
||||
}
|
||||
|
||||
// bornage au praticien cible
|
||||
$upd = $pdo->prepare("UPDATE rendez_vous SET statut = :s WHERE id = :id AND praticien_id = :pid");
|
||||
$upd->execute([':s'=>$statut, ':id'=>$id, ':pid'=>$praticien_id]);
|
||||
if ($upd->rowCount() === 0) {
|
||||
out(false, $translations['aucune_modification'] ?? 'Aucune modification.');
|
||||
}
|
||||
// =========================
|
||||
// LOG RDV (rdv_actions_log) : update_status (secrétaire)
|
||||
// =========================
|
||||
$beforeJson = json_encode([
|
||||
'statut' => $beforeRow['statut'],
|
||||
'date_rdv' => $beforeRow['date_rdv'],
|
||||
'duree_minutes' => (int)$beforeRow['duree_minutes'],
|
||||
'lieu' => $beforeRow['lieu'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$afterJson = json_encode([
|
||||
'statut' => $statut,
|
||||
'date_rdv' => $beforeRow['date_rdv'], // date inchangée ici
|
||||
'duree_minutes' => (int)$beforeRow['duree_minutes'], // idem
|
||||
'lieu' => $beforeRow['lieu'],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$logStmt = $pdo->prepare("
|
||||
INSERT INTO rdv_actions_log
|
||||
(rdv_id, action, by_user_id, by_role, before_json, after_json, created_at)
|
||||
VALUES
|
||||
(?, 'update_status', ?, 'secretaire', ?, ?, NOW())
|
||||
");
|
||||
$logStmt->execute([(int)$id, (int)$compteId, $beforeJson, $afterJson]);
|
||||
|
||||
// Récup info RDV (+ lieu + adresse praticien)
|
||||
$q = $pdo->prepare("
|
||||
SELECT rv.id, rv.client_id, rv.date_rdv, rv.duree_minutes, rv.uuid_ical, rv.lieu,
|
||||
p.email AS email_praticien, p.prenom AS prenom_praticien, p.nom AS nom_praticien,
|
||||
p.adresse AS p_adresse, p.ville AS p_ville, p.code_postal AS p_cp,
|
||||
c.email AS email_client, c.prenom AS prenom_client, c.nom AS nom_client
|
||||
FROM rendez_vous rv
|
||||
JOIN praticien p ON rv.praticien_id = p.id
|
||||
JOIN client c ON rv.client_id = c.id
|
||||
WHERE rv.id = :id AND rv.praticien_id = :pid
|
||||
");
|
||||
$q->execute([':id'=>$id, ':pid'=>$praticien_id]);
|
||||
$rdv = $q->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$rdv) out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
|
||||
// ✅ compte_id pour badges
|
||||
$clientCompteId = get_compte_id($pdo, 'client', (int)$rdv['client_id']);
|
||||
$praticienCompteId = get_compte_id($pdo, 'praticien', (int)$praticien_id);
|
||||
|
||||
|
||||
// Données communes
|
||||
$tzStr='Europe/Paris'; $tz=new DateTimeZone($tzStr);
|
||||
$start=new DateTime($rdv['date_rdv'], $tz);
|
||||
$end=(clone $start)->modify('+'.(int)($rdv['duree_minutes'] ?? 60).' minutes');
|
||||
$when=$start->format('d/m/Y H:i');
|
||||
$when_a=$start->format('d/m/Y') . ' ' . ($translations['a'] ?? 'в') . ' ' . $start->format('H:i');
|
||||
|
||||
$cliFull = trim(($rdv['prenom_client'] ?? '').' '.($rdv['nom_client'] ?? ''));
|
||||
$pratFull = trim(($rdv['prenom_praticien'] ?? '').' '.($rdv['nom_praticien'] ?? ''));
|
||||
|
||||
// ====== FORMAT (online/offline + adresse) ======
|
||||
$lieu = strtolower(trim((string)($rdv['lieu'] ?? '')));
|
||||
$addrParts = array_filter([
|
||||
trim((string)($rdv['p_adresse'] ?? '')),
|
||||
trim((string)($rdv['p_cp'] ?? '') . ' ' . (string)($rdv['p_ville'] ?? ''))
|
||||
], fn($x) => $x !== '' && $x !== ' ');
|
||||
$addr = trim(implode(', ', $addrParts));
|
||||
|
||||
$formatLabel = $translations['format_label'] ?? 'Формат';
|
||||
$onlineLabel = $translations['option_online'] ?? 'Онлайн';
|
||||
$cabinetLabel = $translations['option_cabinet'] ?? 'Очно';
|
||||
$addressLabel = $translations['adresse_label'] ?? 'Адрес';
|
||||
|
||||
$formatLine = '';
|
||||
if ($lieu === 'online') {
|
||||
$formatLine = "\n\n{$formatLabel}: {$onlineLabel}";
|
||||
} elseif ($lieu === 'offline') {
|
||||
$formatLine = "\n\n{$formatLabel}: {$cabinetLabel}" . ($addr ? "\n{$addressLabel}: {$addr}" : '');
|
||||
}
|
||||
|
||||
// LOCATION pour ICS
|
||||
if ($lieu === 'online') {
|
||||
$icsLocation = $onlineLabel;
|
||||
} elseif ($lieu === 'offline') {
|
||||
$icsLocation = $addr !== '' ? $addr : $cabinetLabel;
|
||||
} else {
|
||||
$icsLocation = '';
|
||||
}
|
||||
|
||||
$cta_client_url='https://uslugi.fr/public/client/mes_rendez_vous.php';
|
||||
$cta_prat_url ='https://uslugi.fr/public/praticien/agenda.php';
|
||||
$cta_label =$translations['consultez_espace_pour_details'] ?? 'Voir les détails';
|
||||
$statutLabelMap=[
|
||||
'confirmé'=>$translations['statut_confirme'] ?? 'Confirmé',
|
||||
'annulé' =>$translations['statut_annule'] ?? 'Annulé',
|
||||
'prévu' =>$translations['statut_prevu'] ?? 'Prévu',
|
||||
'terminé' =>$translations['statut_termine'] ?? 'Terminé',
|
||||
];
|
||||
$statutLibelle=$statutLabelMap[$statut] ?? $statut;
|
||||
|
||||
$warnings=[];
|
||||
|
||||
// ===== HOOK SUIVI CLIENT — création/MAJ dossier + entrée auto (sur confirmation) =====
|
||||
if ($statut === 'confirmé') {
|
||||
// =========================
|
||||
// PUSH RDV — confirmé (par secrétaire)
|
||||
// =========================
|
||||
try {
|
||||
// 🔔 Praticien : confirmé par secrétaire
|
||||
push_rdv_to_praticien(
|
||||
$pdo,
|
||||
(int)$praticien_id,
|
||||
$translations['push_rdv_confirmed_praticien_title'] ?? 'Запись подтверждена',
|
||||
$translations['push_rdv_confirmed_praticien_body'] ?? 'Секретарь подтвердил(а) запись.',
|
||||
'/public/praticien/agenda.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'confirm',
|
||||
'from_role' => 'secretaire',
|
||||
'secretaire_id' => (int)$compteId,
|
||||
'client_id' => (int)$rdv['client_id'],
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
'date_rdv' => $rdv['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
// ✅ badges
|
||||
badge_rdv_inc($pdo, $clientCompteId, 1);
|
||||
badge_rdv_inc($pdo, $praticienCompteId, 1);
|
||||
|
||||
push_rdv_to_client(
|
||||
$pdo,
|
||||
(int)$rdv['client_id'],
|
||||
$translations['push_rdv_confirmed_title'] ?? 'Запись подтверждена',
|
||||
$translations['push_rdv_confirmed_body'] ?? 'Ваша запись подтверждена администратором.',
|
||||
'/public/client/mes_rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'confirm',
|
||||
'from_role' => 'secretaire',
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
'date_rdv' => $rdv['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push rdv confirm (secretaire) failed: '.$e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Upsert dossier praticien↔client
|
||||
$upsert = $pdo->prepare("
|
||||
INSERT INTO client_dossiers (praticien_id, client_id, first_rdv_id)
|
||||
VALUES (:p, :c, :r)
|
||||
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP
|
||||
");
|
||||
$upsert->execute([
|
||||
':p' => (int)$praticien_id,
|
||||
':c' => (int)$rdv['client_id'],
|
||||
':r' => (int)$rdv['id']
|
||||
]);
|
||||
|
||||
// Récup id dossier (si existed déjà, lastInsertId = 0)
|
||||
$dossierId = (int)$pdo->lastInsertId();
|
||||
if ($dossierId === 0) {
|
||||
$getId = $pdo->prepare("SELECT id FROM client_dossiers WHERE praticien_id=:p AND client_id=:c LIMIT 1");
|
||||
$getId->execute([':p'=>(int)$praticien_id, ':c'=>(int)$rdv['client_id']]);
|
||||
$dossierId = (int)$getId->fetchColumn();
|
||||
}
|
||||
|
||||
// Éviter doublon côté suivi
|
||||
$exists = $pdo->prepare("SELECT 1 FROM client_suivi WHERE dossier_id=:d AND rdv_id=:r LIMIT 1");
|
||||
$exists->execute([':d'=>$dossierId, ':r'=>(int)$rdv['id']]);
|
||||
if (!$exists->fetchColumn()) {
|
||||
$type = ($lieu === 'online') ? 'online' : (($lieu === 'offline') ? 'cabinet' : 'autre');
|
||||
$ins = $pdo->prepare("
|
||||
INSERT INTO client_suivi (dossier_id, rdv_id, date_rdv, type_rdv, titre, notes, prive, duree_minutes)
|
||||
VALUES (:d, :r, :dt, :ty, :ti, :no, 1, :du)
|
||||
");
|
||||
$ins->execute([
|
||||
':d' => $dossierId,
|
||||
':r' => (int)$rdv['id'],
|
||||
':dt' => $start->format('Y-m-d H:i:s'),
|
||||
':ty' => $type,
|
||||
':ti' => $translations['suivi_rdv_confirme_titre'] ?? 'RDV confirmé',
|
||||
':no' => ($translations['suivi_rdv_confirme_note'] ?? 'Créé automatiquement suite à la confirmation du rendez-vous.'),
|
||||
':du' => (int)($rdv['duree_minutes'] ?? 60),
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $ex) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
error_log('Suivi auto (confirmé/cabinet) failed: '.$ex->getMessage());
|
||||
// on continue quand même (mails, etc.)
|
||||
}
|
||||
|
||||
// E-mails confirmation
|
||||
$d_c = [
|
||||
'hello_line'=>sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s',$cliFull),
|
||||
'intro'=>($translations['votre_rdv_confirme'] ?? 'Votre rendez-vous est confirmé').' '.
|
||||
($translations['email_avec'] ?? 'avec').' '.$pratFull.'.',
|
||||
'who_label'=>$translations['praticien_label'] ?? 'Praticien',
|
||||
'who_value'=>$pratFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url'=>$cta_client_url,
|
||||
'cta_label'=>$cta_label,
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
if ($formatLine !== '') $d_c['intro'] .= $formatLine;
|
||||
|
||||
$icsC = !empty($rdv['uuid_ical'])
|
||||
? ics_build($rdv['uuid_ical'],$start,$end,$tzStr,
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.$pratFull,
|
||||
'', $icsLocation,
|
||||
[['cn'=>$pratFull,'email'=>$rdv['email_praticien']],['cn'=>$cliFull,'email'=>$rdv['email_client']]],
|
||||
'REQUEST','CONFIRMED')
|
||||
: null;
|
||||
[$okC,$errC] = smtp_send(
|
||||
$rdv['email_client'],
|
||||
$translations['email_confirmation_rdv_sujet'] ?? 'Confirmation de rendez-vous',
|
||||
email_text_status($d_c,$translations),
|
||||
email_html_status($d_c,$translations),
|
||||
$icsC?('rdv_'.$rdv['uuid_ical'].'_client.ics'):null,
|
||||
$icsC,
|
||||
$icsC?'REQUEST':null
|
||||
);
|
||||
if (!$okC) $warnings[]='mail_client';
|
||||
|
||||
$d_p = [
|
||||
'hello_line'=>($translations['email_bonjour_dr'] ?? 'Bonjour').' '.$pratFull,
|
||||
'intro'=>$translations['nouveau_rdv_pris_avec'] ?? 'Un nouveau rendez-vous a été confirmé.',
|
||||
'who_label'=>$translations['client_label'] ?? 'Client',
|
||||
'who_value'=>$cliFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url'=>$cta_prat_url,
|
||||
'cta_label'=>$cta_label,
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
if ($formatLine !== '') $d_p['intro'] .= $formatLine;
|
||||
|
||||
$icsP = !empty($rdv['uuid_ical'])
|
||||
? ics_build($rdv['uuid_ical'],$start,$end,$tzStr,
|
||||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.$cliFull,
|
||||
'', $icsLocation,
|
||||
[['cn'=>$cliFull,'email'=>$rdv['email_client']],['cn'=>$pratFull,'email'=>$rdv['email_praticien']]],
|
||||
'REQUEST','CONFIRMED')
|
||||
: null;
|
||||
[$okP,$errP] = smtp_send(
|
||||
$rdv['email_praticien'],
|
||||
($translations['rdv_confirme_pour'] ?? 'RDV confirmé pour').' '.$cliFull,
|
||||
email_text_status($d_p,$translations),
|
||||
email_html_status($d_p,$translations),
|
||||
$icsP?('rdv_'.$rdv['uuid_ical'].'_praticien.ics'):null,
|
||||
$icsP,
|
||||
$icsP?'REQUEST':null
|
||||
);
|
||||
if (!$okP) $warnings[]='mail_praticien';
|
||||
}
|
||||
|
||||
if ($statut === 'annulé') {
|
||||
// =========================
|
||||
// PUSH RDV — annulé (par secrétaire)
|
||||
// =========================
|
||||
try {
|
||||
// 🔔 Praticien : annulé par secrétaire
|
||||
push_rdv_to_praticien(
|
||||
$pdo,
|
||||
(int)$praticien_id,
|
||||
$translations['push_rdv_cancel_praticien_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_praticien_body'] ?? 'Секретарь отменил(а) запись.',
|
||||
'/public/praticien/rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'cancel',
|
||||
'from_role' => 'secretaire',
|
||||
'secretaire_id' => (int)$compteId,
|
||||
'client_id' => (int)$rdv['client_id'],
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
'date_rdv' => $rdv['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
// ✅ badges
|
||||
badge_rdv_inc($pdo, $clientCompteId, 1);
|
||||
badge_rdv_inc($pdo, $praticienCompteId, 1);
|
||||
|
||||
push_rdv_to_client(
|
||||
$pdo,
|
||||
(int)$rdv['client_id'],
|
||||
$translations['push_rdv_cancel_secretaire_title'] ?? 'Запись отменена',
|
||||
$translations['push_rdv_cancel_secretaire_body'] ?? 'Администратор отменил запись.',
|
||||
'/public/client/mes_rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'cancel',
|
||||
'from_role' => 'secretaire',
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
'date_rdv' => $rdv['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push rdv cancel (secretaire) failed: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$summary = ($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.$pratFull;
|
||||
$attBoth = [
|
||||
['cn'=>$cliFull,'email'=>$rdv['email_client']],
|
||||
['cn'=>$pratFull,'email'=>$rdv['email_praticien']]
|
||||
];
|
||||
$ics = !empty($rdv['uuid_ical'])
|
||||
? ics_build($rdv['uuid_ical'],$start,$end,$tzStr,$summary,'',$icsLocation, $attBoth,'CANCEL','CANCELLED')
|
||||
: null;
|
||||
|
||||
$d_c = [
|
||||
'hello_line'=>sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s',$cliFull),
|
||||
'intro'=>($translations['email_votre_rdv_du'] ?? 'Votre rendez-vous du').' '.$when_a.' '.
|
||||
($translations['email_avec'] ?? 'avec').' '.$pratFull.' '.
|
||||
($translations['email_a_ete_annule'] ?? 'a été annulé.'),
|
||||
'who_label'=>$translations['praticien_label'] ?? 'Praticien',
|
||||
'who_value'=>$pratFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url'=>'https://uslugi.fr/public/client/mes_rendez_vous.php',
|
||||
'cta_label'=>$translations['consultez_espace_pour_details'] ?? 'Voir les détails',
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
[$okC,$errC] = smtp_send(
|
||||
$rdv['email_client'],
|
||||
$translations['email_annulation_client_sujet'] ?? 'Confirmation d’annulation de votre rendez-vous',
|
||||
email_text_status($d_c,$translations),
|
||||
email_html_status($d_c,$translations),
|
||||
$ics?('rdv_cancel_'.$rdv['uuid_ical'].'_client.ics'):null,
|
||||
$ics,
|
||||
$ics?'CANCEL':null
|
||||
);
|
||||
if (!$okC) $warnings[]='mail_client';
|
||||
|
||||
$d_p = [
|
||||
'hello_line'=>($translations['email_bonjour_dr'] ?? 'Bonjour').' '.$pratFull,
|
||||
'intro'=>($translations['email_rdv_du'] ?? 'Le rendez-vous du').' '.$when_a.' '.
|
||||
($translations['email_avec'] ?? 'avec').' '.$cliFull.' '.
|
||||
($translations['email_annule_par_praticien'] ?? 'a été annulé.'),
|
||||
'who_label'=>$translations['client_label'] ?? 'Client',
|
||||
'who_value'=>$cliFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url'=>'https://uslugi.fr/public/praticien/agenda.php',
|
||||
'cta_label'=>$translations['consultez_espace_pour_details'] ?? 'Voir les détails',
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
[$okP,$errP] = smtp_send(
|
||||
$rdv['email_praticien'],
|
||||
$translations['email_annulation_sujet'] ?? 'Rendez-vous annulé',
|
||||
email_text_status($d_p,$translations),
|
||||
email_html_status($d_p,$translations),
|
||||
$ics?('rdv_cancel_'.$rdv['uuid_ical'].'_praticien.ics'):null,
|
||||
$ics,
|
||||
$ics?'CANCEL':null
|
||||
);
|
||||
if (!$okP) $warnings[]='mail_praticien';
|
||||
}
|
||||
|
||||
if ($statut === 'terminé') {
|
||||
// =========================
|
||||
// PUSH RDV — terminé (par secrétaire)
|
||||
// =========================
|
||||
try {
|
||||
// 🔔 Praticien : marqué terminé par secrétaire
|
||||
push_rdv_to_praticien(
|
||||
$pdo,
|
||||
(int)$praticien_id,
|
||||
$translations['push_rdv_done_praticien_title'] ?? 'Приём завершён',
|
||||
$translations['push_rdv_done_praticien_body'] ?? 'Секретарь отметил(а) приём как завершённый.',
|
||||
'/public/praticien/agenda.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'done',
|
||||
'from_role' => 'secretaire',
|
||||
'secretaire_id' => (int)$compteId,
|
||||
'client_id' => (int)$rdv['client_id'],
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
'date_rdv' => $rdv['date_rdv'],
|
||||
]
|
||||
);
|
||||
|
||||
// ✅ badges
|
||||
badge_rdv_inc($pdo, $clientCompteId, 1);
|
||||
badge_rdv_inc($pdo, $praticienCompteId, 1);
|
||||
|
||||
push_rdv_to_client(
|
||||
$pdo,
|
||||
(int)$rdv['client_id'],
|
||||
$translations['push_rdv_done_title'] ?? 'Приём завершён',
|
||||
$translations['push_rdv_done_body'] ?? 'Ваш визит завершён.',
|
||||
'/public/client/mes_rendez_vous.php',
|
||||
[
|
||||
'rdv_id' => (int)$rdv['id'],
|
||||
'action' => 'done',
|
||||
'from_role' => 'secretaire',
|
||||
'praticien_id' => (int)$praticien_id,
|
||||
]
|
||||
);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('push rdv done (secretaire) failed: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$d_c = [
|
||||
'hello_line'=>sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s',$cliFull),
|
||||
'intro'=>$translations['email_rdv_termine_message_client'] ?? 'Votre rendez-vous est marqué comme terminé. Merci pour votre confiance.',
|
||||
'who_label'=>$translations['praticien_label'] ?? 'Praticien',
|
||||
'who_value'=>$pratFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url' => 'https://uslugi.fr/public/client/mes_rendez_vous.php',
|
||||
'cta_label'=>$translations['avis_suite_consultation'] ?? 'Voir mes rendez-vous',
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
[$okC,$errC] = smtp_send(
|
||||
$rdv['email_client'],
|
||||
$translations['email_rdv_termine_client_sujet'] ?? 'Merci de votre visite',
|
||||
email_text_status($d_c,$translations),
|
||||
email_html_status($d_c,$translations)
|
||||
);
|
||||
if (!$okC) $warnings[]='mail_client';
|
||||
|
||||
$d_p = [
|
||||
'hello_line'=>($translations['email_bonjour_dr'] ?? 'Bonjour').' '.$pratFull,
|
||||
'intro'=>$translations['email_rdv_termine_message_praticien'] ?? 'Vous avez marqué ce rendez-vous comme terminé.',
|
||||
'who_label'=>$translations['client_label'] ?? 'Client',
|
||||
'who_value'=>$cliFull,
|
||||
'date_label'=>$translations['date_label'] ?? 'Date',
|
||||
'when'=>$when_a,
|
||||
'statut_label'=>$translations['statut_label'] ?? 'Statut',
|
||||
'statut_value'=>$statutLibelle,
|
||||
'cta_url'=>'https://uslugi.fr/public/praticien/agenda.php',
|
||||
'cta_label'=>$translations['consultez_espace_pour_details'] ?? 'Voir les détails',
|
||||
'footer'=>$translations['merci_simple'] ?? 'Merci.',
|
||||
];
|
||||
[$okP,$errP] = smtp_send(
|
||||
$rdv['email_praticien'],
|
||||
$translations['email_rdv_termine_praticien_sujet'] ?? 'Rendez-vous marqué terminé',
|
||||
email_text_status($d_p,$translations),
|
||||
email_html_status($d_p,$translations)
|
||||
);
|
||||
if (!$okP) $warnings[]='mail_praticien';
|
||||
}
|
||||
|
||||
$msg = $translations['statut_mis_a_jour'] ?? 'Statut mis à jour.';
|
||||
if ($warnings) $msg .= ' (mail: '.implode(',',$warnings).')';
|
||||
out(true, $msg, ['status'=>$statut]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('changer_statut (cabinet) fatal: '.$e->getMessage());
|
||||
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
|
||||
}
|
||||
Reference in new issue
Block a user