docs: update group concept
This commit is contained in:
parent
1d1f3b16b1
commit
1c7c56130d
1 changed files with 31 additions and 21 deletions
|
|
@ -117,10 +117,10 @@ CREATE TABLE groups (
|
||||||
description TEXT,
|
description TEXT,
|
||||||
inserted_at TIMESTAMP NOT NULL,
|
inserted_at TIMESTAMP NOT NULL,
|
||||||
updated_at TIMESTAMP NOT NULL,
|
updated_at TIMESTAMP NOT NULL,
|
||||||
CONSTRAINT groups_name_unique UNIQUE (name)
|
CONSTRAINT groups_name_unique UNIQUE (LOWER(name))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX groups_name_index ON groups(name);
|
CREATE INDEX groups_name_index ON groups(LOWER(name));
|
||||||
```
|
```
|
||||||
|
|
||||||
**Attributes:**
|
**Attributes:**
|
||||||
|
|
@ -130,7 +130,7 @@ CREATE INDEX groups_name_index ON groups(name);
|
||||||
- `inserted_at` / `updated_at` - Timestamps
|
- `inserted_at` / `updated_at` - Timestamps
|
||||||
|
|
||||||
**Constraints:**
|
**Constraints:**
|
||||||
- `name` must be unique
|
- `name` must be unique (case-insensitive, using LOWER(name))
|
||||||
- `name` cannot be null
|
- `name` cannot be null
|
||||||
- `name` max length: 100 characters
|
- `name` max length: 100 characters
|
||||||
- `description` max length: 500 characters
|
- `description` max length: 500 characters
|
||||||
|
|
@ -194,7 +194,7 @@ end
|
||||||
- `destroy` - Delete group (with confirmation)
|
- `destroy` - Delete group (with confirmation)
|
||||||
|
|
||||||
**Validations:**
|
**Validations:**
|
||||||
- `name` required, unique, max 100 chars
|
- `name` required, unique (case-insensitive), max 100 chars
|
||||||
- `description` optional, max 500 chars
|
- `description` optional, max 500 chars
|
||||||
|
|
||||||
#### `Mv.Membership.MemberGroup`
|
#### `Mv.Membership.MemberGroup`
|
||||||
|
|
@ -319,7 +319,7 @@ end
|
||||||
- Display group badges for each member
|
- Display group badges for each member
|
||||||
- Badge shows group name
|
- Badge shows group name
|
||||||
- Multiple badges if member in multiple groups
|
- Multiple badges if member in multiple groups
|
||||||
- Click badge to filter by that group
|
- *(Optional)* Click badge to filter by that group (enhanced UX, can be added later)
|
||||||
|
|
||||||
**Filtering:**
|
**Filtering:**
|
||||||
- Dropdown/select to filter by group
|
- Dropdown/select to filter by group
|
||||||
|
|
@ -374,7 +374,11 @@ end
|
||||||
</span>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
**Clickable Group Badge (for filtering):**
|
**Clickable Group Badge (for filtering) - Optional:**
|
||||||
|
|
||||||
|
**Note:** This is an optional enhancement. The dropdown filter provides the same functionality. The clickable badge improves UX by showing the active filter visually and allowing quick removal.
|
||||||
|
|
||||||
|
**Estimated effort:** 1.5-2.5 hours
|
||||||
|
|
||||||
```heex
|
```heex
|
||||||
<button
|
<button
|
||||||
|
|
@ -712,13 +716,13 @@ Das MVP umfasst die **grundlegenden Funktionen**, die notwendig sind, um Gruppen
|
||||||
7. ✅ Nach Gruppen filtern
|
7. ✅ Nach Gruppen filtern
|
||||||
8. ✅ Nach Gruppen sortieren
|
8. ✅ Nach Gruppen sortieren
|
||||||
9. ✅ Gruppen in Mitgliederdetail anzeigen
|
9. ✅ Gruppen in Mitgliederdetail anzeigen
|
||||||
|
10. ✅ Gruppen in Mitgliedersuche (automatisch via search_vector)
|
||||||
|
|
||||||
**Nicht im MVP:**
|
**Nicht im MVP:**
|
||||||
- ❌ Hierarchische Gruppen
|
- ❌ Hierarchische Gruppen
|
||||||
- ❌ Rollen/Positionen in Gruppen
|
- ❌ Rollen/Positionen in Gruppen
|
||||||
- ❌ Erweiterte Gruppenattribute (Datum, Status, etc.)
|
- ❌ Erweiterte Gruppenattribute (Datum, Status, etc.)
|
||||||
- ❌ Gruppen-spezifische Berechtigungen
|
- ❌ Gruppen-spezifische Berechtigungen
|
||||||
- ❌ Gruppen in Mitgliedersuche (kann später kommen)
|
|
||||||
|
|
||||||
### Fachliche Einheiten (Vertikale Slices)
|
### Fachliche Einheiten (Vertikale Slices)
|
||||||
|
|
||||||
|
|
@ -846,11 +850,9 @@ Das MVP umfasst die **grundlegenden Funktionen**, die notwendig sind, um Gruppen
|
||||||
- ✅ Einheit 3: Gruppen-Verwaltungs-UI
|
- ✅ Einheit 3: Gruppen-Verwaltungs-UI
|
||||||
- ✅ Einheit 4: Gruppen in Mitgliederübersicht
|
- ✅ Einheit 4: Gruppen in Mitgliederübersicht
|
||||||
- ✅ Einheit 5: Gruppen in Mitgliederdetail
|
- ✅ Einheit 5: Gruppen in Mitgliederdetail
|
||||||
|
- ✅ Einheit 6: Gruppen in Mitgliedersuche (automatisch via search_vector)
|
||||||
- ✅ Einheit 7: Berechtigungen
|
- ✅ Einheit 7: Berechtigungen
|
||||||
|
|
||||||
**Optional für MVP (kann später kommen):**
|
|
||||||
- ⏸️ Einheit 6: Gruppen in Mitgliedersuche (kann in Phase 2 kommen)
|
|
||||||
|
|
||||||
**Total MVP Estimation:** 13-15h
|
**Total MVP Estimation:** 13-15h
|
||||||
|
|
||||||
### Implementierungsreihenfolge
|
### Implementierungsreihenfolge
|
||||||
|
|
@ -947,11 +949,13 @@ Jede fachliche Einheit kann als **separates Issue** umgesetzt werden:
|
||||||
3. Group filter dropdown
|
3. Group filter dropdown
|
||||||
4. Group sorting
|
4. Group sorting
|
||||||
5. URL query param persistence
|
5. URL query param persistence
|
||||||
|
6. *(Optional)* Clickable group badges for filtering (enhanced UX)
|
||||||
|
|
||||||
**Deliverables:**
|
**Deliverables:**
|
||||||
- Groups visible in member overview
|
- Groups visible in member overview
|
||||||
- Filter by group
|
- Filter by group (via dropdown)
|
||||||
- Sort by group
|
- Sort by group
|
||||||
|
- *(Optional)* Clickable badges for quick filtering
|
||||||
|
|
||||||
**Estimation:** 2-3h
|
**Estimation:** 2-3h
|
||||||
|
|
||||||
|
|
@ -1046,12 +1050,14 @@ Jede fachliche Einheit kann als **separates Issue** umgesetzt werden:
|
||||||
- Group filter dropdown
|
- Group filter dropdown
|
||||||
- Group sorting
|
- Group sorting
|
||||||
- URL persistence
|
- URL persistence
|
||||||
|
- *(Optional)* Clickable group badges for filtering
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- Groups visible in member overview
|
- Groups visible in member overview
|
||||||
- Can filter by group
|
- Can filter by group (via dropdown)
|
||||||
- Can sort by group
|
- Can sort by group
|
||||||
- Filter persists in URL
|
- Filter persists in URL
|
||||||
|
- *(Optional)* Can filter by clicking group badge
|
||||||
|
|
||||||
### Issue 4: Member Detail - Groups Display
|
### Issue 4: Member Detail - Groups Display
|
||||||
**Type:** Frontend
|
**Type:** Frontend
|
||||||
|
|
@ -1069,13 +1075,16 @@ Jede fachliche Einheit kann als **separates Issue** umgesetzt werden:
|
||||||
**Type:** Backend
|
**Type:** Backend
|
||||||
**Estimation:** 2h
|
**Estimation:** 2h
|
||||||
**Tasks:**
|
**Tasks:**
|
||||||
- Update search vector trigger
|
- Update search vector trigger to include group names
|
||||||
- Extend fuzzy search
|
- Extend fuzzy search to search group names
|
||||||
- Test search
|
- Test search functionality
|
||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- Group names searchable in member search
|
- Group names searchable in member search (automatic via search_vector)
|
||||||
- Search finds members by group name
|
- Search finds members by group name
|
||||||
|
- Search vector updates automatically when member-group associations change
|
||||||
|
|
||||||
|
**Note:** This is part of MVP as group names are automatically included in full-text search once the search_vector trigger is updated.
|
||||||
|
|
||||||
### Issue 6: Authorization
|
### Issue 6: Authorization
|
||||||
**Type:** Backend/Frontend
|
**Type:** Backend/Frontend
|
||||||
|
|
@ -1132,11 +1141,12 @@ defmodule Mv.Membership.GroupTest do
|
||||||
assert %{name: ["has already been taken"]} = errors_on(changeset)
|
assert %{name: ["has already been taken"]} = errors_on(changeset)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "name uniqueness is case-sensitive" do
|
test "name uniqueness is case-insensitive" do
|
||||||
Group.create!(%{name: "Vorstand"})
|
Group.create!(%{name: "Vorstand"})
|
||||||
attrs = %{name: "VORSTAND"}
|
attrs = %{name: "VORSTAND"}
|
||||||
# For MVP: case-sensitive uniqueness
|
# Name uniqueness should be case-insensitive
|
||||||
assert {:ok, _} = Group.create(attrs)
|
assert {:error, changeset} = Group.create(attrs)
|
||||||
|
assert %{name: ["has already been taken"]} = errors_on(changeset)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "allows description to be nil" do
|
test "allows description to be nil" do
|
||||||
|
|
@ -1458,8 +1468,8 @@ create table(:groups, primary_key: false) do
|
||||||
add :updated_at, :utc_datetime_usec, null: false
|
add :updated_at, :utc_datetime_usec, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create unique_index(:groups, [:name])
|
create unique_index(:groups, [fragment("LOWER(name)")], name: :groups_name_unique)
|
||||||
create index(:groups, [:name])
|
create index(:groups, [fragment("LOWER(name)")], name: :groups_name_index)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Migration 2: Create member_groups join table**
|
**Migration 2: Create member_groups join table**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue