Mass changes
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Contract;
|
||||
use App\Models\Import;
|
||||
use App\Models\ImportEvent;
|
||||
use App\Models\ImportTemplate;
|
||||
@@ -366,6 +368,122 @@ public function getEvents(Import $import)
|
||||
return response()->json(['events' => $events]);
|
||||
}
|
||||
|
||||
// Preview (up to N) raw CSV rows for an import for mapping review
|
||||
public function preview(Import $import, Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'limit' => 'nullable|integer|min:1|max:500',
|
||||
]);
|
||||
$limit = (int) ($validated['limit'] ?? 200);
|
||||
|
||||
// Determine header/delimiter the same way as columns() stored them
|
||||
$meta = $import->meta ?? [];
|
||||
$hasHeader = (bool) ($meta['has_header'] ?? true);
|
||||
// Forced delimiter overrides everything; else detected; fallback comma
|
||||
$delimiter = $meta['forced_delimiter']
|
||||
?? $meta['detected_delimiter']
|
||||
?? ',';
|
||||
|
||||
$rows = [];
|
||||
$columns = [];
|
||||
$truncated = false;
|
||||
$path = Storage::disk($import->disk)->path($import->path);
|
||||
if (! is_readable($path)) {
|
||||
return response()->json([
|
||||
'error' => 'File not readable',
|
||||
], 422);
|
||||
}
|
||||
$fh = @fopen($path, 'r');
|
||||
if (! $fh) {
|
||||
return response()->json([
|
||||
'error' => 'Unable to open file',
|
||||
], 422);
|
||||
}
|
||||
try {
|
||||
if ($hasHeader) {
|
||||
$header = fgetcsv($fh, 0, $delimiter) ?: [];
|
||||
$columns = array_map(function ($h) {
|
||||
return is_string($h) ? trim($h) : (string) $h;
|
||||
}, $header);
|
||||
} else {
|
||||
// Use meta stored columns when available, else infer later from widest row
|
||||
$columns = is_array($meta['columns'] ?? null) ? $meta['columns'] : [];
|
||||
}
|
||||
$count = 0;
|
||||
$widest = count($columns);
|
||||
while (($data = fgetcsv($fh, 0, $delimiter)) !== false) {
|
||||
if ($count >= $limit) {
|
||||
$truncated = true;
|
||||
break;
|
||||
}
|
||||
// Track widest for non-header scenario
|
||||
if (! $hasHeader) {
|
||||
$widest = max($widest, count($data));
|
||||
}
|
||||
$rows[] = $data;
|
||||
$count++;
|
||||
}
|
||||
if (! $hasHeader && $widest > count($columns)) {
|
||||
// Generate positional column labels if missing
|
||||
$columns = [];
|
||||
for ($i = 0; $i < $widest; $i++) {
|
||||
$columns[] = 'col_'.($i + 1);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
// Normalize each row into assoc keyed by columns (pad/truncate as needed)
|
||||
$assocRows = [];
|
||||
foreach ($rows as $r) {
|
||||
$assoc = [];
|
||||
foreach ($columns as $i => $colName) {
|
||||
$assoc[$colName] = array_key_exists($i, $r) ? $r[$i] : null;
|
||||
}
|
||||
$assocRows[] = $assoc;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'columns' => $columns,
|
||||
'rows' => $assocRows,
|
||||
'limit' => $limit,
|
||||
'truncated' => $truncated,
|
||||
'has_header' => $hasHeader,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate application of payment rows for a payments import without persisting changes.
|
||||
* Returns per-row projected balance changes and resolution of contract / account references.
|
||||
*/
|
||||
public function simulatePayments(Import $import, Request $request)
|
||||
{
|
||||
// Delegate to the generic simulate method for backward compatibility.
|
||||
return $this->simulate($import, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic simulation endpoint: projects what would happen if the import were processed
|
||||
* using the first N rows and current saved mappings. Works for both payments and non-payments
|
||||
* templates. For payments templates, payment-specific summaries/entities will be included
|
||||
* automatically by the simulation service when mappings contain the payment root.
|
||||
*/
|
||||
public function simulate(Import $import, Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'limit' => 'nullable|integer|min:1|max:500',
|
||||
'verbose' => 'nullable|boolean',
|
||||
]);
|
||||
$limit = (int) ($validated['limit'] ?? 100);
|
||||
$verbose = (bool) ($validated['verbose'] ?? false);
|
||||
|
||||
$service = app(\App\Services\ImportSimulationService::class);
|
||||
$result = $service->simulate($import, $limit, $verbose);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
// Show an existing import by UUID to continue where left off
|
||||
public function show(Import $import)
|
||||
{
|
||||
@@ -426,4 +544,40 @@ public function show(Import $import)
|
||||
'client' => $client,
|
||||
]);
|
||||
}
|
||||
|
||||
// Delete an import if not finished (statuses allowed: uploaded, mapping, processing_failed etc.)
|
||||
public function destroy(Request $request, Import $import)
|
||||
{
|
||||
// Only allow deletion if not completed or processing
|
||||
if (in_array($import->status, ['completed', 'processing'])) {
|
||||
return back()->with([
|
||||
'ok' => false,
|
||||
'message' => 'Import can not be deleted in its current status.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Attempt to delete stored file
|
||||
try {
|
||||
if ($import->disk && $import->path && Storage::disk($import->disk)->exists($import->path)) {
|
||||
Storage::disk($import->disk)->delete($import->path);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Log event but proceed with deletion
|
||||
ImportEvent::create([
|
||||
'import_id' => $import->id,
|
||||
'user_id' => $request->user()?->getAuthIdentifier(),
|
||||
'event' => 'file_delete_failed',
|
||||
'level' => 'warning',
|
||||
'message' => 'Failed to delete import file: '.$e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Clean up related events/rows optionally (soft approach: rely on FKs if cascade configured)
|
||||
// If not cascaded, we could manually delete; check quickly
|
||||
// Assuming foreign key ON DELETE CASCADE for import_rows & import_events
|
||||
|
||||
$import->delete();
|
||||
|
||||
return back()->with(['ok' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user