Files
desktop/modules/boersenchecker/src/Support/InstrumentRegistry.php
Lars Gebhardt-Kusche dc66f75eef
All checks were successful
Deploy / deploy-staging (push) Successful in 28s
Deploy / deploy-production (push) Has been skipped
Boersenchecker
2026-06-22 23:29:03 +02:00

191 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Modules\Boersenchecker\Support;
use PDO;
use RuntimeException;
final class InstrumentRegistry
{
public function __construct(
private PDO $pdo,
private string $instrumentTable,
private string $positionTable,
private string $quoteTable,
) {
}
public function save(array $payload): int
{
$currentId = (int) ($payload['id'] ?? 0);
$data = $this->normalizePayload($payload);
$matchingId = $this->findMatchingInstrumentId($data, $currentId);
if ($currentId > 0 && $matchingId > 0 && $matchingId !== $currentId) {
return $this->mergeIntoExistingInstrument($currentId, $matchingId, $data);
}
if ($currentId > 0) {
$this->updateInstrument($currentId, $data);
return $currentId;
}
if ($matchingId > 0) {
$this->updateInstrument($matchingId, $data);
return $matchingId;
}
return $this->insertInstrument($data);
}
public function findMatchingInstrumentId(array $payload, int $excludeId = 0): ?int
{
$data = $this->normalizePayload($payload);
$conditions = [];
$excludeSql = $excludeId > 0 ? ' AND id <> :exclude_id' : '';
if ($data['isin'] !== null) {
$conditions[] = [
'sql' => 'SELECT id FROM ' . $this->instrumentTable . ' WHERE isin = :isin' . $excludeSql . ' LIMIT 1',
'params' => ['isin' => $data['isin']],
];
}
if ($data['symbol'] !== null && $data['market'] !== null) {
$conditions[] = [
'sql' => 'SELECT id FROM ' . $this->instrumentTable . ' WHERE symbol = :symbol AND market = :market' . $excludeSql . ' LIMIT 1',
'params' => ['symbol' => $data['symbol'], 'market' => $data['market']],
];
}
if ($data['symbol'] !== null && $data['name'] !== '') {
$conditions[] = [
'sql' => 'SELECT id FROM ' . $this->instrumentTable . ' WHERE symbol = :symbol AND name = :name' . $excludeSql . ' LIMIT 1',
'params' => ['symbol' => $data['symbol'], 'name' => $data['name']],
];
}
foreach ($conditions as $condition) {
$params = $condition['params'];
if ($excludeId > 0) {
$params['exclude_id'] = $excludeId;
}
$stmt = $this->pdo->prepare($condition['sql']);
$stmt->execute($params);
$id = $stmt->fetchColumn();
if ($id !== false) {
return (int) $id;
}
}
return null;
}
private function normalizePayload(array $payload): array
{
$data = [
'isin' => $this->normalizeUpper($payload['isin'] ?? null),
'wkn' => $this->normalizeUpper($payload['wkn'] ?? null),
'symbol' => $this->normalizeUpper($payload['symbol'] ?? null),
'name' => trim((string) ($payload['name'] ?? '')),
'quote_currency' => $this->normalizeUpper($payload['quote_currency'] ?? 'EUR', 'EUR'),
'market' => trim((string) ($payload['market'] ?? '')) ?: null,
];
if ($data['name'] === '') {
throw new RuntimeException('Bitte mindestens einen Aktiennamen angeben.');
}
return $data;
}
private function normalizeUpper(mixed $value, string $fallback = ''): ?string
{
$normalized = strtoupper(trim((string) $value));
if ($normalized !== '') {
return $normalized;
}
return $fallback !== '' ? $fallback : null;
}
private function updateInstrument(int $instrumentId, array $data): void
{
$stmt = $this->pdo->prepare(
'UPDATE ' . $this->instrumentTable . '
SET isin = :isin,
wkn = :wkn,
symbol = :symbol,
name = :name,
quote_currency = :quote_currency,
market = :market,
updated_at = CURRENT_TIMESTAMP
WHERE id = :id'
);
$stmt->execute($data + ['id' => $instrumentId]);
}
private function insertInstrument(array $data): int
{
$driver = strtolower((string) $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
if ($driver === 'pgsql') {
$stmt = $this->pdo->prepare(
'INSERT INTO ' . $this->instrumentTable . ' (isin, wkn, symbol, name, quote_currency, market)
VALUES (:isin, :wkn, :symbol, :name, :quote_currency, :market)
RETURNING id'
);
$stmt->execute($data);
return (int) $stmt->fetchColumn();
}
$stmt = $this->pdo->prepare(
'INSERT INTO ' . $this->instrumentTable . ' (isin, wkn, symbol, name, quote_currency, market)
VALUES (:isin, :wkn, :symbol, :name, :quote_currency, :market)'
);
$stmt->execute($data);
return (int) $this->pdo->lastInsertId();
}
private function mergeIntoExistingInstrument(int $sourceId, int $targetId, array $data): int
{
$this->pdo->beginTransaction();
try {
$this->updateInstrument($targetId, $data);
$stmt = $this->pdo->prepare(
'UPDATE ' . $this->positionTable . '
SET instrument_id = :target_id, updated_at = CURRENT_TIMESTAMP
WHERE instrument_id = :source_id'
);
$stmt->execute([
'target_id' => $targetId,
'source_id' => $sourceId,
]);
$stmt = $this->pdo->prepare(
'UPDATE ' . $this->quoteTable . '
SET instrument_id = :target_id
WHERE instrument_id = :source_id'
);
$stmt->execute([
'target_id' => $targetId,
'source_id' => $sourceId,
]);
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->instrumentTable . ' WHERE id = :id');
$stmt->execute(['id' => $sourceId]);
$this->pdo->commit();
} catch (\Throwable $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
throw $e;
}
return $targetId;
}
}