Cycle Generation System closes #277 #290

Open
moritz wants to merge 16 commits from feature/277_cycle_generation into main
Showing only changes of commit ecddf55331 - Show all commits

View file

@ -50,14 +50,47 @@ defmodule Mv.MembershipFees.CalendarCycles do
## Parameters
- `date` - The date for which to find the cycle start (used as default if `reference_date` not provided)
- `date` - Ignored in this 3-argument version (kept for API consistency)
- `interval` - The interval type (`:monthly`, `:quarterly`, `:half_yearly`, `:yearly`)
- `reference_date` - The reference date to use for calculation (defaults to `date`)
- `reference_date` - The date used to determine which cycle to calculate
## Returns
The start date of the cycle containing the reference date.
## Examples
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly, ~D[2024-05-20])
~D[2024-05-01]
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :quarterly, ~D[2024-05-20])
~D[2024-04-01]
"""
@spec calculate_cycle_start(Date.t(), interval(), Date.t()) :: Date.t()
def calculate_cycle_start(_date, interval, reference_date) do
case interval do
:monthly -> monthly_cycle_start(reference_date)
:quarterly -> quarterly_cycle_start(reference_date)
:half_yearly -> half_yearly_cycle_start(reference_date)
:yearly -> yearly_cycle_start(reference_date)
end
end
@doc """
Calculates the start date of the cycle that contains the given date.
This is a convenience function that calls `calculate_cycle_start/3` with `date` as both
the input and reference date.
## Parameters
- `date` - The date used to determine which cycle to calculate
- `interval` - The interval type (`:monthly`, `:quarterly`, `:half_yearly`, `:yearly`)
## Returns
The start date of the cycle containing the given date.
## Examples
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly)
@ -71,20 +104,7 @@ defmodule Mv.MembershipFees.CalendarCycles do
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-12-15], :yearly)
~D[2024-01-01]
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly, ~D[2024-05-20])
~D[2024-05-01]
"""
@spec calculate_cycle_start(Date.t(), interval(), Date.t()) :: Date.t()
def calculate_cycle_start(_date, interval, reference_date) do
case interval do
:monthly -> monthly_cycle_start(reference_date)
:quarterly -> quarterly_cycle_start(reference_date)
:half_yearly -> half_yearly_cycle_start(reference_date)
:yearly -> yearly_cycle_start(reference_date)
end
end
@spec calculate_cycle_start(Date.t(), interval()) :: Date.t()
def calculate_cycle_start(date, interval) do
calculate_cycle_start(date, interval, date)
@ -203,7 +223,13 @@ defmodule Mv.MembershipFees.CalendarCycles do
end
@doc """
Checks if the cycle was just completed (ended before or on the given date, but is the most recent completed cycle).
Checks if the cycle is the last completed cycle.
A cycle is considered the last completed cycle if:
- The cycle has ended (cycle_end < today)
- The next cycle has not ended yet (today <= next_end)
In other words: `cycle_end < today <= next_end`
## Parameters
@ -213,7 +239,7 @@ defmodule Mv.MembershipFees.CalendarCycles do
## Returns
`true` if the cycle ended before or on the given date and is the last completed cycle, `false` otherwise.
`true` if the cycle is the last completed cycle, `false` otherwise.
## Examples
@ -230,11 +256,11 @@ defmodule Mv.MembershipFees.CalendarCycles do
def last_completed_cycle?(cycle_start, interval, today) do
cycle_end = calculate_cycle_end(cycle_start, interval)
# Cycle must have ended (before or on the given date)
# Cycle must have ended (cycle_end < today)
case Date.compare(today, cycle_end) do
:gt ->
# Check if this is the most recent completed cycle
# by verifying that the next cycle hasn't ended yet
# by verifying that the next cycle hasn't ended yet (today <= next_end)
next_start = next_cycle_start(cycle_start, interval)
next_end = calculate_cycle_end(next_start, interval)