Compare commits

..

17 Commits

Author SHA1 Message Date
chaim 763638abd5 fix: add non-breaking space after checkbox characters
PHPWord's TemplateProcessor loses regular spaces between placeholder
replacement values and adjacent text in RTL documents. Using NBSP
(\u00A0) after ☑/☐ ensures Word preserves the spacing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:21:44 +00:00
chaim 99bc442495 fix: handle stdClass legalAnalysis in DirectAccessReportGenerator
When legalAnalysis arrives as a stdClass (from JSON decode), cast it to
array before using array access. Prevents fatal error in PHP 8.x.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 20:59:14 +00:00
chaim 61fe16a0ea fix: enforce legal analysis step and correct recommendation selection
- Prompt: CRITICAL instruction — must complete all 7 steps before generating report
- Step 5 (legal analysis) is mandatory, cannot be skipped even if user asks
- Step 6: explicit instruction to use "המשך ייצוג" when user is filing/continuing
- Prevent AI from choosing "עדיין לא ניתן להחליט" when user has already decided

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:49:44 +00:00
chaim 14979dba4b fix: direct access report placeholder mismatches and missing recommendation option
- Fix cLegalAidNumber placeholder key mismatch (template expects cLegalAidNumber, code sent legalAidNumber)
- Add fallback for flat legalAnalysis fields (AI may send them outside the nested object)
- Improve assignedUserName resolution: prefer case assignee, filter out system/API user names
- Add "המשך ייצוג" positive recommendation option to enum, checkboxes, prompt, and DOCX template

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:30:19 +00:00
chaim 015586a9df fix: resolve report template from WebDAV when not found locally
DirectAccessReportGenerator now downloads the template from NetworkStorage
(WebDAV) if it's not found in data/document-templates/. This means template
updates via WebDAV are picked up automatically without manual file copy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:41:26 +00:00
chaim 3992384d96 fix: correct tool name in prompts from generate_initial_report to generate_direct_access_report
The AI prompt referenced the old tool name (generate_initial_report) but the
metadata and handler were registered as generate_direct_access_report, causing
404 errors when Shira tried to generate direct access reports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:06:06 +00:00
chaim cf523cbd86 fix: resolve template path and PHPWord autoload in DirectAccessReportGenerator
- Fix resolveTemplatePath to check data/document-templates/ prefix
- Add PHPWord autoloader fallback when not in composer autoload
- Fix Document-Case linking (use Case.documents relation side)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:24:17 +00:00
chaim 21cf570cbc feat!: v2.0.0 — replace LegalAid entity with direct JSON-to-DOCX generation
Breaking change: LegalAid entity removed entirely. Reports are now
generated directly from JSON params to DOCX without any intermediate
entity or database table.

New:
- DirectAccessReportGenerator: takes JSON from Shira, generates DOCX
  via PHPWord, attaches as Document to Case (single PHP file)
- AfterUninstall: full cleanup script (drops tables, columns, templates)
- docs/examples/entity-reference-legalaid.md: full reference for future
  entity creation

Removed:
- LegalAid entity (entityDefs, scopes, clientDefs, layouts, i18n, controller)
- GenerateInitialReport tool (replaced by DirectAccessReportGenerator)
- Custom JS field view for cLegalAidNumber
- update_legal_aid and get_legal_aid tools
- Case link to legalAids

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:12:28 +00:00
chaim 4c0586cce8 feat: remove DirectAccessReport, keep only LegalAid entity
Complete removal of the old DirectAccessReport entity and all related
files. LegalAid is now the sole entity for legal aid data management.

- Remove DirectAccessReport: controller, service, hooks, entity defs,
  scopes, clientDefs, layouts, i18n (all 3 locales)
- Remove directAccessReports link from Case
- Rewrite GenerateInitialReport tool to work directly with LegalAid
- Clean up Case i18n (remove old field translations)
- Bump version to 1.5.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:44:24 +00:00
chaim b3c219aa57 feat: add LegalAid entity — centralized legal aid data management
Replace scattered Case fields with a dedicated LegalAid entity that
holds all legal aid data (aid type, urgency, client contact, proceeding
details, legal analysis, recommendation, etc.). DirectAccessReport now
pulls from LegalAid for DOCX generation with automatic checkbox mapping.

- Add LegalAid entity with controller, layouts, i18n (he/en/fa)
- Remove cAidType, cUrgencyLevel, cAppointmentDate, cFilingDeadline,
  cLegalAidType from Case (migrated to LegalAid)
- Add legalAidType field to LegalAid (DirectAccess/Regular/Duty)
- Update DirectAccessReportService to use LegalAid entity
- Add update_legal_aid and get_legal_aid SmartAssistant tools
- Fix AfterInstall template entityType to LegalAid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:27:23 +00:00
chaim 31e6359df4 fix: update direct access report template
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:41:55 +00:00
chaim d4008569ca fix: add missing DirectAccessReport controller class
EspoCRM requires an explicit controller class for every entity — without
it the API returns 404. Added a simple Record controller for DirectAccessReport.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:31:00 +00:00
chaim 836594a863 chore: bump version to 1.3.1 for reinstall
Version 1.2.3 was installed on EspoCRM but DirectAccessReport entity
was returning 404. Bumped to 1.3.1 to allow clean upgrade install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:10:51 +00:00
chaim 19db7c1093 feat: AfterInstall creates custom i18n directories for all locales
Ensures custom/Espo/Custom/Resources/i18n/{locale}/ exists for
en_US, fa_IR, he_IL, and the system language. Without this, Label
Manager fails with 500 on fresh installs using fa_IR (Hebrew RTL
wrapper) because the directory doesn't exist and EspoCRM doesn't
create it automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:27:44 +00:00
chaim ee55f1ecef fix: repair broken Unicode characters in entityDefs and PHP service
- Case.json: "תחום א��ר" → "תחום אחר", "��הול" → "בהול"
- DirectAccessReportService.php: "גישה ישיר��" → "גישה ישירה", broken em-dash

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:20:29 +00:00
chaim 0cca55da0c fix: translate $LegalAssistance panel tab via "tabs" i18n key
Bottom panel tab labels with $ prefix resolve via translate(key, 'tabs', scope),
not 'labels'. Added "tabs" section to Case.json (fa_IR/he_IL/en_US).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:07:51 +00:00
chaim ca3d5e7890 fix: add Hebrew translation for LegalAssistance panel tab label
Translates the bottom panel tab "$LegalAssistance" → "סיוע משפטי"
in Case detail view (fa_IR + he_IL + en_US).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:54:14 +00:00
34 changed files with 2091 additions and 1336 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -1,76 +0,0 @@
<?php
namespace Espo\Modules\LegalAssistance\Hooks\Case;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Log;
class CreateDirectAccessTasks
{
public function __construct(
private EntityManager $entityManager,
private Log $log
) {}
public function afterSave(Entity $entity, array $options): void
{
if (!empty($options['skipHooks'])) {
return;
}
if (!$entity->isAttributeChanged('cLegalAidType')) {
return;
}
$newValue = $entity->get('cLegalAidType');
$oldValue = $entity->getFetched('cLegalAidType');
if ($newValue !== 'DirectAccess' || $oldValue === 'DirectAccess') {
return;
}
$this->log->info("LegalAssistance: Creating direct access tasks for case {$entity->getId()}");
$assignedUserId = $entity->get('assignedUserId');
$now = new \DateTime('now', new \DateTimeZone('Asia/Jerusalem'));
$tasks = [
[
'name' => 'שיחת טלפון ראשונה עם הלקוח',
'dateEnd' => (clone $now)->modify('+2 days')->format('Y-m-d'),
'priority' => 'High',
'description' => 'יש ליצור קשר ראשוני עם הלקוח תוך 48 שעות ממועד קבלת המינוי.',
],
[
'name' => 'פגישה עם הלקוח',
'dateEnd' => (clone $now)->modify('+7 days')->format('Y-m-d'),
'priority' => 'Normal',
'description' => 'לקבוע ולקיים פגישה ראשונה עם הלקוח. לתעד נוכחים ותוכן הפגישה.',
],
[
'name' => 'שליחת דוח גישה ישירה',
'dateEnd' => (clone $now)->modify('+14 days')->format('Y-m-d'),
'priority' => 'High',
'description' => 'למלא ולשלוח דוח דיווח ראשוני לסיוע המשפטי. ניתן לבקש משירה: "תיצרי דוח גישה ישירה".',
],
];
foreach ($tasks as $taskData) {
$task = $this->entityManager->getNewEntity('Task');
$task->set([
'name' => $taskData['name'],
'status' => 'Not Started',
'priority' => $taskData['priority'],
'dateEnd' => $taskData['dateEnd'],
'description' => $taskData['description'],
'parentType' => 'Case',
'parentId' => $entity->getId(),
'assignedUserId' => $assignedUserId,
]);
$this->entityManager->saveEntity($task, ['skipHooks' => false]);
}
$this->log->info("LegalAssistance: Created 3 direct access tasks for case {$entity->getId()}");
}
}
@@ -1,34 +1,8 @@
{
"fields": {
"cLegalAidType": "Legal Aid Type",
"cAppointmentDate": "Appointment Date",
"cAidType": "Aid Type",
"cUrgencyLevel": "Urgency Level",
"cFilingDeadline": "Filing Deadline"
"labels": {
"LegalAssistance": "Legal Assistance"
},
"options": {
"cLegalAidType": {
"": "",
"DirectAccess": "Direct Access",
"Regular": "Regular",
"Duty": "Duty"
},
"cAidType": {
"": "",
"הגשת ערעור": "Filing Appeal",
"יעוץ והדרכה": "Counseling & Guidance",
"סיוע נוסף ביטוח לאומי": "Additional NI Aid",
"סיוע נוסף תחום אחר": "Additional Aid Other",
"אי מתן סיוע": "No Aid Provided"
},
"cUrgencyLevel": {
"": "",
"רגיל": "Normal",
"דחוף": "Urgent",
"בהול": "Critical"
}
},
"links": {
"directAccessReports": "Direct Access Reports"
"tabs": {
"LegalAssistance": "Legal Assistance"
}
}
@@ -1,150 +0,0 @@
{
"scopeName": "Direct Access Report",
"scopeNamesPlural": "Direct Access Reports",
"labels": {
"Create DirectAccessReport": "Create Direct Access Report"
},
"fields": {
"name": "Report Name",
"number": "Number",
"status": "Status",
"case": "Case",
"aidType": "Aid Type",
"urgencyLevel": "Urgency Level",
"appointmentDate": "Appointment Date",
"filingDeadline": "Filing Deadline",
"additionalUrgency": "Additional Urgency",
"additionalDeadline": "Additional Deadline",
"firstContactDate": "First Contact Date",
"contactDelayReason": "Contact Delay Reason",
"meetingDate": "Meeting Date",
"meetingDelayReason": "Meeting Delay Reason",
"meetingAttendees": "Meeting Attendees",
"contactStatus": "Contact Status",
"contactStatusDetail": "Contact Status Detail",
"proceedingNumber": "Proceeding Number",
"courtName": "Court Name",
"proceedingSubject": "Proceeding Subject",
"claimSummary": "Claim Summary",
"defenseClaims": "Defense Claims",
"q1DocsReviewed": "Committee Reviewed All Documents?",
"q1DocsDetail": "Documents Detail",
"q2Reasoned": "Decision Reasoned?",
"q2ReasonDetail": "Missing Reasoning Detail",
"q3PanelMatch": "Panel Matches Impairments?",
"q4PanelComposition": "Panel Composition",
"q5ComplaintsAddressed": "All Complaints Addressed?",
"q6ComplaintsDetail": "Missing Complaints Detail",
"q7ClinicalExam": "Clinical Exam Performed?",
"q8ClinicalGap": "Clinical vs Documents Gap",
"q9MoharaApplied": "Mohara Doctrine Applied?",
"q9MoharaDetail": "Missing Mohara Data",
"q10RehabGap": "Rehab Officer vs Committee Gap?",
"q10RehabDetail": "Gap Detail",
"q11ScoreGap": "Score vs Condition Gap?",
"q11ScoreDetail": "Score Gap Detail",
"q12UniqueAspects": "Unique Aspects Addressed?",
"q12UniqueDetail": "Unique Aspects Detail",
"recommendationType": "Recommendation Type",
"recommendationDetail": "Recommendation Detail",
"additionalAidNeeded": "Additional Aid Needed",
"additionalAidNIDetail": "Additional NI Aid Detail",
"additionalAidOtherDetail": "Additional Other Aid Detail",
"additionalAidUrgency": "Additional Aid Urgency",
"additionalAidDeadlines": "Additional Aid Deadlines",
"willingToRepresent": "Willing to Represent?",
"notes": "General Notes",
"assignedUser": "Assigned User",
"teams": "Teams",
"createdAt": "Created At",
"modifiedAt": "Modified At",
"createdBy": "Created By",
"modifiedBy": "Modified By"
},
"options": {
"status": {
"Draft": "Draft",
"Submitted": "Submitted",
"Approved": "Approved",
"Rejected": "Rejected"
},
"aidType": {
"": "",
"הגשת ערעור": "Filing Appeal",
"יעוץ והדרכה": "Counseling & Guidance",
"סיוע נוסף ביטוח לאומי": "Additional NI Aid",
"סיוע נוסף תחום אחר": "Additional Aid Other",
"אי מתן סיוע": "No Aid Provided"
},
"urgencyLevel": {
"": "",
"רגיל": "Normal",
"דחוף": "Urgent",
"בהול": "Critical"
},
"additionalUrgency": {
"": "",
"רגיל": "Normal",
"דחוף": "Urgent",
"בהול": "Critical"
},
"contactStatus": {
"": "",
"תקין": "Normal",
"לא תקין": "Not Normal"
},
"courtName": {
"": "",
"בית הדין האזורי לעבודה ירושלים": "Regional Labor Court Jerusalem",
"בית הדין האזורי לעבודה תל אביב": "Regional Labor Court Tel Aviv",
"בית הדין האזורי לעבודה חיפה": "Regional Labor Court Haifa",
"בית הדין האזורי לעבודה באר שבע": "Regional Labor Court Be'er Sheva",
"בית הדין האזורי לעבודה נצרת": "Regional Labor Court Nazareth",
"בית הדין הארצי לעבודה": "National Labor Court"
},
"proceedingSubject": {
"": "",
"נכות כללית": "General Disability",
"נכות מעבודה": "Work Disability",
"נכות איבה": "Hostility Disability",
"ניידות": "Mobility",
"אי כושר": "Incapacity",
"שירותים מיוחדים": "Special Services",
"סיעוד": "Nursing Care",
"גמלת הבטחת הכנסה": "Income Support",
"דמי אבטלה": "Unemployment Benefits",
"אחר": "Other"
},
"recommendationType": {
"": "",
"ייעוץ והדרכה חלף ייצוג": "Counseling Instead of Representation",
"ויתור הלקוח": "Client Waiver",
"החלפת ייצוג": "Change Representation",
"לא נוצר קשר": "No Contact Made",
"לא התקיימה פגישה": "No Meeting Held",
"עדיין לא ניתן להחליט": "Cannot Decide Yet",
"נדרשים מסמכים נוספים": "Additional Documents Required",
"המלצה לסירוב": "Recommendation to Refuse"
},
"additionalAidNeeded": {
"": "",
"לא נחוץ": "Not Needed",
"נחוץ בתחום ביטוח לאומי": "Needed - NI Field",
"נחוץ בתחום אחר": "Needed - Other Field"
},
"additionalAidUrgency": {
"": "",
"רגיל": "Normal",
"דחוף": "Urgent",
"בהול": "Critical"
}
},
"links": {
"case": "Case"
},
"presetFilters": {
"draft": "Draft",
"submitted": "Submitted",
"approved": "Approved"
}
}
@@ -1,8 +1,2 @@
{
"scopeNames": {
"DirectAccessReport": "Direct Access Report"
},
"scopeNamesPlural": {
"DirectAccessReport": "Direct Access Reports"
}
}
@@ -1,20 +1,8 @@
{
"fields": {
"cLegalAidType": "סוג מינוי סיוע משפטי",
"cAppointmentDate": "מועד קבלת המינוי",
"cAidType": "מהות הסיוע",
"cUrgencyLevel": "דחיפות",
"cFilingDeadline": "מועד אחרון להגשה"
"labels": {
"LegalAssistance": "סיוע משפטי"
},
"options": {
"cLegalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
}
},
"links": {
"directAccessReports": "דוחות גישה ישירה"
"tabs": {
"LegalAssistance": "סיוע משפטי"
}
}
@@ -1,150 +0,0 @@
{
"scopeName": "דוח גישה ישירה",
"scopeNamesPlural": "דוחות גישה ישירה",
"labels": {
"Create DirectAccessReport": "צור דוח גישה ישירה"
},
"fields": {
"name": "שם הדוח",
"number": "מספר",
"status": "סטטוס",
"case": "תיק",
"aidType": "מהות הסיוע",
"urgencyLevel": "דחיפות הליך",
"appointmentDate": "מועד קבלת המינוי",
"filingDeadline": "מועד אחרון להגשה",
"additionalUrgency": "דחיפות הליך נוסף",
"additionalDeadline": "מועדים להליך נוסף",
"firstContactDate": "תאריך יצירת קשר ראשוני",
"contactDelayReason": "סיבת עיכוב (אם לא תוך 48 שעות)",
"meetingDate": "תאריך פגישה עם הלקוח",
"meetingDelayReason": "סיבת עיכוב פגישה",
"meetingAttendees": "נוכחים בפגישה",
"contactStatus": "מצב קשר עם הלקוח",
"contactStatusDetail": "פירוט מצב קשר",
"proceedingNumber": "מספר הליך",
"courtName": "בית הדין",
"proceedingSubject": "עניין ההליך",
"claimSummary": "תמצית התביעה/הערעור",
"defenseClaims": "טענות ההגנה",
"q1DocsReviewed": "הוועדה התייחסה לכל המסמכים?",
"q1DocsDetail": "פירוט מסמכים",
"q2Reasoned": "ההחלטה מנומקת?",
"q2ReasonDetail": "פירוט הנמקה חסרה",
"q3PanelMatch": "הרכב תואם ליקויים?",
"q4PanelComposition": "הרכב הוועדה / הרכב חסר",
"q5ComplaintsAddressed": "התייחסו לכל התלונות?",
"q6ComplaintsDetail": "פירוט תלונות חסרות",
"q7ClinicalExam": "בדיקה קלינית נערכה?",
"q8ClinicalGap": "פער בדיקה קלינית vs מסמכים",
"q9MoharaApplied": "בחינת הלכת מוהרה (גיל, רקע, השכלה)?",
"q9MoharaDetail": "נתונים חסרים (מוהרה)",
"q10RehabGap": "פער פקיד שיקום vs ועדה?",
"q10RehabDetail": "פירוט הפער",
"q11ScoreGap": "פער ניקוד vs מצב ומסמכים?",
"q11ScoreDetail": "פירוט פער הניקוד",
"q12UniqueAspects": "התייחסות להיבטים ייחודיים?",
"q12UniqueDetail": "פירוט היבטים ייחודיים",
"recommendationType": "סוג ההמלצה",
"recommendationDetail": "פירוט ההמלצה",
"additionalAidNeeded": "צורך בסיוע נוסף",
"additionalAidNIDetail": "פירוט סיוע נוסף (ביטוח לאומי)",
"additionalAidOtherDetail": "פירוט סיוע נוסף (תחום אחר)",
"additionalAidUrgency": "דחיפות הסיוע הנוסף",
"additionalAidDeadlines": "מועדים (סיוע נוסף)",
"willingToRepresent": "בתחום התמחות + מעוניין לייצג?",
"notes": "הערות כלליות",
"assignedUser": "אחראי",
"teams": "צוותים",
"createdAt": "נוצר ב",
"modifiedAt": "עודכן ב",
"createdBy": "נוצר על ידי",
"modifiedBy": "עודכן על ידי"
},
"options": {
"status": {
"Draft": "טיוטה",
"Submitted": "נשלח",
"Approved": "אושר",
"Rejected": "נדחה"
},
"aidType": {
"": "",
"הגשת ערעור": "הגשת ערעור",
"יעוץ והדרכה": "יעוץ והדרכה",
"סיוע נוסף ביטוח לאומי": "סיוע נוסף ביטוח לאומי",
"סיוע נוסף תחום אחר": "סיוע נוסף תחום אחר",
"אי מתן סיוע": "אי מתן סיוע"
},
"urgencyLevel": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
},
"additionalUrgency": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
},
"contactStatus": {
"": "",
"תקין": "תקין",
"לא תקין": "לא תקין"
},
"courtName": {
"": "",
"בית הדין האזורי לעבודה ירושלים": "בית הדין האזורי לעבודה ירושלים",
"בית הדין האזורי לעבודה תל אביב": "בית הדין האזורי לעבודה תל אביב",
"בית הדין האזורי לעבודה חיפה": "בית הדין האזורי לעבודה חיפה",
"בית הדין האזורי לעבודה באר שבע": "בית הדין האזורי לעבודה באר שבע",
"בית הדין האזורי לעבודה נצרת": "בית הדין האזורי לעבודה נצרת",
"בית הדין הארצי לעבודה": "בית הדין הארצי לעבודה"
},
"proceedingSubject": {
"": "",
"נכות כללית": "נכות כללית",
"נכות מעבודה": "נכות מעבודה",
"נכות איבה": "נכות איבה",
"ניידות": "ניידות",
"אי כושר": "אי כושר",
"שירותים מיוחדים": "שירותים מיוחדים",
"סיעוד": "סיעוד",
"גמלת הבטחת הכנסה": "גמלת הבטחת הכנסה",
"דמי אבטלה": "דמי אבטלה",
"אחר": "אחר"
},
"recommendationType": {
"": "",
"ייעוץ והדרכה חלף ייצוג": "ייעוץ והדרכה חלף ייצוג",
"ויתור הלקוח": "ויתור הלקוח",
"החלפת ייצוג": "החלפת ייצוג",
"לא נוצר קשר": "לא נוצר קשר",
"לא התקיימה פגישה": "לא התקיימה פגישה",
"עדיין לא ניתן להחליט": "עדיין לא ניתן להחליט",
"נדרשים מסמכים נוספים": "נדרשים מסמכים נוספים",
"המלצה לסירוב": "המלצה לסירוב"
},
"additionalAidNeeded": {
"": "",
"לא נחוץ": "לא נחוץ",
"נחוץ בתחום ביטוח לאומי": "נחוץ בתחום ביטוח לאומי",
"נחוץ בתחום אחר": "נחוץ בתחום אחר"
},
"additionalAidUrgency": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
}
},
"links": {
"case": "תיק"
},
"presetFilters": {
"draft": "טיוטות",
"submitted": "נשלחו",
"approved": "אושרו"
}
}
@@ -1,8 +1,2 @@
{
"scopeNames": {
"DirectAccessReport": "דוח גישה ישירה"
},
"scopeNamesPlural": {
"DirectAccessReport": "דוחות גישה ישירה"
}
}
@@ -1,20 +1,8 @@
{
"fields": {
"cLegalAidType": "סוג מינוי סיוע משפטי",
"cAppointmentDate": "מועד קבלת המינוי",
"cAidType": "מהות הסיוע",
"cUrgencyLevel": "דחיפות",
"cFilingDeadline": "מועד אחרון להגשה"
"labels": {
"LegalAssistance": "סיוע משפטי"
},
"options": {
"cLegalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
}
},
"links": {
"directAccessReports": "דוחות גישה ישירה"
"tabs": {
"LegalAssistance": "סיוע משפטי"
}
}
@@ -1,150 +0,0 @@
{
"scopeName": "דוח גישה ישירה",
"scopeNamesPlural": "דוחות גישה ישירה",
"labels": {
"Create DirectAccessReport": "צור דוח גישה ישירה"
},
"fields": {
"name": "שם הדוח",
"number": "מספר",
"status": "סטטוס",
"case": "תיק",
"aidType": "מהות הסיוע",
"urgencyLevel": "דחיפות הליך",
"appointmentDate": "מועד קבלת המינוי",
"filingDeadline": "מועד אחרון להגשה",
"additionalUrgency": "דחיפות הליך נוסף",
"additionalDeadline": "מועדים להליך נוסף",
"firstContactDate": "תאריך יצירת קשר ראשוני",
"contactDelayReason": "סיבת עיכוב (אם לא תוך 48 שעות)",
"meetingDate": "תאריך פגישה עם הלקוח",
"meetingDelayReason": "סיבת עיכוב פגישה",
"meetingAttendees": "נוכחים בפגישה",
"contactStatus": "מצב קשר עם הלקוח",
"contactStatusDetail": "פירוט מצב קשר",
"proceedingNumber": "מספר הליך",
"courtName": "בית הדין",
"proceedingSubject": "עניין ההליך",
"claimSummary": "תמצית התביעה/הערעור",
"defenseClaims": "טענות ההגנה",
"q1DocsReviewed": "הוועדה התייחסה לכל המסמכים?",
"q1DocsDetail": "פירוט מסמכים",
"q2Reasoned": "ההחלטה מנומקת?",
"q2ReasonDetail": "פירוט הנמקה חסרה",
"q3PanelMatch": "הרכב תואם ליקויים?",
"q4PanelComposition": "הרכב הוועדה / הרכב חסר",
"q5ComplaintsAddressed": "התייחסו לכל התלונות?",
"q6ComplaintsDetail": "פירוט תלונות חסרות",
"q7ClinicalExam": "בדיקה קלינית נערכה?",
"q8ClinicalGap": "פער בדיקה קלינית vs מסמכים",
"q9MoharaApplied": "בחינת הלכת מוהרה (גיל, רקע, השכלה)?",
"q9MoharaDetail": "נתונים חסרים (מוהרה)",
"q10RehabGap": "פער פקיד שיקום vs ועדה?",
"q10RehabDetail": "פירוט הפער",
"q11ScoreGap": "פער ניקוד vs מצב ומסמכים?",
"q11ScoreDetail": "פירוט פער הניקוד",
"q12UniqueAspects": "התייחסות להיבטים ייחודיים?",
"q12UniqueDetail": "פירוט היבטים ייחודיים",
"recommendationType": "סוג ההמלצה",
"recommendationDetail": "פירוט ההמלצה",
"additionalAidNeeded": "צורך בסיוע נוסף",
"additionalAidNIDetail": "פירוט סיוע נוסף (ביטוח לאומי)",
"additionalAidOtherDetail": "פירוט סיוע נוסף (תחום אחר)",
"additionalAidUrgency": "דחיפות הסיוע הנוסף",
"additionalAidDeadlines": "מועדים (סיוע נוסף)",
"willingToRepresent": "בתחום התמחות + מעוניין לייצג?",
"notes": "הערות כלליות",
"assignedUser": "אחראי",
"teams": "צוותים",
"createdAt": "נוצר ב",
"modifiedAt": "עודכן ב",
"createdBy": "נוצר על ידי",
"modifiedBy": "עודכן על ידי"
},
"options": {
"status": {
"Draft": "טיוטה",
"Submitted": "נשלח",
"Approved": "אושר",
"Rejected": "נדחה"
},
"aidType": {
"": "",
"הגשת ערעור": "הגשת ערעור",
"יעוץ והדרכה": "יעוץ והדרכה",
"סיוע נוסף ביטוח לאומי": "סיוע נוסף ביטוח לאומי",
"סיוע נוסף תחום אחר": "סיוע נוסף תחום אחר",
"אי מתן סיוע": "אי מתן סיוע"
},
"urgencyLevel": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
},
"additionalUrgency": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
},
"contactStatus": {
"": "",
"תקין": "תקין",
"לא תקין": "לא תקין"
},
"courtName": {
"": "",
"בית הדין האזורי לעבודה ירושלים": "בית הדין האזורי לעבודה ירושלים",
"בית הדין האזורי לעבודה תל אביב": "בית הדין האזורי לעבודה תל אביב",
"בית הדין האזורי לעבודה חיפה": "בית הדין האזורי לעבודה חיפה",
"בית הדין האזורי לעבודה באר שבע": "בית הדין האזורי לעבודה באר שבע",
"בית הדין האזורי לעבודה נצרת": "בית הדין האזורי לעבודה נצרת",
"בית הדין הארצי לעבודה": "בית הדין הארצי לעבודה"
},
"proceedingSubject": {
"": "",
"נכות כללית": "נכות כללית",
"נכות מעבודה": "נכות מעבודה",
"נכות איבה": "נכות איבה",
"ניידות": "ניידות",
"אי כושר": "אי כושר",
"שירותים מיוחדים": "שירותים מיוחדים",
"סיעוד": "סיעוד",
"גמלת הבטחת הכנסה": "גמלת הבטחת הכנסה",
"דמי אבטלה": "דמי אבטלה",
"אחר": "אחר"
},
"recommendationType": {
"": "",
"ייעוץ והדרכה חלף ייצוג": "ייעוץ והדרכה חלף ייצוג",
"ויתור הלקוח": "ויתור הלקוח",
"החלפת ייצוג": "החלפת ייצוג",
"לא נוצר קשר": "לא נוצר קשר",
"לא התקיימה פגישה": "לא התקיימה פגישה",
"עדיין לא ניתן להחליט": "עדיין לא ניתן להחליט",
"נדרשים מסמכים נוספים": "נדרשים מסמכים נוספים",
"המלצה לסירוב": "המלצה לסירוב"
},
"additionalAidNeeded": {
"": "",
"לא נחוץ": "לא נחוץ",
"נחוץ בתחום ביטוח לאומי": "נחוץ בתחום ביטוח לאומי",
"נחוץ בתחום אחר": "נחוץ בתחום אחר"
},
"additionalAidUrgency": {
"": "",
"רגיל": "רגיל",
"דחוף": "דחוף",
"בהול": "בהול"
}
},
"links": {
"case": "תיק"
},
"presetFilters": {
"draft": "טיוטות",
"submitted": "נשלחו",
"approved": "אושרו"
}
}
@@ -1,8 +1,2 @@
{
"scopeNames": {
"DirectAccessReport": "דוח גישה ישירה"
},
"scopeNamesPlural": {
"DirectAccessReport": "דוחות גישה ישירה"
}
}
@@ -1,7 +1,7 @@
{
"tools": {
"generate_initial_report": {
"handler": "Espo\\Modules\\LegalAssistance\\SmartAssistant\\Tools\\GenerateInitialReport",
"generate_direct_access_report": {
"handler": "Espo\\Modules\\LegalAssistance\\SmartAssistant\\Tools\\DirectAccessReportGenerator",
"requiresCase": true
}
}
@@ -1,36 +0,0 @@
{
"controller": "controllers/record",
"boolFilterList": ["onlyMy"],
"iconClass": "fas fa-file-alt",
"color": "#6C3483",
"kanbanViewMode": true,
"statusField": "status",
"filterList": [
{
"name": "draft",
"style": "default"
},
{
"name": "submitted",
"style": "warning"
},
{
"name": "approved",
"style": "success"
}
],
"menu": {
"list": {
"buttons": []
},
"detail": {
"buttons": []
}
},
"relationshipPanels": {
"case": {
"select": true,
"create": false
}
}
}
@@ -1,43 +1,2 @@
{
"fields": {
"cLegalAidType": {
"type": "enum",
"options": ["", "DirectAccess", "Regular", "Duty"],
"audited": true,
"displayAsLabel": true,
"style": {
"DirectAccess": "primary",
"Regular": "default",
"Duty": "info"
}
},
"cAppointmentDate": {
"type": "date"
},
"cAidType": {
"type": "enum",
"options": ["", "הגשת ערעור", "יעוץ והדרכה", "סיוע נוסף ביטוח לאומי", "סיוע נוסף תחום אר", "אי מתן סיוע"]
},
"cUrgencyLevel": {
"type": "enum",
"options": ["", "רגיל", "דחוף", "הול"],
"displayAsLabel": true,
"style": {
"רגיל": "default",
"דחוף": "warning",
"בהול": "danger"
}
},
"cFilingDeadline": {
"type": "date"
}
},
"links": {
"directAccessReports": {
"type": "hasMany",
"entity": "DirectAccessReport",
"foreign": "case",
"layoutRelationshipsDisabled": false
}
}
}
@@ -1,269 +0,0 @@
{
"fields": {
"name": {
"type": "varchar",
"required": true,
"pattern": "$noBadCharacters"
},
"number": {
"type": "autoincrement",
"index": true
},
"status": {
"type": "enum",
"options": ["Draft", "Submitted", "Approved", "Rejected"],
"default": "Draft",
"audited": true,
"displayAsLabel": true,
"style": {
"Draft": "default",
"Submitted": "warning",
"Approved": "success",
"Rejected": "danger"
}
},
"case": {
"type": "link",
"required": true
},
"aidType": {
"type": "enum",
"options": ["", "הגשת ערעור", "יעוץ והדרכה", "סיוע נוסף ביטוח לאומי", "סיוע נוסף תחום אחר", "אי מתן סיוע"],
"audited": true
},
"urgencyLevel": {
"type": "enum",
"options": ["", "רגיל", "דחוף", "בהול"],
"default": "רגיל",
"displayAsLabel": true,
"style": {
"רגיל": "default",
"דחוף": "warning",
"בהול": "danger"
}
},
"appointmentDate": {
"type": "date"
},
"filingDeadline": {
"type": "date"
},
"additionalUrgency": {
"type": "enum",
"options": ["", "רגיל", "דחוף", "בהול"]
},
"additionalDeadline": {
"type": "varchar",
"maxLength": 150
},
"firstContactDate": {
"type": "date"
},
"contactDelayReason": {
"type": "text"
},
"meetingDate": {
"type": "date"
},
"meetingDelayReason": {
"type": "text"
},
"meetingAttendees": {
"type": "text"
},
"contactStatus": {
"type": "enum",
"options": ["", "תקין", "לא תקין"]
},
"contactStatusDetail": {
"type": "text"
},
"proceedingNumber": {
"type": "varchar",
"maxLength": 50
},
"courtName": {
"type": "enum",
"options": ["", "בית הדין האזורי לעבודה ירושלים", "בית הדין האזורי לעבודה תל אביב", "בית הדין האזורי לעבודה חיפה", "בית הדין האזורי לעבודה באר שבע", "בית הדין האזורי לעבודה נצרת", "בית הדין הארצי לעבודה"]
},
"proceedingSubject": {
"type": "enum",
"options": ["", "נכות כללית", "נכות מעבודה", "נכות איבה", "ניידות", "אי כושר", "שירותים מיוחדים", "סיעוד", "גמלת הבטחת הכנסה", "דמי אבטלה", "אחר"]
},
"claimSummary": {
"type": "text"
},
"defenseClaims": {
"type": "text"
},
"q1DocsReviewed": {
"type": "bool",
"default": false
},
"q1DocsDetail": {
"type": "text"
},
"q2Reasoned": {
"type": "bool",
"default": false
},
"q2ReasonDetail": {
"type": "text"
},
"q3PanelMatch": {
"type": "bool",
"default": false
},
"q4PanelComposition": {
"type": "text"
},
"q5ComplaintsAddressed": {
"type": "bool",
"default": false
},
"q6ComplaintsDetail": {
"type": "text"
},
"q7ClinicalExam": {
"type": "bool",
"default": false
},
"q8ClinicalGap": {
"type": "text"
},
"q9MoharaApplied": {
"type": "bool",
"default": false
},
"q9MoharaDetail": {
"type": "text"
},
"q10RehabGap": {
"type": "bool",
"default": false
},
"q10RehabDetail": {
"type": "text"
},
"q11ScoreGap": {
"type": "bool",
"default": false
},
"q11ScoreDetail": {
"type": "text"
},
"q12UniqueAspects": {
"type": "bool",
"default": false
},
"q12UniqueDetail": {
"type": "text"
},
"recommendationType": {
"type": "enum",
"options": ["", "ייעוץ והדרכה חלף ייצוג", "ויתור הלקוח", "החלפת ייצוג", "לא נוצר קשר", "לא התקיימה פגישה", "עדיין לא ניתן להחליט", "נדרשים מסמכים נוספים", "המלצה לסירוב"]
},
"recommendationDetail": {
"type": "text"
},
"additionalAidNeeded": {
"type": "enum",
"options": ["", "לא נחוץ", "נחוץ בתחום ביטוח לאומי", "נחוץ בתחום אחר"]
},
"additionalAidNIDetail": {
"type": "text"
},
"additionalAidOtherDetail": {
"type": "text"
},
"additionalAidUrgency": {
"type": "enum",
"options": ["", "רגיל", "דחוף", "בהול"]
},
"additionalAidDeadlines": {
"type": "varchar",
"maxLength": 150
},
"willingToRepresent": {
"type": "bool",
"default": false
},
"notes": {
"type": "text"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"modifiedBy": {
"type": "link",
"readOnly": true,
"view": "views/fields/user"
},
"assignedUser": {
"type": "link",
"view": "views/fields/assigned-user"
},
"teams": {
"type": "linkMultiple",
"view": "views/fields/teams"
}
},
"links": {
"case": {
"type": "belongsTo",
"entity": "Case",
"foreign": "directAccessReports"
},
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"assignedUser": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "entityTeam",
"layoutRelationshipsDisabled": true
}
},
"collection": {
"orderBy": "createdAt",
"order": "desc",
"textFilterFields": ["name", "number"]
},
"indexes": {
"status": {
"columns": ["status", "deleted"]
},
"createdAt": {
"columns": ["createdAt"]
}
}
}
@@ -1,81 +0,0 @@
[
{
"label": "כותרת ופרטי תיק",
"rows": [
[{"name": "name"}, {"name": "number"}],
[{"name": "case"}, {"name": "status"}],
[{"name": "assignedUser"}, {"name": "teams"}]
]
},
{
"label": "מהות הסיוע",
"rows": [
[{"name": "aidType"}, {"name": "urgencyLevel"}],
[{"name": "appointmentDate"}, {"name": "filingDeadline"}],
[{"name": "additionalUrgency"}, {"name": "additionalDeadline"}]
]
},
{
"label": "קשר עם הלקוח",
"rows": [
[{"name": "firstContactDate"}, {"name": "contactDelayReason"}],
[{"name": "meetingDate"}, {"name": "meetingDelayReason"}],
[{"name": "meetingAttendees"}, {"name": "contactStatus"}],
[{"name": "contactStatusDetail"}, false]
]
},
{
"label": "פרטי ההליך",
"rows": [
[{"name": "proceedingNumber"}, {"name": "courtName"}],
[{"name": "proceedingSubject"}, false],
[{"name": "claimSummary", "fullWidth": true}],
[{"name": "defenseClaims", "fullWidth": true}]
]
},
{
"label": "בחינת סיכוי משפטי — נכות",
"rows": [
[{"name": "q1DocsReviewed"}, {"name": "q1DocsDetail"}],
[{"name": "q2Reasoned"}, {"name": "q2ReasonDetail"}],
[{"name": "q3PanelMatch"}, {"name": "q4PanelComposition"}],
[{"name": "q5ComplaintsAddressed"}, {"name": "q6ComplaintsDetail"}],
[{"name": "q7ClinicalExam"}, {"name": "q8ClinicalGap"}]
]
},
{
"label": "בחינת סיכוי משפטי — אי כושר",
"rows": [
[{"name": "q9MoharaApplied"}, {"name": "q9MoharaDetail"}],
[{"name": "q10RehabGap"}, {"name": "q10RehabDetail"}]
]
},
{
"label": "בחינת סיכוי משפטי — שירותים מיוחדים/סיעוד",
"rows": [
[{"name": "q11ScoreGap"}, {"name": "q11ScoreDetail"}],
[{"name": "q12UniqueAspects"}, {"name": "q12UniqueDetail"}]
]
},
{
"label": "המלצה",
"rows": [
[{"name": "recommendationType"}, false],
[{"name": "recommendationDetail", "fullWidth": true}]
]
},
{
"label": "מיצוי זכויות",
"rows": [
[{"name": "additionalAidNeeded"}, {"name": "additionalAidUrgency"}],
[{"name": "additionalAidNIDetail"}, {"name": "additionalAidOtherDetail"}],
[{"name": "additionalAidDeadlines"}, {"name": "willingToRepresent"}]
]
},
{
"label": "הערות",
"rows": [
[{"name": "notes", "fullWidth": true}]
]
}
]
@@ -1,11 +0,0 @@
[
{
"rows": [
[{"name": "name"}],
[{"name": "case"}],
[{"name": "status"}],
[{"name": "aidType"}],
[{"name": "recommendationType"}]
]
}
]
@@ -1,10 +0,0 @@
[
{"name": "number", "width": 8},
{"name": "name", "link": true},
{"name": "case", "width": 20},
{"name": "status", "width": 12},
{"name": "aidType", "width": 15},
{"name": "recommendationType", "width": 15},
{"name": "createdAt", "width": 15},
{"name": "assignedUser", "width": 15}
]
@@ -1,15 +0,0 @@
{
"entity": true,
"object": true,
"stream": true,
"tab": false,
"acl": true,
"aclPortal": false,
"disabled": false,
"module": "LegalAssistance",
"isCustom": false,
"notifications": true,
"calendar": false,
"activity": false,
"kanbanStatusIgnoreList": ["Approved", "Rejected"]
}
@@ -1,214 +0,0 @@
<?php
namespace Espo\Modules\LegalAssistance\Services;
use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Log;
use Espo\Core\Utils\DateTime as DateTimeUtil;
use Espo\Entities\User;
class DirectAccessReportService
{
public function __construct(
private EntityManager $entityManager,
private InjectableFactory $injectableFactory,
private Log $log,
private User $user,
private DateTimeUtil $dateTimeUtil
) {}
public function generateReport(string $caseId, string $userId, array $params): array
{
$case = $this->entityManager->getEntityById('Case', $caseId);
if (!$case) {
throw new Error("Case {$caseId} not found.");
}
$contact = null;
$contactId = $case->get('contactId');
if ($contactId) {
$contact = $this->entityManager->getEntityById('Contact', $contactId);
}
// Build report name
$contactName = $contact
? ($contact->get('firstName') . ' ' . $contact->get('lastName'))
: 'ללא איש קשר';
$reportName = "דוח גישה ישיר{$contactName}";
// Extract legal analysis from nested object
$legalAnalysis = $params['legalAnalysis'] ?? [];
if (is_string($legalAnalysis)) {
$legalAnalysis = json_decode($legalAnalysis, true) ?? [];
}
// Create DirectAccessReport entity
$report = $this->entityManager->getNewEntity('DirectAccessReport');
$report->set([
'name' => $reportName,
'status' => 'Draft',
'caseId' => $caseId,
'assignedUserId' => $userId,
// Section ב' מהות הסיוע
'aidType' => $params['aidType'] ?? null,
'urgencyLevel' => $params['urgencyLevel'] ?? null,
'appointmentDate' => $this->normalizeDate($params['appointmentDate'] ?? null),
'filingDeadline' => $this->normalizeDate($params['filingDeadline'] ?? null),
'additionalUrgency' => $params['additionalUrgency'] ?? null,
'additionalDeadline' => $params['additionalDeadline'] ?? null,
// Section ג' — קשר עם הלקוח
'firstContactDate' => $this->normalizeDate($params['firstContactDate'] ?? null),
'contactDelayReason' => $params['contactDelayReason'] ?? null,
'meetingDate' => $this->normalizeDate($params['meetingDate'] ?? null),
'meetingDelayReason' => $params['meetingDelayReason'] ?? null,
'meetingAttendees' => $params['meetingAttendees'] ?? null,
'contactStatus' => $params['contactStatus'] ?? null,
'contactStatusDetail' => $params['contactStatusDetail'] ?? null,
// Section ד' — פרטי ההליך
'proceedingNumber' => $params['proceedingNumber'] ?? $case->get('cCourtCaseNumber'),
'courtName' => $params['courtName'] ?? null,
'proceedingSubject' => $params['proceedingSubject'] ?? null,
'claimSummary' => $params['claimSummary'] ?? null,
'defenseClaims' => $params['defenseClaims'] ?? null,
// Section ה'-ז' — בחינת סיכוי משפטי
'q1DocsReviewed' => $legalAnalysis['q1DocsReviewed'] ?? false,
'q1DocsDetail' => $legalAnalysis['q1DocsDetail'] ?? null,
'q2Reasoned' => $legalAnalysis['q2Reasoned'] ?? false,
'q2ReasonDetail' => $legalAnalysis['q2ReasonDetail'] ?? null,
'q3PanelMatch' => $legalAnalysis['q3PanelMatch'] ?? false,
'q4PanelComposition' => $legalAnalysis['q4PanelComposition'] ?? null,
'q5ComplaintsAddressed' => $legalAnalysis['q5ComplaintsAddressed'] ?? false,
'q6ComplaintsDetail' => $legalAnalysis['q6ComplaintsDetail'] ?? null,
'q7ClinicalExam' => $legalAnalysis['q7ClinicalExam'] ?? false,
'q8ClinicalGap' => $legalAnalysis['q8ClinicalGap'] ?? null,
'q9MoharaApplied' => $legalAnalysis['q9MoharaApplied'] ?? false,
'q9MoharaDetail' => $legalAnalysis['q9MoharaDetail'] ?? null,
'q10RehabGap' => $legalAnalysis['q10RehabGap'] ?? false,
'q10RehabDetail' => $legalAnalysis['q10RehabDetail'] ?? null,
'q11ScoreGap' => $legalAnalysis['q11ScoreGap'] ?? false,
'q11ScoreDetail' => $legalAnalysis['q11ScoreDetail'] ?? null,
'q12UniqueAspects' => $legalAnalysis['q12UniqueAspects'] ?? false,
'q12UniqueDetail' => $legalAnalysis['q12UniqueDetail'] ?? null,
// Section ח' — המלצה
'recommendationType' => $params['recommendationType'] ?? null,
'recommendationDetail' => $params['recommendationDetail'] ?? null,
// Section ט' — מיצוי זכויות
'additionalAidNeeded' => $params['additionalAidNeeded'] ?? null,
'additionalAidNIDetail' => $params['additionalAidNIDetail'] ?? null,
'additionalAidOtherDetail' => $params['additionalAidOtherDetail'] ?? null,
'additionalAidUrgency' => $params['additionalAidUrgency'] ?? null,
'additionalAidDeadlines' => $params['additionalAidDeadlines'] ?? null,
'willingToRepresent' => $params['willingToRepresent'] ?? false,
// Section י' — הערות
'notes' => $params['notes'] ?? null,
]);
$this->entityManager->saveEntity($report);
$this->log->info("LegalAssistance: Created DirectAccessReport {$report->getId()} for case {$caseId}");
// Also update Case fields if provided
$caseUpdated = false;
if (!empty($params['appointmentDate']) && !$case->get('cAppointmentDate')) {
$case->set('cAppointmentDate', $this->normalizeDate($params['appointmentDate']));
$caseUpdated = true;
}
if (!empty($params['aidType']) && !$case->get('cAidType')) {
$case->set('cAidType', $params['aidType']);
$caseUpdated = true;
}
if (!empty($params['urgencyLevel']) && !$case->get('cUrgencyLevel')) {
$case->set('cUrgencyLevel', $params['urgencyLevel']);
$caseUpdated = true;
}
if (!empty($params['filingDeadline']) && !$case->get('cFilingDeadline')) {
$case->set('cFilingDeadline', $this->normalizeDate($params['filingDeadline']));
$caseUpdated = true;
}
if ($caseUpdated) {
$this->entityManager->saveEntity($case, ['skipHooks' => true]);
}
// Generate DOCX using template
$docxResult = $this->generateDocx($report, $case, $contact, $userId);
$msg = "✅ דוח גישה ישירה נוצר בהצלחה: \"{$reportName}\"";
if (!empty($docxResult['fileName'])) {
$msg .= "\n📄 מסמך DOCX נוצר: {$docxResult['fileName']}";
}
return [
'success' => true,
'message' => $msg,
'entityType' => 'DirectAccessReport',
'entityId' => $report->getId(),
];
}
private function generateDocx($report, $case, $contact, string $userId): array
{
// Try to use LocalDocumentTemplateService if available
try {
$templateServiceClass = 'Espo\\Modules\\LocalDocuments\\Services\\LocalDocumentTemplateService';
if (!class_exists($templateServiceClass)) {
$templateServiceClass = 'Espo\\Modules\\NetworkStorageIntegration\\Services\\DocumentTemplateService';
}
if (!class_exists($templateServiceClass)) {
$this->log->warning("LegalAssistance: No template service available for DOCX generation");
return ['success' => false, 'error' => 'No template service'];
}
// Find the template
$template = $this->entityManager
->getRDBRepository('DocumentTemplate')
->where(['name' => 'דוח גישה ישירה'])
->findOne();
if (!$template) {
$this->log->warning("LegalAssistance: Template 'דוח גישה ישירה' not found");
return ['success' => false, 'error' => 'Template not found'];
}
$templateService = $this->injectableFactory->create($templateServiceClass);
$result = $templateService->createFromTemplate(
$template->getId(),
'Case',
$case->getId(),
$report->get('name')
);
return $result;
} catch (\Throwable $e) {
$this->log->error("LegalAssistance: DOCX generation failed: " . $e->getMessage());
return ['success' => false, 'error' => $e->getMessage()];
}
}
private function normalizeDate(?string $value): ?string
{
if (!$value) return null;
// Handle DD/MM/YYYY format
if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $value, $m)) {
return "{$m[3]}-{$m[2]}-{$m[1]}";
}
// Handle YYYY-MM-DD format (already correct)
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
return $value;
}
return $value;
}
}
@@ -0,0 +1,399 @@
<?php
/************************************************************************
* Direct Access Report Generator
*
* Takes JSON params from Shira (AI assistant), generates a DOCX report
* using PHPWord TemplateProcessor, and attaches it to the Case as a
* Document. No entity creation data goes directly into the template.
************************************************************************/
namespace Espo\Modules\LegalAssistance\SmartAssistant\Tools;
use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Log;
use Espo\Entities\User;
use PhpOffice\PhpWord\TemplateProcessor;
class DirectAccessReportGenerator
{
private const TEMP_DIR = 'data/tmp/';
private const TEMPLATE_NAME = 'דוח גישה ישירה';
public function __construct(
private EntityManager $entityManager,
private InjectableFactory $injectableFactory,
private Log $log,
private User $user
) {}
public function execute(array $params, ?string $caseId, string $userId): array
{
if (!$caseId) {
throw new Error('generate_direct_access_report requires a case context.');
}
$this->log->info("LegalAssistance: Generating direct access report for case {$caseId}");
$case = $this->entityManager->getEntityById('Case', $caseId);
if (!$case) {
throw new Error("Case {$caseId} not found.");
}
// Get contact
$contact = null;
$contactId = $case->get('contactId');
if (!$contactId) {
$contactsIds = $case->getLinkMultipleIdList('contacts');
if (!empty($contactsIds)) {
$contactId = $contactsIds[0];
}
}
if ($contactId) {
$contact = $this->entityManager->getEntityById('Contact', $contactId);
}
$contactName = $contact
? trim($contact->get('firstName') . ' ' . $contact->get('lastName'))
: 'ללא איש קשר';
// Get assigned user name — prefer case's assignedUser, then userId, then current user
$assignedUserName = '';
$caseAssignedUserId = $case->get('assignedUserId');
if ($caseAssignedUserId) {
$caseAssignedUser = $this->entityManager->getEntityById('User', $caseAssignedUserId);
if ($caseAssignedUser) {
$assignedUserName = $caseAssignedUser->get('name') ?? '';
}
}
if (empty($assignedUserName) && $userId) {
$assignedUser = $this->entityManager->getEntityById('User', $userId);
if ($assignedUser) {
$assignedUserName = $assignedUser->get('name') ?? '';
}
}
if (empty($assignedUserName)) {
$assignedUserName = $this->user->get('name') ?? '';
}
// Filter out system/API user names
if (in_array(strtolower($assignedUserName), ['api', 'x-api-key', 'system', 'admin'], true)) {
$assignedUserName = '';
}
// Build placeholder data directly from params
$data = $this->buildPlaceholderData($params, $case, $contactName, $assignedUserName);
// Add checkbox mappings
$this->addCheckboxData($data, $params);
// Find template and generate DOCX
$templatePath = $this->resolveTemplatePath();
$documentName = "דוח גישה ישירה — {$contactName}";
$outputPath = self::TEMP_DIR . uniqid('report_') . '.docx';
if (!is_dir(self::TEMP_DIR)) {
mkdir(self::TEMP_DIR, 0775, true);
}
$this->processTemplate($templatePath, $outputPath, $data);
// Read generated file
$content = file_get_contents($outputPath);
@unlink($outputPath);
// Clean up temp template if downloaded from storage
if (str_starts_with($templatePath, self::TEMP_DIR)) {
@unlink($templatePath);
}
// Create Attachment
$attachment = $this->entityManager->getNewEntity('Attachment');
$attachment->set([
'name' => $documentName . '.docx',
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'role' => 'Attachment',
'size' => strlen($content),
'relatedType' => 'Document',
]);
$this->entityManager->saveEntity($attachment);
file_put_contents('data/upload/' . $attachment->getId(), $content);
// Create Document linked to Case
$document = $this->entityManager->getNewEntity('Document');
$document->set([
'name' => $documentName,
'fileId' => $attachment->getId(),
'fileName' => $documentName . '.docx',
'assignedUserId' => $userId,
'type' => 'דוח',
]);
$this->entityManager->saveEntity($document);
// Link Document to Case (via Case side of relationship)
$this->entityManager->getRDBRepository('Case')
->getRelation($case, 'documents')
->relate($document);
$this->log->info("LegalAssistance: Report generated — Document {$document->getId()} attached to Case {$caseId}");
return [
'success' => true,
'message' => "✅ דוח גישה ישירה נוצר בהצלחה: \"{$documentName}\"\n📄 המסמך צורף לתיק.",
'entityType' => 'Document',
'entityId' => $document->getId(),
];
}
private function buildPlaceholderData(array $params, $case, string $contactName, string $assignedUserName): array
{
$legalAnalysis = $params['legalAnalysis'] ?? [];
if (is_string($legalAnalysis)) {
$legalAnalysis = json_decode($legalAnalysis, true) ?? [];
} elseif (is_object($legalAnalysis)) {
$legalAnalysis = (array) $legalAnalysis;
}
// Fallback: if AI sent legalAnalysis fields flat (not nested), pick them up
$laFields = ['q1DocsDetail', 'q2ReasonDetail', 'q4PanelComposition', 'q6ComplaintsDetail',
'q8ClinicalGap', 'q9MoharaDetail', 'q10RehabDetail', 'q11ScoreDetail', 'q12UniqueDetail'];
foreach ($laFields as $f) {
if (empty($legalAnalysis[$f]) && !empty($params[$f])) {
$legalAnalysis[$f] = $params[$f];
}
}
return [
// Header
'cLegalAidNumber' => $params['legalAidNumber'] ?? $case->get('cLegalAidNumber') ?? '',
'contactName' => $contactName,
'assignedUserName' => $assignedUserName,
'reportDate' => $this->formatDate(date('Y-m-d')),
// Aid type & urgency
'appointmentDate' => $this->formatDate($this->normalizeDate($params['appointmentDate'] ?? null)),
'filingDeadline' => $this->formatDate($this->normalizeDate($params['filingDeadline'] ?? null)),
'additionalDeadline' => $params['additionalDeadline'] ?? '',
// Client contact
'firstContactDate' => $this->formatDate($this->normalizeDate($params['firstContactDate'] ?? null)),
'contactDelayReason' => $params['contactDelayReason'] ?? '',
'meetingDate' => $this->formatDate($this->normalizeDate($params['meetingDate'] ?? null)),
'meetingDelayReason' => $params['meetingDelayReason'] ?? '',
'meetingAttendees' => $params['meetingAttendees'] ?? '',
'contactStatusDetail' => $params['contactStatusDetail'] ?? '',
// Proceeding details
'proceedingNumber' => $params['proceedingNumber'] ?? $case->get('cCourtCaseNumber') ?? '',
'courtName' => $params['courtName'] ?? '',
'proceedingSubject' => $params['proceedingSubject'] ?? '',
'claimSummary' => $params['claimSummary'] ?? '',
'defenseClaims' => $params['defenseClaims'] ?? '',
// Legal analysis
'q1DocsDetail' => $legalAnalysis['q1DocsDetail'] ?? '',
'q2ReasonDetail' => $legalAnalysis['q2ReasonDetail'] ?? '',
'q4PanelComposition' => $legalAnalysis['q4PanelComposition'] ?? '',
'q6ComplaintsDetail' => $legalAnalysis['q6ComplaintsDetail'] ?? '',
'q8ClinicalGap' => $legalAnalysis['q8ClinicalGap'] ?? '',
'q9MoharaDetail' => $legalAnalysis['q9MoharaDetail'] ?? '',
'q10RehabDetail' => $legalAnalysis['q10RehabDetail'] ?? '',
'q11ScoreDetail' => $legalAnalysis['q11ScoreDetail'] ?? '',
'q12UniqueDetail' => $legalAnalysis['q12UniqueDetail'] ?? '',
// Recommendation
'recommendationDetail' => $params['recommendationDetail'] ?? '',
// Additional aid
'additionalAidNIDetail' => $params['additionalAidNIDetail'] ?? '',
'additionalAidOtherDetail' => $params['additionalAidOtherDetail'] ?? '',
'additionalAidDeadlines' => $params['additionalAidDeadlines'] ?? '',
// Notes
'notes' => $params['notes'] ?? '',
];
}
private function addCheckboxData(array &$data, array $params): void
{
$CHK = "\u{00A0}";
$UNCHK = "\u{00A0}";
$urgency = $params['urgencyLevel'] ?? '';
$data['chk_urgency_regular'] = ($urgency === 'רגיל') ? $CHK : $UNCHK;
$data['chk_urgency_urgent'] = ($urgency === 'דחוף') ? $CHK : $UNCHK;
$data['chk_urgency_emergency'] = ($urgency === 'בהול') ? $CHK : $UNCHK;
$aidType = $params['aidType'] ?? '';
$data['chk_aid_appeal'] = ($aidType === 'הגשת ערעור') ? $CHK : $UNCHK;
$data['chk_aid_consulting'] = ($aidType === 'יעוץ והדרכה') ? $CHK : $UNCHK;
$data['chk_aid_additional_ni'] = ($aidType === 'סיוע נוסף ביטוח לאומי') ? $CHK : $UNCHK;
$data['chk_aid_additional_other'] = ($aidType === 'סיוע נוסף תחום אחר') ? $CHK : $UNCHK;
$data['chk_aid_none'] = ($aidType === 'אי מתן סיוע') ? $CHK : $UNCHK;
$addUrgency = $params['additionalUrgency'] ?? '';
$data['chk_add_urgency_regular'] = ($addUrgency === 'רגיל') ? $CHK : $UNCHK;
$data['chk_add_urgency_urgent'] = ($addUrgency === 'דחוף') ? $CHK : $UNCHK;
$data['chk_add_urgency_emergency'] = ($addUrgency === 'בהול') ? $CHK : $UNCHK;
$contactStatus = $params['contactStatus'] ?? '';
$data['chk_contact_ok'] = ($contactStatus === 'תקין') ? $CHK : $UNCHK;
$data['chk_contact_bad'] = ($contactStatus === 'לא תקין') ? $CHK : $UNCHK;
$rec = $params['recommendationType'] ?? '';
$data['chk_rec_proceed'] = ($rec === 'המשך ייצוג') ? $CHK : $UNCHK;
$data['chk_rec_consulting'] = ($rec === 'ייעוץ והדרכה חלף ייצוג') ? $CHK : $UNCHK;
$data['chk_rec_waiver'] = ($rec === 'ויתור הלקוח') ? $CHK : $UNCHK;
$data['chk_rec_replacement'] = ($rec === 'החלפת ייצוג') ? $CHK : $UNCHK;
$data['chk_rec_no_contact'] = ($rec === 'לא נוצר קשר') ? $CHK : $UNCHK;
$data['chk_rec_no_meeting'] = ($rec === 'לא התקיימה פגישה') ? $CHK : $UNCHK;
$data['chk_rec_undecided'] = ($rec === 'עדיין לא ניתן להחליט') ? $CHK : $UNCHK;
$data['chk_rec_docs_needed'] = ($rec === 'נדרשים מסמכים נוספים') ? $CHK : $UNCHK;
$data['chk_rec_refusal'] = ($rec === 'המלצה לסירוב') ? $CHK : $UNCHK;
$addAid = $params['additionalAidNeeded'] ?? '';
$data['chk_no_additional'] = ($addAid === 'לא נחוץ') ? $CHK : $UNCHK;
$data['chk_additional_ni'] = ($addAid === 'נחוץ בתחום ביטוח לאומי') ? $CHK : $UNCHK;
$data['chk_additional_other'] = ($addAid === 'נחוץ בתחום אחר') ? $CHK : $UNCHK;
$addAidUrgency = $params['additionalAidUrgency'] ?? '';
$data['chk_add_aid_regular'] = ($addAidUrgency === 'רגיל') ? $CHK : $UNCHK;
$data['chk_add_aid_urgent'] = ($addAidUrgency === 'דחוף') ? $CHK : $UNCHK;
$data['chk_add_aid_emergency'] = ($addAidUrgency === 'בהול') ? $CHK : $UNCHK;
$data['chk_willing_to_represent'] = !empty($params['willingToRepresent']) ? $CHK : $UNCHK;
}
private function resolveTemplatePath(): string
{
$template = $this->entityManager
->getRDBRepository('DocumentTemplate')
->where(['name' => self::TEMPLATE_NAME])
->findOne();
if ($template) {
$templatePath = $template->get('templatePath');
if ($templatePath) {
// Try as-is (absolute or relative)
if (file_exists($templatePath)) {
return $templatePath;
}
// Try under data/document-templates/
$resolved = 'data/document-templates/' . ltrim($templatePath, '/');
if (file_exists($resolved)) {
return $resolved;
}
// Download from NetworkStorage (WebDAV)
$downloaded = $this->downloadTemplateFromStorage($templatePath);
if ($downloaded) {
return $downloaded;
}
}
}
// Fallback: known locations
$fallbackPaths = [
'data/document-templates/direct-access-report.docx',
'custom/Espo/Modules/LegalAssistance/templates/direct-access-report.docx',
];
foreach ($fallbackPaths as $path) {
if (file_exists($path)) {
return $path;
}
}
throw new Error("Template '" . self::TEMPLATE_NAME . "' not found.");
}
private function downloadTemplateFromStorage(string $templatePath): ?string
{
try {
$nds = $this->injectableFactory->create(
\Espo\Modules\NetworkStorageIntegration\Services\NetworkDocumentService::class
);
$client = $nds->getClient();
// Try Templates/{templatePath} on the storage
$integration = $this->entityManager->getEntityById('Integration', 'NetworkStorage');
$templatesFolderPath = 'Templates';
if ($integration && $integration->get('enabled')) {
$data = (array) ($integration->get('data') ?? []);
$templatesFolderPath = $data['templatesFolderPath'] ?? 'Templates';
}
$storagePath = $templatesFolderPath . '/' . $templatePath;
if (!$client->exists($storagePath)) {
$this->log->debug("LegalAssistance: Template not found on storage: {$storagePath}");
return null;
}
$content = $client->downloadFile($storagePath);
// Save locally for this request
if (!is_dir(self::TEMP_DIR)) {
mkdir(self::TEMP_DIR, 0775, true);
}
$localPath = self::TEMP_DIR . 'template_' . uniqid() . '.docx';
file_put_contents($localPath, $content);
$this->log->debug("LegalAssistance: Template downloaded from storage: {$storagePath}{$localPath}");
return $localPath;
} catch (\Exception $e) {
$this->log->warning("LegalAssistance: Failed to download template from storage: " . $e->getMessage());
return null;
}
}
private function processTemplate(string $inputPath, string $outputPath, array $data): void
{
// Ensure PHPWord autoloader is registered
if (!class_exists(TemplateProcessor::class, false)) {
$autoloader = 'vendor/phpoffice/phpword/src/PhpWord/Autoloader.php';
if (file_exists($autoloader)) {
require_once $autoloader;
\PhpOffice\PhpWord\Autoloader::register();
}
}
$processor = new TemplateProcessor($inputPath);
foreach ($data as $key => $value) {
if ($value === null) {
$value = '';
}
if (is_array($value)) {
$value = implode(', ', $value);
} elseif (is_bool($value)) {
$value = $value ? 'כן' : 'לא';
}
$processor->setValue($key, (string) $value);
}
$processor->saveAs($outputPath);
}
private function normalizeDate(?string $value): ?string
{
if (!$value) return null;
if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $value, $m)) {
return "{$m[3]}-{$m[2]}-{$m[1]}";
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
return $value;
}
return $value;
}
private function formatDate(?string $date): string
{
if (!$date) return '';
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m)) {
return "{$m[3]}/{$m[2]}/{$m[1]}";
}
return $date;
}
}
@@ -1,33 +0,0 @@
<?php
namespace Espo\Modules\LegalAssistance\SmartAssistant\Tools;
use Espo\Core\Exceptions\Error;
use Espo\Core\InjectableFactory;
use Espo\ORM\EntityManager;
use Espo\Core\Utils\Log;
use Espo\Modules\LegalAssistance\Services\DirectAccessReportService;
class GenerateInitialReport
{
public function __construct(
private EntityManager $entityManager,
private InjectableFactory $injectableFactory,
private Log $log
) {}
public function execute(array $params, ?string $caseId, string $userId): array
{
if (!$caseId) {
throw new Error('generate_initial_report requires a case context.');
}
$this->log->info("LegalAssistance: Generating initial report for case {$caseId}");
$service = $this->injectableFactory->create(DirectAccessReportService::class);
$result = $service->generateReport($caseId, $userId, $params);
return $result;
}
}
+3 -3
View File
@@ -1,11 +1,11 @@
{
"name": "LegalAssistance",
"version": "1.2.0",
"version": "2.0.6",
"acceptableVersions": [">=8.0.0"],
"php": [">=8.1"],
"releaseDate": "2026-04-06",
"releaseDate": "2026-04-09",
"author": "Marcus-Law",
"description": "סיוע משפטי — דוח גישה ישירה, משימות אוטומטיות, אינטגרציה עם שירה",
"description": "סיוע משפטי — יצירת דוח גישה ישירה (JSON ישירות ל-DOCX), אינטגרציה עם שירה",
"scripts": {
"afterInstall": "scripts/AfterInstall.php",
"afterUninstall": "scripts/AfterUninstall.php"
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -1,7 +1,7 @@
{
"tools": {
"generate_initial_report": {
"description": "Generate Direct Access initial report (דוח דיווח ראשוני — גישה ישירה). Call ONLY after collecting ALL required data through conversation with the lawyer. This tool creates a DirectAccessReport entity and generates a DOCX document.",
"generate_direct_access_report": {
"description": "Generate Direct Access report (דוח גישה ישירה) as DOCX and attach to Case. Call ONLY after collecting ALL required data through conversation with the lawyer. This tool generates the DOCX directly and attaches it to the Case as a Document — no intermediate entity is created.",
"parameters": {
"type": "object",
"properties": {
@@ -109,8 +109,8 @@
},
"recommendationType": {
"type": "string",
"enum": ["ייעוץ והדרכה חלף ייצוג", "ויתור הלקוח", "החלפת ייצוג", "לא נוצר קשר", "לא התקיימה פגישה", "עדיין לא ניתן להחליט", "נדרשים מסמכים נוספים", "המלצה לסירוב"],
"description": "סוג ההמלצה/ההודעה"
"enum": ["המשך ייצוג", "ייעוץ והדרכה חלף ייצוג", "ויתור הלקוח", "החלפת ייצוג", "לא נוצר קשר", "לא התקיימה פגישה", "עדיין לא ניתן להחליט", "נדרשים מסמכים נוספים", "המלצה לסירוב"],
"description": "סוג ההמלצה/ההודעה — בחר ׳המשך ייצוג׳ כשמומלץ להגיש ערעור או להמשיך בייצוג"
},
"recommendationDetail": {
"type": "string",
+25
View File
@@ -24,6 +24,10 @@ class AfterInstall
$log->info('LegalAssistance: Running AfterInstall...');
// 0. Ensure custom i18n directories exist for all supported locales
$config = $container->getByClass(Config::class);
$this->ensureI18nDirectories($config, $log);
// 1. Deploy ai-gateway plugin files
$this->deployPluginFiles($log);
@@ -42,6 +46,27 @@ class AfterInstall
$log->info('LegalAssistance: AfterInstall completed.');
}
private function ensureI18nDirectories(Config $config, Log $log): void
{
$basePath = 'custom/Espo/Custom/Resources/i18n';
$language = $config->get('language', 'en_US');
$locales = array_unique(['en_US', 'fa_IR', 'he_IL', $language]);
foreach ($locales as $locale) {
$dir = $basePath . '/' . $locale;
if (!is_dir($dir)) {
if (mkdir($dir, 0775, true)) {
$log->info("LegalAssistance: Created i18n directory: {$dir}");
} else {
$log->warning("LegalAssistance: Failed to create i18n directory: {$dir}");
}
}
}
}
private function deployPluginFiles(Log $log): void
{
// Source: extension's plugins/ directory (relative to EspoCRM root after install)
+132 -9
View File
@@ -3,11 +3,15 @@
* LegalAssistance Extension AfterUninstall
*
* 1. Remove plugin files from ai-gateway
* 2. Clear cache
* 2. Drop LegalAid and DirectAccessReport DB tables (with warning log)
* 3. Remove LegalAssistance-specific columns from Case table
* 4. Remove DocumentTemplate records created by this extension
* 5. Clear cache
************************************************************************/
use Espo\Core\Container;
use Espo\Core\DataManager;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Log;
class AfterUninstall
@@ -16,10 +20,40 @@ class AfterUninstall
{
/** @var Log $log */
$log = $container->getByClass(Log::class);
/** @var EntityManager $entityManager */
$entityManager = $container->getByClass(EntityManager::class);
$log->info('LegalAssistance: Running AfterUninstall...');
// Remove ai-gateway plugin files
// 1. Remove ai-gateway plugin files
$this->removePluginFiles($log);
// 2. Drop tables created by this extension
$this->dropTables($entityManager, $log);
// 3. Remove LegalAssistance-specific columns from Case
$this->cleanCaseColumns($entityManager, $log);
// 4. Remove DocumentTemplate records
$this->removeDocumentTemplates($entityManager, $log);
// 5. Remove custom i18n files
$this->removeCustomI18n($log);
// 6. Clear cache
try {
$dataManager = $container->getByClass(DataManager::class);
$dataManager->clearCache();
$log->info('LegalAssistance: Cache cleared.');
} catch (\Throwable $e) {
$log->warning('LegalAssistance: Cache clear failed: ' . $e->getMessage());
}
$log->info('LegalAssistance: AfterUninstall completed.');
}
private function removePluginFiles(Log $log): void
{
$gatewayPaths = [
'/home/chaim/ai-gateway/plugins/legal-assistance',
'/opt/ai-gateway/plugins/legal-assistance',
@@ -37,15 +71,104 @@ class AfterUninstall
$log->info("LegalAssistance: Removed plugin files from {$dir}");
}
}
}
// Clear cache
try {
$dataManager = $container->getByClass(DataManager::class);
$dataManager->clearCache();
} catch (\Throwable $e) {
$log->warning('LegalAssistance: Cache clear failed: ' . $e->getMessage());
private function dropTables(EntityManager $entityManager, Log $log): void
{
$pdo = $entityManager->getPDO();
$tables = ['legal_aid', 'direct_access_report'];
foreach ($tables as $table) {
try {
$check = $pdo->query("SHOW TABLES LIKE '{$table}'")->rowCount();
if ($check > 0) {
$count = $pdo->query("SELECT COUNT(*) FROM `{$table}`")->fetchColumn();
$log->warning("LegalAssistance: Dropping table '{$table}' ({$count} rows).");
$pdo->exec("DROP TABLE `{$table}`");
$log->info("LegalAssistance: Table '{$table}' dropped.");
}
} catch (\Throwable $e) {
$log->warning("LegalAssistance: Failed to drop table '{$table}': " . $e->getMessage());
}
}
}
$log->info('LegalAssistance: AfterUninstall completed.');
private function cleanCaseColumns(EntityManager $entityManager, Log $log): void
{
$pdo = $entityManager->getPDO();
// Only columns that LegalAssistance module added to Case
$columns = [
'c_legal_aid_type',
'c_aid_type',
'c_urgency_level',
'c_appointment_date',
'c_filing_deadline',
];
try {
$stmt = $pdo->query("SHOW COLUMNS FROM `case`");
$existing = array_column($stmt->fetchAll(\PDO::FETCH_ASSOC), 'Field');
foreach ($columns as $col) {
if (in_array($col, $existing)) {
$pdo->exec("ALTER TABLE `case` DROP COLUMN `{$col}`");
$log->info("LegalAssistance: Dropped column case.{$col}");
}
}
} catch (\Throwable $e) {
$log->warning("LegalAssistance: Failed to clean Case columns: " . $e->getMessage());
}
}
private function removeDocumentTemplates(EntityManager $entityManager, Log $log): void
{
try {
$template = $entityManager
->getRDBRepository('DocumentTemplate')
->where(['name' => 'דוח גישה ישירה'])
->findOne();
if ($template) {
$fileId = $template->get('fileId');
$entityManager->removeEntity($template);
$log->info("LegalAssistance: Removed DocumentTemplate 'דוח גישה ישירה'");
// Remove the attachment file
if ($fileId) {
$attachment = $entityManager->getEntityById('Attachment', $fileId);
if ($attachment) {
$filePath = 'data/upload/' . $fileId;
if (file_exists($filePath)) {
unlink($filePath);
}
$entityManager->removeEntity($attachment);
$log->info("LegalAssistance: Removed template attachment file");
}
}
}
} catch (\Throwable $e) {
$log->warning("LegalAssistance: Failed to remove DocumentTemplate: " . $e->getMessage());
}
}
private function removeCustomI18n(Log $log): void
{
$files = [
'custom/Espo/Custom/Resources/i18n/fa_IR/DirectAccessReport.json',
'custom/Espo/Custom/Resources/i18n/fa_IR/LegalAid.json',
'custom/Espo/Custom/Resources/i18n/he_IL/DirectAccessReport.json',
'custom/Espo/Custom/Resources/i18n/he_IL/LegalAid.json',
'custom/Espo/Custom/Resources/i18n/en_US/DirectAccessReport.json',
'custom/Espo/Custom/Resources/i18n/en_US/LegalAid.json',
];
foreach ($files as $file) {
if (file_exists($file)) {
unlink($file);
$log->info("LegalAssistance: Removed {$file}");
}
}
}
}
Binary file not shown.