- Added a file input for PDF uploads with validation for file type and size. - Implemented file size formatting and user feedback for selected files. - Created upload and cancel methods with placeholder for future API integration. feat: Create PaymentForm.vue for handling payments - Developed a form for entering payment details including date, amount, method, and notes. - Integrated currency formatting and dropdown for payment methods. - Implemented save and cancel actions with API call for saving payment data. docs: Add documentation for dynamic plugin menus and permissions - Provided guidelines for defining menu items and permissions in plugins. - Explained the process for synchronizing permissions and integrating menus in the frontend. - Included examples and best practices for plugin development. feat: Add database migrations for invoices, invoice items, and payments - Created migration scripts to define the database schema for invoices, invoice items, and payments. - Established foreign key relationships between invoices and related entities. feat: Implement command for synchronizing plugin permissions - Developed a console command to synchronize plugin permissions with the database. - Added options for dry-run and force synchronization for unlicensed modules. feat: Create API controller for plugin menu items - Implemented API endpoints to retrieve plugin menu items in both flat and grouped formats. - Ensured access control with role-based permissions for API access. feat: Develop service for managing plugin menu items - Created a service to collect and manage menu items from installed plugins. - Implemented methods for retrieving flat and grouped menu items for frontend use. feat: Add service for synchronizing plugin permissions - Developed a service to handle the synchronization of plugin permissions with the database. - Included logic for creating and updating permission modules based on plugin definitions.
188 lines
5.6 KiB
PHP
188 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Entity\Module;
|
|
use App\Plugin\ModuleRegistry;
|
|
use App\Repository\ModuleRepository;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Service zur Synchronisation von Plugin-Permissions mit der Datenbank
|
|
*
|
|
* Liest alle Permission-Module von Plugins und legt sie in der DB an,
|
|
* falls sie noch nicht existieren.
|
|
*/
|
|
class PermissionModuleSync
|
|
{
|
|
public function __construct(
|
|
private readonly ModuleRegistry $moduleRegistry,
|
|
private readonly ModuleRepository $moduleRepository,
|
|
private readonly EntityManagerInterface $em,
|
|
private readonly LoggerInterface $logger
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Synchronisiert alle Plugin-Module mit der Datenbank
|
|
*
|
|
* @param bool $force Synchronisiert auch unlizenzierte Module (für Development)
|
|
* @return array{created: int, updated: int, skipped: int, errors: array<string>}
|
|
*/
|
|
public function syncAll(bool $force = false): array
|
|
{
|
|
$stats = [
|
|
'created' => 0,
|
|
'updated' => 0,
|
|
'skipped' => 0,
|
|
'errors' => [],
|
|
];
|
|
|
|
foreach ($this->moduleRegistry->getAllModules() as $plugin) {
|
|
try {
|
|
$result = $this->syncPlugin($plugin->getIdentifier(), $force);
|
|
$stats['created'] += $result['created'];
|
|
$stats['updated'] += $result['updated'];
|
|
$stats['skipped'] += $result['skipped'];
|
|
} catch (\Throwable $e) {
|
|
$error = sprintf(
|
|
'Plugin "%s": %s',
|
|
$plugin->getIdentifier(),
|
|
$e->getMessage()
|
|
);
|
|
$stats['errors'][] = $error;
|
|
$this->logger->error($error);
|
|
}
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Synchronisiert die Module eines bestimmten Plugins
|
|
*
|
|
* @param string $pluginIdentifier
|
|
* @param bool $force Synchronisiert auch unlizenzierte Module (für Development)
|
|
* @return array{created: int, updated: int, skipped: int}
|
|
*/
|
|
public function syncPlugin(string $pluginIdentifier, bool $force = false): array
|
|
{
|
|
$stats = ['created' => 0, 'updated' => 0, 'skipped' => 0];
|
|
|
|
$plugin = $this->moduleRegistry->getModule($pluginIdentifier);
|
|
if (!$plugin) {
|
|
throw new \RuntimeException(sprintf('Plugin "%s" nicht gefunden', $pluginIdentifier));
|
|
}
|
|
|
|
// Nur lizenzierte Module synchronisieren (außer force)
|
|
if (!$force && !$plugin->isLicensed()) {
|
|
$this->logger->info(sprintf(
|
|
'Plugin "%s" übersprungen: Nicht lizenziert',
|
|
$pluginIdentifier
|
|
));
|
|
return $stats;
|
|
}
|
|
|
|
$permissionModules = $plugin->getPermissionModules();
|
|
|
|
foreach ($permissionModules as $moduleCode) {
|
|
$result = $this->syncModule(
|
|
$moduleCode,
|
|
$plugin->getDisplayName(),
|
|
$plugin->getDescription()
|
|
);
|
|
|
|
$stats[$result]++;
|
|
}
|
|
|
|
$this->em->flush();
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Synchronisiert ein einzelnes Modul
|
|
*
|
|
* @param string $code Modul-Code (z.B. 'billing', 'invoicing')
|
|
* @param string $displayName Display-Name des Plugins
|
|
* @param string $description Plugin-Beschreibung
|
|
* @return string 'created', 'updated' oder 'skipped'
|
|
*/
|
|
private function syncModule(string $code, string $displayName, string $description): string
|
|
{
|
|
$existingModule = $this->moduleRepository->findOneBy(['code' => $code]);
|
|
|
|
if ($existingModule) {
|
|
// Modul existiert bereits - Update nur wenn nötig
|
|
$updated = false;
|
|
|
|
if ($existingModule->getDescription() !== $description) {
|
|
$existingModule->setDescription($description);
|
|
$updated = true;
|
|
}
|
|
|
|
if ($updated) {
|
|
$this->logger->info(sprintf('Modul "%s" aktualisiert', $code));
|
|
return 'updated';
|
|
}
|
|
|
|
return 'skipped';
|
|
}
|
|
|
|
// Neues Modul anlegen
|
|
$module = new Module();
|
|
$module->setCode($code);
|
|
// Name: Plugin-Name + Code (falls unterschiedlich)
|
|
$moduleName = $displayName;
|
|
if (strcasecmp($code, $displayName) !== 0) {
|
|
$moduleName = sprintf('%s (%s)', $displayName, ucfirst($code));
|
|
}
|
|
$module->setName($moduleName);
|
|
$module->setDescription($description);
|
|
$module->setIsActive(true);
|
|
$module->setSortOrder($this->getNextSortOrder());
|
|
|
|
// Icon aus Code ableiten (optional)
|
|
$icon = $this->guessIcon($code);
|
|
if ($icon) {
|
|
$module->setIcon($icon);
|
|
}
|
|
|
|
$this->em->persist($module);
|
|
|
|
$this->logger->info(sprintf('Modul "%s" erstellt', $code));
|
|
|
|
return 'created';
|
|
}
|
|
|
|
/**
|
|
* Ermittelt die nächste Sort-Order
|
|
*/
|
|
private function getNextSortOrder(): int
|
|
{
|
|
$maxSortOrder = $this->em->createQuery(
|
|
'SELECT MAX(m.sortOrder) FROM App\Entity\Module m'
|
|
)->getSingleScalarResult();
|
|
|
|
return ($maxSortOrder ?? 0) + 10;
|
|
}
|
|
|
|
/**
|
|
* Versucht, ein passendes Icon zu raten
|
|
*/
|
|
private function guessIcon(string $code): ?string
|
|
{
|
|
$iconMap = [
|
|
'billing' => 'pi-file-pdf',
|
|
'invoicing' => 'pi-receipt',
|
|
'inventory' => 'pi-box',
|
|
'reporting' => 'pi-chart-bar',
|
|
'crm' => 'pi-users',
|
|
'test' => 'pi-code',
|
|
];
|
|
|
|
return $iconMap[$code] ?? 'pi-circle';
|
|
}
|
|
}
|