$ok, 'message'=>$message] + $extra); exit; } // ------- helpers e-mail HTML/TXT ------- function email_html_status(array $d, array $t): string { $brand = 'USLUGI.FR'; $brand_bas ='УСЛУГИ.ФР'; return 'USLUGI.FR
'. (!empty($d['cta_url']) ? '' : ''). '
'.$brand.'
'.nl2br(htmlspecialchars($d['hello_line'])).'
'.nl2br(htmlspecialchars($d['intro'])).'
'.htmlspecialchars($d['who_label']).''.htmlspecialchars($d['who_value']).'
'.htmlspecialchars($d['date_label']).''.htmlspecialchars($d['when']).'
'.htmlspecialchars($d['statut_label']).''.htmlspecialchars($d['statut_value']).'
'.htmlspecialchars($d['cta_label']).'
'.nl2br(htmlspecialchars($d['footer'])).'
© '.date('Y').' '.$brand_bas.' — '.htmlspecialchars($t['merci_simple'] ?? 'Merci').'
'; } 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.'); }