mitgliederverwaltung/priv/repo/seeds_bulk_members.exs
Simon 2c8a403cc0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
fix(member-live): guard the composite sort event and apply review cleanup
2026-07-16 09:39:18 +02:00

136 lines
4.2 KiB
Elixir

# Optional BULK member seed for load / infinite-scroll testing.
#
# This is NOT part of the default dev seed (`mix run priv/repo/seeds.exs`), so the
# normal dev database stays small. Run it explicitly when you need many members:
#
# just seed-members-bulk
# BULK_MEMBER_COUNT=500 mix run priv/repo/seeds_bulk_members.exs
#
# Requires the bootstrap seeds (admin user + fee types) to have run first. Members
# are upserted by email ("bulk-member-<n>@example.de"), so re-running is idempotent
# and does not duplicate members or their fee cycles.
alias Mv.Accounts
alias Mv.Membership
alias Mv.MembershipFees.CycleGenerator
alias Mv.MembershipFees.MembershipFeeType
require Ash.Query
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
admin_user_with_role =
case Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
{:ok, user} when not is_nil(user) ->
user |> Ash.load!(:role, authorize?: false)
_ ->
raise "Bulk seed requires bootstrap: admin user not found (#{admin_email})"
end
all_fee_types =
MembershipFeeType
|> Ash.Query.sort(name: :asc)
|> Ash.read!(actor: admin_user_with_role, domain: Mv.MembershipFees)
|> Enum.to_list()
count =
case Integer.parse(System.get_env("BULK_MEMBER_COUNT") || "300") do
{n, _} when n > 0 -> n
_ -> 300
end
first_names =
~w(Anna Bruno Clara David Elena Felix Greta Henrik Ines Jakob Laura Max Nina
Oliver Paula Quirin Rosa Stefan Thea Uwe Vera Wolf Xenia Yannick Zoe Ada
Ben Carl Dora Emil)
last_names =
~w(Schmidt Müller Fischer Weber Wagner Becker Schulz Hoffmann Koch Richter Klein
Wolf Neumann Schwarz Zimmermann Braun Krüger Hartmann Lange Werner Krause
Lehmann Köhler Herrmann Walter)
cities = [
{"München", "80331"},
{"Hamburg", "20095"},
{"Berlin", "10435"},
{"Köln", "50667"},
{"Frankfurt", "60313"},
{"Stuttgart", "70173"},
{"Düsseldorf", "40212"},
{"Dortmund", "44137"},
{"Essen", "45127"},
{"Dresden", "01069"},
{"Nürnberg", "90402"},
{"Bremen", "28195"}
]
statuses = [:paid, :unpaid, :suspended]
IO.puts("Seeding #{count} bulk members (set BULK_MEMBER_COUNT to change)…")
Enum.each(1..count, fn i ->
{city, postal_code} = Enum.at(cities, rem(i, length(cities)))
attrs = %{
first_name: Enum.at(first_names, rem(i, length(first_names))),
last_name: Enum.at(last_names, rem(div(i, length(first_names)) + i, length(last_names))),
email: "bulk-member-#{i}@example.de",
join_date: Date.add(~D[2020-01-01], rem(i * 7, 2000)),
city: city,
street: "Teststraße",
house_number: Integer.to_string(rem(i, 200) + 1),
postal_code: postal_code,
country: "Deutschland"
}
attrs =
case all_fee_types do
[] -> attrs
types -> Map.put(attrs, :membership_fee_type_id, Enum.at(types, rem(i, length(types))).id)
end
member =
Membership.create_member!(attrs,
upsert?: true,
upsert_identity: :unique_email,
actor: admin_user_with_role
)
# Give the member fee cycles + a round-robin status so the fee-status column is
# populated at scale. Cycles are only generated when none exist yet; the status
# is (re-)applied on every run so a run interrupted between generation and the
# status update still converges (idempotent — the update is a no-op once set).
if not is_nil(member.membership_fee_type_id) do
member = Ash.load!(member, :membership_fee_cycles, actor: admin_user_with_role)
cycles =
if Enum.empty?(member.membership_fee_cycles) do
{:ok, generated, _} =
CycleGenerator.generate_cycles_for_member(member.id,
skip_lock?: true,
actor: admin_user_with_role
)
generated
else
member.membership_fee_cycles
end
status = Enum.at(statuses, rem(i, length(statuses)))
Enum.each(cycles, fn cycle ->
if cycle.status != status do
cycle
|> Ash.Changeset.for_update(:update, %{status: status})
|> Ash.update!(actor: admin_user_with_role, domain: Mv.MembershipFees)
end
end)
end
if rem(i, 50) == 0, do: IO.puts("#{i}/#{count}")
end)
IO.puts("✅ Bulk seed complete: #{count} members.")