From 00303df95b02c142fffdb478ac177b5f16850af2 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Wed, 15 Apr 2026 11:38:23 +0530 Subject: [PATCH] fix(slots): hide past slots today even on cache hit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous flow cached the unfiltered slot list AND applied the "hide past slots" filter — but only on the fresh-fetch path. A cache hit returned the stored list untouched, so by lunchtime agents saw morning slots that had already passed. Refactored into a post-cache filterPastSlotsForToday() helper applied on both cache-hit and fresh paths. Cache stores the full day's slots (keyed by doctorId + dayOfWeek), so same-weekday reuse across weeks stays correct. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/masterdata/masterdata.service.ts | 42 ++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/masterdata/masterdata.service.ts b/src/masterdata/masterdata.service.ts index 021ad63..e2cea51 100644 --- a/src/masterdata/masterdata.service.ts +++ b/src/masterdata/masterdata.service.ts @@ -124,8 +124,12 @@ export class MasterdataService implements OnModuleInit { const dayOfWeek = new Date(date).toLocaleDateString('en-US', { weekday: 'long' }).toUpperCase(); const cacheKey = `masterdata:slots:${doctorId}:${dayOfWeek}`; + // Cache stores the UNFILTERED full-day slot list (keyed by dayOfWeek, + // so it's reusable across dates that fall on the same weekday). The + // "hide past slots on today" filter is applied AFTER cache read so it + // stays correct as real-time advances without cache churn. const cached = await this.cache.getCache(cacheKey); - if (cached) return JSON.parse(cached); + if (cached) return this.filterPastSlotsForToday(JSON.parse(cached), date); const auth = `Bearer ${this.apiKey}`; const data = await this.platform.queryWithAuth( @@ -169,21 +173,35 @@ export class MasterdataService implements OnModuleInit { // Sort by time slots.sort((a, b) => a.time.localeCompare(b.time)); + // Cache the full UNFILTERED list so reuse across dates (same dayOfWeek) + // doesn't mis-serve filtered data from an earlier date. await this.cache.setCache(cacheKey, JSON.stringify(slots), CACHE_TTL); this.logger.log(`Generated ${slots.length} slots for doctor ${doctorId} on ${dayOfWeek}`); - // Filter out past time slots when the requested date is today (IST). - // Cache stores the full day's slots — filtering happens post-cache - // so slots become available as the day progresses without cache churn. - const todayIST = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Kolkata' }); - if (date === todayIST) { - const nowHHMM = new Date().toLocaleTimeString('en-GB', { timeZone: 'Asia/Kolkata', hour: '2-digit', minute: '2-digit' }); - const filtered = slots.filter(s => s.time >= nowHHMM); - this.logger.log(`[SLOTS] Today filter: ${slots.length} total → ${filtered.length} remaining (now=${nowHHMM} IST)`); - return filtered; - } + return this.filterPastSlotsForToday(slots, date); + } - return slots; + // When the requested date is today (IST), hide slots whose time has + // already passed (30-min buffer so we don't offer the impossible-to-keep + // "in 5 minutes" slot). Applies to both cache-hit and fresh fetch paths. + private filterPastSlotsForToday( + slots: Array<{ time: string; label: string; clinicId: string; clinicName: string }>, + date: string, + ): Array<{ time: string; label: string; clinicId: string; clinicName: string }> { + const todayIst = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Kolkata' }); + if (date !== todayIst) return slots; + + const nowHHMM = new Date().toLocaleTimeString('en-GB', { + timeZone: 'Asia/Kolkata', hour: '2-digit', minute: '2-digit', + }); + const [nowH, nowM] = nowHHMM.split(':').map(Number); + const cutoff = nowH * 60 + nowM + 30; // 30-min buffer + const filtered = slots.filter((s) => { + const [h, m] = s.time.split(':').map(Number); + return h * 60 + m >= cutoff; + }); + this.logger.log(`[SLOTS] Today filter: ${slots.length} → ${filtered.length} (now=${nowHHMM} IST, cutoff=${Math.floor(cutoff / 60)}:${String(cutoff % 60).padStart(2, '0')})`); + return filtered; } async invalidateAll(): Promise {