Backend module: CSV import for the VITEC domain models
New "VITEC Import" module under Web: one import page per domain model (v1: Market, Solution, Product - registry-driven, adding a model is one config entry). Workflow: upload a CSV (delimiter and encoding are auto-detected, including German-Excel semicolon/Windows-1252), map CSV columns to DB fields, persist the mapping per model together with the identity field used for matching (new table tx_vitec_import_mapping, no TCA - pure tool configuration), review a unified list of CSV rows matched against the DB records (new / update with differing fields / unchanged / db-only), then apply the checked rows through DataHandler. Each importable row carries an editable JSON payload textarea - what is written is the textarea content, not the raw CSV, so editors can fix values right in the review step. The parsed CSV travels through the form as a hidden JSON field: no session state, no temp files. Importable fields are derived from TCA at runtime (scalar types only; files, categories and other relations are excluded - a flat CSV cannot carry them). Payloads are whitelisted against that field list on apply; new records require a storage pid (prefilled from existing records). BE user permissions apply via DataHandler. Deliberately out of v1: import log with three-way compare (protection against overwriting manual edits), images/relations, multiple saved mappings per model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
baa0fdd331
commit
10c0328bf2
9 files changed
+831
No files matched your search
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Import;
|
||||
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Persists one CSV-column -> DB-field mapping per import model, so editors
|
||||
* configure the mapper once and reuse it on every delivery.
|
||||
*
|
||||
* Plain table without TCA (tx_vitec_import_mapping) - the records are pure
|
||||
* tool configuration, never edited through FormEngine.
|
||||
*/
|
||||
final class MappingRepository
|
||||
{
|
||||
private const TABLE = 'tx_vitec_import_mapping';
|
||||
|
||||
/**
|
||||
* @return array{mapping: array<string,string>, identity: string}|null
|
||||
*/
|
||||
public function load(string $model): ?array
|
||||
{
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
|
||||
$row = $qb->select('mapping', 'identity_field')->from(self::TABLE)
|
||||
->where($qb->expr()->eq('model', $qb->createNamedParameter($model, ParameterType::STRING)))
|
||||
->setMaxResults(1)
|
||||
->executeQuery()->fetchAssociative();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
$mapping = json_decode((string)$row['mapping'], true);
|
||||
if (!is_array($mapping)) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'mapping' => array_map('strval', $mapping),
|
||||
'identity' => (string)$row['identity_field'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array<string,string> $mapping */
|
||||
public function save(string $model, array $mapping, string $identity): void
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::TABLE);
|
||||
$values = [
|
||||
'mapping' => (string)json_encode($mapping, JSON_UNESCAPED_UNICODE),
|
||||
'identity_field' => $identity,
|
||||
'tstamp' => time(),
|
||||
];
|
||||
$exists = $connection->count('uid', self::TABLE, ['model' => $model]) > 0;
|
||||
if ($exists) {
|
||||
$connection->update(self::TABLE, $values, ['model' => $model]);
|
||||
} else {
|
||||
$connection->insert(self::TABLE, $values + [
|
||||
'model' => $model,
|
||||
'pid' => 0,
|
||||
'crdate' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user