Files
Mitgliederverwaltung/lib/Service/FamilyService.php
T

270 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Mitgliederverwaltung\Service;
use DateTime;
use OCA\Mitgliederverwaltung\Db\Family;
use OCA\Mitgliederverwaltung\Db\FamilyMapper;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Validation\IbanValidator;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;
/**
* Service layer for family CRUD operations.
*
* Part of Issue #28.
*/
class FamilyService {
private FamilyMapper $familyMapper;
private MemberMapper $memberMapper;
private LoggerInterface $logger;
public function __construct(
FamilyMapper $familyMapper,
MemberMapper $memberMapper,
LoggerInterface $logger
) {
$this->familyMapper = $familyMapper;
$this->memberMapper = $memberMapper;
$this->logger = $logger;
}
/**
* Create a new family.
*
* @throws ValidationException
* @throws Exception
*/
public function create(array $data): array {
$this->validateRequiredFields($data);
$this->validateIban($data);
$now = (new DateTime())->format('Y-m-d H:i:s');
$family = new Family();
$family->setName(trim($data['name']));
$family->setKontoinhaberEncrypted($data['kontoinhaberEncrypted'] ?? null);
$family->setIbanEncrypted($data['ibanEncrypted'] ?? null);
$family->setBic($data['bic'] ?? null);
$family->setKreditinstitut($data['kreditinstitut'] ?? null);
$family->setCreatedAt($now);
$family->setUpdatedAt($now);
/** @var Family $family */
$family = $this->familyMapper->insert($family);
return $this->buildFamilyResponse($family);
}
/**
* Find a family by ID with linked members.
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws Exception
*/
public function find(int $id): array {
$family = $this->familyMapper->findById($id);
return $this->buildFamilyResponse($family);
}
/**
* Find all families.
*
* @return array[]
* @throws Exception
*/
public function findAll(?int $limit = null, ?int $offset = null): array {
$families = $this->familyMapper->findAll($limit, $offset);
return array_map(fn(Family $f) => $this->buildFamilyResponse($f), $families);
}
/**
* Search families by name.
*
* @return array[]
* @throws Exception
*/
public function search(string $name): array {
$families = $this->familyMapper->findByName($name);
return array_map(fn(Family $f) => $this->buildFamilyResponse($f), $families);
}
/**
* Count all families.
*
* @throws Exception
*/
public function countAll(): int {
return $this->familyMapper->countAll();
}
/**
* Update a family.
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws ValidationException
* @throws Exception
*/
public function update(int $id, array $data): array {
$this->validateIban($data);
$family = $this->familyMapper->findById($id);
if (isset($data['name'])) {
if (trim($data['name']) === '') {
throw new ValidationException('Familienname darf nicht leer sein.');
}
$family->setName(trim($data['name']));
}
if (array_key_exists('kontoinhaberEncrypted', $data)) {
$family->setKontoinhaberEncrypted($data['kontoinhaberEncrypted']);
}
if (array_key_exists('ibanEncrypted', $data)) {
$family->setIbanEncrypted($data['ibanEncrypted']);
}
if (array_key_exists('bic', $data)) {
$family->setBic($data['bic']);
}
if (array_key_exists('kreditinstitut', $data)) {
$family->setKreditinstitut($data['kreditinstitut']);
}
$family->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
/** @var Family $family */
$family = $this->familyMapper->update($family);
return $this->buildFamilyResponse($family);
}
/**
* Delete a family. Unlinks all members first.
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws Exception
*/
public function delete(int $id): void {
$family = $this->familyMapper->findById($id);
// Unlink all members from this family
$members = $this->memberMapper->findByFamily($id);
foreach ($members as $member) {
$member->setFamilyId(null);
$this->memberMapper->update($member);
}
$this->familyMapper->delete($family);
$this->logger->info('Family deleted', [
'familyId' => $id,
'app' => 'mitgliederverwaltung',
]);
}
/**
* Link a member to a family.
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws Exception
*/
public function linkMember(int $familyId, int $memberId): void {
// Verify family exists
$this->familyMapper->findById($familyId);
$member = $this->memberMapper->findById($memberId);
$member->setFamilyId($familyId);
$member->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
$this->memberMapper->update($member);
}
/**
* Unlink a member from a family.
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws Exception
*/
public function unlinkMember(int $familyId, int $memberId): void {
$member = $this->memberMapper->findById($memberId);
if ($member->getFamilyId() !== $familyId) {
throw new ValidationException('Mitglied gehoert nicht zu dieser Familie.');
}
$member->setFamilyId(null);
$member->setUpdatedAt((new DateTime())->format('Y-m-d H:i:s'));
$this->memberMapper->update($member);
}
/**
* Get the count of active children in a family.
*
* @throws Exception
*/
public function getActiveChildrenCount(int $familyId): int {
$members = $this->memberMapper->findByFamily($familyId);
return count(array_filter($members, function ($member) {
return $member->getStatus() === 'aktiv' && $member->getRolle() === 'mitglied';
}));
}
// ── Helpers ──────────────────────────────────────────────────────
/**
* @throws ValidationException
*/
private function validateRequiredFields(array $data): void {
if (!isset($data['name']) || trim($data['name']) === '') {
throw new ValidationException('Familienname ist erforderlich.');
}
}
/**
* Validate IBAN if provided and non-empty.
*
* @throws ValidationException
*/
private function validateIban(array $data): void {
$iban = $data['ibanEncrypted'] ?? null;
if ($iban !== null && trim($iban) !== '') {
$error = IbanValidator::getError($iban);
if ($error !== null) {
throw new ValidationException($error);
}
}
}
/**
* Build response array with linked members.
*
* @throws Exception
*/
private function buildFamilyResponse(Family $family): array {
$result = $family->jsonSerialize();
$members = $this->memberMapper->findByFamily($family->getId());
$result['members'] = array_map(fn($m) => [
'id' => $m->getId(),
'vorname' => $m->getVorname(),
'nachname' => $m->getNachname(),
'rolle' => $m->getRolle(),
'status' => $m->getStatus(),
], $members);
$result['activeChildrenCount'] = count(array_filter($members, function ($m) {
return $m->getStatus() === 'aktiv' && $m->getRolle() === 'mitglied';
}));
return $result;
}
}