Importação de dados
CSV → tabelas via template de mapeamento, upsert multi-driver. Status: experimental (WIP).
Importação de dados (Data Import)
Status: experimental. O código está completo e funcional — service, telas administrativas, model, migration e a API REST de templates estão todos implementados, sem stubs. O que falta é cobertura de teste automatizada: não há nenhum teste de feature exercitando o motor de import, que roda
INSERT/REPLACE/UPSERTcru contra conexões e tabelas configuráveis pelo usuário. Use em produção com cautela e revisão manual até essa lacuna fechar — é por isso que oCHANGELOG.mdainda marca o módulo como "WIP".
O que faz
App\Service\Sys\DataImportService::import() lê um CSV, mapeia colunas para
uma ou mais tabelas via um template de mapeamento salvo em JSON, e grava
os dados com upsert nativo do driver da conexão de destino:
DataImportService::import(
mappingJson: $template->mapping_json, // {"database": "...", "mappings": [...]}
csvPath: $uploadedCsvPath,
database: 'business', // conexão Eloquent de destino
mode: 'partial', // 'atomic' | 'partial'
);
atomic— tudo dentro de 1 transação; qualquer linha inválida reverte o lote inteiro.partial— processa em lotes de 100, com fallback linha-a-linha dentro de um lote que falhou (boas commitam, ruins ficam só no log de erro do resultado).- Multi-driver:
mysql(REPLACE INTO),sqlite(INSERT OR REPLACE),pgsql(ON CONFLICT),firebird(MERGE), commssqlcomo fallback default. - Parsing de CSV tolera formato pt-BR (separador decimal) e auto-detecta o delimitador.
Descoberta de bancos e tabelas — introspecção local
A lista de bancos/tabelas/colunas oferecida nos formulários vem de
App\Service\Sys\DataImportDatabaseService, que faz introspecção local do
schema pelo Schema (Illuminate SchemaBuilder) — sem depender do webservice
externo do MadBuilder (manager_url) que o legado exigia:
use App\Service\Sys\DataImportDatabaseService;
DataImportDatabaseService::listDatabases();
// ['business' => 'business', 'iam' => 'iam', ...]
// ← uma entrada por conexão listada em config('mad.app_connections')
DataImportDatabaseService::listTables('business');
// ['mad_iam_user' => ['name' => 'mad_iam_user',
// 'columns' => [['name' => 'id', 'type' => 'integer'], ...]], ...]
O DataImportForm chama listDatabases() dentro de um try/catch no mount():
se a introspecção falhar (permissão, driver sem catálogo), o form ainda renderiza
— só sem a lista de bancos pré-carregada.
Staging do CSV
DataImportForm::onUpload($tempPath) lê o cabeçalho (auto-detecta ; ou ,),
monta a lista de colunas de origem e copia o arquivo para um nome único de
scratch (tmp/<uniqid>.<ext>), guardado na prop pública csvTempPath. É esse
caminho — não o $tempPath original da request — que onImport() valida com
file_exists() e passa para DataImportService::import(), e cujo basename() vai
para o file_name do ImportLog.
Telas e API
| Peça | O que é |
|---|---|
Sys\DataImportDashboard |
Grid de templates de importação salvos (/app/admin/..., rota data_import). |
Sys\DataImportTemplateForm |
Criar/editar um template — mapeamento CSV → colunas. |
Sys\DataImportForm |
Upload do CSV + escolha do template + modo (atomic/partial). |
Sys\DataImportUseForm |
Execução de uma importação a partir de um template existente. |
Sys\DataImportLogView |
Resultado da última execução: linhas importadas, erros por linha. |
App\Models\Sys\ImportTemplate (+ ImportTemplateGroup/ImportTemplateUser) |
Template + ACL de quem pode usá-lo (isAdmin()/getVisibleTemplates()). |
Route::apiResource('import-templates', ...) |
CRUD JSON via Sys\ImportTemplateApiController (Mad\Rest\ApiResourceController), grupo mad.auth em routes/modules/admin.php — ver REST API para o padrão geral desse controller. |
Schema em database/migrations/0001_01_01_000100_create_mad_schema.php:
mad_sys_import_template, mad_sys_import_template_group,
mad_sys_import_template_user, mad_sys_import_log.
Antes de usar em produção
- Escreva testes de feature cobrindo pelo menos:
atomiccom 1 linha inválida (deve reverter tudo),partialcom 1 linha inválida no meio de um lote de 100 (deve commitar as 99 boas), e um CSV com delimitador alternativo. - O
databasede destino é um parâmetro livre — trate a configuração do template como algo que só usuários de confiança devem poder editar (a ACL deImportTemplateexiste exatamente para isso). - Revise o mapeamento de colunas de um template antes de rodar contra dados reais; não há dry-run embutido na UI atual.