feat: make all member fields optional, so member creation can be continued later
This commit is contained in:
parent
d4c7af558d
commit
800a55d42f
8 changed files with 286 additions and 40 deletions
|
|
@ -24,8 +24,6 @@ defmodule Mv.Accounts.User.MemberCreationNotifier do
|
|||
|
||||
defp create_member_for_user(user) do
|
||||
member_attrs = %{
|
||||
first_name: "User",
|
||||
last_name: "Generated",
|
||||
member_email: user.email
|
||||
}
|
||||
|
||||
|
|
@ -40,4 +38,4 @@ defmodule Mv.Accounts.User.MemberCreationNotifier do
|
|||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -60,11 +60,7 @@ defmodule Mv.Membership.Member do
|
|||
end
|
||||
|
||||
validations do
|
||||
# Required fields are covered by allow_nil? false
|
||||
|
||||
# First name and last name must not be empty
|
||||
validate present(:first_name)
|
||||
validate present(:last_name)
|
||||
# All fields are optional - no required validations
|
||||
|
||||
# Birth date not in the future
|
||||
validate compare(:birth_date, less_than_or_equal_to: &Date.utc_today/0),
|
||||
|
|
@ -93,32 +89,31 @@ defmodule Mv.Membership.Member do
|
|||
|
||||
# Email validation with EctoCommons.EmailValidator (only for member_email)
|
||||
validate fn changeset, _ ->
|
||||
member_email = Ash.Changeset.get_attribute(changeset, :member_email)
|
||||
member_email = Ash.Changeset.get_attribute(changeset, :member_email)
|
||||
|
||||
changeset2 =
|
||||
{%{}, %{email: :string}}
|
||||
|> Ecto.Changeset.cast(%{email: member_email}, [:email])
|
||||
|> EctoCommons.EmailValidator.validate_email(:email, checks: [:html_input, :pow])
|
||||
changeset2 =
|
||||
{%{}, %{email: :string}}
|
||||
|> Ecto.Changeset.cast(%{email: member_email}, [:email])
|
||||
|> EctoCommons.EmailValidator.validate_email(:email, checks: [:html_input, :pow])
|
||||
|
||||
if changeset2.valid? do
|
||||
:ok
|
||||
else
|
||||
{:error, field: :member_email, message: "is not a valid email"}
|
||||
end
|
||||
end, where: [present(:member_email)]
|
||||
if changeset2.valid? do
|
||||
:ok
|
||||
else
|
||||
{:error, field: :member_email, message: "is not a valid email"}
|
||||
end
|
||||
end,
|
||||
where: [present(:member_email)]
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_v7_primary_key :id
|
||||
|
||||
attribute :first_name, :string do
|
||||
allow_nil? false
|
||||
constraints min_length: 1
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :last_name, :string do
|
||||
allow_nil? false
|
||||
constraints min_length: 1
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
# Internal email field for members without users
|
||||
|
|
@ -176,5 +171,4 @@ defmodule Mv.Membership.Member do
|
|||
calculations do
|
||||
calculate :email, :string, Mv.Membership.MemberEmailCalculation
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ defmodule Mv.Repo.Migrations.AddUniqueMemberId do
|
|||
def down do
|
||||
drop_if_exists unique_index(:users, [:member_id], name: "users_unique_member_id_index")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
defmodule Mv.Repo.Migrations.UpdateMemberConstraints do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:members) do
|
||||
modify :last_name, :text, null: true
|
||||
modify :first_name, :text, null: true
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:members) do
|
||||
modify :first_name, :text, null: false
|
||||
modify :last_name, :text, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
187
priv/resource_snapshots/repo/members/20250805151226.json
Normal file
187
priv/resource_snapshots/repo/members/20250805151226.json
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
{
|
||||
"attributes": [
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"uuid_generate_v7()\")",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": true,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "id",
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "first_name",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "last_name",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "member_email",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "birth_date",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "paid",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "phone_number",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "join_date",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "exit_date",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "notes",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "city",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "street",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "house_number",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "postal_code",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "A287EF28CB6E052DB408E8045D54E43CED1252820DD43D261E26E1DDF5CA7966",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.Mv.Repo",
|
||||
"schema": null,
|
||||
"table": "members"
|
||||
}
|
||||
52
test/accounts/empty_member_creation_test.exs
Normal file
52
test/accounts/empty_member_creation_test.exs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
defmodule Mv.Accounts.EmptyMemberCreationTest do
|
||||
use Mv.DataCase, async: true
|
||||
alias Mv.Accounts
|
||||
alias Mv.Membership
|
||||
|
||||
describe "Empty Member Creation" do
|
||||
test "can create completely empty member" do
|
||||
{:ok, member} = Membership.create_member(%{})
|
||||
|
||||
assert member.id
|
||||
assert member.first_name == nil
|
||||
assert member.last_name == nil
|
||||
assert member.member_email == nil
|
||||
assert member.birth_date == nil
|
||||
assert member.paid == nil
|
||||
assert member.phone_number == nil
|
||||
assert member.join_date == nil
|
||||
assert member.exit_date == nil
|
||||
assert member.notes == nil
|
||||
assert member.city == nil
|
||||
assert member.street == nil
|
||||
assert member.house_number == nil
|
||||
assert member.postal_code == nil
|
||||
end
|
||||
|
||||
test "user creation creates empty member automatically" do
|
||||
{:ok, user} = Accounts.create_user(%{email: "test@example.com"})
|
||||
|
||||
# Reload user to get the member_id
|
||||
user = Ash.reload!(user, domain: Mv.Accounts)
|
||||
assert user.member_id
|
||||
|
||||
# Get the created member
|
||||
member = Ash.get!(Mv.Membership.Member, user.member_id, domain: Mv.Membership)
|
||||
|
||||
# Member should be mostly empty except for email
|
||||
assert member.member_email == "test@example.com"
|
||||
assert member.first_name == nil
|
||||
assert member.last_name == nil
|
||||
assert member.birth_date == nil
|
||||
assert member.paid == nil
|
||||
assert member.phone_number == nil
|
||||
assert member.join_date == nil
|
||||
assert member.exit_date == nil
|
||||
assert member.notes == nil
|
||||
assert member.city == nil
|
||||
assert member.street == nil
|
||||
assert member.house_number == nil
|
||||
assert member.postal_code == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,8 +8,6 @@ defmodule Mv.Accounts.UserMemberIntegrationTest do
|
|||
test "ein User kann einem Member zugeordnet werden" do
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Max",
|
||||
last_name: "Mustermann",
|
||||
member_email: "max@example.com"
|
||||
})
|
||||
|
||||
|
|
@ -20,8 +18,6 @@ defmodule Mv.Accounts.UserMemberIntegrationTest do
|
|||
test "ein Member kann nur einem User zugeordnet werden (unique constraint)" do
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Anna",
|
||||
last_name: "Test",
|
||||
member_email: "anna@example.com"
|
||||
})
|
||||
|
||||
|
|
@ -66,12 +62,10 @@ defmodule Mv.Accounts.UserMemberIntegrationTest do
|
|||
test "ein Member kann ohne User existieren" do
|
||||
{:ok, member} =
|
||||
Membership.create_member(%{
|
||||
first_name: "Lisa",
|
||||
last_name: "Solo",
|
||||
member_email: "lisa@example.com"
|
||||
})
|
||||
|
||||
assert member.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,16 +19,14 @@ defmodule Mv.Membership.MemberTest do
|
|||
postal_code: "12345"
|
||||
}
|
||||
|
||||
test "First name is required and must not be empty" do
|
||||
attrs = Map.put(@valid_attrs, :first_name, "")
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
||||
assert error_message(errors, :first_name) =~ "must be present"
|
||||
test "First name is optional" do
|
||||
attrs = Map.delete(@valid_attrs, :first_name)
|
||||
assert {:ok, _member} = Membership.create_member(attrs)
|
||||
end
|
||||
|
||||
test "Last name is required and must not be empty" do
|
||||
attrs = Map.put(@valid_attrs, :last_name, "")
|
||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
||||
assert error_message(errors, :last_name) =~ "must be present"
|
||||
test "Last name is optional" do
|
||||
attrs = Map.delete(@valid_attrs, :last_name)
|
||||
assert {:ok, _member} = Membership.create_member(attrs)
|
||||
end
|
||||
|
||||
test "Email is optional" do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue