Calendar Cycle Calculation Logic closes #276 #284
1 changed files with 45 additions and 19 deletions
|
|
@ -50,14 +50,47 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
||||||
|
|
||||||
## Parameters
|
## 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)
|
||||||
|
moritz marked this conversation as resolved
|
|||||||
- `interval` - The interval type (`:monthly`, `:quarterly`, `:half_yearly`, `:yearly`)
|
- `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
|
## Returns
|
||||||
|
|
||||||
The start date of the cycle containing the reference date.
|
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
|
## Examples
|
||||||
|
|
||||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly)
|
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)
|
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-12-15], :yearly)
|
||||||
~D[2024-01-01]
|
~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()
|
@spec calculate_cycle_start(Date.t(), interval()) :: Date.t()
|
||||||
def calculate_cycle_start(date, interval) do
|
def calculate_cycle_start(date, interval) do
|
||||||
calculate_cycle_start(date, interval, date)
|
calculate_cycle_start(date, interval, date)
|
||||||
|
|
@ -203,7 +223,13 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@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
|
## Parameters
|
||||||
|
|
||||||
|
|
@ -213,7 +239,7 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
||||||
|
|
||||||
## Returns
|
## 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
|
## Examples
|
||||||
|
|
||||||
|
|
@ -230,11 +256,11 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
||||||
def last_completed_cycle?(cycle_start, interval, today) do
|
def last_completed_cycle?(cycle_start, interval, today) do
|
||||||
cycle_end = calculate_cycle_end(cycle_start, interval)
|
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
|
case Date.compare(today, cycle_end) do
|
||||||
:gt ->
|
:gt ->
|
||||||
# Check if this is the most recent completed cycle
|
# 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_start = next_cycle_start(cycle_start, interval)
|
||||||
next_end = calculate_cycle_end(next_start, interval)
|
next_end = calculate_cycle_end(next_start, interval)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
why for API consistency?
Because all other cycle function use the current date as first argument for testing the functions. This is just kept to have consistency between the other functions.