Mager updated

This commit is contained in:
Simon Pocrnjič
2025-09-27 17:45:55 +02:00
parent d17e34941b
commit 7227c888d4
74 changed files with 6339 additions and 342 deletions
@@ -0,0 +1,432 @@
<?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');
}
}