webmoney/backend/app/Http/Controllers/Api/ImportController.php
marco 54cccdd095 refactor: migração para desenvolvimento direto no servidor
- Removido README.md padrão do Laravel (backend)
- Removidos scripts de deploy (não mais necessários)
- Atualizado copilot-instructions.md para novo fluxo
- Adicionada documentação de auditoria do servidor
- Sincronizado código de produção com repositório

Novo workflow:
- Trabalhamos diretamente em /root/webmoney (symlink para /var/www/webmoney)
- Mudanças PHP são instantâneas
- Mudanças React requerem 'npm run build'
- Commit após validação funcional
2025-12-19 11:45:32 +01:00

402 lines
13 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ImportMapping;
use App\Models\ImportLog;
use App\Services\Import\ImportService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class ImportController extends Controller
{
protected ImportService $importService;
public function __construct(ImportService $importService)
{
$this->importService = $importService;
}
/**
* Upload file and get preview
*/
public function upload(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'file' => 'required|file|max:10240', // 10MB max
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed',
'errors' => $validator->errors(),
], 422);
}
$file = $request->file('file');
$extension = strtolower($file->getClientOriginalExtension());
// Verificar extensão permitida
if (!in_array($extension, ImportMapping::FILE_TYPES)) {
return response()->json([
'success' => false,
'message' => "Unsupported file type: $extension. Allowed: " . implode(', ', ImportMapping::FILE_TYPES),
], 422);
}
// Salvar arquivo temporariamente
$filename = Str::uuid() . '.' . $extension;
$path = $file->storeAs('imports/temp', $filename);
$fullPath = Storage::path($path);
try {
// Obter preview
$preview = $this->importService->getPreview($fullPath, 15);
return response()->json([
'success' => true,
'data' => [
'temp_file' => $filename,
'original_name' => $file->getClientOriginalName(),
'file_type' => $extension,
'size' => $file->getSize(),
'preview' => $preview,
],
]);
} catch (\Exception $e) {
// Limpar arquivo em caso de erro
Storage::delete($path);
return response()->json([
'success' => false,
'message' => 'Error processing file: ' . $e->getMessage(),
], 500);
}
}
/**
* Get headers from file with specific row
*/
public function getHeaders(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'temp_file' => 'required|string',
'header_row' => 'required|integer|min:0',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$fullPath = Storage::path('imports/temp/' . $request->temp_file);
if (!file_exists($fullPath)) {
return response()->json([
'success' => false,
'message' => 'Temporary file not found. Please upload again.',
], 404);
}
try {
$headers = $this->importService->getHeaders($fullPath, [
'header_row' => $request->header_row,
]);
// Sugerir mapeamentos
$suggestions = $this->importService->suggestMapping($headers);
return response()->json([
'success' => true,
'data' => [
'headers' => $headers,
'suggestions' => $suggestions,
],
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error reading headers: ' . $e->getMessage(),
], 500);
}
}
/**
* Process import with mapping
*/
public function import(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'temp_file' => 'required|string',
'mapping_id' => 'nullable|exists:import_mappings,id',
'column_mappings' => 'required_without:mapping_id|array',
'header_row' => 'required_without:mapping_id|integer|min:0',
'data_start_row' => 'required_without:mapping_id|integer|min:0',
'date_format' => 'nullable|string',
'decimal_separator' => 'nullable|string|max:1',
'thousands_separator' => 'nullable|string|max:1',
'account_id' => 'nullable|exists:accounts,id',
'category_id' => 'nullable|exists:categories,id',
'cost_center_id' => 'nullable|exists:cost_centers,id',
'save_mapping' => 'nullable|boolean',
'mapping_name' => 'required_if:save_mapping,true|nullable|string|max:255',
'bank_name' => 'nullable|string|max:100',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$fullPath = Storage::path('imports/temp/' . $request->temp_file);
if (!file_exists($fullPath)) {
return response()->json([
'success' => false,
'message' => 'Temporary file not found. Please upload again.',
], 404);
}
$userId = auth()->id();
try {
// Usar mapeamento existente ou criar novo
if ($request->mapping_id) {
$mapping = ImportMapping::where('user_id', $userId)
->findOrFail($request->mapping_id);
} else {
$extension = pathinfo($request->temp_file, PATHINFO_EXTENSION);
$mappingData = [
'user_id' => $userId,
'name' => $request->mapping_name ?? 'Importação ' . now()->format('d/m/Y H:i'),
'bank_name' => $request->bank_name,
'file_type' => $extension,
'header_row' => $request->header_row,
'data_start_row' => $request->data_start_row,
'date_format' => $request->date_format ?? 'd/m/Y',
'decimal_separator' => $request->decimal_separator ?? ',',
'thousands_separator' => $request->thousands_separator ?? '.',
'column_mappings' => $request->column_mappings,
'default_account_id' => $request->account_id,
'default_category_id' => $request->category_id,
'default_cost_center_id' => $request->cost_center_id,
'is_active' => $request->save_mapping ?? false,
];
if ($request->save_mapping) {
$mapping = ImportMapping::create($mappingData);
} else {
$mapping = new ImportMapping($mappingData);
// Não definir ID para mapeamento temporário - será tratado no ImportService
}
}
// Executar importação
$importLog = $this->importService->importTransactions(
$fullPath,
$mapping,
$userId,
$request->account_id,
$request->category_id,
$request->cost_center_id
);
// Limpar arquivo temporário
Storage::delete('imports/temp/' . $request->temp_file);
return response()->json([
'success' => true,
'message' => "Importação concluída: {$importLog->imported_rows} transações importadas",
'data' => [
'import_log' => $importLog,
'mapping_saved' => $request->save_mapping && isset($mapping->id) && $mapping->id > 0,
],
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Import failed: ' . $e->getMessage(),
], 500);
}
}
/**
* List saved mappings
*/
public function mappings(Request $request): JsonResponse
{
$mappings = ImportMapping::where('user_id', auth()->id())
->where('is_active', true)
->with(['defaultAccount', 'defaultCategory', 'defaultCostCenter'])
->orderBy('name')
->get();
return response()->json([
'success' => true,
'data' => $mappings,
]);
}
/**
* Get single mapping
*/
public function getMapping(int $id): JsonResponse
{
$mapping = ImportMapping::where('user_id', auth()->id())
->with(['defaultAccount', 'defaultCategory', 'defaultCostCenter'])
->findOrFail($id);
return response()->json([
'success' => true,
'data' => $mapping,
]);
}
/**
* Update mapping
*/
public function updateMapping(Request $request, int $id): JsonResponse
{
$mapping = ImportMapping::where('user_id', auth()->id())
->findOrFail($id);
$validator = Validator::make($request->all(), [
'name' => 'sometimes|string|max:255',
'bank_name' => 'nullable|string|max:100',
'header_row' => 'sometimes|integer|min:0',
'data_start_row' => 'sometimes|integer|min:0',
'date_format' => 'sometimes|string',
'decimal_separator' => 'sometimes|string|max:1',
'thousands_separator' => 'sometimes|string|max:1',
'column_mappings' => 'sometimes|array',
'default_account_id' => 'nullable|exists:accounts,id',
'default_category_id' => 'nullable|exists:categories,id',
'default_cost_center_id' => 'nullable|exists:cost_centers,id',
'is_active' => 'sometimes|boolean',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
$mapping->update($request->all());
return response()->json([
'success' => true,
'data' => $mapping->fresh(),
]);
}
/**
* Delete mapping
*/
public function deleteMapping(int $id): JsonResponse
{
$mapping = ImportMapping::where('user_id', auth()->id())
->findOrFail($id);
$mapping->delete();
return response()->json([
'success' => true,
'message' => 'Mapping deleted successfully',
]);
}
/**
* Get available bank presets
*/
public function presets(): JsonResponse
{
$presets = $this->importService->getAvailablePresets();
return response()->json([
'success' => true,
'data' => $presets,
]);
}
/**
* Create mapping from preset
*/
public function createFromPreset(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'preset' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors(),
], 422);
}
try {
$mapping = $this->importService->createBankPreset(
$request->preset,
auth()->id()
);
return response()->json([
'success' => true,
'data' => $mapping,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* Get import history
*/
public function history(Request $request): JsonResponse
{
$logs = ImportLog::where('user_id', auth()->id())
->with('importMapping')
->orderBy('created_at', 'desc')
->limit(50)
->get();
return response()->json([
'success' => true,
'data' => $logs,
]);
}
/**
* Get mappable fields info
*/
public function fields(): JsonResponse
{
return response()->json([
'success' => true,
'data' => [
'fields' => ImportMapping::MAPPABLE_FIELDS,
'date_formats' => ImportMapping::DATE_FORMATS,
'file_types' => ImportMapping::FILE_TYPES,
],
]);
}
}