Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ docs/module-structure.html
.idea/
.vscode/
*.swp
.aider*
18 changes: 18 additions & 0 deletions api/feature1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Feature1ListView(generics.ListCreateAPIView):
permission_classes = [HasModuleGrant("elm"),
HasAnyPermission("elm.feature1.view")]
filterset_class = Feature1Filter

def get_queryset(self):
return selectors.visible_feature1s(self.request.principal) # never Feature1.objects

def get_serializer_class(self):
return WriteFeature1Serializer if self.request.method == "POST" else ReadFeature1Serializer

@extend_schema(summary="List feature 1s visible to the caller", tags=["elm"],
examples=[OpenApiExample("open feature 1", value={...})])
def get(self, *a, **kw):
return super().get(*a, **kw)
```

tests/test_feature1.py
17 changes: 17 additions & 0 deletions apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.apps import AppConfig
from django.core.checks import register


class ELMConfig(AppConfig):
name = "modules.elm"
label = "elm"
verbose_name = "Example Module"

def ready(self) -> None:
# ELM_UPLOAD_ROOT is this module's setting, so it owns the check.
from core.files.checks import check_upload_root

register(check_upload_root)
```

registry.py
36 changes: 36 additions & 0 deletions docs/phase2-evidence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ELM Phase 2 Development Evidence

## Objective
Implement the Employee Leave Management module in the Fusion-Integrated
repository while preserving the Phase 1 branch.

## Development environment
- Coding agent: Aider
- Model: Ollama qwen2.5-coder:3b
- Repository branch: phase2-elm-improvements
- Operating environment: Windows PowerShell

## Current implementation
- Django app configuration: modules/elm/apps.py
- Domain entities: modules/elm/domain/entities.py
- Domain exceptions: modules/elm/domain/errors.py
- Domain tests: modules/elm/tests/test_entities.py

## Evidence contents
- prompts.md records the main implementation prompts.
- verification-log.md records commands, test outcomes, and limitations.

## Scope limitation
The current work covers the initial domain foundation and basic unit tests.
Leave entitlement calculations, calendar validation, overlap detection,
approval workflows, persistence, APIs, permissions, and integration remain
future implementation tasks until they are actually implemented and tested.

This evidence must be updated as development continues. Do not describe
unimplemented functionality as complete.

## Latest verified milestone
- Continuous leave-day counting implemented in `modules/elm/domain/policy.py`.
- The focused domain suite currently contains 12 passing tests.
- Focused Flake8 checks completed successfully.
- Broader ELM business rules and integration are still pending.
71 changes: 71 additions & 0 deletions docs/phase2-evidence/prompts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ELM Phase 2 Prompt Log

The following are the main implementation instructions used in the
development session. Tool responses and failed attempts are recorded in the
verification log; this file is not a verbatim export of the entire chat.

## Prompt 1 — Domain entities

Create modules/elm/domain/entities.py as an actual repository file.

- Define LeaveCategory with exactly CL, RH, SCL, EL, COL, and VL.
- Define frozen dataclasses LeaveRequestDateRange and LeaveRequest.
- Reject a date range where start_date is later than end_date.
- Use Python standard-library types and keep the domain independent of Django.
- Make an actual file edit rather than returning a plan.

## Prompt 2 — Domain exceptions

Create modules/elm/domain/errors.py.

Define ELMDomainError and specific exceptions:
InvalidDateRangeError, IneligibleCategoryError, InsufficientBalanceError,
OverlappingLeaveError, and InvalidWorkflowTransitionError.

Use Python standard-library features and create the actual repository file.

## Prompt 3 — Domain tests

Implement focused unit tests for the ELM domain foundation.

- Verify the six supported leave-category values.
- Test valid and reversed date ranges.
- Test LeaveRequest construction using the current API.
- Test inheritance and instantiation of the specific domain exceptions.
- Follow repository test conventions.
- Do not test business logic that has not yet been implemented.
- Do not modify unrelated modules.

## Prompt 4 — Test correction

After the generated test file contained duplicate classes and incorrect
imports, replace it with one clean unittest test module using imports from
modules.elm.domain.entities and modules.elm.domain.errors.

Preserve the existing production implementation and run the focused tests.

## Tool-use note

Aider generated some incorrect or duplicated content during initial attempts.
The test file was subsequently cleaned up manually. OpenCode was also tried,
but its session produced unrelated task content; it was not used as evidence
of a successful ELM implementation.

## Milestone: Continuous Leave-Day Counting

Implemented in `modules/elm/domain/policy.py`.

Function:
`count_continuous_leave_days(category, date_range)`

Behavior:
- Supports EL, COL, and VL only.
- Counts start and end dates inclusively.
- Includes weekends and closed holidays.
- Rejects CL, RH, and SCL for this continuous-counting operation.
- Rejects invalid argument types.

The implementation was written into the repository using PowerShell
after reviewing and correcting the proposed implementation. Aider's earlier
attempt contained incorrect weekday-only counting and duplicated domain
classes, so that proposal was not used as the final implementation.
110 changes: 110 additions & 0 deletions docs/phase2-evidence/verification-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# ELM Phase 2 Verification Log

## Initial domain tests
Command:
python -m unittest modules.elm.tests.test_entities -v

Recorded result:
6 tests passed.

Covered:
- Exact leave-category values
- Valid date range
- Reversed date range rejection
- LeaveRequest construction
- Domain exception inheritance
- Domain exception instantiation

## Lint
A focused Flake8 command was run against the ELM domain and test files.

Recorded result:
Exit code 0 with no lint output for the selected checks.

## Diff check
Command:
git diff --cached --check

Recorded result:
No whitespace errors were reported. Git previously warned that LF line
endings may be converted to CRLF on Windows; this was a line-ending warning.

## Test environment note
The command pytest modules/elm/tests/ could not run because pytest was not
available as a recognized PowerShell command. The tests were run successfully
using Python's built-in unittest runner instead.

## Current limitations
These results verify only the initial domain foundation. They do not verify
entitlements, CL half-day calculations, RH calendar rules, eligibility,
overlap detection, approval workflows, database persistence, API behavior,
permissions, or frontend integration.

Update this file with new commands and their actual outcomes after every
subsequent implementation milestone.

## Continuous leave-day counting milestone

Command:
`python -m unittest modules.elm.tests.test_entities -v`

Actual result:
12 tests passed in 0.003 seconds.

Additional verification:
- Focused Flake8 command completed with exit code 0.
- `git diff --check` reported no whitespace errors.
- Git emitted a Windows LF/CRLF line-ending warning for the test file.
This is a warning, not a test failure.

Additional test coverage:
- EL, COL, and VL count inclusive calendar days.
- A one-day interval counts as one day.
- Friday through Monday counts as four calendar days.
- CL, RH, and SCL are rejected by this function.
- Incorrect argument types are rejected.
- `datetime` boundaries are rejected by the date-only policy.

Limitations:
This function calculates continuous calendar days only. It does not yet
implement leave balances, entitlement enforcement, employee eligibility,
RH calendar validation, overlap detection, or approval workflows.

## Milestone: Sufficient-Balance Validation

Implemented in `modules/elm/domain/policy.py`.

Function:
`validate_sufficient_balance(available_balance, requested_days)`

Verified behavior:
- Accepts non-negative integer and Decimal quantities.
- Supports fractional leave values such as 0.5.
- Rejects negative values and invalid argument types.
- Rejects non-finite Decimal values.
- Raises InsufficientBalanceError when the requested amount exceeds
the available balance.

Verification command:
`python -m unittest modules.elm.tests.test_entities modules.elm.tests.test_policy -v`

Recorded result:
19 tests passed.

Focused Flake8 checks completed with exit code 0.

Limitations:
This validates a supplied balance and requested quantity. It does not
calculate annual entitlement, determine employee eligibility, or deduct
leave from a database balance.

## Annual leave limits — 2026-10-17

- Added annual-limit policy handling for CL, RH, SCL, EL, VL, and COL.
- EL is limited to 30 days for non-faculty employees; the faculty EL limit remains unspecified.
- VL is limited to eligible faculty, with a limit of 60 days.
- COL requires explicit eligibility; the configured limit is 20 days.
- SCL's 15-day value represents a maximum, not an automatic grant.
- Verification: 26 unit tests passed across the entity, balance-policy, and entitlement-policy test modules.
- Focused Flake8 check for selected syntax and undefined-name errors returned exit code 0.
- Scope limitation: these results verify the tested domain helpers, not the complete ELM application or its approval workflows.
7 changes: 7 additions & 0 deletions modules/elm/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.core.checks import register


class ElmConfig(AppConfig):
name = "modules.elm"
label = "elm"
25 changes: 25 additions & 0 deletions modules/elm/domain/entities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from enum import Enum
from dataclasses import dataclass
from datetime import date

class LeaveCategory(Enum):
CL = "CL"
RH = "RH"
SCL = "SCL"
EL = "EL"
COL = "COL"
VL = "VL"

@dataclass(frozen=True)
class LeaveRequestDateRange:
start_date: date
end_date: date

def __post_init__(self):
if self.start_date > self.end_date:
raise ValueError("start_date must not be later than end_date")

@dataclass(frozen=True)
class LeaveRequest:
category: LeaveCategory
date_range: LeaveRequestDateRange
39 changes: 39 additions & 0 deletions modules/elm/domain/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from datetime import date
from decimal import Decimal

class ELMDomainError(Exception):
"""Base class for all ELM domain errors."""
pass

class InvalidDateRangeError(ELMDomainError):
"""Error raised when the date range is invalid."""
def __init__(self, start_date: date, end_date: date):
self.start_date = start_date
self.end_date = end_date
super().__init__(f"Invalid date range: {start_date} to {end_date}")

class IneligibleCategoryError(ELMDomainError):
"""Error raised when the leave category is ineligible."""
def __init__(self, category: str):
self.category = category
super().__init__(f"Category {category} is ineligible for leave.")

class InsufficientBalanceError(ELMDomainError):
"""Error raised when the leave balance is insufficient."""
def __init__(self, balance: int | Decimal, required: int | Decimal):
self.balance = balance
self.required = required
super().__init__(f"Insufficient leave balance: {balance} available, {required} required.")

class OverlappingLeaveError(ELMDomainError):
"""Error raised when the leave request overlaps with an existing leave request."""
def __init__(self, request: object):
self.request = request
super().__init__(f"Leave request overlaps with existing request: {request}")

class InvalidWorkflowTransitionError(ELMDomainError):
"""Error raised when an invalid workflow transition is attempted."""
def __init__(self, current_state: str, target_state: str):
self.current_state = current_state
self.target_state = target_state
super().__init__(f"Invalid workflow transition: {current_state} -> {target_state}")
Loading