From f616065585c21ec10597223b911ac41c85818a4f Mon Sep 17 00:00:00 2001 From: Chaim Date: Fri, 3 Apr 2026 10:21:58 +0000 Subject: [PATCH] fix: dashlet detail shows actual items instead of empty alerts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cards "תיקים פתוחים" and "דיונים השבוע" showed counts but clicking revealed "אין פריטים להצגה" because card counts came from direct DB queries while detail filtered from alerts with stricter criteria. Added getOpenCaseItems() and getUpcomingHearingItems() to AlertCalculator, included them in summary API response, and wired dashlet to use them. Also: renamed extension to SmartAssistant, title to "שירה — עוזרת אישית". Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 4 +- .../src/views/dashlets/smart-assistant.js | 8 ++- .../Services/AlertCalculator.php | 72 +++++++++++++++++++ .../Services/SmartAssistantService.php | 46 ++++++------ manifest.json | 4 +- 5 files changed, 101 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index eff301e..75f2fbf 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ # SmartAssistant - עוזר חכם -**גרסה:** 1.0.3 | **מחבר:** klear | **EspoCRM:** >= 8.0.0 +**גרסה:** 1.1.0 | **מחבר:** klear | **EspoCRM:** >= 8.0.0 ## תיאור עוזר AI מאוחד למשרד עורכי דין. מספק ממשק צ'אט צף עם שני מצבי עבודה: **מצב משרד** (סקירה כללית, התראות, סטטיסטיקות) ו**מצב תיק** (סיוע מעמיק בתיק ספציפי). כולל מערכת זיכרון תיק מובנית, ביצוע פעולות באישור המשתמש, ואינטגרציה עם Stream של EspoCRM. -מחליף את CrmAssistant + OfficeAssistant (deprecated). - ## תלויות - Webhook חיצוני (n8n או דומה) לעיבוד AI - NetworkStorageIntegration / NextCloudIntegration (אופציונלי — לניתוח מסמכים) diff --git a/files/client/custom/modules/smart-assistant/src/views/dashlets/smart-assistant.js b/files/client/custom/modules/smart-assistant/src/views/dashlets/smart-assistant.js index 99adb08..49ad1aa 100644 --- a/files/client/custom/modules/smart-assistant/src/views/dashlets/smart-assistant.js +++ b/files/client/custom/modules/smart-assistant/src/views/dashlets/smart-assistant.js @@ -5,7 +5,7 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct name: 'SmartAssistant', getTitle: function () { - return this.translate('Smart Assistant', 'labels', 'SmartAssistant') || 'Smart Assistant'; + return 'שירה — עוזרת אישית'; }, templateContent: '' + @@ -45,6 +45,8 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct Espo.Ajax.getRequest('SmartAssistant/action/summary').then(function (response) { self._summary = response.summary || {}; self._alerts = response.alerts || []; + self._openCases = response.openCases || []; + self._upcomingHearings = response.upcomingHearings || []; self.renderCards(self._summary); self.showDetail(self._activeType); }).catch(function () {}); @@ -93,11 +95,11 @@ define('modules/smart-assistant/views/dashlets/smart-assistant', ['view'], funct if (type === 'overdue') { items = alerts.filter(function (a) { return a.type === 'overdue_task'; }); } else if (type === 'hearings') { - items = alerts.filter(function (a) { return a.type === 'hearing_no_prep'; }); + items = this._upcomingHearings || []; } else if (type === 'attention') { items = alerts.filter(function (a) { return a.severity === 'critical' || a.severity === 'warning'; }); } else if (type === 'open') { - items = alerts.filter(function (a) { return a.type === 'inactive_case' || a.type === 'unassigned_case'; }); + items = this._openCases || []; } if (items.length === 0) { diff --git a/files/custom/Espo/Modules/SmartAssistant/Services/AlertCalculator.php b/files/custom/Espo/Modules/SmartAssistant/Services/AlertCalculator.php index 03e51bb..8122299 100644 --- a/files/custom/Espo/Modules/SmartAssistant/Services/AlertCalculator.php +++ b/files/custom/Espo/Modules/SmartAssistant/Services/AlertCalculator.php @@ -243,6 +243,78 @@ class AlertCalculator return array_values($userCounts); } + public function getOpenCaseItems(int $limit = 30): array + { + $items = []; + $cases = $this->entityManager->getRDBRepository('Case') + ->select(['id', 'name', 'status', 'assignedUserName', 'cLastActivityAt', 'createdAt']) + ->where(['status!=' => self::CLOSED_STATUSES, 'deleted' => false]) + ->order('createdAt', 'DESC') + ->limit(0, $limit) + ->find(); + + $statusLabels = [ + 'New' => 'חדש', 'Assigned' => 'שובץ', 'Pending' => 'ממתין', + 'PendingHearing' => 'ממתין לדיון', 'PendingDecision' => 'ממתין להחלטה', + 'PendingResponse' => 'ממתין לתגובה', 'Negotiation' => 'משא ומתן', 'Suspended' => 'מושהה', + ]; + + foreach ($cases as $case) { + $status = $case->get('status'); + $label = $statusLabels[$status] ?? $status; + $assigned = $case->get('assignedUserName'); + + $items[] = [ + 'type' => 'open_case', + 'severity' => 'info', + 'caseId' => $case->get('id'), + 'caseName' => $case->get('name'), + 'message' => $case->get('name') . ' — ' . $label . ($assigned ? ' (' . $assigned . ')' : ''), + ]; + } + return $items; + } + + public function getUpcomingHearingItems(int $days = 7): array + { + $today = date('Y-m-d'); + $threshold = date('Y-m-d', strtotime("+{$days} days")); + $items = []; + + $cases = $this->entityManager->getRDBRepository('Case') + ->select(['id', 'name', 'cNextHearing', 'cCourt', 'assignedUserName']) + ->where([ + 'cNextHearing>=' => $today, + 'cNextHearing<=' => $threshold, + 'status!=' => self::CLOSED_STATUSES, + 'deleted' => false, + ]) + ->order('cNextHearing', 'ASC') + ->find(); + + foreach ($cases as $case) { + $daysUntil = (new \DateTime())->diff(new \DateTime($case->get('cNextHearing')))->days; + $court = $case->get('cCourt'); + $severity = ($daysUntil <= 1) ? 'warning' : 'info'; + + $msg = $case->get('name') . ' — דיון בעוד ' . $daysUntil . ' ימים'; + if ($court) { + $msg .= ' (' . $court . ')'; + } + + $items[] = [ + 'type' => 'upcoming_hearing', + 'severity' => $severity, + 'caseId' => $case->get('id'), + 'caseName' => $case->get('name'), + 'hearingDate' => $case->get('cNextHearing'), + 'daysUntil' => $daysUntil, + 'message' => $msg, + ]; + } + return $items; + } + public function getUpcomingHearings(int $days = 7): array { $today = date('Y-m-d'); diff --git a/files/custom/Espo/Modules/SmartAssistant/Services/SmartAssistantService.php b/files/custom/Espo/Modules/SmartAssistant/Services/SmartAssistantService.php index 351c0df..62a8021 100644 --- a/files/custom/Espo/Modules/SmartAssistant/Services/SmartAssistantService.php +++ b/files/custom/Espo/Modules/SmartAssistant/Services/SmartAssistantService.php @@ -250,10 +250,13 @@ class SmartAssistantService { $alertCalc = $this->injectableFactory->create(AlertCalculator::class); $thresholds = $this->getThresholds(); + $hearingDays = $thresholds['upcomingHearingDays'] ?? 7; return [ 'summary' => $alertCalc->getSummary(), 'alerts' => $alertCalc->calculateAlerts($thresholds), + 'openCases' => $alertCalc->getOpenCaseItems(), + 'upcomingHearings' => $alertCalc->getUpcomingHearingItems($hearingDays), ]; } @@ -416,14 +419,11 @@ class SmartAssistantService private function getWebhookUrl(): ?string { - // Try SmartAssistant first, then fallback to old integrations - foreach (['SmartAssistant', 'CrmAssistant', 'OfficeAssistant'] as $name) { - $integration = $this->entityManager->getEntityById('Integration', $name); - if ($integration && $integration->get('enabled')) { - $data = $integration->get('data') ?? (object) []; - if (!empty($data->webhookUrl)) { - return $data->webhookUrl; - } + $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant'); + if ($integration && $integration->get('enabled')) { + $data = $integration->get('data') ?? (object) []; + if (!empty($data->webhookUrl)) { + return $data->webhookUrl; } } return null; @@ -431,13 +431,11 @@ class SmartAssistantService private function getApiKey(): ?string { - foreach (['SmartAssistant', 'CrmAssistant', 'OfficeAssistant'] as $name) { - $integration = $this->entityManager->getEntityById('Integration', $name); - if ($integration && $integration->get('enabled')) { - $data = $integration->get('data') ?? (object) []; - if (!empty($data->apiKey)) { - return $data->apiKey; - } + $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant'); + if ($integration && $integration->get('enabled')) { + $data = $integration->get('data') ?? (object) []; + if (!empty($data->apiKey)) { + return $data->apiKey; } } return null; @@ -445,16 +443,14 @@ class SmartAssistantService private function getThresholds(): array { - foreach (['SmartAssistant', 'OfficeAssistant'] as $name) { - $integration = $this->entityManager->getEntityById('Integration', $name); - if ($integration && $integration->get('enabled')) { - $data = (array) ($integration->get('data') ?? []); - return [ - 'inactivityWarningDays' => $data['inactivityWarningDays'] ?? 14, - 'inactivityCriticalDays' => $data['inactivityCriticalDays'] ?? 30, - 'upcomingHearingDays' => $data['upcomingHearingDays'] ?? 7, - ]; - } + $integration = $this->entityManager->getEntityById('Integration', 'SmartAssistant'); + if ($integration && $integration->get('enabled')) { + $data = (array) ($integration->get('data') ?? []); + return [ + 'inactivityWarningDays' => $data['inactivityWarningDays'] ?? 14, + 'inactivityCriticalDays' => $data['inactivityCriticalDays'] ?? 30, + 'upcomingHearingDays' => $data['upcomingHearingDays'] ?? 7, + ]; } return []; } diff --git a/manifest.json b/manifest.json index 58cb20a..cf6d729 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { - "name": "Smart Assistant", + "name": "SmartAssistant", "module": "SmartAssistant", "description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and n8n integration", "author": "klear", - "version": "1.0.9", + "version": "1.1.0", "acceptableVersions": [ ">=8.0.0" ],