Merge pull request 'develop -> main (v0.1)' (#16) from develop into master

Reviewed-on: ERRMS/ERRMS#16
This commit is contained in:
anghenfil 2020-12-30 12:39:52 +01:00
commit ae86b585eb
244 changed files with 12538 additions and 8 deletions

4
.gitignore vendored
View File

@ -3,10 +3,6 @@
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

2037
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[package]
name = "ERRMS"
name = "errms"
version = "0.1.0"
authors = ["Keanu Doelle <ares@anghenfil.de>"]
edition = "2018"
@ -7,3 +7,24 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
config = "0.10.1"
serde_derive = "1.0.115"
serde = { version = "1.0.115", features = ["derive"] }
serde_json = "1.0.57"
log = "0.4.11"
env_logger = "0.7.1"
rocket = "0.4.5"
diesel = { version = "1.4.5", features = ["postgres", "uuidv07", "chrono"] } #uuidv07 vs uuid to use uuid >= 0.7
diesel_geometry = "1.4.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
rust-argon2 = "0.8.2"
chrono = { version = "0.4.15", features = ["serde"] }
rand = "0.7.3"
iban_validate = "4"
lettre = "0.9"
lettre_email = "0.9"
[dependencies.rocket_contrib]
version = "0.4.5"
default-features = false
features = ["handlebars_templates", "serve", "json"]

View File

@ -520,7 +520,7 @@ governed by version 3 of the GNU General Public License.
The Free Software Foundation may publish revised and/or new versions of the
GNU Affero General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to address
be similar in spirit to the present version, but may differ in detail to Address
new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies

View File

@ -1,3 +1,39 @@
# ERRMS
Build status (develop branch):
[![builds.sr.ht status](https://builds.sr.ht/~anghenfil.svg)](https://builds.sr.ht/~anghenfil?search=)
![project status](https://www.repostatus.org/badges/latest/wip.svg)
## About ERRMS
* ERRMS stands for **E**mergency **R**esponse and
**R**escue **M**anagement **S**ystem
* application for fire departments, humanitarian aid organisations and similar
* organise member data, events/operations and other resources
Checkout our presentation: https://md.kabi.tk/p/H1nWPbueL
## characteristics
* fully open source (APGLv3)
* modular software
* strong focus on data privacy & data security
* provides feature-rich API for integration
* multilingual, easy to translate software
* strong, precise permission system
## How to contribute
* You can contribute by signing pull requests and issues or programming!
* Get in contact (Matrix: #errms@matrix.anghenfil.de) and check our website errms.dev and our project management tool: pm.errms.dev
## Zeitplan/Timetable
**Version 0.1:**
* core system
* Mitgliedsverwaltung/member management
**Version 0.2:**
* Fahrzeugverwaltung (nur Basisfunktionen) / vehicle management (limited functionality)
* Einsatzverwaltung / event management
* Veranstalterverwaltung / manage event organizer
**Version 0.3:**
* Abrechnung / billing module
* Fahrzeugverwaltung (erweitert) / expended vehicle management

23
Rocket.toml Normal file
View File

@ -0,0 +1,23 @@
[development]
address = "localhost"
port = 8000
keep_alive = 5
log = "normal"
limits = { forms = 32768 }
template_dir = "resources/templates"
[staging]
address = "0.0.0.0"
port = 8000
keep_alive = 5
log = "normal"
limits = { forms = 32768 }
template_dir = "resources/templates"
[production]
address = "0.0.0.0"
port = 8000
keep_alive = 5
log = "critical"
limits = { forms = 32768 }
template_dir = "resources/templates"

22
config/default.toml Normal file
View File

@ -0,0 +1,22 @@
[database]
connection_string = "postgresql://postgres:qwertz@localhost:5432/postgres"
[application]
url = "http://localhost:8000/"
name = "einsatz.online"
default_language = "de-DE"
fallback_language = "en-US"
loglevel = "debug"
#Session timeout in seconds. Default is 15 minutes
session_timeout = 900
upload_path = "uploads/"
#Maximum login attempts until email is locked for login
max_login_attempts = 6
#Duration of email lock after max_login_attempts in seconds. Default is 30 minutes
login_lock_duration = 1800
#How long does it take until tokens expire?
reset_password_token_valid_duration = 3600
[mail]
from = "No Reply <noreply@localhost>"
reply_to = "support@localhost"

7
diesel.toml Normal file
View File

@ -0,0 +1,7 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
# Add types from `diesel_full_text_search` like `tsvector`
import_types = ["diesel::sql_types::*", "diesel_geometry::sql_types::*"]

0
migrations/.gitkeep Normal file
View File

View File

@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table entities;

View File

@ -0,0 +1,9 @@
-- Your SQL goes here
create table entities
(
entity_id uuid default uuid_generate_v1() not null,
constraint entities_pk
primary key (entity_id)
);
INSERT INTO entities (entity_id) VALUES ('00000000-0000-0000-0000-000000000000');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table users;

View File

@ -0,0 +1,13 @@
-- Your SQL goes here
create table users
(
id uuid default uuid_generate_v1() not null
constraint pk___users___id
primary key,
password text,
email text not null
);
create unique index users_email_uindex
on users (email);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table members;

View File

@ -0,0 +1,31 @@
create table members
(
entity_id uuid default uuid_generate_v1() not null
constraint pk___members___id
primary key
constraint members_entities_entity_id_fk
references entities
on update cascade on delete cascade,
users_id uuid
constraint fk___members___users_id___users
references users
on update cascade on delete set null,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
personnel_number integer,
ui_language text,
nationality text,
entrance_date date,
birth_name text,
iban text,
bic text
);
create unique index members_personnel_number_uindex
on members (personnel_number);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE addresses;

View File

@ -0,0 +1,13 @@
-- Your SQL goes here
create table addresses
(
id uuid default uuid_generate_v1() not null
constraint addresses_pk
primary key,
title text,
street text,
number text,
zipcode text,
city text,
country text
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table addresses_entities;

View File

@ -0,0 +1,14 @@
-- Your SQL goes here
create table addresses_entities
(
address_id uuid not null,
entitiy_id uuid not null,
constraint addresses_entities_pk
primary key (address_id, entitiy_id),
constraint addresses_entities_addresses_id_fk
foreign key (address_id) references addresses
on update cascade on delete cascade,
constraint addresses_entities_entities_entity_id_fk
foreign key (entitiy_id) references entities
on update cascade on delete cascade
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table buildings;

View File

@ -0,0 +1,12 @@
-- Your SQL goes here
create table buildings
(
entity_id uuid default uuid_generate_v1() not null,
name text not null,
description text,
constraint buildings_pk
primary key (entity_id),
constraint buildings_entities_entity_id_fk
foreign key (entity_id) references entities
on update cascade on delete cascade
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table vehicles;

View File

@ -0,0 +1,17 @@
-- Your SQL goes here
create table vehicles
(
entity_id uuid default uuid_generate_v1() not null,
identifier text not null,
numberplate text,
description text,
next_inspection date,
is_operational boolean default true not null,
admissible_total_weight real,
required_license text,
constraint vehicles_pk
primary key (entity_id),
constraint vehicles_entities_entity_id_fk
foreign key (entity_id) references entities
on update cascade on delete cascade
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table communication_types;

View File

@ -0,0 +1,8 @@
-- Your SQL goes here
create table communication_types
(
type_id uuid default uuid_generate_v1() not null
constraint pk___communication_types___id
primary key,
type_name text not null
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table communication_targets;

View File

@ -0,0 +1,16 @@
-- Your SQL goes here
create table communication_targets
(
target_id uuid default uuid_generate_v1() not null
constraint pk___communication_targets___id
primary key,
entity_id uuid not null
constraint communication_targets_entities_entity_id_fk
references entities
on update cascade on delete cascade,
com_type uuid not null
constraint fk___communication_target___type___communication_types
references communication_types,
value text not null,
description text
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table permissions;

View File

@ -0,0 +1,50 @@
-- Your SQL goes here
create table permissions
(
permission text not null
constraint permissions_pk
primary key,
description text
);
INSERT INTO permissions (permission, description) VALUES ('modules.configuration.fields.communication_types.view', 'Permission to see all communication types');
INSERT INTO permissions (permission, description) VALUES ('modules.dashboard.view', 'Permission to see Dashboard');
INSERT INTO permissions (permission, description) VALUES ('modules.event_management.view', 'Permission to see Event Management');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.core.edit', 'Permission to edit group name + description');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.create', 'Permission to create new group');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.delete', 'Permission to delete entire group');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.edit', 'Permission to see edit group mode');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.members.edit', 'Permission to edit group members (adding/removing)');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.members.view', 'Permission to see all members in group');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.permissions.edit', 'Permission to edit group permissions');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.permissions.view', 'Permission to see group permissions');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.groups.view', 'Permission to see group data');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.list.view', 'Permission to access member list. Note: Member will only see members selected in context. Exception: members with modules.member_management.list.view.all will see any members, ignoring context!');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.communication.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.communication.view', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.core.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.create', 'Permission to create new member');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.delete', 'Permission to delete specified member profile');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.drive_permissions_licenses.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.drive_permissions_licenses.view', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.edit', 'Permission to edit specified member profile');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.groups.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.groups.view', 'Permission to see all groups of member in profile view');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.iban_bic.edit', 'Permission to edit member''s IBAN + BIC');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.iban_bic.view', 'Permission to see member''s IBAN + BIC');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.image.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.image.view', 'Permission to see members profile image');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.qualification.view', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.qualifications.edit', null);
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.view', 'Permission to see specified member profile');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.search', 'Permission to search for members');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.view', 'Permission to see Member Management');
INSERT INTO permissions (permission, description) VALUES ('modules.resource_management.view', 'Permission to see Resource Management');
INSERT INTO permissions (permission, description) VALUES ('modules.units.edit', 'Permission to edit existing unit');
INSERT INTO permissions (permission, description) VALUES ('modules.units.delete', 'Permission to delete existing unit');
INSERT INTO permissions (permission, description) VALUES ('modules.units.create', 'Permission to create new unit');
INSERT INTO permissions (permission, description) VALUES ('modules.units.members.view', 'Permission to see all unit members');
INSERT INTO permissions (permission, description) VALUES ('modules.units.members.edit', 'Permission to change unit members');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.units.view', 'Permission to see members units');
INSERT INTO permissions (permission, description) VALUES ('modules.member_management.profile.units.edit', 'Permission to edit members units');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table roles;

View File

@ -0,0 +1,11 @@
-- Your SQL goes here
create table roles
(
id text not null
constraint roles_pk
primary key,
description text
);
INSERT INTO roles (id, description) VALUES ('member', 'Default Member role');
INSERT INTO roles (id, description) VALUES ('admin', 'Default Administrator role');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table roles_permissions;

View File

@ -0,0 +1,51 @@
create table roles_permissions
(
role_id text not null
constraint roles_permissions_roles_id_fk
references roles
on update cascade on delete cascade,
permission_id text not null
constraint roles_permissions_permissions_permission_fk
references permissions
on update cascade on delete cascade,
role_permission_id uuid default uuid_generate_v1() not null
constraint roles_permissions_pk_2
primary key
);
create unique index roles_permissions_role_permission_id_uindex
on roles_permissions (role_permission_id);
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.event_management.view', 'a2ac9294-e4b3-11ea-ab3e-e86a6432fc61');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.view', 'a2ad8f1e-e4b3-11ea-ab3e-e86a6432fc61');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.resource_management.view', 'a2ae30fe-e4b3-11ea-ab3e-e86a6432fc61');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.dashboard.view', 'a2aeb7c2-e4b3-11ea-ab3e-e86a6432fc61');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('member', 'modules.dashboard.view', 'a2af63fc-e4b3-11ea-ab3e-e86a6432fc61');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.list.view', 'ff5a63e4-e71b-11ea-9387-a4c3f0e2b1e4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.view', '45bf2b6e-193d-11eb-aab5-e86a645407c8');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.edit', '62312536-1a32-11eb-9574-e86a642ef0c5');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('member', 'modules.configuration.fields.communication_types.view', '66f5052e-1a3c-11eb-9574-e86a642ef0c5');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.configuration.fields.communication_types.view', '66f62fe4-1a3c-11eb-9574-e86a642ef0c5');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.delete', '57221870-1ae7-11eb-98ca-a4c3f0e8cdb7');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.search', '8fa74146-1c6a-11eb-ac8d-e86a64e97ce1');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.qualifications.edit', '5d03484c-2170-11eb-9d03-e86a6481f7b8');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.communication.view', 'adb72014-2171-11eb-9d03-e86a6481f7b8');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.groups.view', '6813a728-241c-11eb-b21a-e86a64d15afc');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.view', '5bbc2b16-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.members.view', '5bbd2a02-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.members.edit', '5bbdf900-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.edit', '5bbee55e-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.delete', '5bbfc42e-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.core.edit', '5bc085f8-25ee-11eb-bb25-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.create', '7da2f954-278c-11eb-a7ba-e86a644092b4');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.permissions.view', '02668fca-3282-11eb-8766-e86a64433ce6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.groups.permissions.edit', '0268544a-3282-11eb-8766-e86a64433ce6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.iban_bic.view', '7a29dc26-3726-11eb-8f7d-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.iban_bic.edit', 'abcecbba-3726-11eb-8f7d-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.units.edit', '990c4fd2-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.units.delete', '990d09fe-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.units.create', '990db9e4-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.units.members.view', '990e9eae-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.units.members.edit', '990f68ac-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.units.view', '991035c0-373c-11eb-92e1-e86a6413f6e6');
INSERT INTO roles_permissions (role_id, permission_id, role_permission_id) VALUES ('admin', 'modules.member_management.profile.units.edit', '991129b2-373c-11eb-92e1-e86a6413f6e6');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table members_roles;

View File

@ -0,0 +1,14 @@
-- Your SQL goes here
create table members_roles
(
member_id uuid not null
constraint members_roles_entities_entity_id_fk
references entities
on update cascade on delete cascade,
role_id text not null
constraint members_roles_roles_id_fk
references roles
on update cascade on delete cascade,
constraint members_roles_pk
primary key (member_id, role_id)
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table groups;

View File

@ -0,0 +1,17 @@
-- Your SQL goes here
create table groups
(
entity_id uuid default uuid_generate_v1() not null
constraint groups_pk
primary key
constraint groups_entities_entity_id_fk
references entities
on update cascade on delete cascade,
group_name text not null,
group_description text
);
create unique index groups_group_name_uindex
on groups (group_name);
INSERT INTO groups (entity_id, group_name, group_description) VALUES ('00000000-0000-0000-0000-000000000000', 'default', '!autogenerated group!');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table groups_entities;

View File

@ -0,0 +1,14 @@
-- Your SQL goes here
create table groups_entities
(
group_id uuid not null
constraint groups_entities_groups_group_id_fk
references groups
on update cascade on delete cascade,
entity_id uuid not null
constraint groups_entities_entities_entity_id_fk
references entities
on update cascade on delete cascade,
constraint groups_entities_pk
primary key (group_id, entity_id)
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table roles_permissions_context;

View File

@ -0,0 +1,30 @@
-- Your SQL goes here
create table roles_permissions_context
(
role_permission_id uuid
constraint roles_permissions_contexts_roles_permissions_role_permission_id
references roles_permissions
on update cascade on delete cascade,
entity uuid
constraint roles_permissions_contexts_entities_entity_id_fk
references entities
on update cascade on delete cascade,
constraint roles_permissions_context_pk
primary key (role_permission_id, entity)
);
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('7a29dc26-3726-11eb-8f7d-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('990e9eae-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('990db9e4-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('990d09fe-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('990c4fd2-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('abcecbba-3726-11eb-8f7d-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('ff5a63e4-e71b-11ea-9387-a4c3f0e2b1e4', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('45bf2b6e-193d-11eb-aab5-e86a645407c8', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('62312536-1a32-11eb-9574-e86a642ef0c5', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('57221870-1ae7-11eb-98ca-a4c3f0e8cdb7', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('5d03484c-2170-11eb-9d03-e86a6481f7b8', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('adb72014-2171-11eb-9d03-e86a6481f7b8', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('991129b2-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('991035c0-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');
INSERT INTO roles_permissions_context (role_permission_id, entity) VALUES ('990f68ac-373c-11eb-92e1-e86a6413f6e6', '00000000-0000-0000-0000-000000000000');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table license_categories;

View File

@ -0,0 +1,26 @@
-- Your SQL goes here
create table license_categories
(
name text not null
constraint license_categories_pk
primary key,
description text
);
INSERT INTO license_categories (name, description) VALUES ('AM', 'zwei- oder dreirädrige Kraftfahrzeuge mit einer bauartbedingten Höchstgeschwindigkeit von bis zu 45 km/h, bis 4 kW, 50 cm³, bis 270 kg leer. Vierrädrige Leichtkraftfahrzeuge bis 45 km/h, bis 6 kW, bis 50 cm³, bis 425 kg leer.');
INSERT INTO license_categories (name, description) VALUES ('A1', 'Krafträder mit einem Hubraum von bis zu 125 cm³ mit einer Motorleistung von bis zu 11 kW (Leichtkrafträder) und einem Leistungsgewicht bis zu 0,1 kW/kg sowie dreirädrige Kraftfahrzeuge mit einer Leistung von bis zu 15 kW');
INSERT INTO license_categories (name, description) VALUES ('A2', 'Krafträder mit einer Motorleistung von bis zu 35 kW und einem Leistungsgewicht bis zu 0,2 kW/kg, die nicht von einem Fahrzeug mit mehr als der doppelten Motorleistung abgeleitet sind');
INSERT INTO license_categories (name, description) VALUES ('A', 'Krafträder über 50 cm³ oder über 45 km/h, auch mit Beiwagen, sowie dreirädrige Kraftfahrzeuge[4] mit einer Leistung von mehr als 15 kW');
INSERT INTO license_categories (name, description) VALUES ('B1', 'Mehrspurige Kraftfahrzeuge bis 550 kg Leermasse');
INSERT INTO license_categories (name, description) VALUES ('B', 'Mehrspurige Kraftfahrzeuge bis 3,5 t zulässiger Gesamtmasse und maximal 9 Sitzplätzen (einschließlich Fahrer).');
INSERT INTO license_categories (name, description) VALUES ('C1', 'Mehrspuriges Kraftfahrzeug bis 7,5 t zulässiger Gesamtmasse, maximal 9 Sitzplätze (einschließlich Fahrer)');
INSERT INTO license_categories (name, description) VALUES ('C', 'Mehrspurige Kraftfahrzeuge über 7,5 t zulässiger Gesamtmasse, maximal 9 Sitzplätze (einschließlich Fahrer)');
INSERT INTO license_categories (name, description) VALUES ('D1', 'Omnibusse mit bis 16 Sitzplätzen einschließlich Fahrer und höchstens 8 m Länge');
INSERT INTO license_categories (name, description) VALUES ('D', 'Omnibusse mit mehr als 9 Sitzplätzen (einschließlich Fahrer)');
INSERT INTO license_categories (name, description) VALUES ('BE', 'Züge aus B-Zugfahrzeug und Anhänger über 0,75 t zulässiger Gesamtmasse (sofern der Zug nicht unter Klasse B fällt)');
INSERT INTO license_categories (name, description) VALUES ('C1E', 'Züge aus C1-Zugfahrzeug und Anhänger über 0,75 t zulässiger Gesamtmasse, sowie Züge aus B-Zugfahrzeug und Anhänger über 3,5 t zulässiger Gesamtmasse');
INSERT INTO license_categories (name, description) VALUES ('CE', 'Lastzüge und Sattelkraftfahrzeuge');
INSERT INTO license_categories (name, description) VALUES ('D1E', 'Züge aus D1-Zugfahrzeug und Anhänger mit mehr als 0,75 t zulässiger Gesamtmasse');
INSERT INTO license_categories (name, description) VALUES ('DE', 'Züge aus D-Zugfahrzeug und Anhänger mit mehr als 0,75 t zulässiger Gesamtmasse');
INSERT INTO license_categories (name, description) VALUES ('L', '');
INSERT INTO license_categories (name, description) VALUES ('T', '');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table licenses_members;

View File

@ -0,0 +1,15 @@
-- Your SQL goes here
create table licenses_members
(
member_id uuid not null
constraint members_licenses_members_entity_id_fk
references members
on update cascade on delete cascade,
license_name text not null
constraint members_licenses_license_categories_name_fk
references license_categories
on update cascade on delete cascade,
drive_permission boolean default false not null,
constraint members_licenses_pk
primary key (member_id, license_name)
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table qualification_categories;

View File

@ -0,0 +1,12 @@
-- Your SQL goes here
create table qualification_categories
(
id uuid default uuid_generate_v1() not null
constraint qualification_categories_pk
primary key,
name text not null,
description text
);
INSERT INTO public.qualification_categories (id, name, description) VALUES ('168faee2-f159-11ea-8d76-e86a6444789b', 'Medizinisch', null);
INSERT INTO public.qualification_categories (id, name, description) VALUES ('1690690e-f159-11ea-8d76-e86a6444789b', 'Führung', 'Führungsqualifikationen nach DV 100');

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table qualifications;

View File

@ -0,0 +1,24 @@
-- Your SQL goes here
create table qualifications
(
id uuid default uuid_generate_v1() not null
constraint qualifications_pk
primary key,
name text not null,
description text,
category uuid not null
constraint qualifications_qualification_categories_id_fk
references qualification_categories
on update cascade on delete cascade
);
INSERT INTO public.qualifications (id, name, description, category) VALUES ('59214062-f164-11ea-8e01-e86a6444789b', 'Rettungssanitäter', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('6567de9e-f164-11ea-8e01-e86a6444789b', 'Notfallsanitäter', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('7045e310-f164-11ea-8e01-e86a6444789b', 'Zugführer', null, '1690690e-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('7047574a-f164-11ea-8e01-e86a6444789b', 'Gruppenführer', null, '1690690e-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('70491904-f164-11ea-8e01-e86a6444789b', 'Verbandsführer', null, '1690690e-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('d3b907ba-f164-11ea-8e01-e86a6444789b', 'Arzt', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('d3b97b5a-f164-11ea-8e01-e86a6444789b', 'Rettungsassistent', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('d3b9e28e-f164-11ea-8e01-e86a6444789b', 'Erste-Hilfe-Lehrgang', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('d3ba426a-f164-11ea-8e01-e86a6444789b', 'Notarzt', null, '168faee2-f159-11ea-8d76-e86a6444789b');
INSERT INTO public.qualifications (id, name, description, category) VALUES ('d3baa624-f164-11ea-8e01-e86a6444789b', 'Rettungshelfer', null, '168faee2-f159-11ea-8d76-e86a6444789b');

View File

@ -0,0 +1 @@
-- This file should undo anything in `up.sql`

View File

@ -0,0 +1,14 @@
-- Your SQL goes here
create table qualifications_members
(
member_id uuid not null
constraint qualifications_members_members_entity_id_fk
references members
on update cascade on delete cascade,
qualification_id uuid not null
constraint qualifications_members_qualifications_id_fk
references qualifications
on update cascade on delete cascade,
constraint qualifications_members_pk
primary key (member_id, qualification_id)
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table units;

View File

@ -0,0 +1,11 @@
-- Your SQL goes here
create table units
(
unit_id uuid default uuid_generate_v1() not null
constraint untis_pk
primary key
constraint units_entities_entity_id_fk
references entities
on update cascade on delete cascade,
name text not null
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table units_members;

View File

@ -0,0 +1,14 @@
create table units_members
(
unit_id uuid
constraint units_members_units_unit_id_fk
references units
on update cascade on delete cascade,
member_id uuid
constraint units_members_members_entity_id_fk
references members
on update cascade on delete cascade,
crew smallint not null,
PRIMARY KEY (unit_id, member_id)
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE login_attempts;

View File

@ -0,0 +1,7 @@
-- Your SQL goes here
create table login_attempts
(
id uuid default uuid_generate_v1() primary key,
email text not null,
timestamp timestamp default now() not null
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table password_resets;

View File

@ -0,0 +1,17 @@
-- Your SQL goes here
create table password_resets
(
token text not null
constraint password_resets_pk
primary key,
user_id uuid not null
constraint password_resets_users_id_fk
references users
on update cascade on delete cascade,
issued timestamp default CURRENT_TIMESTAMP not null
);
create unique index password_resets_token_uindex
on password_resets (token);

187
resources/css/errms.css Normal file
View File

@ -0,0 +1,187 @@
#sidebar {
min-width: 200px;
max-width: 200px;
min-height: 100vh;
background: #2D4360;
color: #fff;
transition: all 0.3s;
box-shadow: 10px -5px 8px -7px #AAAAAA;
}
.sidebar-header button{
color: white;
}
.wrapper {
display: flex;
align-items: stretch;
width: 100%;
}
a[data-toggle="collapse"] {
position: relative;
}
.dropdown-toggle::after {
display: block;
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
}
a, a:hover, a:focus {
color: inherit;
text-decoration: none;
transition: all 0.3s;
}
#sidebar .sidebar-header {
padding: 20px;
background: #355072;
}
#sidebar ul.components {
padding: 0 0;
}
#sidebar ul p {
color: #fff;
padding: 10px;
}
#sidebar ul li a {
padding: 10px;
font-size: 1.1em;
display: block;
border-bottom: 1px solid rgba(71,95,126,.30);
}
#sidebar ul li a:hover {
color: #7386D5;
background: #fff;
}
#sidebar ul li.active > a, a[aria-expanded="true"] {
color: #fff;
background: #475F7E;
text-decoration: underline;
}
#sidebar li{
list-style: none;
}
ul ul a {
font-size: 0.9em !important;
padding-left: 30px !important;
background: #355072;
}
.components h4{
padding-left: 10px;
font-size: 20px;
}
/* Welcome module */
.welcome_logo{
width: 200px;
float: right;
}
.login{
margin: 40px;
}
.login form{
margin: 50px;
}
.login_submit{
float: right;
}
.sidebar_entry_active{
text-decoration: underline;
}
.group_selection_group{
display: inline;
}
.filter{
border: 1px solid #dee2e6;
margin-bottom: 20px;
padding: 10px;
}
.filter_select{
float: unset !important;
margin-top: 10px;
}
#content{
width: 100%;
padding: 10px;
}
.profile_image{
border: 1px solid black;
}
.col-form-label{
}
[readonly]{
border: none;
background-color: unset !important;
color: black;
}
/*[readonly]:focus{
outline: none !important;
-webkit-appearance:none;
box-shadow: none !important;
}*/
label{
color: dimgrey;
}
.iconbutton{
color: unset !important;
background-color: unset !important;
border: none !important;
padding: 0px;
}
.iconbar{
}
#qualifications_edit{
float: right;
}
.search-result-overlay{
position:absolute;
z-index: 1;
width: 60%;
}
th.rotate {
height: 160px;
white-space: nowrap;
font-size: 13px;
}
.table-header-rotated {
border-collapse: collapse;
}
th.rotate > div {
transform:
translate(-9px, 0px)
rotate(-45deg);
width: 30px;
}
th.rotate > div > span {
border-bottom: 1px solid #ccc;
padding: 5px 10px;
}

BIN
resources/images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

18
resources/js/global.js Normal file
View File

@ -0,0 +1,18 @@
function show_error(api_error){
alert("Fehler "+api_error.error.code+" aufgetreten: "+api_error.error.description);
}
function is_ok(data){
if(data == null){
return true;
}else if(typeof data !== 'object') {
return true;
}else{
if('error' in data){
show_error(data);
return false;
}else{
return true;
}
}
}

2
resources/js/lib/jquery-3.5.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
$( document ).ready(function() {
$(".delete_member_button").on("click", MemberDeleteModule.delete_button_listener);
//Searchbar typing events:
$("#member_delete_modal_name_confirmation").on("keyup", MemberDeleteModule.name_confirmation_typing);
$("#member_delete_modal_name_confirmation").bind("paste", MemberDeleteModule.name_confirmation_typing);
$("#member_delete_modal_name_confirmation").bind("cut", MemberDeleteModule.name_confirmation_typing);
$("#member_delete_modal_delete_button").on("click", MemberDeleteModule.delete_member);
});
var name = "";
var uuid = "";
MemberDeleteModule = ( function() {
var delete_button_listener = function(){
$("#member_delete_modal_delete_button").prop("disabled", true);
$("#member_delete_modal_member_name").html("");
$("#member_delete_modal_name_confirmation").val("");
$("#member_delete_modal_name_confirmation").attr("placeholder", "");
name = $(this).data("member-firstname")+" "+$(this).data("member-lastname");
uuid = $(this).data("member-id");
$("#member_delete_modal_member_name").html(name);
$("#member_delete_modal_name_confirmation").attr("placeholder", name);
$("#member_delete_modal").modal();
};
var delete_member = function(){
$.ajax({
url: '/api/members/'+uuid,
type: 'DELETE',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
location.reload();
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
var name_confirmation_typing = function(){
if($("#member_delete_modal_name_confirmation").val() === name){
$('#member_delete_modal_delete_button').removeClass('disabled');
$('#member_delete_modal_delete_button').prop("disabled", false);
}
};
return{
delete_button_listener: delete_button_listener,
name_confirmation_typing: name_confirmation_typing,
delete_member: delete_member,
};
})();

View File

@ -0,0 +1,61 @@
$( document ).ready(function() {
$('body').click(function(evt){
if(evt.target.class === "group-detailed-view-member-search-result-overlay")
return;
if($(evt.target).closest('.group-detailed-view-member-search-result-overlay').length)
return;
$(".group-detailed-view-member-search-result-overlay").hide();
});
//Remove current searchbar value on click
$("#group-detailed-view-member-searchbar").on("click", GroupMemberSearch.searchbar_onclick);
$("#group-detailed-view-member-searchbar").on("keyup", GroupMemberSearch.searchbar_typing);
$("#group-detailed-view-member-searchbar").bind("paste", GroupMemberSearch.searchbar_typing);
$("#group-detailed-view-member-searchbar").bind("cut", GroupMemberSearch.searchbar_typing);
$("#group-detailed-view-member-searchbar").on("mouseenter", function (){
$(".group-detailed-view-member-search-result-overlay").show();
});
$(".group-detailed-view-member-search-result-overlay").on("focusout", function (){
$(".group-detailed-view-member-search-result-overlay").hide();
})
$(".group-detailed-view-member-search-result-overlay").on("mouseleave", function (){
$(".group-detailed-view-member-search-result-overlay").hide();
})
});
GroupMemberSearch = ( function() {
var searchbar_onclick = function(){
$("#group-detailed-view-member-searchbar").val("");
$(".group-detailed-view-member-search-result-overlay").show()
};
var searchbar_typing = function (){
$("#group-detailed-view-member-searchbar-type").html("");
$(".group-detailed-view-member-search-result-overlay-list").html("");
$(".group-detailed-view-member-search-result-overlay").show();
$.ajax({
url: '/api/members/',
type: 'GET',
data: {
"name": $("#group-detailed-view-member-searchbar").val()
},
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$.each(data.members, function(index, value){
$(".group-detailed-view-member-search-result-overlay-list").append("<span onclick=\"GroupModule.add_member_click_listener(this)\" style=\"cursor: pointer\" data-entity-id=\""+value.entity_id+"\" data-firstname=\""+value.firstname+"\" data-lastname=\""+value.lastname+"\"><li class='list-group-item'><span class=\"badge badge-secondary\">Hinzufügen:</span> "+value.firstname+" "+value.lastname+"</li></span>")
});
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
return {
searchbar_onclick : searchbar_onclick,
searchbar_typing : searchbar_typing,
};
})();

View File

@ -0,0 +1,281 @@
var all_checked = false;
var all_members_checked = false;
var delete_list = [];
var active_group_id = "";
$( document ).ready(function() {
$(".new_group_button").on("click", GroupModule.create_group_button_listener);
$(".groups_delete_button").on("click", GroupModule.delete_group_button_listener);
$(".check_all_groups").on("click",GroupModule.check_all_button_listener);
$("#groups_delete_modal_confirmation").on("keyup", GroupModule.delete_group_confirmation_check);
$("#groups_delete_modal_confirmation").bind("paste", GroupModule.delete_group_confirmation_check);
$("#groups_delete_modal_confirmation").bind("cut", GroupModule.delete_group_confirmation_check);
$("#groups_delete_modal_delete_button").on("click", GroupModule.delete_groups_request);
$(".group_list_row").on("click", GroupModule.load_group_detailed_view);
$(".group-detailed-view-member-search-member-to-add").on("click", GroupModule.add_member_click_listener);
$(".group_detailed_view_check_all_members").on("click", GroupModule.check_all_members_listener);
$(".group-detailed-view-remove-member-button").on("click", GroupModule.remove_member_from_group_listener);
$(".group_detailed_view_submit_core_data").on("click", GroupModule.save_core_data_button_listener);
});
//TODO: Remove add member to group in UI if user missing permission
GroupModule = ( function() {
var load_group_detailed_view = function(){
active_group_id = $(this).data("group-id");
$(".group_detailed_view").prop("hidden", false);
$.ajax({
type: "GET",
url: "/api/groups/"+active_group_id,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
clear_detailed_view();
set_group_detailed_view_core_data(data.group.name, data.group.description, !data.caller_permissions.permission_groups_core_edit);
if(data.caller_permissions.permission_groups_members_view){
set_group_detailed_view_member_list(data.members)
}else{
$(".group_detailed_view_member_list").hide();
}
}
}
});
};
var clear_detailed_view = function(){
$(".group_detailed_view_member_tr").remove();
$("#group_detailed_view_name_input").val("");
$("#group_detailed_view_description_input").val("");
$("#group_detailed_view_name_input").prop('readonly', false);
$("#group_detailed_view_description_input").prop('readonly', false);
$(".group_detailed_view_member_list").show();
$(".group_detailed_view_submit_core_data").show();
};
var set_group_detailed_view_core_data = function(name, description, readonly){
$("#group_detailed_view_name_input").val(name);
$("#group_detailed_view_description_input").val(description);
if(readonly){
$("#group_detailed_view_name_input").prop('readonly', true);
$("#group_detailed_view_description_input").prop('readonly', true);
$(".group_detailed_view_submit_core_data").hide();
}
};
var set_group_detailed_view_member_list = function(members){
if(!members){
return;
}
$(members).each(function(index, value){
$(".group_detailed_view_tbody").append("<tr class=\"group_detailed_view_member_tr\" data-member-id=\""+value.entity_id+"\">\n" +
" <td><input type=\"checkbox\" class=\"group_detailed_view_member_selected\" data-member-id=\""+value.entity_id+"\"></td>\n" +
" <td>"+value.firstname+"</td>\n" +
" <td>"+value.lastname+"</td>\n" +
" </tr>");
});
};
var create_group_button_listener = function(){
if($.trim($('#new_group_name').val()) == ''){
alert("Fehler: Es wurde kein Gruppenname angegeben!");
return
}
var create_group_data = {};
var group_data = {};
var role_permissions = [];
group_data.group_name = $("#new_group_name").val();
if(!$.trim($("#new_group_description").val()) == ''){
group_data.group_description = $("#new_group_description").val();
}
$(".new_group_role_row").each(function(index, row){
var group_role_perm = {};
group_role_perm.role_id = $(row).data("role-id");
group_role_perm.permission_groups_core_edit = $(row).find("input.permission_groups_core_edit").prop("checked");
group_role_perm.permission_groups_delete = $(row).find("input.permission_groups_delete").prop("checked");
group_role_perm.permission_groups_members_view = $(row).find("input.permission_groups_members_view").prop("checked");
group_role_perm.permission_groups_members_edit = $(row).find("input.permission_groups_members_edit").prop("checked");
group_role_perm.permission_groups_permissions_edit = $(row).find("input.permission_groups_permissions_edit").prop("checked");
group_role_perm.permission_groups_permissions_view = $(row).find("input.permission_groups_permissions_view").prop("checked");
role_permissions.push(group_role_perm);
});
create_group_data.role_permissions = role_permissions;
create_group_data.group_data = group_data;
create_group_request(create_group_data);
};
var create_group_request = function(data){
$.ajax({
type: "POST",
url: "/api/groups",
contentType: 'application/json',
data: JSON.stringify(data),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
location.reload();//TODO: update data directly instead of reloading page
}
}
});
};
var delete_group_confirmation_check = function(){
if($("#groups_delete_modal_confirmation").val() === "GRUPPEN LÖSCHEN"){
$("#groups_delete_modal_delete_button").prop("disabled", false);
}
};
var delete_group_button_listener = function(){
delete_list = [];
var delete_list_names = "";
$(".group_entry_checkbox").each(function(){
if($(this).prop('checked')) {
delete_list.push($(this).data("group-id"));
delete_list_names = delete_list_names + $(this).data("group-name")+", ";
}
})
if(delete_list.length == 0){
return;
}
$("#groups_delete_modal_delete_button").prop("disabled", true);
$("#groups_delete_modal_group_list").html("");
$("#groups_delete_modal_confirmation").val("");
$("#groups_delete_modal_group_list").html(delete_list_names);
$("#groups_delete_modal").modal();
};
var check_all_button_listener = function(){
if(all_checked){
$(".group_entry_checkbox").prop('checked', false);
all_checked = false;
}else {
$(".group_entry_checkbox").prop('checked', true);
all_checked = true;
}
};
var delete_groups_request = function(){
if(delete_list.length===0){
return;
}
$.ajax({
type: "DELETE",
url: "/api/groups",
contentType: 'application/json',
data: JSON.stringify(delete_list),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
location.reload(); //TODO: delete row in table instead of reloading page
}
}
});
};
var add_member_click_listener = function(element){
GroupModule.add_member_to_group(active_group_id, $(element).data("entity-id"), $(element).data("firstname"), $(element).data("lastname"));
}
var add_member_to_group = function(group_id, member_id, member_firstname, member_lastname){
$.ajax({
type: "PUT",
url: "/api/groups/"+group_id+"/members/"+member_id,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$(".group_detailed_view_tbody").append("<tr class=\"group_detailed_view_member_tr\" data-member-id=\""+member_id+"\">\n" +
" <td><input type=\"checkbox\" class=\"group_detailed_view_member_selected\" data-member-id=\""+member_id+"\"></td>\n" +
" <td>"+member_firstname+"</td>\n" +
" <td>"+member_lastname+"</td>\n" +
" </tr>");
}
}
});
};
var check_all_members_listener = function(){
if(all_members_checked){
$(".group_detailed_view_member_selected").prop('checked', false);
all_members_checked = false;
}else {
$(".group_detailed_view_member_selected").prop('checked', true);
all_members_checked = true;
}
};
var remove_member_from_group_listener = function(){
$(".group_detailed_view_member_selected").each(function(){
var member_to_delete = $(this).data("member-id");
if($(this).prop("checked")) {
$.ajax({
type: "DELETE",
url: "/api/groups/"+active_group_id+"/members/"+member_to_delete,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$(".group_detailed_view_member_tr").each(function(){
if($(this).data("member-id") === member_to_delete){
$(this).remove();
}
});
}
}
});
}
})
};
var save_core_data_button_listener = function(){
var group = {};
group.group_name = $("#group_detailed_view_name_input").val();
var description = $("#group_detailed_view_description_input").val();
if(description!=null && description!==""){
group.group_description = description;
}
console.log(group);
$.ajax({
type: "PUT",
url: "/api/groups/"+active_group_id,
contentType: 'application/json',
data: JSON.stringify(group),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
location.reload();
}
}
});
};
return{
create_group_button_listener: create_group_button_listener,
delete_group_button_listener: delete_group_button_listener,
check_all_button_listener: check_all_button_listener,
delete_group_confirmation_check: delete_group_confirmation_check,
delete_groups_request: delete_groups_request,
load_group_detailed_view: load_group_detailed_view,
add_member_to_group: add_member_to_group,
add_member_click_listener: add_member_click_listener,
check_all_members_listener: check_all_members_listener,
remove_member_from_group_listener: remove_member_from_group_listener,
save_core_data_button_listener: save_core_data_button_listener,
};
})();

View File

@ -0,0 +1,509 @@
var member_id = "";
var qualification_id = "";
$( document ).ready(function() {
member_id = $("#member_entity_id").val();
if(member_id !== null){
$.getJSON("/api/member_management/get_qualifications?member_id=" + member_id, function (data) {
QualificationList.load_list(data)
});
$(".qualification_delete").on("click", QualificationList.delete_qualification);
$("#add_qualification_category").change(QualificationList.get_qualification_for_category);
$("#add_qualification_submit").on("click", QualificationList.add_qualification);
$(".drive_permission_update").on("click", DrivingPermissionModule.update);
CommunicationModule.update_communication_types();
$(".communication_edit_button").on("click", CommunicationModule.handle_edit_button);
$(".communication_save_new_button").on("click", CommunicationModule.handle_save_new_button);
$(".communication_delete_button").on("click", CommunicationModule.handle_delete_button);
$(".communication_edit_save_button").on("click", CommunicationModule.handle_edit_save_button);
UnitModule.get_units();
$("#add_operation_unit_submit").on("click", UnitModule.add_unit_submit_listener);
$(".delete_unit_from_member_button").on("click", UnitModule.delete_unit);
$("#login_allowed").on("change", LoginModule.login_allowed_checkbox_listener);
$("#login_save_button").on("click", LoginModule.login_submit);
}
});
QualificationList = ( function() {
var list = [];
var qualification_list = [];
var load_list = function (data) {
list = data;
};
var get_qualification_for_category = function (){
$("#add_qualification_qualification").children().remove();
if($("#add_qualification_category").val() === "none"){
$("#add_qualification_qualification").attr('disabled','disabled');
$("#add_qualification_submit").attr('disabled','disabled');
}else {
$.getJSON("/api/member_management/get_qualifications_for_category?category_id=" + $(this).val(), function (data) {
if (is_ok(data)) {
qualification_list = data;
for (var i = 0; i < qualification_list.length; i++) {
var option = "<option value=\"" + qualification_list[i].id + "\">" + qualification_list[i].name + "</option>";
$("#add_qualification_qualification").append(option);
}
if (qualification_list.length > 0) {
$("#add_qualification_qualification").removeAttr("disabled");
$("#add_qualification_submit").removeAttr("disabled");
}
}
});
}
};
var delete_qualification = function () {
qualification_id = $(this).data("qualification-id");
var category = $(this).data("category-id");
for(var i=0; i<list.categories.length;i++){ //Search for category
if (list.categories[i].id === category){ //Category found
for(var v=0;v<list.categories[i].qualifications.length;v++){
if (list.categories[i].qualifications[v].id === qualification_id){ //Qualification found
list.categories[i].qualifications.splice(v, 1);
if ($(this).parent().parent().children().length === 3){
$(this).parent().parent().remove();
}else {
$(this).parent().remove();
}
delete_qualification_save()
break;
}
}
break;
}
}
};
var delete_qualification_save = function(){
console.log("Trying to save");
$.ajax({
url: '/api/member_management/remove_qualification?member_id='+member_id+'&qualification_id='+qualification_id,
type: 'PUT',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
var add_qualification = function(){
var qual_id = $("#add_qualification_qualification").val();
var qual_category = $("#add_qualification_category").val();
$.ajax({
url: '/api/member_management/add_qualification?member_id='+member_id+'&qualification_id='+qual_id,
type: 'PUT',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
location.assign("/portal/mm/profile?action=edit&id="+member_id);
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
return{
load_list: load_list,
delete_qualification: delete_qualification,
get_qualification_for_category: get_qualification_for_category,
delete_qualification_save: delete_qualification_save,
add_qualification: add_qualification,
};
})();
DrivingPermissionModule = ( function() {
var update = function(){
var permission_name = $(this).data("permission-name");
var permission_level = $(this).data("permission-level");
if(permission_name === undefined || permission_level === undefined){
return;
}
switch(permission_level){
case "none":
update_to_license(this, permission_name);
break;
case "license":
update_to_drive_permission(this,permission_name);
break;
case "drive_permission":
update_to_none(this,permission_name);
break;
}
};
var update_to_license = function(element, permission_name){
if(permission_name === undefined){
return;
}
$.ajax({
url: '/api/member_management/add_driving_license?member_id='+member_id+'&license_name='+permission_name,
type: 'PUT',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$(element).removeClass("btn-success").addClass("btn-warning");
$(element).data("permission-level", "license");
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
var update_to_drive_permission = function(element,permission_name){
if(permission_name === undefined){
return;
}
$.ajax({
url: '/api/member_management/add_driving_permission?member_id='+member_id+'&license_name='+permission_name,
type: 'PUT',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$(element).removeClass("btn-warning").addClass("btn-success");
$(element).data("permission-level", "drive_permission");
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
};
var update_to_none = function(element,permission_name){
if(permission_name === undefined){
return;
}
$.ajax({
url: '/api/member_management/remove_driving_license?member_id='+member_id+'&license_name='+permission_name,
type: 'PUT',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$(element).removeClass("btn-warning").removeClass("btn-success");
$(element).data("permission-level", "none");
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
}
return{
update: update,
};
}
)();
CommunicationModule = ( function() {
var update_communication_types = function(){
$.getJSON("/api/member_management/get_communication_types", function (data){
if(is_ok(data)) {
$(".communication_types").empty().each(function () {
var type_id = $(this).data("type-id");
for (var i = 0; i < data.types.length; i++) {
var selected = "";
if (type_id === data.types[i].type_id) {
selected = "selected";
}
var option = "<option value=\"" + data.types[i].type_id + "\"" + selected + ">" + data.types[i].type_name + "</option>";
$(this).append(option);
}
});
}
});
};
var handle_edit_button = function(){
var row = $(this).parent().parent();
row.find("input").prop("readonly", false);
row.find("select").prop("disabled", false);
row.find(".communication_edit_save_button").prop("hidden", false);
$(this).hide();
};
var handle_save_new_button = function(){
var row = $(this).parent().parent();
var type_id = row.find(".communication_types").val();
var value = row.find(".communication_value").val();
var description = row.find(".communication_description").val();
if(type_id===""||value===""){
return;
}
var com_entry = {};
com_entry.entity_id = member_id;
com_entry.target_id = "00000000-0000-0000-0000-000000000000";
com_entry.type_id = type_id;
com_entry.type_name = "";
com_entry.value = value;
com_entry.description = description;
$.ajax({
type: "POST",
url: "/api/member_management/add_communication_target",
data: JSON.stringify(com_entry),
contentType: 'application/json',
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data){
if(is_ok(data)) {
row.before("<tr data-target-id=\"" + data.target_id + "\"><td><select data-type-id=\"" + com_entry.type_id + "\" disabled class=\"communication_types form-control\"></select></td><td><input type=\"text\" value=\"" + com_entry.value + "\" class=\"form-control\" readonly></td><td><input type=\"text\" value=\"" + com_entry.description + "\" class=\"form-control\" readonly></td><td><button class=\"iconbutton communication_edit_button\"><svg width=\"1.5em\" height=\"1.5em\" viewBox=\"0 0 16 16\" class=\"bi bi-pencil-square\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <path d=\"M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z\"/>\n" +
" <path fill-rule=\"evenodd\" d=\"M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z\"/>\n" +
" </svg></button>\n" +
" <button class=\"iconbutton communication_save_button\" hidden><svg width=\"1.5em\" height=\"1.5em\" viewBox=\"0 0 16 16\" class=\"bi bi-check-square\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <path fill-rule=\"evenodd\" d=\"M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z\"/>\n" +
" <path fill-rule=\"evenodd\" d=\"M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z\"/>\n" +
" </svg></button>\n" +
" <button class=\"iconbutton communication_delete_button\"><svg width=\"1.5em\" height=\"1.5em\" viewBox=\"0 0 16 16\" class=\"bi bi-trash\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
" <path d=\"M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z\"></path>\n" +
" <path fill-rule=\"evenodd\" d=\"M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z\"></path>\n" +
" </svg></button>\n" +
" </td></tr>");
//Apply button handlers to new buttons
$(".communication_edit_button").on("click", CommunicationModule.handle_edit_button);
$(".communication_save_new_button").on("click", CommunicationModule.handle_save_new_button);
$(".communication_delete_button").on("click", CommunicationModule.handle_delete_button);
//Clear input fields
row.find(".communication_types").val("");
row.find(".communication_value").val("");
row.find(".communication_description").val("");
update_communication_types()
}
},
});
};
var handle_delete_button = function(){
var row = $(this).parent().parent();
var target_id = row.data("target-id");
$.ajax({
type: "PUT",
url: "/api/member_management/remove_communication_target?target_id="+target_id,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
row.remove();
}
}
});
};
var handle_edit_save_button = function(){
var row = $(this).parent().parent();
var com_entry = {};
com_entry.com_type = row.find(".communication_types").val();
com_entry.value = row.find(".communication_value").val();
com_entry.description = row.find(".communication_description").val();
$.ajax({
type: "PUT",
url: "/api/communication_targets/"+row.data("target-id"),
contentType: 'application/json',
data: JSON.stringify(com_entry),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
row.find("input").prop("readonly", true);
row.find("select").prop("disabled", true);
row.find(".communication_edit_save_button").prop("hidden", true);
row.find(".communication_edit_button").show();
}
}
});
};
return{
update_communication_types: update_communication_types,
handle_edit_button: handle_edit_button,
handle_save_new_button: handle_save_new_button,
handle_delete_button: handle_delete_button,
handle_edit_save_button: handle_edit_save_button,
}
})();
UnitModule = (
function(){
var get_units = function(){
$.ajax({
type: "GET",
url: "/api/units",
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
var count = 0;
$(data).each(function(){
count++;
$("#add_operation_unit_unit").append("<option value=\""+this.unit_id+"\" data-unit_name=\""+this.name+"\">"+this.name+"</option>");
});
if(count>0 && $("#add_operation_unit_unit").val() != null){
$("#add_operation_unit_submit").prop("disabled", false);
}
}
}
});
};
var add_unit_submit_listener = function(){
var unit_id = $("#add_operation_unit_unit").val();
var unit_name = $("#add_operation_unit_unit option:selected").data("unit_name");
var crew = $("#add_operation_unit_crew").val();
$.ajax({
type: "PUT",
url: "/api/units/"+unit_id+"/members/"+member_id+"?crew="+crew,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#units_tbody").append("<tr>\n" +
" <td>"+unit_name+"</td>\n" +
" <td>"+crew+". Besetzung</td>\n" +
" <td><button type=\"button\" class=\"iconbutton delete_unit_from_member_button\" data-unit-id=\""+unit_id+"\">\n" +
" <svg width=\"1.5em\" height=\"1.5em\" fill=\"currentColor\">\n" +
" <use xlink:href=\"/img/bootstrap-icons.svg#trash\"/>\n" +
" </svg>\n" +
" </button></td>\n" +
" </tr>");
$(".delete_unit_from_member_button").on("click", UnitModule.delete_unit);
}
}
});
};
var delete_unit = function(){
var unit_id = $(this).data("unit-id");
var row = $(this).parent().parent();
$.ajax({
type: "DELETE",
url: "/api/units/"+unit_id+"/members/"+member_id,
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
row.remove();
}
}
});
}
return{
get_units: get_units,
add_unit_submit_listener: add_unit_submit_listener,
delete_unit: delete_unit,
}
}
)();
LoginModule = (function(){
var login_allowed_checkbox_listener = function(){
var ro = $("#login_email").prop('readonly');
if(ro){
$("#login_email").prop('readonly', false);
}else{
$("#login_email").prop('readonly', true);
}
};
var login_submit = function(){
var login_allowed = $("#login_allowed").prop('checked');
if(login_allowed){
var email = $("#login_email").val();
if(email.trim() === ""){
alert("Bitte eine Email-Adresse für den Login angeben!");
}else{
var user_id = $("#login_save_button").data("user-id");
if(user_id === ""){//New entry
var user = $();
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "POST",
url: "/api/users/",
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_save_button").data("user-id", data.user_id);
}
}
});
}else{ //Update existing entry
var user = $();
user.user_id = user_id;
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "PUT",
url: "/api/users/"+user_id,
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
}
}
});
}
}
}else{
//Delete user for member
$.ajax({
type: "DELETE",
url: "/api/users/"+$("#login_save_button").data("user-id"),
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_email").val("");
}
}
});
}
}
return{
login_allowed_checkbox_listener: login_allowed_checkbox_listener,
login_submit: login_submit,
}
})();

113
resources/js/searchbar.js Normal file
View File

@ -0,0 +1,113 @@
$( document ).ready(function() {
$('body').click(function(evt){
if(evt.target.class === "search-result-overlay")
return;
if($(evt.target).closest('.search-result-overlay').length)
return;
$(".search-result-overlay").hide();
});
Searchbar.set_active_entity();
//Remove current searchbar value on click
$("#searchbar").on("click", Searchbar.searchbar_onclick);
$("#searchbar").on("keyup", Searchbar.searchbar_typing);
$("#searchbar").bind("paste", Searchbar.searchbar_typing);
$("#searchbar").bind("cut", Searchbar.sWearchbar_typing);
$("#searchbar").on("mouseenter", function (){
$(".search-result-overlay").show();
});
$(".search-result-overlay").on("focusout", function (){
$(".search-result-overlay").hide();
})
$(".search-result-overlay").on("mouseleave", function (){
$(".search-result-overlay").hide();
})
});
Searchbar = ( function() {
var set_active_entity = function(){
var entity_id = $("#searchbar").data("active-entity");
var type = $("#searchbar").data("entity-type");
if(type === "member" && entity_id !== ""){
$("#searchbar-type").html("Mitglied: ");
$.ajax({
url: '/api/members/'+entity_id,
type: 'GET',
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$("#searchbar").val(data.firstname+" "+data.lastname);
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
}else{
console.log("Unknown search entity type: "+type);
}
};
var searchbar_onclick = function(){
$("#searchbar").val("");
$(".search-result-overlay").show()
};
var searchbar_typing = function (){
var type = $("#searchbar").data("entity-type");
$("#searchbar-type").html("");
$(".search-result-overlay-list").html("");
$(".search-result-overlay").show();
if(type === "member"){
$.ajax({
url: '/api/members/',
type: 'GET',
data: {
"name": $("#searchbar").val()
},
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$.each(data.members, function(index, value){
$(".search-result-overlay-list").append("<a href=\"/portal/mm/profile?action=view&id="+value.entity_id+"\"><li class='list-group-item'><span class=\"badge badge-secondary\">Mitglied</span> "+value.firstname+" "+value.lastname+"</li></a>")
});
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
}else{
$.ajax({
url: '/api/members/',
type: 'GET',
data: {
"name": $("#searchbar").val()
},
contentType: 'application/json',
success: function(data) {
if(is_ok(data)) {
$.each(data.members, function(index, value){
$(".search-result-overlay-list").append("<a href=\"/portal/mm/profile?action=view&id="+value.entity_id+"\"><li class='list-group-item'><span class=\"badge badge-secondary\">Mitglied</span> "+value.firstname+" "+value.lastname+"</li></a>")
});
}
},
timeout: 3000,
error: function() {
alert("Verbindung zum Server unterbrochen!");
}
});
}
};
return {
set_active_entity : set_active_entity,
searchbar_onclick : searchbar_onclick,
searchbar_typing : searchbar_typing,
};
})();

View File

@ -0,0 +1,7 @@
Jemand hat das Zurücksetzen des Passworts auf {{frontpage}} für die Email
{{email}}
angefordert.
Falls dies nicht beabsichtigt war, ignoriere einfach diese E-Mail. Dein altes Passwort bleibt wirksam.
Um dein Passwort zurückzusetzen, besuche folgende Adresse:
{{reset_url}}

View File

@ -0,0 +1 @@
<div class="alert {{alert.class}}" role="alert">{{alert.content}}</div>

View File

@ -0,0 +1,21 @@
<div class="modal fade" id="groups_delete_modal" tabindex="-1" role="dialog" aria-labelledby="groups_delete_modal_label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="groups_delete_modal_label">Gruppen löschen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<b>Sollen die Gruppen <span id="groups_delete_modal_group_list"></span> UNWIDERRUFLICH gelöscht werden?</b>
<p>Zur Bestätigung GRUPPEN LÖSCHEN eintragen:</p>
<input type="text" class="form-control" id="groups_delete_modal_confirmation" placeholder="GRUPPEN LÖSCHEN">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="groups_delete_modal_abort_button" data-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-danger" id="groups_delete_modal_delete_button">Gruppen löschen</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
<div class="modal fade" id="member_delete_modal" tabindex="-1" role="dialog" aria-labelledby="member_delete_modal_label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="member_delete_modal_label">Mitglied löschen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<b>Soll das Mitglied <span id="member_delete_modal_member_name"></span> UNWIDERRUFLICH gelöscht werden?</b>
<p>Zur Bestätigung den vollständigen Namen eintragen:</p>
<input type="text" class="form-control" id="member_delete_modal_name_confirmation">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="member_delete_modal_abort_button" data-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-danger" id="member_delete_modal_delete_button">Mitglied löschen</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
<script src="/js/lib/jquery-3.5.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="/js/global.js"></script>
<script src="/js/searchbar.js"></script>
{{#each footer.scripts}}
<script src="{{path}}"></script>
{{/each}}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="{{header.html_language}}">
<head>
<meta charset="UTF-8">
<title>{{header.site_title}}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
{{#each header.stylesheets}}
<link rel="stylesheet" href="{{path}}">
{{/each}}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

View File

@ -0,0 +1,15 @@
{{> header }}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
{{> searchbar}}
<hr>
{{#if alert}}
{{> alert}}
{{/if}}
<h1>Willkommen beim ERMS!</h1>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,79 @@
{{> header }}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
{{> searchbar}}
<hr>
{{#if alert}}
{{> alert}}
{{/if}}
<h1>Neues Mitglied anlegen</h1>
<div class="col">
<form action="#" method="post">
<div class="row">
<div class="col-lg-6">
<div class="form-group row">
<label for="salutation" class="col-sm-3 col-form-label">Anrede*</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="salutation" name="salutation" required>
</div>
</div>
<div class="form-group row">
<label for="academic_titles" class="col-sm-3 col-form-label">akadem. Titel</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="academic_titles" name="academic_titles">
</div>
</div>
<hr>
<div class="form-group row">
<label for="firstname" class="col-sm-3 col-form-label">Vorname*</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="firstname" name="firstname" required>
</div>
</div>
<div class="form-group row">
<label for="lastname" class="col-sm-3 col-form-label">Nachname*</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="lastname" name="lastname" required>
</div>
</div>
<div class="form-group row">
<label for="sex" class="col-sm-3 col-form-label">Geschlecht*</label>
<div class="col-sm-9">
<select class="form-control" id="sex" name="sex" required>
<option value="0"></option>
<option value="1">männlich</option>
<option value="2">weiblich</option>
<option value="9">anderes</option>
</select>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="form-group row">
<label for="personnel_number" class="col-sm-3 col-form-label">Personalnummer</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="personnel_number" name="personnel_number">
</div>
</div>
<div class="form-group row">
<label for="entrance_date" class="col-sm-3 col-form-label">Eintrittsdatum</label>
<div class="col-sm-9">
<input type="date" class="form-control" id="entrance_date" name="entrance_date">
</div>
</div>
</div>
</div>
<div class="row">
<p>* Pflichtfelder</p>
<div class="col text-center">
<button type="submit" class="btn btn-success">Mitglied anlegen</button>
</div>
</div>
</form>
</div>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,165 @@
{{> header }}
{{> delete-groups-modal}}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
{{> searchbar}}
<hr>
{{#if alert}}
{{> alert}}
{{/if}}
<div class="col">
<div class="row">
<div class="col-md-6">
{{#if_in_list ../../caller_permissions "modules.member_management.groups.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Gruppen</div>
<div class="card-body" id="groups">
<table class="table table-hover table-striped">
<thead class="thead">
<tr>
<th><button class="iconbutton check_all_groups"><svg width="1.25em" height="1.25em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#check-all"/>
</svg></button></th>
<th>Name</th>
<th>Beschreibung</th>
<th># Mitglieder</th>
</tr>
</thead>
<tbody>
{{#each groups}}
<tr data-group-id="{{group_id}}" {{#if (or permission_edit_core permission_view_members)}}style="cursor: pointer;"{{/if}} class="{{#if (or permission_edit_core permission_view_members)}}group_list_row{{/if}} {{#if (not permission_delete)}}text-muted{{/if}}">
<td>{{#if (not permission_delete)}}<input type="checkbox" disabled>{{else}}<input type="checkbox" class="group_entry_checkbox" data-group-id="{{group_id}}" data-group-name="{{name}}">{{/if}}</td>
<td>{{name}}</td>
<td>{{description}}</td>
<td>{{members_count}}</td>
</tr></span>
{{/each}}
</tbody>
</table>
<span><button class="iconbutton check_all_groups" data-check-all-selector=".check_all_groups"><svg width="1.25em" height="1.25em" fill="currentColor" style="margin-left: 12px;margin-right: 12px;"><use xlink:href="/img/bootstrap-icons.svg#check-all"/></svg></button><!-- <button style="margin-right: 12px;" type="button" class="btn btn-secondary btn-sm">Duplizieren</button>-->{{#if_in_list ../../caller_permissions "modules.member_management.groups.delete"}}<button type="button" class="btn btn-danger btn-sm groups_delete_button">Löschen</button>{{/if_in_list}}</span>
</div>
</div>{{/if_in_list}}
{{#if_in_list caller_permissions "modules.member_management.groups.create"}}<div class="card bg-light mb-3">
<div class="card-header">Neue Gruppe anlegen</div>
<div class="card-body" id="groups">
<form>
<div class="form-group row">
<label for="new_group_name" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="new_group_name">
</div>
</div>
<div class="form-group row">
<label for="new_group_description" class="col-sm-2 col-form-label">Beschreibung</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="new_group_description">
</div>
</div>
<div class="form-group row">
<b>Zugriff beschränken</b>
<p>Hinweis: Einer Rolle kann nur ein Recht für die neue Gruppe gegeben werden, wenn die Rolle das Recht bereits besitzt. Beispiel: Wenn die Rolle "admin" das Recht "Gruppen löschen" nicht besitzt, kann ihr nicht das Recht gegeben werden, die neue Gruppe zu löschen.</p>
<div>
<table class="table table-header-rotated">
<thead>
<tr>
<th scope="col">Rolle</th>
<th scope="col" class="rotate"><div><span>Name/Beschreibung ändern</span></div></th>
<th scope="col" class="rotate"><div><span>löschen</span></div></th>
<th scope="col" class="rotate"><div><span>Gruppenmitglieder sehen</span></div></th>
<th scope="col" class="rotate"><div><span>Gruppenmitglieder ändern</span></div></th>
<th scope="col" class="rotate"><div><span>Zugriffsbeschränkungen sehen</span></div></th>
<th scope="col" class="rotate"><div><span>Zugriffsbeschränkungen ändern</span></div></th>
</tr>
</thead>
<tbody>
{{#each roles}}
<tr data-role-id="{{id}}" class="new_group_role_row">
<td title="{{description}}">{{id}}</td>
<td><input type="checkbox" class="permission_groups_core_edit"></td>
<td><input type="checkbox" class="permission_groups_delete"></td>
<td><input type="checkbox" class="permission_groups_members_view"></td>
<td><input type="checkbox" class="permission_groups_members_edit"></td>
<td><input type="checkbox" class="permission_groups_permissions_edit"></td>
<td><input type="checkbox" class="permission_groups_permissions_view"></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
<button type="button" class="btn btn-primary new_group_button" style="float: right">Gruppe Hinzufügen</button>
</form>
</div>
</div>{{/if_in_list}}
</div>
<div class="col-md-6">
<div class="card bg-light mb-3 group_detailed_view" hidden>
<div class="card-header">Gruppe <span class="group_detailed_view_name"></span></div>
<div class="card-body" id="groups">
<div class="group_detailed_view_core_data">
<div class="form-group row">
<label for="group_detailed_view_name_input" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="group_detailed_view_name_input">
</div>
</div>
<div class="form-group row">
<label for="group_detailed_view_description_input" class="col-sm-2 col-form-label">Beschreibung</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="group_detailed_view_description_input">
</div>
</div>
<button type="button" class="group_detailed_view_submit_core_data btn btn-primary" style="float: right; margin-bottom:15px;">Änderungen Speichern</button>
</div>
<div class="group_detailed_view_member_list">
<table class="table">
<thead>
<tr>
<th><button class="iconbutton group_detailed_view_check_all_members"><svg width="1.25em" height="1.25em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#check-all"/>
</svg></button></th>
<th>Vorname</th>
<th>Nachname</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody class="group_detailed_view_tbody"></tbody>
</table>
{{#if_in_list caller_permissions "modules.member_management.groups.members.edit"}}
<div>
<button class="iconbutton group_detailed_view_check_all_members"><svg width="1.25em" height="1.25em" fill="currentColor" style="margin-left: 12px;margin-right: 12px;"><use xlink:href="/img/bootstrap-icons.svg#check-all"/></svg></button><button type="button" class="btn btn-warning btn-sm group-detailed-view-remove-member-button">Entfernen</button>
</div><br>
<div class="group_detailed_view_add_member">
<div class="form-group row">
<label for="group-detailed-view-member-searchbar" class="col-sm-2 col-form-label">Mitglied hinzufügen:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="group-detailed-view-member-searchbar">
<span class="input-group-append">
<span class="btn btn-outline-secondary" type="button">
<svg width="16" height="16" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#search"/>
</svg>
</span>
</span>
</div>
<div class="group-detailed-view-member-search-result-overlay" style="display: none">
<ul class="list-group group-detailed-view-member-search-result-overlay-list">
</ul>
</div>
</div>
</div>
</div>{{/if_in_list}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,281 @@
{{> header }}
{{> delete-member-modal}}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
<div class="col">
<div class="row">
<div class="col-10">
<h1>Mein Profil</h1>
</div>
<div class="col-2">
{{#if member}}<div class="iconbar">
{{#if readonly}}<a href="/portal/personal_profile?action=edit"><svg width="2em" height="2em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#pencil-square"/>
</svg></a>{{else}}
<button type="submit" class="iconbutton" form="member_management_profile_form">
<svg width="2.1em" height="2.1em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#check2-square"/>
</svg>
</button>
<a href="/portal/personal_profile?action=view"><svg width="2em" height="2em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#x-square"/>
</svg></a>
{{/if}}
</div>{{/if}}
</div>
</div>
</div>
<hr>
<div class="col">
<form id="member_management_profile_form" action="/portal/personal_profile?action=view" method="post">
<input type="hidden" id="member_entity_id" value="{{member.entity_id}}" name="entity_id">
<div class="row">
<div class="col-lg-5">
<div class="form-group row">
<label for="salutation" class="col-sm-3 col-form-label">Anrede</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="salutation" name="salutation" readonly value="{{member.salutation}}">
</div>
</div>
<div class="form-group row">
<label for="academic_titles" class="col-sm-3 col-form-label">akadem. Titel</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="academic_titles" name="academic_titles" readonly value="{{member.academic_titles}}">
</div>
</div>
<hr>
<div class="form-group row">
<label for="firstname" class="col-sm-3 col-form-label">Vorname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="firstname" name="firstname" readonly value="{{member.firstname}}">
</div>
</div>
<div class="form-group row">
<label for="lastname" class="col-sm-3 col-form-label">Nachname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="lastname" name="lastname" readonly value="{{member.lastname}}">
</div>
</div>
<div class="form-group row">
<label for="sex" class="col-sm-3 col-form-label">Geschlecht</label>
<div class="col-sm-9">
<input type="hidden" id="sex" name="sex" value="{{member.sex}}">
<select class="form-control" id="sex" name="sex" disabled readonly>
<option value="0" {{#if (eq member.sex 0)}}selected{{/if}}></option>
<option value="1" {{#if (eq member.sex 1)}}selected{{/if}}>männlich</option>
<option value="2" {{#if (eq member.sex 2)}}selected{{/if}}>weiblich</option>
<option value="9" {{#if (eq member.sex 9)}}selected{{/if}}>anderes</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="date_of_birth" class="col-sm-3 col-form-label">Geburtsdatum</label>
<div class="col-sm-9">
<input type="date" class="form-control" id="date_of_birth" name="date_of_birth" {{#if readonly}}readonly{{/if}} value="{{member.date_of_birth}}">
</div>
</div>
<div class="form-group row">
<label for="age" class="col-sm-3 col-form-label">Alter (berechnet)</label>
<div class="col-sm-9">
<input type="number" class="form-control" id="age" {{#if readonly}}readonly{{/if}} value="{{member_age}}">
</div>
</div>
<div class="form-group row">
<label for="place_of_birth" class="col-sm-3 col-form-label">Geburtsort</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="place_of_birth" name="place_of_birth" {{#if readonly}}readonly{{/if}} value="{{member.place_of_birth}}">
</div>
</div>
<div class="form-group row">
<label for="birth_name" class="col-sm-3 col-form-label">Geburtsname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="birth_name" name="birth_name" {{#if readonly}}readonly{{/if}} value="{{member.birth_name}}">
</div>
</div>
<div class="form-group row">
<label for="nationality" class="col-sm-3 col-form-label">Staatsangehörigkeit</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="nationality" name="nationality" {{#if readonly}}readonly{{/if}} value="{{member.nationality}}">
</div>
</div>
</div>
<div class="col-lg-5">
<div class="form-group row">
<label for="personnel_number" class="col-sm-3 col-form-label">Personalnummer</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="personnel_number" name="personnel_number" readonly value="{{member.personnel_number}}">
</div>
</div>
<div class="form-group row">
<label for="entrance_date" class="col-sm-3 col-form-label">Eintrittsdatum</label>
<div class="col-sm-9">
<input type="date" class="form-control" id="entrance_date" name="entrance_date" readonly value="{{member.entrance_date}}">
</div>
</div>
<hr>
<input type="hidden" value="{{address.id}}" name="address_id">
<div class="form-group row">
<label for="street" class="col-sm-3 col-form-label">Straße</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="street" name="address_street" {{#if readonly}}readonly{{/if}} value="{{address.street}}">
</div>
</div>
<div class="form-group row">
<label for="street_number" class="col-sm-3 col-form-label">Hausnummer</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="street_number" name="address_number" {{#if readonly}}readonly{{/if}} value="{{address.number}}">
</div>
</div>
<div class="form-group row">
<label for="zipcode" class="col-sm-3 col-form-label">Postleitzahl</label>
<div class="col-sm-9">
<input type="number" class="form-control" id="zipcode" name="address_zipcode" {{#if readonly}}readonly{{/if}} value="{{address.zipcode}}">
</div>
</div>
<div class="form-group row">
<label for="city" class="col-sm-3 col-form-label">Ort</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="city" name="address_city" {{#if readonly}}readonly{{/if}} value="{{address.city}}">
</div>
</div>
<div class="form-group row">
<label for="country" class="col-sm-3 col-form-label">Land</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="country" name="address_country" {{#if readonly}}readonly{{/if}} value="{{address.country}}">
</div>
</div>
<hr>{{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.view"}}
<div class="form-group row">
<label for="iban" class="col-sm-3 col-form-label">IBAN:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="iban" name="iban" {{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.edit"}}{{#if ../readonly}}readonly{{/if}}{{else}}readonly{{/if_in_list}} value="{{member.iban}}">
</div>
</div>
<div class="form-group row">
<label for="bic" class="col-sm-3 col-form-label">BIC:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="bic" name="bic" {{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.edit"}}{{#if ../readonly}}readonly{{/if}}{{else}}readonly{{/if_in_list}} value="{{member.bic}}">
</div>
</div>
<hr>{{/if_in_list}}
</div>
<div class="col-lg-2">
<!-- <img class="profile_image img-fluid" src="">-->
</div>
</div>
</form>
<hr>
<div class="row">
<div class="col-lg-6">
<div class="card bg-light mb-3">
<div class="card-header">Qualifikationen </div>
<div class="card-body" id="qualification_categories">
{{#each qualification_categories}}
{{#if visible}}
<span data-category-id="{{id}}"><b>{{name}}: </b>{{#each qualifications}}<span class="badge badge-secondary" data-type="qualification">{{name}}
</span>
{{/each}}<br></span>
{{/if}}
{{/each}}
</div>
</div>
<div class="card bg-light mb-3">
<div class="card-header">Kommunikation</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th scope="col">Typ</th>
<th scope="col">Wert</th>
<th scope="col">Beschreibung</th>
{{#if (not ../readonly)}}<th scope="col">Aktionen</th>{{/if}}
</tr>
</thead>
<tbody>
{{#each communication_targets}}
<tr data-target-id="{{target_id}}">
{{#if ../readonly}}
<td><input type="text" value="{{type_name}}" readonly size="8" readonly></td>
{{else}}
<td><select data-type-id="{{type_id}}" disabled class="communication_types form-control"></select></td>
{{/if}}
<td><input type="text" value="{{value}}" class="form-control communication_value" readonly></td>
<td><input type="text" value="{{description}}" class="form-control communication_description" readonly></td>
{{#if (not ../readonly)}}<td><button class="iconbutton communication_edit_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-pencil-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
</svg></button>
<button class="iconbutton communication_edit_save_button" hidden><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-check-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
</svg></button>
<button class="iconbutton communication_delete_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-trash" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"></path>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"></path>
</svg></button>
</td>{{/if}}
</tr>
{{/each}}
{{#if (not ../readonly)}}
<tr>
<td><select class="communication_types form-control"></select></td>
<td><input type="text" class="form-control communication_value"></td>
<td><input type="text" class="form-control communication_description"></td>
<td><button class="iconbutton communication_save_new_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-check-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
</svg></button></td>
</tr>{{/if}}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card bg-light mb-3">
<div class="card-header">Fahrberechtigungen & Führerscheine</div>
<div class="card-body">
{{#each licenses}}
<button type="button" data-permission-name="{{category_name}}" data-permission-level="{{#if drive_permission}}drive_permission{{else}}{{#if license}}license{{else}}none{{/if}}{{/if}}" class="btn {{#if drive_permission}}btn-success{{else}}{{#if license}}btn-warning{{/if}}{{/if}} drive_permission_update" data-toggle="tooltip" data-placement="bottom" title="{{category_description}}">{{category_name}}</button>
{{/each}}
<hr>
<button type="button" class="btn btn-success">Führerschein + Fahrerlaubnis vorhanden</button>
<button type="button" class="btn btn-warning">Führerschein vorhanden, keine Fahrerlaubnis</button>
</div>
</div>{{#if_in_list ../../caller_permissions "modules.member_management.profile.units.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Einsatzeinheiten</div>
<div class="card-body">
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th>Einheit</th>
<th>Besetzung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="units_tbody">{{#each units}}
<tr>
<td>{{name}}</td>
<td>{{crew}}. Besetzung</td>
<td></td>
</tr>{{/each}}
</tbody>
</table></div>
</div>
</div>
</div>{{/if_in_list}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,335 @@
{{> header }}
{{> delete-member-modal}}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
<div class="col">
<div class="row">
<div class="col-10">
{{> searchbar active_entity=member.entity_id entity_type="member"}}
</div>
<div class="col-2">
{{#if member}}<div class="iconbar">
{{#if readonly}}<a href="/portal/mm/profile?action=edit&id={{member.entity_id}}"><svg width="2em" height="2em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#pencil-square"/>
</svg></a>{{else}}
<button type="submit" class="iconbutton" form="member_management_profile_form">
<svg width="2.1em" height="2.1em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#check2-square"/>
</svg>
</button>
<a href="/portal/mm/profile?action=view&id={{member.entity_id}}"><svg width="2em" height="2em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#x-square"/>
</svg></a>
{{/if}}
<button class="iconbutton delete_member_button" data-member-id="{{member.entity_id}}" data-member-firstname="{{member.firstname}}" data-member-lastname="{{member.lastname}}">
<svg width="2em" height="2em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#trash"/>
</svg>
</button>
</div>{{/if}}
</div>
</div>
</div>
<hr>{{#if member}}
<div class="col">
<form id="member_management_profile_form" action="/portal/mm/profile?action=view&id={{member.entity_id}}" method="post">
<input type="hidden" id="member_entity_id" value="{{member.entity_id}}" name="entity_id">
<div class="row">
<div class="col-lg-5">
<div class="form-group row">
<label for="salutation" class="col-sm-3 col-form-label">Anrede</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="salutation" name="salutation" {{#if readonly}}readonly{{/if}} value="{{member.salutation}}">
</div>
</div>
<div class="form-group row">
<label for="academic_titles" class="col-sm-3 col-form-label">akadem. Titel</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="academic_titles" name="academic_titles" {{#if readonly}}readonly{{/if}} value="{{member.academic_titles}}">
</div>
</div>
<hr>
<div class="form-group row">
<label for="firstname" class="col-sm-3 col-form-label">Vorname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="firstname" name="firstname" {{#if readonly}}readonly{{/if}} value="{{member.firstname}}">
</div>
</div>
<div class="form-group row">
<label for="lastname" class="col-sm-3 col-form-label">Nachname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="lastname" name="lastname" {{#if readonly}}readonly{{/if}} value="{{member.lastname}}">
</div>
</div>
<div class="form-group row">
<label for="sex" class="col-sm-3 col-form-label">Geschlecht</label>
<div class="col-sm-9">
<select class="form-control" id="sex" name="sex" {{#if readonly}}readonly disabled{{/if}}>
<option value="0" {{#if (eq member.sex 0)}}selected{{/if}}></option>
<option value="1" {{#if (eq member.sex 1)}}selected{{/if}}>männlich</option>
<option value="2" {{#if (eq member.sex 2)}}selected{{/if}}>weiblich</option>
<option value="9" {{#if (eq member.sex 9)}}selected{{/if}}>anderes</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="date_of_birth" class="col-sm-3 col-form-label">Geburtsdatum</label>
<div class="col-sm-9">
<input type="date" class="form-control" id="date_of_birth" name="date_of_birth" {{#if readonly}}readonly{{/if}} value="{{member.date_of_birth}}">
</div>
</div>
<div class="form-group row">
<label for="age" class="col-sm-3 col-form-label">Alter (berechnet)</label>
<div class="col-sm-9">
<input type="number" class="form-control" id="age" {{#if readonly}}readonly{{/if}} value="{{member_age}}">
</div>
</div>
<div class="form-group row">
<label for="place_of_birth" class="col-sm-3 col-form-label">Geburtsort</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="place_of_birth" name="place_of_birth" {{#if readonly}}readonly{{/if}} value="{{member.place_of_birth}}">
</div>
</div>
<div class="form-group row">
<label for="birth_name" class="col-sm-3 col-form-label">Geburtsname</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="birth_name" name="birth_name" {{#if readonly}}readonly{{/if}} value="{{member.birth_name}}">
</div>
</div>
<div class="form-group row">
<label for="nationality" class="col-sm-3 col-form-label">Staatsangehörigkeit</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="nationality" name="nationality" {{#if readonly}}readonly{{/if}} value="{{member.nationality}}">
</div>
</div>
</div>
<div class="col-lg-5">
<div class="form-group row">
<label for="personnel_number" class="col-sm-3 col-form-label">Personalnummer</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="personnel_number" name="personnel_number" {{#if readonly}}readonly{{/if}} value="{{member.personnel_number}}">
</div>
</div>
<div class="form-group row">
<label for="entrance_date" class="col-sm-3 col-form-label">Eintrittsdatum</label>
<div class="col-sm-9">
<input type="date" class="form-control" id="entrance_date" name="entrance_date" {{#if readonly}}readonly{{/if}} value="{{member.entrance_date}}">
</div>
</div>
<hr>
<input type="hidden" value="{{address.id}}" name="address_id">
<div class="form-group row">
<label for="street" class="col-sm-3 col-form-label">Straße</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="street" name="address_street" {{#if readonly}}readonly{{/if}} value="{{address.street}}">
</div>
</div>
<div class="form-group row">
<label for="street_number" class="col-sm-3 col-form-label">Hausnummer</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="street_number" name="address_number" {{#if readonly}}readonly{{/if}} value="{{address.number}}">
</div>
</div>
<div class="form-group row">
<label for="zipcode" class="col-sm-3 col-form-label">Postleitzahl</label>
<div class="col-sm-9">
<input type="number" class="form-control" id="zipcode" name="address_zipcode" {{#if readonly}}readonly{{/if}} value="{{address.zipcode}}">
</div>
</div>
<div class="form-group row">
<label for="city" class="col-sm-3 col-form-label">Ort</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="city" name="address_city" {{#if readonly}}readonly{{/if}} value="{{address.city}}">
</div>
</div>
<div class="form-group row">
<label for="country" class="col-sm-3 col-form-label">Land</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="country" name="address_country" {{#if readonly}}readonly{{/if}} value="{{address.country}}">
</div>
</div>
<hr>{{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.view"}}
<div class="form-group row">
<label for="iban" class="col-sm-3 col-form-label">IBAN:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="iban" name="iban" {{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.edit"}}{{#if ../readonly}}readonly{{/if}}{{else}}readonly{{/if_in_list}} value="{{member.iban}}">
</div>
</div>
<div class="form-group row">
<label for="bic" class="col-sm-3 col-form-label">BIC:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="bic" name="bic" {{#if_in_list caller_permissions "modules.member_management.profile.iban_bic.edit"}}{{#if ../readonly}}readonly{{/if}}{{else}}readonly{{/if_in_list}} value="{{member.bic}}">
</div>
</div>
<hr>{{/if_in_list}}
</div>
<div class="col-lg-2">
<!-- <img class="profile_image img-fluid" src="">-->
</div>
</div>
</form>
<hr>
<div class="row">
<div class="col-lg-6">
<div class="card bg-light mb-3">
<div class="card-header">Qualifikationen </div>
<div class="card-body" id="qualification_categories">
{{#each qualification_categories}}
{{#if visible}}
<span data-category-id="{{id}}"><b>{{name}}: </b>{{#each qualifications}}<span class="badge badge-secondary" data-type="qualification">{{name}}
<span class="qualification_delete" data-qualification-id="{{id}}" data-category-id="{{../id}}" {{#if (../../readonly)}}hidden{{/if}}><svg width="1em" height="1em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#trash"/>
</svg></span></span>
{{/each}}<br></span>
{{/if}}
{{/each}}
{{#if (not ../../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.qualifications.edit"}}<hr><div class="row">
<div class="col-3">
<b>Qualifikation hinzufügen:</b></div>
<div class="col-3"><select id="add_qualification_category" class="form-control">
<option value="none"></option>
{{#each qualification_categories}}<option value="{{id}}">{{name}}</option>{{/each}}
</select></div>
<div class="col-3"><select id="add_qualification_qualification" class="form-control" disabled><option value="none"></option></select>
</div><div class="col-3"><button id="add_qualification_submit" class="btn btn-primary" disabled>Hinzufügen</button>
</div></div>{{/if_in_list}}{{/if}}
</div>
</div>
{{#if_in_list ../../caller_permissions "modules.member_management.profile.communication.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Kommunikation</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th scope="col">Typ</th>
<th scope="col">Wert</th>
<th scope="col">Beschreibung</th>
{{#if (not ../readonly)}}<th scope="col">Aktionen</th>{{/if}}
</tr>
</thead>
<tbody>
{{#each communication_targets}}
<tr data-target-id="{{target_id}}">
{{#if ../readonly}}
<td><input type="text" value="{{type_name}}" readonly size="8" readonly></td>
{{else}}
<td><select data-type-id="{{type_id}}" disabled class="communication_types form-control"></select></td>
{{/if}}
<td><input type="text" value="{{value}}" class="form-control communication_value" readonly></td>
<td><input type="text" value="{{description}}" class="form-control communication_description" readonly></td>
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.communication.edit"}}<td><button class="iconbutton communication_edit_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-pencil-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
</svg></button>
<button class="iconbutton communication_edit_save_button" hidden><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-check-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
</svg></button>
<button class="iconbutton communication_delete_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-trash" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"></path>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"></path>
</svg></button>
</td>{{/if_in_list}}{{/if}}
</tr>
{{/each}}
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.communication.edit"}}
<tr>
<td><select class="communication_types form-control"></select></td>
<td><input type="text" class="form-control communication_value"></td>
<td><input type="text" class="form-control communication_description"></td>
<td><button class="iconbutton communication_save_new_button"><svg width="1.5em" height="1.5em" viewBox="0 0 16 16" class="bi bi-check-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
</svg></button></td>
</tr>
{{/if_in_list}}{{/if}}
</tbody>
</table>
</div>
</div>{{/if_in_list}}
{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Login</div>
<div class="card-body">
<div class="col">
<p>Achtung: Wird der Login deaktiviert, werden Email-Adresse und Passwort dauerhaft gelöscht und müssen nach dann ggf. neu gesetzt werden.</p>
<div class="form-check">
<input type="checkbox" class="form-check-input" {{#if login.login_allowed}}checked{{/if}} id="login_allowed" {{#if ../readonly}}disabled{{/if}}>
<label class="form-check-label" for="login_allowed">Login aktiviert</label>
</div><hr>
<div class="form-group row">
<label for="login_email" class="col-sm-3">Email-Adresse</label>
<input type="email" class="form-control col-sm-9" id="login_email" value="{{login.email}}" {{#if (not login.login_allowed)}}readonly{{/if}}{{#if ../readonly}}readonly{{/if}}>
</div>
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.edit"}}<button type="button" id="login_save_button" class="btn btn-primary" data-user-id="{{login.user_id}}" style="float: right">Speichern</button>{{/if_in_list}}{{/if}}
</div>
</div>
</div>{{/if_in_list}}
</div>
<div class="col-lg-6">
<div class="card bg-light mb-3">
<div class="card-header">Fahrberechtigungen & Führerscheine</div>
<div class="card-body">
{{#each licenses}}
<button type="button" data-permission-name="{{category_name}}" data-permission-level="{{#if drive_permission}}drive_permission{{else}}{{#if license}}license{{else}}none{{/if}}{{/if}}" class="btn {{#if drive_permission}}btn-success{{else}}{{#if license}}btn-warning{{/if}}{{/if}} drive_permission_update" data-toggle="tooltip" data-placement="bottom" title="{{category_description}}">{{category_name}}</button>
{{/each}}
<hr>
<button type="button" class="btn btn-success">Führerschein + Fahrerlaubnis vorhanden</button>
<button type="button" class="btn btn-warning">Führerschein vorhanden, keine Fahrerlaubnis</button>
</div>
</div>{{#if_in_list ../../caller_permissions "modules.member_management.profile.units.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Einsatzeinheiten</div>
<div class="card-body">
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th>Einheit</th>
<th>Besetzung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="units_tbody">{{#each units}}
<tr>
<td>{{name}}</td>
<td>{{crew}}. Besetzung</td>
<td>{{#if (not ../../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.units.edit"}}<button type="button" class="iconbutton delete_unit_from_member_button" data-unit-id="{{unit_id}}">
<svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#trash"/>
</svg>
</button>{{/if_in_list}}{{/if}}</td>
</tr>{{/each}}
</tbody>
</table></div>
</div>{{#if_in_list ../../caller_permissions "modules.member_management.profile.units.edit"}}{{#if (not ../readonly)}}
<hr>
<div class="row">
<div class="col-3">
<b>Einsatzeinheit hinzufügen:</b></div>
<div class="col-3"><select id="add_operation_unit_unit" class="form-control">
</select></div>
<div class="col-3">
<select id="add_operation_unit_crew" class="form-control">
<option value="1">1. Besetzung</option>
<option value="2">2. Besetzung</option>
</select>
</div><div class="col-3"><button id="add_operation_unit_submit" class="btn btn-primary" disabled="">Hinzufügen</button>
</div></div>{{/if}}{{/if_in_list}}
</div>
</div>{{/if_in_list}}
</div>
</div>
</div>
{{else}}<h3>Bitte Mitglied auswählen.</h3>{{/if}}
</div>
</div>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,107 @@
{{> header }}
{{> delete-member-modal}}
<div class="container-fluid">
<div class="row">
<div class="wrapper">
{{> sidebar }}
<div id="content">
{{> searchbar}}
<hr>
<h1>Member List</h1>
<div class="col-md-6">
<div class="filter">
<form action="/portal/mm" method="post">
<ul class="nav nav-tabs" id="filterTab" role="tablist">
<li class="nav-it<em">
<a class="nav-link active" id="group-tab" data-toggle="tab" href="#group" role="tab" aria-controls="group" aria-selected="true">Group Selection</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" id="searchfields-tab" data-toggle="tab" href="#searchfields" role="tab" aria-controls="searchfields" aria-selected="false">Search fields</a>
</li>
</ul>
<div class="tab-content" id="filterTabContent">
<div class="tab-pane fade show active" id="group" role="tabpanel" aria-labelledby="group-tab">
<div class="group_selection_list">
{{#each group_list}}
<span class="form-check group_selection_group">
<input type="checkbox" class="form-check-input" id="{{entity_id}}" name="selected_groups" value="{{entity_id}}" {{#if selected}}checked{{/if}}>
<label class="form-check-label" for="{{entity_id}}">{{group_name}}</label>
</span>
{{/each}}
</div>
</div>
<div class="tab-pane fade" id="searchfields" role="tabpanel" aria-labelledby="searchfields-tab">
<div class="form-row">
<div class="col form-group">
<select class="form-control">
<option>first name</option>
<option>last name</option>
<option>date of birth</option>
<option>sex</option>
<option>salutation</option>
<option>place of birth</option>
<option>academic titles</option>
<option>personnel number</option>
<option>ui language</option>
</select>
</div>
<div class="col form-group">
<select class="form-control">
<option>=</option>
<option>!=</option>
<option>LIKE</option>
<option>></option>
<option><</option>
<option>>=</option>
<option><=</option>
</select>
</div>
<div class="col form-group">
<input type="text" class="form-control">
</div>
</div>
</div>
</div>
<input type="submit" class="form-control btn btn-primary filter_select" style="width: fit-content;float: right;">
</form>
</div>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">personnel number</th>
<th scope="col">first name</th>
<th scope="col">last name</th>
<th scope="col">date of birth</th>
<th scope="col">actions</th>
</tr>
</thead>
<tbody>
{{#each member_list}}
<tr>
<td>{{member.personnel_number}}</td>
<td>{{member.firstname}}</td>
<td>{{member.lastname}}</td>
<td>{{member.date_of_birth}}</td>
<td>{{#if read}}
<a href="/portal/mm/profile?action=view&id={{member.entity_id}}"><svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#eye-fill"/>
</svg></a>{{/if}}{{#if write}}
<a href="/portal/mm/profile?action=edit&id={{member.entity_id}}"><svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#pencil-square"/>
</svg></a>{{/if}}{{#if delete}}
<button class="iconbutton delete_member_button" data-member-id="{{member.entity_id}}" data-member-firstname="{{member.firstname}}" data-member-lastname="{{member.lastname}}">
<svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#trash"/>
</svg>
</button>{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,43 @@
{{> header }}
<div class="jumbotron">
<div class="row">
<div class="col-lq">
<h1>Willkommen bei einsatz.online!</h1>
</div>
{{#if logo_path}}
<div class="col">
<img class="welcome_logo" src="{{logo_path}}">
</div>
{{/if}}
</div>
</div>
<div class="login row">
<div class="col">
<form method="post">
{{#if alert}}
{{> alert}}
{{/if}}
<h3>Anmelden</h3>
<div class="form-group">
<label for="login_email">E-Mail Adresse</label>
<input type="email" class="form-control" name="login_email" id="login_email" required>
</div>
<div class="form-group">
<label for="login_password">Passwort</label>
<input type="password" class="form-control" name="login_password" id="login_password" required>
</div>
<button type="submit" class="login_submit btn btn-primary">Anmelden</button>
</form>
</div>
<div class="col">
<form method="post" action="/password_reset">
<h3>Passwort vergessen?</h3>
<div class="form-group">
<label for="email">E-Mail Adresse</label>
<input type="email" class="form-control" name="email" id="email" required>
</div>
<button type="submit" class="login_submit btn btn-secondary">Passwort zurücksetzen</button>
</form>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,30 @@
{{> header }}
<div class="jumbotron">
<div class="row">
<div class="col-lq">
<h1>Willkommen bei einsatz.online!</h1>
</div>
{{#if logo_path}}
<div class="col">
<img class="welcome_logo" src="{{logo_path}}">
</div>
{{/if}}
</div>
</div>
<div class="row">
<div class="col-10">
<form method="post">
{{#if alert}}
{{> alert}}
{{/if}}
<h3>Neues Passwort festlegen</h3>
<p>Ihr Passwort muss mindestens 10 Zeichen lang sein und sollte Buchstaben, Zahlen und Sonderzeichen enthalten.</p>
<div class="form-group">
<label for="password">Passwort</label>
<input type="password" class="form-control" name="password" id="password" required>
</div>
<button type="submit" class="login_submit btn btn-primary">Passwort ändern</button>
</form>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,18 @@
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="searchbar-type"></span>
</div>
<input type="text" class="form-control" id="searchbar" data-active-entity="{{active_entity}}" data-entity-type="{{entity_type}}">
<span class="input-group-append">
<span class="btn btn-outline-secondary" type="button">
<svg width="16" height="16" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#search"/>
</svg>
</span>
</span>
</div>
<div class="search-result-overlay" style="display: none">
<ul class="list-group search-result-overlay-list">
</ul>
</div>

View File

@ -0,0 +1,76 @@
<!-- Sidebar -->
<nav id="sidebar">
<div class="sidebar-header">
<h3>ERMS</h3>
<hr>
<div class="user-info">
<p><a href="/portal/personal_profile?action=view"><button class="btn text-left btn-sm" ><svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#file-earmark-person"/>
</svg> {{sidebar.firstname}} {{sidebar.lastname}}</button></a></p>
</div><hr>
<a href="/portal/logout"><button class="btn btn-sm"><svg width="1.5em" height="1.5em" fill="currentColor">
<use xlink:href="/img/bootstrap-icons.svg#door-open"/>
</svg> Abmelden</button></a>
</div>
<ul class="components sidebar-navigation">
{{#if sidebar.summary.visible}}
<li {{#if sidebar.summary.active}}class="sidebar_entry_active"{{/if}}>
<a href="/portal">Übersicht</a>
</li>
{{/if}}
{{#if sidebar.member_management.visible}}
<li>
<a {{#if sidebar.member_management.active}}class="sidebar_entry_active"{{/if}} href="/portal/mm">Mitglieder</a>
{{#if sidebar.member_management.active}}
<ul>
<li><a href="/portal/mm">Mitgliederliste</a></li>
</ul>
<ul>
<li><a href="/portal/mm/profile">Profil</a></li>
</ul>
<ul>
<li><a href="/portal/mm/groups">Gruppen</a></li>
</ul>
<ul>
<li><a href="/portal/mm/trainings">Aus- und Fortbildungen</a></li>
</ul>
{{/if}}
</li>
{{/if}}
{{#if sidebar.resource_management.visible}}
<li>
<a {{#if sidebar.resource_management.active}}class="sidebar_entry_active"{{/if}} href="/portal/rm">Ressourcen</a>
{{#if sidebar.resource_management.active}}
<ul>
<li><a href="/portal/rm">List</a></li>
</ul>
<ul>
<li><a href="/portal/rm/vehicles">Vehicles</a></li>
</ul>
<ul>
<li><a href="/portal/rm/items">Items</a></li>
</ul>
{{/if}}
</li>
{{/if}}
{{#if sidebar.event_management.visible}}
<li>
<a {{#if sidebar.event_management.active}}class="sidebar_entry_active"{{/if}} href="/portal/em">Einsätze</a>
{{#if sidebar.event_management.active}}
<ul>
<li><a href="/portal/em">Calendar</a></li>
</ul>
{{/if}}
</li>
{{/if}}
</ul>
<ul class="components sidebar-actions">
{{#if sidebar.member_management.active}}
<li>
<a href="/portal/mm/add_member">Mitglied hinzufügen</a>
</li>
{{/if}}
</ul>
</nav>

235
sqlschema.sql Normal file
View File

@ -0,0 +1,235 @@
create table __diesel_schema_migrations
(
version varchar(50) not null
constraint __diesel_schema_migrations_pkey
primary key,
run_on timestamp default CURRENT_TIMESTAMP not null
);
create table users
(
id uuid default uuid_generate_v1() not null
constraint pk___users___id
primary key,
password text,
email text
);
create table communication_types
(
id uuid default uuid_generate_v1() not null
constraint pk___communication_types___id
primary key,
name text not null
);
create table addresses
(
id uuid default uuid_generate_v1() not null
constraint addresses_pk
primary key,
title text,
street text not null,
number text not null,
zipcode text not null,
city text not null,
geo_location point
);
create table entities
(
entity_id uuid default uuid_generate_v1() not null
constraint entities_pk
primary key
);
create table members
(
entity_id uuid default uuid_generate_v1() not null
constraint pk___members___id
primary key
constraint members_entities_entity_id_fk
references entities
on update cascade on delete cascade,
users_id uuid
constraint fk___members___users_id___users
references users,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
personnel_number integer,
ui_language text
);
create unique index members_personnel_number_uindex
on members (personnel_number);
create table addresses_entities
(
address_id uuid not null
constraint addresses_entities_addresses_id_fk
references addresses
on update cascade on delete cascade,
entitiy_id uuid not null
constraint addresses_entities_entities_entity_id_fk
references entities
on update cascade on delete cascade,
constraint addresses_entities_pk
primary key (address_id, entitiy_id)
);
create table buildings
(
entity_id uuid default uuid_generate_v1() not null
constraint buildings_pk
primary key
constraint buildings_entities_entity_id_fk
references entities
on update cascade on delete cascade,
name text not null,
description text
);
create table vehicle_categories
(
id uuid default uuid_generate_v1() not null
constraint vehicle_categories_pk
primary key,
name text not null,
description text
);
create table vehicles
(
entity_id uuid default uuid_generate_v1() not null
constraint vehicles_pk
primary key
constraint vehicles_entities_entity_id_fk
references entities
on update cascade on delete cascade
constraint vehicles_vehicle_categories_id_fk
references vehicle_categories
on update cascade on delete set null,
identifier text not null,
numberplate text,
description text,
vehicle_category uuid default uuid_generate_v1(),
next_inspection date,
is_operational boolean default true not null,
admissible_total_weight real,
required_license uuid
);
create table communication_targets
(
id uuid default uuid_generate_v1() not null
constraint pk___communication_targets___id
primary key
constraint communication_targets_entities_entity_id_fk
references entities
on update cascade on delete cascade,
entity text not null,
entity_id uuid not null,
com_type uuid not null
constraint fk___communication_target___type___communication_types
references communication_types,
value text not null,
description text,
visibility boolean default false not null
);
create table permissions
(
permission text not null
constraint permissions_pk
primary key,
description text
);
create table roles
(
id text not null
constraint roles_pk
primary key,
description text
);
create table roles_permissions
(
role_id text not null
constraint roles_permissions_roles_id_fk
references roles
on update cascade on delete cascade,
permission_id text not null
constraint roles_permissions_permissions_permission_fk
references permissions
on update cascade on delete cascade,
role_permission_id uuid default uuid_generate_v1() not null
constraint roles_permissions_pk_2
primary key
);
create unique index roles_permissions_role_permission_id_uindex
on roles_permissions (role_permission_id);
create table members_roles
(
member_id uuid not null
constraint members_roles_entities_entity_id_fk
references entities
on update cascade on delete cascade,
role_id text not null
constraint members_roles_roles_id_fk
references roles
on update cascade on delete cascade,
constraint members_roles_pk
primary key (member_id, role_id)
);
create table groups
(
entity_id uuid default uuid_generate_v1() not null
constraint groups_pk
primary key
constraint groups_entities_entity_id_fk
references entities
on update cascade on delete cascade,
group_name text not null,
group_description text
);
create unique index groups_group_name_uindex
on groups (group_name);
create table groups_entities
(
group_id uuid not null
constraint groups_entities_groups_group_id_fk
references groups
on update cascade on delete cascade,
entity_id uuid not null
constraint groups_entities_entities_entity_id_fk
references entities
on update cascade on delete cascade,
constraint groups_entities_pk
primary key (group_id, entity_id)
);
create table roles_permissions_context
(
role_permission_id uuid not null
constraint roles_permissions_contexts_roles_permissions_role_permission_id
references roles_permissions
on update cascade on delete cascade,
entity uuid not null
constraint roles_permissions_contexts_entities_entity_id_fk
references entities
on update cascade on delete cascade,
constraint roles_permissions_context_pk
primary key (role_permission_id, entity)
);

View File

@ -0,0 +1,54 @@
use crate::database::controller::connector::establish_connection;
use crate::database::model::addresses::Address;
use crate::diesel::RunQueryDsl;
use crate::helper::settings::Settings;
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl};
use rocket::State;
/// Reads all addresses for entity_id specified
/// Returns None if error occurred or there are no addresses
pub fn get_addresses_for_entity(
settings: &State<Settings>,
entity_id_to_get: uuid::Uuid,
) -> Option<Vec<Address>> {
use crate::schema::addresses::dsl::*;
use crate::schema::addresses_entities::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<Address>, diesel::result::Error> = addresses
.left_join(addresses_entities.on(id.eq(address_id)))
.filter(entitiy_id.eq(entity_id_to_get))
.select((id, title, street, number, zipcode, city, country))
.load(&connection);
match data {
Ok(data_addresses) => Some(data_addresses),
Err(e) => {
error!(
"Error reading addresses for {} from database: {}",
entity_id_to_get, e
);
None
}
}
}
pub fn get_addresses_for_entity_only_one_allowed(
settings: &State<Settings>,
entity_id_to_get: uuid::Uuid,
) -> Option<Address> {
match get_addresses_for_entity(settings, entity_id_to_get) {
Some(addresses) => {
if addresses.len() == 0 {
None
} else if addresses.len() != 1 {
error!("There is more then one address saved for entity {}, but called with get_addresses_for_entity_only_one_allowed()! Check for data inconsistency.", entity_id_to_get);
None
} else {
Some(addresses.first().unwrap().clone()) //Yes, we are allowed to unwrap because we checked that there is exact one element 3 lines before
}
}
None => None,
}
}

View File

@ -0,0 +1,33 @@
use crate::database::controller::connector::establish_connection;
use crate::helper::settings::Settings;
use crate::modules::api::members::member_communication::CommunicationTarget;
use crate::schema::communication_targets::dsl::{communication_targets, target_id};
use diesel::query_dsl::filter_dsl::FilterDsl;
use diesel::{ExpressionMethods, RunQueryDsl};
use rocket::State;
/// Updates communication target via API.
/// Will only change field if field is Some, will ignore field if it's None.
///
/// Parameters:
/// * settings: Settings as managed State
/// * com_target_id: uuid of communication target to change
/// * com_target: transmitted CommunicationTarget struct, (all fields optional)
pub fn update_communication_target(
settings: &State<Settings>,
com_target_id: uuid::Uuid,
com_target: CommunicationTarget,
) -> Result<usize, diesel::result::Error> {
let connection = establish_connection(settings);
match diesel::update(communication_targets.filter(target_id.eq(com_target_id)))
.set(&com_target)
.execute(&connection)
{
Ok(affected_rows) => Ok(affected_rows),
Err(e) => {
warn!("Couldn't update communication target: {}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,142 @@
use crate::database::controller::connector::establish_connection;
use crate::database::controller::groups::get_groups_for_member;
use crate::database::model::api_members::RawMemberSearchResult;
use crate::diesel::query_dsl::filter_dsl::OrFilterDsl;
use crate::diesel::query_dsl::limit_dsl::LimitDsl;
use crate::diesel::query_dsl::select_dsl::SelectDsl;
use crate::diesel::RunQueryDsl;
use crate::helper::check_access::check_access;
use crate::helper::settings::Settings;
use crate::modules::api::members::get_member::MemberSearchResult;
use diesel::query_dsl::filter_dsl::FilterDsl;
use diesel::{ExpressionMethods, TextExpressionMethods};
use rocket::State;
pub fn get_raw_member_search_result(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
) -> Result<RawMemberSearchResult, diesel::result::Error> {
use crate::schema::members::dsl::*;
let connection = establish_connection(&settings);
let short_member: Result<RawMemberSearchResult, diesel::result::Error> = members
.filter(entity_id.eq(member_entity_id))
.select((entity_id, firstname, lastname))
.first(&connection);
match short_member {
Ok(raw_member) => Ok(raw_member),
Err(e) => {
warn!("Couldn't load RawMemberSearchResult: {}", e);
return Err(e);
}
}
}
pub fn get_member_search_result(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
caller_entity_id: uuid::Uuid,
) -> Result<MemberSearchResult, diesel::result::Error> {
let member = get_raw_member_search_result(settings, member_entity_id)?;
let groups = get_groups_for_member(settings, member_entity_id);
let readable = check_access(
settings,
member_entity_id,
groups,
caller_entity_id,
"modules.member_management.profile.view".to_string(),
);
Ok(MemberSearchResult {
entity_id: member.entity_id,
firstname: member.firstname,
lastname: member.lastname,
readable,
})
}
pub fn get_raw_member_search_result_by_name(
settings: &State<Settings>,
member_name: String,
) -> Result<Vec<RawMemberSearchResult>, diesel::result::Error> {
use crate::schema::members::dsl::*;
let connection = establish_connection(&settings);
let splitted_name: Vec<&str> = member_name.split_whitespace().collect();
let short_members: Result<Vec<RawMemberSearchResult>, diesel::result::Error>;
if splitted_name.len() == 0 {
return Ok(vec![]);
} else if splitted_name.len() == 1 {
short_members = members
.filter(firstname.like(format!("{}%", member_name)))
.or_filter(lastname.like(format!("{}%", member_name)))
.select((entity_id, firstname, lastname))
.limit(5)
.load(&connection);
} else {
short_members = members
.filter(firstname.like(format!("{}%", splitted_name[0])))
.filter(lastname.like(format!("{}%", splitted_name[1])))
.select((entity_id, firstname, lastname))
.limit(5)
.load(&connection);
}
match short_members {
Ok(raw_member) => Ok(raw_member),
Err(e) => {
warn!("Couldn't load RawMemberSearchResult: {}", e);
return Err(e);
}
}
}
pub fn get_member_search_result_by_name(
settings: &State<Settings>,
name: String,
caller_entity_id: uuid::Uuid,
) -> Result<Vec<MemberSearchResult>, diesel::result::Error> {
let raw_members = get_raw_member_search_result_by_name(settings, name)?;
let mut members: Vec<MemberSearchResult> = vec![];
for member in raw_members {
let groups = get_groups_for_member(settings, member.entity_id);
let readable = check_access(
settings,
member.entity_id,
groups,
caller_entity_id,
"modules.member_management.profile.view".to_string(),
);
members.push(MemberSearchResult {
entity_id: member.entity_id,
firstname: member.firstname,
lastname: member.lastname,
readable,
});
}
Ok(members)
}
pub fn delete_entity(
settings: &State<Settings>,
entity_id_to_delete: uuid::Uuid,
) -> Result<usize, diesel::result::Error> {
use crate::schema::entities::dsl::*;
let connection = establish_connection(&settings);
match diesel::delete(entities.filter(entity_id.eq(entity_id_to_delete))).execute(&connection) {
Ok(size) => Ok(size),
Err(e) => {
warn!("Couldn't delete entity: {}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,9 @@
use crate::helper::settings::Settings;
use diesel::{Connection, PgConnection};
use rocket::State;
pub fn establish_connection(settings: &State<Settings>) -> PgConnection {
//TODO: return Error
let db_url = settings.database.connection_string.clone();
PgConnection::establish(&db_url).expect(&format!("Error connecting to database."))
}

View File

@ -0,0 +1,77 @@
use rocket::State;
use crate::helper::settings::Settings;
use crate::modules::member_management::model::member::CreateMemberForm;
use crate::database::controller::connector::establish_connection;
use crate::schema::entities::dsl::entities;
use diesel::{RunQueryDsl, ExpressionMethods};
use crate::schema::entities::dsl::entity_id;
use crate::schema::members::dsl::members;
use crate::database::model::members::CreateMember;
use crate::helper::forms::into_option;
use chrono::NaiveDate;
use crate::database::controller::members_groups::add_member_to_group;
use diesel::result::Error;
pub fn create_member(settings: &State<Settings>, form: CreateMemberForm) -> Result<uuid::Uuid, diesel::result::Error>{
let connection = establish_connection(settings);
let entity_id_result: Result<uuid::Uuid, diesel::result::Error> =diesel::insert_into(entities).default_values().returning(crate::schema::entities::dsl::entity_id).get_result(&connection);
let id = match entity_id_result{
Ok(id) => id,
Err(e) => {
error!("Konnte keine entity anlegen: {}", e);
return Err(e)
}
};
use crate::schema::members::dsl::*;
let personnel_number2 = if form.personnel_number.is_empty(){
None
}else{
match form.personnel_number.parse::<i32>(){
Ok(num) => Some(num),
Err(e) => {
warn!("Couldn't parse personnel number: {}", e);
None
}
}
};
let entrance_date2 = if form.entrance_date.is_empty(){
None
}else {
match NaiveDate::parse_from_str(&form.entrance_date, "%Y-%m-%d") {
Ok(date) => Some(date),
Err(e) => {
warn!("Couldn't parse date: {}", e);
None
}
}
};
let member = CreateMember{
entity_id: id,
firstname: form.firstname,
lastname: form.lastname,
sex: form.sex.parse::<i16>().unwrap(),
salutation: form.salutation,
academic_titles: into_option(form.academic_titles),
personnel_number: personnel_number2,
entrance_date: entrance_date2
};
match add_member_to_group(settings, id, uuid::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()){
Ok(_) => {},
Err(_) => {}
}
match diesel::insert_into(members).values(member).execute(&connection){
Ok(_) => Ok(id),
Err(e) => {
error!("Couldn't create member: {}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,267 @@
use crate::database::controller::connector::establish_connection;
use crate::database::controller::members::check_member_has_access;
use crate::database::model::groups::{GroupMemberCount, RawGroup};
use crate::helper::check_access::check_access;
use crate::helper::settings::Settings;
use crate::modules::member_management::model::groups::{Group, GroupData, GroupUpdateData};
use crate::schema::groups_entities::dsl::group_id;
use crate::schema::roles_permissions_context::columns::entity;
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl};
use rocket::State;
use std::collections::HashMap;
use diesel::associations::HasTable;
/// Returns list of all groups (database::model::groups::RawGroup)
///
/// #Arguments
/// * 'settings' - Settings, as managed State
///
/// #Returns
/// * Result<Vec<RawGroup>, diesel::result::Error> - RawGroup items inside Vector or diesel database error.
pub fn get_raw_groups(settings: &State<Settings>) -> Result<Vec<RawGroup>, diesel::result::Error> {
use crate::schema::groups::dsl::*;
let connection = establish_connection(settings);
let group_vec: Result<Vec<RawGroup>, diesel::result::Error> = groups.order(group_name.asc()).load(&connection);
match group_vec {
Ok(group_vec) => Ok(group_vec),
Err(e) => {
error!("Error occured while loading groups: {}", e);
Err(e)
}
}
}
/// Returns member count for a group
///
/// #Arguments
/// * 'settings' - Settings, as managed State
/// * 'group_id_to_check'
///
/// #Returns
/// * Result<i64, diesel::result::Error> - member count for group or diesel database error.
pub fn get_members_in_groups_count(
settings: &State<Settings>,
group_id_to_check: uuid::Uuid,
) -> Result<i64, diesel::result::Error> {
use crate::schema::groups_entities::dsl::*;
let connection = establish_connection(settings);
let count: Result<i64, diesel::result::Error> = groups_entities
.filter(group_id.eq(group_id_to_check))
.count()
.get_result(&connection);
match count {
Ok(count) => Ok(count),
Err(e) => {
error!("Couldn't count members in group: {}", e);
Err(e)
}
}
}
/// Returns all groups with member count caller is allowed to see
///
/// #Arguments
/// * 'settings' - Settings, as managed State
/// * 'caller_uuid'
///
/// #Returns
/// * Result<Vec<GroupMemberCount, diesel::result::Error> - Vector of all groups member has access to or diesel database error.
pub fn get_group_member_count(
settings: &State<Settings>,
caller_id: uuid::Uuid,
) -> Result<Vec<GroupMemberCount>, diesel::result::Error> {
let groups = get_raw_groups(settings)?;
let mut groups_member_count: Vec<GroupMemberCount> = vec![];
for group in groups {
groups_member_count.push(GroupMemberCount {
group_id: group.group_id,
name: group.name,
description: group.description,
members_count: get_members_in_groups_count(settings, group.group_id)?,
permission_edit_core: check_member_has_access(
settings,
caller_id,
group.group_id,
crate::permissions::modules::member_management::groups::core::EDIT,
),
permission_delete: check_member_has_access(
settings,
caller_id,
group.group_id,
crate::permissions::modules::member_management::groups::DELETE,
),
permission_view_members: check_member_has_access(
settings,
caller_id,
group.group_id,
crate::permissions::modules::member_management::groups::members::VIEW,
),
permission_edit_members: check_member_has_access(
settings,
caller_id,
group.group_id,
crate::permissions::modules::member_management::groups::members::EDIT,
),
})
}
Ok(groups_member_count)
}
/// Returns HashMap of all groups (modules::member_management::model::group::Group)
/// Calls get_raw_groups and adding selected: false for every group except default group
/// Key is group_id
///
/// #Arguments
/// * 'settings' - Settings, as managed State
///
/// #Returns
/// * HashMap<Group> - Group items inside Vector.
pub fn get_groups(
settings: &State<Settings>,
) -> Result<HashMap<uuid::Uuid, Group>, diesel::result::Error> {
let raw_groups: Vec<RawGroup> = get_raw_groups(settings)?;
let mut groups: HashMap<uuid::Uuid, Group> = HashMap::new();
for raw_group in raw_groups {
groups.insert(
raw_group.group_id,
Group {
entity_id: raw_group.group_id,
group_name: raw_group.name,
selected: false,
group_description: raw_group.description,
},
);
}
Ok(groups)
}
pub fn get_groups_for_member(settings: &State<Settings>, member_id2: uuid::Uuid) -> Vec<Group> {
//TODO: return errors
use crate::schema::groups::dsl::*;
use crate::schema::groups_entities::dsl::entity_id as groups_entities_entity_id;
use crate::schema::groups_entities::dsl::group_id as groups_entities_group_id;
use crate::schema::groups_entities::dsl::groups_entities;
let connection = establish_connection(settings);
let data: Result<Vec<RawGroup>, diesel::result::Error> = groups
.left_join(groups_entities.on(group_id.eq(crate::schema::groups::dsl::entity_id)))
.filter(groups_entities_entity_id.eq(member_id2))
.select((entity_id, group_name, group_description))
.load(&connection);
match data {
Ok(data) => {
let mut groups_result: Vec<Group> = Vec::new();
for raw_group in data {
groups_result.push(Group {
entity_id: raw_group.group_id,
group_name: raw_group.name,
selected: false,
group_description: raw_group.description,
});
}
groups_result
}
Err(e) => {
warn!("Couldn't get groups for member: {}", e);
vec![]
}
}
}
pub fn insert_group(
settings: &State<Settings>,
group: GroupData,
) -> Result<RawGroup, diesel::result::Error> {
use crate::schema::entities::dsl::*;
use crate::schema::groups::dsl::*;
let connection = establish_connection(settings);
let group_entity_id: uuid::Uuid = match diesel::insert_into(entities)
.default_values()
.returning(crate::schema::entities::dsl::entity_id)
.get_result(&connection)
{
Ok(id) => id,
Err(e) => {
error!("Couldn't create entity_id for new group: {}", e);
return Err(e);
}
};
let inserted: Result<RawGroup, diesel::result::Error> = diesel::insert_into(groups)
.values((
crate::schema::groups::dsl::entity_id.eq(group_entity_id),
group_name.eq(group.group_name.unwrap()),
group_description.eq(group.group_description),
))
.get_result(&connection);
match inserted {
Ok(inserted) => Ok(inserted),
Err(e) => {
error!("Couldn't insert new group: {}", e);
Err(e)
}
}
}
pub fn delete_group(
settings: &State<Settings>,
group_id2: uuid::Uuid,
) -> Result<(), diesel::result::Error> {
use crate::schema::groups::dsl::*;
let connection = establish_connection(settings);
match diesel::delete(groups.filter(entity_id.eq(group_id2))).execute(&connection) {
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't delete group: {}", e);
Err(e)
}
}
}
pub fn get_group(
settings: &State<Settings>,
group_id2: uuid::Uuid,
) -> Result<RawGroup, diesel::result::Error> {
use crate::schema::groups::dsl::*;
let connection = establish_connection(settings);
let group: Result<RawGroup, diesel::result::Error> =
groups.filter(entity_id.eq(group_id2)).first(&connection);
match group {
Ok(group) => Ok(group),
Err(e) => {
warn!("Couldn't load group: {}", e);
Err(e)
}
}
}
pub fn update_group_core_data(settings: &State<Settings>, group_id2: uuid::Uuid, changeset: GroupUpdateData) -> Result<RawGroup, diesel::result::Error>{
use crate::schema::groups::dsl::*;
let connection = establish_connection(settings);
let updated: Result<RawGroup, diesel::result::Error> = diesel::update(groups).filter(entity_id.eq(group_id2)).set(&changeset).get_result(&connection);
match updated{
Ok(updated) => Ok(updated),
Err(e) => {
error!("Couldn't update group core data: {}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,225 @@
use crate::database::controller::connector::establish_connection;
use crate::database::controller::roles::{
add_permission, add_permission_context, get_role_permission_id, get_roles,
};
use crate::helper::check_access::check_access;
use crate::helper::settings::Settings;
use crate::modules::api::groups::create::GroupRolePermission;
use crate::schema::roles_permissions_context::dsl::roles_permissions_context;
use diesel::pg::types::sql_types::Uuid;
use diesel::result::Error;
use diesel::sql_types::Text;
use diesel::{sql_query, RunQueryDsl};
use rocket::State;
/// Execute add_permission_context for given group_id and permission
/// Warning: Will return Ok, even if permission context wasn't added due to missing permission for role
pub fn add_group_role_permission(
settings: &State<Settings>,
group_id: uuid::Uuid,
role_id: String,
permission: &str,
) -> Result<(), diesel::result::Error> {
let role_permission_id = get_role_permission_id(settings, role_id, permission);
match role_permission_id {
Some(id) => match add_permission_context(settings, id, group_id) {
Ok(_) => return Ok(()),
Err(e) => return Err(e),
},
None => return Ok(()),
}
}
pub fn add_group_role_permissions(
settings: &State<Settings>,
group_id: uuid::Uuid,
permissions: Vec<GroupRolePermission>,
) -> Result<(), diesel::result::Error> {
for permission in permissions {
if permission.role_id != "admin".to_string() {
if permission.permission_groups_core_edit {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::core::EDIT,
)?;
}
if permission.permission_groups_delete {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::DELETE,
)?;
}
if permission.permission_groups_members_view {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::members::VIEW,
)?;
}
if permission.permission_groups_members_edit {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::members::EDIT,
)?;
}
if permission.permission_groups_permissions_view {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::permissions::VIEW,
)?;
}
if permission.permission_groups_permissions_edit {
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::permissions::EDIT,
)?;
}
} else {
//Manual overwrite for admin. Gives admin all permissions
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::core::EDIT,
)?;
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::DELETE,
)?;
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::members::VIEW,
)?;
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::members::EDIT,
)?;
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::permissions::EDIT,
)?;
add_group_role_permission(
settings,
group_id,
permission.role_id.clone(),
crate::permissions::modules::member_management::groups::permissions::VIEW,
)?;
}
}
Ok(())
}
pub fn check_role_has_permission_on_entity(
settings: &State<Settings>,
role_id: &str,
permission: &str,
entity_id: uuid::Uuid,
) -> Result<bool, diesel::result::Error> {
use crate::database::controller::members::TempQuery;
let connection = establish_connection(settings);
let permission_count: Result<TempQuery, diesel::result::Error> = sql_query(
"SELECT COUNT(role_permission_id) FROM roles_permissions_context WHERE entity = $1 AND role_permission_id IN (SELECT role_permission_id FROM roles_permissions WHERE role_id = $2 AND permission_id = $3)",
)
.bind::<Uuid, _>(entity_id)
.bind::<Text, _>(role_id)
.bind::<Text, _>(permission)
.get_result(&connection);
match permission_count {
Ok(count) => {
if count.count > 0 {
Ok(true)
} else {
Ok(false)
}
}
Err(e) => {
error!("Error while checking members permissions (context): {}", e);
Err(e)
}
}
}
pub fn get_group_permission_for_role(
settings: &State<Settings>,
group_id: uuid::Uuid,
role_id: String,
) -> Result<GroupRolePermission, diesel::result::Error> {
Ok(GroupRolePermission {
role_id: role_id.clone(),
permission_groups_core_edit: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::core::EDIT,
group_id,
)?,
permission_groups_delete: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::DELETE,
group_id,
)?,
permission_groups_members_view: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::members::VIEW,
group_id,
)?,
permission_groups_members_edit: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::members::EDIT,
group_id,
)?,
permission_groups_permissions_view: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::permissions::VIEW,
group_id,
)?,
permission_groups_permissions_edit: check_role_has_permission_on_entity(
settings,
&role_id,
crate::permissions::modules::member_management::groups::permissions::EDIT,
group_id,
)?,
})
}
pub fn get_group_role_permissions(
settings: &State<Settings>,
group_id: uuid::Uuid,
) -> Result<Vec<GroupRolePermission>, diesel::result::Error> {
let mut grp: Vec<GroupRolePermission> = vec![];
let roles = get_roles(settings)?;
for role in roles {
match get_group_permission_for_role(settings, group_id, role.id) {
Ok(sgrp) => grp.push(sgrp),
Err(e) => return Err(e),
}
}
Ok(grp)
}

View File

@ -0,0 +1,47 @@
use rocket::State;
use crate::helper::settings::Settings;
use crate::database::controller::connector::establish_connection;
use diesel::{sql_query, RunQueryDsl, ExpressionMethods};
use diesel::sql_types::{Integer, Text};
use crate::database::model::login_protection::LoginAttemptsResult;
use chrono::{NaiveDateTime, Duration};
use std::ops::Add;
use crate::schema::login_attempts::dsl::login_attempts;
/// Checks if maximum login attempts exceeded. Locks account if exceeded
/// Returns:
/// * Ok(true) if login attempts exceeded and account got locked
/// * Ok(false) if login attempts not exceeded
/// * Err(diesel::result::Error) if database error occured
pub fn login_attempts_exceeded(settings: &State<Settings>, email: String) -> Result<bool, diesel::result::Error>{
let connection = establish_connection(settings);
let result : Result<LoginAttemptsResult, diesel::result::Error> = sql_query("SELECT COUNT(*) AS count, MAX(timestamp) AS last_timestamp FROM login_attempts WHERE email=$1 AND timestamp > (now() - interval '1800 seconds')").bind::<Text, _>(email.clone()).get_result(&connection);
let result = match result{
Ok(res) => res,
Err(e) => {
error!("Couldn't check for login attempts: {}", e);
return Err(e)
}
};
if result.count > settings.application.max_login_attempts as i64 {
Ok(true)
}else{
add_login_attempt(settings, email)?;
Ok(false)
}
}
fn add_login_attempt(settings: &State<Settings>, email2: String) -> Result<(), diesel::result::Error>{
use crate::schema::login_attempts::dsl::{login_attempts, email};
let connection = establish_connection(settings);
match diesel::insert_into(login_attempts).values(email.eq(email2)).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't write login attempt into DB! {}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,119 @@
use crate::database::controller::connector::establish_connection;
use crate::database::model::member_communication::CommunicationTarget;
use crate::helper::settings::Settings;
use crate::modules::api::member_management::model::member_communication::CommunicationType;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
use rocket::State;
pub fn get_member_communication(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
) -> Vec<CommunicationTarget> {
use crate::schema::communication_targets::dsl::*;
use crate::schema::communication_types::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<CommunicationTarget>, diesel::result::Error> = communication_targets
.inner_join(communication_types)
.filter(entity_id.eq(member_entity_id))
.select((target_id, entity_id, type_name, type_id, value, description))
.load(&connection);
match data {
Ok(data) => data,
Err(e) => {
error!(
"Couldn't get communication targets for {}: {}",
member_entity_id, e
);
vec![]
}
}
}
pub fn get_communication_target(
settings: &State<Settings>,
target_id2: uuid::Uuid,
) -> Result<CommunicationTarget, diesel::result::Error> {
use crate::schema::communication_targets::dsl::*;
use crate::schema::communication_types::dsl::*;
let connection = establish_connection(settings);
let data: Result<CommunicationTarget, diesel::result::Error> = communication_targets
.inner_join(communication_types)
.filter(target_id.eq(target_id2))
.select((target_id, entity_id, type_name, type_id, value, description)).get_result(&connection);
match data {
Ok(data) => Ok(data),
Err(e) => {
error!(
"Couldn't get communication target: {}",
e
);
Err(e)
}
}
}
pub fn get_member_communication_types(settings: &State<Settings>) -> Vec<CommunicationType> {
use crate::schema::communication_types::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<CommunicationType>, diesel::result::Error> = communication_types
.select((type_id, type_name))
.load(&connection);
match data {
Ok(data) => data,
Err(e) => {
error!("Couldn't get communication types: {}", e);
vec![]
}
}
}
pub fn add_communication_target(
settings: &State<Settings>,
target: CommunicationTarget,
) -> Result<uuid::Uuid, diesel::result::Error> {
use crate::schema::communication_targets::dsl::*;
let connection = establish_connection(settings);
let result = diesel::insert_into(communication_targets)
.values((
entity_id.eq(target.entity_id),
com_type.eq(target.type_id),
value.eq(target.value),
description.eq(target.description),
))
.returning(target_id)
.get_result(&connection);
result
}
pub fn remove_communication_target(
settings: &State<Settings>,
target_id_to_delete: uuid::Uuid,
) -> Result<usize, diesel::result::Error> {
use crate::schema::communication_targets::dsl::*;
let connection = establish_connection(settings);
let result = diesel::delete(communication_targets.filter(target_id.eq(target_id_to_delete)))
.execute(&connection);
match result {
Ok(result) => Ok(result),
Err(e) => {
error!(
"Couldn't delete communication target {}: {}",
target_id_to_delete, e
);
Err(e)
}
}
}

View File

@ -0,0 +1,60 @@
use crate::database::controller::connector::establish_connection;
use crate::database::model::addresses::Address;
use crate::helper::settings::Settings;
use crate::schema::addresses::columns::id;
use crate::schema::addresses::dsl::addresses;
use crate::schema::addresses_entities::columns::{address_id, entitiy_id};
use crate::schema::addresses_entities::dsl::addresses_entities;
use diesel::{ExpressionMethods, RunQueryDsl};
use rocket::State;
pub fn insert_address(
settings: &State<Settings>,
new_address: Address,
belongs_to: uuid::Uuid,
) -> bool {
let connection = establish_connection(settings);
let address = diesel::insert_into(addresses)
.default_values()
.get_result(&connection);
let address: Address = match address {
Ok(address) => address,
Err(e) => {
warn!("Couldn't receive inserted address: {}", e);
return false;
}
};
let mut new_address = new_address;
new_address.id = address.id;
let update = diesel::update(addresses)
.filter(id.eq(address.id))
.set(&new_address)
.get_result(&connection);
let address: Address = match update {
Ok(address) => address,
Err(e) => {
warn!("Couldn't receive updated address: {}", e);
return false;
}
};
let result = diesel::insert_into(addresses_entities)
.values((address_id.eq(address.id), entitiy_id.eq(belongs_to.clone())))
.execute(&connection);
match result {
Ok(_) => true,
Err(e) => {
warn!(
"Couldn't insert new address: {} for entity {}",
e, belongs_to
);
false
}
}
}

View File

@ -0,0 +1,119 @@
use crate::database::controller::connector::establish_connection;
use crate::database::model::member_licenses::RawMemberLicense;
use crate::helper::settings::Settings;
use crate::modules::member_management::model::licenses::MemberLicense;
use diesel::pg::types::sql_types::Uuid;
use diesel::{sql_query, ExpressionMethods, RunQueryDsl};
use rocket::State;
pub fn get_member_licenses(
settings: &State<Settings>,
entity_id: uuid::Uuid,
) -> Vec<MemberLicense> {
let connection = establish_connection(settings);
let data: Result<Vec<RawMemberLicense>, diesel::result::Error> = sql_query(
"SELECT * FROM license_categories as lc
LEFT JOIN licenses_members lm on lc.name = lm.license_name AND lm.member_id=$1 ORDER BY lc.name ASC;",
)
.bind::<Uuid, _>(entity_id)
.load(&connection);
match data {
Ok(data) => {
let mut output: Vec<MemberLicense> = Vec::new();
for license in data {
output.push(MemberLicense::from(license.clone()));
trace!("LICENSE: {}", license.name);
}
output
}
Err(e) => {
error!("Couldn't get licenses for member {} : {}", entity_id, e);
vec![]
}
}
}
pub fn add_member_license(
settings: &State<Settings>,
member_id2: uuid::Uuid,
license_name2: String,
) -> Result<usize, diesel::result::Error> {
use crate::schema::licenses_members::dsl::*;
let connection = establish_connection(settings);
let result = diesel::insert_into(licenses_members)
.values((
member_id.eq(member_id2),
license_name.eq(license_name2.clone()),
))
.execute(&connection);
match result {
Ok(num) => Ok(num),
Err(e) => {
error!(
"Couldn't add license {} for member {} : {}",
license_name2, member_id2, e
);
Err(e)
}
}
}
pub fn add_member_driving_permission(
settings: &State<Settings>,
member_id2: uuid::Uuid,
license_name2: String,
) -> Result<usize, diesel::result::Error> {
use crate::schema::licenses_members::dsl::*;
let connection = establish_connection(settings);
let result = diesel::update(licenses_members)
.filter(member_id.eq(member_id2))
.filter(license_name.eq(license_name2.clone()))
.set(drive_permission.eq(true))
.execute(&connection);
match result {
Ok(num) => Ok(num),
Err(e) => {
error!(
"Couldn't add drive_permission to license {} for member {} : {}",
license_name2, member_id2, e
);
Err(e)
}
}
}
pub fn remove_member_license(
settings: &State<Settings>,
member_id2: uuid::Uuid,
license_name2: String,
) -> Result<usize, diesel::result::Error> {
use crate::schema::licenses_members::dsl::*;
let connection = establish_connection(settings);
let result = diesel::delete(licenses_members)
.filter(member_id.eq(member_id2))
.filter(license_name.eq(license_name2.clone()))
.execute(&connection);
match result {
Ok(num) => Ok(num),
Err(e) => {
error!(
"Couldn't remove license {} for member {} : {}",
license_name2, member_id2, e
);
Err(e)
}
}
}

View File

@ -0,0 +1,158 @@
use crate::database::controller::connector::establish_connection;
use crate::database::model::member_qualifications::RawQualificationCategory;
use crate::helper::settings::Settings;
use crate::modules::member_management::model::qualifications::{
Qualification, QualificationCategory,
};
use crate::schema::qualification_categories::dsl::qualification_categories;
use crate::schema::qualifications_members::columns::{member_id, qualification_id};
use crate::schema::qualifications_members::dsl::qualifications_members;
use diesel::result::Error;
use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl, RunQueryDsl};
use rocket::State;
pub fn get_raw_qualification_categories(
settings: &State<Settings>,
) -> Vec<RawQualificationCategory> {
use crate::schema::qualification_categories::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<RawQualificationCategory>, diesel::result::Error> =
qualification_categories.load(&connection);
match data {
Ok(data) => data,
Err(e) => {
error!("Couldn't read Qualification categories: {}", e);
vec![]
}
}
}
pub fn get_qualifcation_categories(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
) -> Vec<QualificationCategory> {
let raw_categories = get_raw_qualification_categories(settings);
let mut categories: Vec<QualificationCategory> = vec![];
for category in raw_categories {
let qualifications =
get_qualifications_for_member_for_category(settings, member_entity_id, category.id);
categories.push(QualificationCategory {
id: category.id,
name: category.name,
description: category.description,
qualifications,
visible: true,
})
}
for mut category in categories.iter_mut() {
if category.qualifications.is_empty() {
category.visible = false;
}
}
categories
}
pub fn get_qualifications_for_member_for_category(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
category_id: uuid::Uuid,
) -> Vec<Qualification> {
use crate::schema::qualifications::dsl::*;
use crate::schema::qualifications_members::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<Qualification>, diesel::result::Error> = qualifications
.left_join(qualifications_members.on(qualification_id.eq(id)))
.filter(member_id.eq(member_entity_id))
.filter(category.eq(category_id))
.select((id, name, description))
.load(&connection);
match data {
Ok(data) => data,
Err(e) => {
error!(
"Couldn't read Qualifications for category {} for member {}: {}",
category_id, member_entity_id, e
);
vec![]
}
}
}
pub fn remove_qualification_for_member(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
qualification_id2: uuid::Uuid,
) -> Result<usize, Error> {
let connection = establish_connection(settings);
let result = diesel::delete(
qualifications_members
.filter(member_id.eq(member_entity_id))
.filter(qualification_id.eq(qualification_id2)),
)
.execute(&connection);
match result {
Ok(result) => Ok(result),
Err(e) => {
error!("Couldn't delete Qualifications {}", e);
Err(e)
}
}
}
pub fn add_qualification_for_member(
settings: &State<Settings>,
member_entity_id: uuid::Uuid,
qualification_id2: uuid::Uuid,
) -> Result<usize, Error> {
let connection = establish_connection(settings);
let result = diesel::insert_into(qualifications_members)
.values((
member_id.eq(member_entity_id),
qualification_id.eq(qualification_id2),
))
.execute(&connection);
match result {
Ok(result) => Ok(result),
Err(e) => {
error!("Couldn't insert Qualification {}", e);
Err(e)
}
}
}
pub fn get_qualifications_for_category(
settings: &State<Settings>,
category_id2: uuid::Uuid,
) -> Vec<Qualification> {
use crate::schema::qualifications::dsl::*;
let connection = establish_connection(settings);
let data: Result<Vec<Qualification>, diesel::result::Error> = qualifications
.left_join(qualification_categories.on(category.eq(id)))
.filter(category.eq(category_id2))
.select((id, name, description))
.load(&connection);
match data {
Ok(data) => data,
Err(e) => {
error!(
"Couldn't read Qualifications for category {} : {}",
category_id2, e
);
vec![]
}
}
}

View File

@ -0,0 +1,286 @@
use crate::database::controller::connector::establish_connection;
use crate::database::controller::member_insert::insert_address;
use crate::database::model::addresses::Address;
use crate::database::model::members::RawMember;
use crate::helper::forms::into_option;
use crate::helper::settings::Settings;
use crate::modules::member_management::model::member::{Member, MemberProfileForm};
use crate::schema::addresses::dsl::{addresses, id};
use crate::schema::members;
use chrono::NaiveDate;
use diesel::{ExpressionMethods, RunQueryDsl};
use rocket::State;
use std::str::FromStr;
use uuid::Uuid;
use iban::{Iban, ParseIbanError};
pub fn update_member(
settings: &State<Settings>,
member_form: MemberProfileForm,
old_member_data: Member,
old_address: Option<Address>,
personal_profile_update: bool,
) -> bool {
use crate::schema::members::columns::entity_id;
let connection = establish_connection(settings);
let member_id = old_member_data.entity_id.clone();
let mut success = false;
let new_address =
match parse_profile_member_form_address(member_form.clone(), old_address.clone()) {
Some(address) => address,
None => return false,
};
match old_address {
Some(old_address) => {
let address_id_old = old_address.id.clone();
let affected_rows_address = diesel::update(addresses)
.filter(id.eq(address_id_old))
.set(&new_address)
.execute(&connection);
match affected_rows_address {
Ok(num) => {
if num != 1 {
error!(
"Updated address {}, but query affected {} rows!",
address_id_old, num
);
success = false;
} else {
success = true;
}
}
Err(e) => {
error!(
"Tried to update address {}, but query failed: {}",
address_id_old, e
);
success = false
}
}
}
None => {
let result = insert_address(settings, new_address, member_id);
if !result {
success = false;
}
}
}
let new_member = match parse_profile_member_form_member(member_form.clone(), old_member_data, personal_profile_update) {
Some(member) => member,
None => return false,
};
let affected_rows_member = diesel::update(members::table)
.filter(entity_id.eq(member_id))
.set(&new_member)
.execute(&connection);
match affected_rows_member {
Ok(num) => {
if num != 1 {
error!(
"Updated member {}, but query affected {} rows!",
member_id, num
);
success = false;
} else {
success = true;
}
}
Err(e) => {
error!(
"Tried to update member {}, but query failed: {}",
member_id, e
);
success = false
}
}
success
}
fn parse_profile_member_form_address(
member_form: MemberProfileForm,
old_address: Option<Address>,
) -> Option<Address> {
let address_id: uuid::Uuid = if old_address.is_some() {
match Uuid::from_str(&member_form.address_id) {
Ok(uuid) => {
if uuid != old_address.clone().unwrap().id {
warn!(
"Submitted address_id doesn't match with old entity_id! {} vs {}",
uuid,
old_address.unwrap().id
);
return None;
} else {
uuid
}
}
Err(e) => {
warn!(
"Couldn't parse address_id submitted via MemberProfileForm: {} {}",
member_form.address_id, e
);
return None;
}
}
} else {
Default::default()
};
let title = if old_address.is_some() {
old_address.unwrap().title
} else {
None
};
Some(Address {
id: address_id,
title,
street: Some(member_form.address_street),
number: Some(member_form.address_number),
zipcode: Some(member_form.address_zipcode),
city: Some(member_form.address_city),
country: Some(member_form.address_country),
})
}
fn parse_profile_member_form_member(
member_form: MemberProfileForm,
old_member_data: Member,
personnel_profile_change: bool,
) -> Option<RawMember> {
let entity_id = match Uuid::from_str(&member_form.entity_id) {
Ok(uuid) => {
if uuid != old_member_data.entity_id {
warn!(
"Submitted entity_id doesn't match with old entity_id! {} vs {}",
uuid, old_member_data.entity_id
);
return None;
} else {
uuid
}
}
Err(e) => {
warn!(
"Couldn't parse entity_id submitted via MemberProfileForm: {}",
e
);
return None;
}
};
let date_of_birth = if member_form.date_of_birth.is_empty() {
None
} else {
match NaiveDate::parse_from_str(&member_form.date_of_birth, "%Y-%m-%d") {
Ok(date) => Some(date),
Err(e) => {
warn!(
"Couldn't parse date_of_birth submitted via MemberProfileForm: {}",
e
);
old_member_data.date_of_birth
}
}
};
let sex = if member_form.sex.is_empty() {
None
} else {
match i16::from_str(&member_form.sex) {
Ok(value) => match value {
0 | 1 | 2 | 9 => Some(value),
_ => old_member_data.sex,
},
Err(e) => {
warn!("Couldn't parse sex submitted via MemberProfileForm: {}", e);
old_member_data.sex
}
}
};
let personnel_number = match i32::from_str(&member_form.personnel_number) {
Ok(value) => Some(value),
Err(e) => {
warn!(
"Couldn't parse personnel_number submitted via MemberProfileForm: {}",
e
);
old_member_data.personnel_number
}
};
let entrance_date = if member_form.entrance_date.is_empty() {
None
} else {
match NaiveDate::parse_from_str(&member_form.entrance_date, "%Y-%m-%d") {
Ok(date) => Some(date),
Err(e) => {
warn!(
"Couldn't parse entrance_date submitted via MemberProfileForm: {}",
e
);
old_member_data.entrance_date
}
}
};
let iban = if member_form.iban.is_empty(){
None
}else{
match member_form.iban.parse::<Iban>(){
Ok(iban) => Some(iban.to_string()),
Err(e) => {warn!("Couldn't parse iban submitted via MemberProfileForm: {}", e);
old_member_data.iban
}
}
};
if(personnel_profile_change){
Some(RawMember {
entity_id,
users_id: old_member_data.users_id,
firstname: old_member_data.firstname,
lastname: old_member_data.lastname,
date_of_birth,
sex: old_member_data.sex,
salutation: old_member_data.salutation,
place_of_birth: into_option(member_form.place_of_birth),
academic_titles: old_member_data.academic_titles,
personnel_number: old_member_data.personnel_number,
ui_language: old_member_data.ui_language,
nationality: into_option(member_form.nationality),
entrance_date,
birth_name: into_option(member_form.birth_name),
iban,
bic: into_option(member_form.bic),
})
}else{
Some(RawMember {
entity_id,
users_id: old_member_data.users_id,
firstname: member_form.firstname,
lastname: member_form.lastname,
date_of_birth,
sex,
salutation: into_option(member_form.salutation),
place_of_birth: into_option(member_form.place_of_birth),
academic_titles: into_option(member_form.academic_titles),
personnel_number,
ui_language: old_member_data.ui_language,
nationality: into_option(member_form.nationality),
entrance_date,
birth_name: into_option(member_form.birth_name),
iban,
bic: into_option(member_form.bic),
})
}
}

Some files were not shown because too many files have changed in this diff Show More