added download button for orignal import csv file
This commit is contained in:
parent
2968bcf3f8
commit
9cc1b7072c
|
|
@ -9,7 +9,6 @@
|
||||||
use App\Models\ImportEvent;
|
use App\Models\ImportEvent;
|
||||||
use App\Models\ImportTemplate;
|
use App\Models\ImportTemplate;
|
||||||
use App\Services\CsvImportService;
|
use App\Services\CsvImportService;
|
||||||
use App\Services\Import\ImportServiceV2;
|
|
||||||
use App\Services\Import\ImportSimulationServiceV2;
|
use App\Services\Import\ImportSimulationServiceV2;
|
||||||
use App\Services\ImportProcessor;
|
use App\Services\ImportProcessor;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -187,9 +186,10 @@ public function store(Request $request)
|
||||||
public function process(Import $import, Request $request, ImportProcessor $processor)
|
public function process(Import $import, Request $request, ImportProcessor $processor)
|
||||||
{
|
{
|
||||||
$import->update(['status' => 'validating', 'started_at' => now()]);
|
$import->update(['status' => 'validating', 'started_at' => now()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = $processor->process($import, user: $request->user());
|
$result = $processor->process($import, user: $request->user());
|
||||||
|
|
||||||
return response()->json($result);
|
return response()->json($result);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Log::error('Import processing failed', [
|
\Log::error('Import processing failed', [
|
||||||
|
|
@ -197,12 +197,12 @@ public function process(Import $import, Request $request, ImportProcessor $proce
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString(),
|
'trace' => $e->getTraceAsString(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$import->update(['status' => 'failed']);
|
$import->update(['status' => 'failed']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Import processing failed: ' . $e->getMessage(),
|
'message' => 'Import processing failed: '.$e->getMessage(),
|
||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -712,8 +712,6 @@ public function simulatePayments(Import $import, Request $request)
|
||||||
* templates. For payments templates, payment-specific summaries/entities will be included
|
* templates. For payments templates, payment-specific summaries/entities will be included
|
||||||
* automatically by the simulation service when mappings contain the payment root.
|
* automatically by the simulation service when mappings contain the payment root.
|
||||||
*
|
*
|
||||||
* @param Import $import
|
|
||||||
* @param Request $request
|
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function simulate(Import $import, Request $request)
|
public function simulate(Import $import, Request $request)
|
||||||
|
|
@ -829,4 +827,19 @@ public function destroy(Request $request, Import $import)
|
||||||
|
|
||||||
return back()->with('success', 'Import deleted successfully');
|
return back()->with('success', 'Import deleted successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download the original import file
|
||||||
|
public function download(Import $import)
|
||||||
|
{
|
||||||
|
// Verify file exists
|
||||||
|
if (! $import->disk || ! $import->path || ! Storage::disk($import->disk)->exists($import->path)) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'File not found',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = $import->original_name ?? 'import_'.$import->uuid;
|
||||||
|
|
||||||
|
return Storage::disk($import->disk)->download($import->path, $fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1094,6 +1094,16 @@ async function fetchEvents() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadImport() {
|
||||||
|
if (!importId.value) return;
|
||||||
|
try {
|
||||||
|
const url = route("imports.download", { import: importId.value });
|
||||||
|
window.location.href = url;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Download failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simulation (generic or payments) state
|
// Simulation (generic or payments) state
|
||||||
const showPaymentSim = ref(false);
|
const showPaymentSim = ref(false);
|
||||||
const paymentSimLoading = ref(false);
|
const paymentSimLoading = ref(false);
|
||||||
|
|
@ -1339,6 +1349,7 @@ async function fetchSimulation() {
|
||||||
:can-process="canProcess"
|
:can-process="canProcess"
|
||||||
:selected-mappings-count="selectedMappingsCount"
|
:selected-mappings-count="selectedMappingsCount"
|
||||||
@preview="openPreview"
|
@preview="openPreview"
|
||||||
|
@download="downloadImport"
|
||||||
@save-mappings="saveMappings"
|
@save-mappings="saveMappings"
|
||||||
@process-import="processImport"
|
@process-import="processImport"
|
||||||
@simulate="openSimulation"
|
@simulate="openSimulation"
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import {
|
||||||
ArrowPathIcon,
|
ArrowPathIcon,
|
||||||
BeakerIcon,
|
BeakerIcon,
|
||||||
ArrowDownOnSquareIcon,
|
ArrowDownOnSquareIcon,
|
||||||
|
ArrowDownTrayIcon,
|
||||||
} from "@heroicons/vue/24/outline";
|
} from "@heroicons/vue/24/outline";
|
||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from "@/Components/ui/button";
|
||||||
import { Badge } from '@/Components/ui/badge';
|
import { Badge } from "@/Components/ui/badge";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
importId: [Number, String],
|
importId: [Number, String],
|
||||||
|
|
@ -16,54 +17,68 @@ const props = defineProps({
|
||||||
canProcess: Boolean,
|
canProcess: Boolean,
|
||||||
selectedMappingsCount: Number,
|
selectedMappingsCount: Number,
|
||||||
});
|
});
|
||||||
const emits = defineEmits(["preview", "save-mappings", "process-import", "simulate"]);
|
const emits = defineEmits([
|
||||||
|
"preview",
|
||||||
|
"save-mappings",
|
||||||
|
"process-import",
|
||||||
|
"simulate",
|
||||||
|
"download",
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap gap-2 items-center" v-if="!isCompleted">
|
<div class="flex flex-wrap gap-2 items-center">
|
||||||
|
<!-- Download button - always visible -->
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@click.prevent="$emit('preview')"
|
@click.prevent="$emit('download')"
|
||||||
:disabled="!importId"
|
:disabled="!importId"
|
||||||
|
title="Preznesi originalno uvozno datoteko"
|
||||||
>
|
>
|
||||||
<EyeIcon class="h-4 w-4 mr-2" />
|
<ArrowDownTrayIcon class="h-4 w-4 mr-2" />
|
||||||
Predogled vrstic
|
Presnemi datoteko
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
class="bg-orange-600 hover:bg-orange-700"
|
|
||||||
@click.prevent="$emit('save-mappings')"
|
|
||||||
:disabled="!importId || processing || savingMappings || isCompleted"
|
|
||||||
title="Shrani preslikave za ta uvoz"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="savingMappings"
|
|
||||||
class="inline-block h-4 w-4 mr-2 border-2 border-white/70 border-t-transparent rounded-full animate-spin"
|
|
||||||
></span>
|
|
||||||
<ArrowPathIcon v-else class="h-4 w-4 mr-2" />
|
|
||||||
<span>Shrani preslikave</span>
|
|
||||||
<Badge
|
|
||||||
v-if="selectedMappingsCount"
|
|
||||||
variant="secondary"
|
|
||||||
class="ml-2 text-xs"
|
|
||||||
>{{ selectedMappingsCount }}</Badge>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
class="bg-purple-600 hover:bg-purple-700"
|
|
||||||
@click.prevent="$emit('process-import')"
|
|
||||||
:disabled="!canProcess"
|
|
||||||
>
|
|
||||||
<BeakerIcon class="h-4 w-4 mr-2" />
|
|
||||||
{{ processing ? "Obdelava…" : "Obdelaj uvoz" }}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
class="bg-blue-600 hover:bg-blue-700"
|
|
||||||
@click.prevent="$emit('simulate')"
|
|
||||||
:disabled="!importId || processing"
|
|
||||||
>
|
|
||||||
<ArrowDownOnSquareIcon class="h-4 w-4 mr-2" />
|
|
||||||
Simulacija vnosa
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<!-- Other action buttons - only when not completed -->
|
||||||
|
<div class="flex flex-wrap gap-2 items-center" v-if="!isCompleted">
|
||||||
|
<Button variant="secondary" @click.prevent="$emit('preview')" :disabled="!importId">
|
||||||
|
<EyeIcon class="h-4 w-4 mr-2" />
|
||||||
|
Predogled vrstic
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
class="bg-orange-600 hover:bg-orange-700"
|
||||||
|
@click.prevent="$emit('save-mappings')"
|
||||||
|
:disabled="!importId || processing || savingMappings || isCompleted"
|
||||||
|
title="Shrani preslikave za ta uvoz"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="savingMappings"
|
||||||
|
class="inline-block h-4 w-4 mr-2 border-2 border-white/70 border-t-transparent rounded-full animate-spin"
|
||||||
|
></span>
|
||||||
|
<ArrowPathIcon v-else class="h-4 w-4 mr-2" />
|
||||||
|
<span>Shrani preslikave</span>
|
||||||
|
<Badge v-if="selectedMappingsCount" variant="secondary" class="ml-2 text-xs">{{
|
||||||
|
selectedMappingsCount
|
||||||
|
}}</Badge>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
class="bg-purple-600 hover:bg-purple-700"
|
||||||
|
@click.prevent="$emit('process-import')"
|
||||||
|
:disabled="!canProcess"
|
||||||
|
>
|
||||||
|
<BeakerIcon class="h-4 w-4 mr-2" />
|
||||||
|
{{ processing ? "Obdelava…" : "Obdelaj uvoz" }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700"
|
||||||
|
@click.prevent="$emit('simulate')"
|
||||||
|
:disabled="!importId || processing"
|
||||||
|
>
|
||||||
|
<ArrowDownOnSquareIcon class="h-4 w-4 mr-2" />
|
||||||
|
Simulacija vnosa
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,7 @@
|
||||||
Route::get('imports/{import}/missing-keyref-rows', [ImportController::class, 'missingKeyrefRows'])->name('imports.missing-keyref-rows');
|
Route::get('imports/{import}/missing-keyref-rows', [ImportController::class, 'missingKeyrefRows'])->name('imports.missing-keyref-rows');
|
||||||
Route::get('imports/{import}/missing-keyref-csv', [ImportController::class, 'exportMissingKeyrefCsv'])->name('imports.missing-keyref-csv');
|
Route::get('imports/{import}/missing-keyref-csv', [ImportController::class, 'exportMissingKeyrefCsv'])->name('imports.missing-keyref-csv');
|
||||||
Route::get('imports/{import}/preview', [ImportController::class, 'preview'])->name('imports.preview');
|
Route::get('imports/{import}/preview', [ImportController::class, 'preview'])->name('imports.preview');
|
||||||
|
Route::get('imports/{import}/download', [ImportController::class, 'download'])->name('imports.download');
|
||||||
Route::get('imports/{import}/missing-contracts', [ImportController::class, 'missingContracts'])->name('imports.missing-contracts');
|
Route::get('imports/{import}/missing-contracts', [ImportController::class, 'missingContracts'])->name('imports.missing-contracts');
|
||||||
Route::post('imports/{import}/options', [ImportController::class, 'updateOptions'])->name('imports.options');
|
Route::post('imports/{import}/options', [ImportController::class, 'updateOptions'])->name('imports.options');
|
||||||
// Generic simulation endpoint (new) – provides projected effects for first N rows regardless of payments template
|
// Generic simulation endpoint (new) – provides projected effects for first N rows regardless of payments template
|
||||||
|
|
|
||||||
72
tests/Feature/ImportDownloadTest.php
Normal file
72
tests/Feature/ImportDownloadTest.php
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Import;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
it('downloads the original import file', function () {
|
||||||
|
// Create a test file
|
||||||
|
$uuid = (string) Str::uuid();
|
||||||
|
$disk = 'local';
|
||||||
|
$path = "imports/{$uuid}.csv";
|
||||||
|
$csv = "email,reference\nalpha@example.com,REF-1\n";
|
||||||
|
Storage::disk($disk)->put($path, $csv);
|
||||||
|
|
||||||
|
// Authenticate a user
|
||||||
|
$user = User::factory()->create();
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
// Create import record
|
||||||
|
$import = Import::create([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'import_template_id' => null,
|
||||||
|
'client_id' => null,
|
||||||
|
'source_type' => 'csv',
|
||||||
|
'file_name' => basename($path),
|
||||||
|
'original_name' => 'test-import.csv',
|
||||||
|
'disk' => $disk,
|
||||||
|
'path' => $path,
|
||||||
|
'size' => strlen($csv),
|
||||||
|
'status' => 'uploaded',
|
||||||
|
'meta' => ['has_header' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Test download endpoint
|
||||||
|
$response = test()->get(route('imports.download', ['import' => $import->id]));
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
expect($response->headers->get('Content-Disposition'))->toContain('test-import.csv');
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
Storage::disk($disk)->delete($path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 404 when file does not exist', function () {
|
||||||
|
// Authenticate a user
|
||||||
|
$user = User::factory()->create();
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
// Create import record with non-existent file
|
||||||
|
$import = Import::create([
|
||||||
|
'uuid' => (string) Str::uuid(),
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'import_template_id' => null,
|
||||||
|
'client_id' => null,
|
||||||
|
'source_type' => 'csv',
|
||||||
|
'file_name' => 'missing.csv',
|
||||||
|
'original_name' => 'missing.csv',
|
||||||
|
'disk' => 'local',
|
||||||
|
'path' => 'imports/nonexistent.csv',
|
||||||
|
'size' => 0,
|
||||||
|
'status' => 'uploaded',
|
||||||
|
'meta' => ['has_header' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Test download endpoint
|
||||||
|
$response = test()->get(route('imports.download', ['import' => $import->id]));
|
||||||
|
|
||||||
|
$response->assertNotFound();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user