רכיב לוח זמנים לרופא ב-Bitrix: שמירה במטמון ואינטגרציה עם MIS

כשמטופלים לא רואים לוחות זמנים עדכניים של רופאים, הם פונים למתחרים או מבזבזים זמן בהתקשרות למוקד. אנחנו בונים רכיב תזמון עבור 1C-Bitrix שמסתנכרן עם ה-MIS שלך, נטען במהירות ומוצג כראוי בכל מכשיר. הצוות שלנו מספק פתרון מלא—מתכנון ארכיטקטורה ועד יישום ותמיכה שוטפת—ובכך מבטיח פעילות אמינה ללא פספוס מועדים.
השירותים שאנו מציעים
מציג 1 מתוך 1כל 1626 השירותים
רכיב לוח זמנים לרופא ב-Bitrix: שמירה במטמון ואינטגרציה עם MIS
פשוט
~1 יום

הכישורים שלנו:

שאלות נפוצות

רכיב לוח זמנים לרופאים ב-Bitrix: שילוב מטמון ו-MIS

עמוד רופא עם טקסט "הזמנה בטלפון" מאבד תנועה מקוונת. משתמשים רוצים לראות ימים ושעות זמינים ספציפיים, לא להתקשר לדלפק הקבלה. הצגת לוח זמנים היא משימה נפרדת מהזמנה מקוונת: לוח הזמנים חייב להיות ברור, מהיר ועדכני, גם אם כפתור "הזמנה" מוביל לשיחת טלפון. אנו מתכננים את הארכיטקטורה כך שהנתונים נטענים מה-MIS או מבלוקים בעלי עומס גבוה, נשמרים במטמון עם ביטול אוטומטי, ומוצגים בממשק אדפטיבי. מאמר זה מכסה את הגישה שלנו עם קוד אמיתי ומקרים למרפאות עם 50+ רופאים.

למה פתרונות מוכנים לא עובדים

רוב המודולים המוכנים ב-Marketplace מציגים טבלה ריקה או דורשים הזנת משבצות ידנית. הם לא משתלבים עם ה-MIS הפנימי שלך, לא מטפלים בכללי קביעת פגישות מורכבים (דפוסים חוזרים, ימי חופש). התוצאה היא נתונים מיושנים או זמני טעינת עמוד עד 10 שניות.

אילו בעיות אנו פותרים

עקביות נתונים

לוח הזמנים חייב להסתנכרן עם ה-MIS (1C:Medicine, MedMix, iMed) דרך REST או SQL. אם מתרחש שינוי בלוח הזמנים ב-MIS, הוא מופיע באתר בתוך תקופת ה-TTL של המטמון (2–5 דקות), לא אחרי יום.

ביצועים

עמוד המפרט 50+ רופאים לא אמור לקחת 10 שניות לטעינה. בפרויקט אחרון צמצמנו את זמן הטעינה מ-3.2 שניות ל-0.4 שניות. אנו משתמשים בשאילתת SQL אחת לתאריך הקרוב, שומרים את התוצאה במטמון עם תגיות. לניווט AJAX, אנו מעדכנים ברקע באמצעות \Bitrix\Main\Data\Cache.

התאמה למובייל

מחוון תאריכים במובייל אינו רק גלילה אופקית—זה רכיב עם אירועי מגע. לשולחן העבודה, רשת שבועית עם תאים ירוקים.

כיצד אנו מיישמים את רכיב לוח הזמנים

טכנולוגיה: PHP 8.1, Bitrix ORM, בלוקים בעלי עומס גבוה (local_doctor_slots), מטמון מתויג. רכיב local:doctor.schedule עם פרמטרים DOCTOR_ID, WEEKS_AHEAD, VIEW_TYPE. אלגוריתם טעינת משבצות: כל משבצת היא רשומת בלוק בעל עומס גבוה עם שדות DOCTOR_ID, SLOT_DATE, SLOT_TIME, STATUS, PATIENT_ID (אם מוזמן), SOURCE (ידני/API). בלוקים בעלי עומס גבוה מסננים פי 5–10 מהר יותר מבלוקי מידע על מערכי נתונים גדולים (50k+ רשומות).

לבחירת תבנית אנו מתחשבים בסוג הרופא. ללוחות זמנים צפופים (מטפלים), רשת שבועית המציגה את כל המשבצות עובדת הכי טוב. למומחים צרים (מנתח, נוירולוג), רשימה קומפקטית של תאריכים קרובים מתאימה יותר—היא מסתירה תאים ריקים ומתמקדת בחלונות זמינים. במכשירים ניידים אנו משתמשים במחוון תאריכים עם אירועי מגע; זה אינטואיטיבי אך מטיל עומס JS כבד יותר.

קוד הרכיב

/local/components/local/doctor.schedule/class.php:

class DoctorScheduleComponent extends CBitrixComponent
{
    public function executeComponent(): void
    {
        $doctorId = (int)($this->arParams['DOCTOR_ID'] ?? 0);
        $weeksAhead = (int)($this->arParams['WEEKS_AHEAD'] ?? 2);

        if (!$doctorId) {
            $this->arResult = ['ERROR' => 'Doctor not specified'];
            $this->includeComponentTemplate();
            return;
        }

        $dateFrom = new \DateTime();
        $dateTo = (clone $dateFrom)->modify("+{$weeksAhead} weeks");

        $slots = $this->loadSlots($doctorId, $dateFrom, $dateTo);

        $scheduleByDate = [];
        foreach ($slots as $slot) {
            $date = $slot['SLOT_DATE'];
            if (!isset($scheduleByDate[$date])) {
                $scheduleByDate[$date] = [
                    'date' => $date,
                    'day_name' => $this->getDayName(new \DateTime($date)),
                    'free_count' => 0,
                    'slots' => [],
                ];
            }
            $scheduleByDate[$date]['slots'][] = $slot;
            if ($slot['STATUS'] === 'free') {
                $scheduleByDate[$date]['free_count']++;
            }
        }

        $nextFreeSlot = $this->getNextFreeSlot($slots);

        $this->arResult = [
            'DOCTOR_ID' => $doctorId,
            'SCHEDULE' => $scheduleByDate,
            'NEXT_FREE_SLOT' => $nextFreeSlot,
            'DATE_FROM' => $dateFrom->format('Y-m-d'),
            'DATE_TO' => $dateTo->format('Y-m-d'),
        ];

        $this->setResultCacheKeys(['SCHEDULE', 'NEXT_FREE_SLOT']);
        $this->includeComponentTemplate();
    }

    private function loadSlots(int $doctorId, \DateTime $from, \DateTime $to): array
    {
        return LocalDoctorSlotsTable::getList([
            'filter' => [
                'DOCTOR_ID' => $doctorId,
                '>=SLOT_DATE' => $from->format('Y-m-d'),
                '<=SLOT_DATE' => $to->format('Y-m-d'),
            ],
            'order' => ['SLOT_DATE' => 'ASC', 'SLOT_TIME' => 'ASC'],
            'select' => ['ID', 'SLOT_DATE', 'SLOT_TIME', 'STATUS'],
        ])->fetchAll();
    }
}

מטמון

נתוני לוח הזמנים משתנים עם כל הזמנה חדשה. אנו שומרים במטמון עם ביטול אוטומטי:

$this->arParams['CACHE_TYPE'] = 'A';
$this->arParams['CACHE_TIME'] = 120;
// При создании записи сбрасываем кеш компонента для врача
\CBitrixComponent::clearComponentCache('local:doctor.schedule', '', ['DOCTOR_ID' => $doctorId]);

לבקשות AJAX בעת מעבר בין שבועות, אנו משתמשים במטמון נפרד דרך class DoctorScheduleComponent extends CBitrixComponent { public function executeComponent(): void { $doctorId = (int)($this->arParams['DOCTOR_ID'] ?? 0); $weeksAhead = (int)($this->arParams['WEEKS_AHEAD'] ?? 2); if (!$doctorId) { $this->arResult = ['ERROR' => 'Doctor not specified']; $this->includeComponentTemplate(); return; } $dateFrom = new \DateTime(); $dateTo = (clone $dateFrom)->modify("+{$weeksAhead} weeks"); $slots = $this->loadSlots($doctorId, $dateFrom, $dateTo); $scheduleByDate = []; foreach ($slots as $slot) { $date = $slot['SLOT_DATE']; if (!isset($scheduleByDate[$date])) { $scheduleByDate[$date] = [ 'date' => $date, 'day_name' => $this->getDayName(new \DateTime($date)), 'free_count' => 0, 'slots' => [], ]; } $scheduleByDate[$date]['slots'][] = $slot; if ($slot['STATUS'] === 'free') { $scheduleByDate[$date]['free_count']++; } } $nextFreeSlot = $this->getNextFreeSlot($slots); $this->arResult = [ 'DOCTOR_ID' => $doctorId, 'SCHEDULE' => $scheduleByDate, 'NEXT_FREE_SLOT' => $nextFreeSlot, 'DATE_FROM' => $dateFrom->format('Y-m-d'), 'DATE_TO' => $dateTo->format('Y-m-d'), ]; $this->setResultCacheKeys(['SCHEDULE', 'NEXT_FREE_SLOT']); $this->includeComponentTemplate(); } private function loadSlots(int $doctorId, \DateTime $from, \DateTime $to): array { return LocalDoctorSlotsTable::getList([ 'filter' => [ 'DOCTOR_ID' => $doctorId, '>=SLOT_DATE' => $from->format('Y-m-d'), '<=SLOT_DATE' => $to->format('Y-m-d'), ], 'order' => ['SLOT_DATE' => 'ASC', 'SLOT_TIME' => 'ASC'], 'select' => ['ID', 'SLOT_DATE', 'SLOT_TIME', 'STATUS'], ])->fetchAll(); } } .

מנגנון מטמון מפורטהרכיב משתמש במטמון מבוסס תגיות עם ניקוי אוטומטי באירועי בלוק בעל עומס גבוה. תג מטמון מוקצה לכל רופא. כאשר משבצת נוצרת או מתעדכנת דרך פאנל הניהול או API, תג המטמון מבוטל, מה שמבטיח שהבקשה הבאה תקבל נתונים טריים. ה-TTL של 120 שניות מספק איזון בין טריות לעומס.

תבנית: רשת שבועית

$this->arParams['CACHE_TYPE'] = 'A'; $this->arParams['CACHE_TIME'] = 120; // При создании записи сбрасываем кеш компонента для врача \CBitrixComponent::clearComponentCache('local:doctor.schedule', '', ['DOCTOR_ID' => $doctorId]); :

$today = new \DateTime();
$daysOfWeek = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
?>
<div class="doctor-schedule" data-doctor-id="<?= $arResult['DOCTOR_ID'] ?>">
    <div class="schedule-nav">
        <button class="schedule-prev" data-offset="-7">← Предыдущая неделя</button>
        <button class="schedule-next" data-offset="7">Следующая неделя →</button>
    </div>
    <div class="schedule-grid">
        <?php foreach ($arResult['SCHEDULE'] as $dateStr => $dayData): ?>
            <?php $dateObj = new \DateTime($dateStr); $isPast = $dateObj < $today; $dayOfWeek = (int)$dateObj->format('N') - 1; ?>
            <div class="schedule-day <?= $isPast ? 'past' : '' ?> <?= $dayData['free_count'] > 0 ? 'has-slots' : 'no-slots' ?>">
                <div class="day-header">
                    <span class="day-name"><?= $daysOfWeek[$dayOfWeek] ?></span>
                    <span class="day-date"><?= $dateObj->format('d.m') ?></span>
                </div>
                <?php if ($dayData['free_count'] > 0): ?>
                    <div class="slots-container">
                        <?php foreach ($dayData['slots'] as $slot): ?>
                            <?php if ($slot['STATUS'] === 'free'): ?>
                                <button class="slot-btn free" data-slot-id="<?= $slot['ID'] ?>" data-time="<?= substr($slot['SLOT_TIME'], 0, 5) ?>">
                                    <?= substr($slot['SLOT_TIME'], 0, 5) ?>
                                </button>
                            <?php endif; ?>
                        <?php endforeach; ?>
                    </div>
                    <div class="day-free-count"><?= $dayData['free_count'] ?> места</div>
                <?php else: ?>
                    <div class="no-slots-label">Нет записи</div>
                <?php endif; ?>
            </div>
        <?php endforeach; ?>
    </div>
    <?php if ($arResult['NEXT_FREE_SLOT']): ?>
        <div class="next-available">
            Ближайшая свободная запись: <strong><?= date('d.m.Y', strtotime($arResult['NEXT_FREE_SLOT']['SLOT_DATE'])) ?></strong> в <strong><?= substr($arResult['NEXT_FREE_SLOT']['SLOT_TIME'], 0, 5) ?></strong>
        </div>
    <?php endif; ?>
</div>

טעינת AJAX למעבר בין שבועות

document.querySelectorAll('.schedule-prev, .schedule-next').forEach(btn => {
  btn.addEventListener('click', async function() {
    const doctorId = document.querySelector('.doctor-schedule').dataset.doctorId;
    const offset = parseInt(this.dataset.offset);
    const dateFrom = new Date(currentDateFrom);
    dateFrom.setDate(dateFrom.getDate() + offset);
    const res = await fetch('/local/ajax/doctor-schedule.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        doctor_id: doctorId,
        date_from: dateFrom.toISOString().split('T')[0],
        sessid: BX.bitrix_sessid()
      })
    });
    const data = await res.json();
    renderScheduleGrid(data.schedule);
    currentDateFrom = dateFrom;
  });
});

הצגת לוח זמנים בעמוד רשימת הרופאים

בעמוד קטלוג הרופאים, אין צורך בלוח זמנים מלא—רק אינדיקטור "הפגישה הבאה: מחר". זו שאילתת SQL אחת על כל הרופאים:

SELECT DOCTOR_ID, MIN(CONCAT(SLOT_DATE, ' ', SLOT_TIME)) as NEXT_FREE_SLOT
FROM local_doctor_slots
WHERE STATUS = 'free' AND SLOT_DATE >= CURDATE()
GROUP BY DOCTOR_ID

כיצד לשלב את לוח הזמנים עם ה-MIS?

שילוב MIS הוא שלב מפתח. אם ה-MIS מספק REST API, אנו מגדירים סוכן עם תקופה של 1–5 דקות. עם גישת SQL ישירה, אנו יוצרים תצוגה מהותית או טריגרים. בכל מקרה, לאחר הסנכרון אנו מבטלים את המטמון המתויג עבור הרופא המתאים.

תהליך היישום

  • ניתוח דרישות ועיצוב מודל נתוני משבצות.
  • פיתוח סכמת בלוק בעל עומס גבוה עם אינדוקס.
  • קידוד רכיב (רשת שבועית + תבניות רשימה).
  • יישום AJAX ומחוון תאריכים למובייל.
  • שילוב MIS (REST או SQL) עם סוכן סנכרון.
  • כוונון מטמון ובדיקות עומס (נבדקו מעל 100 רופאים).
  • פריסה ותיעוד.

מה כלול בתוצרים

  • עיצוב סכמת בלוק בעל עומס גבוה ומערכת מטמון.
  • רכיב \Bitrix\Main\Data\Cache עם שתי תבניות (רשת שבועית ורשימה).
  • טעינת AJAX ומחוון תאריכים למובייל.
  • שילוב MIS (REST/SQL) וסוכן סנכרון.
  • בדיקות עם נתונים אמיתיים (50+ רופאים) ובדיקות עומס.
  • תיעוד תפעול וניהול.
  • גישת ניהול והדרכה.
  • חודש תמיכה לאחר ההשקה.

השוואת תבניות

פרמטר רשת שבועית רשימת תאריכים
מתאים ביותר ל לוחות זמנים צפופים (מטפלים) משבצות דלילות (מנתחים)
רמת מידע מציג את כל המשבצות המוזמנות והפנויות מתמקד בתאריכים זמינים
התאמה למובייל מחוון תאריכים עם אירועי מגע רשימה אנכית
מהירות טעינה דורש יותר נתונים (כל המשבצות) פחות נתונים (רק תאריכים)

ביצועים: בלוקים בעלי עומס גבוה לעומת בלוקי מידע

פרמטר בלוקים בעלי עומס גבוה בלוקי מידע
זמן שאילתה ל-50k רשומות ~150 אלפיות שנייה ~800 אלפיות שנייה
גמישות אינדוקס אינדקסים על כל שדה רק אינדקסים סטנדרטיים
מורכבות שילוב ORM פשוט דורש שדות מטא
מתאים ל נתונים טבלאיים (משבצות) נתוני תוכן (חדשות)

לפי תיעוד Bitrix (https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&CHAPTER_ID=04225), בלוקים בעלי עומס גבוה מותאמים לנתונים טבלאיים ומבצעים פי 5–10 מהר יותר מבלוקי מידע.

טעויות יישום אופייניות

  • אחסון לוח זמנים בבלוק מידע (סינון איטי). אנו משתמשים בבלוקים בעלי עומס גבוה—ORM עובד מהר יותר, קל יותר להגדיר אינדקסים.
  • אי ביטול מטמון בעת הזמנה דרך פאנל הניהול או API. אנו משתמשים באירועי בלוק בעל עומס גבוה templates/.default/template.php.
  • התעלמות מאזורי זמן. רופאים עשויים לעבוד בסניפים שונים—אחסן שעות ב-UTC, המר בצד הלקוח.

איך להתחיל?

יישמנו 20+ פרויקטים למרפאות עם אחריות ביצועים. עלות הפיתוח מתחילה מ-$1,200 לרכיב. צור קשר כדי לדון בפרויקט שלך ונוכל להציג הדגמה עם הנתונים שלך. קבל ייעוץ.