MARLO’s data model is phase-aware: every significant entity carries an explicitDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/CCAFS/MARLO/llms.txt
Use this file to discover all available pages before exploring further.
phase_id foreign key that ties it to a specific planning or reporting cycle. When a user saves a record in the PLANNING phase, that change must appear in all subsequent phases so that nothing is silently lost as the program moves forward in time. This forward propagation is the phase replication contract, and it lives inside each ManagerImpl class in marlo-data.
What phases are
Thephases table enumerates every cycle moment for every global unit. Phases form a linked list: each phase record has a next relationship pointing to the immediately following cycle. The phase types used for replication decisions are:
PLANNING— the initial annual planning cycle.REPORTING— the mid-year or end-of-year reporting cycle.UpKeep— the carry-forward phase that sits between REPORTING and the next PLANNING cycle (reached viaphase.getNext().getNext()from REPORTING).
Replication is strictly forward-only. A save in a PLANNING phase propagates to every later phase through the linked list but never touches phases that already closed. Past phases are immutable and fully auditable.
The five replication rules
EveryManagerImpl that handles a phase-aware entity must implement all five rules:
- PLANNING save → replicate the record to every phase reachable through
phase.getNext()until the chain is exhausted. - PLANNING delete → remove the record from every next phase in the same chain.
- REPORTING save → replicate to
phase.getNext().getNext()(the UpKeep phase) and recurse forward from there. - REPORTING delete → remove from the UpKeep phase chain forward.
- Section-specific skip flags (for example,
isPublicationon deliverables) must be checked before any replication runs, and replication must be skipped when the flag is set. - Duplicate prevention — before inserting into a target phase, filter the existing records to confirm the entity does not already exist for that phase.
Canonical implementation
DeliverableFundingSourceManagerImpl in marlo-data/src/main/java/org/cgiar/ccafs/marlo/data/manager/impl/DeliverableFundingSourceManagerImpl.java is the reference implementation. The full class is reproduced below with inline annotations:
Save path
Delete path
The delete path mirrors the save path exactly. The same phase-type switch and the same recursion pattern are required:Replication behavior by phase type
| Current phase | Save behavior | Delete behavior |
|---|---|---|
PLANNING | Replicate to phase.getNext() and recurse until null | Remove from phase.getNext() and recurse until null |
REPORTING | Replicate to phase.getNext().getNext() (UpKeep) and recurse | Remove from phase.getNext().getNext() and recurse |
UpKeep | No automatic replication; UpKeep is a terminal destination for REPORTING saves | Same — removal stops at UpKeep unless a subsequent phase is linked |
Other verified implementations
The same pattern appears across many manager implementations:ProjectInnovationOrganizationManagerImpl— usessaveInnovationOrganizationPhase(...)anddeleteProjectInnovationOrganizationPhase(...).ProjectInnovationComplementarySolutionManagerImpl— usessaveProjectInnovationComplementarySolutionPhase(...)anddeleteProjectInnovationComplementarySolutionPhase(...).
add*Phase, save*Phase, delete*Phase) but the structure is identical in all cases.
How to add phase replication to a new entity
Implement the Manager interface and ManagerImpl
Create a Manager interface under
marlo-data/.../manager/ and a *ManagerImpl under marlo-data/.../manager/impl/. Annotate the implementation with @Named.Inject PhaseDAO
The
PhaseDAO is the only dependency needed for replication. Inject it via constructor injection alongside the entity’s own DAO:Write the recursive helper methods
Create two private helpers — one for save replication, one for delete replication — following the pattern in
DeliverableFundingSourceManagerImpl. Both must:- Re-fetch the phase via
phaseDao.find(next.getId())(do not trust the detached instance). - Filter existing records before inserting (duplicate prevention).
- Recurse with
if (phase.getNext() != null).
Wire the helpers into save() and delete()
In
saveMyEntity(...), after persisting the current record, check currentPhase.getDescription() and call the appropriate helper. Mirror this logic exactly in deleteMyEntity(...).Risk checklist for changes to existing ManagerImpl save paths
Before modifying anyManagerImpl that handles a phase-aware entity, verify:
- The recursive propagation guard (
phase.getNext() != null) is intact in both helpers. - Save and delete replication paths remain symmetric.
- The duplicate-prevention filter is preserved when cloning records into target phases.
- Section-specific skip rules (such as
isPublication) are evaluated before any replication call. - Downstream phase consistency is verified after the change — replication that skips a link in the chain corrupts all phases after the break.