diff --git a/.vscode/settings.json b/.vscode/settings.json index 9f00ecbf..30ab8c88 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,6 +24,14 @@ "/^python3 custom/scripts/validate_and_rebuild\\.py 2>&1 \\| tail -15$/": { "approve": true, "matchCommandLine": true + }, + "/^python3 custom/scripts/validate_and_rebuild\\.py$/": { + "approve": true, + "matchCommandLine": true + }, + "/^python3 /var/lib/docker/volumes/vmh-espocrm_espocrm/_data/custom/scripts/validate_and_rebuild\\.py$/": { + "approve": true, + "matchCommandLine": true } } } \ No newline at end of file diff --git a/client/custom/src/handlers/mietobjekt/eviction-action.js b/client/custom/src/handlers/mietobjekt/eviction-action.js new file mode 100644 index 00000000..2cc1628e --- /dev/null +++ b/client/custom/src/handlers/mietobjekt/eviction-action.js @@ -0,0 +1,57 @@ +define('custom:handlers/mietobjekt/eviction-action', [], function () { + + /** + * Handler for initiating eviction lawsuit from Mietobjekt + */ + return class { + + constructor(view) { + this.view = view; + } + + /** + * Initialize the handler + */ + initEvictionAction() { + // Initialization if needed + } + + /** + * Action to initiate eviction (Räumungsklage) + */ + initiateEviction() { + const model = this.view.model; + + // Confirm dialog + this.view.confirm( + this.view.translate('initiateEvictionConfirmation', 'messages', 'CMietobjekt'), + () => { + Espo.Ui.notify(this.view.translate('pleaseWait', 'messages')); + + // POST request to backend + Espo.Ajax.postRequest('CMietobjekt/action/initiateEviction', { + id: model.id + }).then(response => { + Espo.Ui.success( + this.view.translate('evictionCreatedSuccess', 'messages', 'CMietobjekt') + ); + + // Navigate to the newly created Räumungsklage + this.view.getRouter().navigate( + '#CVmhRumungsklage/view/' + response.id, + {trigger: true} + ); + }).catch(xhr => { + let message = this.view.translate('evictionCreatedError', 'messages', 'CMietobjekt'); + + if (xhr.responseJSON && xhr.responseJSON.message) { + message = xhr.responseJSON.message; + } + + Espo.Ui.error(message); + }); + } + ); + } + }; +}); diff --git a/custom/Espo/Custom/Controllers/CMietobjekt.php b/custom/Espo/Custom/Controllers/CMietobjekt.php index 1a4f5380..d904faaa 100644 --- a/custom/Espo/Custom/Controllers/CMietobjekt.php +++ b/custom/Espo/Custom/Controllers/CMietobjekt.php @@ -2,6 +2,27 @@ namespace Espo\Custom\Controllers; +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Api\Request; + class CMietobjekt extends \Espo\Core\Templates\Controllers\Base { + /** + * POST Action: Initiate eviction lawsuit from Mietobjekt + */ + public function postActionInitiateEviction(Request $request): array + { + $data = $request->getParsedBody(); + + $id = $data->id ?? null; + if (!$id) { + throw new BadRequest('No Mietobjekt ID provided'); + } + + $service = $this->getRecordService(); + $result = $service->initiateEviction($id); + + return $result; + } } diff --git a/custom/Espo/Custom/Resources/i18n/de_DE/CMietobjekt.json b/custom/Espo/Custom/Resources/i18n/de_DE/CMietobjekt.json index 9dc0f9d6..9638c128 100644 --- a/custom/Espo/Custom/Resources/i18n/de_DE/CMietobjekt.json +++ b/custom/Espo/Custom/Resources/i18n/de_DE/CMietobjekt.json @@ -18,9 +18,15 @@ "dokumentesMietobjekt": "Dokumente" }, "labels": { - "Create CMietobjekt": "Mietobjekt erstellen" + "Create CMietobjekt": "Mietobjekt erstellen", + "Räumungsklage einleiten": "Räumungsklage einleiten" }, "tooltips": { "lage": "Lage innerhalb des Objekts (z.B. EG links, 1. OG rechts)" + }, + "messages": { + "initiateEvictionConfirmation": "Möchten Sie für dieses Mietobjekt eine Räumungsklage einleiten? Es werden automatisch alle zugehörigen Mietverhältnisse, Vermieter (als Kläger) und Mieter (als Beklagte) verknüpft.", + "evictionCreatedSuccess": "Räumungsklage wurde erfolgreich erstellt", + "evictionCreatedError": "Fehler beim Erstellen der Räumungsklage" } } \ No newline at end of file diff --git a/custom/Espo/Custom/Resources/i18n/en_US/CMietobjekt.json b/custom/Espo/Custom/Resources/i18n/en_US/CMietobjekt.json index 2be10fca..fc630b1c 100644 --- a/custom/Espo/Custom/Resources/i18n/en_US/CMietobjekt.json +++ b/custom/Espo/Custom/Resources/i18n/en_US/CMietobjekt.json @@ -24,7 +24,8 @@ "dokumentesMietobjekt": "Documents" }, "labels": { - "Create CMietobjekt": "Create Mietobjekt" + "Create CMietobjekt": "Create Mietobjekt", + "Räumungsklage einleiten": "Initiate Eviction Lawsuit" }, "options": { "objekttyp": { @@ -42,5 +43,10 @@ }, "tooltips": { "lage": "Location within the property (e.g. ground floor left, 1st floor right)" + }, + "messages": { + "initiateEvictionConfirmation": "Do you want to initiate an eviction lawsuit for this property? All associated rental agreements, landlords (as plaintiffs) and tenants (as defendants) will be automatically linked.", + "evictionCreatedSuccess": "Eviction lawsuit created successfully", + "evictionCreatedError": "Error creating eviction lawsuit" } } \ No newline at end of file diff --git a/custom/Espo/Custom/Resources/metadata/clientDefs/CMietobjekt.json b/custom/Espo/Custom/Resources/metadata/clientDefs/CMietobjekt.json index 21d6326f..d56ba627 100644 --- a/custom/Espo/Custom/Resources/metadata/clientDefs/CMietobjekt.json +++ b/custom/Espo/Custom/Resources/metadata/clientDefs/CMietobjekt.json @@ -5,6 +5,22 @@ ], "color": "#dea185", "iconClass": "fas fa-house", + "menu": { + "detail": { + "buttons": [ + { + "label": "Räumungsklage einleiten", + "name": "initiateEviction", + "iconHtml": "", + "style": "danger", + "acl": "edit", + "handler": "custom:handlers/mietobjekt/eviction-action", + "initFunction": "initEvictionAction", + "actionFunction": "initiateEviction" + } + ] + } + }, "relationshipPanels": { "vmhMietverhltnises2Mietobjekt": { "layout": null, diff --git a/custom/Espo/Custom/Services/CMietobjekt.php b/custom/Espo/Custom/Services/CMietobjekt.php new file mode 100644 index 00000000..de1cd882 --- /dev/null +++ b/custom/Espo/Custom/Services/CMietobjekt.php @@ -0,0 +1,132 @@ +entityManager->getEntity('CMietobjekt', $mietobjektId); + if (!$mietobjekt) { + throw new NotFound('Mietobjekt not found'); + } + + // Check ACL - Read and Create permissions + if (!$this->acl->check($mietobjekt, 'read')) { + throw new Forbidden('No read access to Mietobjekt'); + } + + if (!$this->acl->checkScope('CVmhRumungsklage', 'create')) { + throw new Forbidden('No create access to Räumungsklage'); + } + + // Start transaction to ensure atomicity + $this->entityManager->getTransactionManager()->start(); + + try { + // Prepare data for new Räumungsklage + $data = new \stdClass(); + $data->name = 'Räumungsklage - ' . $mietobjekt->get('name'); + + // Copy assignedUser and teams + if ($mietobjekt->get('assignedUserId')) { + $data->assignedUserId = $mietobjekt->get('assignedUserId'); + } + + $teamsIds = $mietobjekt->getLinkMultipleIdList('teams'); + if (!empty($teamsIds)) { + $data->teamsIds = $teamsIds; + } + + // Create Räumungsklage entity + $raeumungsklage = $this->entityManager->createEntity('CVmhRumungsklage', (array)$data); + + if (!$raeumungsklage) { + throw new \RuntimeException('Failed to create Räumungsklage'); + } + + $raeumungsklagenRepo = $this->entityManager->getRepository('CVmhRumungsklage'); + + // Link Mietobjekt to Räumungsklage + $raeumungsklagenRepo + ->getRelation($raeumungsklage, 'mietobjekte') + ->relate($mietobjekt); + + // Get and link Mietverhältnisse + $mietverhaeltnisse = $this->entityManager + ->getRepository('CMietobjekt') + ->getRelation($mietobjekt, 'vmhMietverhltnises') + ->find(); + + foreach ($mietverhaeltnisse as $mietverhaeltnis) { + // Link Mietverhältnis to Räumungsklage + $raeumungsklagenRepo + ->getRelation($raeumungsklage, 'vmhMietverhltnises') + ->relate($mietverhaeltnis); + + // Get Vermieter (Kläger) from Mietverhältnis + $vermieterBeteiligte = $this->entityManager + ->getRepository('CVmhMietverhltnis') + ->getRelation($mietverhaeltnis, 'vmhbeteiligtevermieter') + ->find(); + + foreach ($vermieterBeteiligte as $vermieter) { + // Link as Kläger + $raeumungsklagenRepo + ->getRelation($raeumungsklage, 'klaeger') + ->relate($vermieter); + } + + // Get Mieter (Beklagte) from Mietverhältnis + $mieterBeteiligte = $this->entityManager + ->getRepository('CVmhMietverhltnis') + ->getRelation($mietverhaeltnis, 'vmhbeteiligtemieter') + ->find(); + + foreach ($mieterBeteiligte as $mieter) { + // Link as Beklagte + $raeumungsklagenRepo + ->getRelation($raeumungsklage, 'beklagte') + ->relate($mieter); + } + } + + // Copy portal contacts from Mietobjekt + $portalContacts = $this->entityManager + ->getRepository('CMietobjekt') + ->getRelation($mietobjekt, 'contactsMietobjekt') + ->find(); + + foreach ($portalContacts as $contact) { + $raeumungsklagenRepo + ->getRelation($raeumungsklage, 'contactsRumungsklage') + ->relate($contact); + } + + // Commit transaction + $this->entityManager->getTransactionManager()->commit(); + + return [ + 'id' => $raeumungsklage->getId(), + 'name' => $raeumungsklage->get('name') + ]; + } catch (\Exception $e) { + // Rollback on any error + $this->entityManager->getTransactionManager()->rollback(); + throw $e; + } + } +} diff --git a/data/config.php b/data/config.php index 8e48496a..e7a1339f 100644 --- a/data/config.php +++ b/data/config.php @@ -359,8 +359,8 @@ return [ 0 => 'youtube.com', 1 => 'google.com' ], - 'cacheTimestamp' => 1769212339, - 'microtime' => 1769212339.249026, + 'cacheTimestamp' => 1769249126, + 'microtime' => 1769249126.108164, 'siteUrl' => 'https://crm.bitbylaw.com', 'fullTextSearchMinLength' => 4, 'appTimestamp' => 1768843902,