Oprava Import SDF.
parent
236abde4ec
commit
38e169bfcf
|
|
@ -358,6 +358,12 @@ class DatabazeFacade
|
|||
|
||||
public function getSdfFilePath(int $loginId): ?string
|
||||
{
|
||||
// JSON export funguje i na Linuxu; .sdf vyžaduje Windows + SQL Compact.
|
||||
$export = $this->sdfExportPath($loginId);
|
||||
if (is_file($export)) {
|
||||
return $export;
|
||||
}
|
||||
|
||||
$path = $this->sdfPath($loginId);
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
|
@ -386,12 +392,25 @@ class DatabazeFacade
|
|||
}
|
||||
|
||||
$extension = strtolower(pathinfo($file->getUntrustedName(), PATHINFO_EXTENSION));
|
||||
if ($extension !== 'sdf') {
|
||||
throw new \InvalidArgumentException('Soubor musí mít příponu .sdf.');
|
||||
if (!in_array($extension, ['sdf', 'json'], true)) {
|
||||
throw new \InvalidArgumentException('Soubor musí mít příponu .sdf nebo .json (export).');
|
||||
}
|
||||
|
||||
FileSystem::createDir($this->sdfStorageDir);
|
||||
$file->move($this->sdfPath($loginId));
|
||||
|
||||
$target = $extension === 'json'
|
||||
? $this->sdfExportPath($loginId)
|
||||
: $this->sdfPath($loginId);
|
||||
|
||||
// Nový upload přepíše druhý formát, ať import nebere starý soubor.
|
||||
$other = $extension === 'json'
|
||||
? $this->sdfPath($loginId)
|
||||
: $this->sdfExportPath($loginId);
|
||||
if (is_file($other)) {
|
||||
FileSystem::delete($other);
|
||||
}
|
||||
|
||||
$file->move($target);
|
||||
$this->clearSdfImportMeta($loginId);
|
||||
|
||||
$meta = [
|
||||
|
|
@ -399,6 +418,7 @@ class DatabazeFacade
|
|||
'uploadedAt' => (new \DateTimeImmutable())->format('c'),
|
||||
'size' => $file->getSize(),
|
||||
'loginId' => $loginId,
|
||||
'format' => $extension,
|
||||
];
|
||||
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
||||
}
|
||||
|
|
@ -490,12 +510,12 @@ class DatabazeFacade
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array{originalName?: string, uploadedAt?: string, size?: int}|array{}
|
||||
* @return array{originalName?: string, uploadedAt?: string, size?: int, format?: string}|array{}
|
||||
*/
|
||||
private function readSdfMeta(int $loginId): array
|
||||
{
|
||||
$sdf = $this->sdfPath($loginId);
|
||||
if (!is_file($sdf)) {
|
||||
$source = $this->getSdfFilePath($loginId);
|
||||
if ($source === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -510,9 +530,10 @@ class DatabazeFacade
|
|||
}
|
||||
}
|
||||
|
||||
$meta['size'] = filesize($sdf) ?: ($meta['size'] ?? null);
|
||||
$meta['uploadedAt'] ??= date('c', filemtime($sdf) ?: time());
|
||||
$meta['originalName'] ??= $loginId . '.sdf';
|
||||
$meta['size'] = filesize($source) ?: ($meta['size'] ?? null);
|
||||
$meta['uploadedAt'] ??= date('c', filemtime($source) ?: time());
|
||||
$meta['originalName'] ??= basename($source);
|
||||
$meta['format'] ??= strtolower(pathinfo($source, PATHINFO_EXTENSION));
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
|
@ -550,6 +571,11 @@ class DatabazeFacade
|
|||
return $this->sdfStorageDir . DIRECTORY_SEPARATOR . $loginId . '.sdf';
|
||||
}
|
||||
|
||||
private function sdfExportPath(int $loginId): string
|
||||
{
|
||||
return $this->sdfStorageDir . DIRECTORY_SEPARATOR . $loginId . '.export.json';
|
||||
}
|
||||
|
||||
private function sdfMetaPath(int $loginId): string
|
||||
{
|
||||
return $this->sdfStorageDir . DIRECTORY_SEPARATOR . $loginId . '.json';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class SdfCompactExporter
|
|||
}
|
||||
|
||||
/**
|
||||
* Přečte SQL Compact .sdf a vrátí tabulky jako pole.
|
||||
* Přečte SQL Compact .sdf (Windows) nebo hotový JSON export a vrátí tabulky jako pole.
|
||||
*
|
||||
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
|
||||
*/
|
||||
|
|
@ -23,22 +23,55 @@ class SdfCompactExporter
|
|||
if (!is_file($sdfPath)) {
|
||||
throw new \RuntimeException('SDF soubor nebyl nalezen.');
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo($sdfPath, PATHINFO_EXTENSION));
|
||||
if ($extension === 'json') {
|
||||
return $this->normalizeExport(Json::decode(FileSystem::read($sdfPath), forceArrays: true));
|
||||
}
|
||||
|
||||
if (!is_file($this->scriptPath)) {
|
||||
throw new \RuntimeException('Exportní skript bin/sdf-export.ps1 chybí.');
|
||||
}
|
||||
|
||||
$powershell = $this->findPowershell();
|
||||
if ($powershell === null) {
|
||||
throw new \RuntimeException(
|
||||
'Čtení .sdf vyžaduje Windows a PowerShell se SQL Server Compact 4.0. '
|
||||
. 'Na Linux serveru nejdřív na Windows spusťte bin/sdf-export.ps1 '
|
||||
. 'a nahrajte výsledný .json.'
|
||||
);
|
||||
}
|
||||
|
||||
$workDir = dirname($sdfPath) . DIRECTORY_SEPARATOR . 'export-' . bin2hex(random_bytes(4));
|
||||
FileSystem::createDir($workDir);
|
||||
$outFile = $workDir . DIRECTORY_SEPARATOR . 'export.json';
|
||||
|
||||
try {
|
||||
$this->runPowershell($sdfPath, $outFile);
|
||||
$this->runPowershell($powershell, $sdfPath, $outFile);
|
||||
$raw = FileSystem::read($outFile);
|
||||
$data = Json::decode($raw, forceArrays: true);
|
||||
return $this->normalizeExport(Json::decode($raw, forceArrays: true));
|
||||
} finally {
|
||||
FileSystem::delete($workDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
|
||||
*/
|
||||
private function normalizeExport(mixed $data): array
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
throw new \RuntimeException('Export SDF nevrátil platná data.');
|
||||
}
|
||||
|
||||
$hasTables = isset($data['uzivatel']) || isset($data['zakaznik']) || isset($data['faktura']);
|
||||
if (!$hasTables) {
|
||||
throw new \RuntimeException(
|
||||
'JSON nemá očekávanou strukturu exportu (uzivatel/zakaznik/faktura). '
|
||||
. 'Vygenerujte ho přes bin/sdf-export.ps1.'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'uzivatel' => $data['uzivatel'] ?? [],
|
||||
'zakaznik' => $data['zakaznik'] ?? [],
|
||||
|
|
@ -47,14 +80,10 @@ class SdfCompactExporter
|
|||
'platba' => $data['platba'] ?? [],
|
||||
'konfigurace' => $data['konfigurace'] ?? [],
|
||||
];
|
||||
} finally {
|
||||
FileSystem::delete($workDir);
|
||||
}
|
||||
}
|
||||
|
||||
private function runPowershell(string $sdfPath, string $outFile): void
|
||||
private function runPowershell(string $powershell, string $sdfPath, string $outFile): void
|
||||
{
|
||||
$powershell = $this->findPowershell();
|
||||
$cmd = sprintf(
|
||||
'%s -NoProfile -NonInteractive -ExecutionPolicy Bypass -File %s -SdfPath %s -OutFile %s -Password %s',
|
||||
$this->winArg($powershell),
|
||||
|
|
@ -85,8 +114,12 @@ class SdfCompactExporter
|
|||
}
|
||||
}
|
||||
|
||||
private function findPowershell(): string
|
||||
private function findPowershell(): ?string
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidates = [
|
||||
getenv('SystemRoot') . '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
||||
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class SdfKufrikImporter
|
|||
|
||||
$sdfPath = $this->databazeFacade->getSdfFilePath($loginId);
|
||||
if ($sdfPath === null) {
|
||||
throw new \RuntimeException('K této databázi ještě není nahraný soubor SDF.');
|
||||
throw new \RuntimeException('K této databázi ještě není nahraný soubor SDF ani JSON export.');
|
||||
}
|
||||
|
||||
$exported = $this->sdfExporter->export($sdfPath);
|
||||
|
|
|
|||
|
|
@ -248,8 +248,8 @@ class AdminPresenter extends BasePresenter
|
|||
$maxBytes = min(self::SDF_MAX_BYTES, ...($iniLimits ?: [self::SDF_MAX_BYTES]));
|
||||
$maxMb = max(1, (int) floor($maxBytes / (1024 * 1024)));
|
||||
|
||||
$form->addUpload('sdf', 'Soubor SDF:')
|
||||
->setRequired('Vyberte soubor s příponou .sdf.')
|
||||
$form->addUpload('sdf', 'Soubor SDF / JSON:')
|
||||
->setRequired('Vyberte soubor .sdf nebo JSON export.')
|
||||
->addRule($form::MaxFileSize, "Soubor může mít nejvýše {$maxMb} MB.", $maxBytes)
|
||||
->addRule(
|
||||
function (UploadControl $control): bool {
|
||||
|
|
@ -258,9 +258,11 @@ class AdminPresenter extends BasePresenter
|
|||
return false;
|
||||
}
|
||||
|
||||
return strtolower(pathinfo($file->getUntrustedName(), PATHINFO_EXTENSION)) === 'sdf';
|
||||
$ext = strtolower(pathinfo($file->getUntrustedName(), PATHINFO_EXTENSION));
|
||||
|
||||
return in_array($ext, ['sdf', 'json'], true);
|
||||
},
|
||||
'Soubor musí mít příponu .sdf.'
|
||||
'Soubor musí mít příponu .sdf nebo .json.'
|
||||
);
|
||||
|
||||
$form->addSubmit('send', 'Import');
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
<form n:name="sdfImportForm" class="d-none">
|
||||
<input n:name="loginId">
|
||||
<input n:name="sdf" accept=".sdf">
|
||||
<input n:name="sdf" accept=".sdf,.json,application/json">
|
||||
<button n:name="send">Import</button>
|
||||
</form>
|
||||
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
class="btn btn-sm btn-success js-sdf-import"
|
||||
data-login-id="{$firma->LOGIN_ID}"
|
||||
data-firma="{$firma->NAZEV}"
|
||||
title="Import ze SDF (smaže data firmy)">
|
||||
title="Import ze SDF / JSON exportu (smaže data firmy)">
|
||||
<i class="bi bi-box-arrow-in-down me-1"></i>Import
|
||||
</button>
|
||||
{else}
|
||||
|
|
@ -148,7 +148,7 @@ $(document).ready(function() {
|
|||
|
||||
$(document).on('click', '.js-sdf-import', function() {
|
||||
const firma = $(this).data('firma');
|
||||
if (!confirm('VAROVÁNÍ: Import ze SDF pro firmu ' + firma + ' SMAŽE všechna stávající data této firmy.\n\nOpravdu pokračovat?')) {
|
||||
if (!confirm('VAROVÁNÍ: Import ze SDF/JSON pro firmu ' + firma + ' SMAŽE všechna stávající data této firmy.\n\nNa Linux serveru nahrajte JSON z bin/sdf-export.ps1 (Windows).\n\nOpravdu pokračovat?')) {
|
||||
return;
|
||||
}
|
||||
$importLoginId.val($(this).data('login-id'));
|
||||
|
|
|
|||
Loading…
Reference in New Issue