- Introduced CSS custom properties for spacing, typography, colors, and shadows. - Developed styles for form sections, grids, invoice items, and summary components. - Implemented responsive design adjustments for various screen sizes. - Added utility classes for text, spacing, and flex layouts. - Included dark mode and high contrast mode support. - Established loading states and validation/error styles. - Enhanced accessibility features with focus styles and screen reader utilities.
1377 lines
39 KiB
Markdown
1377 lines
39 KiB
Markdown
# myCRM Architecture Review - December 12, 2025
|
|
|
|
## Executive Summary
|
|
|
|
This comprehensive architecture review examines the recent billing module implementation and overall system architecture of the myCRM application. The review focuses on architectural consistency, SOLID principles adherence, security layer integrity, and maintainability.
|
|
|
|
**Overall Assessment: GOOD with CRITICAL ISSUES requiring immediate attention**
|
|
|
|
The application demonstrates a well-designed modular architecture with sophisticated security mechanisms. However, the billing module implementation currently exists only as documentation/example code and has critical gaps that prevent it from functioning as a production-ready module.
|
|
|
|
---
|
|
|
|
## 1. Recent Changes Analysis
|
|
|
|
### 1.1 Billing Module Status
|
|
|
|
**CRITICAL FINDING: The billing module is currently only EXAMPLE/DOCUMENTATION code**
|
|
|
|
Location: `/Users/olli/Git/__privat/myCRM/docs/example-module/`
|
|
|
|
**Issues Identified:**
|
|
|
|
1. **No Production Entities**: The Invoice, InvoiceItem, and Payment entities exist only in `/docs/example-module/Entity/Invoice.php` with namespace `MyCRM\BillingModule\Entity`, NOT in the actual `src/Entity/` directory.
|
|
|
|
2. **Missing Voter Implementation**: Despite cache references to `InvoiceVoter`, no actual voter exists in `/Users/olli/Git/__privat/myCRM/src/Security/Voter/`.
|
|
|
|
3. **Database Migration Exists but Entities Don't**:
|
|
- Migration file: `/Users/olli/Git/__privat/myCRM/migrations/Version20251205095156.php`
|
|
- Creates tables: `invoices`, `invoice_items`, `payments`
|
|
- **Problem**: No corresponding entity classes in `src/Entity/` to map these tables
|
|
|
|
4. **Frontend Implementation Without Backend**:
|
|
- InvoiceManagement.vue exists and is registered in router
|
|
- InvoiceForm.vue, PaymentForm.vue, PDFUploadForm.vue all exist
|
|
- **Problem**: API endpoints `/api/invoices` referenced in frontend don't exist (no Invoice entity with ApiResource)
|
|
|
|
5. **Plugin Registration**: BillingModulePlugin is properly registered (confirmed via `debug:container`), but it references entities that don't exist in the autoload path.
|
|
|
|
### 1.2 Contact Entity Changes
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/Entity/Contact.php`
|
|
|
|
**Changes Reviewed**:
|
|
```php
|
|
// Added invoice:read serialization group
|
|
#[Groups(['contact:read', 'project:read', 'invoice:read'])]
|
|
private ?int $id = null;
|
|
|
|
#[Groups(['contact:read', 'contact:write', 'project:read', 'invoice:read'])]
|
|
private ?string $companyName = null;
|
|
```
|
|
|
|
**Assessment**: GOOD
|
|
- Follows established pattern of adding serialization groups for cross-entity references
|
|
- Consistent with existing patterns (e.g., `project:read` group)
|
|
- Enables proper Contact serialization within Invoice context
|
|
- **However**: No actual Invoice entity exists to consume these groups
|
|
|
|
---
|
|
|
|
## 2. Architectural Consistency Assessment
|
|
|
|
### 2.1 Module Architecture Pattern Adherence
|
|
|
|
**Expected Pattern** (from CLAUDE.md):
|
|
1. Entity implements `ModuleAwareInterface`
|
|
2. Entity annotated with `#[ApiResource]` with security attributes
|
|
3. Voter created (either custom or delegating to ModuleVoter)
|
|
4. Frontend Vue component
|
|
5. Route added to router.js
|
|
6. Menu item added to AppMenu.vue
|
|
|
|
**Billing Module Status**:
|
|
| Component | Expected | Actual | Status |
|
|
|-----------|----------|--------|--------|
|
|
| Entity (Invoice) | `src/Entity/Invoice.php` | `docs/example-module/Entity/Invoice.php` | MISSING |
|
|
| ModuleAwareInterface | Implemented | Implemented (in example) | EXAMPLE ONLY |
|
|
| ApiResource annotation | Present with security | Present (in example) | EXAMPLE ONLY |
|
|
| Voter | `InvoiceVoter` | Not found | MISSING |
|
|
| Frontend Component | InvoiceManagement.vue | EXISTS | OK |
|
|
| Router Entry | `/billing/invoices` | EXISTS | OK |
|
|
| Menu Item | Expected | NOT FOUND | MISSING |
|
|
|
|
**Architectural Inconsistency Score: 4/6 (67%) - NEEDS IMPROVEMENT**
|
|
|
|
### 2.2 Plugin System Architecture
|
|
|
|
**Assessment**: EXCELLENT
|
|
|
|
The plugin system demonstrates sophisticated architectural design:
|
|
|
|
**Strengths**:
|
|
1. **Loose Coupling**: Core knows only `ModulePluginInterface`, not concrete implementations
|
|
2. **Service Tagging**: Automatic plugin discovery via `#[TaggedIterator('app.module_plugin')]`
|
|
3. **License-Based Activation**: Proper separation of concerns between licensing and functionality
|
|
4. **Development Mode Support**: Graceful degradation for unlicensed modules in dev environment
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/Plugin/ModuleRegistry.php`
|
|
|
|
**Highlights**:
|
|
```php
|
|
public function bootModules(): void
|
|
{
|
|
$isDev = ($_ENV['APP_ENV'] ?? 'prod') === 'dev';
|
|
|
|
foreach ($this->modules as $identifier => $module) {
|
|
if (!$module->isLicensed()) {
|
|
if (!$isDev) {
|
|
// Production: Skip unlicensed
|
|
continue;
|
|
}
|
|
// Development: Boot with warning
|
|
}
|
|
$module->boot();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Compliance**: Follows Open/Closed Principle (SOLID)
|
|
|
|
### 2.3 Security Architecture Integrity
|
|
|
|
**Six-Layer Security Model** (from CLAUDE.md):
|
|
|
|
| Layer | Component | Status | Assessment |
|
|
|-------|-----------|--------|------------|
|
|
| 1. Authentication | Symfony Form Login + OAuth | OK | Properly implemented |
|
|
| 2. Standard Roles | ROLE_ADMIN, ROLE_USER | OK | User.roles JSON array |
|
|
| 3. Module Permissions | UserRoles → Role → Module | OK | Sophisticated system |
|
|
| 4. Voters | ModuleVoter, ProjectVoter, etc. | PARTIAL | 4 voters exist, InvoiceVoter missing |
|
|
| 5. Event Listeners | ProjectTaskSecurityListener | OK | Pre-persist validation working |
|
|
| 6. Query Extensions | ProjectAccessExtension | OK | Auto-filtering collections |
|
|
|
|
**Critical Issue**: Billing module breaks the security chain by:
|
|
1. No InvoiceVoter to enforce entity-level permissions
|
|
2. No entities to apply security annotations to
|
|
3. Frontend assumes permissions exist (`authStore.hasPermission('billing', 'create')`)
|
|
|
|
**Recommendation**: Before billing module can be production-ready, InvoiceVoter must be created following the established pattern.
|
|
|
|
---
|
|
|
|
## 3. SOLID Principles Adherence
|
|
|
|
### 3.1 Single Responsibility Principle (SRP)
|
|
|
|
**Assessment**: GOOD overall, with exceptions
|
|
|
|
**Positive Examples**:
|
|
1. **ModuleVoter** (`/Users/olli/Git/__privat/myCRM/src/Security/Voter/ModuleVoter.php`):
|
|
- Single purpose: Module-level permission checks
|
|
- Delegates to `User::hasModulePermission()`
|
|
- Clean separation of concerns
|
|
|
|
2. **ModuleRegistry** (`/Users/olli/Git/__privat/myCRM/src/Plugin/ModuleRegistry.php`):
|
|
- Single purpose: Plugin lifecycle management
|
|
- Separate from license validation (uses LicenseValidatorInterface)
|
|
- Clear responsibilities: register, boot, query status
|
|
|
|
**Violations**:
|
|
1. **InvoiceManagement.vue** (lines 166-183):
|
|
```javascript
|
|
const deleteInvoice = async (invoice) => {
|
|
if (confirm(`Rechnung ${invoice.invoiceNumber} wirklich löschen?`)) {
|
|
await fetch(`/api/invoices/${invoice.id}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
window.location.reload() // VIOLATION: Side effect + poor UX
|
|
}
|
|
}
|
|
```
|
|
|
|
**Issue**: Component handles HTTP calls, confirmation dialogs, AND page reload. Should delegate to a service layer.
|
|
|
|
**Recommendation**: Extract API calls to a dedicated service:
|
|
```javascript
|
|
// Create: assets/js/services/invoiceService.js
|
|
export class InvoiceService {
|
|
async delete(invoiceId) {
|
|
return await fetch(`/api/invoices/${invoiceId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 Open/Closed Principle (OCP)
|
|
|
|
**Assessment**: EXCELLENT
|
|
|
|
The plugin system is a textbook example of OCP:
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/Plugin/ModulePluginInterface.php`
|
|
|
|
```php
|
|
interface ModulePluginInterface
|
|
{
|
|
public function getIdentifier(): string;
|
|
public function boot(): void;
|
|
public function getPermissionModules(): array;
|
|
public function getMenuItems(): array;
|
|
// ... 7 methods total
|
|
}
|
|
```
|
|
|
|
**Strength**: New modules can be added WITHOUT modifying core code. System is:
|
|
- **Open for extension**: New plugins implement interface
|
|
- **Closed for modification**: Core ModuleRegistry unchanged
|
|
|
|
**Evidence**: Two plugins already registered:
|
|
- `MyCRM\BillingModule\BillingModulePlugin`
|
|
- `MyCRM\TestModule\TestModulePlugin`
|
|
|
|
### 3.3 Liskov Substitution Principle (LSP)
|
|
|
|
**Assessment**: GOOD
|
|
|
|
**Example**: All voters extend `Symfony\Component\Security\Core\Authorization\Voter\Voter`
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/Security/Voter/ModuleVoter.php`
|
|
|
|
```php
|
|
class ModuleVoter extends Voter
|
|
{
|
|
protected function supports(string $attribute, mixed $subject): bool
|
|
{
|
|
return is_string($subject) || $subject instanceof ModuleAwareInterface;
|
|
}
|
|
|
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
|
{
|
|
// Delegates to User::hasModulePermission()
|
|
}
|
|
}
|
|
```
|
|
|
|
**Analysis**: All voters can be used interchangeably by Symfony's authorization system. Proper LSP compliance.
|
|
|
|
### 3.4 Interface Segregation Principle (ISP)
|
|
|
|
**Assessment**: NEEDS IMPROVEMENT
|
|
|
|
**Violation**: `ModulePluginInterface` has 9 methods
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/Plugin/ModulePluginInterface.php`
|
|
|
|
While comprehensive, this interface forces implementations to handle:
|
|
- Licensing (`isLicensed()`, `getLicenseInfo()`)
|
|
- Lifecycle (`boot()`, `canInstall()`)
|
|
- Metadata (`getIdentifier()`, `getDisplayName()`, `getVersion()`, `getDescription()`)
|
|
- Permissions (`getPermissionModules()`)
|
|
- UI (`getMenuItems()`)
|
|
|
|
**Recommendation**: Split into focused interfaces:
|
|
```php
|
|
interface ModuleMetadataInterface {
|
|
public function getIdentifier(): string;
|
|
public function getDisplayName(): string;
|
|
public function getVersion(): string;
|
|
public function getDescription(): string;
|
|
}
|
|
|
|
interface ModuleLifecycleInterface {
|
|
public function boot(): void;
|
|
public function canInstall(): array;
|
|
}
|
|
|
|
interface ModuleLicensingInterface {
|
|
public function isLicensed(): bool;
|
|
public function getLicenseInfo(): array;
|
|
}
|
|
|
|
interface ModulePermissionsInterface {
|
|
public function getPermissionModules(): array;
|
|
}
|
|
|
|
interface ModuleMenuInterface {
|
|
public function getMenuItems(): array;
|
|
}
|
|
|
|
// Main interface composes all
|
|
interface ModulePluginInterface extends
|
|
ModuleMetadataInterface,
|
|
ModuleLifecycleInterface,
|
|
ModuleLicensingInterface,
|
|
ModulePermissionsInterface,
|
|
ModuleMenuInterface
|
|
{
|
|
}
|
|
```
|
|
|
|
**Benefit**: Allows testing/mocking individual concerns. Plugins can selectively implement only needed interfaces.
|
|
|
|
### 3.5 Dependency Inversion Principle (DIP)
|
|
|
|
**Assessment**: EXCELLENT
|
|
|
|
**Example**: License validation abstraction
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/config/services_plugin.yaml`
|
|
|
|
```yaml
|
|
App\Plugin\LicenseValidatorInterface:
|
|
alias: 'App\Service\GiteaLicenseValidator'
|
|
```
|
|
|
|
**Strength**:
|
|
- Core depends on `LicenseValidatorInterface` abstraction
|
|
- Concrete implementation (`GiteaLicenseValidator`) can be swapped
|
|
- Configured via service container, not hardcoded
|
|
|
|
**Usage** (from BillingModulePlugin):
|
|
```php
|
|
public function __construct(
|
|
private readonly LicenseValidatorInterface $licenseValidator,
|
|
// ...
|
|
) {}
|
|
```
|
|
|
|
**Perfect DIP compliance**: High-level module (BillingModulePlugin) depends on abstraction (interface), not concrete implementation.
|
|
|
|
---
|
|
|
|
## 4. Layering and Dependencies Analysis
|
|
|
|
### 4.1 Backend Architecture
|
|
|
|
**Expected Layer Structure** (from CLAUDE.md):
|
|
```
|
|
Controller (thin) → Service → Repository → Entity
|
|
↓
|
|
Voter (security)
|
|
↓
|
|
ModuleVoter → User::hasModulePermission()
|
|
```
|
|
|
|
**Assessment**: GOOD architectural discipline
|
|
|
|
**Positive Example**: Proper service injection
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/config/services.yaml`
|
|
|
|
```yaml
|
|
services:
|
|
_defaults:
|
|
autowire: true
|
|
autoconfigure: true
|
|
bind:
|
|
$licenseServerUrl: '%env(LICENSE_SERVER_URL)%'
|
|
$giteaBaseUrl: '%env(GITEA_BASE_URL)%'
|
|
```
|
|
|
|
**Strength**: Constructor injection everywhere, no service locator anti-pattern.
|
|
|
|
### 4.2 Circular Dependency Analysis
|
|
|
|
**Scan Results**: NO CIRCULAR DEPENDENCIES DETECTED
|
|
|
|
**Evidence**:
|
|
1. **Voter Dependencies**:
|
|
- ModuleVoter → User entity (composition, not circular)
|
|
- ProjectVoter → Project entity (composition, not circular)
|
|
- ProjectTaskVoter → ProjectTask entity (composition, not circular)
|
|
|
|
2. **Plugin Dependencies**:
|
|
- ModuleRegistry → ModulePluginInterface (interface dependency)
|
|
- BillingModulePlugin → LicenseValidatorInterface (interface dependency)
|
|
- ModuleBootListener → ModuleRegistry (one-way dependency)
|
|
|
|
**Dependency Graph** (simplified):
|
|
```
|
|
ModuleBootListener
|
|
↓
|
|
ModuleRegistry
|
|
↓
|
|
ModulePluginInterface ← BillingModulePlugin
|
|
↓ ↓
|
|
LicenseValidatorInterface (implements)
|
|
↓
|
|
GiteaLicenseValidator
|
|
```
|
|
|
|
Clean, acyclic dependency graph. EXCELLENT.
|
|
|
|
### 4.3 Abstraction Levels
|
|
|
|
**Assessment**: GOOD with minor issues
|
|
|
|
**Appropriate Abstractions**:
|
|
1. `ModuleAwareInterface`: Simple, focused contract
|
|
2. `LicenseValidatorInterface`: Clear abstraction boundary
|
|
3. `ModulePluginInterface`: Comprehensive (see ISP concerns above)
|
|
|
|
**Abstraction Leaks**:
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/views/InvoiceManagement.vue` (line 170)
|
|
|
|
```javascript
|
|
window.location.reload() // Low-level DOM manipulation in high-level component
|
|
```
|
|
|
|
**Issue**: Component directly manipulates browser window instead of using Vue Router navigation or reactive state updates.
|
|
|
|
**Recommendation**:
|
|
```javascript
|
|
// Use router.push() or emit event to parent to refresh data
|
|
const router = useRouter()
|
|
// ... after successful save
|
|
router.push({ name: 'invoices', query: { refresh: Date.now() } })
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Database Schema Consistency
|
|
|
|
### 5.1 Schema Validation
|
|
|
|
**Command**: `php bin/console doctrine:schema:validate --skip-sync`
|
|
|
|
**Result**:
|
|
```
|
|
[OK] The mapping files are correct.
|
|
[SKIPPED] The database was not checked for synchronicity.
|
|
```
|
|
|
|
**Assessment**: GOOD - Doctrine mappings are valid for existing entities.
|
|
|
|
### 5.2 Migration Analysis
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/migrations/Version20251205095156.php`
|
|
|
|
**Critical Issue**: **ORPHANED MIGRATION**
|
|
|
|
Migration creates tables but no corresponding entities exist:
|
|
|
|
```php
|
|
$this->addSql('CREATE TABLE invoices (
|
|
id INT AUTO_INCREMENT NOT NULL,
|
|
contact_id INT NOT NULL,
|
|
invoice_number VARCHAR(50) NOT NULL,
|
|
status VARCHAR(20) NOT NULL,
|
|
// ... more fields
|
|
FOREIGN KEY (contact_id) REFERENCES contacts (id)
|
|
)');
|
|
|
|
$this->addSql('CREATE TABLE invoice_items (...)');
|
|
$this->addSql('CREATE TABLE payments (...)');
|
|
```
|
|
|
|
**Problems**:
|
|
1. If this migration has been run, database has tables with no ORM mapping
|
|
2. If migration hasn't run, it will fail validation when actual Invoice entity is created
|
|
3. Foreign key to `contacts` table exists but Invoice entity doesn't
|
|
|
|
**Recommendation**:
|
|
1. Check if migration has been executed: `php bin/console doctrine:migrations:status`
|
|
2. If executed: Rollback or create matching entities immediately
|
|
3. If not executed: Delete migration and regenerate when Invoice entity is properly created in `src/Entity/`
|
|
|
|
### 5.3 Entity Relationship Validation
|
|
|
|
**Existing Entity Relationships** (confirmed working):
|
|
|
|
1. **Contact ↔ ContactPerson**: One-to-Many with cascade persist/remove
|
|
2. **Project ↔ ProjectTask**: One-to-Many with proper access control
|
|
3. **User ↔ UserRoles ↔ Role**: Many-to-Many for permissions
|
|
4. **Project ↔ User**: Many-to-Many for team members
|
|
|
|
**Missing Relationships** (for billing module):
|
|
|
|
Expected:
|
|
- **Invoice → Contact**: Many-to-One (ForeignKey exists in migration)
|
|
- **Invoice → InvoiceItem**: One-to-Many with cascade
|
|
- **Invoice → Payment**: One-to-Many with cascade
|
|
|
|
**Status**: Relationships defined in example Invoice entity but not in production codebase.
|
|
|
|
---
|
|
|
|
## 6. Frontend Architecture Review
|
|
|
|
### 6.1 Component Structure Analysis
|
|
|
|
**Pattern Assessment**: GOOD - Composition API used consistently
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/views/InvoiceForm.vue`
|
|
|
|
**Strengths**:
|
|
1. Composition API with `<script setup>`
|
|
2. Proper use of `defineProps`, `defineEmits`
|
|
3. Reactive state with `ref()`
|
|
4. Lifecycle hooks (`onMounted`) used appropriately
|
|
|
|
**Issues**:
|
|
|
|
1. **Direct API Calls in Components** (SRP violation):
|
|
```javascript
|
|
const response = await fetch('/api/invoices', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/ld+json' },
|
|
body: JSON.stringify(payload)
|
|
})
|
|
```
|
|
|
|
**Problem**: Components should not handle HTTP details.
|
|
|
|
**Recommendation**: Create API service layer:
|
|
```javascript
|
|
// assets/js/services/api/invoiceApi.js
|
|
export const invoiceApi = {
|
|
async create(invoiceData) {
|
|
return await apiClient.post('/api/invoices', invoiceData)
|
|
},
|
|
async update(id, invoiceData) {
|
|
return await apiClient.put(`/api/invoices/${id}`, invoiceData)
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Error Handling**:
|
|
```javascript
|
|
} catch (error) {
|
|
console.error('Error saving invoice:', error) // Console pollution
|
|
toast.add({ /* ... */ })
|
|
}
|
|
```
|
|
|
|
**Problem**: Console errors in production. Should use proper logging service.
|
|
|
|
3. **Window Reload Anti-pattern**:
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/views/InvoiceManagement.vue` (line 170)
|
|
|
|
```javascript
|
|
window.location.reload() // Forces full page reload
|
|
```
|
|
|
|
**Problem**: Defeats SPA purpose, loses state, poor UX.
|
|
|
|
### 6.2 Router Configuration
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/router.js`
|
|
|
|
**Assessment**: GOOD with missing guard implementation
|
|
|
|
```javascript
|
|
{
|
|
path: '/billing/invoices',
|
|
name: 'invoices',
|
|
component: InvoiceManagement,
|
|
meta: { requiresPermission: { module: 'billing', action: 'view' } }
|
|
}
|
|
```
|
|
|
|
**Issue**: `requiresPermission` meta defined but no navigation guard implementation visible.
|
|
|
|
**Expected**: Navigation guard should check permissions before route activation:
|
|
```javascript
|
|
router.beforeEach((to, from, next) => {
|
|
if (to.meta.requiresPermission) {
|
|
const { module, action } = to.meta.requiresPermission
|
|
if (!authStore.hasPermission(module, action)) {
|
|
next({ name: 'dashboard' }) // Redirect if no permission
|
|
return
|
|
}
|
|
}
|
|
next()
|
|
})
|
|
```
|
|
|
|
**Recommendation**: Verify navigation guard exists or implement it.
|
|
|
|
### 6.3 State Management
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/stores/auth.js`
|
|
|
|
**Assessment**: Pinia store used for authentication
|
|
|
|
**Usage**: `authStore.hasPermission('billing', 'create')`
|
|
|
|
**Question**: Is permission check reactive? Does it refresh when permissions change?
|
|
|
|
**Recommendation**: Ensure auth store properly syncs with backend permission changes.
|
|
|
|
---
|
|
|
|
## 7. Code Quality and Maintainability
|
|
|
|
### 7.1 Code Convention Adherence
|
|
|
|
**Backend (PHP)**:
|
|
|
|
**Conventions from CLAUDE.md**:
|
|
- ✅ Constructor injection used throughout
|
|
- ✅ Doctrine attributes (not annotations)
|
|
- ✅ ApiResource security attributes
|
|
- ✅ Voter pattern followed
|
|
- ✅ Service layer pattern visible
|
|
|
|
**Example** (ModuleVoter):
|
|
```php
|
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
|
{
|
|
$user = $token->getUser();
|
|
|
|
if (!$user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
if (!$user->isActive()) { // Early returns, clean code
|
|
return false;
|
|
}
|
|
|
|
// ... rest of logic
|
|
}
|
|
```
|
|
|
|
**Quality**: HIGH - Clean, readable, follows PSR standards.
|
|
|
|
**Frontend (Vue.js)**:
|
|
|
|
**Conventions from CLAUDE.md**:
|
|
- ✅ Composition API used
|
|
- ✅ PrimeVue components
|
|
- ✅ Single File Components
|
|
- ⚠️ Service layer missing (direct fetch calls)
|
|
|
|
**Quality**: MEDIUM - Needs refactoring for service layer.
|
|
|
|
### 7.2 Documentation
|
|
|
|
**Assessment**: EXCELLENT documentation, POOR implementation alignment
|
|
|
|
**Documentation Files**:
|
|
- `/Users/olli/Git/__privat/myCRM/CLAUDE.md` (1,156 lines) - Comprehensive
|
|
- `/Users/olli/Git/__privat/myCRM/docs/PLUGIN_SYSTEM.md` - Detailed plugin architecture
|
|
- `/Users/olli/Git/__privat/myCRM/docs/EXAMPLE_MODULE_STRUCTURE.md` - Module template
|
|
|
|
**Problem**: Billing module documentation exists but implementation doesn't follow through.
|
|
|
|
**Gap**: README missing for actual billing module. Only example documentation exists.
|
|
|
|
### 7.3 Technical Debt Assessment
|
|
|
|
**High Priority Debt**:
|
|
|
|
1. **Billing Module Implementation Gap** (CRITICAL)
|
|
- Estimated effort: 16-24 hours
|
|
- Risk: High - Frontend expects backend that doesn't exist
|
|
|
|
2. **Missing InvoiceVoter** (HIGH)
|
|
- Estimated effort: 2-4 hours
|
|
- Risk: High - Security gap
|
|
|
|
3. **Frontend Service Layer** (MEDIUM)
|
|
- Estimated effort: 8-12 hours
|
|
- Risk: Medium - Maintainability concern
|
|
|
|
4. **Menu Item Integration** (LOW)
|
|
- Estimated effort: 1-2 hours
|
|
- Risk: Low - UX issue
|
|
|
|
**Medium Priority Debt**:
|
|
|
|
1. **Interface Segregation** (ModulePluginInterface)
|
|
- Estimated effort: 4-6 hours
|
|
- Risk: Medium - Future maintainability
|
|
|
|
2. **Navigation Guard Implementation**
|
|
- Estimated effort: 2-3 hours
|
|
- Risk: Medium - Security UX
|
|
|
|
**Low Priority Debt**:
|
|
|
|
1. **Console.error usage in production**
|
|
- Estimated effort: 2-4 hours
|
|
- Risk: Low - Performance/privacy
|
|
|
|
---
|
|
|
|
## 8. Security Boundaries and Validation
|
|
|
|
### 8.1 API Security Configuration
|
|
|
|
**Assessment**: GOOD for existing entities, MISSING for billing
|
|
|
|
**Example** (Contact entity):
|
|
```php
|
|
#[ApiResource(
|
|
operations: [
|
|
new GetCollection(security: "is_granted('VIEW', 'contacts')"),
|
|
new Get(security: "is_granted('VIEW', object)"),
|
|
new Post(security: "is_granted('CREATE', 'contacts')"),
|
|
new Put(security: "is_granted('EDIT', object)"),
|
|
new Delete(security: "is_granted('DELETE', object)")
|
|
]
|
|
)]
|
|
```
|
|
|
|
**Strength**: Every operation secured with voter-based authorization.
|
|
|
|
**Pattern**: Consistent across all entities (Contact, Project, ProjectTask)
|
|
|
|
**Missing**: Invoice entity with similar security configuration
|
|
|
|
### 8.2 Input Validation
|
|
|
|
**Backend Validation** (Example from Contact):
|
|
```php
|
|
#[Assert\NotBlank(message: 'Der Firmenname darf nicht leer sein')]
|
|
#[Assert\Length(max: 255)]
|
|
private ?string $companyName = null;
|
|
|
|
#[Assert\Email(message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein')]
|
|
private ?string $email = null;
|
|
```
|
|
|
|
**Assessment**: GOOD - Symfony Validator constraints used properly.
|
|
|
|
**Frontend Validation** (InvoiceForm.vue):
|
|
```javascript
|
|
if (!form.value.invoiceNumber || !form.value.contactId) {
|
|
toast.add({
|
|
severity: 'warn',
|
|
summary: 'Validierung fehlgeschlagen',
|
|
detail: 'Bitte füllen Sie alle Pflichtfelder aus'
|
|
})
|
|
return
|
|
}
|
|
```
|
|
|
|
**Assessment**: BASIC - Client-side validation exists but not comprehensive.
|
|
|
|
**Recommendation**: Add schema validation library (e.g., Vuelidate, Zod) for consistent validation rules.
|
|
|
|
### 8.3 Authorization Flow
|
|
|
|
**Current Flow**:
|
|
```
|
|
1. Frontend: authStore.hasPermission('billing', 'create')
|
|
2. API Request: POST /api/invoices
|
|
3. API Platform: security: "is_granted('CREATE', 'billing')"
|
|
4. Symfony Security: Calls voters
|
|
5. InvoiceVoter (MISSING) → ModuleVoter fallback?
|
|
6. ModuleVoter: User::hasModulePermission('billing', 'create')
|
|
7. User entity: Checks UserRoles → RolePermissions → Module
|
|
```
|
|
|
|
**Gap**: Step 5 - InvoiceVoter missing means authorization might fail or fall through incorrectly.
|
|
|
|
**Expected Voter**:
|
|
```php
|
|
class InvoiceVoter extends Voter
|
|
{
|
|
protected function supports(string $attribute, mixed $subject): bool
|
|
{
|
|
return in_array($attribute, ['VIEW', 'EDIT', 'DELETE'])
|
|
&& ($subject instanceof Invoice || $subject === 'billing');
|
|
}
|
|
|
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
|
{
|
|
$user = $token->getUser();
|
|
if (!$user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
// Map to module permission
|
|
$action = strtolower($attribute);
|
|
return $user->hasModulePermission('billing', $action);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Scalability and Performance Implications
|
|
|
|
### 9.1 Plugin Boot Performance
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/src/EventListener/ModuleBootListener.php`
|
|
|
|
**Concern**: Plugins boot on EVERY request (priority 256 on kernel.request)
|
|
|
|
```php
|
|
#[AsEventListener(event: KernelEvents::REQUEST, priority: 256)]
|
|
class ModuleBootListener
|
|
{
|
|
private static bool $booted = false;
|
|
|
|
public function __invoke(RequestEvent $event): void
|
|
{
|
|
if (!$event->isMainRequest() || self::$booted) {
|
|
return; // Guard prevents re-booting
|
|
}
|
|
|
|
$this->moduleRegistry->bootModules();
|
|
self::$booted = true;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Assessment**: ACCEPTABLE
|
|
- Static flag prevents multiple boots
|
|
- Only main requests trigger boot
|
|
- Short-circuits early if already booted
|
|
|
|
**Potential Issue**: On high-traffic sites, even single boot per request might be expensive if plugins have heavy initialization.
|
|
|
|
**Recommendation**: Consider moving plugin boot to application boot phase (kernel.boot event) instead of per-request.
|
|
|
|
### 9.2 License Validation Caching
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/docs/example-module/BillingModulePlugin.php`
|
|
|
|
```php
|
|
private ?array $licenseCache = null;
|
|
|
|
public function getLicenseInfo(): array
|
|
{
|
|
if ($this->licenseCache !== null) {
|
|
return $this->licenseCache; // In-memory cache
|
|
}
|
|
|
|
$this->licenseCache = $this->licenseValidator->validate(self::MODULE_IDENTIFIER);
|
|
return $this->licenseCache;
|
|
}
|
|
```
|
|
|
|
**Assessment**: BASIC caching only per request
|
|
|
|
**Concern**: License validation might make HTTP calls to license server on every request (depending on LicenseValidator implementation).
|
|
|
|
**Recommendation**: Implement persistent caching:
|
|
```php
|
|
// Use Symfony Cache
|
|
public function getLicenseInfo(): array
|
|
{
|
|
return $this->cache->get(
|
|
'module.license.' . $this->getIdentifier(),
|
|
function() {
|
|
return $this->licenseValidator->validate($this->getIdentifier());
|
|
},
|
|
3600 // 1 hour TTL
|
|
);
|
|
}
|
|
```
|
|
|
|
### 9.3 Database Query Optimization
|
|
|
|
**Assessment**: No N+1 query issues detected in existing code
|
|
|
|
**Evidence**: Entities use proper eager/lazy loading:
|
|
```php
|
|
#[ORM\OneToMany(
|
|
targetEntity: ContactPerson::class,
|
|
mappedBy: 'contact',
|
|
cascade: ['persist', 'remove'],
|
|
orphanRemoval: true
|
|
)]
|
|
private Collection $contactPersons;
|
|
```
|
|
|
|
**API Platform**: Uses automatic pagination
|
|
```php
|
|
#[ApiResource(
|
|
paginationClientItemsPerPage: true,
|
|
paginationItemsPerPage: 30,
|
|
paginationMaximumItemsPerPage: 5000
|
|
)]
|
|
```
|
|
|
|
**Strength**: Prevents unbounded result sets.
|
|
|
|
---
|
|
|
|
## 10. Improvement Recommendations
|
|
|
|
### 10.1 CRITICAL PRIORITY (Fix Immediately)
|
|
|
|
**1. Complete Billing Module Implementation**
|
|
|
|
**Current State**: Only example code in `/docs/example-module/`
|
|
|
|
**Required Actions**:
|
|
|
|
**Step 1**: Create production entities
|
|
```bash
|
|
# Location: /Users/olli/Git/__privat/myCRM/src/Entity/Invoice.php
|
|
|
|
<?php
|
|
namespace App\Entity;
|
|
|
|
use ApiPlatform\Metadata\ApiResource;
|
|
use App\Entity\Interface\ModuleAwareInterface;
|
|
// ... implement based on docs/example-module/Entity/Invoice.php
|
|
// BUT: use namespace App\Entity, not MyCRM\BillingModule\Entity
|
|
```
|
|
|
|
**Step 2**: Create InvoiceVoter
|
|
```bash
|
|
# Location: /Users/olli/Git/__privat/myCRM/src/Security/Voter/InvoiceVoter.php
|
|
```
|
|
|
|
**Step 3**: Verify migration or regenerate
|
|
```bash
|
|
php bin/console doctrine:migrations:status
|
|
# If Version20251205095156 already executed, verify entities match
|
|
# If not executed, delete and regenerate:
|
|
rm migrations/Version20251205095156.php
|
|
php bin/console make:migration
|
|
```
|
|
|
|
**Step 4**: Add menu item
|
|
```bash
|
|
# Location: /Users/olli/Git/__privat/myCRM/assets/js/layout/AppMenu.vue
|
|
# Add billing menu entry
|
|
```
|
|
|
|
**Estimated Effort**: 20-24 hours
|
|
**Risk if not fixed**: Application is broken - frontend calls non-existent API
|
|
|
|
---
|
|
|
|
**2. Resolve Entity/Migration Mismatch**
|
|
|
|
**Issue**: Migration exists for invoice tables but no entities
|
|
|
|
**Action**:
|
|
```bash
|
|
cd /Users/olli/Git/__privat/myCRM
|
|
php bin/console doctrine:migrations:status
|
|
|
|
# If migration executed:
|
|
# Option A: Create matching entities (recommended)
|
|
# Option B: Rollback migration
|
|
php bin/console doctrine:migrations:migrate prev
|
|
|
|
# If migration not executed:
|
|
# Delete orphaned migration
|
|
rm migrations/Version20251205095156.php
|
|
```
|
|
|
|
**Estimated Effort**: 2 hours
|
|
**Risk if not fixed**: Database inconsistency, schema validation failures
|
|
|
|
---
|
|
|
|
### 10.2 HIGH PRIORITY (Fix This Sprint)
|
|
|
|
**3. Implement Frontend Service Layer**
|
|
|
|
**Current Problem**: Direct fetch calls in Vue components
|
|
|
|
**Solution**: Create API service abstraction
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/services/api/client.js`
|
|
```javascript
|
|
class ApiClient {
|
|
constructor(baseURL = '/api') {
|
|
this.baseURL = baseURL
|
|
}
|
|
|
|
async request(endpoint, options = {}) {
|
|
const url = `${this.baseURL}${endpoint}`
|
|
const headers = {
|
|
'Content-Type': 'application/ld+json',
|
|
'Accept': 'application/ld+json',
|
|
...options.headers
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers,
|
|
credentials: 'include'
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json()
|
|
throw new ApiError(error)
|
|
}
|
|
|
|
return response.json()
|
|
}
|
|
|
|
get(endpoint) {
|
|
return this.request(endpoint, { method: 'GET' })
|
|
}
|
|
|
|
post(endpoint, data) {
|
|
return this.request(endpoint, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
})
|
|
}
|
|
}
|
|
|
|
export const apiClient = new ApiClient()
|
|
```
|
|
|
|
**Then refactor components**:
|
|
```javascript
|
|
// InvoiceForm.vue
|
|
import { apiClient } from '@/services/api/client'
|
|
|
|
const save = async () => {
|
|
try {
|
|
const invoice = await apiClient.post('/invoices', payload)
|
|
emit('save', invoice)
|
|
} catch (error) {
|
|
// Handle error
|
|
}
|
|
}
|
|
```
|
|
|
|
**Estimated Effort**: 8-12 hours
|
|
**Benefit**: Better maintainability, testability, error handling
|
|
|
|
---
|
|
|
|
**4. Add Navigation Guards**
|
|
|
|
**File**: `/Users/olli/Git/__privat/myCRM/assets/js/router.js`
|
|
|
|
```javascript
|
|
import { useAuthStore } from './stores/auth'
|
|
|
|
router.beforeEach(async (to, from, next) => {
|
|
const authStore = useAuthStore()
|
|
|
|
// Check authentication
|
|
if (!authStore.isAuthenticated) {
|
|
// Redirect to login or handle unauthenticated access
|
|
}
|
|
|
|
// Check admin requirement
|
|
if (to.meta.requiresAdmin && !authStore.isAdmin) {
|
|
next({ name: 'dashboard' })
|
|
return
|
|
}
|
|
|
|
// Check module permission
|
|
if (to.meta.requiresPermission) {
|
|
const { module, action } = to.meta.requiresPermission
|
|
if (!authStore.hasPermission(module, action)) {
|
|
next({ name: 'dashboard' })
|
|
return
|
|
}
|
|
}
|
|
|
|
next()
|
|
})
|
|
```
|
|
|
|
**Estimated Effort**: 3-4 hours
|
|
**Benefit**: Proper authorization UX, prevents unauthorized route access
|
|
|
|
---
|
|
|
|
### 10.3 MEDIUM PRIORITY (Next Sprint)
|
|
|
|
**5. Refactor ModulePluginInterface for ISP**
|
|
|
|
**Current**: Single interface with 9 methods
|
|
|
|
**Proposed**: Split into focused interfaces (see section 3.4)
|
|
|
|
**Estimated Effort**: 6-8 hours (includes updating implementations)
|
|
**Benefit**: Better testability, cleaner abstractions
|
|
|
|
---
|
|
|
|
**6. Implement Persistent License Caching**
|
|
|
|
**Current**: In-memory cache only
|
|
|
|
**Proposed**: Symfony Cache component with TTL
|
|
|
|
**Estimated Effort**: 4 hours
|
|
**Benefit**: Reduced license server load, faster plugin boot
|
|
|
|
---
|
|
|
|
**7. Replace window.location.reload() with Reactive Updates**
|
|
|
|
**Files**: InvoiceManagement.vue, other components
|
|
|
|
**Replace**:
|
|
```javascript
|
|
window.location.reload() // BAD
|
|
```
|
|
|
|
**With**:
|
|
```javascript
|
|
// Use emit to parent or composable for data refresh
|
|
const refresh = inject('refreshData')
|
|
refresh()
|
|
|
|
// Or update local state reactively
|
|
items.value = await fetchUpdatedItems()
|
|
```
|
|
|
|
**Estimated Effort**: 6 hours
|
|
**Benefit**: Better UX, maintains SPA state
|
|
|
|
---
|
|
|
|
### 10.4 LOW PRIORITY (Backlog)
|
|
|
|
**8. Add Comprehensive Frontend Validation**
|
|
|
|
**Tool**: Vuelidate or Zod
|
|
|
|
**Example**:
|
|
```javascript
|
|
import { useVuelidate } from '@vuelidate/core'
|
|
import { required, email, minLength } from '@vuelidate/validators'
|
|
|
|
const rules = {
|
|
invoiceNumber: { required, minLength: minLength(5) },
|
|
contactId: { required },
|
|
items: {
|
|
$each: {
|
|
description: { required },
|
|
quantity: { required, minValue: minValue(0) }
|
|
}
|
|
}
|
|
}
|
|
|
|
const v$ = useVuelidate(rules, form)
|
|
```
|
|
|
|
**Estimated Effort**: 8 hours
|
|
**Benefit**: Consistent validation, better UX
|
|
|
|
---
|
|
|
|
**9. Remove console.error in Production**
|
|
|
|
**Replace**:
|
|
```javascript
|
|
console.error('Error:', error) // REMOVE
|
|
```
|
|
|
|
**With**:
|
|
```javascript
|
|
import { logger } from '@/services/logger'
|
|
logger.error('Error saving invoice', { error, context })
|
|
```
|
|
|
|
**Estimated Effort**: 4 hours
|
|
**Benefit**: Cleaner production builds, proper error tracking
|
|
|
|
---
|
|
|
|
**10. Add API Response Interceptor**
|
|
|
|
**Purpose**: Centralized error handling, token refresh
|
|
|
|
**Implementation**:
|
|
```javascript
|
|
class ApiClient {
|
|
constructor() {
|
|
this.interceptors = {
|
|
response: [],
|
|
error: []
|
|
}
|
|
}
|
|
|
|
async request(endpoint, options) {
|
|
try {
|
|
const response = await fetch(...)
|
|
|
|
// Run response interceptors
|
|
for (const interceptor of this.interceptors.response) {
|
|
await interceptor(response)
|
|
}
|
|
|
|
return response
|
|
} catch (error) {
|
|
// Run error interceptors
|
|
for (const interceptor of this.interceptors.error) {
|
|
await interceptor(error)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register global error handler
|
|
apiClient.interceptors.error.push(async (error) => {
|
|
if (error.status === 401) {
|
|
// Handle unauthorized
|
|
router.push('/login')
|
|
}
|
|
})
|
|
```
|
|
|
|
**Estimated Effort**: 6 hours
|
|
**Benefit**: Centralized error handling, token management
|
|
|
|
---
|
|
|
|
## 11. Risk Assessment
|
|
|
|
### 11.1 Architectural Risks
|
|
|
|
| Risk | Severity | Likelihood | Impact | Mitigation |
|
|
|------|----------|------------|--------|------------|
|
|
| Billing module implementation gap | CRITICAL | High | Users cannot create invoices | Complete implementation (Rec #1) |
|
|
| Missing InvoiceVoter security gap | HIGH | Medium | Unauthorized access possible | Create InvoiceVoter (Rec #1) |
|
|
| Entity/migration mismatch | HIGH | High | Database schema drift | Resolve mismatch (Rec #2) |
|
|
| License validation performance | MEDIUM | Medium | Slow plugin boot | Implement caching (Rec #6) |
|
|
| Frontend service layer missing | MEDIUM | Low | Difficult maintenance | Create service layer (Rec #3) |
|
|
|
|
### 11.2 Technical Debt Heatmap
|
|
|
|
```
|
|
│ Impact
|
|
High │ ██ Billing Gap
|
|
│ █ InvoiceVoter
|
|
│ █ Entity/Migration
|
|
Med │ ██ Service Layer
|
|
│ █ License Cache
|
|
Low │ █ Console errors
|
|
└────────────────────────────
|
|
Low Med High
|
|
Effort
|
|
```
|
|
|
|
**Legend**:
|
|
- █ = 1 issue
|
|
- ██ = 2+ issues
|
|
|
|
---
|
|
|
|
## 12. Conclusion and Action Plan
|
|
|
|
### 12.1 Summary
|
|
|
|
**Strengths**:
|
|
1. Excellent plugin architecture with proper abstraction
|
|
2. Sophisticated 6-layer security system
|
|
3. Clean SOLID adherence in core modules
|
|
4. Good documentation
|
|
5. Consistent coding patterns
|
|
|
|
**Critical Gaps**:
|
|
1. Billing module exists only as documentation
|
|
2. Frontend expects backend API that doesn't exist
|
|
3. Database migration without corresponding entities
|
|
4. Missing security voter for invoices
|
|
|
|
**Overall Grade**: B- (Good foundation, critical implementation gaps)
|
|
|
|
### 12.2 Immediate Action Plan
|
|
|
|
**Week 1** (Critical Fixes):
|
|
1. **Day 1-2**: Create Invoice, InvoiceItem, Payment entities in `src/Entity/`
|
|
2. **Day 3**: Implement InvoiceVoter
|
|
3. **Day 4**: Resolve migration mismatch, test database schema
|
|
4. **Day 5**: Add billing menu item, test end-to-end flow
|
|
|
|
**Week 2** (High Priority):
|
|
5. **Day 1-2**: Implement frontend service layer
|
|
6. **Day 3**: Add navigation guards
|
|
7. **Day 4-5**: Integration testing, bug fixes
|
|
|
|
**Week 3+** (Medium/Low Priority):
|
|
8. Refactor ModulePluginInterface for ISP
|
|
9. Implement persistent license caching
|
|
10. Replace window.reload with reactive updates
|
|
|
|
### 12.3 Long-term Recommendations
|
|
|
|
1. **Establish Architecture Decision Records (ADR)**: Document why architectural choices were made
|
|
2. **Automated Architecture Tests**: Use ArchUnit-style tests to enforce layering rules
|
|
3. **CI/CD Integration**: Add schema validation to CI pipeline
|
|
4. **Performance Monitoring**: Track plugin boot times, license validation latency
|
|
5. **Security Audits**: Regular review of voter implementations and API security
|
|
|
|
---
|
|
|
|
## Appendix A: File Inventory
|
|
|
|
### Backend (PHP)
|
|
|
|
**Entities** (13 files):
|
|
- `/Users/olli/Git/__privat/myCRM/src/Entity/Contact.php` (422 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/src/Entity/User.php`
|
|
- `/Users/olli/Git/__privat/myCRM/src/Entity/Project.php`
|
|
- `/Users/olli/Git/__privat/myCRM/src/Entity/ProjectTask.php`
|
|
- ... (full list in review)
|
|
|
|
**Voters** (4 files):
|
|
- `/Users/olli/Git/__privat/myCRM/src/Security/Voter/ModuleVoter.php` (79 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/src/Security/Voter/ProjectVoter.php`
|
|
- `/Users/olli/Git/__privat/myCRM/src/Security/Voter/ProjectTaskVoter.php`
|
|
- `/Users/olli/Git/__privat/myCRM/src/Security/Voter/GitRepositoryVoter.php`
|
|
|
|
**Plugin System**:
|
|
- `/Users/olli/Git/__privat/myCRM/src/Plugin/ModulePluginInterface.php` (103 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/src/Plugin/ModuleRegistry.php` (215 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/src/EventListener/ModuleBootListener.php` (38 lines)
|
|
|
|
**Example Module**:
|
|
- `/Users/olli/Git/__privat/myCRM/docs/example-module/BillingModulePlugin.php` (180 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/docs/example-module/Entity/Invoice.php` (229 lines)
|
|
|
|
### Frontend (Vue)
|
|
|
|
**Components**:
|
|
- `/Users/olli/Git/__privat/myCRM/assets/js/views/InvoiceManagement.vue` (259 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/assets/js/views/InvoiceForm.vue` (314 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/assets/js/views/PaymentForm.vue`
|
|
- `/Users/olli/Git/__privat/myCRM/assets/js/views/PDFUploadForm.vue`
|
|
|
|
**Router**:
|
|
- `/Users/olli/Git/__privat/myCRM/assets/js/router.js` (31 lines)
|
|
|
|
### Configuration
|
|
|
|
- `/Users/olli/Git/__privat/myCRM/config/services.yaml` (63 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/config/services_plugin.yaml` (32 lines)
|
|
- `/Users/olli/Git/__privat/myCRM/config/packages/billing_module.yaml` (7 lines)
|
|
|
|
### Migrations
|
|
|
|
- `/Users/olli/Git/__privat/myCRM/migrations/Version20251205095156.php` (42 lines) - **ORPHANED**
|
|
|
|
---
|
|
|
|
## Appendix B: Command Reference
|
|
|
|
### Architecture Validation Commands
|
|
|
|
```bash
|
|
# Doctrine schema validation
|
|
php bin/console doctrine:schema:validate
|
|
|
|
# List registered plugins
|
|
php bin/console debug:container --tag=app.module_plugin
|
|
|
|
# Check migrations status
|
|
php bin/console doctrine:migrations:status
|
|
|
|
# List all routes
|
|
php bin/console debug:router
|
|
|
|
# Check security voters
|
|
php bin/console debug:autowiring Voter
|
|
```
|
|
|
|
### Recommended Test Commands
|
|
|
|
```bash
|
|
# Before implementing billing module entities
|
|
php bin/console doctrine:schema:validate --skip-sync
|
|
|
|
# After creating entities
|
|
php bin/console make:migration
|
|
php bin/console doctrine:migrations:migrate
|
|
php bin/console doctrine:schema:validate
|
|
|
|
# Test API endpoints
|
|
curl -X GET http://localhost:8000/api/invoices -H "Accept: application/ld+json"
|
|
```
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Review Date**: December 12, 2025
|
|
**Reviewer**: Claude Sonnet 4.5 (Architecture Review Agent)
|
|
**Next Review**: After critical fixes implementation (2-3 weeks)
|