Teren-app/app/Http/Controllers/ImportTemplateController.php
Simon Pocrnjič 7227c888d4 Mager updated
2025-09-27 17:45:55 +02:00

433 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Models\Import;
use App\Models\ImportTemplate;
use App\Models\ImportTemplateMapping;
use App\Models\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Inertia;
class ImportTemplateController extends Controller
{
public function index()
{
$templates = ImportTemplate::query()
->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 templates 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');
}
}