diff --git a/lib/membership_fees/membership_fee_cycle.ex b/lib/membership_fees/membership_fee_cycle.ex index cb881938..d6842efe 100644 --- a/lib/membership_fees/membership_fee_cycle.ex +++ b/lib/membership_fees/membership_fee_cycle.ex @@ -36,6 +36,16 @@ defmodule Mv.MembershipFees.MembershipFeeCycle do postgres do table "membership_fee_cycles" repo Mv.Repo + + custom_indexes do + # Serves the period-scoped unpaid-cycle count on the member overview: + # count of a member's cycles with status = :unpaid and cycle_end in the + # active period. The composite (member_id, status, cycle_end) lets the + # aggregate seek the per-member unpaid slice and range-scan cycle_end + # without touching paid/suspended rows. + index [:member_id, :status, :cycle_end], + name: "membership_fee_cycles_member_status_end_index" + end end resource do diff --git a/priv/repo/migrations/20260706191440_add_membership_fee_cycles_status_end_index.exs b/priv/repo/migrations/20260706191440_add_membership_fee_cycles_status_end_index.exs new file mode 100644 index 00000000..b3fb8a55 --- /dev/null +++ b/priv/repo/migrations/20260706191440_add_membership_fee_cycles_status_end_index.exs @@ -0,0 +1,22 @@ +defmodule Mv.Repo.Migrations.AddMembershipFeeCyclesStatusEndIndex do + @moduledoc """ + Adds a composite index on membership_fee_cycles(member_id, status, cycle_end) + to serve the period-scoped unpaid-cycle count on the member overview. + + This file was autogenerated with `mix ash_postgres.generate_migrations`. + """ + + use Ecto.Migration + + def up do + create index(:membership_fee_cycles, [:member_id, :status, :cycle_end], + name: "membership_fee_cycles_member_status_end_index" + ) + end + + def down do + drop_if_exists index(:membership_fee_cycles, [:member_id, :status, :cycle_end], + name: "membership_fee_cycles_member_status_end_index" + ) + end +end diff --git a/priv/resource_snapshots/repo/membership_fee_cycles/20260706191442.json b/priv/resource_snapshots/repo/membership_fee_cycles/20260706191442.json new file mode 100644 index 00000000..5d974b60 --- /dev/null +++ b/priv/resource_snapshots/repo/membership_fee_cycles/20260706191442.json @@ -0,0 +1,206 @@ +{ + "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?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "cycle_start", + "type": "date" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "cycle_end", + "type": "date" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": 2, + "size": null, + "source": "amount", + "type": "decimal" + }, + { + "allow_nil?": false, + "default": "\"unpaid\"", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "status", + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": null, + "scale": null, + "size": null, + "source": "notes", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "membership_fee_cycles_member_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "members" + }, + "scale": null, + "size": null, + "source": "member_id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "precision": null, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "membership_fee_cycles_membership_fee_type_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "membership_fee_types" + }, + "scale": null, + "size": null, + "source": "membership_fee_type_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "create_table_options": null, + "custom_indexes": [ + { + "all_tenants?": false, + "concurrently": false, + "error_fields": [ + "member_id", + "status", + "cycle_end" + ], + "fields": [ + { + "type": "atom", + "value": "member_id" + }, + { + "type": "atom", + "value": "status" + }, + { + "type": "atom", + "value": "cycle_end" + } + ], + "include": null, + "message": null, + "name": "membership_fee_cycles_member_status_end_index", + "nulls_distinct": true, + "prefix": null, + "table": null, + "unique": false, + "using": null, + "where": null + } + ], + "custom_statements": [], + "has_create_action": true, + "hash": "6CDBA37AC6000D63F3014D3EED08B613B32E8D908D598248747EFA093BC5246A", + "identities": [ + { + "all_tenants?": false, + "base_filter": null, + "index_name": "membership_fee_cycles_unique_cycle_per_member_index", + "keys": [ + { + "type": "atom", + "value": "member_id" + }, + { + "type": "atom", + "value": "cycle_start" + } + ], + "name": "unique_cycle_per_member", + "nils_distinct?": true, + "where": null + } + ], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.Mv.Repo", + "schema": null, + "table": "membership_fee_cycles" +} \ No newline at end of file