Oprava Import SDF.
parent
236abde4ec
commit
38e169bfcf
|
|
@ -358,6 +358,12 @@ class DatabazeFacade
|
||||||
|
|
||||||
public function getSdfFilePath(int $loginId): ?string
|
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);
|
$path = $this->sdfPath($loginId);
|
||||||
return is_file($path) ? $path : null;
|
return is_file($path) ? $path : null;
|
||||||
}
|
}
|
||||||
|
|
@ -386,12 +392,25 @@ class DatabazeFacade
|
||||||
}
|
}
|
||||||
|
|
||||||
$extension = strtolower(pathinfo($file->getUntrustedName(), PATHINFO_EXTENSION));
|
$extension = strtolower(pathinfo($file->getUntrustedName(), PATHINFO_EXTENSION));
|
||||||
if ($extension !== 'sdf') {
|
if (!in_array($extension, ['sdf', 'json'], true)) {
|
||||||
throw new \InvalidArgumentException('Soubor musí mít příponu .sdf.');
|
throw new \InvalidArgumentException('Soubor musí mít příponu .sdf nebo .json (export).');
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystem::createDir($this->sdfStorageDir);
|
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);
|
$this->clearSdfImportMeta($loginId);
|
||||||
|
|
||||||
$meta = [
|
$meta = [
|
||||||
|
|
@ -399,6 +418,7 @@ class DatabazeFacade
|
||||||
'uploadedAt' => (new \DateTimeImmutable())->format('c'),
|
'uploadedAt' => (new \DateTimeImmutable())->format('c'),
|
||||||
'size' => $file->getSize(),
|
'size' => $file->getSize(),
|
||||||
'loginId' => $loginId,
|
'loginId' => $loginId,
|
||||||
|
'format' => $extension,
|
||||||
];
|
];
|
||||||
FileSystem::write($this->sdfMetaPath($loginId), Json::encode($meta, pretty: true));
|
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
|
private function readSdfMeta(int $loginId): array
|
||||||
{
|
{
|
||||||
$sdf = $this->sdfPath($loginId);
|
$source = $this->getSdfFilePath($loginId);
|
||||||
if (!is_file($sdf)) {
|
if ($source === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,9 +530,10 @@ class DatabazeFacade
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$meta['size'] = filesize($sdf) ?: ($meta['size'] ?? null);
|
$meta['size'] = filesize($source) ?: ($meta['size'] ?? null);
|
||||||
$meta['uploadedAt'] ??= date('c', filemtime($sdf) ?: time());
|
$meta['uploadedAt'] ??= date('c', filemtime($source) ?: time());
|
||||||
$meta['originalName'] ??= $loginId . '.sdf';
|
$meta['originalName'] ??= basename($source);
|
||||||
|
$meta['format'] ??= strtolower(pathinfo($source, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
return $meta;
|
return $meta;
|
||||||
}
|
}
|
||||||
|
|
@ -550,6 +571,11 @@ class DatabazeFacade
|
||||||
return $this->sdfStorageDir . DIRECTORY_SEPARATOR . $loginId . '.sdf';
|
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
|
private function sdfMetaPath(int $loginId): string
|
||||||
{
|
{
|
||||||
return $this->sdfStorageDir . DIRECTORY_SEPARATOR . $loginId . '.json';
|
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}
|
* @return array{uzivatel: array, zakaznik: array, faktura: array, polozka: array, platba: array, konfigurace: array}
|
||||||
*/
|
*/
|
||||||
|
|
@ -23,22 +23,55 @@ class SdfCompactExporter
|
||||||
if (!is_file($sdfPath)) {
|
if (!is_file($sdfPath)) {
|
||||||
throw new \RuntimeException('SDF soubor nebyl nalezen.');
|
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)) {
|
if (!is_file($this->scriptPath)) {
|
||||||
throw new \RuntimeException('Exportní skript bin/sdf-export.ps1 chybí.');
|
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));
|
$workDir = dirname($sdfPath) . DIRECTORY_SEPARATOR . 'export-' . bin2hex(random_bytes(4));
|
||||||
FileSystem::createDir($workDir);
|
FileSystem::createDir($workDir);
|
||||||
$outFile = $workDir . DIRECTORY_SEPARATOR . 'export.json';
|
$outFile = $workDir . DIRECTORY_SEPARATOR . 'export.json';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->runPowershell($sdfPath, $outFile);
|
$this->runPowershell($powershell, $sdfPath, $outFile);
|
||||||
$raw = FileSystem::read($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)) {
|
if (!is_array($data)) {
|
||||||
throw new \RuntimeException('Export SDF nevrátil platná 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 [
|
return [
|
||||||
'uzivatel' => $data['uzivatel'] ?? [],
|
'uzivatel' => $data['uzivatel'] ?? [],
|
||||||
'zakaznik' => $data['zakaznik'] ?? [],
|
'zakaznik' => $data['zakaznik'] ?? [],
|
||||||
|
|
@ -47,14 +80,10 @@ class SdfCompactExporter
|
||||||
'platba' => $data['platba'] ?? [],
|
'platba' => $data['platba'] ?? [],
|
||||||
'konfigurace' => $data['konfigurace'] ?? [],
|
'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(
|
$cmd = sprintf(
|
||||||
'%s -NoProfile -NonInteractive -ExecutionPolicy Bypass -File %s -SdfPath %s -OutFile %s -Password %s',
|
'%s -NoProfile -NonInteractive -ExecutionPolicy Bypass -File %s -SdfPath %s -OutFile %s -Password %s',
|
||||||
$this->winArg($powershell),
|
$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 = [
|
$candidates = [
|
||||||
getenv('SystemRoot') . '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
getenv('SystemRoot') . '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
||||||
'C:\\Windows\\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);
|
$sdfPath = $this->databazeFacade->getSdfFilePath($loginId);
|
||||||
if ($sdfPath === null) {
|
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);
|
$exported = $this->sdfExporter->export($sdfPath);
|
||||||
|
|
|
||||||
|
|
@ -248,8 +248,8 @@ class AdminPresenter extends BasePresenter
|
||||||
$maxBytes = min(self::SDF_MAX_BYTES, ...($iniLimits ?: [self::SDF_MAX_BYTES]));
|
$maxBytes = min(self::SDF_MAX_BYTES, ...($iniLimits ?: [self::SDF_MAX_BYTES]));
|
||||||
$maxMb = max(1, (int) floor($maxBytes / (1024 * 1024)));
|
$maxMb = max(1, (int) floor($maxBytes / (1024 * 1024)));
|
||||||
|
|
||||||
$form->addUpload('sdf', 'Soubor SDF:')
|
$form->addUpload('sdf', 'Soubor SDF / JSON:')
|
||||||
->setRequired('Vyberte soubor s příponou .sdf.')
|
->setRequired('Vyberte soubor .sdf nebo JSON export.')
|
||||||
->addRule($form::MaxFileSize, "Soubor může mít nejvýše {$maxMb} MB.", $maxBytes)
|
->addRule($form::MaxFileSize, "Soubor může mít nejvýše {$maxMb} MB.", $maxBytes)
|
||||||
->addRule(
|
->addRule(
|
||||||
function (UploadControl $control): bool {
|
function (UploadControl $control): bool {
|
||||||
|
|
@ -258,9 +258,11 @@ class AdminPresenter extends BasePresenter
|
||||||
return false;
|
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');
|
$form->addSubmit('send', 'Import');
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
<form n:name="sdfImportForm" class="d-none">
|
<form n:name="sdfImportForm" class="d-none">
|
||||||
<input n:name="loginId">
|
<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>
|
<button n:name="send">Import</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
class="btn btn-sm btn-success js-sdf-import"
|
class="btn btn-sm btn-success js-sdf-import"
|
||||||
data-login-id="{$firma->LOGIN_ID}"
|
data-login-id="{$firma->LOGIN_ID}"
|
||||||
data-firma="{$firma->NAZEV}"
|
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
|
<i class="bi bi-box-arrow-in-down me-1"></i>Import
|
||||||
</button>
|
</button>
|
||||||
{else}
|
{else}
|
||||||
|
|
@ -148,7 +148,7 @@ $(document).ready(function() {
|
||||||
|
|
||||||
$(document).on('click', '.js-sdf-import', function() {
|
$(document).on('click', '.js-sdf-import', function() {
|
||||||
const firma = $(this).data('firma');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
$importLoginId.val($(this).data('login-id'));
|
$importLoginId.val($(this).data('login-id'));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue