changes 0230092025
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user