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" ],