changes 0230092025

This commit is contained in:
Simon Pocrnjič
2025-09-30 00:06:47 +02:00
parent 1fddf959f0
commit a2bb75fdcc
31 changed files with 2729 additions and 628 deletions
+55 -34
View File
@@ -4,16 +4,15 @@
use App\Models\Client;
use App\Models\Import;
use App\Models\ImportTemplate;
use App\Models\ImportRow;
use App\Models\ImportEvent;
use App\Models\ImportTemplate;
use App\Services\CsvImportService;
use App\Services\ImportProcessor;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Inertia\Inertia;
use App\Services\CsvImportService;
class ImportController extends Controller
{
@@ -64,7 +63,7 @@ public function index(Request $request)
'full_name' => $imp->client->person->full_name,
] : null,
] : null,
'template' => $imp->template ? [ 'id' => $imp->template->id, 'name' => $imp->template->name ] : null,
'template' => $imp->template ? ['id' => $imp->template->id, 'name' => $imp->template->name] : null,
];
}, $imports['data']);
@@ -99,7 +98,6 @@ public function create(Request $request)
DB::raw('person.full_name as name'),
]);
return Inertia::render('Imports/Create', [
'templates' => $templates,
'clients' => $clients,
@@ -129,7 +127,7 @@ public function store(Request $request)
// Resolve client_uuid to client_id if provided
$clientId = null;
if (!empty($validated['client_uuid'] ?? null)) {
if (! empty($validated['client_uuid'] ?? null)) {
$clientId = Client::where('uuid', $validated['client_uuid'])->value('id');
}
@@ -163,6 +161,7 @@ public function process(Import $import, Request $request, ImportProcessor $proce
{
$import->update(['status' => 'validating', 'started_at' => now()]);
$result = $processor->process($import, user: $request->user());
return response()->json($result);
}
@@ -188,25 +187,44 @@ public function columns(Request $request, Import $import, CsvImportService $csv)
if ($tplDelimiter) {
$explicitDelimiter = (string) $tplDelimiter;
}
} elseif (!empty($import->meta['forced_delimiter'] ?? null)) {
} elseif (! empty($import->meta['forced_delimiter'] ?? null)) {
$explicitDelimiter = (string) $import->meta['forced_delimiter'];
}
// Only implement CSV/TSV detection for now; others can be added later
if (!in_array($import->source_type, ['csv','txt'])) {
return response()->json([
'columns' => [],
'note' => 'Column preview supported for CSV/TXT at this step.',
]);
// Prefer CSV/TXT; if source_type is unknown, attempt best-effort based on file extension
$treatAsText = in_array($import->source_type, ['csv', 'txt']);
if (! $treatAsText) {
$orig = strtolower(pathinfo($import->original_name ?? '', PATHINFO_EXTENSION));
if (in_array($orig, ['csv', 'txt'])) {
$treatAsText = true;
}
}
$fullPath = Storage::disk($import->disk)->path($import->path);
if ($explicitDelimiter !== null && $explicitDelimiter !== '') {
$columns = $csv->parseColumnsFromCsv($fullPath, $explicitDelimiter, $hasHeader);
$delimiter = $explicitDelimiter;
} else {
[$delimiter, $columns] = $csv->detectColumnsFromCsv($fullPath, $hasHeader);
}
$fullPath = Storage::disk($import->disk)->path($import->path);
$note = '';
if ($treatAsText) {
if ($explicitDelimiter !== null && $explicitDelimiter !== '') {
$columns = $csv->parseColumnsFromCsv($fullPath, $explicitDelimiter, $hasHeader);
$delimiter = $explicitDelimiter;
} else {
[$delimiter, $columns] = $csv->detectColumnsFromCsv($fullPath, $hasHeader);
// Backstop: if single column but file clearly has separators, try common ones
if (is_array($columns) && count($columns) <= 1) {
foreach ([';', "\t", '|', ' ', ','] as $try) {
$alt = $csv->parseColumnsFromCsv($fullPath, $try, $hasHeader);
if (is_array($alt) && count($alt) > 1) {
$delimiter = $try;
$columns = $alt;
$note = 'Delimiter auto-detection backstopped to '.json_encode($try);
break;
}
}
}
}
} else {
// Best-effort: try detect anyway
[$delimiter, $columns] = $csv->detectColumnsFromCsv($fullPath, $hasHeader);
}
// Save meta
$meta = $import->meta ?? [];
@@ -225,6 +243,7 @@ public function columns(Request $request, Import $import, CsvImportService $csv)
'columns' => $columns,
'has_header' => $hasHeader,
'detected_delimiter' => $delimiter,
'note' => $note,
]);
}
@@ -236,9 +255,9 @@ public function saveMappings(Request $request, Import $import)
$data = $request->validate([
'mappings' => 'required|array',
'mappings.*.source_column' => 'required|string',
'mappings.*.entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts',
'mappings.*.entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts,client_cases',
'mappings.*.target_field' => 'required|string',
'mappings.*.transform' => 'nullable|string|in:trim,upper,lower',
'mappings.*.transform' => 'nullable|string|in:trim,upper,lower,decimal,ref',
'mappings.*.apply_mode' => 'nullable|string|in:insert,update,both',
'mappings.*.options' => 'nullable|array',
]);
@@ -247,14 +266,14 @@ public function saveMappings(Request $request, Import $import)
$now = now();
$existing = \DB::table('import_mappings')
->where('import_id', $import->id)
->get(['id','source_column','position']);
->get(['id', 'source_column', 'position']);
$bySource = [];
$dupes = [];
foreach ($existing as $row) {
$src = (string) $row->source_column;
if (!array_key_exists($src, $bySource)) {
$bySource[$src] = [ 'id' => $row->id, 'position' => $row->position ];
if (! array_key_exists($src, $bySource)) {
$bySource[$src] = ['id' => $row->id, 'position' => $row->position];
} else {
$dupes[$src] = ($dupes[$src] ?? []);
$dupes[$src][] = $row->id;
@@ -262,7 +281,9 @@ public function saveMappings(Request $request, Import $import)
}
$basePosition = (int) (\DB::table('import_mappings')->where('import_id', $import->id)->max('position') ?? -1);
$inserted = 0; $updated = 0; $deduped = 0;
$inserted = 0;
$updated = 0;
$deduped = 0;
foreach ($data['mappings'] as $pos => $m) {
$src = (string) $m['source_column'];
@@ -281,7 +302,7 @@ public function saveMappings(Request $request, Import $import)
\DB::table('import_mappings')->where('id', $bySource[$src]['id'])->update($payload);
$updated++;
// Remove duplicates if any
if (!empty($dupes[$src])) {
if (! empty($dupes[$src])) {
$deleted = \DB::table('import_mappings')->whereIn('id', $dupes[$src])->delete();
$deduped += (int) $deleted;
unset($dupes[$src]);
@@ -325,8 +346,9 @@ public function getMappings(Import $import)
'transform',
'apply_mode',
'options',
'position'
'position',
]);
return response()->json(['mappings' => $rows]);
}
@@ -339,7 +361,8 @@ public function getEvents(Import $import)
->where('import_id', $import->id)
->orderByDesc('id')
->limit($limit)
->get(['id','created_at','level','event','message','import_row_id','context']);
->get(['id', 'created_at', 'level', 'event', 'message', 'import_row_id', 'context']);
return response()->json(['events' => $events]);
}
@@ -363,7 +386,6 @@ public function show(Import $import)
'clients.uuid as client_uuid',
]);
$clients = Client::query()
->join('person', 'person.id', '=', 'clients.person_id')
->orderBy('person.full_name')
@@ -371,7 +393,7 @@ public function show(Import $import)
->get([
'clients.id',
'clients.uuid',
'person.full_name as name'
'person.full_name as name',
]);
// Import client
@@ -383,7 +405,6 @@ public function show(Import $import)
'person.full_name as name',
]);
// Render a dedicated page to continue the import
return Inertia::render('Imports/Import', [
'import' => [
@@ -398,11 +419,11 @@ public function show(Import $import)
'imported_rows' => $import->imported_rows,
'invalid_rows' => $import->invalid_rows,
'valid_rows' => $import->valid_rows,
'finished_at' => $import->finished_at
'finished_at' => $import->finished_at,
],
'templates' => $templates,
'clients' => $clients,
'client' => $client
'client' => $client,
]);
}
}
@@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers;
use App\Models\ImportEntity;
use Illuminate\Http\Request;
class ImportEntityController extends Controller
{
public function index()
{
$entities = ImportEntity::query()
->orderByRaw("(ui->>'order')::int nulls last")
->get();
// Fallback: if no entities are seeded yet, return a sensible default set
if ($entities->isEmpty()) {
$entities = collect([
[
'key' => 'person',
'canonical_root' => 'person',
'label' => 'Person',
'fields' => ['first_name', 'last_name', 'full_name', 'gender', 'birthday', 'tax_number', 'social_security_number', 'description'],
'ui' => ['order' => 1],
],
[
'key' => 'person_addresses',
'canonical_root' => 'address',
'label' => 'Person Addresses',
'fields' => ['address', 'country', 'type_id', 'description'],
'ui' => ['order' => 2],
],
[
'key' => 'person_phones',
'canonical_root' => 'phone',
'label' => 'Person Phones',
'fields' => ['nu', 'country_code', 'type_id', 'description'],
'ui' => ['order' => 3],
],
[
'key' => 'emails',
'canonical_root' => 'email',
'label' => 'Emails',
'fields' => ['value', 'is_primary', 'label'],
'ui' => ['order' => 4],
],
[
'key' => 'contracts',
'canonical_root' => 'contract',
'label' => 'Contracts',
'fields' => ['reference', 'start_date', 'end_date', 'description', 'type_id', 'client_case_id'],
'ui' => ['order' => 5],
],
[
'key' => 'accounts',
'canonical_root' => 'account',
'label' => 'Accounts',
'fields' => ['reference', 'balance_amount', 'contract_id', 'contract_reference', 'type_id', 'active', 'description'],
'ui' => ['order' => 6],
],
]);
} else {
// Ensure fields are arrays for frontend consumption
$entities = $entities->map(function ($ent) {
$ent->fields = is_array($ent->fields) ? $ent->fields : [];
return $ent;
});
}
return response()->json(['entities' => $entities]);
}
public function suggest(Request $request)
{
$cols = $request->input('columns', []);
if (! is_array($cols)) {
$cols = [];
}
$entities = ImportEntity::all();
$suggestions = [];
foreach ($cols as $col) {
$s = $this->suggestFor($col, $entities);
if ($s) {
$suggestions[$col] = $s;
}
}
return response()->json(['suggestions' => $suggestions]);
}
private function suggestFor(string $source, $entities): ?array
{
$s = trim(mb_strtolower($source));
foreach ($entities as $ent) {
$rules = (array) ($ent->rules ?? []);
foreach ($rules as $rule) {
$pattern = $rule['pattern'] ?? null;
$field = $rule['field'] ?? null;
if (! $pattern || ! $field) {
continue;
}
if (@preg_match($pattern, $s)) {
return [
'entity' => $ent->key, // UI key (plural except person)
'field' => $field,
'canonical_root' => $ent->canonical_root,
];
}
}
}
return null;
}
}
+136 -24
View File
@@ -2,10 +2,13 @@
namespace App\Http\Controllers;
use App\Models\Action;
use App\Models\Client;
use App\Models\Decision;
use App\Models\Import;
use App\Models\ImportTemplate;
use App\Models\ImportTemplateMapping;
use App\Models\Client;
use App\Models\Segment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -21,7 +24,7 @@ public function index()
->get();
return Inertia::render('Imports/Templates/Index', [
'templates' => $templates->map(fn($t) => [
'templates' => $templates->map(fn ($t) => [
'uuid' => $t->uuid,
'name' => $t->name,
'description' => $t->description,
@@ -48,8 +51,22 @@ public function create()
DB::raw('person.full_name as name'),
]);
$segments = Segment::query()->orderBy('name')->get(['id', 'name']);
$decisions = Decision::query()->orderBy('name')->get(['id', 'name']);
$actions = Action::with(['decisions:id,name'])
->orderBy('name')
->get(['id', 'name'])
->map(fn ($a) => [
'id' => $a->id,
'name' => $a->name,
'decisions' => $a->decisions->map(fn ($d) => ['id' => $d->id, 'name' => $d->name])->values(),
]);
return Inertia::render('Imports/Templates/Create', [
'clients' => $clients,
'segments' => $segments,
'decisions' => $decisions,
'actions' => $actions,
]);
}
@@ -57,8 +74,15 @@ public function store(Request $request)
{
// Normalize payload to be resilient to UI variations
$raw = $request->all();
// Allow passing default segment/decision either inside meta or as top-level
if (isset($raw['segment_id']) && ! isset($raw['meta']['segment_id'])) {
$raw['meta']['segment_id'] = $raw['segment_id'];
}
if (isset($raw['decision_id']) && ! isset($raw['meta']['decision_id'])) {
$raw['meta']['decision_id'] = $raw['decision_id'];
}
// Resolve client by uuid if provided, or cast string numeric to int
if (!empty($raw['client_uuid'] ?? null)) {
if (! empty($raw['client_uuid'] ?? null)) {
$raw['client_id'] = Client::where('uuid', $raw['client_uuid'])->value('id');
} elseif (isset($raw['client_id']) && is_string($raw['client_id']) && ctype_digit($raw['client_id'])) {
$raw['client_id'] = (int) $raw['client_id'];
@@ -66,8 +90,13 @@ public function store(Request $request)
// Normalize entities to array of strings
if (isset($raw['entities']) && is_array($raw['entities'])) {
$raw['entities'] = array_values(array_filter(array_map(function ($e) {
if (is_string($e)) return $e;
if (is_array($e) && array_key_exists('value', $e)) return (string) $e['value'];
if (is_string($e)) {
return $e;
}
if (is_array($e) && array_key_exists('value', $e)) {
return (string) $e['value'];
}
return null;
}, $raw['entities'])));
}
@@ -84,14 +113,29 @@ public function store(Request $request)
'entities.*' => 'string|in:person,person_addresses,person_phones,emails,accounts,contracts',
'mappings' => 'array',
'mappings.*.source_column' => 'required|string',
'mappings.*.entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts',
'mappings.*.entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts,client_cases',
'mappings.*.target_field' => 'nullable|string',
'mappings.*.transform' => 'nullable|string|max:50',
'mappings.*.apply_mode' => 'nullable|string|in:insert,update,both',
'mappings.*.options' => 'nullable|array',
'mappings.*.position' => 'nullable|integer',
'meta' => 'nullable|array',
'meta.segment_id' => 'nullable|integer|exists:segments,id',
'meta.decision_id' => 'nullable|integer|exists:decisions,id',
'meta.action_id' => 'nullable|integer|exists:actions,id',
])->validate();
// Ensure decision belongs to action if both provided in meta
$meta = $data['meta'] ?? [];
if (! empty($meta['action_id']) && ! empty($meta['decision_id'])) {
$belongs = \DB::table('action_decision')
->where('action_id', $meta['action_id'])
->where('decision_id', $meta['decision_id'])
->exists();
if (! $belongs) {
return back()->withErrors(['meta.decision_id' => 'Selected decision is not associated with the chosen action.'])->withInput();
}
}
$template = null;
DB::transaction(function () use (&$template, $request, $data) {
$template = ImportTemplate::create([
@@ -104,9 +148,12 @@ public function store(Request $request)
'user_id' => $request->user()?->id,
'client_id' => $data['client_id'] ?? null,
'is_active' => $data['is_active'] ?? true,
'meta' => [
'meta' => array_filter([
'entities' => $data['entities'] ?? [],
],
'segment_id' => data_get($data, 'meta.segment_id'),
'decision_id' => data_get($data, 'meta.decision_id'),
'action_id' => data_get($data, 'meta.action_id'),
], fn ($v) => ! is_null($v) && $v !== ''),
]);
foreach (($data['mappings'] ?? []) as $m) {
@@ -144,6 +191,17 @@ public function edit(ImportTemplate $template)
DB::raw('person.full_name as name'),
]);
$segments = Segment::query()->orderBy('name')->get(['id', 'name']);
$decisions = Decision::query()->orderBy('name')->get(['id', 'name']);
$actions = Action::with(['decisions:id,name'])
->orderBy('name')
->get(['id', 'name'])
->map(fn ($a) => [
'id' => $a->id,
'name' => $a->name,
'decisions' => $a->decisions->map(fn ($d) => ['id' => $d->id, 'name' => $d->name])->values(),
]);
return Inertia::render('Imports/Templates/Edit', [
'template' => [
'uuid' => $template->uuid,
@@ -155,9 +213,12 @@ public function edit(ImportTemplate $template)
'client_uuid' => $template->client?->uuid,
'sample_headers' => $template->sample_headers,
'meta' => $template->meta,
'mappings' => $template->mappings()->orderBy('position')->get(['id','entity','source_column','target_field','transform','apply_mode','options','position']),
'mappings' => $template->mappings()->orderBy('position')->get(['id', 'entity', 'source_column', 'target_field', 'transform', 'apply_mode', 'options', 'position']),
],
'clients' => $clients,
'segments' => $segments,
'decisions' => $decisions,
'actions' => $actions,
]);
}
@@ -171,7 +232,7 @@ public function addMapping(Request $request, ImportTemplate $template)
}
$data = validator($raw, [
'source_column' => 'required|string',
'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts',
'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts,client_cases',
'target_field' => 'nullable|string',
'transform' => 'nullable|string|in:trim,upper,lower',
'apply_mode' => 'nullable|string|in:insert,update,both',
@@ -193,6 +254,7 @@ public function addMapping(Request $request, ImportTemplate $template)
'options' => $data['options'] ?? $existing->options,
'position' => $data['position'] ?? $existing->position,
]);
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('info', 'Mapping already existed. Updated existing mapping.');
} else {
@@ -207,6 +269,7 @@ public function addMapping(Request $request, ImportTemplate $template)
'options' => $data['options'] ?? null,
'position' => $position,
]);
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('success', 'Mapping added');
}
@@ -216,7 +279,7 @@ public function addMapping(Request $request, ImportTemplate $template)
public function update(Request $request, ImportTemplate $template)
{
$raw = $request->all();
if (!empty($raw['client_uuid'] ?? null)) {
if (! empty($raw['client_uuid'] ?? null)) {
$raw['client_id'] = Client::where('uuid', $raw['client_uuid'])->value('id');
}
$data = validator($raw, [
@@ -229,16 +292,35 @@ public function update(Request $request, ImportTemplate $template)
'sample_headers' => 'nullable|array',
'meta' => 'nullable|array',
'meta.delimiter' => 'nullable|string|max:4',
'meta.segment_id' => 'nullable|integer|exists:segments,id',
'meta.decision_id' => 'nullable|integer|exists:decisions,id',
'meta.action_id' => 'nullable|integer|exists:actions,id',
])->validate();
// Validate decision/action consistency on update as well
$meta = $data['meta'] ?? [];
if (! empty($meta['action_id']) && ! empty($meta['decision_id'])) {
$belongs = \DB::table('action_decision')
->where('action_id', $meta['action_id'])
->where('decision_id', $meta['decision_id'])
->exists();
if (! $belongs) {
return back()->withErrors(['meta.decision_id' => 'Selected decision is not associated with the chosen action.'])->withInput();
}
}
// Merge meta safely, preserving existing keys when not provided
$newMeta = $template->meta ?? [];
if (array_key_exists('meta', $data) && is_array($data['meta'])) {
$newMeta = array_merge($newMeta, $data['meta']);
// Drop empty delimiter to allow auto-detect
if (array_key_exists('delimiter', $newMeta) && (!is_string($newMeta['delimiter']) || trim((string) $newMeta['delimiter']) === '')) {
if (array_key_exists('delimiter', $newMeta) && (! is_string($newMeta['delimiter']) || trim((string) $newMeta['delimiter']) === '')) {
unset($newMeta['delimiter']);
}
foreach (['segment_id', 'decision_id', 'action_id'] as $k) {
if (array_key_exists($k, $newMeta) && ($newMeta[$k] === '' || is_null($newMeta[$k]))) {
unset($newMeta[$k]);
}
}
}
$template->update([
@@ -266,14 +348,14 @@ public function bulkAddMappings(Request $request, ImportTemplate $template)
}
$data = validator($raw, [
'sources' => 'required|string', // comma and/or newline separated
'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts',
'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts,client_cases',
'default_field' => 'nullable|string', // if provided, used as the field name for all entries
'apply_mode' => 'nullable|string|in:insert,update,both',
'transform' => 'nullable|string|in:trim,upper,lower',
])->validate();
$list = preg_split('/\r?\n|,/', $data['sources']);
$list = array_values(array_filter(array_map(fn($s) => trim($s), $list), fn($s) => $s !== ''));
$list = array_values(array_filter(array_map(fn ($s) => trim($s), $list), fn ($s) => $s !== ''));
if (empty($list)) {
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
@@ -286,14 +368,15 @@ public function bulkAddMappings(Request $request, ImportTemplate $template)
$entity = $data['entity'] ?? null;
$defaultField = $data['default_field'] ?? null; // allows forcing a specific field for all
$created = 0; $updated = 0;
$created = 0;
$updated = 0;
DB::transaction(function () use ($template, $list, $apply, $transform, $entity, $defaultField, $basePosition, &$created, &$updated) {
foreach ($list as $idx => $source) {
$targetField = null;
if ($defaultField) {
$targetField = $entity ? ($entity . '.' . $defaultField) : $defaultField;
$targetField = $entity ? ($entity.'.'.$defaultField) : $defaultField;
} elseif ($entity) {
$targetField = $entity . '.' . $source;
$targetField = $entity.'.'.$source;
}
$existing = ImportTemplateMapping::where('import_template_id', $template->id)
@@ -327,10 +410,17 @@ public function bulkAddMappings(Request $request, ImportTemplate $template)
});
$msg = [];
if ($created) $msg[] = "$created created";
if ($updated) $msg[] = "$updated updated";
if ($created) {
$msg[] = "$created created";
}
if ($updated) {
$msg[] = "$updated updated";
}
$text = 'Mappings processed';
if (!empty($msg)) $text .= ': ' . implode(', ', $msg);
if (! empty($msg)) {
$text .= ': '.implode(', ', $msg);
}
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('success', $text);
}
@@ -338,7 +428,9 @@ public function bulkAddMappings(Request $request, ImportTemplate $template)
// Update an existing mapping
public function updateMapping(Request $request, ImportTemplate $template, ImportTemplateMapping $mapping)
{
if ($mapping->import_template_id !== $template->id) abort(404);
if ($mapping->import_template_id !== $template->id) {
abort(404);
}
$raw = $request->all();
if (array_key_exists('transform', $raw) && $raw['transform'] === '') {
$raw['transform'] = null;
@@ -361,6 +453,7 @@ public function updateMapping(Request $request, ImportTemplate $template, Import
'options' => $data['options'] ?? null,
'position' => $data['position'] ?? $mapping->position,
]);
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('success', 'Mapping updated');
}
@@ -368,8 +461,11 @@ public function updateMapping(Request $request, ImportTemplate $template, Import
// Delete a mapping
public function deleteMapping(ImportTemplate $template, ImportTemplateMapping $mapping)
{
if ($mapping->import_template_id !== $template->id) abort(404);
if ($mapping->import_template_id !== $template->id) {
abort(404);
}
$mapping->delete();
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('success', 'Mapping deleted');
}
@@ -385,11 +481,14 @@ public function reorderMappings(Request $request, ImportTemplate $template)
// Ensure all ids belong to template
$validIds = ImportTemplateMapping::where('import_template_id', $template->id)
->whereIn('id', $ids)->pluck('id')->all();
if (count($validIds) !== count($ids)) abort(422, 'Invalid mapping ids');
if (count($validIds) !== count($ids)) {
abort(422, 'Invalid mapping ids');
}
// Apply new positions
foreach ($ids as $idx => $id) {
ImportTemplateMapping::where('id', $id)->update(['position' => $idx]);
}
return redirect()->route('importTemplates.edit', ['template' => $template->uuid])
->with('success', 'Mappings reordered');
}
@@ -422,7 +521,20 @@ public function applyToImport(Request $request, ImportTemplate $template, Import
$copied++;
}
$import->update(['import_template_id' => $template->id]);
// Merge default actions (segment/decision) into import meta for processing
$importMeta = $import->meta ?? [];
$tplMeta = $template->meta ?? [];
$merged = array_merge($importMeta, array_filter([
'segment_id' => $tplMeta['segment_id'] ?? null,
'decision_id' => $tplMeta['decision_id'] ?? null,
'action_id' => $tplMeta['action_id'] ?? null,
'template_name' => $template->name,
], fn ($v) => ! is_null($v) && $v !== ''));
$import->update([
'import_template_id' => $template->id,
'meta' => $merged,
]);
});
return response()->json(['ok' => true, 'copied' => $copied, 'cleared' => $clear]);