# Refactoring: Junction Table to n:1 Relationship
## AdvowareAkte ↔ CDokumente

**Date:** 23. März 2026  
**Status:** ✅ COMPLETE

---

## Summary

Successfully refactored the relationship between CAdvowareAkten and CDokumente from a many-to-many junction table (CAdvowareAktenCDokumente) to a direct n:1 (many-to-one) relationship using a foreign key.

---

## Changes Implemented

### Phase 1: Database & Entity Structure ✅

**CDokumente entity** - Added fields:
- `cAdvowareAktenId` (varchar 17) - Foreign key to CAdvowareAkten
- `cAdvowareAktenName` (varchar) - Name field for relationship
- `hnr` (int) - Advoware hierarchical reference number
- `syncStatus` (enum) - Values: new, unclean, synced, failed, unsupported
- `syncedHash` (varchar 64) - For change detection

**Relationship changes:**
- CDokumente → CAdvowareAkten: Changed from `hasMany` (junction) to `belongsTo` with foreign key
- CAdvowareAkten → CDokumente: Changed from `hasMany` (junction) to `hasMany` with direct foreign field
- Removed `columnAttributeMap` and `additionalColumns`
- Disabled old junction column fields in CAdvowareAkten (marked as `disabled: true`)

**Deleted files:**
- `custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAktenCDokumente.json`
- `custom/Espo/Custom/Services/CAdvowareAktenCDokumente.php`

### Phase 2: Junction API Removal ✅

**Deleted API files:**
- `custom/Espo/Custom/Api/JunctionData/GetAktenDokumentes.php`
- `custom/Espo/Custom/Api/JunctionData/LinkAktenDokument.php`
- `custom/Espo/Custom/Api/JunctionData/UpdateAktenJunction.php`

**Updated routes:**
- Removed 3 AdvowareAkten junction routes from `custom/Espo/Custom/Resources/routes.json`
- Kept AIKnowledge junction routes (unchanged)

### Phase 3: Hooks Refactoring ✅

**UpdateJunctionSyncStatus.php** (CDokumente)
- Removed AdvowareAkten junction table updates
- Now sets `syncStatus = 'unclean'` directly on CDokumente entity when document is modified
- Updates parent AdvowareAkte's syncStatus as well
- Kept AIKnowledge junction updates (unchanged)

**DokumenteSyncStatus.php** (CAdvowareAkten)
- ✅ DELETED - No longer needed with direct fields

**PropagateDocumentsUp.php** (CAdvowareAkten)
- Refactored from `AfterRelate/AfterUnrelate` to `AfterSave`
- Now triggers when `cAdvowareAktenId` changes on CDokumente
- Propagates to Räumungsklage/Mietinkasso
- Also propagates to AICollection
- Enhanced with loop protection

### Phase 4: Dokumenten-Duplikation ✅

**CDokumente Service** (NEW)
- Created `custom/Espo/Custom/Services/CDokumente.php`
- Implemented `duplicateDocument()` method:
  - Copies entity fields (name, description, etc.)
  - Duplicates attachment file physically using FileStorageManager
  - Duplicates preview if exists
  - Resets `fileStatus = 'new'`
  - Blake3hash recalculation happens automatically via CDokumente Hook

**CVmhRumungsklage Service**
- Updated `createFromCollectedEntities()` method
- Changed from `relate()` to `duplicateDocument()` for:
  - Documents from Mietverhältnisse
  - Documents from Kündigungen
  - Documents from Mietobjekte
  - Documents from Beteiligte
- Added error handling with logging

**CVmhMietverhltnis Service**
- Updated `initiateRentCollection()` method
- Changed from `relate()` to `duplicateDocument()` for:
  - Documents from Mietverhältnis
  - Documents from Mietobjekt
  - Documents from Beteiligte
- Added error handling with logging

### Phase 5: Dokumenten-Sharing & Auto-Linking ✅

**CVmhRumungsklage PropagateDocuments Hook**
- Refactored `AfterRelate` on `dokumentesvmhraumungsklage`
- Auto-links to AdvowareAkte using direct foreign key (`cAdvowareAktenId`)
- Sets `syncStatus = 'new'` for Advoware sync
- Auto-links to AICollection (if exists)
- Loop protection with static $processing array

**CMietinkasso PropagateDocuments Hook**
- Same logic as CVmhRumungsklage
- Auto-links to AdvowareAkte using direct foreign key
- Auto-links to AICollection
- Loop protection

**CAdvowareAkten PropagateDocumentsUp Hook**
- Enhanced to propagate upward to Räumungsklage/Mietinkasso
- Also propagates to AICollection
- Works with new direct foreign key structure
- Loop protection

**CAIKnowledge PropagateDocumentsUp Hook**
- Enhanced `AfterRelate` on `dokumentes`
- Auto-links to parent (Räumungsklage/Mietinkasso)
- Auto-links to AdvowareAkte using direct foreign key
- Loop protection

**i18n Translations** (German & English)
- Added translations for new CDokumente fields:
  - cAdvowareAkten, cAdvowareAktenId, cAdvowareAktenName
  - hnr, syncStatus, syncedHash
- Added tooltips explaining each field
- Added options for syncStatus enum values

---

## Architecture Changes

### Before (Junction Table):
```
CAdvowareAkten (1) ←→ CAdvowareAktenDokumente (n) ←→ CDokumente (1)
                        ├─ hnr
                        ├─ syncStatus
                        └─ lastSync
```

### After (Direct n:1):
```
CAdvowareAkten (1) ←─── CDokumente (n)
                        ├─ cAdvowareAktenId (FK)
                        ├─ hnr
                        ├─ syncStatus
                        └─ syncedHash
```

---

## Key Benefits

1. **Simplified Data Model**: Direct foreign key relationship is cleaner and more maintainable
2. **Better Performance**: No junction table queries needed
3. **Document Isolation**: Duplication ensures Räumungsklage/Mietinkasso/AdvowareAkte documents are isolated from Mietverhältnis source
4. **Auto-Linking**: Documents automatically propagate to all relevant entities
5. **Sync Status Tracking**: Direct fields on CDokumente for better tracking
6. **Frontend Visibility**: belongsTo relationship is visible in UI (linkParent)

---

## Document Flow After Refactoring

```
Mietverhältnis Dokument (Source)
    ↓ (duplicate on Räumungsklage/Mietinkasso creation)
Räumungsklage/Mietinkasso Dokument (New Copy)
    ↓ (auto-link via PropagateDocuments hook)
    ├─ Set cAdvowareAktenId (if Akte linked)
    └─ Link to AICollection (if exists)
        ↓ (auto-propagate via AIKnowledge hook)
        └─ Also ensure linked to parent & Akte
```

---

## Files Modified

**Entity Definitions (2):**
- `custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json`
- `custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAkten.json`

**Services (3):**
- `custom/Espo/Custom/Services/CDokumente.php` (NEW)
- `custom/Espo/Custom/Services/CVmhRumungsklage.php`
- `custom/Espo/Custom/Services/CVmhMietverhltnis.php`

**Hooks (5):**
- `custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php`
- `custom/Espo/Custom/Hooks/CAdvowareAkten/PropagateDocumentsUp.php`
- `custom/Espo/Custom/Hooks/CVmhRumungsklage/PropagateDocuments.php`
- `custom/Espo/Custom/Hooks/CMietinkasso/PropagateDocuments.php`
- `custom/Espo/Custom/Hooks/CAIKnowledge/PropagateDocumentsUp.php`

**i18n (2):**
- `custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json`
- `custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json`

**Routes (1):**
- `custom/Espo/Custom/Resources/routes.json`

**Files Deleted (5):**
- CAdvowareAktenCDokumente.json (entity def)
- CAdvowareAktenCDokumente.php (service)
- DokumenteSyncStatus.php (hook)
- GetAktenDokumentes.php (API)
- LinkAktenDokument.php (API)
- UpdateAktenJunction.php (API)

---

## Validation Results

```
✓ JSON Syntax: All 763 files valid
✓ Relationship Consistency: 50 relationships checked
✓ Required Files: All present
✓ File Permissions: Fixed
✓ PHP Syntax: All 362 files valid
✓ EspoCRM Rebuild: Successful
```

**Note:** CRUD test failures are expected since database tables haven't been migrated yet (user confirmed no data migration needed - test data only).

---

## Next Steps (User Action Required)

### Database Migration:
Since this is test data only, the old junction table can be dropped:

```sql
-- Optional: Backup old junction table
CREATE TABLE c_advoware_akten_dokumente_backup AS 
SELECT * FROM c_advoware_akten_dokumente;

-- Drop old junction table
DROP TABLE IF EXISTS c_advoware_akten_dokumente;

-- The new fields (cAdvowareAktenId, hnr, syncStatus, syncedHash) 
-- will be created automatically by EspoCRM on next access
```

### Testing:
1. Create a new Mietverhältnis with documents
2. Create Räumungsklage from it → Documents should be duplicated
3. Link Räumungsklage to AdvowareAkte → Documents should auto-link
4. Link Räumungsklage to AICollection → Documents should auto-propagate
5. Verify in UI that CDokumente shows AdvowareAkte in detail view

### Advoware Sync:
- Sync scripts may need updates to use new direct fields instead of junction queries
- New fields: `cAdvowareAktenId`, `hnr`, `syncStatus`, `syncedHash`

---

## Constraints Verified

✅ No data migration needed (only test data)  
✅ lastSync NOT migrated to CDokumente (stays in AdvowareAkte)  
✅ AICollection junction (CAIKnowledgeDokumente) unchanged  
✅ Document isolation maintained (duplicate on create)  
✅ belongsTo relationship visible in frontend  

---

## Implementation Complete ✅

All 5 phases successfully implemented and validated.
