Files
maria_uslugi/public/praticien/changer_statut.php
T
2026-03-05 10:25:39 +01:00

650 lines
27 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../includes/init_praticien.php';
require_once __DIR__ . '/../../push/push_endpoint.php';
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);
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 error: '.$e->getMessage().($m->ErrorInfo ? ' | '.$m->ErrorInfo : ''));
// retry sans ICS si c'est la PJ
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()];
}
}
// =================== Contrôles d’entrée ===================
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') out(false, 'Méthode invalide.');
$praticien_id = $_SESSION['reference_id'] ?? null;
$id = (int)($_POST['id'] ?? 0);
$statut = (string)($_POST['statut'] ?? '');
$whitelist = ['prévu','confirmé','annulé','terminé'];
if (!$praticien_id || $id <= 0 || !in_array($statut, $whitelist, true)) {
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
}
// =================== Update DB sécurisé ===================
try {
// =========================
// SNAPSHOT AVANT UPDATE_STATUS (pour log)
// =========================
$beforeStmt = $pdo->prepare("
SELECT statut, date_rdv, duree_minutes, lieu
FROM rendez_vous
WHERE id = :id AND praticien_id = :pid
LIMIT 1
");
$beforeStmt->execute([':id'=>$id, ':pid'=>$praticien_id]);
$before = $beforeStmt->fetch(PDO::FETCH_ASSOC);
if (!$before) {
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
}
// Mise à jour statut
$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
// =========================
$beforeJson = json_encode([
'statut' => $before['statut'],
'date_rdv' => $before['date_rdv'],
'duree_minutes' => (int)$before['duree_minutes'],
'lieu' => $before['lieu'],
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$afterJson = json_encode([
'statut' => $statut,
'date_rdv' => $before['date_rdv'],
], 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
(:rid, 'update_status', :uid, 'praticien', :b, :a, NOW())
");
$logStmt->execute([
':rid' => (int)$id,
':uid' => (int)$praticien_id,
':b' => $beforeJson,
':a' => $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);
// AJOUT DE GARDE FOUS POUR CLIENT RENDEZ_VOUS
if (empty($rdv['client_id'])) {
error_log('changer_statut: client_id manquant pour RDV id='.$id);
}
// FIN DE GARDE FOUS
if (!$rdv) out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
// ✅ compte_id du client (pour compteur badge rdv)
$clientCompteId = 0;
if (!empty($rdv['client_id'])) {
$st = $pdo->prepare("SELECT id FROM compte WHERE role='client' AND reference_id=? LIMIT 1");
$st->execute([(int)$rdv['client_id']]);
$clientCompteId = (int)($st->fetchColumn() ?: 0);
}
// 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 = $rdv['prenom_client'].' '.$rdv['nom_client'];
$pratFull = $rdv['prenom_praticien'].' '.$rdv['nom_praticien'];
// ====== FORMAT (Онлайн/Очно + adresse si dispo) ======
$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 (Онлайн OU adresse, sinon Очно)
if ($lieu === 'online') {
$icsLocation = $onlineLabel; // "Онлайн"
} elseif ($lieu === 'offline') {
$icsLocation = $addr !== '' ? $addr : $cabinetLabel; // adresse si dispo, sinon "Очно"
} else {
$icsLocation = ''; // fallback neutre
}
// ======================================================
$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;
// ----- envois mails selon statut -----
$warnings=[];
// =====NOUVEAU BLOCK POUR CONTROLER LEZ RENDEZ-VOUS==============
//***************************************************************** */
// ===== HOOK SUIVI CLIENT — création/MAJ dossier + entrée auto =====
if ($statut === 'confirmé') {
// =========================
// PUSH RDV — confirmé (par praticien)
// =========================
try {
// ✅ Badge RDV : +1 pour le client (confirm)
if ($clientCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:cid, 0, 1)
ON DUPLICATE KEY UPDATE
rdv_unread = LEAST(99, rdv_unread + 1),
updated_at = NOW()
")->execute([':cid' => $clientCompteId]);
}
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' => 'praticien',
'praticien_id' => (int)$praticien_id,
'date_rdv' => $rdv['date_rdv'],
]
);
} catch (Throwable $e) {
error_log('push rdv confirm failed: '.$e->getMessage());
}
try {
$pdo->beginTransaction();
// 1) 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();
}
// 2) Créer entrée de suivi automatique (une fois par RDV confirmé)
// On évite les doublons si reconfirmation
$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é) failed: '.$ex->getMessage());
// on n’interrompt pas le flux principal (mails etc.)
}
// ================================================
// ===== FIN HOOK INSERTION CLIENT POUR SUIVIE=====
//=================================================
// Client
$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.',
];
// Ajout format dans le corps du mail (intro)
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';
// Praticien
$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.',
];
// Ajout format pour le praticien
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 praticien)
// =========================
try {
// ✅ Badge RDV : +1 pour le client (cancel)
if ($clientCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:cid, 0, 1)
ON DUPLICATE KEY UPDATE
rdv_unread = LEAST(99, rdv_unread + 1),
updated_at = NOW()
")->execute([':cid' => $clientCompteId]);
}
push_rdv_to_client(
$pdo,
(int)$rdv['client_id'],
$translations['push_rdv_cancel_praticien_title'] ?? 'Запись отменена',
$translations['push_rdv_cancel_praticien_body'] ?? 'Специалист отменил запись.',
'/public/client/mes_rendez_vous.php',
[
'rdv_id' => (int)$rdv['id'],
'action' => 'cancel',
'from_role' => 'praticien',
'praticien_id' => (int)$praticien_id,
'date_rdv' => $rdv['date_rdv'],
]
);
} catch (Throwable $e) {
error_log('push rdv cancel (praticien) 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 CANCEL — on met aussi la LOCATION correcte pour cohérence
$ics = !empty($rdv['uuid_ical'])
? ics_build($rdv['uuid_ical'],$start,$end,$tzStr,$summary,'',$icsLocation, $attBoth,'CANCEL','CANCELLED')
: null;
// Client
$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';
// Praticien
$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é
// =========================
try {
// ✅ Badge RDV : +1 pour le client (done)
if ($clientCompteId > 0) {
$pdo->prepare("
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
VALUES (:cid, 0, 1)
ON DUPLICATE KEY UPDATE
rdv_unread = LEAST(99, rdv_unread + 1),
updated_at = NOW()
")->execute([':cid' => $clientCompteId]);
}
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' => 'praticien',
'praticien_id' => (int)$praticien_id,
]
);
} catch (Throwable $e) {
error_log('push rdv done failed: '.$e->getMessage());
}
// Client
$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/avis.php?praticien_id='.(int)$praticien_id,
'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';
// Praticien
$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 fatal: '.$e->getMessage());
out(false, $translations['erreur_mise_a_jour'] ?? 'Erreur de mise à jour.');
}