Compare commits

..

7 Commits

Author SHA1 Message Date
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
29 changed files with 349 additions and 489 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,9 @@
<?php
namespace Espo\Modules\LegalAssistance\Controllers;
use Espo\Core\Controllers\Record;
class LegalAid extends Record
{
}
@@ -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()}");
}
}
@@ -5,36 +5,7 @@
"tabs": {
"LegalAssistance": "Legal Assistance"
},
"fields": {
"cLegalAidType": "Legal Aid Type",
"cAppointmentDate": "Appointment Date",
"cAidType": "Aid Type",
"cUrgencyLevel": "Urgency Level",
"cFilingDeadline": "Filing Deadline"
},
"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"
"legalAids": "Legal Aid"
}
}
@@ -1,8 +1,8 @@
{
"scopeNames": {
"DirectAccessReport": "Direct Access Report"
"LegalAid": "Legal Aid"
},
"scopeNamesPlural": {
"DirectAccessReport": "Direct Access Reports"
"LegalAid": "Legal Aid Records"
}
}
@@ -1,20 +1,25 @@
{
"scopeName": "Direct Access Report",
"scopeNamesPlural": "Direct Access Reports",
"scopeName": "Legal Aid",
"scopeNamesPlural": "Legal Aid Records",
"labels": {
"Create DirectAccessReport": "Create Direct Access Report"
"Create LegalAid": "Create Legal Aid Record"
},
"fields": {
"name": "Report Name",
"name": "Name",
"number": "Number",
"status": "Status",
"legalAidNumber": "Legal Aid Number",
"legalAidType": "Legal Aid Type",
"reportDate": "Report Date",
"case": "Case",
"contact": "Contact",
"contactName": "Client Name",
"aidType": "Aid Type",
"urgencyLevel": "Urgency Level",
"appointmentDate": "Appointment Date",
"filingDeadline": "Filing Deadline",
"additionalUrgency": "Additional Urgency",
"additionalDeadline": "Additional Deadline",
"additionalDeadline": "Additional Deadlines",
"firstContactDate": "First Contact Date",
"contactDelayReason": "Contact Delay Reason",
"meetingDate": "Meeting Date",
@@ -27,34 +32,34 @@
"proceedingSubject": "Proceeding Subject",
"claimSummary": "Claim Summary",
"defenseClaims": "Defense Claims",
"q1DocsReviewed": "Committee Reviewed All Documents?",
"q1DocsReviewed": "Committee reviewed all documents?",
"q1DocsDetail": "Documents Detail",
"q2Reasoned": "Decision Reasoned?",
"q2Reasoned": "Decision is reasoned?",
"q2ReasonDetail": "Missing Reasoning Detail",
"q3PanelMatch": "Panel Matches Impairments?",
"q3PanelMatch": "Panel matches impairments?",
"q4PanelComposition": "Panel Composition",
"q5ComplaintsAddressed": "All Complaints Addressed?",
"q5ComplaintsAddressed": "All complaints addressed?",
"q6ComplaintsDetail": "Missing Complaints Detail",
"q7ClinicalExam": "Clinical Exam Performed?",
"q8ClinicalGap": "Clinical vs Documents Gap",
"q9MoharaApplied": "Mohara Doctrine Applied?",
"q7ClinicalExam": "Clinical exam performed?",
"q8ClinicalGap": "Clinical Exam vs Documents Gap",
"q9MoharaApplied": "Mohara doctrine applied?",
"q9MoharaDetail": "Missing Mohara Data",
"q10RehabGap": "Rehab Officer vs Committee Gap?",
"q10RehabGap": "Rehab Officer vs Committee gap?",
"q10RehabDetail": "Gap Detail",
"q11ScoreGap": "Score vs Condition Gap?",
"q11ScoreGap": "Score vs condition gap?",
"q11ScoreDetail": "Score Gap Detail",
"q12UniqueAspects": "Unique Aspects Addressed?",
"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",
"additionalAidNIDetail": "Additional Aid Detail (NI)",
"additionalAidOtherDetail": "Additional Aid Detail (Other)",
"additionalAidUrgency": "Additional Aid Urgency",
"additionalAidDeadlines": "Additional Aid Deadlines",
"willingToRepresent": "Willing to Represent?",
"notes": "General Notes",
"assignedUser": "Assigned User",
"assignedUser": "Assigned Lawyer",
"teams": "Teams",
"createdAt": "Created At",
"modifiedAt": "Modified At",
@@ -64,16 +69,21 @@
"options": {
"status": {
"Draft": "Draft",
"Submitted": "Submitted",
"Approved": "Approved",
"Rejected": "Rejected"
"Active": "Active",
"Closed": "Closed"
},
"legalAidType": {
"": "",
"DirectAccess": "Direct Access",
"Regular": "Regular",
"Duty": "Duty"
},
"aidType": {
"": "",
"הגשת ערעור": "Filing Appeal",
"יעוץ והדרכה": "Counseling & Guidance",
"סיוע נוסף ביטוח לאומי": "Additional NI Aid",
"סיוע נוסף תחום אחר": "Additional Aid Other",
"סיוע נוסף תחום אחר": "Additional Aid (Other)",
"אי מתן סיוע": "No Aid Provided"
},
"urgencyLevel": {
@@ -91,7 +101,7 @@
"contactStatus": {
"": "",
"תקין": "Normal",
"לא תקין": "Not Normal"
"לא תקין": "Abnormal"
},
"courtName": {
"": "",
@@ -129,8 +139,8 @@
"additionalAidNeeded": {
"": "",
"לא נחוץ": "Not Needed",
"נחוץ בתחום ביטוח לאומי": "Needed - NI Field",
"נחוץ בתחום אחר": "Needed - Other Field"
"נחוץ בתחום ביטוח לאומי": "Needed (NI Field)",
"נחוץ בתחום אחר": "Needed (Other Field)"
},
"additionalAidUrgency": {
"": "",
@@ -140,11 +150,12 @@
}
},
"links": {
"case": "Case"
"case": "Case",
"contact": "Contact"
},
"presetFilters": {
"draft": "Draft",
"submitted": "Submitted",
"approved": "Approved"
"draft": "Drafts",
"active": "Active",
"closed": "Closed"
}
}
@@ -5,22 +5,7 @@
"tabs": {
"LegalAssistance": "סיוע משפטי"
},
"fields": {
"cLegalAidType": "סוג מינוי סיוע משפטי",
"cAppointmentDate": "מועד קבלת המינוי",
"cAidType": "מהות הסיוע",
"cUrgencyLevel": "דחיפות",
"cFilingDeadline": "מועד אחרון להגשה"
},
"options": {
"cLegalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
}
},
"links": {
"directAccessReports": "דוחות גישה ישירה"
"legalAids": "סיוע משפטי"
}
}
@@ -1,8 +1,8 @@
{
"scopeNames": {
"DirectAccessReport": "דוח גישה ישירה"
"LegalAid": "סיוע משפטי"
},
"scopeNamesPlural": {
"DirectAccessReport": "דוחות גישה ישירה"
"LegalAid": "סיוע משפטי"
}
}
@@ -1,14 +1,19 @@
{
"scopeName": "דוח גישה ישירה",
"scopeNamesPlural": "דוחות גישה ישירה",
"scopeName": "סיוע משפטי",
"scopeNamesPlural": "סיוע משפטי",
"labels": {
"Create DirectAccessReport": "צור דוח גישה ישירה"
"Create LegalAid": "צור רשומת סיוע משפטי"
},
"fields": {
"name": "שם הדוח",
"name": "שם",
"number": "מספר",
"status": "סטטוס",
"legalAidNumber": "מספר תיק סיוע משפטי",
"legalAidType": "סוג מינוי",
"reportDate": "תאריך הדוח",
"case": "תיק",
"contact": "איש קשר",
"contactName": "שם הלקוח",
"aidType": "מהות הסיוע",
"urgencyLevel": "דחיפות הליך",
"appointmentDate": "מועד קבלת המינוי",
@@ -54,7 +59,7 @@
"additionalAidDeadlines": "מועדים (סיוע נוסף)",
"willingToRepresent": "בתחום התמחות + מעוניין לייצג?",
"notes": "הערות כלליות",
"assignedUser": "אחראי",
"assignedUser": "עורך דין אחראי",
"teams": "צוותים",
"createdAt": "נוצר ב",
"modifiedAt": "עודכן ב",
@@ -64,9 +69,14 @@
"options": {
"status": {
"Draft": "טיוטה",
"Submitted": "נשלח",
"Approved": "אושר",
"Rejected": "נדחה"
"Active": "פעיל",
"Closed": "סגור"
},
"legalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
},
"aidType": {
"": "",
@@ -140,11 +150,12 @@
}
},
"links": {
"case": "תיק"
"case": "תיק",
"contact": "איש קשר"
},
"presetFilters": {
"draft": "טיוטות",
"submitted": "נשלחו",
"approved": "אושרו"
"active": "פעילים",
"closed": "סגורים"
}
}
@@ -5,22 +5,7 @@
"tabs": {
"LegalAssistance": "סיוע משפטי"
},
"fields": {
"cLegalAidType": "סוג מינוי סיוע משפטי",
"cAppointmentDate": "מועד קבלת המינוי",
"cAidType": "מהות הסיוע",
"cUrgencyLevel": "דחיפות",
"cFilingDeadline": "מועד אחרון להגשה"
},
"options": {
"cLegalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
}
},
"links": {
"directAccessReports": "דוחות גישה ישירה"
"legalAids": "סיוע משפטי"
}
}
@@ -1,8 +1,8 @@
{
"scopeNames": {
"DirectAccessReport": "דוח גישה ישירה"
"LegalAid": "סיוע משפטי"
},
"scopeNamesPlural": {
"DirectAccessReport": "דוחות גישה ישירה"
"LegalAid": "סיוע משפטי"
}
}
@@ -1,14 +1,19 @@
{
"scopeName": "דוח גישה ישירה",
"scopeNamesPlural": "דוחות גישה ישירה",
"scopeName": "סיוע משפטי",
"scopeNamesPlural": "סיוע משפטי",
"labels": {
"Create DirectAccessReport": "צור דוח גישה ישירה"
"Create LegalAid": "צור רשומת סיוע משפטי"
},
"fields": {
"name": "שם הדוח",
"name": "שם",
"number": "מספר",
"status": "סטטוס",
"legalAidNumber": "מספר תיק סיוע משפטי",
"legalAidType": "סוג מינוי",
"reportDate": "תאריך הדוח",
"case": "תיק",
"contact": "איש קשר",
"contactName": "שם הלקוח",
"aidType": "מהות הסיוע",
"urgencyLevel": "דחיפות הליך",
"appointmentDate": "מועד קבלת המינוי",
@@ -54,7 +59,7 @@
"additionalAidDeadlines": "מועדים (סיוע נוסף)",
"willingToRepresent": "בתחום התמחות + מעוניין לייצג?",
"notes": "הערות כלליות",
"assignedUser": "אחראי",
"assignedUser": "עורך דין אחראי",
"teams": "צוותים",
"createdAt": "נוצר ב",
"modifiedAt": "עודכן ב",
@@ -64,9 +69,14 @@
"options": {
"status": {
"Draft": "טיוטה",
"Submitted": "נשלח",
"Approved": "אושר",
"Rejected": "נדחה"
"Active": "פעיל",
"Closed": "סגור"
},
"legalAidType": {
"": "",
"DirectAccess": "גישה ישירה",
"Regular": "מינוי רגיל",
"Duty": "תורנות"
},
"aidType": {
"": "",
@@ -140,11 +150,12 @@
}
},
"links": {
"case": "תיק"
"case": "תיק",
"contact": "איש קשר"
},
"presetFilters": {
"draft": "טיוטות",
"submitted": "נשלחו",
"approved": "אושרו"
"active": "פעילים",
"closed": "סגורים"
}
}
@@ -4,6 +4,9 @@
"rows": [
[{"name": "name"}, {"name": "number"}],
[{"name": "case"}, {"name": "status"}],
[{"name": "legalAidNumber"}, {"name": "legalAidType"}],
[{"name": "reportDate"}, false],
[{"name": "contact"}, {"name": "contactName"}],
[{"name": "assignedUser"}, {"name": "teams"}]
]
},
@@ -0,0 +1,11 @@
[
{"name": "number", "width": 8},
{"name": "name", "link": true},
{"name": "legalAidNumber", "width": 12},
{"name": "case", "width": 15},
{"name": "contact", "width": 15},
{"name": "status", "width": 10},
{"name": "aidType", "width": 12},
{"name": "urgencyLevel", "width": 8},
{"name": "assignedUser", "width": 12}
]
@@ -1,8 +1,8 @@
{
"controller": "controllers/record",
"boolFilterList": ["onlyMy"],
"iconClass": "fas fa-file-alt",
"color": "#6C3483",
"iconClass": "fas fa-balance-scale",
"color": "#2E86C1",
"kanbanViewMode": true,
"statusField": "status",
"filterList": [
@@ -11,12 +11,12 @@
"style": "default"
},
{
"name": "submitted",
"style": "warning"
"name": "active",
"style": "success"
},
{
"name": "approved",
"style": "success"
"name": "closed",
"style": "info"
}
],
"menu": {
@@ -1,41 +1,8 @@
{
"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": {
"legalAids": {
"type": "hasMany",
"entity": "DirectAccessReport",
"entity": "LegalAid",
"foreign": "case",
"layoutRelationshipsDisabled": false
}
@@ -11,21 +11,45 @@
},
"status": {
"type": "enum",
"options": ["Draft", "Submitted", "Approved", "Rejected"],
"options": ["Draft", "Active", "Closed"],
"default": "Draft",
"audited": true,
"displayAsLabel": true,
"style": {
"Draft": "default",
"Submitted": "warning",
"Approved": "success",
"Rejected": "danger"
"Active": "success",
"Closed": "info"
}
},
"legalAidNumber": {
"type": "varchar",
"maxLength": 50
},
"legalAidType": {
"type": "enum",
"options": ["", "DirectAccess", "Regular", "Duty"],
"audited": true,
"displayAsLabel": true,
"style": {
"DirectAccess": "primary",
"Regular": "default",
"Duty": "info"
}
},
"reportDate": {
"type": "date"
},
"case": {
"type": "link",
"required": true
},
"contact": {
"type": "link"
},
"contactName": {
"type": "varchar",
"maxLength": 150
},
"aidType": {
"type": "enum",
@@ -232,7 +256,11 @@
"case": {
"type": "belongsTo",
"entity": "Case",
"foreign": "directAccessReports"
"foreign": "legalAids"
},
"contact": {
"type": "belongsTo",
"entity": "Contact"
},
"createdBy": {
"type": "belongsTo",
@@ -256,12 +284,15 @@
"collection": {
"orderBy": "createdAt",
"order": "desc",
"textFilterFields": ["name", "number"]
"textFilterFields": ["name", "number", "legalAidNumber"]
},
"indexes": {
"status": {
"columns": ["status", "deleted"]
},
"legalAidNumber": {
"columns": ["legalAidNumber", "deleted"]
},
"createdAt": {
"columns": ["createdAt"]
}
@@ -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}
]
@@ -2,14 +2,15 @@
"entity": true,
"object": true,
"stream": true,
"tab": false,
"tab": true,
"acl": true,
"aclPortal": false,
"disabled": false,
"module": "LegalAssistance",
"isCustom": false,
"isCustom": true,
"layouts": true,
"notifications": true,
"calendar": false,
"activity": false,
"kanbanStatusIgnoreList": ["Approved", "Rejected"]
"kanbanStatusIgnoreList": ["Closed"]
}
@@ -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;
}
}
@@ -6,7 +6,6 @@ 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
{
@@ -24,10 +23,128 @@ class GenerateInitialReport
$this->log->info("LegalAssistance: Generating initial report for case {$caseId}");
$service = $this->injectableFactory->create(DirectAccessReportService::class);
$case = $this->entityManager->getEntityById('Case', $caseId);
if (!$case) {
throw new Error("Case {$caseId} not found.");
}
$result = $service->generateReport($caseId, $userId, $params);
// 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);
}
return $result;
$contactName = $contact
? trim($contact->get('firstName') . ' ' . $contact->get('lastName'))
: 'ללא איש קשר';
// Extract legal analysis from nested object
$legalAnalysis = $params['legalAnalysis'] ?? [];
if (is_string($legalAnalysis)) {
$legalAnalysis = json_decode($legalAnalysis, true) ?? [];
}
// Find or create LegalAid
$legalAid = $this->entityManager
->getRDBRepository('LegalAid')
->where(['caseId' => $caseId])
->findOne();
if (!$legalAid) {
$legalAid = $this->entityManager->getNewEntity('LegalAid');
}
$legalAid->set([
'name' => "סיוע משפטי — {$contactName}",
'status' => 'Active',
'caseId' => $caseId,
'contactId' => $contactId,
'contactName' => $contactName,
'assignedUserId' => $userId,
'legalAidNumber' => $case->get('cLegalAidNumber') ?? '',
'reportDate' => date('Y-m-d'),
'aidType' => $params['aidType'] ?? $legalAid->get('aidType'),
'urgencyLevel' => $params['urgencyLevel'] ?? $legalAid->get('urgencyLevel'),
'appointmentDate' => $this->normalizeDate($params['appointmentDate'] ?? null) ?? $legalAid->get('appointmentDate'),
'filingDeadline' => $this->normalizeDate($params['filingDeadline'] ?? null) ?? $legalAid->get('filingDeadline'),
'additionalUrgency' => $params['additionalUrgency'] ?? $legalAid->get('additionalUrgency'),
'additionalDeadline' => $params['additionalDeadline'] ?? $legalAid->get('additionalDeadline'),
'firstContactDate' => $this->normalizeDate($params['firstContactDate'] ?? null) ?? $legalAid->get('firstContactDate'),
'contactDelayReason' => $params['contactDelayReason'] ?? $legalAid->get('contactDelayReason'),
'meetingDate' => $this->normalizeDate($params['meetingDate'] ?? null) ?? $legalAid->get('meetingDate'),
'meetingDelayReason' => $params['meetingDelayReason'] ?? $legalAid->get('meetingDelayReason'),
'meetingAttendees' => $params['meetingAttendees'] ?? $legalAid->get('meetingAttendees'),
'contactStatus' => $params['contactStatus'] ?? $legalAid->get('contactStatus'),
'contactStatusDetail' => $params['contactStatusDetail'] ?? $legalAid->get('contactStatusDetail'),
'proceedingNumber' => $params['proceedingNumber'] ?? $legalAid->get('proceedingNumber') ?? $case->get('cCourtCaseNumber'),
'courtName' => $params['courtName'] ?? $legalAid->get('courtName'),
'proceedingSubject' => $params['proceedingSubject'] ?? $legalAid->get('proceedingSubject'),
'claimSummary' => $params['claimSummary'] ?? $legalAid->get('claimSummary'),
'defenseClaims' => $params['defenseClaims'] ?? $legalAid->get('defenseClaims'),
'q1DocsReviewed' => $legalAnalysis['q1DocsReviewed'] ?? $legalAid->get('q1DocsReviewed') ?? false,
'q1DocsDetail' => $legalAnalysis['q1DocsDetail'] ?? $legalAid->get('q1DocsDetail'),
'q2Reasoned' => $legalAnalysis['q2Reasoned'] ?? $legalAid->get('q2Reasoned') ?? false,
'q2ReasonDetail' => $legalAnalysis['q2ReasonDetail'] ?? $legalAid->get('q2ReasonDetail'),
'q3PanelMatch' => $legalAnalysis['q3PanelMatch'] ?? $legalAid->get('q3PanelMatch') ?? false,
'q4PanelComposition' => $legalAnalysis['q4PanelComposition'] ?? $legalAid->get('q4PanelComposition'),
'q5ComplaintsAddressed' => $legalAnalysis['q5ComplaintsAddressed'] ?? $legalAid->get('q5ComplaintsAddressed') ?? false,
'q6ComplaintsDetail' => $legalAnalysis['q6ComplaintsDetail'] ?? $legalAid->get('q6ComplaintsDetail'),
'q7ClinicalExam' => $legalAnalysis['q7ClinicalExam'] ?? $legalAid->get('q7ClinicalExam') ?? false,
'q8ClinicalGap' => $legalAnalysis['q8ClinicalGap'] ?? $legalAid->get('q8ClinicalGap'),
'q9MoharaApplied' => $legalAnalysis['q9MoharaApplied'] ?? $legalAid->get('q9MoharaApplied') ?? false,
'q9MoharaDetail' => $legalAnalysis['q9MoharaDetail'] ?? $legalAid->get('q9MoharaDetail'),
'q10RehabGap' => $legalAnalysis['q10RehabGap'] ?? $legalAid->get('q10RehabGap') ?? false,
'q10RehabDetail' => $legalAnalysis['q10RehabDetail'] ?? $legalAid->get('q10RehabDetail'),
'q11ScoreGap' => $legalAnalysis['q11ScoreGap'] ?? $legalAid->get('q11ScoreGap') ?? false,
'q11ScoreDetail' => $legalAnalysis['q11ScoreDetail'] ?? $legalAid->get('q11ScoreDetail'),
'q12UniqueAspects' => $legalAnalysis['q12UniqueAspects'] ?? $legalAid->get('q12UniqueAspects') ?? false,
'q12UniqueDetail' => $legalAnalysis['q12UniqueDetail'] ?? $legalAid->get('q12UniqueDetail'),
'recommendationType' => $params['recommendationType'] ?? $legalAid->get('recommendationType'),
'recommendationDetail' => $params['recommendationDetail'] ?? $legalAid->get('recommendationDetail'),
'additionalAidNeeded' => $params['additionalAidNeeded'] ?? $legalAid->get('additionalAidNeeded'),
'additionalAidNIDetail' => $params['additionalAidNIDetail'] ?? $legalAid->get('additionalAidNIDetail'),
'additionalAidOtherDetail' => $params['additionalAidOtherDetail'] ?? $legalAid->get('additionalAidOtherDetail'),
'additionalAidUrgency' => $params['additionalAidUrgency'] ?? $legalAid->get('additionalAidUrgency'),
'additionalAidDeadlines' => $params['additionalAidDeadlines'] ?? $legalAid->get('additionalAidDeadlines'),
'willingToRepresent' => $params['willingToRepresent'] ?? $legalAid->get('willingToRepresent') ?? false,
'notes' => $params['notes'] ?? $legalAid->get('notes'),
]);
$this->entityManager->saveEntity($legalAid);
$this->log->info("LegalAssistance: Saved LegalAid {$legalAid->getId()} for case {$caseId}");
return [
'success' => true,
'message' => "✅ רשומת סיוע משפטי נוצרה/עודכנה: \"{$contactName}\"",
'entityType' => 'LegalAid',
'entityId' => $legalAid->getId(),
];
}
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;
}
}
+3 -3
View File
@@ -1,11 +1,11 @@
{
"name": "LegalAssistance",
"version": "1.2.2",
"version": "1.5.0",
"acceptableVersions": [">=8.0.0"],
"php": [">=8.1"],
"releaseDate": "2026-04-06",
"releaseDate": "2026-04-07",
"author": "Marcus-Law",
"description": "סיוע משפטי — דוח גישה ישירה, משימות אוטומטיות, אינטגרציה עם שירה",
"description": "סיוע משפטי — ישות LegalAid מרכזית, דוח גישה ישירה, משימות אוטומטיות, אינטגרציה עם שירה",
"scripts": {
"afterInstall": "scripts/AfterInstall.php",
"afterUninstall": "scripts/AfterUninstall.php"
+34 -1
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.",
"description": "Generate Direct Access initial report (דוח דיווח ראשוני — גישה ישירה). Call ONLY after collecting ALL required data through conversation with the lawyer. This tool creates/updates a LegalAid entity and generates a DOCX document from it.",
"parameters": {
"type": "object",
"properties": {
@@ -148,6 +148,39 @@
},
"required": ["aidType", "proceedingSubject", "recommendationType"]
}
},
"update_legal_aid": {
"description": "Update fields on an existing LegalAid entity. Use this to fill or modify any field on the legal aid record. Pass only the fields you want to update — existing values are preserved for unspecified fields. You can update any combination of fields.",
"parameters": {
"type": "object",
"properties": {
"legalAidId": {
"type": "string",
"description": "ID of the LegalAid entity to update"
},
"fields": {
"type": "object",
"description": "Key-value pairs of fields to update. Any field from the LegalAid entity is valid: aidType, urgencyLevel, appointmentDate, filingDeadline, firstContactDate, contactDelayReason, meetingDate, meetingDelayReason, meetingAttendees, contactStatus, contactStatusDetail, proceedingNumber, courtName, proceedingSubject, claimSummary, defenseClaims, q1DocsReviewed, q1DocsDetail, q2Reasoned, q2ReasonDetail, q3PanelMatch, q4PanelComposition, q5ComplaintsAddressed, q6ComplaintsDetail, q7ClinicalExam, q8ClinicalGap, q9MoharaApplied, q9MoharaDetail, q10RehabGap, q10RehabDetail, q11ScoreGap, q11ScoreDetail, q12UniqueAspects, q12UniqueDetail, recommendationType, recommendationDetail, additionalAidNeeded, additionalAidNIDetail, additionalAidOtherDetail, additionalAidUrgency, additionalAidDeadlines, willingToRepresent, notes, status, contactName, legalAidNumber, reportDate, additionalUrgency, additionalDeadline"
}
},
"required": ["legalAidId", "fields"]
}
},
"get_legal_aid": {
"description": "Read an existing LegalAid entity and return all its field values. Use this to check what data has already been filled before updating.",
"parameters": {
"type": "object",
"properties": {
"legalAidId": {
"type": "string",
"description": "ID of the LegalAid entity to read"
},
"caseId": {
"type": "string",
"description": "Alternatively, find the LegalAid by Case ID"
}
}
}
}
}
}
+26 -1
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)
@@ -128,7 +153,7 @@ class AfterInstall
$template = $entityManager->getNewEntity('DocumentTemplate');
$template->set([
'name' => 'דוח גישה ישירה',
'entityType' => 'Case',
'entityType' => 'LegalAid',
'fileId' => $attachment->getId(),
]);
$entityManager->saveEntity($template);
Binary file not shown.