]> gitweb.hamatoma.de Git - zentrum.gemeinwohl-gesellschaft.org.git/commitdiff
V0.1.3 Process ExportController drawio2db.py
authorHamatoma <author@hamatoma.de>
Wed, 26 Mar 2025 22:28:54 +0000 (23:28 +0100)
committerHamatoma <author@hamatoma.de>
Wed, 26 Mar 2025 22:28:54 +0000 (23:28 +0100)
- ExportController: $_SERVER['documentroot] replaced by
  FileHelper::documentRoot()
- new module Process
- new drawio2db.py

13 files changed:
CHANGELOG.md
app/Http/Controllers/ExportController.php
app/Http/Controllers/ProcessController.php [new file with mode: 0644]
app/Models/Process.php [new file with mode: 0644]
composer.lock
database/migrations/2025_03_25_125345_create_processes_table.php [new file with mode: 0644]
database/seeders/ProcessSeeder.php [new file with mode: 0644]
resources/views/process/create.blade.php [new file with mode: 0644]
resources/views/process/edit.blade.php [new file with mode: 0644]
resources/views/process/index.blade.php [new file with mode: 0644]
resources/views/process/show.blade.php [new file with mode: 0644]
routes/web.php
scripts/drawio2db.py [new file with mode: 0755]

index 6e84b8ae92862eaa4ee6aec708781beb1181b736..cf2f7d8eed2e59c50bd5f641d4e97669396fdb92 100644 (file)
@@ -1,5 +1,11 @@
 # Änderungen an zentrum
 
+# V0.1.3 Process ExportController drawio2db.py
+
+- ExportController: $_SERVER['documentroot] replaced by FileHelper::documentRoot()
+- new module Process
+- new drawio2db.py
+
 # V0.1.2 Layout: Startmenü oder Startmenü-Public
 - User: neu: isGuest()
 - zentrum.blade: ohne Anmeldung: keine "Verwaltung", "Anmelden"-Button
index d8f58d6ce5bc6f2f540490065f0002fe2b2532fe..8ac43e552158d1a7344f31391c5943bc97f72934 100644 (file)
@@ -35,7 +35,7 @@ class ExportController extends Controller
                     'patterns' => '',
                 ];
             }
-            $path = $_SERVER['DOCUMENT_ROOT'] . "/export";
+            $path = FileHelper::documentRoot() . "/export";
             $patterns = str_replace(['*', '?'], ['.*', '.'], $fields['patterns']);
             $patterns = '/^' . auth()->user()->name . "\\.$patterns/";
             $files = FileHelper::fileInfoList($path, $patterns);
@@ -55,7 +55,7 @@ class ExportController extends Controller
                 'filename' => '',
             ];
         }
-        $path = $_SERVER['DOCUMENT_ROOT'] . "/temp";
+        $path = FileHelper::documentRoot() . "/temp";
         if ($request->btnSubmit === 'btnUpload') {
             if (($file = $request->file('file')) != null) {
                 $relativePath = basename($path);
@@ -88,7 +88,7 @@ class ExportController extends Controller
     public function removeFile(string $nodeEncoded, Request $request)
     {
         $node = FileHelper::decodeUrl($nodeEncoded);
-        $full = $_SERVER['DOCUMENT_ROOT'] . "/export/$node";
+        $full = FileHelper::documentRoot() . "/export/$node";
         if (file_exists($full)) {
             unlink($full);
         }
diff --git a/app/Http/Controllers/ProcessController.php b/app/Http/Controllers/ProcessController.php
new file mode 100644 (file)
index 0000000..7cd1cb1
--- /dev/null
@@ -0,0 +1,332 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\File;
+use App\Models\Change;
+use App\Helpers\Helper;
+use App\Models\Process;
+use App\Helpers\DbHelper;
+use App\Models\SProperty;
+use App\Helpers\FileHelper;
+use App\Helpers\Pagination;
+use App\Helpers\ViewHelper;
+use Illuminate\Http\Request;
+use App\Helpers\ContextLaraKnife;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Facades\Validator;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+
+class ProcessController extends Controller
+{
+    /**
+     * Show the form for creating a new resource.
+     */
+    public function create(Request $request)
+    {
+        if ($request->btnSubmit === 'btnCancel') {
+            $rc = redirect('/process-index');
+        } else {
+            $fields = $request->all();
+            if (count($fields) === 0) {
+                $fields = [
+                    'division_scope' => '',
+                    'activity_scope' => '',
+                    'file' => '',
+                    'owner_id' => auth()->id()
+                ];
+            }
+            $context = new ContextLaraKnife($request, $fields);
+            $optionsDivision = SProperty::optionsByScope('division', $fields['division_scope'], '');
+            $optionsActivity = SProperty::optionsByScope('activity', $fields['activity_scope'], '');
+            $rc = view('process.create', [
+                'context' => $context,
+                'optionsDivision' => $optionsDivision,
+                'optionsActivity' => $optionsActivity
+            ]);
+        }
+        return $rc;
+    }
+    /**
+     * Show the form for editing the specified resource.
+     */
+    public function edit(Process $process, Request $request)
+    {
+        if ($request->btnSubmit === 'btnCancel') {
+            $rc = redirect('/process-index');
+        } else {
+            $fields = $request->all();
+            if (count($fields) === 0) {
+                $fields = [
+                    'serialno' => Process::nextSerialNo(),
+                    'division_scope' => $process->division_scope,
+                    'activity_scope' => $process->activity_scope,
+                    'path' => $process->path,
+                    'title' => $process->title,
+                    'roles' => $process->roles,
+                    'subprocesses' => $process->subprocesses,
+                    'datasources' => $process->datasources,
+                    'texts' => $process->texts,
+                    'info' => $process->info,
+                    'owner_id' => $process->owner_id
+                ];
+            }
+            $context = new ContextLaraKnife($request, $fields, $process);
+            $optionsDivision = SProperty::optionsByScope('division', $process->division_scope, '');
+            $optionsActivity = SProperty::optionsByScope('activity', $process->activity_scope, '');
+            $optionsOwner = DbHelper::comboboxDataOfTable('users', 'name', 'id', $fields['owner_id'], __('<Please select>'));
+            $rc = view('process.edit', [
+                'context' => $context,
+                'optionsDivision' => $optionsDivision,
+                'optionsActivity' => $optionsActivity,
+                'optionsOwner' => $optionsOwner,
+            ]);
+        }
+        return $rc;
+    }
+    /**
+     * Remove the specified resource from storage.
+     */
+    public function destroy(Process $process, Request $request)
+    {
+        if ($request->btnSubmit === 'btnDelete') {
+            $process->delete();
+        }
+        return redirect('/process-index');
+    }
+    /**
+     * Display the database records of the resource.
+     */
+    public function index(Request $request)
+    {
+        if ($request->btnSubmit === 'btnNew') {
+            return redirect('/process-create');
+        } else {
+            $sql = "
+SELECT t0.*,
+  t1.name as division,
+  t2.name as activity,
+  t3.name as owner
+FROM processes t0
+LEFT JOIN sproperties t1 ON t1.id=t0.division_scope
+LEFT JOIN sproperties t2 ON t2.id=t0.activity_scope
+LEFT JOIN users t3 ON t3.id=t0.owner_id
+";
+            $parameters = [];
+            $fields = $request->all();
+            if (count($fields) == 0) {
+                $fields = [
+                    'division' => '',
+                    'activity' => '',
+                    'owner' => '',
+                    'serialno' => '',
+                    'path' => '',
+                    'title' => '',
+                    'roles' => '',
+                    'subprocesses' => '',
+                    'datasources' => '',
+                    'texts' => '',
+                    'info' => '',
+                    '_sortParams' => 'id:asc'
+                ];
+            }
+            $conditions = [];
+            ViewHelper::addConditionComparison($fields, $conditions, $parameters, 'division_scope', 'division');
+            ViewHelper::addConditionComparison($fields, $conditions, $parameters, 'activity_scope', 'activity');
+            ViewHelper::addConditionComparison($fields, $conditions, $parameters, 'owner_id', 'owner');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'path');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'title');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'roles');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'subprocesses');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'datasources');
+            ViewHelper::addConditionPattern($fields, $conditions, $parameters, 'texts');
+            $sql = DbHelper::addConditions($sql, $conditions);
+            $sql = DbHelper::addOrderBy($sql, $fields['_sortParams']);
+            $pagination = new Pagination($sql, $parameters, $fields);
+            $records = $pagination->records;
+            $optionsDivision = SProperty::optionsByScope('division', $fields['division'], 'all');
+            $optionsActivity = SProperty::optionsByScope('activity', $fields['activity'], 'all');
+            $optionsOwner = DbHelper::comboboxDataOfTable('users', 'name', 'id', $fields['owner'], __('all'));
+            $context = new ContextLaraKnife($request, $fields);
+            return view('process.index', [
+                'context' => $context,
+                'records' => $records,
+                'optionsDivision' => $optionsDivision,
+                'optionsActivity' => $optionsActivity,
+                'optionsOwner' => $optionsOwner,
+                'pagination' => $pagination
+            ]);
+        }
+    }
+    public function parseDrawIoXml(string $filename, array &$fields): void
+    {
+        $script = FileHelper::baseDirectory() . '/scripts/drawio2db.py';
+        $output = [];
+        $fullPath = storage_path('app/public/' . $filename);
+        exec("$script $fullPath", $output);
+        $multiline = '';
+        $matches = null;
+        $target = null;
+        $fields['roles'] = $fields['subprocesses'] = $fields['datasources'] = $fields['texts'] = null;
+        foreach ($output as $line) {
+            if (str_starts_with($line, 'Title: ')) {
+                $fields['title'] = substr($line, 7);
+            } elseif (str_starts_with($line, '  ')) {
+                $multiline .= substr($line, 2) . "\n";
+            } elseif (preg_match('/^(Roles|SubProcesses|Data Sources|Texts)/', $line, $matches)) {
+                switch ($target) {
+                    case 'Roles':
+                        $fields['roles'] = $multiline;
+                        break;
+                    case 'SubProcesses':
+                        $fields['subprocesses'] = $multiline;
+                        break;
+                    case 'Data Sources':
+                        $fields['datasources'] = $multiline;
+                        break;
+                    case 'Texts':
+                        $fields['texts'] = $multiline;
+                        break;
+                    default:
+                        break;
+                }
+                $multiline = '';
+                $target = $matches[1];
+            }
+        }
+        if ($target === 'Texts') {
+            $fields['texts'] = $multiline;
+        }
+    }
+
+    /**
+     * Returns the validation rules.
+     * @return array<string, string> The validation rules.
+     */
+    private function rules(bool $isCreate = false): array
+    {
+        $rc = [
+            'serialno' => $isCreate ? '' : 'required',
+            'division_scope' => $isCreate ? '' : 'required',
+            'activity_scope' => $isCreate ? '' : 'required',
+            'path' => $isCreate ? '' : 'required',
+            'title' => $isCreate ? '' : 'required',
+            'roles' => '',
+            'subprocesses' => '',
+            'datasources' => '',
+            'texts' => '',
+            'info' => '',
+            'owner_id' => $isCreate ? '' : 'required'
+        ];
+        return $rc;
+    }
+    public static function routes()
+    {
+        Route::get('/process-index', [ProcessController::class, 'index'])->middleware('auth');
+        Route::post('/process-index', [ProcessController::class, 'index'])->middleware('auth');
+        Route::get('/process-create', [ProcessController::class, 'create'])->middleware('auth');
+        Route::put('/process-store', [ProcessController::class, 'store'])->middleware('auth');
+        Route::post('/process-edit/{process}', [ProcessController::class, 'edit'])->middleware('auth');
+        Route::get('/process-edit/{process}', [ProcessController::class, 'edit'])->middleware('auth');
+        Route::post('/process-update/{process}', [ProcessController::class, 'update'])->middleware('auth');
+        Route::get('/process-show/{process}/delete', [ProcessController::class, 'show'])->middleware('auth');
+        Route::delete('/process-show/{process}/delete', [ProcessController::class, 'destroy'])->middleware('auth');
+    }
+    /**
+     * Display the specified resource.
+     */
+    public function show(Process $process, Request $request)
+    {
+        if ($request->btnSubmit === 'btnCancel') {
+            $rc = redirect('/process-index')->middleware('auth');
+        } else {
+            $optionsDivision = SProperty::optionsByScope('division', $process->division_scope, '');
+            $optionsActivity = SProperty::optionsByScope('activity', $process->activity_scope, '');
+            $optionsOwner = DbHelper::comboboxDataOfTable('users', 'name', 'id', $process->owner_id, __(''));
+            $context = new ContextLaraKnife($request, null, $process);
+            $rc = view('process.show', [
+                'context' => $context,
+                'optionsDivision' => $optionsDivision,
+                'optionsActivity' => $optionsActivity,
+                'optionsOwner' => $optionsOwner,
+                'mode' => 'delete'
+            ]);
+        }
+        return $rc;
+    }
+    /**
+     * Store a newly created resource in storage.
+     */
+    public function store(Request $request)
+    {
+        $rc = null;
+        if ($request->btnSubmit === 'btnStore') {
+            $fields = $request->all();
+            $validator = Validator::make($fields, $this->rules(true));
+            if ($validator->fails()) {
+                $rc = back()->withErrors($validator)->withInput();
+            } else {
+                $validated = $validator->validated();
+                $fullName = $this->storeFile($request);
+                if ($fullName == null) {
+                    $rc = back()->withErrors($validator)->withInput();
+                } else {
+                    $this->parseDrawIoXml($fullName, $validated);
+                    $process = Process::create($validated);
+                    Change::createFromFields($validated, Change::$CREATE, 'Process', $process->id);
+                    $rc = redirect('/process-edit/' . $process->id);
+                }
+            }
+        }
+        if ($rc == null) {
+            $rc = redirect('/process-index');
+        }
+        return $rc;
+    }
+    /**
+     * Stores the file and returns the full file name.
+     * @param \Illuminate\Http\Request $request the request information
+     * @return string|null the full file name or null if no file was uploaded
+     */
+    public function storeFile(Request $request): ?string
+    {
+        $rc = null;
+        $file = $request->file();
+        $file = $request->file('file');
+        if ($file != null) {
+            $name = $file->getClientOriginalName();
+            $filename = session('userName') . '_' . strval(time()) . '!' . $name;
+            $rc = FileHelper::storeFile($request, 'file', $filename);
+        }
+        return $rc;
+    }
+
+    /**
+     * Update the specified resource in storage.
+     */
+    public function update(Process $process, Request $request)
+    {
+        $rc = null;
+        if ($request->btnSubmit === 'btnStore') {
+            $fields = $request->all();
+            $validator = Validator::make($fields, $this->rules(false));
+            if ($validator->fails()) {
+                $rc = back()->withErrors($validator)->withInput();
+            } else {
+                $validated = $validator->validated();
+                $validated['path'] = strip_tags($validated['path']);
+                $validated['roles'] = strip_tags($validated['roles']);
+                $validated['subprocesses'] = strip_tags($validated['subprocesses']);
+                $validated['datasources'] = strip_tags($validated['datasources']);
+                $validated['texts'] = strip_tags($validated['texts']);
+                $validated['info'] = strip_tags($validated['info']);
+                $process->update($validated);
+            }
+        }
+        if ($rc == null) {
+            $rc = redirect('/process-index');
+        }
+        return $rc;
+    }
+}
diff --git a/app/Models/Process.php b/app/Models/Process.php
new file mode 100644 (file)
index 0000000..307ca87
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
+use Hamatoma\Laraknife\ViewHelpers;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+
+class Process extends Model
+{
+    use HasFactory;
+    protected $table = 'processes';
+    protected $fillable = [
+        'serialno',
+        'division_scope',
+        'activity_scope',
+        'path',
+        'title',
+        'roles',
+        'subprocesses',
+        'datasources',
+        'texts',
+        'info',
+        'owner_id'
+    ];
+    public static function nextSerialNo(): int
+    {
+        $max = Process::max('serialno') ?? 0;
+        return $max + 1;
+    }
+}
index 0b24dc651bf730db464396923833992aa2ff18f6..697ec82e1c7f3f43206083e32e518c51dc2dba06 100644 (file)
             "dist": {
                 "type": "path",
                 "url": "../laraknife",
-                "reference": "aa3c5c562078b91aba72a3407b98d08461a36a09"
+                "reference": "919c89cab04807f66b7f4a4881139e8df5b687b9"
             },
             "require-dev": {
                 "phpunit/phpunit": "11.0.x-dev"
         },
         {
             "name": "monolog/monolog",
-            "version": "3.8.1",
+            "version": "3.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
+                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
-                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
+                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/Seldaek/monolog/issues",
-                "source": "https://github.com/Seldaek/monolog/tree/3.8.1"
+                "source": "https://github.com/Seldaek/monolog/tree/3.9.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-12-05T17:15:07+00:00"
+            "time": "2025-03-24T10:02:05+00:00"
         },
         {
             "name": "nesbot/carbon",
diff --git a/database/migrations/2025_03_25_125345_create_processes_table.php b/database/migrations/2025_03_25_125345_create_processes_table.php
new file mode 100644 (file)
index 0000000..471de75
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('processes', function (Blueprint $table) {
+            $table->id();
+            $table->timestamps();
+            $table->integer('serialno')->nullable()->unique();
+            $table->integer('division_scope');
+            $table->integer('activity_scope');
+            $table->text('path')->nullable();
+            $table->string('title')->unique();
+            $table->text('roles')->nullable();
+            $table->text('subprocesses')->nullable();
+            $table->text('datasources')->nullable();
+            $table->text('texts')->nullable();
+            $table->text('info')->nullable();
+            $table->foreignId('owner_id')->nullable()->references('id')->on('users');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('processes');
+    }
+};
diff --git a/database/seeders/ProcessSeeder.php b/database/seeders/ProcessSeeder.php
new file mode 100644 (file)
index 0000000..4ab5424
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+namespace Database\Seeders;
+
+use App\Models\Module;
+use App\Models\Menuitem;
+use App\Models\SProperty;
+use Illuminate\Database\Seeder;
+use Illuminate\Database\Console\Seeds\WithoutModelEvents;
+
+class ProcessSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     */
+    public function run(): void
+    {
+        Menuitem::insertIfNotExists('processes', 'bi bi-diagram-3');
+        Module::insertIfNotExists('Process');
+
+        SProperty::insertIfNotExists(2051, 'division', 'Piepmatz', 10, 'PM');
+        SProperty::insertIfNotExists(2052, 'division', 'Verein', 20, 'V');
+        SProperty::insertIfNotExists(2053, 'division', 'Unverpackt', 30, 'UV');
+        SProperty::insertIfNotExists(2054, 'division', 'FairFashion', 40, 'FF');
+        SProperty::insertIfNotExists(2055, 'division', 'Allgäu Fairnetzt', 50, 'AF');
+        SProperty::insertIfNotExists(2056, 'division', 'CommYounitySpace', 60, 'CS');
+
+        SProperty::insertIfNotExists(2071, 'activity', 'Fibu', 10, 'F');
+        SProperty::insertIfNotExists(2072, 'activity', 'Einkauf', 20, 'EK');
+        SProperty::insertIfNotExists(2073, 'activity', 'Lager', 30, 'LG');
+        SProperty::insertIfNotExists(2074, 'activity', 'Reklamation', 40, 'R');
+        SProperty::insertIfNotExists(2075, 'activity', 'Ladenbetrieb', 50, 'LD');
+        SProperty::insertIfNotExists(2076, 'activity', 'Mitgliederverwaltung', 60, 'M');
+        SProperty::insertIfNotExists(2077, 'activity', 'Events', 70, 'EV');
+        SProperty::insertIfNotExists(2078, 'activity', 'Öffentlichkeitsarbeit', 80, 'Ö');
+        SProperty::insertIfNotExists(2079, 'activity', 'Spendenverwaltung', 90, 'SV');
+        SProperty::insertIfNotExists(2070, 'activity', 'Fördermittelaquise', 100, 'FA');
+        SProperty::insertIfNotExists(2081, 'activity', 'Bildung', 110, 'BI');
+        SProperty::insertIfNotExists(2082, 'activity', 'Webseitenverwaltung', 120, 'WS');
+        SProperty::insertIfNotExists(2083, 'activity', 'interne Kommunikation', 130, 'IK');
+        SProperty::insertIfNotExists(2084, 'activity', 'externe Kommunikation', 140, 'EK');
+        SProperty::insertIfNotExists(2085, 'activity', 'Zugangsverwaltung', 150, 'ZV');
+    }
+}
diff --git a/resources/views/process/create.blade.php b/resources/views/process/create.blade.php
new file mode 100644 (file)
index 0000000..5235774
--- /dev/null
@@ -0,0 +1,18 @@
+@extends('layouts.backend')
+
+@section('content')
+    <form enctype="multipart/form-data" id="process-create" action="/process-store" method="POST">
+        @csrf
+        @method('PUT')
+        <x-laraknife.panels.create title="Import einer Prozessbeschreibung">
+            <x-laraknife.forms.file position="first" name="file" label="Drawio-Datei" width2="4" />
+            <x-laraknife.forms.const-text position="alone" name="message" text="{{ $context->valueOf('message') }}"
+                width1="0" width2="12" />
+            <input type="hidden" name="owner_id" value="{{ $context->valueOf('owner_id') }}">
+            <x-laraknife.forms.combobox position="first" name="division_scope" label="Division" :options="$optionsDivision"
+                width2="4" />
+            <x-laraknife.forms.combobox position="last" name="activity_scope" label="Activity" :options="$optionsActivity"
+                width2="4" />
+        </x-laraknife.panels.create>
+    </form>
+@endsection
diff --git a/resources/views/process/edit.blade.php b/resources/views/process/edit.blade.php
new file mode 100644 (file)
index 0000000..e861983
--- /dev/null
@@ -0,0 +1,31 @@
+@extends('layouts.backend')
+
+@section('content')
+    <form id="process-edit" action="/process-update/{{ $context->model->id }}" method="POST">
+        @csrf
+        <x-laraknife.panels.edit title="Änderung einer Prozessbeschreibung">
+            <x-laraknife.forms.string position="alone" name="title" label="Title" value="{{ $context->valueOf('title') }}"
+                width2="10" attribute="readonly" />
+            <x-laraknife.forms.string type="number" position="first" name="serialno" label="Nummer"
+                value="{{ $context->valueOf('serialno') }}" width2="4" />
+            <x-laraknife.forms.combobox position="last" name="owner_id" label="Besitzer" :options="$optionsOwner" width2="4"
+                attribute="readonly" />
+            <x-laraknife.forms.combobox position="first" name="division_scope" label="Abteilung" :options="$optionsDivision"
+                width2="4" />
+            <x-laraknife.forms.combobox position="last" name="activity_scope" label="Tätigkeitsfeld" :options="$optionsActivity"
+                width2="4" />
+            <x-laraknife.forms.string position="alone" name="path" label="Pfad" value="{{ $context->valueOf('path') }}"
+                width2="10" />
+            <x-laraknife.forms.text position="first" name="roles" label="Rollen" value="{{ $context->valueOf('roles') }}"
+                width2="4" rows="4" attribute="readonly" />
+            <x-laraknife.forms.text position="last" name="subprocesses" label="Sub-Prozesse"
+                value="{{ $context->valueOf('subprocesses') }}" width2="4" rows="4" attribute="readonly" />
+                <x-laraknife.forms.text position="first" name="texts" label="Texte" value="{{ $context->valueOf('texts') }}"
+                    width2="4" rows="4" attribute="readonly" />
+            <x-laraknife.forms.text position="last" name="datasources" label="Datenquellen"
+                value="{{ $context->valueOf('datasources') }}" width2="4" rows="4" attribute="readonly" />
+            <x-laraknife.forms.text position="alone" name="info" label="Info" value="{{ $context->valueOf('info') }}"
+                width2="10" rows="2" />
+        </x-laraknife.panels.edit>
+    </form>
+@endsection
diff --git a/resources/views/process/index.blade.php b/resources/views/process/index.blade.php
new file mode 100644 (file)
index 0000000..33fc8c6
--- /dev/null
@@ -0,0 +1,49 @@
+@extends('layouts.backend')
+
+@section('content')
+<form id="process-index" action="/process-index" method="POST">
+    @csrf
+    <x-laraknife.panels.index title="Prozesse">
+      <x-laraknife.panels.filter legend="{{ $pagination->legendText() }}">
+      <x-laraknife.forms.combobox position="first" name="division" label="Abteilung" :options="$optionsDivision" class="lkn-autoupdate" width2="4" />
+      <x-laraknife.forms.combobox position="last" name="owner" label="Besitzer" :options="$optionsOwner" class="lkn-autoupdate" width2="4" />
+      <x-laraknife.forms.combobox position="alone" name="activity" label="Tätigkeitsfeld" :options="$optionsActivity" class="lkn-autoupdate" width2="4" />
+      <x-laraknife.forms.string position="first" name="title" label="Titel" value="{{ $context->valueOf('title') }}" width2="4" />
+      <x-laraknife.forms.string position="last" name="path" label="Pfad" value="{{ $context->valueOf('path') }}" width2="4" />
+      <x-laraknife.forms.string position="first" name="roles" label="Rolle" value="{{ $context->valueOf('roles') }}" width2="4" />
+      <x-laraknife.forms.string position="last" name="subprocesses" label="Subprozess" value="{{ $context->valueOf('subprocesses') }}" width2="4" />
+        <x-laraknife.forms.string position="first" name="texts" label="Text" value="{{ $context->valueOf('texts') }}" width2="4" />
+      <x-laraknife.forms.string position="last" name="datasources" label="Datenquelle" value="{{ $context->valueOf('datasources') }}" width2="4" />
+      </x-laraknife.panels.filter>
+      <x-laraknife.panels.index-button buttonType="new"/>
+      <x-laraknife.panels.sortable-table :context="$context" :pagination="$pagination">
+        <thead>
+          <tr>
+            <th></th>
+            <th sortId="serialno">Nummer</th>
+            <th sortId="title">Titel</th>
+            <th sortId="division">Abteilung</th>
+            <th sortId="activity">Tätigkeitsfeld</th>
+            <th sortId="path">Pfad</th>
+            <th sortId="owner">Besitzer</th>
+            <th></th>
+          </tr>
+        </thead>
+        <tbody>
+@foreach ($records as $process)
+        <tr>
+            <td><x-laraknife.icons.change-record module="process" no="{{ $process->id }}" /></td>
+              <td>{{$process->serialno}}</td>
+              <td>{{$process->title}}</td>
+              <td>{{ __($process->division) }}</td>
+              <td>{{ __($process->activity) }}</td>
+              <td>{{$process->path}}</td>
+              <td>{{$process->owner}}</td>
+            <td><x-laraknife.icons.delete-record module="process" no="{{ $process->id }}" /></td>
+        </tr>
+@endforeach
+      </tbody>
+    </x-laraknife.panels.sortable-table>
+  </x-laraknife.panels.index>
+</form>
+@endsection
diff --git a/resources/views/process/show.blade.php b/resources/views/process/show.blade.php
new file mode 100644 (file)
index 0000000..5bb7199
--- /dev/null
@@ -0,0 +1,36 @@
+@extends('layouts.backend')
+
+@section('content')
+    <form id="process-show" action="/process-show/{{ $context->model->id }}/{{ $mode }}" method="POST">
+        @csrf
+        @if ($mode === 'delete')
+            @method('DELETE')
+        @endif
+        <x-laraknife.panels.show
+            title="{{ __($mode !== 'delete' ? 'Eine Prozessbeschreibung' : 'Löschen einer Prozessbeschreibung') }}"
+            mode="{{ $mode }}">
+            <x-laraknife.forms.string position="alone" name="title" label="Titel" value="{{ $context->valueOf('title') }}"
+                width2="10" attribute="readonly" />
+            <x-laraknife.forms.string position="first" name="serialno" label="Nummer"
+                value="{{ $context->valueOf('serialno') }}" width2="4" attribute="readonly" />
+            <x-laraknife.forms.combobox position="last" name="owner_id" label="Besitzer" :options="$optionsOwner" width2="4"
+                attribute="readonly" />
+            <x-laraknife.forms.combobox position="first" name="division_scope" label="Abteilung" :options="$optionsDivision"
+                width2="4" attribute="readonly" />
+            <x-laraknife.forms.combobox position="last" name="activity_scope" label="Tätigkeitsfeld" :options="$optionsActivity"
+                width2="4" attribute="readonly" />
+            <x-laraknife.forms.string position="alone" name="path" label="Pfad" value="{{ $context->valueOf('path') }}"
+                width2="10" attribute="readonly" />
+            <x-laraknife.forms.text position="first" name="roles" label="Rollen" value="{{ $context->valueOf('roles') }}"
+                width2="4" attribute="readonly" rows="4" />
+            <x-laraknife.forms.text position="last" name="subprocesses" label="Subprozesse"
+                value="{{ $context->valueOf('subprocesses') }}" width2="4" attribute="readonly" rows="4" />
+            <x-laraknife.forms.text position="first" name="texts" label="Texte" value="{{ $context->valueOf('texts') }}"
+                width2="4" attribute="readonly" rows="4" />
+            <x-laraknife.forms.text position="last" name="datasources" label="Datenquellen"
+                value="{{ $context->valueOf('datasources') }}" width2="4" attribute="readonly" rows="4" />
+            <x-laraknife.forms.text position="alone" name="info" label="Info" value="{{ $context->valueOf('info') }}"
+                width2="10" attribute="readonly" rows="2" />
+        </x-laraknife.panels.show>
+    </form>
+@endsection
index 0b0f80a9119afd4cb5eb32e137ae249fdf9247aa..730b7338cdcdbfca13d23d4983045f293bfbdeaa 100644 (file)
@@ -18,6 +18,7 @@ use App\Http\Controllers\LocationController;
 use App\Http\Controllers\MandatorController;
 use App\Http\Controllers\MenuitemController;
 use App\Http\Controllers\SPropertyController;
+use App\Http\Controllers\ProcessController;
 use App\Http\Controllers\TransactionController;
 
 if (User::isGuest()) {
@@ -47,3 +48,4 @@ AddressController::routes();
 MandatorController::routes();
 AccountController::routes();
 TransactionController::routes();
+ProcessController::routes();
diff --git a/scripts/drawio2db.py b/scripts/drawio2db.py
new file mode 100755 (executable)
index 0000000..e03642a
--- /dev/null
@@ -0,0 +1,177 @@
+#! /usr/bin/python3
+import xml.etree.ElementTree as ET
+import sys
+import os
+import re
+
+
+class AnalyseDrawio:
+    """
+    A tool for analyzing a drawio file (XML)
+    Prints the database relevant information.
+    """
+
+    def __init__(self, xml_file: str):
+        """
+        Constructor
+        :param xml_file: the name of the  drawio file to analyze
+        """
+        self.xml_file = xml_file
+        self.actions = 0
+        self.comments = 0
+        self.decisions = 0
+        self.delays = 0
+        self.documents = 0
+        self.manual_inputs = 0
+        self.start_end = 0
+        self.data_sources = []
+        self.subProcesses = {}
+        self.texts = []
+        self.title = ""
+        self.roles = {}
+        self.regColor = re.compile(r"fillColor=.*?(#[0-9A-Fa-f]{6})")
+        self.role_to_color = {
+            "Ladenteam": "#E6FFCC",
+            "Eventteam": "#CCFF99",
+            "Beirät:innen": "#CCE5FF",
+            "Vorstandschaft": "#99CCFF",
+            "Buchhaltung": "#FFCCFF",
+            "Einkauf": "#FF99CC",
+            "Postfachverwaltung": "#FFE6CC",
+            "Mitgliederverwaltung": "#FFCC99",
+            "Admins": "#E9E0CB",
+            "Ehrenamtlicher": "#FF9999",
+            "Mitglieder": "#FF6666",
+            "Community": "#FF3333",
+            "Interessierte": "#B266FF",
+            "Kund:innen": "#7F00FF",
+        }
+        self.color_to_role = {v: k for k, v in self.role_to_color.items()}
+        if len(self.color_to_role) != len(self.role_to_color):
+            print("+++ Error: duplicate color in role_to_color")
+
+    def one_item(self, elem):
+        """
+        Analyze one item in the XML file
+        :param elem: the item to analyze
+        """
+        has_value = "value" in elem.attrib
+        text = ""
+        if has_value:
+            text = elem.attrib["value"]
+            if text != "" and text not in self.texts:
+                self.texts.append(text)
+        if "style" in elem.attrib:
+            style = elem.attrib["style"]
+            if style.startswith("ellipse"):
+                self.start_end += 1
+            elif style.startswith("rhombus"):
+                self.decisions += 1
+            elif style.find("shape=callout") >= 0:
+                self.comments += 1
+            elif style.find("shape=delay") >= 0:
+                self.delays += 1
+            elif style.find("shape=manualInput") >= 0:
+                self.manual_inputs += 1
+            elif style.find("shape=note") >= 0:
+                self.documents += 1
+            elif style.startswith("shape=process"):
+                self.subProcesses[text] = 1
+            elif style.startswith("shape=parallelogram"):
+                self.subProcesses[text] = 1
+            elif style.startswith("text;"):
+                if style.find("fontSize=24") >= 0:
+                    self.title = text
+            elif style.find("shape=mxgraph.flowchart.stored_data") >= 0:
+                if text not in self.data_sources:
+                    self.data_sources.append(text)
+            elif style.startswith("rounded=1"):
+                self.actions += 1
+            # if style.find('fillColor=') >= 0:
+            matcher = self.regColor.search(style)
+            if matcher:
+                color = matcher.group(1).upper()
+                if color in self.color_to_role:
+                    role = self.color_to_role[color]
+                    if role not in self.roles:
+                        self.roles[role] = 1
+                    else:
+                        self.roles[role] += 1
+
+    def analyze_mxCell(self):
+        """
+        Analyze the mxCell elements in the XML file
+        """
+        tree = ET.parse(self.xml_file)
+        root = tree.getroot()
+
+        for elem in root.iter("mxCell"):
+            self.one_item(elem)
+
+    def print_results(self):
+        """
+        Print the results of the analysis
+        """
+        print(f"Title: {self.title}")
+        print(f"Actions: {self.actions}")
+        print(f"Comments: {self.comments}")
+        print(f"Decisions: {self.decisions}")
+        print(f"Delays: {self.delays}")
+        print(f"Documents: {self.documents}")
+        print(f"Manual inputs: {self.manual_inputs}")
+        print(f"Start/End: {self.start_end}")
+        print(f"Roles ({len(self.roles.keys())}):")
+        keys = self.roles.keys()
+        keys = sorted(keys, key=lambda x: x.lower())
+        for key in keys:
+            print(f"  {key}")
+        print(f"SubProcesses ({len(self.subProcesses)}):")
+        keys = self.subProcesses.keys()
+        keys = sorted(keys, key=lambda x: x.lower())
+        for key in self.subProcesses:
+            key = self.stripText(key)
+            print(f"  {key}")
+        print(f"Data sources ({len(self.data_sources)}):")
+        keys = self.data_sources
+        keys = sorted(keys, key=lambda x: x.lower())
+        for key in self.data_sources:
+            key = self.stripText(key)
+            print(f"  {key}")
+        print(f"Texts ({len(self.texts)}):")
+        texts = self.texts
+        texts = sorted(texts, key=lambda x: x.lower())
+        for text in texts:
+            text = self.stripText(text)
+            print(f"  {text}")
+
+    def stripText(self, text: str) -> str:
+        """
+        Strip the text from unwanted HTML elements
+        :param text: the text to strip
+        :return: the stripped text
+        """
+        text = text.replace("-<div>", "")
+        text = text.replace("<div>", "")
+        text = text.replace("</div>", "")
+        text = text.replace("<br>", "")
+        text = text.replace("<br/>", "")
+        text = text.replace("&nbsp;", " ")
+        return text
+
+
+def main(argv: list):
+    """
+    Main program
+    :param argv: the list of command line arguments (without the program name)
+    """
+    xml_file = argv[0] if len(argv) > 0 else "data/drawio.xml"
+    if not os.path.exists(xml_file):
+        print(f"File {xml_file} not found")
+    else:
+        analyse = AnalyseDrawio(xml_file)
+        analyse.analyze_mxCell()
+        analyse.print_results()
+
+
+if __name__ == "__main__":
+    main(sys.argv[1:])