with(['client:id,uuid,person_id', 'client.person:id,full_name']) ->orderBy('name') ->get(); return Inertia::render('Imports/Templates/Index', [ 'templates' => $templates->map(fn($t) => [ 'uuid' => $t->uuid, 'name' => $t->name, 'description' => $t->description, 'source_type' => $t->source_type, 'is_active' => $t->is_active, 'client' => $t->client ? [ 'uuid' => $t->client->uuid, 'name' => $t->client->person?->full_name, ] : null, ]), ]); } // Show the template creation page public function create() { // Preload clients for optional association (global when null) $clients = Client::query() ->join('person', 'person.id', '=', 'clients.person_id') ->orderBy('person.full_name') ->get([ 'clients.id', // kept for compatibility, UI will use uuid 'clients.uuid', DB::raw('person.full_name as name'), ]); return Inertia::render('Imports/Templates/Create', [ 'clients' => $clients, ]); } public function store(Request $request) { // Normalize payload to be resilient to UI variations $raw = $request->all(); // Resolve client by uuid if provided, or cast string numeric to int 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']; } // 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']; return null; }, $raw['entities']))); } $data = validator($raw, [ 'name' => 'required|string|max:100', 'description' => 'nullable|string|max:255', 'source_type' => 'required|string|in:csv,xml,xls,xlsx,json', 'default_record_type' => 'nullable|string|max:50', 'sample_headers' => 'nullable|array', 'client_id' => 'nullable|integer|exists:clients,id', 'is_active' => 'boolean', 'entities' => 'nullable|array', '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.*.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', ])->validate(); $template = null; DB::transaction(function () use (&$template, $request, $data) { $template = ImportTemplate::create([ 'uuid' => (string) Str::uuid(), 'name' => $data['name'], 'description' => $data['description'] ?? null, 'source_type' => $data['source_type'], 'default_record_type' => $data['default_record_type'] ?? null, 'sample_headers' => $data['sample_headers'] ?? null, 'user_id' => $request->user()?->id, 'client_id' => $data['client_id'] ?? null, 'is_active' => $data['is_active'] ?? true, 'meta' => [ 'entities' => $data['entities'] ?? [], ], ]); foreach (($data['mappings'] ?? []) as $m) { ImportTemplateMapping::create([ 'import_template_id' => $template->id, 'entity' => $m['entity'] ?? null, 'source_column' => $m['source_column'], 'target_field' => $m['target_field'] ?? null, 'transform' => $m['transform'] ?? null, 'apply_mode' => $m['apply_mode'] ?? 'both', 'options' => $m['options'] ?? null, 'position' => $m['position'] ?? null, ]); } }); // Redirect to edit page for the newly created template return redirect() ->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', 'Template created successfully.'); } // Edit template UI (by uuid) public function edit(ImportTemplate $template) { // Eager-load mappings $template->load(['mappings']); // Preload clients list (uuid + name) for possible reassignment $clients = Client::query() ->join('person', 'person.id', '=', 'clients.person_id') ->orderBy('person.full_name') ->get([ 'clients.uuid', DB::raw('person.full_name as name'), ]); return Inertia::render('Imports/Templates/Edit', [ 'template' => [ 'uuid' => $template->uuid, 'name' => $template->name, 'description' => $template->description, 'source_type' => $template->source_type, 'default_record_type' => $template->default_record_type, 'is_active' => $template->is_active, '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']), ], 'clients' => $clients, ]); } // Add a new mapping to a template (by uuid) public function addMapping(Request $request, ImportTemplate $template) { // Normalize empty transform to null $raw = $request->all(); if (array_key_exists('transform', $raw) && $raw['transform'] === '') { $raw['transform'] = null; } $data = validator($raw, [ 'source_column' => 'required|string', 'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts', 'target_field' => 'nullable|string', 'transform' => 'nullable|string|in:trim,upper,lower', 'apply_mode' => 'nullable|string|in:insert,update,both', 'options' => 'nullable|array', 'position' => 'nullable|integer', ])->validate(); // Avoid duplicates by source_column within the same template: update if exists $existing = ImportTemplateMapping::where('import_template_id', $template->id) ->where('source_column', $data['source_column']) ->first(); if ($existing) { $existing->update([ 'target_field' => $data['target_field'] ?? $existing->target_field, 'entity' => $data['entity'] ?? $existing->entity, 'transform' => $data['transform'] ?? $existing->transform, 'apply_mode' => $data['apply_mode'] ?? $existing->apply_mode ?? 'both', '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 { $position = $data['position'] ?? (int) (($template->mappings()->max('position') ?? 0) + 1); ImportTemplateMapping::create([ 'import_template_id' => $template->id, 'entity' => $data['entity'] ?? null, 'source_column' => $data['source_column'], 'target_field' => $data['target_field'] ?? null, 'transform' => $data['transform'] ?? null, 'apply_mode' => $data['apply_mode'] ?? 'both', 'options' => $data['options'] ?? null, 'position' => $position, ]); return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', 'Mapping added'); } } // Update template basic fields public function update(Request $request, ImportTemplate $template) { $raw = $request->all(); if (!empty($raw['client_uuid'] ?? null)) { $raw['client_id'] = Client::where('uuid', $raw['client_uuid'])->value('id'); } $data = validator($raw, [ 'name' => 'required|string|max:100', 'description' => 'nullable|string|max:255', 'source_type' => 'required|string|in:csv,xml,xls,xlsx,json', 'default_record_type' => 'nullable|string|max:50', 'client_id' => 'nullable|integer|exists:clients,id', 'is_active' => 'boolean', 'sample_headers' => 'nullable|array', ])->validate(); $template->update([ 'name' => $data['name'], 'description' => $data['description'] ?? null, 'source_type' => $data['source_type'], 'default_record_type' => $data['default_record_type'] ?? null, 'client_id' => $data['client_id'] ?? null, 'is_active' => $data['is_active'] ?? $template->is_active, 'sample_headers' => $data['sample_headers'] ?? $template->sample_headers, ]); return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', 'Template updated'); } // Bulk add multiple mappings from a textarea input public function bulkAddMappings(Request $request, ImportTemplate $template) { // Accept either commas or newlines as separators $raw = $request->all(); if (array_key_exists('transform', $raw) && $raw['transform'] === '') { $raw['transform'] = null; } $data = validator($raw, [ 'sources' => 'required|string', // comma and/or newline separated 'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts', '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 !== '')); if (empty($list)) { return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('warning', 'No valid source columns provided.'); } $basePosition = (int) (($template->mappings()->max('position') ?? 0)); $apply = $data['apply_mode'] ?? 'both'; $transform = $data['transform'] ?? null; $entity = $data['entity'] ?? null; $defaultField = $data['default_field'] ?? null; // allows forcing a specific field for all $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; } elseif ($entity) { $targetField = $entity . '.' . $source; } $existing = ImportTemplateMapping::where('import_template_id', $template->id) ->where('source_column', $source) ->first(); if ($existing) { $existing->update([ 'target_field' => $targetField ?? $existing->target_field, 'entity' => $entity ?? $existing->entity, 'transform' => $transform ?? $existing->transform, 'apply_mode' => $apply ?? $existing->apply_mode ?? 'both', 'options' => $existing->options, // keep existing position ]); $updated++; } else { ImportTemplateMapping::create([ 'import_template_id' => $template->id, 'entity' => $entity, 'source_column' => $source, 'target_field' => $targetField, 'transform' => $transform, 'apply_mode' => $apply, 'options' => null, 'position' => $basePosition + $idx + 1, ]); $created++; } } }); $msg = []; if ($created) $msg[] = "$created created"; if ($updated) $msg[] = "$updated updated"; $text = 'Mappings processed'; if (!empty($msg)) $text .= ': ' . implode(', ', $msg); return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', $text); } // Update an existing mapping public function updateMapping(Request $request, ImportTemplate $template, ImportTemplateMapping $mapping) { if ($mapping->import_template_id !== $template->id) abort(404); $raw = $request->all(); if (array_key_exists('transform', $raw) && $raw['transform'] === '') { $raw['transform'] = null; } $data = validator($raw, [ 'source_column' => 'required|string', 'entity' => 'nullable|string|in:person,person_addresses,person_phones,emails,accounts,contracts', 'target_field' => 'nullable|string', 'transform' => 'nullable|string|in:trim,upper,lower', 'apply_mode' => 'nullable|string|in:insert,update,both', 'options' => 'nullable|array', 'position' => 'nullable|integer', ])->validate(); $mapping->update([ 'source_column' => $data['source_column'], 'entity' => $data['entity'] ?? null, 'target_field' => $data['target_field'] ?? null, 'transform' => $data['transform'] ?? null, 'apply_mode' => $data['apply_mode'] ?? 'both', 'options' => $data['options'] ?? null, 'position' => $data['position'] ?? $mapping->position, ]); return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', 'Mapping updated'); } // Delete a mapping public function deleteMapping(ImportTemplate $template, ImportTemplateMapping $mapping) { if ($mapping->import_template_id !== $template->id) abort(404); $mapping->delete(); return redirect()->route('importTemplates.edit', ['template' => $template->uuid]) ->with('success', 'Mapping deleted'); } // Reorder mappings in bulk public function reorderMappings(Request $request, ImportTemplate $template) { $data = $request->validate([ 'order' => 'required|array', 'order.*' => 'integer', ]); $ids = $data['order']; // 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'); // 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'); } // Apply a template’s mappings to a specific import (copy into import_mappings) public function applyToImport(Request $request, ImportTemplate $template, Import $import) { // optional: clear previous mappings $clear = $request->boolean('clear', true); $copied = 0; DB::transaction(function () use ($clear, $template, $import, &$copied) { if ($clear) { \DB::table('import_mappings')->where('import_id', $import->id)->delete(); } $rows = $template->mappings()->orderBy('position')->get(); foreach ($rows as $row) { \DB::table('import_mappings')->insert([ 'import_id' => $import->id, 'entity' => $row->entity, 'source_column' => $row->source_column, 'target_field' => $row->target_field, 'transform' => $row->transform, 'apply_mode' => $row->apply_mode ?? 'both', 'options' => $row->options, 'position' => $row->position ?? null, 'created_at' => now(), 'updated_at' => now(), ]); $copied++; } $import->update(['import_template_id' => $template->id]); }); return response()->json(['ok' => true, 'copied' => $copied, 'cleared' => $clear]); } // Delete a template and cascade delete its mappings; detach from imports public function destroy(ImportTemplate $template) { DB::transaction(function () use ($template) { // Nullify references from imports to this template \DB::table('imports')->where('import_template_id', $template->id)->update(['import_template_id' => null]); // Delete mappings first (if FK cascade not set) \DB::table('import_template_mappings')->where('import_template_id', $template->id)->delete(); // Delete the template $template->delete(); }); return redirect()->route('importTemplates.index')->with('success', 'Template deleted'); } }