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; } }