Merge pull request 'Remove the join_date future-date validation closes #482' (#495) from issue/mitgliederverwaltung-482 into main
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #495
This commit is contained in:
commit
fa9cd0a35b
5 changed files with 37 additions and 13 deletions
|
|
@ -188,7 +188,6 @@ Settings (1) → MembershipFeeType (0..1)
|
||||||
### Member Constraints
|
### Member Constraints
|
||||||
- First name and last name required (min 1 char)
|
- First name and last name required (min 1 char)
|
||||||
- Email unique, validated format (5-254 chars)
|
- Email unique, validated format (5-254 chars)
|
||||||
- Join date cannot be in future
|
|
||||||
- Exit date must be after join date
|
- Exit date must be after join date
|
||||||
- Phone: `+?[0-9\- ]{6,20}`
|
- Phone: `+?[0-9\- ]{6,20}`
|
||||||
- Postal code: optional (no format validation)
|
- Postal code: optional (no format validation)
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ Table members {
|
||||||
first_name text [null, note: 'Member first name (min length: 1 if present)']
|
first_name text [null, note: 'Member first name (min length: 1 if present)']
|
||||||
last_name text [null, note: 'Member last name (min length: 1 if present)']
|
last_name text [null, note: 'Member last name (min length: 1 if present)']
|
||||||
email text [not null, unique, note: 'Member email address (5-254 chars, validated)']
|
email text [not null, unique, note: 'Member email address (5-254 chars, validated)']
|
||||||
join_date date [null, note: 'Date when member joined club (cannot be in future)']
|
join_date date [null, note: 'Date when member joined club']
|
||||||
exit_date date [null, note: 'Date when member left club (must be after join_date)']
|
exit_date date [null, note: 'Date when member left club (must be after join_date)']
|
||||||
notes text [null, note: 'Additional notes about member']
|
notes text [null, note: 'Additional notes about member']
|
||||||
city text [null, note: 'City of residence']
|
city text [null, note: 'City of residence']
|
||||||
|
|
@ -187,7 +187,6 @@ Table members {
|
||||||
**Validation Rules:**
|
**Validation Rules:**
|
||||||
- first_name, last_name: optional, but if present min 1 character
|
- first_name, last_name: optional, but if present min 1 character
|
||||||
- email: 5-254 characters, valid email format (required)
|
- email: 5-254 characters, valid email format (required)
|
||||||
- join_date: cannot be in future
|
|
||||||
- exit_date: must be after join_date (if both present)
|
- exit_date: must be after join_date (if both present)
|
||||||
- postal_code: optional (no format validation)
|
- postal_code: optional (no format validation)
|
||||||
- country: optional
|
- country: optional
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ defmodule Mv.Membership.Member do
|
||||||
## Validations
|
## Validations
|
||||||
- Required: email (all other fields are optional)
|
- Required: email (all other fields are optional)
|
||||||
- Email format validation (using EctoCommons.EmailValidator)
|
- Email format validation (using EctoCommons.EmailValidator)
|
||||||
- Date validations: join_date not in future, exit_date after join_date
|
- Date validations: exit_date after join_date
|
||||||
- Email uniqueness: prevents conflicts with unlinked users
|
- Email uniqueness: prevents conflicts with unlinked users
|
||||||
- Linked member email change: only admins or the linked user may change a linked member's email (see `Mv.Membership.Member.Validations.EmailChangePermission`)
|
- Linked member email change: only admins or the linked user may change a linked member's email (see `Mv.Membership.Member.Validations.EmailChangePermission`)
|
||||||
|
|
||||||
|
|
@ -473,11 +473,6 @@ defmodule Mv.Membership.Member do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Join date not in future
|
|
||||||
validate compare(:join_date, less_than_or_equal_to: &Date.utc_today/0),
|
|
||||||
where: [present(:join_date)],
|
|
||||||
message: "cannot be in the future"
|
|
||||||
|
|
||||||
# Exit date not before join date
|
# Exit date not before join date
|
||||||
validate compare(:exit_date, greater_than: :join_date),
|
validate compare(:exit_date, greater_than: :join_date),
|
||||||
where: [present([:join_date, :exit_date])],
|
where: [present([:join_date, :exit_date])],
|
||||||
|
|
|
||||||
2
mix.lock
2
mix.lock
|
|
@ -22,7 +22,7 @@
|
||||||
"credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"},
|
"credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"},
|
||||||
"crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"},
|
"crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"},
|
||||||
"db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"},
|
"db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"},
|
||||||
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
"decimal": {:hex, :decimal, "3.1.0", "9ede268cff827e6f0c4fb1b34747c82630dce5d7b877dfb22ec8f0cb25855fce", [:mix], [], "hexpm", "e8b3efb3bb3a13cb5e4268ffe128569067b1972e9dee013537c71a5b073168f9"},
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
||||||
"ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"},
|
"ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"},
|
||||||
"ecto_commons": {:hex, :ecto_commons, "0.3.7", "f33c162a6f63695d5939af02c65a0e76aa6e7278b82c7bfc357ffbfea353bf0f", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.4", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "9c33771ebd38cd83d3f90fab6069826ba9d4f7580f1481b3c0913f8b9795c5fd"},
|
"ecto_commons": {:hex, :ecto_commons, "0.3.7", "f33c162a6f63695d5939af02c65a0e76aa6e7278b82c7bfc357ffbfea353bf0f", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.4", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "9c33771ebd38cd83d3f90fab6069826ba9d4f7580f1481b3c0913f8b9795c5fd"},
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,43 @@ defmodule Mv.Membership.MemberTest do
|
||||||
assert error_message(errors, :email) =~ "is not a valid email"
|
assert error_message(errors, :email) =~ "is not a valid email"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Join date cannot be in the future", %{actor: actor} do
|
test "Join date can be in the future", %{actor: actor} do
|
||||||
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
|
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
|
||||||
|
|
||||||
assert {:error,
|
assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
|
||||||
%Ash.Error.Invalid{errors: [%Ash.Error.Changes.InvalidAttribute{field: :join_date}]}} =
|
end
|
||||||
|
|
||||||
|
test "Join date far in the future (2099) is accepted", %{actor: actor} do
|
||||||
|
attrs = Map.put(@valid_attrs, :join_date, ~D[2099-12-31])
|
||||||
|
|
||||||
|
assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Join date today is accepted", %{actor: actor} do
|
||||||
|
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today())
|
||||||
|
|
||||||
|
assert {:ok, _member} = Membership.create_member(attrs, actor: actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Join date in the future is accepted on update", %{actor: actor} do
|
||||||
|
{:ok, member} = Membership.create_member(@valid_attrs, actor: actor)
|
||||||
|
|
||||||
|
assert {:ok, _updated} =
|
||||||
|
Membership.update_member(member, %{join_date: Date.utc_today() |> Date.add(30)},
|
||||||
|
actor: actor
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Exit date before future join date is rejected", %{actor: actor} do
|
||||||
|
attrs =
|
||||||
|
@valid_attrs
|
||||||
|
|> Map.put(:join_date, Date.utc_today() |> Date.add(10))
|
||||||
|
|> Map.put(:exit_date, Date.utc_today() |> Date.add(5))
|
||||||
|
|
||||||
|
assert {:error, %Ash.Error.Invalid{errors: errors}} =
|
||||||
Membership.create_member(attrs, actor: actor)
|
Membership.create_member(attrs, actor: actor)
|
||||||
|
|
||||||
|
assert error_message(errors, :exit_date) =~ "cannot be before join date"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Exit date is optional but must not be before join date if both are specified", %{
|
test "Exit date is optional but must not be before join date if both are specified", %{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue