102 lines
2.7 KiB
Elixir
102 lines
2.7 KiB
Elixir
defmodule Mv.MembershipFees.MembershipFeeCycle do
|
|
@moduledoc """
|
|
Ash resource representing an individual membership fee cycle for a member.
|
|
|
|
## Overview
|
|
MembershipFeeCycle represents a single billing cycle for a member. Each cycle
|
|
tracks the payment status and amount for a specific time period.
|
|
|
|
## Attributes
|
|
- `cycle_start` - Start date of the billing cycle (aligned to calendar boundaries)
|
|
- `amount` - The fee amount for this cycle (stored for audit trail)
|
|
- `status` - Payment status: unpaid, paid, or suspended
|
|
- `notes` - Optional notes for this cycle
|
|
|
|
## Design Decisions
|
|
- **No cycle_end field**: Calculated from cycle_start + interval (from fee type)
|
|
- **Amount stored per cycle**: Preserves historical amounts when fee type changes
|
|
- **Calendar-aligned cycles**: All cycles start on calendar boundaries
|
|
|
|
## Relationships
|
|
- `belongs_to :member` - The member this cycle belongs to
|
|
- `belongs_to :membership_fee_type` - The fee type for this cycle
|
|
|
|
## Constraints
|
|
- Unique constraint on (member_id, cycle_start) - one cycle per period per member
|
|
- CASCADE delete when member is deleted
|
|
- RESTRICT delete on membership_fee_type if cycles exist
|
|
"""
|
|
use Ash.Resource,
|
|
domain: Mv.MembershipFees,
|
|
data_layer: AshPostgres.DataLayer
|
|
|
|
postgres do
|
|
table "membership_fee_cycles"
|
|
repo Mv.Repo
|
|
end
|
|
|
|
resource do
|
|
description "Individual membership fee cycle for a member"
|
|
end
|
|
|
|
actions do
|
|
defaults [:read, :destroy]
|
|
|
|
create :create do
|
|
primary? true
|
|
accept [:cycle_start, :amount, :status, :notes, :member_id, :membership_fee_type_id]
|
|
end
|
|
|
|
update :update do
|
|
primary? true
|
|
accept [:status, :notes]
|
|
end
|
|
end
|
|
|
|
attributes do
|
|
uuid_v7_primary_key :id
|
|
|
|
attribute :cycle_start, :date do
|
|
allow_nil? false
|
|
public? true
|
|
description "Start date of the billing cycle"
|
|
end
|
|
|
|
attribute :amount, :decimal do
|
|
allow_nil? false
|
|
public? true
|
|
|
|
description "Fee amount for this cycle (stored for audit trail, non-negative, max 2 decimal places)"
|
|
|
|
constraints min: 0, scale: 2
|
|
end
|
|
|
|
attribute :status, :atom do
|
|
allow_nil? false
|
|
public? true
|
|
default :unpaid
|
|
description "Payment status of this cycle"
|
|
constraints one_of: [:unpaid, :paid, :suspended]
|
|
end
|
|
|
|
attribute :notes, :string do
|
|
allow_nil? true
|
|
public? true
|
|
description "Optional notes for this cycle"
|
|
end
|
|
end
|
|
|
|
relationships do
|
|
belongs_to :member, Mv.Membership.Member do
|
|
allow_nil? false
|
|
end
|
|
|
|
belongs_to :membership_fee_type, Mv.MembershipFees.MembershipFeeType do
|
|
allow_nil? false
|
|
end
|
|
end
|
|
|
|
identities do
|
|
identity :unique_cycle_per_member, [:member_id, :cycle_start]
|
|
end
|
|
end
|