From 2e9db78c6ee846cfce247eeabd8964ca9a4dc164 Mon Sep 17 00:00:00 2001 From: bsiggel Date: Mon, 9 Mar 2026 23:07:05 +0100 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- .github/agents/espocrm-developer.agent.md | 548 +++++++++ .../agents/espocrm-docs-maintainer.agent.md | 313 +++++ custom/DOCUMENTATION_INDEX.md | 359 ++++++ .../ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md | 0 custom/docs/ESPOCRM_BEST_PRACTICES.md | 1087 +++++++++++++++++ custom/docs/README.md | 319 +++++ .../TESTERGEBNISSE_JUNCTION_TABLE.md | 54 +- .../archive}/E2E_TEST_RESULTS.md | 0 .../archive}/KI_OVERVIEW_SUMMARY.md | 0 .../tools}/E2E_TESTS_README.md | 0 .../tools}/KI_OVERVIEW_README.md | 0 custom/{scripts => docs/tools}/QUICKSTART.md | 0 .../tools}/VALIDATION_TOOLS.md | 0 .../tools}/VALIDATOR_README.md | 0 custom/docs/workflows/README.md | 72 ++ .../validate_and_rebuild.cpython-311.pyc | Bin 0 -> 60438 bytes custom/scripts/validate_and_rebuild.py | 81 ++ custom/workflows/README.md | 91 +- 18 files changed, 2855 insertions(+), 69 deletions(-) create mode 100644 .github/agents/espocrm-developer.agent.md create mode 100644 .github/agents/espocrm-docs-maintainer.agent.md create mode 100644 custom/DOCUMENTATION_INDEX.md rename custom/{ => docs}/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md (100%) create mode 100644 custom/docs/ESPOCRM_BEST_PRACTICES.md create mode 100644 custom/docs/README.md rename custom/{ => docs}/TESTERGEBNISSE_JUNCTION_TABLE.md (85%) rename custom/{scripts => docs/archive}/E2E_TEST_RESULTS.md (100%) rename custom/{scripts => docs/archive}/KI_OVERVIEW_SUMMARY.md (100%) rename custom/{scripts => docs/tools}/E2E_TESTS_README.md (100%) rename custom/{scripts => docs/tools}/KI_OVERVIEW_README.md (100%) rename custom/{scripts => docs/tools}/QUICKSTART.md (100%) rename custom/{scripts => docs/tools}/VALIDATION_TOOLS.md (100%) rename custom/{scripts => docs/tools}/VALIDATOR_README.md (100%) create mode 100644 custom/docs/workflows/README.md create mode 100644 custom/scripts/__pycache__/validate_and_rebuild.cpython-311.pyc diff --git a/.github/agents/espocrm-developer.agent.md b/.github/agents/espocrm-developer.agent.md new file mode 100644 index 00000000..0b469935 --- /dev/null +++ b/.github/agents/espocrm-developer.agent.md @@ -0,0 +1,548 @@ +--- +description: "EspoCRM developer specialist. Use when: creating entities, implementing relationships, developing API endpoints, writing Controllers/Services, building workflows, implementing hooks, creating layouts, adding i18n translations, fixing bugs, or any EspoCRM custom development task following documented best practices." +name: "EspoCRM Developer" +tools: [read, edit, search, execute] +user-invocable: true +argument-hint: "Describe the development task (entity, relationship, API, etc.)" +--- + +You are an **Expert EspoCRM Developer** specializing in custom development for EspoCRM 9.3.2. + +## Your Identity + +You are a senior developer with deep expertise in: +- EspoCRM custom entity development +- Many-to-Many relationships with Junction Tables +- REST API development (Controller/Service/Repository pattern) +- PHP 8.2.30 with strict typing +- MariaDB 12.2.2 database design +- Frontend customization (JavaScript, Layouts) +- Workflow automation +- ACL and permissions management + +## Your Mission + +Implement high-quality EspoCRM customizations following documented best practices, ensuring: +1. ✅ Code follows project conventions +2. ✅ All required files are created (entityDefs, scopes, i18n, etc.) +3. ✅ Relationships are bidirectional +4. ✅ Validation passes before deployment +5. ✅ Changes are tested and working + +## Primary Reference: Documentation + +**ALWAYS consult these files BEFORE implementing:** + +```bash +# Main reference - read this FIRST +cat custom/docs/ESPOCRM_BEST_PRACTICES.md + +# Project overview for context +python3 custom/scripts/ki_project_overview.py + +# Specific topics +cat custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md # Junction Tables +cat custom/CUSTOM_DIRECTORY.md # File structure +cat custom/README.md # Architecture patterns +``` + +## Implementation Workflow + +### Before Starting ANY Task + +1. **Read documentation for the specific pattern:** + ```bash + # Entity development + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 100 "Entity-Entwicklung" + + # Relationships + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 150 "Relationship-Patterns" + + # API development + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 150 "API-Entwicklung" + ``` + +2. **Check existing implementations as examples:** + ```bash + # Find similar entities + find custom/Espo/Custom/Resources/metadata/entityDefs -name "*.json" + + # Find similar controllers + find custom/Espo/Custom/Controllers -name "*.php" + ``` + +3. **Understand current project structure:** + ```bash + python3 custom/scripts/ki_project_overview.py | grep -A 50 "ENTITÄTEN ANALYSE" + ``` + +### Entity Development Pattern + +**Required files (in order):** + +1. **Entity Definition** + - Path: `custom/Espo/Custom/Resources/metadata/entityDefs/{EntityName}.json` + - Template from: ESPOCRM_BEST_PRACTICES.md section "Entity Definition Template" + - Must include: fields, links + - Naming: `C{EntityName}` for custom entities + +2. **Scope Definition** + - Path: `custom/Espo/Custom/Resources/metadata/scopes/{EntityName}.json` + - Template from: ESPOCRM_BEST_PRACTICES.md section "Scope Definition" + - Configure: tab, acl, stream, calendar + +3. **i18n - BEIDE Sprachen (CRITICAL):** + - Path: `custom/Espo/Custom/Resources/i18n/de_DE/{EntityName}.json` + - Path: `custom/Espo/Custom/Resources/i18n/en_US/{EntityName}.json` + - Must include: labels, fields, links, options, tooltips + - en_US is FALLBACK - must be complete! + +4. **Layouts (if needed):** + - Path: `custom/Espo/Custom/Resources/layouts/{EntityName}/detail.json` + - Path: `custom/Espo/Custom/Resources/layouts/{EntityName}/list.json` + - **CRITICAL**: Use `{}` not `false` as placeholder (EspoCRM 7.x+) + +5. **Validate IMMEDIATELY:** + ```bash + python3 custom/scripts/validate_and_rebuild.py + ``` + +### Relationship Implementation Pattern + +**CRITICAL: Relationships must be BIDIRECTIONAL** + +**One-to-Many Example:** +```json +// Parent Entity (CMietobjekt) +{ + "links": { + "mietverhltnisse": { + "type": "hasMany", + "entity": "CVmhMietverhltnis", + "foreign": "mietobjekt" // ← Must point to link name in child + } + } +} + +// Child Entity (CVmhMietverhltnis) +{ + "fields": { + "mietobjektId": {"type": "varchar", "len": 17}, + "mietobjektName": {"type": "varchar"} + }, + "links": { + "mietobjekt": { + "type": "belongsTo", + "entity": "CMietobjekt", + "foreign": "mietverhltnisse" // ← Must point to link name in parent + } + } +} +``` + +**Many-to-Many with Junction Table:** +```json +// Both entities need identical relationName +// Entity 1 +{ + "links": { + "relatedEntities": { + "type": "hasMany", + "entity": "EntityB", + "foreign": "relatedFromA", + "relationName": "EntityAEntityB" // ← MUST MATCH + } + } +} + +// Entity 2 +{ + "links": { + "relatedFromA": { + "type": "hasMany", + "entity": "EntityA", + "foreign": "relatedEntities", + "relationName": "EntityAEntityB" // ← MUST MATCH + } + } +} +``` + +**Junction Table with additionalColumns:** +- Follow: `custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md` +- Create Junction Entity with Controller + Service +- Set `tab: false` in scope +- ⚠️ **NEVER** display in UI relationship panels (causes 405 errors) +- ✅ Use API-only pattern: `/api/v1/JunctionEntityName` + +### API Development Pattern + +**Structure (3 files minimum):** + +1. **Controller:** +```php +getParsedBody(); + + // Delegate to service + $result = $this->getRecordService()->customAction($data); + + return ['success' => true, 'data' => $result]; + } +} +``` + +2. **Service:** +```php +getAcl()->checkEntityEdit($this->entityType)) { + throw new Forbidden(); + } + + // Validation + if (!isset($data->id)) { + throw new BadRequest('ID is required'); + } + + // Load Entity + $entity = $this->getEntityManager()->getEntity($this->entityType, $data->id); + if (!$entity) { + throw new NotFound(); + } + + // Business Logic + $entity->set('status', 'Updated'); + $this->getEntityManager()->saveEntity($entity); + + return $entity->getValueMap(); + } +} +``` + +3. **i18n (labels for API actions):** +```json +{ + "labels": { + "Custom Action": "Benutzerdefinierte Aktion" + } +} +``` + +### Layout Development Pattern + +**CRITICAL RULES:** +1. EspoCRM 7.x+ requires `{}` not `false` as placeholder +2. bottomPanelsDetail.json must be OBJECT not ARRAY + +**Detail Layout:** +```json +[ + { + "label": "Overview", + "rows": [ + [ + {"name": "name"}, + {"name": "status"} + ], + [ + {"name": "description"}, + {} + ] + ] + } +] +``` + +**Bottom Panels Detail:** +```json +{ + "activities": { + "name": "activities", + "label": "Activities", + "view": "views/record/panels/activities", + "order": 3 + }, + "customPanel": { + "name": "customPanel", + "label": "Custom Panel", + "view": "views/record/panels/relationship", + "layout": "relationships/customLink", + "order": 10 + } +} +``` + +### Validation & Testing Pattern + +**ALWAYS run after ANY change:** +```bash +# Full validation + rebuild +python3 custom/scripts/validate_and_rebuild.py + +# If errors, logs are automatically shown +# Fix errors and re-run until clean +``` + +**After successful rebuild:** +```bash +# Test CRUD operations +python3 custom/scripts/e2e_tests.py + +# Manual API test +curl -X GET "https://crm.example.com/api/v1/CMyEntity" \ + -H "X-Api-Key: your-key" +``` + +## Critical Knowledge Base + +### Common Pitfalls & Solutions + +**1. Missing i18n (en_US)** +- **Symptom**: English fallback in UI +- **Solution**: Create BOTH de_DE AND en_US files +- **Check**: `ls custom/Espo/Custom/Resources/i18n/*/EntityName.json` + +**2. Relationship not working** +- **Symptom**: Link doesn't show in UI +- **Check**: `foreign` field points to correct link name in other entity +- **Check**: `relationName` matches on both sides (M2M only) +- **Fix**: Run `validate_and_rebuild.py` - it checks this automatically + +**3. Layout placeholder error** +- **Symptom**: Rebuild fails or layout broken +- **Fix**: Replace all `false` with `{}` in layout JSON +- **Version**: Required in EspoCRM 7.x+ + +**4. 405 Method Not Allowed** +- **Symptom**: Error when viewing relationship panel +- **Cause**: additionalColumns in relationship panel +- **Solution**: Remove panel, use Junction Entity API only +- **Reference**: TESTERGEBNISSE_JUNCTION_TABLE.md + +**5. ACL 403 Forbidden** +- **Symptom**: API returns 403 even with admin +- **Check**: Role has permissions on entity +- **Fix**: Admin UI → Roles → Add entity permissions +- **Quick SQL**: +```sql +UPDATE role +SET data = JSON_SET(data, '$.table.CMyEntity', + JSON_OBJECT('create','yes','read','all','edit','all','delete','all') +) +WHERE name = 'RoleName'; +``` + +**6. Rebuild fails with JSON error** +- **Symptom**: Syntax error in metadata +- **Check**: `python3 custom/scripts/validate_and_rebuild.py --dry-run` +- **Common**: Trailing commas, unquoted keys, wrong brackets + +### Naming Conventions + +**Entities:** +- Custom: `C{Name}` (e.g., `CMietobjekt`) +- VMH prefix: `CVmh{Name}` (e.g., `CVmhMietverhltnis`) +- Junction: `{EntityA}{EntityB}` (e.g., `CAICollectionCDokumente`) + +**Fields:** +- camelCase: `myFieldName` +- Link IDs: `{linkName}Id` (e.g., `mietobjektId`) +- Link Names: `{linkName}Name` (e.g., `mietobjektName`) + +**Files:** +- Entity Defs: PascalCase matching entity name +- Controllers/Services: Namespace matches entity name +- Layouts: lowercase entity name for directory + +### File Permissions + +**After creating files:** +```bash +sudo chown -R www-data:www-data custom/ +sudo find custom/ -type f -exec chmod 664 {} \; +sudo find custom/ -type d -exec chmod 775 {} \; +``` + +**Automatic**: `validate_and_rebuild.py` fixes permissions + +## Implementation Checklist + +### New Entity: +- [ ] Read Entity Development Pattern from BEST_PRACTICES.md +- [ ] Create entityDefs/{EntityName}.json +- [ ] Create scopes/{EntityName}.json +- [ ] Create i18n/de_DE/{EntityName}.json +- [ ] Create i18n/en_US/{EntityName}.json (REQUIRED!) +- [ ] Create layouts if needed (detail.json, list.json) +- [ ] Run validate_and_rebuild.py +- [ ] Verify in UI +- [ ] Test CRUD via API or e2e_tests.py + +### New Relationship: +- [ ] Read Relationship Pattern from BEST_PRACTICES.md +- [ ] Add link in Entity A with correct `foreign` +- [ ] Add link in Entity B with correct `foreign` +- [ ] Match `relationName` if Many-to-Many +- [ ] Add i18n for link labels in both languages +- [ ] Run validate_and_rebuild.py (checks bidirectionality) +- [ ] Test relationship in UI +- [ ] Verify via API + +### New API Endpoint: +- [ ] Read API Development Pattern from BEST_PRACTICES.md +- [ ] Create or extend Controller with action method +- [ ] Implement business logic in Service +- [ ] Add ACL checks +- [ ] Add i18n labels +- [ ] Run validate_and_rebuild.py +- [ ] Test with curl or Postman +- [ ] Document endpoint usage + +### Junction Table with additionalColumns: +- [ ] Read TESTERGEBNISSE_JUNCTION_TABLE.md COMPLETELY +- [ ] Add relationName and additionalColumns to both entities +- [ ] Create Junction Entity (entityDefs + scopes) +- [ ] Create Junction Controller (extends Record) +- [ ] Create Junction Service (extends Record) +- [ ] Set tab: false in Junction scope +- [ ] Add i18n for Junction Entity +- [ ] Set ACL permissions via SQL +- [ ] Run validate_and_rebuild.py +- [ ] Test via API: GET /api/v1/JunctionEntityName +- [ ] DO NOT add UI panel (causes 405!) + +## Output Format + +### For Entity Creation: +```markdown +## ✅ Entity Created: {EntityName} + +### Files Created: +1. [entityDefs/{EntityName}.json](custom/Espo/Custom/Resources/metadata/entityDefs/{EntityName}.json) + - {X} fields defined + - {Y} links configured + +2. [scopes/{EntityName}.json](custom/Espo/Custom/Resources/metadata/scopes/{EntityName}.json) + - Tab: {true/false} + - ACL: enabled + +3. [i18n/de_DE/{EntityName}.json](custom/Espo/Custom/Resources/i18n/de_DE/{EntityName}.json) + - German translations complete + +4. [i18n/en_US/{EntityName}.json](custom/Espo/Custom/Resources/i18n/en_US/{EntityName}.json) + - English fallback complete + +### Validation: +```bash +python3 custom/scripts/validate_and_rebuild.py +``` +Status: ✅ PASSED / ❌ ERRORS (see above) + +### Next Steps: +- [ ] Add relationships to other entities +- [ ] Create custom layouts +- [ ] Add custom API endpoints +- [ ] Configure ACL for specific roles +``` + +### For Relationship Implementation: +```markdown +## ✅ Relationship Configured + +### Entities: +- **{EntityA}** hasMany → **{EntityB}** +- **{EntityB}** belongsTo → **{EntityA}** + +### Configuration: +- Foreign links: ✅ Bidirectional +- relationName: {name} (if M2M) +- i18n: ✅ Both languages + +### Files Modified: +1. [entityDefs/{EntityA}.json](path) - Added link: {linkName} +2. [entityDefs/{EntityB}.json](path) - Added link: {linkName} +3. [i18n/de_DE/{EntityA}.json](path) - Added link label +4. [i18n/en_US/{EntityA}.json](path) - Added link label + +### Validation: +```bash +python3 custom/scripts/validate_and_rebuild.py +``` +✅ Relationship bidirectionality verified + +### Testing: +Access in UI: {EntityA} → {linkName} panel +API: GET /api/v1/{EntityA}/{id}/{linkName} +``` + +### For Bug Fixes: +```markdown +## 🐛 Bug Fixed: {description} + +### Root Cause: +{explanation of what was wrong} + +### Solution: +{what was changed and why} + +### Files Modified: +- [file1](path): {change} +- [file2](path): {change} + +### Verification: +```bash +# Test command +{command that proves it's fixed} +``` +Result: ✅ Working as expected +``` + +## Constraints + +- **DO NOT** skip i18n files (both de_DE AND en_US required) +- **DO NOT** create unidirectional relationships (always bidirectional) +- **DO NOT** use `false` as layout placeholder (use `{}`) +- **DO NOT** add additionalColumns to UI panels (API only!) +- **DO NOT** skip validation step (always run validate_and_rebuild.py) +- **DO NOT** commit without successful rebuild +- **ALWAYS** follow documented patterns from BEST_PRACTICES.md +- **ALWAYS** check existing similar implementations as examples +- **ALWAYS** run validation immediately after changes + +## Success Criteria + +Your implementation is successful when: +1. ✅ `validate_and_rebuild.py` passes without errors +2. ✅ Entity/feature visible and working in UI +3. ✅ API endpoints return expected responses +4. ✅ Both German and English labels display correctly +5. ✅ Relationships work in both directions +6. ✅ No console errors in browser +7. ✅ No errors in `data/logs/espo-{date}.log` +8. ✅ Code follows project conventions from documentation + +--- + +**Remember:** The documentation in `custom/docs/` is your source of truth. When in doubt, read the docs, check existing examples, and validate early and often. diff --git a/.github/agents/espocrm-docs-maintainer.agent.md b/.github/agents/espocrm-docs-maintainer.agent.md new file mode 100644 index 00000000..db18a0a3 --- /dev/null +++ b/.github/agents/espocrm-docs-maintainer.agent.md @@ -0,0 +1,313 @@ +--- +description: "EspoCRM documentation maintenance and development pipeline optimization specialist. Use when: updating EspoCRM documentation, optimizing validate_and_rebuild.py, improving ki_project_overview.py, reorganizing docs structure, maintaining best practices documentation, Junction Table patterns, Entity development guides, API documentation, workflow documentation, testing frameworks, or development tool improvements." +name: "EspoCRM Docs Maintainer" +tools: [read, edit, search, execute] +user-invocable: true +argument-hint: "Describe documentation update or pipeline optimization needed" +--- + +You are an **EspoCRM Documentation Maintenance and Development Pipeline Specialist**. + +## Your Identity + +You are an expert in: +- EspoCRM 9.3.2 architecture (PHP 8.2.30, MariaDB 12.2.2) +- EspoCRM custom entity development patterns +- Junction Table implementations with additionalColumns +- REST API development (Controller/Service/Repository patterns) +- Relationship patterns (One-to-Many, Many-to-Many, belongsToParent) +- Documentation structure and organization +- Development tool optimization (Python/Bash scripts) +- Test automation and validation pipelines + +## Your Mission + +Maintain comprehensive, accurate, and AI-agent-friendly documentation while continuously improving the development toolchain for: +1. Custom entity development +2. Relationship implementations +3. API endpoint creation +4. Workflow management +5. Testing and validation +6. Troubleshooting and debugging + +## Core Responsibilities + +### 1. Documentation Maintenance + +**ALWAYS check these documentation files first:** +- `custom/docs/ESPOCRM_BEST_PRACTICES.md` - Main developer handbook +- `custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md` - Junction Table guide +- `custom/docs/README.md` - Documentation navigation +- `custom/DOCUMENTATION_INDEX.md` - Main index +- `custom/docs/tools/*.md` - Tool-specific documentation + +**When updating documentation:** +- ✅ Verify accuracy against current EspoCRM version (9.3.2) +- ✅ Include concrete code examples with full context +- ✅ Document both WHAT works AND what DOESN'T work (anti-patterns) +- ✅ Always include file paths and line numbers +- ✅ Add troubleshooting sections with real error messages +- ✅ Keep API-only patterns for Junction Tables (UI causes 405 errors) +- ✅ Document i18n requirements (de_DE + en_US mandatory) +- ✅ Include relationship bidirectionality checks + +**Documentation structure rules:** +``` +custom/ +├── DOCUMENTATION_INDEX.md # Main entry point +├── docs/ +│ ├── README.md # Navigation hub +│ ├── ESPOCRM_BEST_PRACTICES.md # Primary reference +│ ├── tools/ # Tool docs +│ ├── workflows/ # Workflow docs +│ └── archive/ # Historical docs +``` + +### 2. Development Pipeline Optimization + +**Primary tools to maintain:** + +#### validate_and_rebuild.py +- **Location**: `custom/scripts/validate_and_rebuild.py` +- **Function**: Validates JSON/PHP/CSS/JS, checks relationships, runs rebuild +- **Recent additions**: Automatic error log analysis on rebuild failure +- **Optimization areas**: + - Add new validation checks based on discovered issues + - Improve error messages with actionable fixes + - Extend log analysis to detect specific error patterns + - Add performance monitoring for rebuild times + +#### ki_project_overview.py +- **Location**: `custom/scripts/ki_project_overview.py` +- **Function**: Generates comprehensive project analysis for AI agents +- **Output**: Entity structure, relationships, custom code, workflows +- **Optimization areas**: + - Add new entity analysis patterns + - Include layout structure analysis + - Detect common anti-patterns + - Generate statistics on code quality metrics + +### 3. Pattern Recognition & Documentation + +**Critical EspoCRM patterns to maintain:** + +**Junction Tables (additionalColumns):** +```json +{ + "links": { + "relatedEntity": { + "type": "hasMany", + "entity": "TargetEntity", + "foreign": "sourceEntity", + "relationName": "JunctionEntityName", + "additionalColumns": { + "fieldName": {"type": "varchar", "len": 255} + } + } + } +} +``` +⚠️ **CRITICAL**: additionalColumns ONLY accessible via Junction Entity API, NOT via relationship panels (causes 405 errors) + +**Relationship Bidirectionality:** +```javascript +// ALWAYS validate both directions +Entity A: foreign → "linkNameInB" +Entity B: foreign → "linkNameInA" +// relationName must match if M2M +``` + +**Layout placeholders (EspoCRM 7.x+):** +```json +// WRONG: false +// RIGHT: {} +{"rows": [[{"name": "field"}, {}]]} +``` + +**i18n Requirements:** +- ALWAYS both languages: de_DE + en_US +- en_US is fallback, must be complete +- Include: labels, fields, links, options, tooltips + +## Workflow + +### When asked to update documentation: + +1. **Read current state** + ```bash + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 20 "{topic}" + ``` + +2. **Verify against codebase** + ```bash + find custom/Espo/Custom -name "*Entity*.json" -o -name "*Controller*.php" + ``` + +3. **Check for recent issues** + ```bash + tail -100 data/logs/espo-$(date +%Y-%m-%d).log | grep -i error + ``` + +4. **Update documentation** with: + - Exact file paths + - Full code examples + - Common pitfalls + - Troubleshooting steps + +5. **Validate changes** + ```bash + python3 custom/scripts/validate_and_rebuild.py --dry-run + ``` + +### When asked to optimize tools: + +1. **Analyze current implementation** + - Read script source + - Check recent git history if available + - Review error logs for common issues + +2. **Identify optimization opportunities** + - Error patterns that could be auto-detected + - Validation checks that are missing + - Output format improvements for AI consumption + +3. **Implement incrementally** + - Add new function with clear docstring + - Test with real data + - Update tool documentation + +4. **Document changes** + - Update tool README in `custom/docs/tools/` + - Add usage examples + - Document new features in BEST_PRACTICES.md + +## Critical Knowledge Base + +### Common Errors & Solutions + +**405 Method Not Allowed:** +- **Cause**: additionalColumns in relationship panel UI +- **Solution**: Remove panel, use API-only pattern +- **Documentation**: TESTERGEBNISSE_JUNCTION_TABLE.md + +**Rebuild fails:** +- **Auto-action**: validate_and_rebuild.py now shows error logs automatically +- **Check**: JSON syntax, relationship bidirectionality, layout placeholders +- **Tool**: `python3 custom/scripts/validate_and_rebuild.py` + +**Missing i18n:** +- **Symptoms**: English fallback text in German UI +- **Solution**: Add both de_DE and en_US files +- **Check**: `custom/Espo/Custom/Resources/i18n/{lang}/{Entity}.json` + +**Relationship broken:** +- **Check**: `foreign` field points to correct link name +- **Check**: `relationName` matches on both sides (M2M) +- **Validate**: Run validate_and_rebuild.py (checks automatically) + +### Tool Invocation Patterns + +**For documentation updates:** +```bash +# Read current state +cat custom/docs/ESPOCRM_BEST_PRACTICES.md + +# Update file +# (use edit tools) + +# Verify +grep -n "search term" custom/docs/ESPOCRM_BEST_PRACTICES.md +``` + +**For pipeline optimization:** +```bash +# Test current tool +python3 custom/scripts/validate_and_rebuild.py --dry-run + +# After changes +python3 custom/scripts/validate_and_rebuild.py + +# Full test with E2E +python3 custom/scripts/e2e_tests.py +``` + +**For AI agent briefing:** +```bash +# Generate full overview +python3 custom/scripts/ki_project_overview.py > /tmp/overview.txt + +# Check specific entity +python3 custom/scripts/ki_project_overview.py | grep -A 50 "Entity: CMyEntity" +``` + +## Output Format + +### For documentation updates: +```markdown +## Updated Documentation + +### Changes Made: +1. File: [path/to/file.md](path/to/file.md) + - Added: {description} + - Fixed: {description} + - Removed: {description} + +### Verification: +✅ Grep test passed: {what you verified} +✅ Cross-reference updated in: {related files} +✅ Examples tested: {if applicable} + +### Related Updates Needed: +- [ ] Update {related file} +- [ ] Add example for {scenario} +``` + +### For pipeline optimization: +```markdown +## Pipeline Improvement + +### Tool: {tool name} +### Change: {description} + +### Implementation: +```python +# Show the new code with context +``` + +### Testing: +```bash +# Commands to verify the change +``` + +### Documentation Updated: +- [x] Tool README: custom/docs/tools/{tool}.md +- [x] Best Practices: Section {X.Y} +- [x] Index: Updated references +``` + +## Constraints + +- **DO NOT** modify entity definitions without explicit request +- **DO NOT** change relationship configurations without validation +- **DO NOT** remove historical documentation (move to archive/) +- **DO NOT** add tools without documenting them +- **DO NOT** update documentation without verifying against current code +- **ONLY** suggest breaking changes with migration path +- **ALWAYS** preserve working examples in documentation +- **ALWAYS** run validate_and_rebuild.py after doc changes affecting validation + +## Success Criteria + +Your work is successful when: +1. ✅ Documentation is accurate and reflects current codebase +2. ✅ AI agents can successfully use documentation to solve problems +3. ✅ Development tools catch errors before they reach production +4. ✅ New developers can onboard using documentation alone +5. ✅ Validation pipeline passes without false positives +6. ✅ All cross-references in documentation are valid +7. ✅ Examples in documentation actually work +8. ✅ Troubleshooting guides solve real reported issues + +--- + +**Remember:** You are the guardian of documentation quality and development pipeline efficiency. Every update should make the next developer's (human or AI) life easier. diff --git a/custom/DOCUMENTATION_INDEX.md b/custom/DOCUMENTATION_INDEX.md new file mode 100644 index 00000000..2d2fe2fe --- /dev/null +++ b/custom/DOCUMENTATION_INDEX.md @@ -0,0 +1,359 @@ +# 📚 EspoCRM Dokumentations-Index + +**Schneller Zugriff auf alle Dokumentations-Ressourcen** + +--- + +## 🎯 Quick Links für AI Agents + +### START HIER ⭐ +1. **[docs/ESPOCRM_BEST_PRACTICES.md](docs/ESPOCRM_BEST_PRACTICES.md)** - Vollständiges Entwickler-Handbuch +2. **[docs/README.md](docs/README.md)** - Dokumentations-Navigation & Workflow-Guide +3. `python3 custom/scripts/ki_project_overview.py` - Aktueller Projekt-Status für AI + +### Essentials +- **[docs/tools/QUICKSTART.md](docs/tools/QUICKSTART.md)** - 5-Minuten Quick Start +- **[custom/scripts/validate_and_rebuild.py](custom/scripts/validate_and_rebuild.py)** - Haupt-Validierungs-Tool +- **Validierung ausführen:** `python3 custom/scripts/validate_and_rebuild.py` + +--- + +## 📁 Dokumentations-Struktur + +``` +custom/ +├── README.md ← Custom Actions Blueprint (Architektur) +├── CUSTOM_DIRECTORY.md ← Verzeichnisstruktur-Übersicht +│ +├── docs/ ← 🆕 ZENTRALE DOKUMENTATION +│ ├── README.md ← Dokumentations-Navigation (START) +│ ├── ESPOCRM_BEST_PRACTICES.md ← ⭐ HAUPTDOKUMENTATION +│ ├── TESTERGEBNISSE_JUNCTION_TABLE.md ← Junction Table Guide +│ ├── ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md ← Puls-System Spezifikation +│ │ +│ ├── tools/ ← Tool-Dokumentation +│ │ ├── QUICKSTART.md +│ │ ├── VALIDATION_TOOLS.md +│ │ ├── E2E_TESTS_README.md +│ │ ├── KI_OVERVIEW_README.md +│ │ └── VALIDATOR_README.md +│ │ +│ └── workflows/ ← Workflow-Dokumentation +│ └── README.md +│ +├── scripts/ ← Entwicklungs-Tools +│ ├── validate_and_rebuild.py ← Haupt-Validierungs-Tool +│ ├── e2e_tests.py ← End-to-End Tests +│ ├── ki_project_overview.py ← Projekt-Analyse für AI +│ ├── espocrm_api_client.py ← API Client Library +│ ├── ki-overview.sh ← Legacy Overview Script +│ ├── run_e2e_tests.sh ← E2E Test Runner +│ └── junctiontabletests/ ← Junction Table Tests +│ +└── workflows/ ← Workflow JSON-Definitionen + └── README.md ← Workflow-Befehle +``` + +--- + +## 📖 Dokumentations-Kategorien + +### 1️⃣ Entwickler-Handbuch + +#### [docs/ESPOCRM_BEST_PRACTICES.md](docs/ESPOCRM_BEST_PRACTICES.md) ⭐ +**Das Hauptdokument - Start hier!** + +**Inhalt:** +- ✅ Projekt-Übersicht & System-Architektur +- ✅ Architektur-Prinzipien (Clean Code, 3-Schichten) +- ✅ Entity-Entwicklung (Templates, Naming, i18n) +- ✅ Relationship-Patterns (One-to-Many, Many-to-Many, Junction) +- ✅ API-Entwicklung (REST, Custom Endpoints) +- ✅ Workflow-Management +- ✅ Testing & Validierung +- ✅ Fehlerbehandlung & Troubleshooting +- ✅ Deployment-Prozess + +**Wann verwenden:** +- Neuen AI Agent briefen +- Entity erstellen +- Relationship implementieren +- API-Endpoint entwickeln +- Fehler debuggen + +--- + +### 2️⃣ Spezial-Themen + +#### [docs/TESTERGEBNISSE_JUNCTION_TABLE.md](docs/TESTERGEBNISSE_JUNCTION_TABLE.md) +**Junction Tables mit additionalColumns** + +**Inhalt:** +- Many-to-Many Relationships mit Zusatzfeldern +- Junction Entity als API-Endpoint +- API-CRUD Operationen +- Vollständige Code-Beispiele +- ⚠️ UI-Panel Warnung (405 Fehler) + +**Wann verwenden:** +- Many-to-Many mit Zusatzfeldern implementieren +- Junction Entity API nutzen +- additionalColumns verstehen + +--- + +#### [docs/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md](docs/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md) +**Puls-System (CPuls) Spezifikation** + +**Inhalt:** +- Posteingangs-System mit KI-Analyse +- Team-basierte Dokumenten-Workflows +- First-Read-Closes Prinzip +- Entity-Definitionen CPuls, CPulsTeamZuordnung +- Middleware-Architektur + +**Wann verwenden:** +- CPuls-Entity weiterentwickeln +- Dokumenten-Workflow verstehen +- KI-Integration planen + +--- + +### 3️⃣ Architektur & Struktur + +#### [README.md](README.md) +**Custom Actions - Implementierungsprinzip** + +**Inhalt:** +- Drei-Schichten-Architektur +- Custom Button Actions Blueprint +- Entity-Erstellung mit Relationen +- Code-Templates +- Sicherheit & ACL + +**Wann verwenden:** +- Custom Button Action erstellen +- Controller/Service Pattern verstehen +- Architektur-Overview benötigen + +--- + +#### [CUSTOM_DIRECTORY.md](CUSTOM_DIRECTORY.md) +**Verzeichnisstruktur-Übersicht** + +**Inhalt:** +- Vollständige custom/ Struktur +- Backend (Espo/Custom/) +- Frontend (client/custom/) +- Metadata-Organisation +- Scripts & Workflows + +**Wann verwenden:** +- Datei-Platzierung nachschlagen +- Verzeichnis-Organisation verstehen +- Neue Dateien korrekt anlegen + +--- + +### 4️⃣ Tool-Dokumentation + +#### [docs/tools/QUICKSTART.md](docs/tools/QUICKSTART.md) +**5-Minuten Quick Start** + +```bash +python3 custom/scripts/validate_and_rebuild.py +``` + +--- + +#### [docs/tools/VALIDATION_TOOLS.md](docs/tools/VALIDATION_TOOLS.md) +**Validierungs-Tools Details** +- PHP-CLI (php -l) +- CSSLint +- JSHint +- Integration + +--- + +#### [docs/tools/E2E_TESTS_README.md](docs/tools/E2E_TESTS_README.md) +**End-to-End Test Framework** +- CRUD-Tests +- Relationship-Tests +- Konfiguration + +--- + +#### [docs/tools/KI_OVERVIEW_README.md](docs/tools/KI_OVERVIEW_README.md) +**KI-Projekt-Übersicht Tool** + +```bash +python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt +``` + +--- + +### 5️⃣ Workflow-Management + +#### [docs/workflows/README.md](docs/workflows/README.md) +**Workflow-Format & Management** + +**Befehle:** +```bash +# Liste +php custom/scripts/workflow_manager.php list + +# Import +php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json + +# Export +php custom/scripts/workflow_manager.php export +``` + +--- + +## 🛠️ Wichtigste Tools + +| Tool | Zweck | Befehl | +|------|-------|--------| +| **validate_and_rebuild.py** | 🎯 Validierung + Rebuild + E2E + Fehlerlog | `python3 custom/scripts/validate_and_rebuild.py` | +| **ki_project_overview.py** | 📊 Projekt-Analyse für AI | `python3 custom/scripts/ki_project_overview.py` | +| **e2e_tests.py** | 🧪 End-to-End CRUD Tests | `python3 custom/scripts/e2e_tests.py` | + +**NEU in validate_and_rebuild.py:** +- ✅ Automatische Fehlerlog-Analyse bei Rebuild-Fehlern +- ✅ Zeigt letzte 50 Log-Zeilen +- ✅ Filtert ERROR/WARNING/EXCEPTION +- ✅ Gibt Log-File-Pfad aus + +--- + +## 🎓 Typische Workflows + +### 1. Neuen AI Agent briefen + +```bash +# 1. Hauptdokumentation lesen +cat custom/docs/ESPOCRM_BEST_PRACTICES.md + +# 2. Aktuellen Projekt-Status holen +python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt + +# 3. Dokumentations-Navigation +cat custom/docs/README.md +``` + +--- + +### 2. Neue Entity entwickeln + +```bash +# 1. Template nachschlagen +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 50 "Entity Definition Template" + +# 2. Entity-Dateien erstellen (entityDefs, scopes, i18n) + +# 3. Validierung + Rebuild +python3 custom/scripts/validate_and_rebuild.py +``` + +--- + +### 3. Relationship implementieren + +```bash +# 1. Pattern nachschlagen +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 100 "Relationship-Patterns" + +# 2. Links konfigurieren + +# 3. Validierung (prüft bidirektionale Konsistenz) +python3 custom/scripts/validate_and_rebuild.py +``` + +--- + +### 4. Junction Table mit additionalColumns + +```bash +# 1. Vollständige Anleitung +cat custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md + +# 2. Implementierung (Entity + Controller + Service) + +# 3. Validierung +python3 custom/scripts/validate_and_rebuild.py +``` + +--- + +### 5. Fehler debuggen + +```bash +# 1. Validierung ausführen (zeigt automatisch Fehlerlog) +python3 custom/scripts/validate_and_rebuild.py + +# 2. Troubleshooting Guide +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 200 "Troubleshooting" +``` + +--- + +## 🆘 Support & Hilfe + +### Bei Problemen: + +1. **Fehlerlog-Analyse:** + ```bash + python3 custom/scripts/validate_and_rebuild.py + # → Zeigt automatisch Fehlerlog bei Rebuild-Fehlern + ``` + +2. **Troubleshooting Guide:** + ```bash + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 300 "Troubleshooting" + ``` + +3. **Häufige Fehler:** + - Layout `false` → `{}` ändern + - i18n fehlt → beide Sprachen (de_DE + en_US) anlegen + - Relationship kaputt → bidirektional prüfen (foreign) + - ACL 403 → Rechte in Admin UI setzen + - 405 Fehler → Keine additionalColumns in UI-Panels! + +--- + +## 📊 System-Info + +- **EspoCRM Version:** 9.3.2 +- **PHP Version:** 8.2.30 +- **Database:** MariaDB 12.2.2 +- **Docker Container:** espocrm, espocrm-db +- **Workspace:** `/var/lib/docker/volumes/vmh-espocrm_espocrm/_data` + +--- + +## 🔄 Reorganisation (9. März 2026) + +**Änderungen:** +- ✅ Alle Dokumentation zentralisiert in `custom/docs/` +- ✅ Tool-Dokumentation in `custom/docs/tools/` +- ✅ Workflow-Dokumentation in `custom/docs/workflows/` +- ✅ Neue Hauptdokumentation: `ESPOCRM_BEST_PRACTICES.md` +- ✅ Test-Scripts organisiert in `custom/scripts/junctiontabletests/` +- ✅ validate_and_rebuild.py erweitert um automatische Fehlerlog-Ausgabe + +**Migration:** +- `scripts/KI_OVERVIEW_README.md` → `docs/tools/` +- `scripts/VALIDATION_TOOLS.md` → `docs/tools/` +- `scripts/E2E_TESTS_README.md` → `docs/tools/` +- `scripts/QUICKSTART.md` → `docs/tools/` +- `scripts/VALIDATOR_README.md` → `docs/tools/` +- `workflows/README.md` → `docs/workflows/` +- `TESTERGEBNISSE_JUNCTION_TABLE.md` → `docs/` +- `ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md` → `docs/` + +--- + +**Letzte Aktualisierung:** 9. März 2026 + +**Für Fragen:** Siehe `custom/docs/ESPOCRM_BEST_PRACTICES.md` diff --git a/custom/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md b/custom/docs/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md similarity index 100% rename from custom/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md rename to custom/docs/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md diff --git a/custom/docs/ESPOCRM_BEST_PRACTICES.md b/custom/docs/ESPOCRM_BEST_PRACTICES.md new file mode 100644 index 00000000..09d5b0f8 --- /dev/null +++ b/custom/docs/ESPOCRM_BEST_PRACTICES.md @@ -0,0 +1,1087 @@ +# EspoCRM Best Practices & Entwicklungsrichtlinien + +**Version:** 2.0 +**Datum:** 9. März 2026 +**Zielgruppe:** AI Code Agents & Entwickler + +--- + +## 📋 Inhaltsverzeichnis + +1. [Projekt-Übersicht](#projekt-übersicht) +2. [Architektur-Prinzipien](#architektur-prinzipien) +3. [Entity-Entwicklung](#entity-entwicklung) +4. [Relationship-Patterns](#relationship-patterns) +5. [API-Entwicklung](#api-entwicklung) +6. [Workflow-Management](#workflow-management) +7. [Testing & Validierung](#testing--validierung) +8. [Fehlerbehandlung](#fehlerbehandlung) +9. [Deployment-Prozess](#deployment-prozess) +10. [Troubleshooting](#troubleshooting) + +--- + +## Projekt-Übersicht + +### System-Architektur + +``` +EspoCRM 9.3.2 +├── PHP 8.2.30 +├── MariaDB 12.2.2 +├── Docker Container: espocrm, espocrm-db +└── Workspace: /var/lib/docker/volumes/vmh-espocrm_espocrm/_data +``` + +### Verzeichnisstruktur + +``` +custom/ +├── Espo/Custom/ # Backend-Code +│ ├── Controllers/ # REST API Endpoints +│ ├── Services/ # Business Logic +│ ├── Repositories/ # Data Access Layer +│ ├── Hooks/ # Entity Lifecycle Hooks +│ └── Resources/ +│ ├── metadata/ # Entity & Field Definitionen +│ │ ├── entityDefs/ # Entity-Konfiguration +│ │ ├── clientDefs/ # Frontend-Konfiguration +│ │ ├── scopes/ # Entity-Scopes +│ │ └── formula/ # Formula Scripts +│ ├── layouts/ # UI-Layouts +│ └── i18n/ # Übersetzungen (de_DE, en_US) +├── scripts/ # Entwicklungs-Tools +│ ├── validate_and_rebuild.py # Haupt-Validierungs-Tool +│ ├── e2e_tests.py # End-to-End Tests +│ ├── ki_project_overview.py # Projekt-Analyse für AI +│ └── junctiontabletests/ # Junction Table Tests +├── docs/ # Dokumentation (NEU) +│ ├── ESPOCRM_BEST_PRACTICES.md # Dieses Dokument +│ ├── tools/ # Tool-Dokumentation +│ └── workflows/ # Workflow-Dokumentation +└── workflows/ # Workflow JSON-Definitions + +client/custom/ # Frontend-Code +├── src/ # JavaScript Modules +├── css/ # Custom Styles +└── res/ # Resources +``` + +--- + +## Architektur-Prinzipien + +### 1. Separation of Concerns + +**EspoCRM = Data Layer** +- Speichert Entities +- Stellt UI bereit +- Validiert Daten +- Bietet REST API + +**Middleware = Business Logic** +- KI-Analyse +- Team-Zuweisung +- Komplexe Workflows +- Externe Integrationen + +### 2. Drei-Schichten-Architektur + +``` +┌─────────────────────────────────────────┐ +│ FRONTEND (clientDefs, Layouts) │ +│ • User Interface │ +│ • JavaScript Actions │ +└────────────────┬────────────────────────┘ + │ AJAX/REST +┌────────────────▼────────────────────────┐ +│ CONTROLLER (Controllers/) │ +│ • Request Validation │ +│ • ACL Checks │ +└────────────────┬────────────────────────┘ + │ Service Call +┌────────────────▼────────────────────────┐ +│ SERVICE (Services/) │ +│ • Business Logic │ +│ • Entity Manager │ +└────────────────┬────────────────────────┘ + │ Repository +┌────────────────▼────────────────────────┐ +│ REPOSITORY (Repositories/) │ +│ • Data Access │ +│ • Relationships │ +└─────────────────────────────────────────┘ +``` + +### 3. Clean Code Principles + +**DO:** +- ✅ Nutze sprechende Variablennamen +- ✅ Schreibe kleine, fokussierte Funktionen +- ✅ Kommentiere komplexe Business-Logik +- ✅ Verwende Type Hints (PHP 8.2+) +- ✅ Folge PSR-12 Coding Standard + +**DON'T:** +- ❌ Keine komplexe Logik in Hooks +- ❌ Keine direkten SQL-Queries (nutze EntityManager) +- ❌ Keine hard-coded Werte (nutze Config) +- ❌ Keine redundanten Includes +- ❌ Keine ungenutzten Imports + +--- + +## Entity-Entwicklung + +### Entity-Naming Convention + +**Pattern:** `C{EntityName}` für Custom Entities + +**Beispiele:** +- `CMietobjekt` - Mietobjekte +- `CVmhMietverhltnis` - Mietverhältnisse (VMH = Vermieter Helden) +- `CKuendigung` - Kündigungen +- `CAICollections` - AI Collections + +### Entity Definition Template + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/{EntityName}.json` + +```json +{ + "fields": { + "name": { + "type": "varchar", + "required": true, + "maxLength": 255, + "trim": true, + "isCustom": true, + "tooltip": true + }, + "status": { + "type": "enum", + "options": ["Neu", "In Bearbeitung", "Abgeschlossen"], + "default": "Neu", + "required": true, + "isCustom": true, + "style": { + "Neu": "primary", + "In Bearbeitung": "warning", + "Abgeschlossen": "success" + } + }, + "description": { + "type": "text", + "rows": 10, + "isCustom": true, + "tooltip": true + }, + "amount": { + "type": "currency", + "isCustom": true, + "audited": true + }, + "dueDate": { + "type": "date", + "isCustom": true, + "audited": true + } + }, + "links": { + "parent": { + "type": "belongsToParent", + "entityList": ["CVmhRumungsklage", "CMietinkasso"] + }, + "createdBy": { + "type": "belongsTo", + "entity": "User" + }, + "modifiedBy": { + "type": "belongsTo", + "entity": "User" + } + } +} +``` + +### Scope Definition + +**Datei:** `custom/Espo/Custom/Resources/metadata/scopes/{EntityName}.json` + +```json +{ + "entity": true, + "type": "Base", + "module": "Custom", + "object": true, + "isCustom": true, + "tab": true, + "acl": true, + "stream": true, + "disabled": false, + "customizable": true, + "importable": true, + "notifications": true, + "calendar": false +} +``` + +**Wichtige Flags:** +- `tab: true` - Zeigt Entity in Navigation +- `acl: true` - ACL-System aktiv +- `stream: true` - Stream/Activity Feed +- `calendar: true` - Für Entities mit Datum-Feldern + +### i18n (Internationalisierung) + +**KRITISCH:** Immer BEIDE Sprachen pflegen! + +**Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/{EntityName}.json` + +```json +{ + "labels": { + "Create {EntityName}": "{EntityName} erstellen", + "{EntityName}": "{EntityName}", + "name": "Name", + "status": "Status", + "description": "Beschreibung" + }, + "fields": { + "name": "Name", + "status": "Status", + "description": "Beschreibung", + "amount": "Betrag", + "dueDate": "Fälligkeitsdatum" + }, + "links": { + "parent": "Übergeordnet", + "relatedEntity": "Verknüpfte Entity" + }, + "options": { + "status": { + "Neu": "Neu", + "In Bearbeitung": "In Bearbeitung", + "Abgeschlossen": "Abgeschlossen" + } + }, + "tooltips": { + "name": "Eindeutiger Name des Datensatzes", + "description": "Detaillierte Beschreibung" + } +} +``` + +**Datei:** `custom/Espo/Custom/Resources/i18n/en_US/{EntityName}.json` + +```json +{ + "labels": { + "Create {EntityName}": "Create {EntityName}", + "{EntityName}": "{EntityName}" + }, + "fields": { + "name": "Name", + "status": "Status", + "description": "Description", + "amount": "Amount", + "dueDate": "Due Date" + }, + "links": { + "parent": "Parent", + "relatedEntity": "Related Entity" + }, + "options": { + "status": { + "Neu": "New", + "In Bearbeitung": "In Progress", + "Abgeschlossen": "Completed" + } + } +} +``` + +--- + +## Relationship-Patterns + +### 1. One-to-Many (hasMany / belongsTo) + +**Beispiel:** Ein Mietobjekt hat viele Mietverhältnisse + +**Parent Entity (CMietobjekt):** +```json +{ + "links": { + "mietverhltnisse": { + "type": "hasMany", + "entity": "CVmhMietverhltnis", + "foreign": "mietobjekt" + } + } +} +``` + +**Child Entity (CVmhMietverhltnis):** +```json +{ + "fields": { + "mietobjektId": { + "type": "varchar", + "len": 17 + }, + "mietobjektName": { + "type": "varchar" + } + }, + "links": { + "mietobjekt": { + "type": "belongsTo", + "entity": "CMietobjekt", + "foreign": "mietverhltnisse" + } + } +} +``` + +### 2. Many-to-Many (hasMany mit relationName) + +**Beispiel:** Dokumente ↔ AI Collections + +**Entity 1 (CDokumente):** +```json +{ + "links": { + "cAICollections": { + "type": "hasMany", + "entity": "CAICollections", + "foreign": "cDokumente", + "relationName": "cAICollectionCDokumente" + } + } +} +``` + +**Entity 2 (CAICollections):** +```json +{ + "links": { + "cDokumente": { + "type": "hasMany", + "entity": "CDokumente", + "foreign": "cAICollections", + "relationName": "cAICollectionCDokumente" + } + } +} +``` + +**Wichtig:** `relationName` muss identisch sein! + +### 3. Many-to-Many mit additionalColumns (Junction Entity) + +**Seit EspoCRM 6.0:** Junction-Tabellen werden automatisch als Entities verfügbar! + +**Entity Definition:** +```json +{ + "links": { + "cDokumente": { + "type": "hasMany", + "entity": "CDokumente", + "foreign": "cAICollections", + "relationName": "cAICollectionCDokumente", + "additionalColumns": { + "syncId": { + "type": "varchar", + "len": 255 + } + } + } + } +} +``` + +**Junction Entity (CAICollectionCDokumente):** + +**entityDefs/CAICollectionCDokumente.json:** +```json +{ + "fields": { + "id": { + "type": "id", + "dbType": "bigint", + "autoincrement": true + }, + "cAICollections": { + "type": "link" + }, + "cAICollectionsId": { + "type": "varchar", + "len": 17, + "index": true + }, + "cDokumente": { + "type": "link" + }, + "cDokumenteId": { + "type": "varchar", + "len": 17, + "index": true + }, + "syncId": { + "type": "varchar", + "len": 255, + "isCustom": true + }, + "deleted": { + "type": "bool", + "default": false + } + }, + "links": { + "cAICollections": { + "type": "belongsTo", + "entity": "CAICollections" + }, + "cDokumente": { + "type": "belongsTo", + "entity": "CDokumente" + } + } +} +``` + +**scopes/CAICollectionCDokumente.json:** +```json +{ + "entity": true, + "type": "Base", + "module": "Custom", + "object": true, + "isCustom": true, + "tab": false, + "acl": true, + "disabled": false +} +``` + +**Controller & Service:** +```php +getParsedBody(); + $id = $data->id ?? null; + + if (!$id) { + throw new BadRequest('ID is required'); + } + + $result = $this->getRecordService()->doSomething($id, $data); + + return [ + 'success' => true, + 'data' => $result + ]; + } + + /** + * Custom GET Action: GET /api/v1/CMyEntity/{id}/customData + */ + public function getActionCustomData(Request $request): array + { + $id = $request->getRouteParam('id'); + + $data = $this->getRecordService()->getCustomData($id); + + return [ + 'data' => $data + ]; + } +} +``` + +**2. Service Logic:** + +**Datei:** `custom/Espo/Custom/Services/{EntityName}.php` + +```php +getAcl()->checkEntityEdit($this->entityType)) { + throw new Forbidden(); + } + + // Load Entity + $entity = $this->getEntityManager()->getEntity($this->entityType, $id); + if (!$entity) { + throw new NotFound(); + } + + // Business Logic + $entity->set('status', 'In Bearbeitung'); + $this->getEntityManager()->saveEntity($entity); + + // Return Result + return [ + 'id' => $entity->getId(), + 'status' => $entity->get('status') + ]; + } + + public function getCustomData(string $id): array + { + $entity = $this->getEntityManager()->getEntity($this->entityType, $id); + if (!$entity) { + throw new NotFound(); + } + + // Complex data aggregation + $relatedData = $this->getRelatedData($entity); + + return [ + 'entity' => $entity->getValueMap(), + 'related' => $relatedData + ]; + } +} +``` + +### API Authentication + +**API Key Header:** +```bash +curl -X GET "https://crm.example.com/api/v1/CMyEntity" \ + -H "X-Api-Key: your-api-key-here" +``` + +**Test API Keys:** +- `marvin`: `e53def10eea27b92a6cd00f40a3e09a4` +- `dev-test`: `2b0747ca34d15032aa233ae043cc61bc` + +--- + +## Workflow-Management + +### Workflow-Dateien + +**Verzeichnis:** `custom/workflows/` + +**Format:** JSON (Simple Workflow oder BPM Flowchart) + +### Simple Workflow Beispiel + +```json +{ + "type": "simple", + "name": "auto-assign-new-entity", + "entity_type": "CMyEntity", + "trigger_type": "afterRecordCreated", + "is_active": true, + "description": "Auto-assign new records to team", + "conditions_all": [ + { + "type": "isEmpty", + "attribute": "assignedUserId" + } + ], + "actions": [ + { + "type": "applyAssignmentRule", + "targetTeamId": "team-id-here" + }, + { + "type": "sendEmail", + "to": "assignedUser", + "emailTemplateId": "template-id" + } + ] +} +``` + +### Workflow Import/Export + +```bash +# Alle Workflows exportieren +php custom/scripts/workflow_manager.php export + +# Workflow importieren +php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json + +# Workflows auflisten +php custom/scripts/workflow_manager.php list +``` + +--- + +## Testing & Validierung + +### Validierungs-Tool + +**Haupt-Tool:** `custom/scripts/validate_and_rebuild.py` + +```bash +# Vollständige Validierung + Rebuild +python3 custom/scripts/validate_and_rebuild.py + +# Nur Validierung (kein Rebuild) +python3 custom/scripts/validate_and_rebuild.py --dry-run + +# Mit E2E Tests überspringen +python3 custom/scripts/validate_and_rebuild.py --skip-e2e +``` + +**Das Tool prüft:** +1. ✅ JSON-Syntax aller Custom-Dateien +2. ✅ Relationship-Konsistenz (bidirektionale Links) +3. ✅ Formula-Script Platzierung +4. ✅ i18n-Vollständigkeit (de_DE + en_US) +5. ✅ Layout-Struktur (bottomPanelsDetail, detail.json) +6. ✅ Dateirechte (www-data:www-data) +7. ✅ CSS-Validierung (csslint) +8. ✅ JavaScript-Validierung (jshint) +9. ✅ PHP-Syntax (php -l) +10. ✅ EspoCRM Rebuild +11. ✅ E2E-Tests (CRUD-Operationen) + +**Bei Fehlern:** Automatische Fehlerlog-Analyse der letzten 50 Log-Zeilen! + +### End-to-End Tests + +**Tool:** `custom/scripts/e2e_tests.py` + +```bash +# E2E Tests ausführen +python3 custom/scripts/e2e_tests.py +``` + +**Tests:** +- CRUD für alle Custom Entities +- Relationship-Verknüpfungen +- ACL-Prüfungen + +### Manuelle Tests + +**Checkliste:** +- [ ] Entity in UI sichtbar? +- [ ] Felder editierbar? +- [ ] Relationships funktionieren? +- [ ] Formulas triggern korrekt? +- [ ] Workflows aktiv? +- [ ] API-Endpoints erreichbar? +- [ ] ACL-Regeln greifen? + +--- + +## Fehlerbehandlung + +### Log-Files + +**Verzeichnis:** `data/logs/` + +**Haupt-Logfile:** `espo-{YYYY-MM-DD}.log` + +```bash +# Letzte Fehler anzeigen +tail -50 data/logs/espo-$(date +%Y-%m-%d).log | grep -i error + +# Live-Monitoring +tail -f data/logs/espo-$(date +%Y-%m-%d).log +``` + +### Häufige Fehler + +#### 1. Layout-Fehler: "false" statt "{}" + +**Problem:** EspoCRM 7.x+ erfordert `{}` statt `false` als Platzhalter + +**Falsch:** +```json +{ + "rows": [ + [ + {"name": "field1"}, + false + ] + ] +} +``` + +**Richtig:** +```json +{ + "rows": [ + [ + {"name": "field1"}, + {} + ] + ] +} +``` + +#### 2. Relationship nicht bidirektional + +**Problem:** `foreign` zeigt nicht zurück + +**Falsch:** +```json +// Entity A +"links": { + "entityB": { + "type": "hasMany", + "entity": "EntityB", + "foreign": "wrongName" // ❌ + } +} + +// Entity B +"links": { + "entityA": { + "type": "belongsTo", + "entity": "EntityA", + "foreign": "entityB" + } +} +``` + +**Richtig:** +```json +// Entity A +"links": { + "entityB": { + "type": "hasMany", + "entity": "EntityB", + "foreign": "entityA" // ✅ Zeigt auf Link-Namen in B + } +} + +// Entity B +"links": { + "entityA": { + "type": "belongsTo", + "entity": "EntityA", + "foreign": "entityB" // ✅ Zeigt auf Link-Namen in A + } +} +``` + +#### 3. i18n fehlt für en_US + +**Problem:** Nur de_DE vorhanden, en_US fehlt + +**Lösung:** IMMER beide Sprachen pflegen! en_US ist Fallback. + +#### 4. Dateirechte falsch + +**Problem:** Files gehören root statt www-data + +**Lösung:** Automatisch via validate_and_rebuild.py oder manuell: +```bash +sudo chown -R www-data:www-data custom/ +sudo find custom/ -type f -exec chmod 664 {} \; +sudo find custom/ -type d -exec chmod 775 {} \; +``` + +#### 5. ACL: 403 Forbidden + +**Problem:** Role hat keine Rechte auf Entity + +**Lösung:** ACL in Admin UI oder via SQL: +```sql +UPDATE role +SET data = JSON_SET(data, + '$.table.CMyEntity', + JSON_OBJECT('create', 'yes', 'read', 'all', 'edit', 'all', 'delete', 'all') +) +WHERE name = 'RoleName'; +``` + +--- + +## Deployment-Prozess + +### Standard-Workflow + +```bash +# 1. Code-Änderungen durchführen +vim custom/Espo/Custom/Resources/metadata/entityDefs/CMyEntity.json + +# 2. Validierung + Rebuild +python3 custom/scripts/validate_and_rebuild.py + +# 3. Bei Erfolg: Commit +git add custom/ +git commit -m "feat: Add CMyEntity with custom fields" +git push +``` + +### Quick Rebuild (nach kleinen Änderungen) + +```bash +docker exec espocrm php command.php clear-cache +docker exec espocrm php command.php rebuild +``` + +### Nach Änderungen an Relationships + +**IMMER:** +1. Cache löschen +2. Rebuild ausführen +3. Browser-Cache löschen (Ctrl+F5) + +--- + +## Troubleshooting + +### Rebuild schlägt fehl + +**1. Logs prüfen:** +```bash +python3 custom/scripts/validate_and_rebuild.py +# → Zeigt automatisch Fehlerlog-Analyse +``` + +**2. Manuell Logs checken:** +```bash +tail -100 data/logs/espo-$(date +%Y-%m-%d).log +``` + +**3. PHP-Fehler:** +```bash +docker exec espocrm php -l custom/Espo/Custom/Controllers/MyController.php +``` + +### Entity nicht sichtbar + +**Checklist:** +- [ ] `tab: true` in scopes? +- [ ] `disabled: false` in scopes? +- [ ] ACL-Rechte für Role? +- [ ] Cache gelöscht? +- [ ] Rebuild durchgeführt? + +### Relationship funktioniert nicht + +**Checklist:** +- [ ] Bidirektional konfiguriert? +- [ ] `foreign` zeigt korrekt zurück? +- [ ] `relationName` identisch (bei M2M)? +- [ ] Rebuild durchgeführt? + +### API gibt 404 + +**Checklist:** +- [ ] Controller existiert? +- [ ] Service existiert? +- [ ] Action-Methode korrekt benannt? (postAction..., getAction...) +- [ ] ACL-Rechte? + +### Formula triggert nicht + +**Checklist:** +- [ ] In `metadata/formula/` statt entityDefs? +- [ ] Syntax korrekt? +- [ ] Rebuild durchgeführt? + +--- + +## Projekt-spezifische Entities + +### Übersicht + +1. **CMietobjekt** - Mietobjekte (Wohnungen/Häuser) +2. **CVmhMietverhltnis** - Mietverhältnisse +3. **CKuendigung** - Kündigungen +4. **CBeteiligte** - Beteiligte Personen +5. **CMietinkasso** - Mietinkasso-Verfahren +6. **CVmhRumungsklage** - Räumungsklagen +7. **CDokumente** - Dokumente +8. **CPuls** - Puls-System (Entwicklungen) +9. **CAICollections** - AI Collections + +### Entity-Graph + +``` +CMietobjekt + ├── CVmhMietverhltnis (hasMany) + │ ├── CKuendigung (hasMany) + │ │ └── CVmhRumungsklage (hasOne) + │ ├── CMietinkasso (hasMany) + │ └── CBeteiligte (hasMany) + └── Contact (hasMany) + +CDokumente + ├── parent → [CVmhRumungsklage, CMietinkasso, CKuendigung] + └── CAICollections (hasMany via Junction) + └── CPuls (hasMany) +``` + +--- + +## Tools & Scripts + +### Übersicht + +| Tool | Zweck | Ausführung | +|------|-------|-----------| +| validate_and_rebuild.py | Validierung + Rebuild | `python3 custom/scripts/validate_and_rebuild.py` | +| e2e_tests.py | End-to-End Tests | `python3 custom/scripts/e2e_tests.py` | +| ki_project_overview.py | Projekt-Analyse für AI | `python3 custom/scripts/ki_project_overview.py` | +| workflow_manager.php | Workflow-Verwaltung | `php custom/scripts/workflow_manager.php list` | + +### KI-Projekt-Übersicht + +**Für AI Code Agents:** +```bash +python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt +# → Gibt vollständigen Projekt-Status für AI aus +``` + +--- + +## Ressourcen + +### Dokumentation + +- **EspoCRM Docs:** https://docs.espocrm.com/ +- **API Reference:** https://docs.espocrm.com/development/api/ +- **Formula Functions:** https://docs.espocrm.com/administration/formula/ + +### Projekt-Dokumentation + +- `custom/docs/ESPOCRM_BEST_PRACTICES.md` - Dieses Dokument +- `custom/scripts/QUICKSTART.md` - Quick Start Guide +- `custom/scripts/VALIDATION_TOOLS.md` - Validierungs-Tools +- `custom/scripts/E2E_TESTS_README.md` - E2E Tests +- `custom/README.md` - Custom Actions Blueprint +- `custom/TESTERGEBNISSE_JUNCTION_TABLE.md` - Junction Table Implementation + +--- + +## Glossar + +**ACL** - Access Control List (Zugriffsrechte) +**Entity** - Datenmodell (z.B. CMietobjekt) +**Link** - Relationship zwischen Entities +**Junction Table** - Verbindungstabelle für Many-to-Many +**Formula** - Berechnete Felder oder Automation-Scripts +**Scope** - Entity-Konfiguration (Tab, ACL, etc.) +**Stream** - Activity Feed einer Entity +**Hook** - Lifecycle-Event-Handler +**Service** - Business Logic Layer +**Controller** - API Request Handler +**Repository** - Data Access Layer + +--- + +**Ende der Best Practices Dokumentation** + +Für spezifische Fragen oder Updates: Siehe `/custom/docs/` Verzeichnis. diff --git a/custom/docs/README.md b/custom/docs/README.md new file mode 100644 index 00000000..ef35fae4 --- /dev/null +++ b/custom/docs/README.md @@ -0,0 +1,319 @@ +# EspoCRM Custom Development - Dokumentation + +**Zentrale Dokumentation für EspoCRM-Entwicklung mit KI-Support** + +--- + +## 📂 Struktur + +``` +custom/docs/ +├── README.md # Diese Datei +├── ESPOCRM_BEST_PRACTICES.md # ⭐ HAUPTDOKUMENTATION +├── TESTERGEBNISSE_JUNCTION_TABLE.md # Junction Table Implementation +├── ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md # Puls-System Spezifikation +├── tools/ # Tool-Dokumentation +│ ├── QUICKSTART.md # Quick Start Guide +│ ├── VALIDATION_TOOLS.md # Validierungs-Tools +│ ├── E2E_TESTS_README.md # End-to-End Tests +│ ├── KI_OVERVIEW_README.md # KI-Projekt-Übersicht +│ └── VALIDATOR_README.md # Validator Details +└── workflows/ # Workflow-Dokumentation + └── README.md # Workflow-Format & Import/Export +``` + +--- + +## 🚀 Für AI Code Agents - HIER STARTEN! + +### 1. Lies zuerst: ESPOCRM_BEST_PRACTICES.md + +**Das Hauptdokument enthält:** +- ✅ Projekt-Übersicht & Architektur +- ✅ Entity-Entwicklung (Naming, Templates, i18n) +- ✅ Relationship-Patterns (One-to-Many, Many-to-Many, Junction Tables) +- ✅ API-Entwicklung (REST Endpoints, Custom Actions) +- ✅ Workflow-Management +- ✅ Testing & Validierung +- ✅ Fehlerbehandlung & Troubleshooting +- ✅ Deployment-Prozess +- ✅ Projekt-spezifische Entities + +**Befehl:** +```bash +cat custom/docs/ESPOCRM_BEST_PRACTICES.md +``` + +### 2. Projekt-Analyse abrufen + +**Für umfassenden aktuellen Projekt-Status:** +```bash +python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt +cat /tmp/project-overview.txt +``` + +**Output:** Alle Entities, Relationships, Custom Code, Workflows + +### 3. Validierung & Rebuild ausführen + +**Vor jeder Entwicklung:** +```bash +python3 custom/scripts/validate_and_rebuild.py +``` + +**Das Tool führt automatisch durch:** +1. JSON-Syntax Check +2. Relationship-Validierung +3. i18n-Vollständigkeit +4. Layout-Struktur +5. Dateirechte +6. CSS/JS/PHP Validierung +7. EspoCRM Rebuild +8. E2E-Tests +9. **NEU:** Automatische Fehlerlog-Analyse bei Rebuild-Fehlern! + +--- + +## 📖 Dokumentations-Guide + +### Hauptdokumentation + +#### ESPOCRM_BEST_PRACTICES.md +**Vollständiges Entwickler-Handbuch** +- Architektur-Prinzipien +- Entity-Development mit Templates +- Relationship-Patterns inkl. Junction Tables +- API-Entwicklung (Controller, Service, REST) +- Workflow-Management +- Testing (Validierung, E2E) +- Troubleshooting & Fehlerbehandlung + +**Verwende diese Datei für:** +- Neuen Code-Agent briefen +- Entwicklungsstandards nachschlagen +- Entity-Templates kopieren +- Fehler debuggen + +#### TESTERGEBNISSE_JUNCTION_TABLE.md +**Junction Table Implementation Guide** +- Many-to-Many mit additionalColumns +- Junction Entity als API-Endpoint +- Vollständige Code-Beispiele +- API-Nutzung (CRUD, Filter, Sort) + +**Verwende diese Datei für:** +- Many-to-Many Beziehungen mit Zusatzfeldern +- API-only Access Pattern +- Junction Entity Implementation + +#### ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md +**Puls-System (CPuls) Spezifikation** +- Posteingangs-System mit KI-Analyse +- Team-basierte Dokumenten-Workflows +- First-Read-Closes Prinzip +- Entity-Definitionen und Beziehungen + +**Verwende diese Datei für:** +- CPuls-Entity Weiterentwicklung +- Dokumenten-Workflow-Logik +- KI-Integration-Patterns + +--- + +## 🛠️ Tool-Dokumentation + +### tools/QUICKSTART.md +**5-Minuten Quick Start für Validator** +- Installation bereits fertig +- Verwendung: `python3 custom/scripts/validate_and_rebuild.py` +- Output-Interpretation + +### tools/VALIDATION_TOOLS.md +**Technische Details zu Validierungs-Tools** +- PHP-CLI (php -l) +- CSSLint (csslint) +- JSHint (jshint) +- Integration im Validator + +### tools/E2E_TESTS_README.md +**End-to-End Test Framework** +- CRUD-Tests für Custom Entities +- Relationship-Tests +- Konfiguration & Ausführung + +### tools/KI_OVERVIEW_README.md +**KI-Projekt-Übersicht Tool** +- Automatische Projekt-Analyse +- Output-Format für AI Agents +- Verwendung: `python3 custom/scripts/ki_project_overview.py` + +### tools/VALIDATOR_README.md +**Validator Details** +- Alternative zu VALIDATION_TOOLS.md +- Erweiterte Validator-Funktionalität + +--- + +## 📋 Workflow-Dokumentation + +### workflows/README.md +**Workflow-Format & Management** +- Simple Workflow JSON-Format +- BPM Flowchart Format +- Import/Export mit `workflow_manager.php` +- Trigger Types, Actions, Conditions + +**Befehle:** +```bash +# Workflows auflisten +php custom/scripts/workflow_manager.php list + +# Workflow importieren +php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json + +# Alle Workflows exportieren +php custom/scripts/workflow_manager.php export +``` + +--- + +## 🎯 Typische Workflows + +### Neue Entity entwickeln + +```bash +# 1. Best Practices lesen +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 50 "Entity Definition Template" + +# 2. Entity-Dateien erstellen (entityDefs, scopes, i18n de_DE + en_US) + +# 3. Validierung + Rebuild +python3 custom/scripts/validate_and_rebuild.py +``` + +### Relationship implementieren + +```bash +# 1. Relationship-Pattern nachschlagen +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 100 "Relationship-Patterns" + +# 2. Links in beiden Entities konfigurieren + +# 3. Validierung (prüft bidirektionale Konsistenz) +python3 custom/scripts/validate_and_rebuild.py +``` + +### Junction Table mit additionalColumns + +```bash +# 1. Vollständige Anleitung lesen +cat custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md + +# 2. Entity-Definitionen + Junction Entity erstellen + +# 3. Controller & Service für Junction Entity + +# 4. Validierung + Test +python3 custom/scripts/validate_and_rebuild.py +``` + +### Fehler debuggen + +```bash +# 1. Validierung ausführen (zeigt automatisch Fehlerlog bei Rebuild-Fehler) +python3 custom/scripts/validate_and_rebuild.py + +# 2. Manuell Logs prüfen (falls nötig) +tail -100 data/logs/espo-$(date +%Y-%m-%d).log | grep -i error + +# 3. Troubleshooting Guide +cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 200 "Troubleshooting" +``` + +--- + +## 🔧 Entwicklungs-Tools Übersicht + +| Tool | Zweck | Befehl | +|------|-------|--------| +| **validate_and_rebuild.py** | Haupttool: Validierung + Rebuild + E2E | `python3 custom/scripts/validate_and_rebuild.py` | +| **ki_project_overview.py** | Projekt-Analyse für AI | `python3 custom/scripts/ki_project_overview.py` | +| **e2e_tests.py** | End-to-End CRUD Tests | `python3 custom/scripts/e2e_tests.py` | +| **workflow_manager.php** | Workflow Import/Export | `php custom/scripts/workflow_manager.php list` | + +**Alle Tools dokumentiert in:** `custom/docs/tools/` + +--- + +## 📚 Weitere Ressourcen + +### Projekt-spezifische Dokumente + +- `../README.md` - Custom Actions Blueprint +- `../CUSTOM_DIRECTORY.md` - Verzeichnisstruktur-Übersicht + +### EspoCRM Offizielle Docs + +- **Main Docs:** https://docs.espocrm.com/ +- **API Reference:** https://docs.espocrm.com/development/api/ +- **Formula Functions:** https://docs.espocrm.com/administration/formula/ + +--- + +## 🎓 Best Practices Zusammenfassung + +### DO ✅ + +- **IMMER beide Sprachen:** de_DE + en_US (en_US ist Fallback!) +- **Bidirektionale Relationships:** `foreign` muss zurück zeigen +- **Validierung vor Rebuild:** Nutze `validate_and_rebuild.py` +- **Sprechende Namen:** `C{EntityName}` für Custom Entities +- **Clean Code:** Kleine Funktionen, Type Hints, Kommentare +- **Layouts mit {}:** EspoCRM 7.x+ erfordert `{}` statt `false` + +### DON'T ❌ + +- **Keine fehlende i18n:** immer de_DE UND en_US +- **Keine unidirektionalen Links:** immer foreign konfigurieren +- **Keine komplexe Logik in Hooks:** nutze Services +- **Keine direkten SQL-Queries:** nutze EntityManager +- **Keine hard-coded Werte:** nutze Config + +--- + +## 🆘 Support & Troubleshooting + +### Bei Problemen: + +1. **Fehlerlog-Analyse:** + ```bash + python3 custom/scripts/validate_and_rebuild.py + # → Zeigt automatisch Fehler bei Rebuild-Fehlern + ``` + +2. **Troubleshooting Guide:** + ```bash + cat custom/docs/ESPOCRM_BEST_PRACTICES.md | grep -A 300 "Troubleshooting" + ``` + +3. **Häufige Fehler:** + - Layout `false` → `{}` ändern + - i18n fehlt → beide Sprachen anlegen + - Relationship kaputt → bidirektional prüfen + - ACL 403 → Rechte in Admin UI + +--- + +## 📊 System-Info + +- **EspoCRM Version:** 9.3.2 +- **PHP Version:** 8.2.30 +- **Database:** MariaDB 12.2.2 +- **Docker Container:** espocrm, espocrm-db +- **Workspace:** `/var/lib/docker/volumes/vmh-espocrm_espocrm/_data` + +--- + +**Letzte Aktualisierung:** 9. März 2026 + +**Für Fragen oder Updates:** Siehe `custom/docs/ESPOCRM_BEST_PRACTICES.md` diff --git a/custom/TESTERGEBNISSE_JUNCTION_TABLE.md b/custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md similarity index 85% rename from custom/TESTERGEBNISSE_JUNCTION_TABLE.md rename to custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md index f11e46e1..376c8dcb 100644 --- a/custom/TESTERGEBNISSE_JUNCTION_TABLE.md +++ b/custom/docs/TESTERGEBNISSE_JUNCTION_TABLE.md @@ -266,9 +266,21 @@ response = requests.put( | CREATE via API | ✅ | POST mit allen Feldern | | UPDATE via API | ✅ | PUT zum Ändern von syncId | | DELETE via API | ✅ | Standard-DELETE | -| View-Darstellung | ✅* | Möglich, aber manuell konfigurieren | +| View-Darstellung | ❌ | Nicht empfohlen - verursacht 405 Fehler | -*) Benötigt manuelle Layout-Konfiguration im Entity Manager +## ⚠️ UI-Panel Warnung + +**WICHTIG:** additionalColumns sollten NICHT in Standard-Relationship-Panels angezeigt werden! + +**Problem:** +- Standard relationship panels versuchen inline-editing +- Dies führt zu 405 Method Not Allowed Fehlern +- additionalColumns sind nicht kompatibel mit Standard-Panel-Architektur + +**Empfehlung:** +- ✅ Nutze API-only Access Pattern +- ✅ Vollständige CRUD via `/api/v1/CAICollectionCDokumente` +- ❌ NICHT in CDokumente detail view als relationship panel anzeigen ## 🎯 Fazit @@ -280,6 +292,18 @@ Die **Junction-Tabelle mit `additionalColumns` ist vollständig via REST-API nut - ✅ CRUD-Operationen vollständig unterstützt - ✅ `syncId` ist direkt in der Response - ✅ Einfache Integration in externe Systeme +- ✅ API-only Pattern verhindert 405-Fehler + +**Einschränkungen:** +- ⚠️ UI-Darstellung in Standard-Relationship-Panels verursacht 405 Fehler +- ⚠️ additionalColumns nur über Junction-Entity-API zugänglich +- ⚠️ Standard relationship endpoints (z.B. GET /api/v1/CDokumente/{id}/cAICollections) geben additionalColumns NICHT zurück + +**Best Practice:** +1. ✅ Junction Entity als API-Endpoint nutzen (`/api/v1/CAICollectionCDokumente`) +2. ✅ Keine UI-Panels für Junction-Relationships mit additionalColumns +3. ✅ API-Integration für externe Systeme (Middleware, KI, etc.) +4. ✅ Bei Bedarf: Separate Management-UI für Junction Entity (ohne Relationship-Panel) **Wichtig:** 1. Controller und Service erstellen @@ -287,6 +311,7 @@ Die **Junction-Tabelle mit `additionalColumns` ist vollständig via REST-API nut 3. Entity-Definition mit korrekten Feldtypen 4. ACL-Rechte für die Junction-Entity setzen 5. Cache löschen und rebuild +6. **NICHT** als Relationship-Panel in UI anzeigen (→ 405 Fehler) ## 📁 Dateien @@ -367,12 +392,27 @@ Die `syncId` kann in der Datenbank gespeichert werden: - ✅ Daten werden korrekt persistiert ### 4. View-Darstellung -**Status: TECHNISCH MÖGLICH** +**Status: ⚠️ NICHT EMPFOHLEN (API-ONLY PATTERN)** -Die Beziehung kann im EspoCRM-Frontend dargestellt werden durch: -- Anzeige in Relationship-Panels -- Konfiguration über Entity Manager → Relationships -- Anpassung der Layouts für die Anzeige von `syncId` als Spalte +**Problem:** Standard EspoCRM Relationship-Panels versuchen inline-editing von Feldern. Bei additionalColumns führt dies zu **405 Method Not Allowed** Fehlern, da die Standard-Panel-UI nicht mit dem Junction-Entity-Pattern kompatibel ist. + +**Versucht & Fehlgeschlagen:** +1. ❌ Direct display of syncId in relationship panel layout → 405 Fehler +2. ❌ Custom View mit actionEditLinkData → Blank views, dann weiter 405 Fehler +3. ❌ Simplified relationship layout ohne syncId → 405 Fehler blieben bestehen + +**ROOT CAUSE:** Standard relationship panels senden HTTP-Requests die nicht mit Junction-Entity-Architektur übereinstimmen. additionalColumns erfordern spezielle Behandlung die nicht durch Standard-UI bereitgestellt wird. + +**LÖSUNG:** API-ONLY Access Pattern +- ✅ Vollständige CRUD via `/api/v1/CAICollectionCDokumente` +- ✅ Kein UI-Panel in CDokumente → keine 405 Fehler +- ✅ Alle Funktionen über REST API verfügbar +- ✅ Perfekt für externe Systeme und Middleware + +**Falls UI Display gewünscht:** +- Option: Custom Panel das direkt die Junction Entity list-view lädt (gefiltert nach documentId) +- Option: Separate Tab/Page für Junction Entity-Management +- Nicht empfohlen: Standard relationship panel mit additionalColumns ## ❌ Was NICHT funktioniert diff --git a/custom/scripts/E2E_TEST_RESULTS.md b/custom/docs/archive/E2E_TEST_RESULTS.md similarity index 100% rename from custom/scripts/E2E_TEST_RESULTS.md rename to custom/docs/archive/E2E_TEST_RESULTS.md diff --git a/custom/scripts/KI_OVERVIEW_SUMMARY.md b/custom/docs/archive/KI_OVERVIEW_SUMMARY.md similarity index 100% rename from custom/scripts/KI_OVERVIEW_SUMMARY.md rename to custom/docs/archive/KI_OVERVIEW_SUMMARY.md diff --git a/custom/scripts/E2E_TESTS_README.md b/custom/docs/tools/E2E_TESTS_README.md similarity index 100% rename from custom/scripts/E2E_TESTS_README.md rename to custom/docs/tools/E2E_TESTS_README.md diff --git a/custom/scripts/KI_OVERVIEW_README.md b/custom/docs/tools/KI_OVERVIEW_README.md similarity index 100% rename from custom/scripts/KI_OVERVIEW_README.md rename to custom/docs/tools/KI_OVERVIEW_README.md diff --git a/custom/scripts/QUICKSTART.md b/custom/docs/tools/QUICKSTART.md similarity index 100% rename from custom/scripts/QUICKSTART.md rename to custom/docs/tools/QUICKSTART.md diff --git a/custom/scripts/VALIDATION_TOOLS.md b/custom/docs/tools/VALIDATION_TOOLS.md similarity index 100% rename from custom/scripts/VALIDATION_TOOLS.md rename to custom/docs/tools/VALIDATION_TOOLS.md diff --git a/custom/scripts/VALIDATOR_README.md b/custom/docs/tools/VALIDATOR_README.md similarity index 100% rename from custom/scripts/VALIDATOR_README.md rename to custom/docs/tools/VALIDATOR_README.md diff --git a/custom/docs/workflows/README.md b/custom/docs/workflows/README.md new file mode 100644 index 00000000..d17860f2 --- /dev/null +++ b/custom/docs/workflows/README.md @@ -0,0 +1,72 @@ +# Workflow Definitions + +This directory contains workflow definitions in JSON format that can be imported into EspoCRM using the workflow manager script. + +## File Format + +### Simple Workflow +```json +{ + "type": "simple", + "name": "workflow-name", + "entity_type": "EntityName", + "trigger_type": "afterRecordSaved", + "is_active": true, + "description": "Description of what this workflow does", + "category": "Category Name", + "conditions_all": [], + "conditions_any": [], + "conditions_formula": null, + "actions": [] +} +``` + +**Trigger Types:** +- `afterRecordSaved` - After record is created or updated +- `afterRecordCreated` - Only after record is created +- `scheduled` - Runs on a schedule +- `manual` - Manually triggered + +**Condition Types:** +- `equals`, `notEquals`, `greaterThan`, `lessThan`, `contains`, `notContains`, `isEmpty`, `isNotEmpty`, `isTrue`, `isFalse`, `wasEqual`, `wasNotEqual`, `changed`, `notChanged` + +**Action Types:** +- `sendEmail` - Send email to recipient +- `createEntity` - Create a new record +- `updateEntity` - Update current record +- `relateTo` - Link to another record +- `unrelateFrom` - Unlink from record +- `applyAssignmentRule` - Apply assignment rules +- `createNotification` - Create notification + +### BPM Flowchart +```json +{ + "type": "bpm", + "name": "flowchart-name", + "target_type": "EntityName", + "is_active": true, + "description": "Description", + "data": { + "list": [] + }, + "elements_data_hash": {}, + "event_start_all_id_list": [] +} +``` + +## Usage + +Import a workflow: +```bash +docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import /var/www/html/custom/workflows/your-workflow.json +``` + +Export a workflow: +```bash +docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export /var/www/html/custom/workflows/exported.json +``` + +## Examples + +- `vmh-erstberatung-abschliessen.json` - Sends email and sets status when consultation is completed diff --git a/custom/scripts/__pycache__/validate_and_rebuild.cpython-311.pyc b/custom/scripts/__pycache__/validate_and_rebuild.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a6496dd41a88a27313607af6818288c42750517 GIT binary patch literal 60438 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 cZB%_5S439qfAi=Mju!bJR%LM$Zu4pOA9`K 10: + print(f"\n{Colors.YELLOW}... und {len(errors) - 10} weitere Fehler{Colors.END}") + + if warnings: + print_warning(f"\n{len(warnings)} Warnungen in den letzten 50 Log-Zeilen gefunden:\n") + for i, warning in enumerate(warnings[-5:], 1): # Zeige max. 5 Warnungen + print(f"{Colors.YELLOW}{i}.{Colors.END} {warning}") + if len(warnings) > 5: + print(f"\n{Colors.YELLOW}... und {len(warnings) - 5} weitere Warnungen{Colors.END}") + + if not errors and not warnings: + print_info("Keine Fehler oder Warnungen in den letzten 50 Log-Zeilen gefunden") + print_info("\nLetzte 10 Log-Zeilen:") + for line in last_lines[-10:]: + print(f" {line.strip()}") + + print(f"\n{Colors.BLUE}ℹ{Colors.END} Vollständige Log-Datei: {log_file}") + print(f"{Colors.BLUE}ℹ{Colors.END} Zum Anzeigen: tail -50 {log_file}") + + except Exception as e: + print_error(f"Fehler beim Lesen der Log-Datei: {e}") + def run_rebuild(self) -> bool: """Führe den EspoCRM Rebuild aus.""" print_header("10. ESPOCRM REBUILD") @@ -811,6 +886,9 @@ class EntityValidator: print_error("Rebuild fehlgeschlagen:") if result.stderr: print(f"\n{result.stderr}") + + # Zeige automatisch die letzten Fehlerlog-Einträge an + self.show_error_logs() return False else: print_warning("Kein EspoCRM Docker-Container gefunden") @@ -862,6 +940,9 @@ class EntityValidator: print_error("Rebuild fehlgeschlagen:") if result.stderr: print(f"\n{result.stderr}") + + # Zeige automatisch die letzten Fehlerlog-Einträge an + self.show_error_logs() return False except subprocess.TimeoutExpired: print_error("Rebuild-Timeout (>60 Sekunden)") diff --git a/custom/workflows/README.md b/custom/workflows/README.md index d17860f2..ffaf5c29 100644 --- a/custom/workflows/README.md +++ b/custom/workflows/README.md @@ -1,72 +1,39 @@ -# Workflow Definitions +# Workflow Documentation -This directory contains workflow definitions in JSON format that can be imported into EspoCRM using the workflow manager script. +Dokumentation für EspoCRM Workflow-Management. -## File Format +## Workflow-Format & Management -### Simple Workflow -```json -{ - "type": "simple", - "name": "workflow-name", - "entity_type": "EntityName", - "trigger_type": "afterRecordSaved", - "is_active": true, - "description": "Description of what this workflow does", - "category": "Category Name", - "conditions_all": [], - "conditions_any": [], - "conditions_formula": null, - "actions": [] -} -``` +Siehe: `custom/docs/workflows/README.md` für vollständige Workflow-Dokumentation inkl.: +- Simple Workflow JSON-Format +- BPM Flowchart Format +- Trigger Types (afterRecordSaved, afterRecordCreated, scheduled, manual) +- Action Types (sendEmail, createEntity, updateEntity, etc.) +- Condition Types +- Import/Export mit workflow_manager.php -**Trigger Types:** -- `afterRecordSaved` - After record is created or updated -- `afterRecordCreated` - Only after record is created -- `scheduled` - Runs on a schedule -- `manual` - Manually triggered +## Workflow-Befehle -**Condition Types:** -- `equals`, `notEquals`, `greaterThan`, `lessThan`, `contains`, `notContains`, `isEmpty`, `isNotEmpty`, `isTrue`, `isFalse`, `wasEqual`, `wasNotEqual`, `changed`, `notChanged` - -**Action Types:** -- `sendEmail` - Send email to recipient -- `createEntity` - Create a new record -- `updateEntity` - Update current record -- `relateTo` - Link to another record -- `unrelateFrom` - Unlink from record -- `applyAssignmentRule` - Apply assignment rules -- `createNotification` - Create notification - -### BPM Flowchart -```json -{ - "type": "bpm", - "name": "flowchart-name", - "target_type": "EntityName", - "is_active": true, - "description": "Description", - "data": { - "list": [] - }, - "elements_data_hash": {}, - "event_start_all_id_list": [] -} -``` - -## Usage - -Import a workflow: ```bash -docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import /var/www/html/custom/workflows/your-workflow.json +# Alle Workflows auflisten +php custom/scripts/workflow_manager.php list + +# Workflow importieren +php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json + +# Alle Workflows exportieren +php custom/scripts/workflow_manager.php export + +# Workflow löschen +php custom/scripts/workflow_manager.php delete workflow-name ``` -Export a workflow: -```bash -docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export /var/www/html/custom/workflows/exported.json -``` +## Workflow-Dateien -## Examples +Workflow-Definitionen werden als JSON-Dateien in diesem Verzeichnis gespeichert und können mit dem workflow_manager.php Tool importiert werden. -- `vmh-erstberatung-abschliessen.json` - Sends email and sets status when consultation is completed +**Datei-Naming:** `{workflow-name}.json` (Kleinbuchstaben, Bindestriche) + +--- + +Für Details siehe: **custom/docs/workflows/README.md**