databazeFacade->getById($loginId); if (!$login) { throw new \InvalidArgumentException('Login / databáze nebyla nalezena.'); } $sdfPath = $this->databazeFacade->getSdfFilePath($loginId); if ($sdfPath === null) { throw new \RuntimeException('K této databázi ještě není nahraný soubor SDF.'); } $exported = $this->sdfExporter->export($sdfPath); $data = [ 'uzivatel' => $this->rows($exported['uzivatel']), 'zakaznik' => $this->rows($exported['zakaznik']), 'faktura' => $this->rows($exported['faktura']), 'polozka' => $this->rows($exported['polozka']), 'platba' => $this->rows($exported['platba']), ]; $firmaId = (int) $login->FIRMA_ID; $explorer = $this->kufrikConnectionFactory->createTenantExplorer( (string) $login->SERVER, (string) $login->DATABASE, $firmaId ); $explorer->getConnection()->beginTransaction(); try { $this->wipeTenantData($explorer, $firmaId); $firmaState = $this->importFirma($explorer, $data['uzivatel'][0] ?? null, $firmaId); $zakaznikMap = $this->importZakaznici($explorer, $data['zakaznik'], $firmaId); $fakturaResult = $this->importFaktury($explorer, $data, $firmaId, $zakaznikMap); $explorer->getConnection()->commit(); } catch (\Throwable $e) { $explorer->getConnection()->rollBack(); throw $e; } $stats = [ 'database' => (string) $login->DATABASE, 'firma' => $firmaState, 'zakazniciInserted' => $zakaznikMap['inserted'], 'zakazniciMatched' => $zakaznikMap['matched'], 'fakturyInserted' => $fakturaResult['inserted'], 'fakturySkipped' => $fakturaResult['skipped'], 'polozkyInserted' => $fakturaResult['polozky'], 'platbyInserted' => $fakturaResult['platby'], ]; $this->databazeFacade->markSdfImported($loginId, $stats); return $stats; } private function wipeTenantData(Explorer $explorer, int $firmaId): void { $explorer->table('FAKTURA')->where('FIRMA_ID', $firmaId)->delete(); $explorer->table('POLOZKA')->where('FIRMA_ID', $firmaId)->delete(); $explorer->table('ZAKAZNIK')->where('FIRMA_ID', $firmaId)->delete(); } private function importFirma(Explorer $explorer, ?array $uzivatel, int $firmaId): string { if (!$uzivatel) { return 'skipped'; } $banka = (string) ($uzivatel['BANKA'] ?? ''); $cisloUctu = $banka; $bankaKod = null; if (preg_match('#^(.*)/(\d{4})$#u', trim($banka), $m)) { $cisloUctu = trim($m[1]); $bankaKod = $m[2]; } $nazev = trim(($uzivatel['JMENO'] ?? '') . ' ' . ($uzivatel['PRIJMENI'] ?? '')); if ($nazev === '') { $nazev = 'Importovaná firma'; } $values = [ 'NAZEV' => $this->clip($nazev, 255), 'IC' => $this->clip($uzivatel['IC'] ?? null, 8), 'DIC' => $this->clip($uzivatel['DIC'] ?? null, 12), 'PLATCE_DPH' => !empty($uzivatel['DIC']) ? 1 : 0, 'TELEFON' => $this->clip($uzivatel['TELEFON'] ?? null, 20), 'EMAIL' => $this->clip($uzivatel['EMAIL'] ?? null, 100), 'ULICE' => $this->clip($uzivatel['ULICE'] ?? null, 100), 'MESTO' => $this->clip($uzivatel['MESTO'] ?? null, 100), 'PSC' => $this->clip($uzivatel['PSC'] ?? null, 10), 'CISLO_UCTU' => $this->clip($cisloUctu, 30), 'BANKA_KOD' => $this->clip($bankaKod, 10), ]; $exists = $explorer->table('FIRMA')->get($firmaId); if ($exists) { $explorer->table('FIRMA')->where('ID', $firmaId)->update($values); return 'updated'; } $values['ID'] = $firmaId; $explorer->query('SET IDENTITY_INSERT dbo.FIRMA ON'); try { $explorer->table('FIRMA')->insert($values); } finally { $explorer->query('SET IDENTITY_INSERT dbo.FIRMA OFF'); } return 'inserted'; } /** * @param array> $rows * @return array{map: array, inserted: int, matched: int} */ private function importZakaznici(Explorer $explorer, array $rows, int $firmaId): array { $map = []; $inserted = 0; foreach ($rows as $row) { $oldId = (int) ($row['ID'] ?? 0); $ic = trim((string) ($row['IC'] ?? '')); $insertedRow = $explorer->table('ZAKAZNIK')->insert([ 'FIRMA_ID' => $firmaId, 'JMENO' => $this->clip($row['JMENO'] ?? null, 255), 'PRIJMENI' => $this->clip($row['PRIJMENI'] ?? null, 255) ?? '—', 'ULICE' => $this->clip($row['ULICE'] ?? null, 50), 'MESTO' => $this->clip($row['MESTO'] ?? null, 50), 'PSC' => $this->clip($row['PSC'] ?? null, 5), 'IC' => $this->clip($ic !== '' ? $ic : null, 8), 'DIC' => $this->clip($row['DIC'] ?? null, 10), 'TELEFON' => $this->clip($row['TELEFON'] ?? null, 15), 'EMAIL' => $this->clip($row['EMAIL'] ?? null, 100), 'AKTIVNI' => 1, ]); $inserted++; if ($oldId) { $map[$oldId] = (int) $insertedRow->ID; } } return ['map' => $map, 'inserted' => $inserted, 'matched' => 0]; } /** * @param array{faktura: array, polozka: array, platba: array} $data * @param array{map: array, inserted: int, matched: int} $zakaznikMap * @return array{inserted: int, skipped: int, polozky: int, platby: int} */ private function importFaktury(Explorer $explorer, array $data, int $firmaId, array $zakaznikMap): array { $polozkyByFaktura = []; foreach ($data['polozka'] as $item) { $polozkyByFaktura[(int) $item['FAKTURA']][] = $item; } $platbyByFaktura = []; foreach ($data['platba'] as $item) { $platbyByFaktura[(int) $item['FAKTURA']][] = $item; } $inserted = 0; $skipped = 0; $polozkyInserted = 0; $platbyInserted = 0; foreach ($data['faktura'] as $row) { $cislo = $this->clip($row['CISLO'] ?? null, 20); if ($cislo === null) { $skipped++; continue; } $oldFakturaId = (int) $row['ID']; $oldZakaznikId = (int) ($row['ZAKAZNIK'] ?? 0); $zakaznikId = $zakaznikMap['map'][$oldZakaznikId] ?? null; $datumVystaveni = $this->toDate($row['DATUM_VYSTAVENI'] ?? null); $datumSplatnosti = $this->toDate($row['DATUM_SPLATNOSTI'] ?? null) ?? $datumVystaveni; if ($datumVystaveni === null) { $skipped++; continue; } $hasPlatba = !empty($platbyByFaktura[$oldFakturaId]); $dBanka = $this->clip($row['D_BANKA'] ?? null, 100); $fakturaId = (int) $explorer->table('FAKTURA')->insert([ 'FIRMA_ID' => $firmaId, 'ZAKAZNIK' => $zakaznikId, 'CISLO' => $cislo, 'CISLO_ABS' => (int) ($row['CISLO_ABS'] ?? 0), 'FORMA_UHRADY' => (int) ($row['FORMA_UHRADY'] ?? 1), 'DATUM_VYSTAVENI' => $datumVystaveni, 'DATUM_SPLATNOSTI' => $datumSplatnosti, 'DATUM_DUZP' => $datumVystaveni, 'D_PRAJM' => $this->clip($row['D_PRAJM'] ?? null, 511), 'D_ULICE' => $this->clip($row['D_ULICE'] ?? null, 50), 'D_MESTO' => $this->clip($row['D_MESTO'] ?? null, 50), 'D_PSC' => $this->clip($row['D_PSC'] ?? null, 50), 'D_IC' => $this->clip($row['D_IC'] ?? null, 8), 'D_DIC' => $this->clip($row['D_DIC'] ?? null, 10), 'D_BANKA' => $dBanka, 'D_TELEFON' => $this->clip($row['D_TELEFON'] ?? null, 15), 'D_EMAIL' => $this->clip($row['D_EMAIL'] ?? null, 100), 'D_WEB' => $this->clip($row['D_WEB'] ?? null, 100), 'O_PRAJM' => $this->clip($row['O_PRAJM'] ?? null, 511), 'O_ULICE' => $this->clip($row['O_ULICE'] ?? null, 50), 'O_MESTO' => $this->clip($row['O_MESTO'] ?? null, 50), 'O_PSC' => $this->clip($row['O_PSC'] ?? null, 5), 'O_IC' => $this->clip($row['O_IC'] ?? null, 8), 'O_DIC' => $this->clip($row['O_DIC'] ?? null, 10), 'CINNOSTI' => $row['CINNOSTI'] ?: null, 'STAV' => $hasPlatba ? 'PAID' : 'ISSUED', ])->ID; $poradi = 1; foreach ($polozkyByFaktura[$oldFakturaId] ?? [] as $polozka) { $explorer->table('POLOZKA')->insert([ 'FAKTURA' => $fakturaId, 'FIRMA_ID' => $firmaId, 'PORADI' => $poradi++, 'NAZEV' => $this->clip($polozka['NAZEV'] ?? null, 255) ?? 'Položka', 'POCET' => (float) ($polozka['POCET'] ?? 1), 'JEDNOTKA' => $this->clip($polozka['JEDNOTKA'] ?? null, 10) ?? 'ks', 'CENA_JEDNOTKA' => (float) ($polozka['CENA_JEDNOTKA'] ?? 0), 'SLEVA' => (float) ($polozka['SLEVA'] ?? 0), 'CENA' => (float) ($polozka['CENA'] ?? 0), ]); $polozkyInserted++; } foreach ($platbyByFaktura[$oldFakturaId] ?? [] as $platba) { $explorer->table('PLATBA')->insert([ 'FAKTURA' => $fakturaId, 'CASTKA' => (float) ($platba['CASTKA'] ?? 0), 'DATUM' => $this->toDateTime($platba['DATUM'] ?? null) ?? $datumVystaveni, 'POZNAMKA' => $platba['POZNAMKA'] ?: null, ]); $platbyInserted++; } $inserted++; } return [ 'inserted' => $inserted, 'skipped' => $skipped, 'polozky' => $polozkyInserted, 'platby' => $platbyInserted, ]; } private function rows(mixed $value): array { if (!is_array($value) || $value === []) { return []; } return array_is_list($value) ? $value : [$value]; } private function clip(mixed $value, int $max): ?string { if ($value === null || $value === '') { return null; } $value = trim((string) $value); if ($value === '') { return null; } return mb_strlen($value) > $max ? mb_substr($value, 0, $max) : $value; } private function toDate(mixed $value): ?string { if ($value === null || $value === '') { return null; } try { return (new \DateTimeImmutable((string) $value))->format('Y-m-d'); } catch (\Throwable) { return null; } } private function toDateTime(mixed $value): ?string { if ($value === null || $value === '') { return null; } try { return (new \DateTimeImmutable((string) $value))->format('Y-m-d H:i:s'); } catch (\Throwable) { return null; } } }