191 lines
6.3 KiB
PHP
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;
|
|
}
|
|
}
|