Refactor AdvowareAkte ↔ CDokumente relationship from junction table to direct n:1 relationship

- Removed CAdvowareAktenCDokumente junction table and associated service.
- Updated CDokumente entity to include foreign key cAdvowareAktenId and related fields.
- Changed relationship in CDokumente from hasMany to belongsTo.
- Updated CAdvowareAkten to reflect new direct relationship.
- Implemented CDokumente service with duplicateDocument method for document duplication.
- Refactored hooks to support new relationship and document propagation.
- Removed obsolete API routes related to the junction table.
- Added i18n translations for new fields and updated tooltips.
- Document flow and auto-linking logic enhanced for better integration with Advoware.
- Validation checks passed, and no data migration needed.
This commit is contained in:
2026-03-23 20:36:10 +01:00
parent 0b829e9dfe
commit 22665948e4
22 changed files with 689 additions and 773 deletions

View File

@@ -1,14 +0,0 @@
<?php
namespace Espo\Custom\Services;
use Espo\Services\Record;
/**
* Junction Service: CAdvowareAkten ↔ CDokumente
*
* Handles business logic for the junction table.
*/
class CAdvowareAktenCDokumente extends Record
{
// Standard CRUD logic inherited from Record service
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Espo\Custom\Services;
use Espo\Services\Record;
use Espo\ORM\Entity;
use Espo\Core\Exceptions\{Forbidden, NotFound, BadRequest};
use Espo\Core\FileStorage\Manager as FileStorageManager;
use Espo\Core\Utils\File\Manager as FileManager;
/**
* Service: CDokumente
*/
class CDokumente extends Record
{
public function __construct(
private FileStorageManager $fileStorageManager,
private FileManager $fileManager
) {
parent::__construct();
}
/**
* Duplicate a document entity including its attachment file
*
* This creates a complete copy of a document with:
* - All entity fields (name, description, etc.)
* - Physical attachment file copied to new location
* - Recalculated blake3hash
* - Reset fileStatus to 'new'
*
* @param string $documentId Source document ID to duplicate
* @return Entity New CDokumente entity
* @throws NotFound If document doesn't exist
* @throws Forbidden If no read access
* @throws BadRequest If document has no attachment
*/
public function duplicateDocument(string $documentId): Entity
{
// 1. Load source document
$sourceDoc = $this->entityManager->getEntity('CDokumente', $documentId);
if (!$sourceDoc) {
throw new NotFound('Document not found');
}
// 2. ACL Check
if (!$this->acl->check($sourceDoc, 'read')) {
throw new Forbidden('No read access to document');
}
// 3. Get source attachment
$sourceAttachmentId = $sourceDoc->get('dokumentId');
if (!$sourceAttachmentId) {
throw new BadRequest('Document has no attachment');
}
$sourceAttachment = $this->entityManager->getEntity('Attachment', $sourceAttachmentId);
if (!$sourceAttachment) {
throw new BadRequest('Source attachment not found');
}
try {
// 4. Copy attachment file physically
$newAttachment = $this->duplicateAttachment($sourceAttachment);
// 5. Create new document entity
$newDoc = $this->entityManager->getEntity('CDokumente');
// Copy all relevant fields
$newDoc->set([
'name' => $sourceDoc->get('name'),
'description' => $sourceDoc->get('description'),
'dokumentId' => $newAttachment->getId(),
'assignedUserId' => $sourceDoc->get('assignedUserId'),
'fileStatus' => 'new' // Reset to 'new'
]);
// Copy teams
$teamsIds = $sourceDoc->getLinkMultipleIdList('teams');
if (!empty($teamsIds)) {
$newDoc->setLinkMultipleIdList('teams', $teamsIds);
}
// Copy preview if exists
if ($sourceDoc->get('previewId')) {
$sourcePreview = $this->entityManager->getEntity('Attachment', $sourceDoc->get('previewId'));
if ($sourcePreview) {
$newPreview = $this->duplicateAttachment($sourcePreview);
$newDoc->set('previewId', $newPreview->getId());
}
}
// 6. Save new document (this will trigger blake3hash calculation via Hook)
$this->entityManager->saveEntity($newDoc);
// 7. Return new document
return $newDoc;
} catch (\Exception $e) {
$GLOBALS['log']->error('CDokumente duplicateDocument Error: ' . $e->getMessage());
throw new \RuntimeException('Failed to duplicate document: ' . $e->getMessage());
}
}
/**
* Duplicate an attachment entity including physical file
*
* @param Entity $sourceAttachment Source attachment to duplicate
* @return Entity New attachment entity
*/
private function duplicateAttachment(Entity $sourceAttachment): Entity
{
// 1. Get source file path
$sourceFilePath = $this->fileStorageManager->getLocalFilePath($sourceAttachment);
if (!file_exists($sourceFilePath)) {
throw new \RuntimeException('Source file not found: ' . $sourceFilePath);
}
// 2. Read source file content
$fileContent = $this->fileManager->getContents($sourceFilePath);
// 3. Create new attachment entity
$newAttachment = $this->entityManager->getEntity('Attachment');
$newAttachment->set([
'name' => $sourceAttachment->get('name'),
'type' => $sourceAttachment->get('type'),
'size' => $sourceAttachment->get('size'),
'role' => $sourceAttachment->get('role') ?? 'Attachment',
'storageFilePath' => null // Will be set by putContents
]);
$this->entityManager->saveEntity($newAttachment);
// 4. Write file content to new location
$this->fileStorageManager->putContents($newAttachment, $fileContent);
// 5. Return new attachment
return $newAttachment;
}
}

View File

@@ -130,19 +130,27 @@ class CVmhMietverhltnis extends \Espo\Services\Record
}
// 10. Copy all documents from Mietverhältnis, Mietobjekt and Beteiligte
// 10a. Dokumente vom Mietverhältnis
// Get CDokumente service for duplication
$dokumenteService = $this->injectableFactory->create(\Espo\Custom\Services\CDokumente::class);
// 10a. Dokumente vom Mietverhältnis - DUPLICATE instead of relate
$dokumenteMV = $this->entityManager
->getRepository('CVmhMietverhltnis')
->getRelation($mietverhaeltnis, 'dokumentesvmhMietverhltnisse')
->find();
foreach ($dokumenteMV as $dokument) {
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($dokument);
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Mietverhältnis: ' . $e->getMessage());
}
}
// 10b. Dokumente vom Mietobjekt
// 10b. Dokumente vom Mietobjekt - DUPLICATE instead of relate
if ($mietobjekt) {
$dokumenteMO = $this->entityManager
->getRepository('CMietobjekt')
@@ -150,13 +158,18 @@ class CVmhMietverhltnis extends \Espo\Services\Record
->find();
foreach ($dokumenteMO as $dokument) {
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($dokument);
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Mietobjekt: ' . $e->getMessage());
}
}
}
// 10c. Dokumente von allen Beteiligten (Vermieter + Mieter + Sonstige)
// 10c. Dokumente von allen Beteiligten (Vermieter + Mieter + Sonstige) - DUPLICATE instead of relate
$alleBeteiligte = array_merge(
iterator_to_array($vermieterBeteiligte),
iterator_to_array($mieterBeteiligte),
@@ -170,9 +183,14 @@ class CVmhMietverhltnis extends \Espo\Services\Record
->find();
foreach ($dokumenteBet as $dokument) {
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($dokument);
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$mietinkassoRepo
->getRelation($mietinkasso, 'dokumentesmietinkasso')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Beteiligter: ' . $e->getMessage());
}
}
}

View File

@@ -267,7 +267,10 @@ class CVmhRumungsklage extends \Espo\Services\Record
// 7. Collect all documents from Mietverhältnisse, Kündigungen, Mietobjekte and Beteiligte
$alleLinkedDokumente = [];
// 7a. Dokumente from all Mietverhältnisse
// Get CDokumente service for duplication
$dokumenteService = $this->injectableFactory->create(\Espo\Custom\Services\CDokumente::class);
// 7a. Dokumente from all Mietverhältnisse - DUPLICATE instead of relate
foreach ($alleMietverhaeltnisse as $mv) {
$dokumenteMV = $this->entityManager
->getRepository('CVmhMietverhltnis')
@@ -277,14 +280,21 @@ class CVmhRumungsklage extends \Espo\Services\Record
foreach ($dokumenteMV as $dokument) {
if (!in_array($dokument->getId(), $alleLinkedDokumente)) {
$alleLinkedDokumente[] = $dokument->getId();
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($dokument);
// Duplicate document instead of relate
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Mietverhältnis: ' . $e->getMessage());
}
}
}
}
// 7b. Dokumente from all Kündigungen
// 7b. Dokumente from all Kündigungen - DUPLICATE instead of relate
foreach ($alleKuendigungen as $kuendigung) {
$dokumenteKuendigung = $this->entityManager
->getRepository('CKuendigung')
@@ -294,14 +304,21 @@ class CVmhRumungsklage extends \Espo\Services\Record
foreach ($dokumenteKuendigung as $dokument) {
if (!in_array($dokument->getId(), $alleLinkedDokumente)) {
$alleLinkedDokumente[] = $dokument->getId();
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($dokument);
// Duplicate document instead of relate
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Kündigung: ' . $e->getMessage());
}
}
}
}
// 7c. Dokumente from all Mietobjekte
// 7c. Dokumente from all Mietobjekte - DUPLICATE instead of relate
foreach ($alleMietobjekte as $mietobjekt) {
$dokumenteMO = $this->entityManager
->getRepository('CMietobjekt')
@@ -311,14 +328,21 @@ class CVmhRumungsklage extends \Espo\Services\Record
foreach ($dokumenteMO as $dokument) {
if (!in_array($dokument->getId(), $alleLinkedDokumente)) {
$alleLinkedDokumente[] = $dokument->getId();
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($dokument);
// Duplicate document instead of relate
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Mietobjekt: ' . $e->getMessage());
}
}
}
}
// 7d. Dokumente from all Beteiligte
// 7d. Dokumente from all Beteiligte - DUPLICATE instead of relate
$alleBeteiligte = array_merge($alleVermieter, $alleMieter, $alleSonstigeBewohner);
foreach ($alleBeteiligte as $beteiligter) {
$dokumenteBet = $this->entityManager
@@ -329,9 +353,16 @@ class CVmhRumungsklage extends \Espo\Services\Record
foreach ($dokumenteBet as $dokument) {
if (!in_array($dokument->getId(), $alleLinkedDokumente)) {
$alleLinkedDokumente[] = $dokument->getId();
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($dokument);
// Duplicate document instead of relate
try {
$duplicatedDoc = $dokumenteService->duplicateDocument($dokument->getId());
$raeumungsklagenRepo
->getRelation($raeumungsklage, 'dokumentesvmhraumungsklage')
->relate($duplicatedDoc);
} catch (\Exception $e) {
$GLOBALS['log']->error('Failed to duplicate document from Beteiligter: ' . $e->getMessage());
}
}
}
}