Skip to content

Commit a7dbd3c

Browse files
committed
Changes from review: show also card for layer group itself, hide empty cards
1 parent e5b3a09 commit a7dbd3c

5 files changed

Lines changed: 88 additions & 33 deletions

File tree

app/domain/sww/group/statistics/memberships.rb

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ class Memberships < ::Group::Statistics::Base
1212

1313
include DateRangeFilter
1414

15-
TotalRow = Data.define(:entries, :exits, :net)
16-
RoleRow = Data.define(:label, :entries, :exits, :net)
15+
TotalRow = Data.define(:entries, :exits, :net, :count) do
16+
def blank? = entries.zero? && exits.zero? && count.zero?
17+
end
18+
RoleRow = Data.define(:label, :entries, :exits, :net, :count)
1719
GroupBreakdown = Data.define(:title, :role_rows, :total_row)
1820

1921
def total_entries
@@ -28,8 +30,13 @@ def net_change
2830
total_entries - total_exits
2931
end
3032

33+
def total_count
34+
@total_count ||= actives_scope.where(group_id: groups.map(&:id)).count
35+
end
36+
3137
def group_breakdowns
3238
@group_breakdowns ||= groups.map { |group| build_group_breakdown(group) }
39+
.reject { |breakdown| breakdown.total_row.blank? }
3340
end
3441

3542
private
@@ -40,23 +47,28 @@ def build_group_breakdown(group)
4047
total_row = TotalRow.new(
4148
entries: role_rows.sum(&:entries),
4249
exits: role_rows.sum(&:exits),
43-
net: role_rows.sum(&:net)
50+
net: role_rows.sum(&:net),
51+
count: role_rows.sum(&:count)
4452
)
4553
GroupBreakdown.new(title:, role_rows:, total_row:)
4654
end
4755

48-
def breadcrumb_title(group)
49-
group.local_hierarchy[1..].map(&:to_s).join(" → ")
56+
def breadcrumb_title(current_group)
57+
if current_group == group
58+
"#{group.to_s} #{I18n.t('group.statistics.memberships.layer_only_suffix')}"
59+
else
60+
current_group.local_hierarchy[1..].map(&:to_s).join(" → ")
61+
end
5062
end
5163

52-
def build_role_rows(group)
53-
role_types_in(group.id).map do |role_type|
54-
build_role_row(group.id, role_type)
64+
def build_role_rows(current_group)
65+
role_types_in(current_group.id).map do |role_type|
66+
build_role_row(current_group.id, role_type)
5567
end
5668
end
5769

5870
def role_types_in(group_id)
59-
(entries_by_group_and_type.keys + exits_by_group_and_type.keys)
71+
(entries_by_group_and_type.keys + exits_by_group_and_type.keys + actives_by_group_and_type.keys)
6072
.select { |id, _| id == group_id }
6173
.map { |_, role_type| role_type }
6274
.uniq
@@ -66,11 +78,12 @@ def build_role_row(group_id, type)
6678
entries = entries_by_group_and_type.fetch([group_id, type], 0)
6779
exits = exits_by_group_and_type.fetch([group_id, type], 0)
6880
net = entries - exits
69-
RoleRow.new(label: type.constantize.label, entries:, exits:, net:)
81+
count = actives_by_group_and_type.fetch([group_id, type], 0)
82+
RoleRow.new(label: type.constantize.label, entries:, exits:, net:, count:)
7083
end
7184

7285
def groups
73-
@groups ||= group.descendants.where(layer_group_id: layer.id).order(:lft).to_a
86+
@groups ||= group.self_and_descendants.where(layer_group_id: layer.id).order(:lft).to_a
7487
end
7588

7689
def entries_by_group_and_type
@@ -81,12 +94,20 @@ def exits_by_group_and_type
8194
@exits_by_group_and_type ||= exits_scope.group(:group_id, :type).count
8295
end
8396

97+
def actives_by_group_and_type
98+
@actives_by_group_and_type ||= actives_scope.group(:group_id, :type).count
99+
end
100+
84101
def entries_scope
85102
::Role.with_inactive.where(start_on: from_date..to_date)
86103
end
87104

88105
def exits_scope
89106
::Role.with_inactive.where(end_on: from_date..to_date)
90107
end
108+
109+
def actives_scope
110+
::Role.active(to_date)
111+
end
91112
end
92113
end

app/views/group/statistics/_memberships.html.haml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
= render_card title: t('.summary_title') do
2121
%dl.row
2222
- rows = [ |
23+
{ label_key: t('.total_count', date: l(statistic.to_date)), value: statistic.total_count }, |
2324
{ label_key: t('.total_entries'), value: signed_number(statistic.total_entries) }, |
2425
{ label_key: t('.total_exits'), value: signed_number(-statistic.total_exits) }, |
2526
{ label_key: t('.net_change'), |
@@ -32,24 +33,28 @@
3233
%table.table.table-bordered.text-end
3334
%colgroup
3435
%col{style: "width: 40%"}
35-
%col{style: "width: 20%"}
36-
%col{style: "width: 20%"}
37-
%col{style: "width: 20%"}
36+
%col{style: "width: 15%"}
37+
%col{style: "width: 15%"}
38+
%col{style: "width: 15%"}
39+
%col{style: "width: 15%"}
3840
%thead
3941
%tr
4042
%th.text-start= t('.role_type')
43+
%th= t('.count')
4144
%th= t('.entries')
4245
%th= t('.exits')
4346
%th= t('.net')
4447
%tbody
4548
- role_rows.each do |row|
4649
%tr
4750
%td.text-start= row.label
51+
%td= row.count
4852
%td= row.entries
4953
%td= row.exits
5054
%td= "#{signed_number(row.net)} #{net_change_arrow(row.net)}"
5155
%tr.fw-bold
5256
%td.text-start= t('.total')
57+
%td= breakdown.total_row.count
5358
%td= breakdown.total_row.entries
5459
%td= breakdown.total_row.exits
5560
%td= "#{signed_number(breakdown.total_row.net)} #{net_change_arrow(breakdown.total_row.net)}"

config/locales/views.sww.de.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,14 @@ de:
9898
total_entries: "Total Eintritte"
9999
total_exits: "Total Austritte"
100100
net_change: "Netto-Veränderung"
101+
total_count: "Anzahl Rollen per %{date}"
101102
role_type: Rollentyp
102103
entries: Eintritte
103104
exits: Austritte
104105
net: Netto
106+
count: Anzahl
105107
total: Total
108+
layer_only_suffix: "(nur Hauptgruppe ohne Untergruppen)"
106109

107110
groups:
108111
form_tabs:

spec/controllers/group/statistics_controller_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@
152152
include Capybara::RSpecMatchers
153153

154154
it "renders the group header, summary card and per-group breakdown cards" do
155+
mitglieder = groups(:berner_mitglieder)
156+
Fabricate(Group::Mitglieder::Aktivmitglied.sti_name.to_sym,
157+
group: mitglieder, start_on: Time.zone.today)
158+
155159
get :show, params: {group_id: group.id, key: :memberships}
156160

157161
expect(response).to have_http_status(200)

spec/domain/sww/group/statistics/memberships_spec.rb

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ def create_role(group:, type: Group::Mitglieder::Aktivmitglied, person: Fabricat
7474
end
7575
end
7676

77+
describe "#total_count" do
78+
it "counts active roles at to_date, across the whole hierarchy" do
79+
create_role(group: mitglieder, start_on: Date.new(2024, 3, 1))
80+
create_role(group: kontakte, type: Group::Kontakte::Kontakt, start_on: Date.new(2024, 7, 1))
81+
82+
# 2 additional roles plus berner_mitglied and no_permissions fixtures
83+
expect(statistic(range).total_count).to eq(4)
84+
end
85+
86+
it "does not count roles from a different layer" do
87+
zuercher = groups(:zuercher_mitglieder)
88+
create_role(group: zuercher, start_on: Date.new(2024, 3, 1))
89+
90+
# only berner_mitglied and no_permissions fixtures are in the layer
91+
expect(statistic(range).total_count).to eq(2)
92+
end
93+
end
94+
7795
describe "a role type change within the same group" do
7896
it "counts as both an exit for the old type and an entry for the new type" do
7997
person = Fabricate(:person)
@@ -90,42 +108,44 @@ def create_role(group:, type: Group::Mitglieder::Aktivmitglied, person: Fabricat
90108
end
91109

92110
describe "#group_breakdowns" do
93-
it "lists every descendant group in the layer, even ones without any changes" do
111+
it "lists only groups with entries, exits or count in the layer" do
112+
create_role(group: mitglieder, start_on: Date.new(2024, 3, 1))
94113
titles = statistic(range).group_breakdowns.map(&:title)
95-
expect(titles).to contain_exactly("Gremium", "Vorstand", "Geschäftsstelle", "Mitglieder",
96-
"Kontakte")
114+
expect(titles).to include("Mitglieder")
115+
expect(titles).to include("Kontakte")
97116
end
98117

99-
it "gives a group without changes an empty role_rows and a zeroed total_row" do
100-
breakdown = statistic(range).group_breakdowns.find { |b| b.title == "Kontakte" }
101-
expect(breakdown.role_rows).to be_empty
102-
expect(breakdown.total_row.to_h).to eq(entries: 0, exits: 0, net: 0)
103-
end
104-
105-
it "returns entries/exits/net per role type, plus a summed total_row" do
118+
it "returns entries/exits/net/count per role type, plus a summed total_row" do
119+
# role(:berner_mitglied) from fixtures is also counted
106120
create_role(group: mitglieder, type: Group::Mitglieder::Aktivmitglied,
107121
start_on: Date.new(2024, 3, 1))
108122
create_role(group: mitglieder, type: Group::Mitglieder::Freimitglied,
109123
start_on: Date.new(2020, 1, 1), end_on: Date.new(2024, 6, 1))
110124

111125
breakdown = statistic(range).group_breakdowns.find { |b| b.title == "Mitglieder" }
112126
expect(breakdown.role_rows.map(&:to_h)).to contain_exactly(
113-
{label: "Aktivmitglied", entries: 1, exits: 0, net: 1},
114-
{label: "Freimitglied", entries: 0, exits: 1, net: -1}
127+
{label: "Aktivmitglied", entries: 1, exits: 0, net: 1, count: 2},
128+
{label: "Freimitglied", entries: 0, exits: 1, net: -1, count: 0}
115129
)
116-
expect(breakdown.total_row.to_h).to eq(entries: 1, exits: 1, net: 0)
130+
expect(breakdown.total_row.to_h).to eq(entries: 1, exits: 1, net: 0, count: 2)
117131
end
118132

119-
it "builds a breadcrumb title for a nested group, excluding the layer itself" do
120-
Fabricate(Group::Mitglieder.sti_name.to_sym, parent: mitglieder, name: "Aktive")
133+
it "builds a breadcrumb title for a nested group, excluding the layer name itself" do
134+
aktive = Fabricate(Group::Mitglieder.sti_name.to_sym, parent: mitglieder, name: "Aktive")
135+
create_role(group: aktive, start_on: Date.new(2024, 3, 1))
121136

122137
titles = statistic(range).group_breakdowns.map(&:title)
123138
expect(titles).to include("Mitglieder → Aktive")
124139
end
125140

126-
it "does not include the layer itself in the breakdown" do
127-
titles = statistic(range).group_breakdowns.map(&:title)
128-
expect(titles).not_to include(layer.to_s)
141+
it "includes the layer itself in the breakdown with suffix when it has entries/exits" do
142+
root = groups(:schweizer_wanderwege)
143+
create_role(group: root, type: Group::SchweizerWanderwege::Mitarbeitende,
144+
start_on: Date.new(2024, 3, 1))
145+
stat = described_class.new(root, ActionController::Parameters.new(range))
146+
147+
titles = stat.group_breakdowns.map(&:title)
148+
expect(titles).to include("#{root} (nur Hauptgruppe ohne Untergruppen)")
129149
end
130150
end
131151

@@ -152,7 +172,9 @@ def create_role(group:, type: Group::Mitglieder::Aktivmitglied, person: Fabricat
152172
create_role(group: aktive, start_on: Date.new(2024, 3, 1))
153173
create_role(group: kontakte, type: Group::Kontakte::Kontakt, start_on: Date.new(2024, 3, 1))
154174
stat = described_class.new(mitglieder.reload, ActionController::Parameters.new(range))
155-
expect(stat.group_breakdowns.map(&:title)).to contain_exactly("Mitglieder → Aktive")
175+
expect(stat.group_breakdowns.map(&:title)).to contain_exactly(
176+
"Mitglieder (nur Hauptgruppe ohne Untergruppen)", "Mitglieder → Aktive"
177+
)
156178
expect(stat.total_entries).to eq(1)
157179
end
158180
end

0 commit comments

Comments
 (0)