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

686 lines
28 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_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.');
}