docs: add testing documentation to claude-product-cycle (#3057)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Rajan Maurya 2026-01-05 14:43:20 +05:30 committed by GitHub
parent caa4285fa3
commit 4bb7e78ffd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 7395 additions and 723 deletions

View File

@ -1,114 +1,282 @@
# Gap Analysis Command
Brief entry point to analyze implementation status. Shows where work is needed across the 5-layer lifecycle.
Comprehensive analysis showing ALL implemented items and ALL gaps across the 5-layer lifecycle. Runs on O(1) by reading index files.
## Usage
```
/gap-analysis # Brief overview (all layers summary)
/gap-analysis design # Design layer status
/gap-analysis design mockup # Design → Mockup sub-section only
/gap-analysis design spec # Design → Spec sub-section only
/gap-analysis server # Server layer status
/gap-analysis client # Client layer status
/gap-analysis client network # Client → Network sub-section
/gap-analysis client data # Client → Data sub-section
/gap-analysis feature # Feature layer status
/gap-analysis feature [name] # Feature → specific feature
/gap-analysis platform # Platform layer status
/gap-analysis platform android # Platform → Android only
/gap-analysis [feature-name] # Specific feature (all 5 layers)
/gap-analysis # FULL comprehensive view (recommended)
/gap-analysis design # Design layer only
/gap-analysis design mockup # Design → Mockup sub-section
/gap-analysis server # Server layer only
/gap-analysis client # Client layer only
/gap-analysis feature # Feature layer only
/gap-analysis feature [name] # Specific feature only
/gap-analysis platform # Platform layer only
/gap-analysis testing # Testing status (all layers)
/gap-analysis testing [layer] # Testing for specific layer
/gap-analysis [feature-name] # Single feature (all 5 layers)
```
## 5-Layer Lifecycle with Sub-Sections
## Comprehensive Output (No Parameters)
When `/gap-analysis` is called without parameters, show the **FULL comprehensive view**:
```
1. Design → spec | mockup | api | status
2. Server → endpoints | availability
3. Client → network | data | model
4. Feature → viewmodel | screen | navigation | di
5. Platform → android | ios | desktop | web
╔══════════════════════════════════════════════════════════════════════════════╗
║ MIFOS MOBILE - GAP ANALYSIS (O(1) Lookup) ║
╠══════════════════════════════════════════════════════════════════════════════╣
## 5-Layer Health Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ Layer Progress Implemented Gaps Status │
├─────────────────────────────────────────────────────────────────────────────┤
│ 1. Design [████████░░] 80% 14/17 3 ⚠️ Mockups │
│ 2. Server [██████████] 100% 11/11 0 ✅ Complete │
│ 3. Client [██████████] 100% 30/30 0 ✅ Complete │
│ 4. Feature [██████████] 100% 63/63 0 ✅ Complete │
│ 5. Platform [█████████░] 95% 4/4 1 ⚠️ Web exp. │
├─────────────────────────────────────────────────────────────────────────────┤
│ OVERALL [█████████░] 95% │
└─────────────────────────────────────────────────────────────────────────────┘
---
## ✅ IMPLEMENTED (What's Complete)
### Design Layer (17 features)
| Feature | SPEC | API | STATUS | Mockups |
|---------|:----:|:---:|:------:|:-------:|
| auth | ✅ | ✅ | ✅ | ✅ |
| home | ✅ | ✅ | ✅ | ⚠️ |
| accounts | ✅ | ✅ | ✅ | ⚠️ |
[... all 17 features ...]
### Server Layer (11 endpoint categories)
| Category | Endpoints | Status |
|----------|:---------:|:------:|
| AUTH | 4 | ✅ |
| CLIENT | 5 | ✅ |
| SAVINGS | 8 | ✅ |
[... all 11 categories ...]
### Client Layer (13 services, 17 repositories)
| Component | Count | Status |
|-----------|:-----:|:------:|
| Services | 13/13 | ✅ |
| Repositories | 17/17 | ✅ |
| DI Modules | 2/2 | ✅ |
### Feature Layer (23 modules, 63 screens)
| Component | Count | Status |
|-----------|:-----:|:------:|
| Modules | 23/23 | ✅ |
| ViewModels | 49/49 | ✅ |
| Screens | 63/63 | ✅ |
| DI Modules | 21/21 | ✅ |
### Platform Layer (4 platforms)
| Platform | Build | Status |
|----------|:-----:|:------:|
| Android | ✅ | Primary |
| iOS | ✅ | CocoaPods |
| Desktop | ✅ | JVM |
| Web | ⚠️ | Experimental |
---
## ❌ GAPS (What Needs Work)
### P0 - Critical (Blocks Other Work)
| Gap | Layer | Impact | Plan Command |
|-----|-------|--------|--------------|
| (none currently) | - | - | - |
### P1 - High Priority (User-Facing)
| Gap | Layer | Impact | Plan Command |
|-----|-------|--------|--------------|
| Missing mockups (10) | Design | v2.0 UI blocked | `/gap-planning design mockup` |
| Missing design-tokens (9) | Design | Theme consistency | `/gap-planning design mockup` |
### P2 - Nice to Have (Polish)
| Gap | Layer | Impact | Plan Command |
|-----|-------|--------|--------------|
| Web experimental | Platform | Limited browser support | `/gap-planning platform web` |
---
## 🧪 TESTING STATUS
| Layer | Unit | UI | Integration | Screenshot | Status |
|-------|:----:|:--:|:-----------:|:----------:|:------:|
| Client (Repo) | 14 | - | - | - | ⚠️ Partial |
| Feature (VM) | 0 | 0 | 0 | 0 | ⬜ Not Started |
| Platform (E2E) | - | - | 0 | 0 | ⬜ Not Started |
**Testing Gaps** (P1):
| Gap | Impact | Plan Command |
|-----|--------|--------------|
| ViewModel tests (0/49) | No regression safety | `/gap-planning testing feature` |
| UI tests (0/63) | No UI verification | `/gap-planning testing feature` |
| E2E tests (0/8) | No flow coverage | `/gap-planning testing platform` |
| Screenshot tests (0/30) | No visual regression | `/gap-planning testing platform` |
→ For detailed testing status: `/gap-analysis testing`
---
## 🛠️ AVAILABLE ACTIONS
### Create Plans
| Gap | Command | What It Does |
|-----|---------|--------------|
| All mockups | `/gap-planning design mockup` | Generate mockups for 10 features |
| Specific feature | `/gap-planning [feature]` | Plan single feature improvements |
| Web platform | `/gap-planning platform web` | Stabilize web build |
### Implement
| Target | Command | What It Does |
|--------|---------|--------------|
| E2E feature | `/implement [feature]` | Full implementation |
| Client only | `/client [feature]` | Network + Data layers |
| UI only | `/feature [feature]` | ViewModel + Screen |
### Verify
| Target | Command | What It Does |
|--------|---------|--------------|
| Any feature | `/verify [feature]` | Check implementation vs spec |
### Testing
| Target | Command | What It Does |
|--------|---------|--------------|
| Run tests | `/verify-tests [feature]` | Run tests for feature |
| Test status | `/gap-analysis testing` | See testing coverage |
| Plan tests | `/gap-planning testing [layer]` | Plan test implementation |
---
## 📊 O(1) PERFORMANCE
| Metric | Before | After | Improvement |
|--------|:------:|:-----:|:-----------:|
| Files to scan | 10-50 | 1-2 | **90% fewer** |
| Lines to read | 500-3000 | 60-200 | **80-95% less** |
| Tool calls | 3-5 | 1-2 | **60% fewer** |
---
## 📁 O(1) INDEX FILES
| Layer | Index File | Lines | Use For |
|-------|------------|:-----:|---------|
| Feature | `MODULES_INDEX.md` | ~120 | Find any module |
| Feature | `SCREENS_INDEX.md` | ~180 | Find any screen |
| Design | `FEATURES_INDEX.md` | ~100 | Check feature status |
| Design | `MOCKUPS_INDEX.md` | ~100 | Check mockup status |
| Client | `FEATURE_MAP.md` | ~150 | Map feature → services |
| Server | `API_INDEX.md` | ~80 | Find any endpoint |
| Platform | `LAYER_STATUS.md` | ~80 | Platform commands |
---
## 🎯 RECOMMENDED NEXT STEPS
Based on current gaps:
1. **Design Mockups** (P1) - 10 features need mockups
`/gap-planning design mockup`
2. **Web Platform** (P2) - Experimental status
`/gap-planning platform web`
3. **Verify Features** - Ensure all features match spec
`/verify [feature-name]`
╚══════════════════════════════════════════════════════════════════════════════╝
```
## Brief Overview Output (No Parameters)
---
When `/gap-analysis` is called without parameters, show a **brief summary**:
## 5-Layer Lifecycle
```
## Gap Analysis - Quick Overview
| Layer | Progress | Gaps | Next Action |
|-------|:--------:|:----:|-------------|
| Design | 85% | mockups/ (16) | /gap-analysis design mockup |
| Server | 100% | - | - |
| Client | 95% | 1 service | /gap-analysis client |
| Feature | 94% | dashboard | /gap-analysis feature |
| Platform | 90% | web fixes | /gap-analysis platform |
**Next Step**: Run `/gap-analysis [layer]` for details, or `/gap-planning [layer]` to plan.
┌─────────────────────────────────────────────────────────────────┐
│ 1. Design → spec | mockup | api | status │
│ 2. Server → endpoints | availability │
│ 3. Client → network | data | model │
│ 4. Feature → viewmodel | screen | navigation | di │
│ 5. Platform → android | ios | desktop | web │
└─────────────────────────────────────────────────────────────────┘
```
---
## Instructions
### Step 1: Determine Output Type
### Step 1: Read O(1) Index Files
**Layer Parameters**:
| Parameter | Template | Action |
|-----------|----------|--------|
| (none) | Brief summary | Quick overview of all layers |
| `design` | `layer-design.md` | Full design layer status |
| `server` | `layer-server.md` | Server layer status |
| `client` | `layer-client.md` | Client layer status |
| `feature` | `layer-feature.md` | Feature layer status |
| `platform` | `layer-platform.md` | Platform layer status |
| `[name]` | `feature-detail.md` | Specific feature (all layers) |
Read these files for instant status (DO NOT scan directories):
**Sub-Section Parameters** (layer + sub-section):
| Parameters | Template | Action |
|------------|----------|--------|
| `design mockup` | `subsection/design-mockup.md` | Mockup generation status |
| `design spec` | `subsection/design-spec.md` | Specification status |
| `design api` | `subsection/design-api.md` | API documentation status |
| `client network` | `subsection/client-network.md` | Network services status |
| `client data` | `subsection/client-data.md` | Repository status |
| `feature [name]` | `subsection/feature-single.md` | Single feature status |
| `platform android` | `subsection/platform-android.md` | Android-only status |
| `platform ios` | `subsection/platform-ios.md` | iOS-only status |
| `platform desktop` | `subsection/platform-desktop.md` | Desktop-only status |
| `platform web` | `subsection/platform-web.md` | Web-only status |
| Layer | Index File | Path |
|-------|------------|------|
| Feature | MODULES_INDEX.md | `claude-product-cycle/feature-layer/MODULES_INDEX.md` |
| Feature | SCREENS_INDEX.md | `claude-product-cycle/feature-layer/SCREENS_INDEX.md` |
| Design | FEATURES_INDEX.md | `claude-product-cycle/design-spec-layer/FEATURES_INDEX.md` |
| Design | MOCKUPS_INDEX.md | `claude-product-cycle/design-spec-layer/MOCKUPS_INDEX.md` |
| Client | FEATURE_MAP.md | `claude-product-cycle/client-layer/FEATURE_MAP.md` |
| Server | API_INDEX.md | `claude-product-cycle/server-layer/API_INDEX.md` |
| Platform | LAYER_STATUS.md | `claude-product-cycle/platform-layer/LAYER_STATUS.md` |
| Testing | TESTING_STATUS.md | `claude-product-cycle/*/TESTING_STATUS.md` (per layer) |
### Step 2: Read Status Files
### Step 2: Calculate Progress
| Layer | Files to Read |
|-------|---------------|
| Design | `design-spec-layer/STATUS.md`, check each `features/*/` folder |
| Server | `server-layer/FINERACT_API.md` |
| Client | `client-layer/LAYER_STATUS.md`, check `core/network/services/` |
| Feature | `feature-layer/LAYER_STATUS.md`, check `feature/*/` folders |
| Platform | Check `cmp-android/`, `cmp-ios/`, `cmp-desktop/`, `cmp-web/` |
From index files, calculate:
- **Design**: Count ✅ in FEATURES_INDEX.md + MOCKUPS_INDEX.md
- **Server**: All 11 categories = 100%
- **Client**: Count services + repositories in FEATURE_MAP.md
- **Feature**: Count modules + screens in MODULES_INDEX.md + SCREENS_INDEX.md
- **Platform**: Count working platforms in LAYER_STATUS.md
### Step 3: Calculate Percentages
### Step 3: Identify Gaps
For each layer, count actual files:
- Design: Count SPEC.md, MOCKUP.md, API.md, STATUS.md per feature
- Client: Count *Service.kt in `core/network/services/`
- Feature: Count *ViewModel.kt, *Screen.kt in `feature/*/`
- Calculate: `exists / expected * 100`
From index files, find items marked ⚠️ or ❌:
- Missing mockups → `MOCKUPS_INDEX.md`
- Missing services → `FEATURE_MAP.md`
- Missing screens → `SCREENS_INDEX.md`
- Platform issues → `LAYER_STATUS.md`
### Step 4: Fill Template
### Step 4: Generate Output
Read template from `claude-product-cycle/templates/gap-analysis/` and replace placeholders with real data.
Fill the comprehensive template with:
1. Real percentages from index files
2. All implemented items (✅)
3. All gaps (⚠️/❌) with `/gap-planning` commands
4. Recommended next steps based on priorities
**Progress Bar Reference**:
```
100% = [██████████] | 50% = [█████░░░░░]
90% = [█████████░] | 40% = [████░░░░░░]
80% = [████████░░] | 30% = [███░░░░░░░]
70% = [███████░░░] | 20% = [██░░░░░░░░]
60% = [██████░░░░] | 10% = [█░░░░░░░░░]
```
---
**Status Icons**: ✅ Complete | ⚠️ Partial | ❌ Missing | `-` N/A
## Layer-Specific Parameters
When a layer parameter is provided, show detailed view for that layer:
| Parameter | Shows |
|-----------|-------|
| `design` | All 17 features: SPEC, API, STATUS, Mockups status |
| `design mockup` | Mockup-specific: Figma links, Stitch prompts, design-tokens |
| `design spec` | Specification status for all features |
| `server` | All 11 endpoint categories with endpoint counts |
| `client` | All services (13) and repositories (17) |
| `client network` | Network services only |
| `client data` | Repositories only |
| `feature` | All 23 modules with screens, ViewModels, DI |
| `feature [name]` | Single feature: all layers |
| `platform` | All 4 platforms with build commands |
| `platform [name]` | Single platform details |
| `testing` | Testing coverage across all layers |
| `testing [layer]` | Testing for specific layer (design/client/feature/platform) |
---
## Feature Reference
@ -132,10 +300,29 @@ Read template from `claude-product-cycle/templates/gap-analysis/` and replace pl
| 16 | client-charge | features/client-charge/ | feature/user-profile/ |
| 17 | dashboard | features/dashboard/ | feature/dashboard/ |
---
## Output Rules
1. Read actual files - don't assume
2. Calculate real percentages
3. Use progress bars for visibility
4. List specific gaps with file paths
5. Suggest next command (gap-planning or implement)
1. **Read index files only** - Never scan directories when index files exist
2. **Show everything** - All implemented + all gaps in one view
3. **Include all `/gap-planning` commands** - For every gap found
4. **Use progress bars** - Visual at-a-glance status
5. **Prioritize gaps** - P0 → P1 → P2
6. **Show recommended next steps** - Based on current gaps
7. **NO interactive questions** - Comprehensive view, user decides
---
## Progress Bar Reference
```
100% = [██████████] | 50% = [█████░░░░░]
95% = [█████████▌] | 40% = [████░░░░░░]
90% = [█████████░] | 30% = [███░░░░░░░]
80% = [████████░░] | 20% = [██░░░░░░░░]
70% = [███████░░░] | 10% = [█░░░░░░░░░]
60% = [██████░░░░] | 0% = [░░░░░░░░░░]
```
**Status Icons**: ✅ Complete | ⚠️ Partial | ❌ Missing | `-` N/A

View File

@ -1,154 +1,346 @@
# Gap Planning Command
Brief entry point to plan implementation tasks. Creates step-by-step plans that persist across sessions.
Creates step-by-step implementation plans for identified gaps. Runs on O(1) by reading index files.
## Usage
```
/gap-planning # Brief overview (what needs planning)
/gap-planning design # Plan design layer work
/gap-planning design mockup # Plan mockup generation specifically
/gap-planning design spec # Plan specification work
/gap-planning server # Plan server layer work
/gap-planning client # Plan client layer work
/gap-planning # Show ALL gaps with ALL plan commands
/gap-planning design # Plan all design layer work
/gap-planning design mockup # Plan mockup generation (10 features)
/gap-planning design spec # Plan specification updates
/gap-planning server # Plan server documentation
/gap-planning client # Plan all client layer work
/gap-planning client network # Plan network services
/gap-planning client data # Plan repositories
/gap-planning feature # Plan feature layer work
/gap-planning feature # Plan all feature layer work
/gap-planning feature [name] # Plan specific feature
/gap-planning platform # Plan platform layer work
/gap-planning platform android # Plan Android-specific work
/gap-planning [feature-name] # Plan specific feature (all layers)
/gap-planning platform # Plan all platform work
/gap-planning platform web # Plan web stabilization
/gap-planning testing # Plan all testing work
/gap-planning testing client # Plan client layer tests
/gap-planning testing feature # Plan feature layer tests (VM + UI)
/gap-planning testing platform # Plan E2E + screenshot tests
/gap-planning testing [feature] # Plan tests for specific feature
/gap-planning [feature-name] # Plan specific feature (all 5 layers)
```
## Brief Overview Output (No Parameters)
---
When `/gap-planning` is called without parameters, show a **brief summary**:
## Comprehensive Output (No Parameters)
When `/gap-planning` is called without parameters, show **ALL gaps with ALL implementation plans**:
```
## Gap Planning - What Needs Work
╔══════════════════════════════════════════════════════════════════════════════╗
║ MIFOS MOBILE - GAP PLANNING (O(1) Lookup) ║
║ All Gaps → All Plans → You Choose ║
╠══════════════════════════════════════════════════════════════════════════════╣
| Layer | Gaps | Priority | Next Plan |
|-------|:----:|:--------:|-----------|
| Design | mockups (16) | P1 | /gap-planning design mockup |
| Server | - | - | - |
| Client | 1 service | P2 | /gap-planning client |
| Feature | dashboard | P0 | /gap-planning feature dashboard |
| Platform | web fixes | P2 | /gap-planning platform web |
## Current Gaps Overview
**Current Focus**: Design Layer → Mockup Generation (Phase 2)
**Next Step**: Run `/gap-planning design mockup` to get step-by-step tasks.
| Layer | Gaps | Priority | Status |
|-------|:----:|:--------:|--------|
| Design | 10 mockups | P1 | Ready to plan |
| Server | 0 | - | ✅ Complete |
| Client | 0 | - | ✅ Complete |
| Feature | 0 | - | ✅ Complete |
| Platform | 1 (web) | P2 | Ready to plan |
---
## 📋 ALL AVAILABLE PLANS
### P0 - Critical (Blocks Other Work)
| Gap | Plan Command | Tasks | Effort |
|-----|--------------|:-----:|:------:|
| (none currently) | - | - | - |
### P1 - High Priority (User-Facing)
| # | Gap | Plan Command | Tasks | Effort |
|:-:|-----|--------------|:-----:|:------:|
| 1 | Missing mockups (10 features) | `/gap-planning design mockup` | 30 | L |
| 2 | Missing design-tokens (9 features) | `/gap-planning design mockup` | 18 | M |
**Design Mockup Tasks Preview**:
```
Features needing mockups:
1. accounts → 3 screens (List, Detail, Transactions)
2. beneficiary → 4 screens (List, Add, Edit, Detail)
3. dashboard → 1 screen (Overview)
4. home → 2 screens (Home, Profile)
5. loan-account → 4 screens (List, Detail, Schedule, Summary)
6. notification → 1 screen (List)
7. recent-transaction → 1 screen (List)
8. savings-account → 4 screens (List, Detail, Update, Withdraw)
9. share-account → 2 screens (List, Detail)
10. transfer → 2 screens (Form, Confirmation)
Run `/gap-planning design mockup` for step-by-step tasks.
```
## Prerequisites
### P2 - Nice to Have (Polish)
Run `/gap-analysis` first to identify gaps, or run `/gap-planning` directly to see what needs work.
| # | Gap | Plan Command | Tasks | Effort |
|:-:|-----|--------------|:-----:|:------:|
| 1 | Web experimental | `/gap-planning platform web` | 5 | M |
## Instructions
**Web Platform Tasks Preview**:
```
1. Fix Kotlin/JS compilation warnings
2. Add CORS handling for production
3. Implement WebSocket fallback
4. Optimize bundle size
5. Add Safari compatibility fixes
### Step 1: Determine Template
Run `/gap-planning platform web` for step-by-step tasks.
```
**Layer Parameters**:
| Parameter | Template | Plans For |
|-----------|----------|-----------|
| (none) | Brief summary | What needs planning |
| `design` | `layer-design.md` | Design layer (specs + mockups) |
| `server` | `layer-server.md` | Server documentation |
| `client` | `layer-client.md` | Network + Data layers |
| `feature` | `layer-feature.md` | Feature implementation |
| `platform` | `layer-platform.md` | Platform-specific work |
| `[name]` | `feature-*.md` | Specific feature |
### 🧪 Testing (Embedded in Layers)
**Sub-Section Parameters** (layer + sub-section):
| Parameters | Template | Plans For |
|------------|----------|-----------|
| `design mockup` | `subsection/design-mockup.md` | Mockup generation (Google Stitch) |
| `design spec` | `subsection/design-spec.md` | Specification updates |
| `design api` | `subsection/design-api.md` | API documentation |
| `client network` | `subsection/client-network.md` | Network services |
| `client data` | `subsection/client-data.md` | Repositories |
| `feature [name]` | `subsection/feature-single.md` | Single feature plan |
| `platform android` | `subsection/platform-android.md` | Android-specific |
| `platform ios` | `subsection/platform-ios.md` | iOS-specific |
| `platform desktop` | `subsection/platform-desktop.md` | Desktop-specific |
| `platform web` | `subsection/platform-web.md` | Web-specific |
| # | Gap | Plan Command | Tests | Effort |
|:-:|-----|--------------|:-----:|:------:|
| 1 | ViewModel tests (0/49) | `/gap-planning testing feature` | 200+ | L |
| 2 | UI tests (0/63 screens) | `/gap-planning testing feature` | 150+ | L |
| 3 | E2E tests (0/8 flows) | `/gap-planning testing platform` | 30+ | M |
| 4 | Screenshot tests (0/30) | `/gap-planning testing platform` | 60+ | M |
| 5 | Repository tests (partial) | `/gap-planning testing client` | 50+ | M |
### Step 2: For Feature Parameter
**Testing Priority by Feature**:
```
P0 - Core: auth, home, accounts, transfer
P1 - Accounts: beneficiary, loan, savings
P2 - Supporting: settings, notification, qr, passcode
P3 - Other: guarantor, location, dashboard
```
Determine gap type by checking if `feature/[name]/` exists:
→ Run `/gap-planning testing [feature]` for per-feature test plan.
| Condition | Gap Type | Template |
|-----------|----------|----------|
| Directory missing | New feature | `templates/gap-planning/feature-new.md` |
| Directory exists | v2.0 UI update | `templates/gap-planning/feature-v2.md` |
---
### Step 3: Read Required Files
## 🎯 QUICK START
| Layer | Files to Read |
|-------|---------------|
| Design | `design-spec-layer/STATUS.md`, feature STATUS.md files |
| Server | `server-layer/FINERACT_API.md`, feature API.md files |
| Client | `client-layer/LAYER_STATUS.md`, check `core/` |
| Feature | `feature-layer/LAYER_STATUS.md`, check `feature/` |
| Platform | Check `cmp-*/` modules |
| [name] | All design files + current implementation |
Pick a plan based on priority:
### Step 4: Fill Template
| Priority | Recommendation | Command |
|:--------:|----------------|---------|
| **P1** | Start with mockups | `/gap-planning design mockup` |
| **P2** | Then web platform | `/gap-planning platform web` |
Read template and replace placeholders with:
- Actual gaps from status files
- Concrete task lists
- Real file paths
- Code sketches
- Verification steps
Or jump directly to implementation:
## Template Reference
| Target | Command |
|--------|---------|
| Single feature mockup | `/design [feature-name]` |
| Feature implementation | `/implement [feature-name]` |
| Verify existing | `/verify [feature-name]` |
---
## 🔄 WORKFLOW
```
templates/gap-planning/
├── dashboard.md # Full planning dashboard
├── layer-design.md # Design layer plan
├── layer-server.md # Server layer plan
├── layer-client.md # Client layer plan
├── layer-feature.md # Feature layer plan
├── layer-platform.md # Platform layer plan
├── feature-new.md # New feature creation
├── feature-v2.md # v2.0 UI upgrade
└── task-template.md # Individual task format
/gap-analysis → See all status (O(1) comprehensive view)
/gap-planning → See all plans (this view)
/gap-planning [target] → Get detailed step-by-step tasks
/implement [target] → Execute the plan
/verify [target] → Confirm completion
/gap-analysis → Updated status (loop back)
```
╚══════════════════════════════════════════════════════════════════════════════╝
```
---
## Detailed Plans (With Parameter)
When a specific target is provided, show the **detailed step-by-step plan**.
### Design Mockup Plan (`/gap-planning design mockup`)
```
## Design Mockup Generation Plan
**Target**: 10 features needing mockups
**Effort**: Large (30 tasks across 10 features)
**Tool**: Google Stitch / Figma
### Features & Tasks
| # | Feature | Screens | Tasks | Priority |
|:-:|---------|:-------:|:-----:|:--------:|
| 1 | accounts | 3 | 6 | P1 |
| 2 | beneficiary | 4 | 8 | P1 |
| 3 | dashboard | 1 | 2 | P0 |
| 4 | home | 2 | 4 | P1 |
| 5 | loan-account | 4 | 8 | P1 |
| 6 | notification | 1 | 2 | P2 |
| 7 | recent-transaction | 1 | 2 | P2 |
| 8 | savings-account | 4 | 8 | P1 |
| 9 | share-account | 2 | 4 | P2 |
| 10 | transfer | 2 | 4 | P1 |
### Per-Feature Tasks
For each feature:
1. Read SPEC.md to understand screens
2. Read API.md to understand data
3. Generate PROMPTS_STITCH.md for Google Stitch
4. Generate mockup images
5. Create design-tokens.json
6. Update FIGMA_LINKS.md with URLs
### Execution Commands
| Feature | Command |
|---------|---------|
| dashboard (P0) | `/design dashboard mockup` |
| accounts | `/design accounts mockup` |
| beneficiary | `/design beneficiary mockup` |
| home | `/design home mockup` |
| loan-account | `/design loan-account mockup` |
| savings-account | `/design savings-account mockup` |
| transfer | `/design transfer mockup` |
| notification | `/design notification mockup` |
| recent-transaction | `/design recent-transaction mockup` |
| share-account | `/design share-account mockup` |
### Verification
After each feature:
- [ ] PROMPTS_STITCH.md exists
- [ ] Mockup images generated
- [ ] design-tokens.json created
- [ ] FIGMA_LINKS.md updated
- [ ] MOCKUPS_INDEX.md updated
```
### Platform Web Plan (`/gap-planning platform web`)
```
## Web Platform Stabilization Plan
**Target**: Move web from experimental to stable
**Effort**: Medium (5 tasks)
**Module**: cmp-web
### Current Status
| Issue | Impact | Fix |
|-------|--------|-----|
| Kotlin/JS warnings | Build noise | Suppress/fix |
| CORS in production | API blocked | Server headers |
| WebSocket issues | Real-time fails | Polling fallback |
| Large bundle | Slow load | Tree shaking |
| Safari compat | 15% users | Polyfills |
### Tasks
1. **Fix compilation warnings**
- File: `cmp-web/build.gradle.kts`
- Action: Add suppressions or fix warnings
2. **CORS configuration**
- File: Server config (Fineract)
- Action: Add Access-Control-Allow-Origin headers
3. **WebSocket fallback**
- File: `cmp-shared/.../network/`
- Action: Implement polling when WebSocket fails
4. **Bundle optimization**
- File: `cmp-web/build.gradle.kts`
- Action: Enable tree shaking, code splitting
5. **Safari compatibility**
- File: `cmp-web/src/jsMain/resources/`
- Action: Add polyfills for missing APIs
### Verification
- [ ] `./gradlew :cmp-web:jsBrowserProductionWebpack` builds clean
- [ ] App loads in Safari
- [ ] API calls work in production
- [ ] Bundle size < 2MB
```
---
## Instructions for Claude
### Step 1: Read O(1) Index Files
Read these files for gap information:
| Need | Index File | Path |
|------|------------|------|
| Design gaps | MOCKUPS_INDEX.md | `design-spec-layer/MOCKUPS_INDEX.md` |
| Feature gaps | MODULES_INDEX.md | `feature-layer/MODULES_INDEX.md` |
| Client gaps | FEATURE_MAP.md | `client-layer/FEATURE_MAP.md` |
| Platform gaps | LAYER_STATUS.md | `platform-layer/LAYER_STATUS.md` |
### Step 2: Identify Gaps
From index files, find items marked ⚠️ or ❌:
- Design: Features missing mockups, design-tokens
- Client: Missing services or repositories
- Feature: Missing screens or ViewModels
- Platform: Experimental or broken builds
### Step 3: Generate Plans
For each gap found:
1. Determine priority (P0/P1/P2)
2. List specific tasks
3. Estimate effort (S/M/L)
4. Provide execution commands
5. Add verification checklist
### Step 4: Output Format
- **No parameters**: Show all gaps + all plan summaries
- **With layer**: Show detailed plan for that layer
- **With feature**: Show detailed plan for that feature
---
## Priority Guidelines
| Priority | Criteria | Examples |
|----------|----------|----------|
| P0 | Critical - blocks other work | Missing feature module |
| P1 | High value - user-facing | v2.0 UI, new screens |
| P2 | Polish - nice to have | Animations, dark mode |
| P1 | High value - user-facing | v2.0 UI, mockups |
| P2 | Polish - nice to have | Animations, web fixes |
## Effort Guidelines
| Effort | Time | Scope |
|--------|------|-------|
| S | <1 hour | Single file, styling |
| M | 1-4 hours | Multiple files, component |
| L | >4 hours | Feature module, architecture |
| Effort | Scope | Tasks |
|--------|-------|:-----:|
| S | Single file change | 1-3 |
| M | Multiple files, one area | 4-10 |
| L | Feature-wide or cross-cutting | 10+ |
---
## Output Rules
1. Read actual status files first
2. Create prioritized task list (P0 → P1 → P2)
3. Include specific file paths
4. Provide code sketches (not full code)
5. Add verification steps
6. End with next command suggestion
## Workflow
```
/gap-analysis → Identify gaps
/gap-planning [target] → Create task list (this command)
/implement [target] → Execute tasks
/verify [target] → Confirm completion
```
1. **Read index files only** - Use O(1) lookup
2. **Show all gaps** - No hidden information
3. **Show all commands** - For every gap
4. **Include effort estimates** - S/M/L
5. **Prioritize** - P0 → P1 → P2
6. **Provide verification** - Checklist for each plan
7. **NO interactive questions** - Show everything, user decides

View File

@ -0,0 +1,197 @@
# Verify Tests Command
Run and verify tests for features across the project.
## Usage
```
/verify-tests # Run all tests, show status
/verify-tests auth # Run auth feature tests
/verify-tests auth unit # Run auth ViewModel tests only
/verify-tests auth ui # Run auth UI tests only
/verify-tests auth integration # Run auth integration tests
/verify-tests auth screenshot # Run auth screenshot tests
/verify-tests client # Run all client layer tests
/verify-tests feature # Run all feature layer tests
/verify-tests platform # Run all platform tests
```
---
## Output Format
```
╔══════════════════════════════════════════════════════════════════════════════╗
║ VERIFY TESTS - [target] ║
╠══════════════════════════════════════════════════════════════════════════════╣
## Test Execution
| Type | Command | Tests | Passed | Failed | Status |
|------|---------|:-----:|:------:|:------:|:------:|
| Unit | `./gradlew :feature:auth:test` | 45 | 45 | 0 | ✅ |
| UI | `./gradlew :feature:auth:connectedDebugAndroidTest` | 25 | 23 | 2 | ⚠️ |
| Integration | `./gradlew :cmp-android:connectedDebugAndroidTest` | 8 | 8 | 0 | ✅ |
| Screenshot | `./gradlew :core:designsystem:compareRoborazziDebug` | 12 | 12 | 0 | ✅ |
## Failed Tests
| Test | Error | File |
|------|-------|------|
| LoginScreenTest.testErrorState | AssertionError | LoginScreenTest.kt:45 |
| LoginScreenTest.testLoading | TimeoutException | LoginScreenTest.kt:32 |
## Coverage Summary
| Component | Coverage | Target | Status |
|-----------|:--------:|:------:|:------:|
| ViewModel | 85% | 80% | ✅ |
| Screen | 72% | 60% | ✅ |
| Repository | 90% | 80% | ✅ |
---
## Next Steps
1. Fix failing tests: `/gap-planning auth testing`
2. Increase coverage: Add tests for uncovered paths
3. Re-run: `/verify-tests auth`
╚══════════════════════════════════════════════════════════════════════════════╝
```
---
## Test Commands Reference
### Unit Tests (ViewModel + Repository)
```bash
# All unit tests
./gradlew test
# Specific module
./gradlew :feature:auth:test
./gradlew :core:data:test
# With coverage
./gradlew test jacocoTestReport
```
### UI Tests (Compose)
```bash
# All UI tests (requires emulator/device)
./gradlew connectedDebugAndroidTest
# Specific feature
./gradlew :feature:auth:connectedDebugAndroidTest
```
### Integration Tests (E2E)
```bash
# Full E2E tests
./gradlew :cmp-android:connectedDebugAndroidTest
# Specific test class
./gradlew :cmp-android:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.mifos.mobile.AuthFlowTest
```
### Screenshot Tests (Roborazzi)
```bash
# Record golden images
./gradlew :core:designsystem:recordRoborazziDebug
# Compare against golden images
./gradlew :core:designsystem:compareRoborazziDebug
# View differences
open build/reports/roborazzi/
```
---
## Instructions for Claude
### Step 1: Determine Test Scope
| Parameter | Test Type | Gradle Command |
|-----------|-----------|----------------|
| (none) | All tests | `./gradlew test connectedDebugAndroidTest` |
| `[feature]` | Feature tests | `./gradlew :feature:[name]:test` |
| `[feature] unit` | ViewModel only | `./gradlew :feature:[name]:test` |
| `[feature] ui` | Screen tests | `./gradlew :feature:[name]:connectedDebugAndroidTest` |
| `[feature] integration` | E2E flow | `./gradlew :cmp-android:connectedDebugAndroidTest` |
| `[feature] screenshot` | Visual | `./gradlew :core:designsystem:compareRoborazziDebug` |
| `client` | Repositories | `./gradlew :core:data:test` |
| `feature` | All ViewModels | `./gradlew feature:test` |
| `platform` | All E2E | `./gradlew :cmp-android:connectedDebugAndroidTest` |
### Step 2: Execute Tests
Run the appropriate Gradle command and capture output.
### Step 3: Parse Results
From Gradle output, extract:
- Total tests run
- Tests passed
- Tests failed
- Failed test names and errors
- Coverage percentages (if available)
### Step 4: Generate Report
Display results in the formatted output above.
### Step 5: Suggest Next Steps
Based on results:
- If all pass: "All tests passing. Coverage: X%"
- If failures: List fixes needed with file paths
- If low coverage: Suggest adding tests
---
## Feature Test Mapping
| Feature | Unit Test Path | UI Test Path |
|---------|----------------|--------------|
| auth | `feature/auth/src/commonTest/` | `feature/auth/src/androidInstrumentedTest/` |
| home | `feature/home/src/commonTest/` | `feature/home/src/androidInstrumentedTest/` |
| accounts | `feature/account/src/commonTest/` | `feature/account/src/androidInstrumentedTest/` |
| beneficiary | `feature/beneficiary/src/commonTest/` | `feature/beneficiary/src/androidInstrumentedTest/` |
| loan-account | `feature/loan-account/src/commonTest/` | `feature/loan-account/src/androidInstrumentedTest/` |
| savings-account | `feature/savings-account/src/commonTest/` | `feature/savings-account/src/androidInstrumentedTest/` |
| transfer | `feature/transfer-process/src/commonTest/` | `feature/transfer-process/src/androidInstrumentedTest/` |
| notification | `feature/notification/src/commonTest/` | `feature/notification/src/androidInstrumentedTest/` |
| settings | `feature/settings/src/commonTest/` | `feature/settings/src/androidInstrumentedTest/` |
| qr | `feature/qr-code/src/commonTest/` | `feature/qr-code/src/androidInstrumentedTest/` |
| guarantor | `feature/guarantor/src/commonTest/` | `feature/guarantor/src/androidInstrumentedTest/` |
| passcode | `libs/mifos-passcode/src/commonTest/` | `libs/mifos-passcode/src/androidInstrumentedTest/` |
| location | `feature/location/src/commonTest/` | `feature/location/src/androidInstrumentedTest/` |
| user-profile | `feature/user-profile/src/commonTest/` | `feature/user-profile/src/androidInstrumentedTest/` |
---
## Coverage Targets
| Component | Minimum | Target | Excellent |
|-----------|:-------:|:------:|:---------:|
| ViewModel | 60% | 80% | 90%+ |
| Repository | 70% | 80% | 90%+ |
| Screen | 40% | 60% | 80%+ |
| Integration | - | 8 flows | 15+ flows |
| Screenshot | - | 30 golden | 60+ golden |
---
## Related Commands
- `/gap-analysis testing` - View testing status
- `/gap-planning testing [layer]` - Plan test implementation
- `/verify [feature]` - Verify implementation vs spec
ARGUMENTS: $ARGUMENTS

View File

@ -0,0 +1,169 @@
# Feature → Client Components Map
> **13 services** | **17 repositories** | **2 DI modules**
---
## Feature to Components (O(1) Lookup)
| Feature | Services | Repositories | Notes |
|---------|----------|--------------|-------|
| auth | AuthenticationService, RegistrationService, UserDetailsService | UserAuthRepository, UserDataRepository | Login, Register, Password |
| home | ClientService, NotificationService | HomeRepository, NotificationRepository | Dashboard, Profile |
| accounts | ClientService | AccountsRepository | Account listing |
| loan-account | LoanAccountsListService, GuarantorService | LoanRepository, GuarantorRepository, ReviewLoanApplicationRepository | Loan details |
| savings-account | SavingAccountsListService | SavingsAccountRepository | Savings details |
| share-account | ShareAccountService | ShareAccountRepository | Share details |
| beneficiary | BeneficiaryService | BeneficiaryRepository | TPT beneficiaries |
| transfer | ThirdPartyTransferService, SavingAccountsListService | TransferRepository, ThirdPartyTransferRepository | Fund transfer |
| notification | NotificationService | NotificationRepository | Push notifications |
| recent-transaction | RecentTransactionsService | RecentTransactionRepository | Transaction history |
| client-charge | ClientChargeService | ClientChargeRepository | Client charges |
| settings | UserDetailsService | UserDetailRepository | Password change |
| guarantor | GuarantorService | GuarantorRepository | Loan guarantors |
| user-profile | ClientService | ClientRepository, UserDetailRepository | Profile display |
---
## Services Inventory (13)
| Service | File | Key Methods |
|---------|------|-------------|
| AuthenticationService | AuthenticationService.kt | `authenticate()` |
| RegistrationService | RegistrationService.kt | `registerUser()`, `verifyUser()` |
| ClientService | ClientService.kt | `clients()`, `getClientForId()`, `getClientAccounts()`, `getClientImage()` |
| LoanAccountsListService | LoanAccountsListService.kt | `getLoanWithAssociations()`, `createLoansAccount()`, `withdrawLoanAccount()` |
| SavingAccountsListService | SavingAccountsListService.kt | `getSavingsWithAssociations()`, `makeTransfer()`, `submitSavingAccountApplication()` |
| BeneficiaryService | BeneficiaryService.kt | `beneficiaryList()`, `createBeneficiary()`, `updateBeneficiary()`, `deleteBeneficiary()` |
| ThirdPartyTransferService | ThirdPartyTransferService.kt | `accountTransferTemplate()`, `makeTransfer()` |
| NotificationService | NotificationService.kt | `getUserNotificationId()`, `registerNotification()`, `updateRegisterNotification()` |
| RecentTransactionsService | RecentTransactionsService.kt | `getRecentTransactionsList()` |
| UserDetailsService | UserDetailsService.kt | `updateAccountPassword()` |
| ShareAccountService | ShareAccountService.kt | `getShareProducts()`, `submitShareApplication()`, `getShareAccountDetails()` |
| GuarantorService | GuarantorService.kt | `getGuarantorList()`, `getGuarantorTemplate()`, `createGuarantor()` |
| ClientChargeService | ClientChargeService.kt | `getClientChargeList()`, `getChargeList()` |
---
## Repositories Inventory (17)
| Repository | Implementation | Depends On |
|------------|----------------|------------|
| AccountsRepository | AccountsRepositoryImp | ClientService |
| BeneficiaryRepository | BeneficiaryRepositoryImp | BeneficiaryService |
| ClientChargeRepository | ClientChargeRepositoryImp | ClientChargeService |
| ClientRepository | ClientRepositoryImp | ClientService |
| GuarantorRepository | GuarantorRepositoryImp | GuarantorService |
| HomeRepository | HomeRepositoryImp | ClientService |
| LoanRepository | LoanRepositoryImp | LoanAccountsListService |
| NotificationRepository | NotificationRepositoryImp | NotificationService |
| RecentTransactionRepository | RecentTransactionRepositoryImp | RecentTransactionsService |
| ReviewLoanApplicationRepository | ReviewLoanApplicationRepositoryImpl | LoanAccountsListService |
| SavingsAccountRepository | SavingsAccountRepositoryImp | SavingAccountsListService |
| ShareAccountRepository | ShareAccountRepositoryImp | ShareAccountService |
| ThirdPartyTransferRepository | ThirdPartyTransferRepositoryImp | ThirdPartyTransferService |
| TransferRepository | TransferRepositoryImp | SavingAccountsListService |
| UserAuthRepository | UserAuthRepositoryImp | AuthenticationService |
| UserDataRepository | AuthenticationUserRepository | DataStore |
| UserDetailRepository | UserDetailRepositoryImp | UserDetailsService |
---
## DI Modules
| Module | Location | Provides |
|--------|----------|----------|
| NetworkModule | `core/network/di/NetworkModule.kt` | HttpClient, KtorfitClient, DataManager |
| RepositoryModule | `core/data/di/RepositoryModule.kt` | All 17 repositories as singletons |
---
## Reverse Lookup: Service → Features
| Service | Used By Features |
|---------|------------------|
| AuthenticationService | auth |
| RegistrationService | auth |
| ClientService | home, accounts, user-profile |
| LoanAccountsListService | loan-account |
| SavingAccountsListService | savings-account, transfer |
| BeneficiaryService | beneficiary |
| ThirdPartyTransferService | transfer |
| NotificationService | home, notification |
| RecentTransactionsService | recent-transaction |
| UserDetailsService | auth, settings |
| ShareAccountService | share-account |
| GuarantorService | loan-account, guarantor |
| ClientChargeService | client-charge |
---
## Reverse Lookup: Repository → Features
| Repository | Used By Features |
|------------|------------------|
| AccountsRepository | accounts |
| BeneficiaryRepository | beneficiary |
| ClientChargeRepository | client-charge |
| ClientRepository | user-profile |
| GuarantorRepository | loan-account, guarantor |
| HomeRepository | home |
| LoanRepository | loan-account |
| NotificationRepository | home, notification |
| RecentTransactionRepository | recent-transaction |
| ReviewLoanApplicationRepository | loan-account |
| SavingsAccountRepository | savings-account |
| ShareAccountRepository | share-account |
| ThirdPartyTransferRepository | transfer |
| TransferRepository | transfer |
| UserAuthRepository | auth |
| UserDataRepository | auth |
| UserDetailRepository | settings, user-profile |
---
## O(1) File Access
| Need | Path |
|------|------|
| Service | `core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/services/[Name]Service.kt` |
| Repository Interface | `core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repository/[Name]Repository.kt` |
| Repository Impl | `core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repository/[Name]RepositoryImp.kt` |
| Network DI | `core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt` |
| Data DI | `core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/di/RepositoryModule.kt` |
---
## Architecture Flow
```
Feature (ViewModel)
Repository (Interface)
RepositoryImpl (Implementation)
DataManager (Service Accessor)
Service (Ktorfit API)
HttpClient (Ktor)
```
---
## Related Files
- [LAYER_STATUS.md](LAYER_STATUS.md) - Implementation status
- [LAYER_GUIDE.md](LAYER_GUIDE.md) - Architecture patterns
- [server-layer/API_INDEX.md](../server-layer/API_INDEX.md) - API endpoints
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| New service added | Add to Services Inventory |
| New repository added | Add to Repositories Inventory + Reverse Lookup |
| Feature uses new service | Update Feature to Components table |

View File

@ -0,0 +1,192 @@
# Client Layer - Testing Status
> Repository and service testing documentation
---
## Overview
The client layer handles data operations. Testing ensures repositories correctly transform API responses and handle errors.
---
## Current State
| Component | Total | Tested | Coverage |
|-----------|:-----:|:------:|:--------:|
| Services | 13 | 0 | 0% |
| Repositories | 17 | 14 | 82% |
| DataStore | 3 | 3 | 100% |
---
## Testing Scope
| Component | Test Type | Purpose |
|-----------|-----------|---------|
| Services | Unit Tests | Verify API call construction |
| Repositories | Unit Tests | Verify data transformation |
| DataStore | Unit Tests | Verify local persistence |
---
## Repository Testing Status
### Existing Tests (14)
| # | Repository | Tests | File |
|:-:|------------|:-----:|------|
| 1 | AccountsRepository | 2 | `AccountsRepositoryTest.kt` |
| 2 | BeneficiaryRepository | 1 | `BeneficiaryRepositoryTest.kt` |
| 3 | ClientChargeRepository | 1 | `ClientChargeRepositoryTest.kt` |
| 4 | ClientRepository | 1 | `ClientRepositoryTest.kt` |
| 5 | GuarantorRepository | 1 | `GuarantorRepositoryTest.kt` |
| 6 | HomeRepository | 1 | `HomeRepositoryTest.kt` |
| 7 | LoanRepository | 1 | `LoanRepositoryTest.kt` |
| 8 | NotificationRepository | 1 | `NotificationRepositoryTest.kt` |
| 9 | RecentTransactionRepository | 1 | `RecentTransactionRepositoryTest.kt` |
| 10 | SavingsAccountRepository | 1 | `SavingsAccountRepositoryTest.kt` |
| 11 | ShareAccountRepository | 1 | `ShareAccountRepositoryTest.kt` |
| 12 | ThirdPartyTransferRepository | 1 | `ThirdPartyTransferRepositoryTest.kt` |
| 13 | TransferRepository | 1 | `TransferRepositoryTest.kt` |
| 14 | UserAuthRepository | - | (needs tests) |
**Location**: `core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repository/`
---
## Repository Test Coverage Matrix
| # | Repository | Success | Error | Empty | Offline | Status |
|:-:|------------|:-------:|:-----:|:-----:|:-------:|:------:|
| 1 | AccountsRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 2 | BeneficiaryRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 3 | ClientChargeRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 4 | ClientRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 5 | GuarantorRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 6 | HomeRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 7 | LoanRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 8 | NotificationRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 9 | RecentTransactionRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 10 | SavingsAccountRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 11 | ShareAccountRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 12 | ThirdPartyTransferRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 13 | TransferRepository | ✅ | ⬜ | ⬜ | ⬜ | Partial |
| 14 | UserAuthRepository | ⬜ | ⬜ | ⬜ | ⬜ | Not Started |
| 15 | UserDataRepository | ⬜ | ⬜ | ⬜ | ⬜ | Not Started |
| 16 | UserPreferencesRepository | ⬜ | ⬜ | ⬜ | ⬜ | Not Started |
| 17 | RegistrationRepository | ⬜ | ⬜ | ⬜ | ⬜ | Not Started |
**Legend**: ✅ Tested | ⬜ Not Tested
---
## Missing Tests
### Priority 1 - Auth Flow
- [ ] UserAuthRepository (login, logout, token refresh)
- [ ] UserPreferencesRepository (user settings)
- [ ] RegistrationRepository (sign up flow)
### Priority 2 - Error Handling
- [ ] All repositories: error scenarios
- [ ] All repositories: empty response handling
- [ ] All repositories: offline caching
### Priority 3 - Edge Cases
- [ ] Pagination handling
- [ ] Concurrent request handling
- [ ] Cache invalidation
---
## Fake Repository Implementations
For ViewModel testing, create fake repositories:
**Location**: `core/testing/src/commonMain/kotlin/org/mifos/mobile/core/testing/fake/`
```kotlin
class FakeHomeRepository : HomeRepository {
private var result: DataState<HomeData> = DataState.Loading
fun setResult(result: DataState<HomeData>) {
this.result = result
}
override suspend fun getHomeData(): Flow<DataState<HomeData>> = flow {
emit(result)
}
}
```
### Fake Repositories Needed
| # | Fake Repository | For Testing |
|:-:|-----------------|-------------|
| 1 | FakeHomeRepository | HomeViewModel |
| 2 | FakeUserAuthRepository | LoginViewModel |
| 3 | FakeAccountsRepository | AccountsViewModel |
| 4 | FakeBeneficiaryRepository | BeneficiaryViewModel |
| 5 | FakeLoanRepository | LoanViewModel |
| 6 | FakeSavingsAccountRepository | SavingsViewModel |
| 7 | FakeTransferRepository | TransferViewModel |
| 8 | FakeNotificationRepository | NotificationViewModel |
| 9 | FakeSettingsRepository | SettingsViewModel |
---
## Test Fixtures
**Location**: `core/testing/src/commonMain/kotlin/org/mifos/mobile/core/testing/fixture/`
```kotlin
object ClientAccountsFixture {
fun create(
savingsAccounts: List<SavingAccount> = emptyList(),
loanAccounts: List<LoanAccount> = emptyList(),
shareAccounts: List<ShareAccount> = emptyList()
) = ClientAccounts(
savingsAccounts = savingsAccounts,
loanAccounts = loanAccounts,
shareAccounts = shareAccounts
)
fun withSavings() = create(
savingsAccounts = listOf(SavingAccountFixture.create())
)
}
```
---
## Implementation Priority
| Phase | Scope | Tests |
|:-----:|-------|:-----:|
| 1 | Auth repositories | 15 |
| 2 | Error handling (all repos) | 34 |
| 3 | Fake implementations | 9 |
| 4 | Test fixtures | 12 |
---
## Commands
```bash
# Run client layer tests
./gradlew :core:data:test
# Check test coverage
/gap-analysis client testing
# Plan missing tests
/gap-planning client testing
```
---
## Related Files
- [FEATURE_MAP.md](./FEATURE_MAP.md) - Feature to service mapping
- [LAYER_STATUS.md](./LAYER_STATUS.md) - Implementation status

View File

@ -0,0 +1,116 @@
# Features Index - O(1) Lookup
> **18 features** | All have SPEC + API + STATUS
---
## Quick Lookup
| # | Feature | Dir | SPEC | API | STATUS | Mockups |
|:-:|---------|-----|:----:|:---:|:------:|:-------:|
| 1 | accounts | features/accounts/ | ✅ | ✅ | ✅ | ⚠️ |
| 2 | auth | features/auth/ | ✅ | ✅ | ✅ | ✅ |
| 3 | beneficiary | features/beneficiary/ | ✅ | ✅ | ✅ | ⚠️ |
| 4 | client-charge | features/client-charge/ | ✅ | ✅ | ✅ | ⚠️ |
| 5 | dashboard | features/dashboard/ | ✅ | ✅ | ✅ | ⚠️ |
| 6 | guarantor | features/guarantor/ | ✅ | ✅ | ✅ | ⚠️ |
| 7 | home | features/home/ | ✅ | ✅ | ✅ | ⚠️ |
| 8 | loan-account | features/loan-account/ | ✅ | ✅ | ✅ | ⚠️ |
| 9 | location | features/location/ | ✅ | ✅ | ✅ | ⚠️ |
| 10 | notification | features/notification/ | ✅ | ✅ | ✅ | ⚠️ |
| 11 | passcode | features/passcode/ | ✅ | ✅ | ✅ | ⚠️ |
| 12 | qr | features/qr/ | ✅ | ✅ | ✅ | ⚠️ |
| 13 | recent-transaction | features/recent-transaction/ | ✅ | ✅ | ✅ | ⚠️ |
| 14 | savings-account | features/savings-account/ | ✅ | ✅ | ✅ | ⚠️ |
| 15 | settings | features/settings/ | ✅ | ✅ | ✅ | ⚠️ |
| 16 | share-account | features/share-account/ | ✅ | ✅ | ✅ | ⚠️ |
| 17 | transfer | features/transfer/ | ✅ | ✅ | ✅ | ⚠️ |
**Legend**: ✅ Complete | ⚠️ Partial | ❌ Missing
---
## O(1) File Access
| Need | Path |
|------|------|
| Specification | `features/[name]/SPEC.md` |
| API Endpoints | `features/[name]/API.md` |
| Feature Status | `features/[name]/STATUS.md` |
| Mockups | `features/[name]/mockups/` |
| Design Tokens | `features/[name]/mockups/design-tokens.json` |
---
## Feature Categories
### Authentication & Security (3)
| Feature | Purpose |
|---------|---------|
| auth | Login, Registration, Password recovery |
| passcode | Biometric/PIN security |
| settings | Password change, security settings |
### Account Management (4)
| Feature | Purpose |
|---------|---------|
| accounts | Account overview, all account types |
| savings-account | Savings account details, operations |
| loan-account | Loan details, repayment schedule |
| share-account | Share account details |
### Transactions (3)
| Feature | Purpose |
|---------|---------|
| beneficiary | Third-party transfer beneficiaries |
| transfer | Fund transfers (self & TPT) |
| recent-transaction | Transaction history |
### Information & Utilities (5)
| Feature | Purpose |
|---------|---------|
| home | Dashboard, quick actions |
| notification | Push notifications |
| qr | QR code generation/scanning |
| location | Branch locator |
| client-charge | Client charges/fees |
### Supporting Features (2)
| Feature | Purpose |
|---------|---------|
| guarantor | Loan guarantor management |
| dashboard | Main navigation hub |
---
## Design Progress Summary
| Status | Count | Features |
|--------|:-----:|----------|
| Complete (all mockups) | 1 | auth |
| Partial (some mockups) | 16 | All others |
| Not Started | 0 | - |
---
## Related Files
- [MOCKUPS_INDEX.md](MOCKUPS_INDEX.md) - Mockup completion status
- [STATUS.md](STATUS.md) - Layer-wide status tracker
- [TOOL_CONFIG.md](TOOL_CONFIG.md) - Design tool configuration
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| New feature added | Add row to Quick Lookup |
| SPEC.md created | Update SPEC column to ✅ |
| API.md created | Update API column to ✅ |
| Mockups complete | Update Mockups column to ✅ |

View File

@ -0,0 +1,152 @@
# Mockups Index - O(1) Lookup
> **Figma**: 7/18 | **Stitch**: 11/18 | **Tokens**: 8/18
---
## Status Matrix
| Feature | FIGMA_LINKS | PROMPTS_FIGMA | PROMPTS_STITCH | design-tokens |
|---------|:-----------:|:-------------:|:--------------:|:-------------:|
| accounts | ❌ | ✅ | ✅ | ❌ |
| auth | ✅ | ✅ | ✅ | ✅ |
| beneficiary | ❌ | ✅ | ✅ | ❌ |
| client-charge | ✅ | ❌ | ❌ | ✅ |
| dashboard | ❌ | ✅ | ✅ | ✅ |
| guarantor | ✅ | ❌ | ❌ | ✅ |
| home | ❌ | ✅ | ✅ | ❌ |
| loan-account | ❌ | ✅ | ✅ | ❌ |
| location | ✅ | ❌ | ❌ | ✅ |
| notification | ❌ | ✅ | ✅ | ❌ |
| passcode | ✅ | ❌ | ❌ | ✅ |
| qr | ✅ | ❌ | ❌ | ✅ |
| recent-transaction | ❌ | ✅ | ✅ | ❌ |
| savings-account | ❌ | ✅ | ✅ | ❌ |
| settings | ✅ | ❌ | ❌ | ✅ |
| share-account | ❌ | ✅ | ✅ | ❌ |
| transfer | ❌ | ✅ | ✅ | ❌ |
**Legend**: ✅ Exists | ❌ Missing
---
## O(1) File Access
| Need | Path |
|------|------|
| Figma Links | `features/[name]/mockups/FIGMA_LINKS.md` |
| Figma Prompts | `features/[name]/mockups/PROMPTS_FIGMA.md` |
| Stitch Prompts | `features/[name]/mockups/PROMPTS_STITCH.md` |
| Design Tokens | `features/[name]/mockups/design-tokens.json` |
---
## Completion Summary
### Complete (All 4 Files)
| Feature | Status |
|---------|--------|
| auth | ✅ All mockup files present |
### Has Figma Links + Tokens (Need Prompts)
| Feature | Has | Needs |
|---------|-----|-------|
| client-charge | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
| guarantor | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
| location | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
| passcode | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
| qr | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
| settings | FIGMA_LINKS, design-tokens | PROMPTS_FIGMA, PROMPTS_STITCH |
### Has Prompts (Need Figma Links + Tokens)
| Feature | Has | Needs |
|---------|-----|-------|
| accounts | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| beneficiary | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| home | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| loan-account | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| notification | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| recent-transaction | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| savings-account | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| share-account | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
| transfer | PROMPTS_FIGMA, PROMPTS_STITCH | FIGMA_LINKS, design-tokens |
### Has Prompts + Tokens (Need Figma Links)
| Feature | Has | Needs |
|---------|-----|-------|
| dashboard | PROMPTS_FIGMA, PROMPTS_STITCH, design-tokens | FIGMA_LINKS |
---
## Gaps by File Type
### Need FIGMA_LINKS.md (11 features)
```
accounts, beneficiary, dashboard, home, loan-account,
notification, recent-transaction, savings-account,
share-account, transfer
```
### Need design-tokens.json (9 features)
```
accounts, beneficiary, home, loan-account, notification,
recent-transaction, savings-account, share-account, transfer
```
### Need PROMPTS_FIGMA.md (6 features)
```
client-charge, guarantor, location, passcode, qr, settings
```
### Need PROMPTS_STITCH.md (6 features)
```
client-charge, guarantor, location, passcode, qr, settings
```
---
## Design Tool Workflows
### Figma-First Workflow
```
1. Create in Figma
2. Add link to FIGMA_LINKS.md
3. Export design-tokens.json
```
### AI-Generation Workflow (Google Stitch)
```
1. Write PROMPTS_STITCH.md
2. Generate mockups
3. Export to Figma
4. Update FIGMA_LINKS.md
```
---
## Related Files
- [FEATURES_INDEX.md](FEATURES_INDEX.md) - Feature overview
- [TOOL_CONFIG.md](TOOL_CONFIG.md) - Tool configuration
- [STATUS.md](STATUS.md) - Layer status
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| Figma link added | Update FIGMA_LINKS column to ✅ |
| Prompts created | Update respective column to ✅ |
| Tokens exported | Update design-tokens column to ✅ |
| All 4 files done | Move to "Complete" section |

View File

@ -0,0 +1,125 @@
# Design Layer - Testing Status
> Testing specifications for design layer validation
---
## Overview
The design layer defines **what** should be tested. Each feature specification includes acceptance criteria that translate directly to test cases.
---
## Testing Scope
| Component | Test Type | Purpose |
|-----------|-----------|---------|
| SPEC.md | Contract Tests | Verify implementation matches specification |
| API.md | API Contract Tests | Verify API usage matches documentation |
| Mockups | Screenshot Tests | Visual regression testing |
| design-tokens.json | Theme Tests | Verify design tokens applied correctly |
---
## Per-Feature Testing Requirements
### Test Coverage Matrix
| # | Feature | Contract | API | Screenshot | Status |
|:-:|---------|:--------:|:---:|:----------:|:------:|
| 1 | auth | ⬜ | ⬜ | ⬜ | Not Started |
| 2 | home | ⬜ | ⬜ | ⬜ | Not Started |
| 3 | accounts | ⬜ | ⬜ | ⬜ | Not Started |
| 4 | savings-account | ⬜ | ⬜ | ⬜ | Not Started |
| 5 | loan-account | ⬜ | ⬜ | ⬜ | Not Started |
| 6 | share-account | ⬜ | ⬜ | ⬜ | Not Started |
| 7 | beneficiary | ⬜ | ⬜ | ⬜ | Not Started |
| 8 | transfer | ⬜ | ⬜ | ⬜ | Not Started |
| 9 | recent-transaction | ⬜ | ⬜ | ⬜ | Not Started |
| 10 | notification | ⬜ | ⬜ | ⬜ | Not Started |
| 11 | settings | ⬜ | ⬜ | ⬜ | Not Started |
| 12 | passcode | ⬜ | - | ⬜ | Not Started |
| 13 | guarantor | ⬜ | ⬜ | ⬜ | Not Started |
| 14 | qr | ⬜ | - | ⬜ | Not Started |
| 15 | location | ⬜ | - | ⬜ | Not Started |
| 16 | client-charge | ⬜ | ⬜ | ⬜ | Not Started |
| 17 | dashboard | ⬜ | ⬜ | ⬜ | Not Started |
**Legend**: ✅ Complete | ⬜ Not Started | - N/A
---
## Testing Specification Template
Each feature's SPEC.md should include a `## Test Scenarios` section:
```markdown
## Test Scenarios
### Loading State
- [ ] Shows loading indicator when data is being fetched
- [ ] Disables user interaction during loading
### Success State
- [ ] Displays all required data fields
- [ ] Data matches API response
- [ ] Navigation works correctly
### Error State
- [ ] Shows error message for network failures
- [ ] Retry button is visible and functional
- [ ] Error message is user-friendly
### Empty State
- [ ] Shows appropriate message when no data
- [ ] Optional: Call-to-action for creating data
### Validation
- [ ] Required fields show error when empty
- [ ] Format validation (email, phone, etc.)
- [ ] Business rules validation
```
---
## Screenshot Test Baseline
For mockups to become screenshot test baselines:
| Step | Action | Output |
|:----:|--------|--------|
| 1 | Generate mockups (Google Stitch/Figma) | PNG/SVG files |
| 2 | Export to `mockups/` folder | Light + Dark variants |
| 3 | Configure Roborazzi | Golden images |
| 4 | Run screenshot tests | Compare against baseline |
---
## Implementation Priority
| Priority | Features | Reason |
|:--------:|----------|--------|
| P0 | auth, home | Core user flow |
| P1 | accounts, transfer, beneficiary | Primary functionality |
| P2 | loan-account, savings-account | Account management |
| P3 | Others | Supporting features |
---
## Commands
```bash
# Validate feature spec has test scenarios
/verify [feature] testing
# Generate test cases from spec
/gap-planning [feature] testing
```
---
## Related Files
- [FEATURES_INDEX.md](./FEATURES_INDEX.md) - Feature status
- [MOCKUPS_INDEX.md](./MOCKUPS_INDEX.md) - Mockup status
- Each feature's `SPEC.md` - Detailed specifications

View File

@ -0,0 +1,113 @@
# Feature Modules Index - O(1) Lookup
> **23 modules** | **63 screens** | **49 ViewModels** | **21 DI modules**
---
## Quick Lookup
| # | Module | Path | DI | VMs | Screens |
|:-:|--------|------|:--:|:---:|:-------:|
| 1 | accounts | feature/accounts | ✅ | 3 | 3 |
| 2 | auth | feature/auth | ✅ | 5 | 6 |
| 3 | beneficiary | feature/beneficiary | ✅ | 4 | 4 |
| 4 | client-charge | feature/client-charge | ✅ | 2 | 2 |
| 5 | guarantor | feature/guarantor | ✅ | 3 | 3 |
| 6 | home | feature/home | ✅ | 1 | 1 |
| 7 | loan-account | feature/loan-account | ✅ | 4 | 4 |
| 8 | loan-application | feature/loan-application | ✅ | 4 | 3 |
| 9 | location | feature/location | ❌ | 0 | 1 |
| 10 | notification | feature/notification | ✅ | 1 | 1 |
| 11 | onboarding-language | feature/onboarding-language | ✅ | 1 | 1 |
| 12 | passcode | feature/passcode | ✅ | 2 | 2 |
| 13 | qr | feature/qr | ✅ | 3 | 3 |
| 14 | recent-transaction | feature/recent-transaction | ✅ | 1 | 1 |
| 15 | savings-account | feature/savings-account | ✅ | 3 | 4 |
| 16 | savings-application | feature/savings-application | ✅ | 2 | 2 |
| 17 | settings | feature/settings | ✅ | 5 | 9 |
| 18 | share-account | feature/share-account | ✅ | 2 | 2 |
| 19 | share-application | feature/share-application | ✅ | 2 | 2 |
| 20 | status | feature/status | ✅ | 1 | 0 |
| 21 | third-party-transfer | feature/third-party-transfer | ✅ | 1 | 1 |
| 22 | transfer-process | feature/transfer-process | ✅ | 2 | 2 |
| 23 | user-profile | feature/user-profile | ✅ | 1 | 1 |
---
## Base Path Pattern
```
feature/[module]/src/commonMain/kotlin/org/mifos/mobile/feature/[package]/
```
---
## O(1) File Access
| Need | Path Pattern |
|------|--------------|
| Screen | `feature/[module]/.../ui/[Name]Screen.kt` |
| ViewModel | `feature/[module]/.../viewmodel/[Name]ViewModel.kt` |
| DI Module | `feature/[module]/.../di/[Name]Module.kt` |
| Navigation | `feature/[module]/.../navigation/[Name]Navigation.kt` |
---
## Module Details
### High-Complexity Modules (5+ screens/VMs)
| Module | Screens | ViewModels | Key Features |
|--------|:-------:|:----------:|--------------|
| auth | 6 | 5 | Login, Registration, OTP, Password |
| settings | 9 | 5 | Theme, Language, Password, About |
| loan-account | 4 | 4 | Details, Repayment, Summary |
| beneficiary | 4 | 4 | List, Add, Edit, Delete |
### Standard Modules (2-4 screens/VMs)
| Module | Screens | ViewModels | Key Features |
|--------|:-------:|:----------:|--------------|
| accounts | 3 | 3 | List, Transactions, Details |
| guarantor | 3 | 3 | List, Add, Details |
| loan-application | 3 | 4 | Apply, Confirm, Upload |
| passcode | 2 | 2 | Set, Verify |
| qr | 3 | 3 | Display, Read, Import |
| savings-account | 4 | 3 | Details, Update, Withdraw |
| savings-application | 2 | 2 | Apply, Fill |
| share-account | 2 | 2 | List, Details |
| share-application | 2 | 2 | Apply, Fill |
| transfer-process | 2 | 2 | Make, Process |
### Simple Modules (1 screen/VM)
| Module | Screens | ViewModels | Key Features |
|--------|:-------:|:----------:|--------------|
| home | 1 | 1 | Dashboard |
| notification | 1 | 1 | List |
| recent-transaction | 1 | 1 | History |
| location | 1 | 0 | Branch Map |
| onboarding-language | 1 | 1 | Language Selection |
| third-party-transfer | 1 | 1 | TPT |
| user-profile | 1 | 1 | Profile |
| client-charge | 2 | 2 | List, Details |
| status | 0 | 1 | Status tracking |
---
## Related Files
- [SCREENS_INDEX.md](SCREENS_INDEX.md) - All 63 screens with ViewModels
- [LAYER_STATUS.md](LAYER_STATUS.md) - Implementation status
- [LAYER_GUIDE.md](LAYER_GUIDE.md) - Architecture patterns
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| New module added | Add row to Quick Lookup table |
| Screen added to module | Update Screens count |
| ViewModel added | Update VMs count |
| DI module added | Update DI column |

View File

@ -0,0 +1,266 @@
# Screens Index - O(1) Lookup
> **63 screens** across **23 modules**
---
## By Module (Alphabetical)
### accounts (3 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| AccountsScreen | AccountsViewModel | ui/AccountsScreen.kt |
| TransactionScreen | TransactionViewModel | ui/TransactionScreen.kt |
| TransactionDetailsScreen | TransactionDetailsViewModel | ui/TransactionDetailsScreen.kt |
### auth (6 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| LoginScreen | LoginViewModel | ui/LoginScreen.kt |
| OtpAuthenticationScreen | OtpAuthenticationViewModel | ui/OtpAuthenticationScreen.kt |
| RecoverPasswordScreen | RecoverPasswordViewModel | ui/RecoverPasswordScreen.kt |
| RegistrationScreen | RegistrationViewModel | ui/RegistrationScreen.kt |
| SetPasswordScreen | SetPasswordViewModel | ui/SetPasswordScreen.kt |
| UploadIdScreen | - | ui/UploadIdScreen.kt |
### beneficiary (4 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| BeneficiaryApplicationScreen | BeneficiaryApplicationViewModel | ui/BeneficiaryApplicationScreen.kt |
| BeneficiaryApplicationConfirmationScreen | BeneficiaryApplicationConfirmationViewModel | ui/BeneficiaryApplicationConfirmationScreen.kt |
| BeneficiaryDetailScreen | BeneficiaryDetailViewModel | ui/BeneficiaryDetailScreen.kt |
| BeneficiaryListScreen | BeneficiaryListViewModel | ui/BeneficiaryListScreen.kt |
### client-charge (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| ChargeDetailScreen | ChargeDetailsViewModel | ui/ChargeDetailScreen.kt |
| ClientChargeScreen | ClientChargeViewModel | ui/ClientChargeScreen.kt |
### guarantor (3 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| AddGuarantorScreen | AddGuarantorViewModel | ui/AddGuarantorScreen.kt |
| GuarantorDetailScreen | GuarantorDetailViewModel | ui/GuarantorDetailScreen.kt |
| GuarantorListScreen | GuarantorListViewModel | ui/GuarantorListScreen.kt |
### home (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| HomeScreen | HomeViewModel | ui/HomeScreen.kt |
### loan-account (4 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| AccountSummaryScreen | AccountSummaryViewModel | ui/AccountSummaryScreen.kt |
| LoanAccountDetailsScreen | LoanAccountDetailsViewModel | ui/LoanAccountDetailsScreen.kt |
| LoanAccountScreen | LoanAccountViewModel | ui/LoanAccountScreen.kt |
| RepaymentScheduleScreen | RepaymentScheduleViewModel | ui/RepaymentScheduleScreen.kt |
### loan-application (3 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| ConfirmDetailsScreen | ConfirmDetailsViewModel | ui/ConfirmDetailsScreen.kt |
| LoanApplyScreen | LoanApplyViewModel | ui/LoanApplyScreen.kt |
| UploadDocsScreen | - | ui/UploadDocsScreen.kt |
### location (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| LocationScreen | - | ui/LocationScreen.kt |
### notification (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| NotificationScreen | NotificationViewModel | ui/NotificationScreen.kt |
### onboarding-language (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| SetOnboardingLanguageScreen | SetOnboardingLanguageViewModel | ui/SetOnboardingLanguageScreen.kt |
### passcode (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| PasscodeScreen | PasscodeViewModel | ui/PasscodeScreen.kt |
| VerifyPasscodeScreen | VerifyPasscodeViewModel | ui/VerifyPasscodeScreen.kt |
### qr (3 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| QrCodeDisplayScreen | QrCodeDisplayViewModel | ui/QrCodeDisplayScreen.kt |
| QrCodeImportScreen | QrCodeImportViewModel | ui/QrCodeImportScreen.kt |
| QrCodeReaderScreen | QrCodeReaderViewModel | ui/QrCodeReaderScreen.kt |
### recent-transaction (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| RecentTransactionScreen | RecentTransactionViewModel | ui/RecentTransactionScreen.kt |
### savings-account (4 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| AccountUpdateScreen | AccountUpdateViewModel | ui/AccountUpdateScreen.kt |
| AccountWithdrawScreen | AccountWithdrawViewModel | ui/AccountWithdrawScreen.kt |
| SavingsAccountDetailsScreen | SavingsAccountDetailsViewModel | ui/SavingsAccountDetailsScreen.kt |
| SavingsAccountScreen | - | ui/SavingsAccountScreen.kt |
### savings-application (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| FillApplicationScreen | FillApplicationViewModel | ui/FillApplicationScreen.kt |
| SavingsApplyScreen | SavingsApplyViewModel | ui/SavingsApplyScreen.kt |
### settings (9 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| AboutScreen | - | ui/AboutScreen.kt |
| AppInfoScreen | - | ui/AppInfoScreen.kt |
| ChangePasswordScreen | ChangePasswordViewModel | ui/ChangePasswordScreen.kt |
| ChangeThemeScreen | ChangeThemeViewModel | ui/ChangeThemeScreen.kt |
| FaqScreen | - | ui/FaqScreen.kt |
| HelpScreen | - | ui/HelpScreen.kt |
| LanguageScreen | LanguageViewModel | ui/LanguageScreen.kt |
| SettingsScreen | SettingsViewModel | ui/SettingsScreen.kt |
| UpdatePasscodeScreen | UpdatePasscodeViewModel | ui/UpdatePasscodeScreen.kt |
### share-account (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| ShareAccountDetailsScreen | ShareAccountDetailsViewModel | ui/ShareAccountDetailsScreen.kt |
| ShareAccountScreen | ShareAccountViewModel | ui/ShareAccountScreen.kt |
### share-application (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| FillApplicationScreen | FillApplicationViewModel | ui/FillApplicationScreen.kt |
| ShareApplyScreen | ShareApplyViewModel | ui/ShareApplyScreen.kt |
### third-party-transfer (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| TptScreen | TptViewModel | ui/TptScreen.kt |
### transfer-process (2 screens)
| Screen | ViewModel | File |
|--------|-----------|------|
| MakeTransferScreen | MakeTransferViewModel | ui/MakeTransferScreen.kt |
| TransferProcessScreen | TransferProcessViewModel | ui/TransferProcessScreen.kt |
### user-profile (1 screen)
| Screen | ViewModel | File |
|--------|-----------|------|
| UserProfileScreen | UserProfileViewModel | ui/UserProfileScreen.kt |
---
## Search by Screen Name (Alphabetical)
| Screen | Module |
|--------|--------|
| AboutScreen | settings |
| AccountsScreen | accounts |
| AccountSummaryScreen | loan-account |
| AccountUpdateScreen | savings-account |
| AccountWithdrawScreen | savings-account |
| AddGuarantorScreen | guarantor |
| AppInfoScreen | settings |
| BeneficiaryApplicationConfirmationScreen | beneficiary |
| BeneficiaryApplicationScreen | beneficiary |
| BeneficiaryDetailScreen | beneficiary |
| BeneficiaryListScreen | beneficiary |
| ChangePasswordScreen | settings |
| ChangeThemeScreen | settings |
| ChargeDetailScreen | client-charge |
| ClientChargeScreen | client-charge |
| ConfirmDetailsScreen | loan-application |
| FaqScreen | settings |
| FillApplicationScreen | savings-application, share-application |
| GuarantorDetailScreen | guarantor |
| GuarantorListScreen | guarantor |
| HelpScreen | settings |
| HomeScreen | home |
| LanguageScreen | settings |
| LoanAccountDetailsScreen | loan-account |
| LoanAccountScreen | loan-account |
| LoanApplyScreen | loan-application |
| LocationScreen | location |
| LoginScreen | auth |
| MakeTransferScreen | transfer-process |
| NotificationScreen | notification |
| OtpAuthenticationScreen | auth |
| PasscodeScreen | passcode |
| QrCodeDisplayScreen | qr |
| QrCodeImportScreen | qr |
| QrCodeReaderScreen | qr |
| RecentTransactionScreen | recent-transaction |
| RecoverPasswordScreen | auth |
| RegistrationScreen | auth |
| RepaymentScheduleScreen | loan-account |
| SavingsAccountDetailsScreen | savings-account |
| SavingsAccountScreen | savings-account |
| SavingsApplyScreen | savings-application |
| SetOnboardingLanguageScreen | onboarding-language |
| SetPasswordScreen | auth |
| SettingsScreen | settings |
| ShareAccountDetailsScreen | share-account |
| ShareAccountScreen | share-account |
| ShareApplyScreen | share-application |
| TptScreen | third-party-transfer |
| TransactionDetailsScreen | accounts |
| TransactionScreen | accounts |
| TransferProcessScreen | transfer-process |
| UpdatePasscodeScreen | settings |
| UploadDocsScreen | loan-application |
| UploadIdScreen | auth |
| UserProfileScreen | user-profile |
| VerifyPasscodeScreen | passcode |
---
## O(1) File Access
| Need | Path Pattern |
|------|--------------|
| Screen file | `feature/[module]/src/commonMain/kotlin/org/mifos/mobile/feature/../ui/[Screen].kt` |
| ViewModel | Same directory structure, `viewmodel/[ViewModel].kt` |
---
## Related Files
- [MODULES_INDEX.md](MODULES_INDEX.md) - Module overview
- [LAYER_STATUS.md](LAYER_STATUS.md) - Implementation status
- [LAYER_GUIDE.md](LAYER_GUIDE.md) - Architecture patterns
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| New screen added | Add to module section + alphabetical list |
| Screen renamed | Update both tables |
| ViewModel added | Update ViewModel column |

View File

@ -0,0 +1,323 @@
# Feature Layer - Testing Status
> ViewModel, Screen, and Integration testing documentation
---
## Overview
The feature layer is the primary testing target. Each feature needs:
- ViewModel unit tests (state, events, actions)
- Screen UI tests (rendering, interactions)
- Integration tests (full user flows)
---
## Current State
| Component | Total | Tested | Coverage |
|-----------|:-----:|:------:|:--------:|
| ViewModels | 49 | 0 | 0% |
| Screens | 63 | 0 | 0% |
| Integration Flows | 8 | 0 | 0% |
---
## Testing Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ FEATURE LAYER TESTING STACK │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Integration Tests (E2E Flows) │ │
│ │ - Login → Passcode → Home │ │
│ │ - Home → Transfer → Confirm │ │
│ │ - Location: cmp-android/src/androidTest/ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ UI Tests (Screen Tests) │ │
│ │ - Compose test rules │ │
│ │ - TestTag-based assertions │ │
│ │ - Location: feature/*/src/androidInstrumentedTest/ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ Unit Tests (ViewModel Tests) │ │
│ │ - StateFlow testing with Turbine │ │
│ │ - Event emission testing │ │
│ │ - Action handling testing │ │
│ │ - Location: feature/*/src/commonTest/ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## ViewModel Testing Status
### By Feature
| # | Feature | VMs | Tests | Coverage | Status |
|:-:|---------|:---:|:-----:|:--------:|:------:|
| 1 | auth | 5 | 0 | 0% | ⬜ Not Started |
| 2 | home | 1 | 0 | 0% | ⬜ Not Started |
| 3 | accounts | 3 | 0 | 0% | ⬜ Not Started |
| 4 | savings-account | 3 | 0 | 0% | ⬜ Not Started |
| 5 | loan-account | 4 | 0 | 0% | ⬜ Not Started |
| 6 | share-account | 2 | 0 | 0% | ⬜ Not Started |
| 7 | beneficiary | 4 | 0 | 0% | ⬜ Not Started |
| 8 | transfer-process | 2 | 0 | 0% | ⬜ Not Started |
| 9 | recent-transaction | 1 | 0 | 0% | ⬜ Not Started |
| 10 | notification | 1 | 0 | 0% | ⬜ Not Started |
| 11 | settings | 5 | 0 | 0% | ⬜ Not Started |
| 12 | mifos-passcode | 2 | 0 | 0% | ⬜ Not Started |
| 13 | guarantor | 3 | 0 | 0% | ⬜ Not Started |
| 14 | qr-code | 3 | 0 | 0% | ⬜ Not Started |
| 15 | location | 1 | 0 | 0% | ⬜ Not Started |
| 16 | user-profile | 2 | 0 | 0% | ⬜ Not Started |
**Legend**: ✅ 80%+ | ⚠️ <80% | 0%
---
## UI Testing Status
### TestTag Coverage
| # | Feature | Screens | TestTags | Coverage | Status |
|:-:|---------|:-------:|:--------:|:--------:|:------:|
| 1 | auth | 6 | ~40% | Partial | ⚠️ |
| 2 | home | 1 | ~30% | Partial | ⚠️ |
| 3 | accounts | 3 | ~30% | Partial | ⚠️ |
| 4 | savings-account | 4 | ~30% | Partial | ⚠️ |
| 5 | loan-account | 4 | ~30% | Partial | ⚠️ |
| 6 | share-account | 2 | ~30% | Partial | ⚠️ |
| 7 | beneficiary | 4 | ~30% | Partial | ⚠️ |
| 8 | transfer-process | 2 | ~30% | Partial | ⚠️ |
| 9 | recent-transaction | 1 | ~30% | Partial | ⚠️ |
| 10 | notification | 1 | ~30% | Partial | ⚠️ |
| 11 | settings | 9 | ~30% | Partial | ⚠️ |
| 12 | mifos-passcode | 2 | ~30% | Partial | ⚠️ |
| 13 | guarantor | 3 | ~30% | Partial | ⚠️ |
| 14 | qr-code | 3 | ~30% | Partial | ⚠️ |
| 15 | location | 1 | ~30% | Partial | ⚠️ |
| 16 | user-profile | 2 | ~30% | Partial | ⚠️ |
**Legend**: ✅ 80%+ TestTags | ⚠️ <80% TestTags | No TestTags
---
## Integration Test Status
### Critical User Flows
| # | Flow | Screens | Tests | Status |
|:-:|------|:-------:|:-----:|:------:|
| 1 | Login → Passcode → Home | 3 | 0 | ⬜ |
| 2 | Registration → OTP → Login | 4 | 0 | ⬜ |
| 3 | Home → Account Details | 2 | 0 | ⬜ |
| 4 | Home → Transfer → Confirm | 3 | 0 | ⬜ |
| 5 | Home → Beneficiary → Add | 2 | 0 | ⬜ |
| 6 | Settings → Change Password | 2 | 0 | ⬜ |
| 7 | Loan → Schedule → Summary | 3 | 0 | ⬜ |
| 8 | QR → Scan → Transfer | 3 | 0 | ⬜ |
---
## TestTag System
### Pattern: `feature:component:element`
```kotlin
object TestTags {
object Auth {
const val SCREEN = "auth:screen"
const val USERNAME_FIELD = "auth:username"
const val PASSWORD_FIELD = "auth:password"
const val LOGIN_BUTTON = "auth:loginButton"
const val ERROR_MESSAGE = "auth:error"
const val LOADING_INDICATOR = "auth:loading"
}
object Home {
const val SCREEN = "home:screen"
const val LOAN_BALANCE = "home:loanBalance"
const val SAVINGS_BALANCE = "home:savingsBalance"
const val TRANSFER_BUTTON = "home:transferButton"
const val ACCOUNTS_CARD = "home:accountsCard"
}
// ... for all 17 features
}
```
### Usage in Screens
```kotlin
@Composable
fun LoginScreenContent(
state: LoginState,
onAction: (LoginAction) -> Unit
) {
Column(modifier = Modifier.testTag(TestTags.Auth.SCREEN)) {
MifosOutlinedTextField(
value = state.username,
onValueChange = { onAction(LoginAction.UsernameChanged(it)) },
modifier = Modifier.testTag(TestTags.Auth.USERNAME_FIELD)
)
// ...
}
}
```
---
## ViewModel Test Template
```kotlin
class LoginViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private lateinit var viewModel: LoginViewModel
private lateinit var fakeUserAuthRepository: FakeUserAuthRepository
@BeforeTest
fun setup() {
fakeUserAuthRepository = FakeUserAuthRepository()
viewModel = LoginViewModel(
userAuthRepository = fakeUserAuthRepository,
savedStateHandle = SavedStateHandle()
)
}
// STATE TESTS
@Test
fun `initial state has empty credentials`() = runTest {
viewModel.stateFlow.test {
val state = awaitItem()
assertEquals("", state.username)
assertEquals("", state.password)
}
}
// ACTION TESTS
@Test
fun `username changed updates state`() = runTest {
viewModel.trySendAction(LoginAction.UsernameChanged("testuser"))
viewModel.stateFlow.test {
assertEquals("testuser", expectMostRecentItem().username)
}
}
// EVENT TESTS
@Test
fun `login success navigates to passcode`() = runTest {
fakeUserAuthRepository.setResult(DataState.Success(UserFixture.create()))
viewModel.trySendAction(LoginAction.LoginClicked)
viewModel.eventFlow.test {
assertEquals(LoginEvent.NavigateToPasscode, awaitItem())
}
}
}
```
---
## UI Test Template
```kotlin
class LoginScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun `login screen displays all elements`() {
composeTestRule.setContent {
LoginScreenContent(state = LoginState(), onAction = {})
}
composeTestRule.onNodeWithTag(TestTags.Auth.USERNAME_FIELD).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.Auth.PASSWORD_FIELD).assertIsDisplayed()
composeTestRule.onNodeWithTag(TestTags.Auth.LOGIN_BUTTON).assertIsDisplayed()
}
@Test
fun `login button disabled when credentials empty`() {
composeTestRule.setContent {
LoginScreenContent(
state = LoginState(username = "", password = ""),
onAction = {}
)
}
composeTestRule.onNodeWithTag(TestTags.Auth.LOGIN_BUTTON).assertIsNotEnabled()
}
}
```
---
## Implementation Priority
### Phase 1: Core Features (P0)
| Feature | VMs | Tests Needed | Effort |
|---------|:---:|:------------:|:------:|
| auth | 5 | 50 | L |
| home | 1 | 12 | M |
| accounts | 3 | 24 | M |
| transfer-process | 2 | 20 | M |
### Phase 2: Account Features (P1)
| Feature | VMs | Tests Needed | Effort |
|---------|:---:|:------------:|:------:|
| beneficiary | 4 | 32 | M |
| loan-account | 4 | 32 | M |
| savings-account | 3 | 24 | M |
| share-account | 2 | 16 | S |
### Phase 3: Supporting Features (P2)
| Feature | VMs | Tests Needed | Effort |
|---------|:---:|:------------:|:------:|
| settings | 5 | 40 | L |
| notification | 1 | 8 | S |
| recent-transaction | 1 | 8 | S |
| qr-code | 3 | 24 | M |
| mifos-passcode | 2 | 16 | M |
| guarantor | 3 | 24 | M |
| user-profile | 2 | 16 | S |
| location | 1 | 8 | S |
---
## Commands
```bash
# Run feature tests
./gradlew :feature:auth:test
./gradlew :feature:home:test
# Run UI tests
./gradlew :cmp-android:connectedDebugAndroidTest
# Check test status
/gap-analysis feature testing
# Plan feature tests
/gap-planning feature auth testing
```
---
## Related Files
- [MODULES_INDEX.md](./MODULES_INDEX.md) - All feature modules
- [SCREENS_INDEX.md](./SCREENS_INDEX.md) - All screens
- [LAYER_GUIDE.md](./LAYER_GUIDE.md) - Architecture patterns

View File

@ -0,0 +1,195 @@
# Platform Layer Guide
> Platform-specific patterns and configurations for Mifos Mobile KMP
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Platform Layer (cmp-android, cmp-ios, cmp-desktop, cmp-web) │
├─────────────────────────────────────────────────────────────────┤
│ Shared Layer (cmp-shared, cmp-navigation) │
├─────────────────────────────────────────────────────────────────┤
│ Feature Layer (feature/*) │
├─────────────────────────────────────────────────────────────────┤
│ Core Layer (core/*) │
└─────────────────────────────────────────────────────────────────┘
```
---
## Kotlin Multiplatform Targets
| Target | Platform | Kotlin Target |
|--------|----------|---------------|
| Android | Android 7.0+ | `android()` |
| iOS | iOS 14+ | `iosArm64()`, `iosSimulatorArm64()` |
| Desktop | JVM 17+ | `jvm()` |
| Web | Modern browsers | `js(IR)` |
---
## Platform-Specific Code
### Source Sets Structure
```
src/
├── commonMain/ # Shared code (all platforms)
├── commonTest/ # Shared tests
├── androidMain/ # Android-specific
├── iosMain/ # iOS-specific
├── jvmMain/ # Desktop-specific
└── jsMain/ # Web-specific
```
### Expect/Actual Pattern
```kotlin
// commonMain - expect declaration
expect fun getPlatformName(): String
// androidMain - actual implementation
actual fun getPlatformName(): String = "Android"
// iosMain - actual implementation
actual fun getPlatformName(): String = "iOS"
```
---
## Dependency Injection
### Koin Setup Per Platform
**Android** (`cmp-android`):
```kotlin
class MifosApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MifosApplication)
modules(appModule)
}
}
}
```
**iOS** (`cmp-ios`):
```swift
KoinKt.doInitKoin()
```
**Desktop/Web**:
```kotlin
fun main() {
startKoin {
modules(appModule)
}
}
```
---
## Navigation
### Compose Navigation (Cross-Platform)
```kotlin
// cmp-navigation/
NavHost(
navController = navController,
startDestination = ROOT_GRAPH,
) {
authGraph(navController)
passcodeGraph(navController)
mainGraph(navController)
}
```
### Navigation Graphs
| Graph | Content |
|-------|---------|
| ROOT_GRAPH | Entry point, splash |
| AUTH_GRAPH | Login, Registration |
| PASSCODE_GRAPH | Passcode setup/verify |
| MAIN_GRAPH | Main app content |
---
## Platform Capabilities
| Capability | Android | iOS | Desktop | Web |
|------------|:-------:|:---:|:-------:|:---:|
| Biometrics | ✅ | ✅ | ❌ | ❌ |
| Push Notifications | ✅ | ✅ | ❌ | ⚠️ |
| Camera (QR) | ✅ | ✅ | ⚠️ | ⚠️ |
| Location | ✅ | ✅ | ❌ | ⚠️ |
| Local Storage | ✅ | ✅ | ✅ | ✅ |
| Deep Links | ✅ | ✅ | ❌ | ✅ |
---
## Build Variants
### Android Flavors
| Flavor | Purpose | Base URL |
|--------|---------|----------|
| demo | Development | tt.mifos.community |
| prod | Production | Configurable |
### Build Types
| Type | Debug | Minify | Signing |
|------|:-----:|:------:|---------|
| debug | ✅ | ❌ | Debug key |
| release | ❌ | ✅ | Release key |
---
## Testing
### Platform Testing Strategy
| Test Type | Location | Runner |
|-----------|----------|--------|
| Unit Tests | `commonTest/` | JUnit/Kotest |
| Android Tests | `androidTest/` | Android Instrumented |
| iOS Tests | `iosTest/` | XCTest |
| UI Tests | Platform-specific | Compose Test |
---
## Resources
### Platform Resources Location
| Platform | Resources |
|----------|-----------|
| Android | `cmp-android/src/main/res/` |
| iOS | `cmp-ios/iosApp/Assets.xcassets/` |
| Desktop | `cmp-desktop/src/main/resources/` |
| Web | `cmp-web/src/jsMain/resources/` |
### Shared Resources (Compose Resources)
```
core/designsystem/src/commonMain/composeResources/
├── drawable/
├── values/
└── font/
```
---
## Related Files
- [LAYER_STATUS.md](LAYER_STATUS.md) - Platform status
- [platforms/ANDROID.md](platforms/ANDROID.md) - Android details
- [platforms/IOS.md](platforms/IOS.md) - iOS details
- [platforms/DESKTOP.md](platforms/DESKTOP.md) - Desktop details
- [platforms/WEB.md](platforms/WEB.md) - Web details

View File

@ -0,0 +1,102 @@
# Platform Layer Status
> **4 platforms** | Android primary | KMP shared code
---
## Platform Matrix
| Platform | Module | Build | Flavors | Status |
|----------|--------|:-----:|:-------:|:------:|
| Android | cmp-android | ✅ | demo, prod | Primary |
| iOS | cmp-ios | ✅ | - | CocoaPods |
| Desktop | cmp-desktop | ✅ | - | JVM |
| Web | cmp-web | ⚠️ | - | Kotlin/JS |
**Legend**: ✅ Working | ⚠️ Experimental | ❌ Not Working
---
## O(1) Platform Lookup
| Need | Read File |
|------|-----------|
| Android build | [platforms/ANDROID.md](platforms/ANDROID.md) |
| iOS setup | [platforms/IOS.md](platforms/IOS.md) |
| Desktop config | [platforms/DESKTOP.md](platforms/DESKTOP.md) |
| Web config | [platforms/WEB.md](platforms/WEB.md) |
---
## Shared Modules
| Module | Purpose | Target |
|--------|---------|--------|
| cmp-shared | KMP shared code | All platforms |
| cmp-navigation | Cross-platform navigation | All platforms |
| core/* | Core business logic | All platforms |
| feature/* | Feature modules | All platforms |
---
## Build Commands Quick Reference
| Platform | Debug | Release |
|----------|-------|---------|
| Android | `./gradlew :cmp-android:assembleDemoDebug` | `./gradlew :cmp-android:assembleProdRelease` |
| Desktop | `./gradlew :cmp-desktop:run` | `./gradlew :cmp-desktop:packageDmg` |
| Web | `./gradlew :cmp-web:jsBrowserRun` | `./gradlew :cmp-web:jsBrowserProductionWebpack` |
| iOS | Xcode Build | Archive & Distribute |
---
## Platform Entry Points
| Platform | Entry Point | Location |
|----------|-------------|----------|
| Android | MainActivity | `cmp-android/src/main/kotlin/.../MainActivity.kt` |
| iOS | iosApp | `cmp-ios/iosApp/` |
| Desktop | Main.kt | `cmp-desktop/src/main/kotlin/.../Main.kt` |
| Web | Main.kt | `cmp-web/src/jsMain/kotlin/.../Main.kt` |
---
## Gradle Module Configuration
| Module | Build File |
|--------|------------|
| cmp-android | `cmp-android/build.gradle.kts` |
| cmp-ios | Uses CocoaPods via `cmp-shared` |
| cmp-desktop | `cmp-desktop/build.gradle.kts` |
| cmp-web | `cmp-web/build.gradle.kts` |
| cmp-shared | `cmp-shared/build.gradle.kts` |
| cmp-navigation | `cmp-navigation/build.gradle.kts` |
---
## CI/CD Status
| Platform | CI Pipeline | Status |
|----------|-------------|--------|
| Android | GitHub Actions | ✅ |
| iOS | - | ⚠️ Manual |
| Desktop | - | ⚠️ Manual |
| Web | - | ⚠️ Manual |
---
## Related Files
- [LAYER_GUIDE.md](LAYER_GUIDE.md) - Platform-specific patterns
- [platforms/](platforms/) - Platform-specific documentation
- [../CLAUDE.md](../../CLAUDE.md) - Project overview
---
## Auto-Update Rules
| Scenario | Action |
|----------|--------|
| Platform status changes | Update Platform Matrix |
| New build variant added | Update Build Commands |
| CI/CD configured | Update CI/CD Status |

View File

@ -0,0 +1,279 @@
# Platform Layer - Testing Status
> E2E, Screenshot, and Platform-specific testing documentation
---
## Overview
The platform layer handles platform-specific testing:
- E2E integration tests (full user journeys)
- Screenshot/visual regression tests (Roborazzi)
- Platform-specific functionality tests
---
## Current State
| Platform | E2E Tests | Screenshot Tests | Status |
|----------|:---------:|:----------------:|:------:|
| Android | 0 | 0 | ⬜ Not Started |
| iOS | - | - | ⚠️ Manual |
| Desktop | 0 | 0 | ⬜ Not Started |
| Web | 0 | 0 | ⬜ Not Started |
---
## Testing by Platform
### Android (Primary)
| Test Type | Framework | Location | Status |
|-----------|-----------|----------|:------:|
| E2E Tests | Compose UI Test | `cmp-android/src/androidTest/` | ⬜ |
| Screenshot | Roborazzi | `core/designsystem/src/test/` | ⬜ |
| Unit Tests | JUnit | `cmp-android/src/test/` | ⬜ |
### iOS (CocoaPods)
| Test Type | Framework | Location | Status |
|-----------|-----------|----------|:------:|
| UI Tests | XCUITest | `cmp-ios/iosApp/` | ⬜ |
| Unit Tests | XCTest | `cmp-ios/iosApp/` | ⬜ |
### Desktop (JVM)
| Test Type | Framework | Location | Status |
|-----------|-----------|----------|:------:|
| UI Tests | Compose Desktop Test | `cmp-desktop/src/test/` | ⬜ |
| Unit Tests | JUnit | `cmp-desktop/src/test/` | ⬜ |
### Web (Kotlin/JS)
| Test Type | Framework | Location | Status |
|-----------|-----------|----------|:------:|
| E2E Tests | Playwright/Cypress | TBD | ⬜ |
| Unit Tests | kotlin.test | `cmp-web/src/jsTest/` | ⬜ |
---
## E2E Test Scenarios
### Critical User Journeys
| # | Journey | Platforms | Tests | Status |
|:-:|---------|-----------|:-----:|:------:|
| 1 | Onboarding → Login → Home | Android | 0 | ⬜ |
| 2 | Registration → OTP → Login | Android | 0 | ⬜ |
| 3 | Home → Account Details | Android | 0 | ⬜ |
| 4 | Home → Transfer → Confirm | Android | 0 | ⬜ |
| 5 | Home → Loan Details → Schedule | Android | 0 | ⬜ |
| 6 | Settings → Change Password | Android | 0 | ⬜ |
| 7 | QR Scan → Transfer | Android | 0 | ⬜ |
| 8 | Offline → Online Sync | Android | 0 | ⬜ |
---
## Screenshot Testing (Roborazzi)
### Configuration
**Location**: `core/designsystem/src/test/`
```kotlin
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ComponentScreenshotTest {
@get:Rule
val roborazziRule = RoborazziRule(
options = RoborazziRule.Options(
captureType = RoborazziRule.CaptureType.LastImage
)
)
@Test
fun mifosButton_light() {
composeTestRule.setContent {
MifosTheme(darkTheme = false) {
MifosButton(text = "Login", onClick = {})
}
}
composeTestRule.onRoot().captureRoboImage()
}
@Test
fun mifosButton_dark() {
composeTestRule.setContent {
MifosTheme(darkTheme = true) {
MifosButton(text = "Login", onClick = {})
}
}
composeTestRule.onRoot().captureRoboImage()
}
}
```
### Screenshot Test Coverage
| Component | Light | Dark | Status |
|-----------|:-----:|:----:|:------:|
| MifosButton | ⬜ | ⬜ | Not Started |
| MifosTextField | ⬜ | ⬜ | Not Started |
| MifosCard | ⬜ | ⬜ | Not Started |
| MifosTopBar | ⬜ | ⬜ | Not Started |
| MifosBottomNav | ⬜ | ⬜ | Not Started |
| MifosDialog | ⬜ | ⬜ | Not Started |
| MifosLoadingWheel | ⬜ | ⬜ | Not Started |
| AccountCard | ⬜ | ⬜ | Not Started |
| TransactionItem | ⬜ | ⬜ | Not Started |
| BeneficiaryItem | ⬜ | ⬜ | Not Started |
---
## Screen Screenshot Tests
| # | Feature | Screens | Golden Images | Status |
|:-:|---------|:-------:|:-------------:|:------:|
| 1 | auth | 6 | 0 | ⬜ |
| 2 | home | 1 | 0 | ⬜ |
| 3 | accounts | 3 | 0 | ⬜ |
| 4 | savings-account | 4 | 0 | ⬜ |
| 5 | loan-account | 4 | 0 | ⬜ |
| 6 | share-account | 2 | 0 | ⬜ |
| 7 | beneficiary | 4 | 0 | ⬜ |
| 8 | transfer | 2 | 0 | ⬜ |
| 9 | recent-transaction | 1 | 0 | ⬜ |
| 10 | notification | 1 | 0 | ⬜ |
| 11 | settings | 9 | 0 | ⬜ |
| 12 | passcode | 2 | 0 | ⬜ |
| 13 | guarantor | 3 | 0 | ⬜ |
| 14 | qr | 3 | 0 | ⬜ |
| 15 | location | 1 | 0 | ⬜ |
| 16 | user-profile | 2 | 0 | ⬜ |
---
## Platform-Specific Tests
### Android-Specific
| Test | Description | Status |
|------|-------------|:------:|
| Deep Links | Verify deep link handling | ⬜ |
| Notifications | Push notification handling | ⬜ |
| Biometrics | Fingerprint/Face ID login | ⬜ |
| Camera | QR code scanning | ⬜ |
| Location | Branch finder | ⬜ |
| Back Navigation | System back button | ⬜ |
### iOS-Specific
| Test | Description | Status |
|------|-------------|:------:|
| Deep Links | Universal links | ⬜ |
| Notifications | APNs handling | ⬜ |
| Biometrics | Touch ID/Face ID | ⬜ |
| Camera | QR code scanning | ⬜ |
| Location | Core Location | ⬜ |
### Desktop-Specific
| Test | Description | Status |
|------|-------------|:------:|
| Window Management | Resize, minimize | ⬜ |
| Keyboard Shortcuts | Standard shortcuts | ⬜ |
| System Tray | Optional integration | ⬜ |
### Web-Specific
| Test | Description | Status |
|------|-------------|:------:|
| Browser Compat | Chrome, Firefox, Safari | ⬜ |
| CORS Handling | API requests | ⬜ |
| Responsive | Mobile/Tablet/Desktop | ⬜ |
| PWA | Service worker, offline | ⬜ |
---
## CI/CD Integration
### GitHub Actions Workflow
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Run unit tests
run: ./gradlew test
android-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Android tests
run: ./gradlew :cmp-android:connectedDebugAndroidTest
screenshot-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Roborazzi tests
run: ./gradlew :core:designsystem:testDebugUnitTest
- name: Compare screenshots
run: ./gradlew :core:designsystem:compareRoborazziDebug
```
---
## Implementation Priority
| Phase | Platform | Tests | Effort |
|:-----:|----------|:-----:|:------:|
| 1 | Android E2E | 20 | L |
| 2 | Screenshot (Roborazzi) | 30 | M |
| 3 | iOS UI | 10 | M |
| 4 | Desktop | 5 | S |
| 5 | Web | 10 | M |
---
## Commands
```bash
# Run Android E2E tests
./gradlew :cmp-android:connectedDebugAndroidTest
# Run screenshot tests
./gradlew :core:designsystem:testDebugUnitTest
# Record new screenshots
./gradlew :core:designsystem:recordRoborazziDebug
# Compare screenshots
./gradlew :core:designsystem:compareRoborazziDebug
# Check platform test status
/gap-analysis platform testing
```
---
## Related Files
- [LAYER_STATUS.md](./LAYER_STATUS.md) - Platform status
- [platforms/ANDROID.md](./platforms/ANDROID.md) - Android details
- [platforms/IOS.md](./platforms/IOS.md) - iOS details
- [platforms/DESKTOP.md](./platforms/DESKTOP.md) - Desktop details
- [platforms/WEB.md](./platforms/WEB.md) - Web details

View File

@ -0,0 +1,109 @@
# Android Platform
## Module: cmp-android
> Primary platform target for Mifos Mobile
---
## Build Flavors
| Flavor | API Base | Use Case |
|--------|----------|----------|
| demo | tt.mifos.community | Development/Testing |
| prod | Configurable | Production |
---
## Build Commands
| Command | Output |
|---------|--------|
| `./gradlew :cmp-android:assembleDemoDebug` | Demo debug APK |
| `./gradlew :cmp-android:assembleDemoRelease` | Demo release APK |
| `./gradlew :cmp-android:assembleProdDebug` | Prod debug APK |
| `./gradlew :cmp-android:assembleProdRelease` | Production release APK |
| `./gradlew :cmp-android:lintRelease` | Lint checks |
| `./gradlew :cmp-android:testDebug` | Run unit tests |
---
## Key Files
| File | Purpose |
|------|---------|
| `cmp-android/build.gradle.kts` | Module configuration |
| `cmp-android/src/main/AndroidManifest.xml` | App manifest |
| `cmp-android/src/main/kotlin/.../MainActivity.kt` | Entry point |
| `cmp-android/src/main/kotlin/.../MifosApplication.kt` | Application class |
---
## Gradle Configuration
```kotlin
android {
namespace = "org.mifos.mobile"
compileSdk = 34
defaultConfig {
applicationId = "org.mifos.mobile"
minSdk = 24
targetSdk = 34
}
productFlavors {
create("demo") { ... }
create("prod") { ... }
}
}
```
---
## Signing Configuration
### Debug
- Auto-generated debug keystore
- Location: `~/.android/debug.keystore`
### Release
- Requires `keystore.properties` file
- Keys: `storeFile`, `storePassword`, `keyAlias`, `keyPassword`
---
## Dependencies
| Category | Key Dependencies |
|----------|------------------|
| Compose | Compose BOM, Material3 |
| DI | Koin Android |
| Network | Ktor Android |
| Storage | DataStore, Room |
---
## Android-Specific Features
| Feature | Implementation |
|---------|----------------|
| Biometrics | AndroidX Biometric |
| Push Notifications | Firebase Cloud Messaging |
| Deep Links | Intent Filters |
| Splash Screen | SplashScreen API |
---
## ProGuard/R8
- Rules in `proguard-rules.pro`
- Keep rules for serialization
- Ktor client rules
---
## Related
- [LAYER_STATUS.md](../LAYER_STATUS.md) - Platform overview
- [LAYER_GUIDE.md](../LAYER_GUIDE.md) - Architecture patterns

View File

@ -0,0 +1,109 @@
# Desktop Platform
## Module: cmp-desktop
> JVM-based desktop application using Compose for Desktop
---
## Build Commands
| Command | Action |
|---------|--------|
| `./gradlew :cmp-desktop:run` | Run desktop app |
| `./gradlew :cmp-desktop:packageDmg` | Package macOS DMG |
| `./gradlew :cmp-desktop:packageMsi` | Package Windows MSI |
| `./gradlew :cmp-desktop:packageDeb` | Package Linux DEB |
| `./gradlew :cmp-desktop:packageRpm` | Package Linux RPM |
---
## Key Files
| File | Purpose |
|------|---------|
| `cmp-desktop/build.gradle.kts` | Module configuration |
| `cmp-desktop/src/main/kotlin/.../Main.kt` | Entry point |
| `cmp-desktop/src/main/resources/` | Desktop resources |
---
## Gradle Configuration
```kotlin
compose.desktop {
application {
mainClass = "org.mifos.mobile.MainKt"
nativeDistributions {
targetFormats(
TargetFormat.Dmg,
TargetFormat.Msi,
TargetFormat.Deb
)
packageName = "Mifos Mobile"
packageVersion = "1.0.0"
}
}
}
```
---
## Entry Point
```kotlin
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Mifos Mobile"
) {
App()
}
}
```
---
## Platform Support
| OS | Target Format | Status |
|----|---------------|:------:|
| macOS | DMG | ✅ |
| Windows | MSI | ✅ |
| Linux | DEB/RPM | ✅ |
---
## Desktop-Specific Features
| Feature | Status | Notes |
|---------|:------:|-------|
| Window Management | ✅ | Resize, minimize, maximize |
| System Tray | ⚠️ | Optional |
| Keyboard Shortcuts | ✅ | Standard shortcuts |
| File System Access | ✅ | Full access |
---
## Requirements
| Requirement | Version |
|-------------|---------|
| JVM | 17+ |
| Compose Desktop | 1.5+ |
---
## Development Notes
- Uses Compose for Desktop (Multiplatform)
- Shares UI code with Android/iOS
- Platform-specific code in `jvmMain/`
---
## Related
- [LAYER_STATUS.md](../LAYER_STATUS.md) - Platform overview
- [LAYER_GUIDE.md](../LAYER_GUIDE.md) - Architecture patterns

View File

@ -0,0 +1,134 @@
# iOS Platform
## Module: cmp-ios
> iOS platform target using CocoaPods integration
---
## Setup
1. Install CocoaPods dependencies:
```bash
cd cmp-ios
pod install
```
2. Open Xcode workspace:
```bash
open iosApp.xcworkspace
```
3. Build and run in Xcode
---
## Key Files
| File | Purpose |
|------|---------|
| `cmp-ios/Podfile` | CocoaPods dependencies |
| `cmp-ios/iosApp/` | iOS app source |
| `cmp-ios/iosApp/Info.plist` | App configuration |
| `cmp-ios/iosApp/ContentView.swift` | Main UI entry |
| `cmp-shared/` | Shared KMP framework |
---
## Build Commands
| Command | Action |
|---------|--------|
| `pod install` | Install dependencies |
| `pod update` | Update dependencies |
| Xcode Build (Cmd+B) | Build app |
| Xcode Run (Cmd+R) | Run on simulator/device |
### Terminal Build
```bash
xcodebuild -workspace iosApp.xcworkspace \
-scheme iosApp \
-sdk iphonesimulator \
-configuration Debug \
build
```
---
## KMP Framework Integration
### Shared Framework
```ruby
# Podfile
target 'iosApp' do
use_frameworks!
pod 'cmp_shared', :path => '../cmp-shared'
end
```
### Using Shared Code
```swift
import cmp_shared
struct ContentView: View {
var body: some View {
ComposeView()
}
}
```
---
## Xcode Project Structure
```
iosApp/
├── iosApp.xcodeproj/
├── iosApp.xcworkspace/
├── iosApp/
│ ├── Assets.xcassets/
│ ├── ContentView.swift
│ ├── Info.plist
│ └── iosApp.swift
└── Podfile
```
---
## iOS-Specific Features
| Feature | Implementation |
|---------|----------------|
| Biometrics | LocalAuthentication |
| Push Notifications | APNs |
| Deep Links | URL Schemes |
---
## Requirements
| Requirement | Version |
|-------------|---------|
| iOS Deployment Target | 14.0+ |
| Xcode | 15.0+ |
| CocoaPods | 1.12+ |
---
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Pod install fails | Run `pod repo update` first |
| Framework not found | Clean build folder (Cmd+Shift+K) |
| Simulator issues | Reset simulator content |
---
## Related
- [LAYER_STATUS.md](../LAYER_STATUS.md) - Platform overview
- [LAYER_GUIDE.md](../LAYER_GUIDE.md) - Architecture patterns

View File

@ -0,0 +1,135 @@
# Web Platform
## Module: cmp-web
> Kotlin/JS web application (Experimental)
---
## Status: Experimental
The web platform is currently in experimental status. Some features may not be fully functional.
---
## Build Commands
| Command | Action |
|---------|--------|
| `./gradlew :cmp-web:jsBrowserRun` | Run in browser (dev) |
| `./gradlew :cmp-web:jsBrowserProductionWebpack` | Production build |
| `./gradlew :cmp-web:jsBrowserDevelopmentWebpack` | Development build |
---
## Key Files
| File | Purpose |
|------|---------|
| `cmp-web/build.gradle.kts` | Module configuration |
| `cmp-web/src/jsMain/kotlin/.../Main.kt` | Entry point |
| `cmp-web/src/jsMain/resources/index.html` | HTML template |
---
## Gradle Configuration
```kotlin
kotlin {
js(IR) {
browser {
commonWebpackConfig {
cssSupport {
enabled.set(true)
}
}
}
binaries.executable()
}
}
```
---
## Entry Point
```kotlin
fun main() {
onWasmReady {
BrowserViewportWindow("Mifos Mobile") {
App()
}
}
}
```
---
## Output Location
| Build Type | Output |
|------------|--------|
| Development | `build/dist/js/developmentExecutable/` |
| Production | `build/dist/js/productionExecutable/` |
---
## Browser Support
| Browser | Status |
|---------|:------:|
| Chrome | ✅ |
| Firefox | ✅ |
| Safari | ⚠️ |
| Edge | ✅ |
---
## Web-Specific Limitations
| Feature | Status | Notes |
|---------|:------:|-------|
| Local Storage | ✅ | Using browser storage |
| Network | ✅ | CORS considerations |
| Biometrics | ❌ | Not available |
| Camera | ⚠️ | Browser permissions |
| Notifications | ⚠️ | Web Push API |
---
## Development Server
When running `jsBrowserRun`:
- Default URL: `http://localhost:8080`
- Hot reload enabled
- Source maps available
---
## Production Deployment
1. Build production bundle:
```bash
./gradlew :cmp-web:jsBrowserProductionWebpack
```
2. Deploy `build/dist/js/productionExecutable/` to web server
3. Configure server for SPA routing
---
## Known Issues
| Issue | Workaround |
|-------|------------|
| Large bundle size | Tree shaking, code splitting |
| CORS errors | Configure server headers |
| WebSocket issues | Use polling fallback |
---
## Related
- [LAYER_STATUS.md](../LAYER_STATUS.md) - Platform overview
- [LAYER_GUIDE.md](../LAYER_GUIDE.md) - Architecture patterns

View File

@ -0,0 +1,551 @@
# Fineract Self-Service API Index
> **Purpose**: Fast API lookup with service method references
> **Pattern**: Static table first → API_REFERENCE.md for details → Design layer for source
---
## Source of Truth Hierarchy
```
┌─────────────────────────────────────────────────────────────────┐
│ Design Layer: features/*/API.md │
│ └─→ ULTIMATE SOURCE OF TRUTH │
│ └─→ Where APIs are first designed/documented │
├─────────────────────────────────────────────────────────────────┤
│ Server Layer: (This directory) │
│ └─→ DERIVED but COMPLETE for client layer │
│ ├─→ API_INDEX.md (this file) - Quick lookup │
│ ├─→ API_REFERENCE.md - Complete endpoint details │
│ ├─→ CLIENT_PATTERNS.md - Service/Repository patterns │
│ └─→ ERROR_HANDLING.md - Exception handling │
├─────────────────────────────────────────────────────────────────┤
│ Client Layer: core/network/, core/data/ │
│ └─→ IMPLEMENTATION based on server layer docs │
└─────────────────────────────────────────────────────────────────┘
```
---
## Table of Contents
1. [Overview](#overview)
2. [Quick Lookup](#quick-lookup)
3. [By Category](#by-category)
4. [Common Patterns](#common-patterns)
5. [Lookup Strategy](#lookup-strategy)
6. [Update Flow](#update-flow)
7. [How to Add New API](#how-to-add-new-api)
---
## Overview
### Server Layer Documentation
| Document | Purpose |
|----------|---------|
| [API_INDEX.md](API_INDEX.md) | Quick lookup table (this file) |
| [endpoints/*.md](endpoints/) | **O(1) Lookup** - Category-specific endpoint docs |
| [API_REFERENCE.md](API_REFERENCE.md) | Complete endpoint overview |
| [CLIENT_PATTERNS.md](CLIENT_PATTERNS.md) | Service/Repository implementation patterns |
| [ERROR_HANDLING.md](ERROR_HANDLING.md) | Exception types and error extraction |
### Endpoint Files (O(1) Lookup)
| File | Category | Endpoints |
|------|----------|:---------:|
| [AUTH.md](endpoints/AUTH.md) | Authentication | 3 |
| [CLIENT.md](endpoints/CLIENT.md) | Client | 4 |
| [SAVINGS.md](endpoints/SAVINGS.md) | Savings Account | 7 |
| [LOAN.md](endpoints/LOAN.md) | Loan Account | 6 |
| [BENEFICIARY.md](endpoints/BENEFICIARY.md) | Beneficiary | 5 |
| [TRANSFER.md](endpoints/TRANSFER.md) | Transfer | 2 |
| [GUARANTOR.md](endpoints/GUARANTOR.md) | Guarantor | 5 |
| [NOTIFICATION.md](endpoints/NOTIFICATION.md) | Notification | 3 |
| [CHARGES.md](endpoints/CHARGES.md) | Charges | 3 |
| [SHARE.md](endpoints/SHARE.md) | Share Account | 3 |
| [USER.md](endpoints/USER.md) | User Settings | 1 |
### External References
| Resource | URL |
|----------|-----|
| **Swagger UI** | [sandbox.mifos.community/fineract-provider/swagger-ui](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html#/) |
| **Self-Service APIs** | Filter by `/self/` endpoints in Swagger |
| **Full API Docs** | [fineract.apache.org](https://fineract.apache.org/) |
### Base URL
```
https://{server}/fineract-provider/api/v1/self/
```
### Authentication
All requests require:
```
Headers:
Authorization: Basic {base64(username:password)}
Fineract-Platform-TenantId: {tenant}
Content-Type: application/json
```
### Demo Server
- **Server**: `tt.mifos.community` or `gsoc.mifos.community`
- **Tenant**: `mobile` or `default`
- **Test User**: `maria` / `password`
---
## Quick Lookup
| Endpoint | Method | Purpose | Service Method | Docs |
|----------|--------|---------|----------------|------|
| `/authentication` | POST | Login user | `authenticate()` | [auth](../design-spec-layer/features/auth/API.md) |
| `/registration` | POST | Register client | `register()` | [auth](../design-spec-layer/features/auth/API.md) |
| `/registration/user` | POST | Verify OTP | `verifyOtp()` | [auth](../design-spec-layer/features/auth/API.md) |
| `/clients` | GET | Get client list | `clients()` | [home](../design-spec-layer/features/home/API.md) |
| `/clients/{clientId}` | GET | Get client details | `getClientDetails()` | [home](../design-spec-layer/features/home/API.md) |
| `/clients/{clientId}/images` | GET | Get client image | `getClientImage()` | [home](../design-spec-layer/features/home/API.md) |
| `/clients/{clientId}/accounts` | GET | Get all accounts | `getClientAccounts()` | [accounts](../design-spec-layer/features/accounts/API.md) |
| `/savingsaccounts/{accountId}` | GET | Savings details | `getSavingsWithAssociations()` | [savings](../design-spec-layer/features/savings-account/API.md) |
| `/savingsaccounts` | POST | Apply for savings | `submitSavingAccountApplication()` | [savings](../design-spec-layer/features/savings-account/API.md) |
| `/savingsaccounts/{id}?command=withdrawnByApplicant` | POST | Withdraw application | `submitWithdrawSavingsAccount()` | [savings](../design-spec-layer/features/savings-account/API.md) |
| `/savingsaccounts/{id}/transactions` | GET | Savings transactions | `getSavingsAccountTransactionTemplate()` | [savings](../design-spec-layer/features/savings-account/API.md) |
| `/savingsproducts` | GET | Savings products | `getSavingsProducts()` | [savings](../design-spec-layer/features/savings-account/API.md) |
| `/loans/{loanId}` | GET | Loan details | `getLoanWithAssociations()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/loans` | POST | Apply for loan | `createLoansAccount()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/loans/{loanId}?command=withdrawnByApplicant` | POST | Withdraw loan | `withdrawLoanAccount()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/loans/{loanId}/transactions/{transId}` | GET | Loan transaction | `getLoanAccountTransaction()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/loanproducts` | GET | Loan products | `getLoanProducts()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/loans/{loanId}/template?templateType=repayment` | GET | Repayment template | `getLoanRepaymentTemplate()` | [loan](../design-spec-layer/features/loan-account/API.md) |
| `/products/share` | GET | Share products | `getShareProducts()` | [share](../design-spec-layer/features/share-account/API.md) |
| `/shareaccounts` | POST | Apply for shares | `submitShareApplication()` | [share](../design-spec-layer/features/share-account/API.md) |
| `/shareaccounts/{accountId}` | GET | Share details | `getShareAccountDetails()` | [share](../design-spec-layer/features/share-account/API.md) |
| `/beneficiaries/tpt` | GET | List beneficiaries | `beneficiaryList()` | [beneficiary](../design-spec-layer/features/beneficiary/API.md) |
| `/beneficiaries/tpt` | POST | Add beneficiary | `createBeneficiary()` | [beneficiary](../design-spec-layer/features/beneficiary/API.md) |
| `/beneficiaries/tpt/{id}` | PUT | Update beneficiary | `updateBeneficiary()` | [beneficiary](../design-spec-layer/features/beneficiary/API.md) |
| `/beneficiaries/tpt/{id}` | DELETE | Delete beneficiary | `deleteBeneficiary()` | [beneficiary](../design-spec-layer/features/beneficiary/API.md) |
| `/beneficiaries/tpt/template` | GET | Beneficiary template | `beneficiaryTemplate()` | [beneficiary](../design-spec-layer/features/beneficiary/API.md) |
| `/accounttransfers/template` | GET | Transfer template | `accountTransferTemplate()` | [transfer](../design-spec-layer/features/transfer/API.md) |
| `/accounttransfers` | POST | Execute transfer | `makeTransfer()` | [transfer](../design-spec-layer/features/transfer/API.md) |
| `/loans/{loanId}/guarantors` | GET | List guarantors | `getGuarantorList()` | [guarantor](../design-spec-layer/features/guarantor/API.md) |
| `/loans/{loanId}/guarantors/template` | GET | Guarantor template | `getGuarantorTemplate()` | [guarantor](../design-spec-layer/features/guarantor/API.md) |
| `/loans/{loanId}/guarantors` | POST | Add guarantor | `addGuarantor()` | [guarantor](../design-spec-layer/features/guarantor/API.md) |
| `/loans/{loanId}/guarantors/{id}` | PUT | Update guarantor | `updateGuarantor()` | [guarantor](../design-spec-layer/features/guarantor/API.md) |
| `/loans/{loanId}/guarantors/{id}` | DELETE | Delete guarantor | `deleteGuarantor()` | [guarantor](../design-spec-layer/features/guarantor/API.md) |
| `/device/registration/client/{clientId}` | GET | Get notification ID | `getUserNotificationId()` | [notification](../design-spec-layer/features/notification/API.md) |
| `/device/registration` | POST | Register device | `registerNotification()` | [notification](../design-spec-layer/features/notification/API.md) |
| `/device/registration/{id}` | PUT | Update registration | `updateRegisterNotification()` | [notification](../design-spec-layer/features/notification/API.md) |
| `/clients/{clientId}/charges` | GET | Client charges | `getClientChargeList()` | [client-charge](../design-spec-layer/features/client-charge/API.md) |
| `/loans/{loanId}/charges` | GET | Loan charges | `getChargeList()` | [client-charge](../design-spec-layer/features/client-charge/API.md) |
| `/savingsaccounts/{id}/charges` | GET | Savings charges | `getChargeList()` | [client-charge](../design-spec-layer/features/client-charge/API.md) |
| `/user/password` | PUT | Change password | `updatePassword()` | [settings](../design-spec-layer/features/settings/API.md) |
---
## By Category
### Authentication (3 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/authentication` | POST | `authenticate()` | Login with username/password |
| `/registration` | POST | `register()` | Register new client |
| `/registration/user` | POST | `verifyOtp()` | Verify OTP for registration |
**Service**: `AuthenticationService`
**Docs**: [auth/API.md](../design-spec-layer/features/auth/API.md)
---
### Client (3 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/clients` | GET | `clients()` | Get client list |
| `/clients/{clientId}` | GET | `getClientDetails()` | Get client details |
| `/clients/{clientId}/images` | GET | `getClientImage()` | Get client profile image |
**Service**: `ClientService`
**Docs**: [home/API.md](../design-spec-layer/features/home/API.md)
---
### Accounts (1 endpoint)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/clients/{clientId}/accounts` | GET | `getClientAccounts()` | Get all client accounts |
**Service**: `ClientService`
**Docs**: [accounts/API.md](../design-spec-layer/features/accounts/API.md)
---
### Savings Account (5 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/savingsaccounts/{accountId}` | GET | `getSavingsWithAssociations()` | Get savings details |
| `/savingsaccounts` | POST | `submitSavingAccountApplication()` | Apply for savings account |
| `/savingsaccounts/{id}?command=withdrawnByApplicant` | POST | `submitWithdrawSavingsAccount()` | Withdraw application |
| `/savingsaccounts/{id}/transactions` | GET | `getSavingsAccountTransactionTemplate()` | Get transaction history |
| `/savingsproducts` | GET | `getSavingsProducts()` | Get available products |
**Service**: `SavingAccountsService`
**Docs**: [savings-account/API.md](../design-spec-layer/features/savings-account/API.md)
---
### Loan Account (6 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/loans/{loanId}` | GET | `getLoanWithAssociations()` | Get loan details |
| `/loans` | POST | `createLoansAccount()` | Apply for loan |
| `/loans/{loanId}?command=withdrawnByApplicant` | POST | `withdrawLoanAccount()` | Withdraw application |
| `/loans/{loanId}/transactions/{transId}` | GET | `getLoanAccountTransaction()` | Get transaction details |
| `/loanproducts` | GET | `getLoanProducts()` | Get available products |
| `/loans/{loanId}/template?templateType=repayment` | GET | `getLoanRepaymentTemplate()` | Get repayment schedule |
**Service**: `LoanService`
**Docs**: [loan-account/API.md](../design-spec-layer/features/loan-account/API.md)
---
### Share Account (3 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/products/share` | GET | `getShareProducts()` | Get share products |
| `/shareaccounts` | POST | `submitShareApplication()` | Apply for shares |
| `/shareaccounts/{accountId}` | GET | `getShareAccountDetails()` | Get share details |
**Service**: `ShareAccountService`
**Docs**: [share-account/API.md](../design-spec-layer/features/share-account/API.md)
---
### Beneficiary (5 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/beneficiaries/tpt` | GET | `beneficiaryList()` | List all beneficiaries |
| `/beneficiaries/tpt` | POST | `createBeneficiary()` | Add beneficiary |
| `/beneficiaries/tpt/{id}` | PUT | `updateBeneficiary()` | Update beneficiary |
| `/beneficiaries/tpt/{id}` | DELETE | `deleteBeneficiary()` | Delete beneficiary |
| `/beneficiaries/tpt/template` | GET | `beneficiaryTemplate()` | Get template for adding |
**Service**: `BeneficiaryService`
**Docs**: [beneficiary/API.md](../design-spec-layer/features/beneficiary/API.md)
---
### Transfer (2 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/accounttransfers/template` | GET | `accountTransferTemplate()` | Get transfer template |
| `/accounttransfers` | POST | `makeTransfer()` | Execute transfer |
**Service**: `TransferService`
**Docs**: [transfer/API.md](../design-spec-layer/features/transfer/API.md)
---
### Guarantor (5 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/loans/{loanId}/guarantors` | GET | `getGuarantorList()` | List guarantors |
| `/loans/{loanId}/guarantors/template` | GET | `getGuarantorTemplate()` | Get add template |
| `/loans/{loanId}/guarantors` | POST | `addGuarantor()` | Add guarantor |
| `/loans/{loanId}/guarantors/{id}` | PUT | `updateGuarantor()` | Update guarantor |
| `/loans/{loanId}/guarantors/{id}` | DELETE | `deleteGuarantor()` | Delete guarantor |
**Service**: `GuarantorService`
**Docs**: [guarantor/API.md](../design-spec-layer/features/guarantor/API.md)
---
### Notification (3 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/device/registration/client/{clientId}` | GET | `getUserNotificationId()` | Get notification ID |
| `/device/registration` | POST | `registerNotification()` | Register device |
| `/device/registration/{id}` | PUT | `updateRegisterNotification()` | Update registration |
**Service**: `NotificationService`
**Docs**: [notification/API.md](../design-spec-layer/features/notification/API.md)
---
### Charges (3 endpoints)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/clients/{clientId}/charges` | GET | `getClientChargeList()` | Get client charges |
| `/loans/{loanId}/charges` | GET | `getChargeList()` | Get loan charges |
| `/savingsaccounts/{id}/charges` | GET | `getChargeList()` | Get savings charges |
**Service**: `ClientChargeService`
**Docs**: [client-charge/API.md](../design-spec-layer/features/client-charge/API.md)
---
### User Settings (1 endpoint)
| Endpoint | Method | Service Method | Purpose |
|----------|--------|----------------|---------|
| `/user/password` | PUT | `updatePassword()` | Change user password |
**Service**: `UserService`
**Docs**: [settings/API.md](../design-spec-layer/features/settings/API.md)
---
## Local-Only Features
These features do not require backend API calls:
| Feature | Storage | Notes |
|---------|---------|-------|
| QR Code | - | Local generation/scanning |
| Location | Static | Hardcoded branch locations |
| Passcode | DataStore | Local biometric/PIN storage |
---
## Common Patterns
### Date Format
```
Format: dd MMMM yyyy
Example: 15 January 2025
```
### Locale
```
locale=en
dateFormat=dd MMMM yyyy
```
### Error Responses
| Status | Description | Handling |
|--------|-------------|----------|
| 401 | Unauthorized | Redirect to login |
| 403 | Forbidden | Show permission error |
| 404 | Not found | Show "not found" message |
| 500 | Server error | Show generic error with retry |
### Error Response Format
```json
{
"developerMessage": "...",
"httpStatusCode": "...",
"defaultUserMessage": "...",
"userMessageGlobalisationCode": "...",
"errors": []
}
```
### Query Parameters
Common query parameters used across endpoints:
| Parameter | Usage | Example |
|-----------|-------|---------|
| `associations` | Include related data | `?associations=transactions` |
| `command` | Execute command | `?command=withdrawnByApplicant` |
| `templateType` | Specify template | `?templateType=repayment` |
| `locale` | Response locale | `?locale=en` |
| `dateFormat` | Date format | `?dateFormat=dd MMMM yyyy` |
---
## Lookup Strategy
```
┌─────────────────────────────────────────────────────────────────┐
│ STEP 1: STATIC LOOKUP - O(1) (Fastest) │
│ → Know category? Read endpoints/[CATEGORY].md directly │
│ → Don't know? Scan Endpoint Files table above │
│ → ~50-100 lines per file vs 600+ in single file │
├─────────────────────────────────────────────────────────────────┤
│ STEP 2: DYNAMIC SEARCH (If Not Found in Static) │
│ → Search codebase: core/network/services/*.kt │
│ → Search design layer: design-spec-layer/features/*/API.md │
│ → Check Swagger UI for endpoint existence │
├─────────────────────────────────────────────────────────────────┤
│ STEP 3: AUTO-UPDATE (After Dynamic Find or Manual Implement) │
│ → Add to endpoints/[CATEGORY].md (O(1) lookup) │
│ → Add row to API_INDEX.md Quick Lookup table │
│ → Update design-layer/*/API.md (source of truth) │
└─────────────────────────────────────────────────────────────────┘
```
### Step 1: Static Lookup (O(1))
**Direct file access by category:**
| Need | Read This File | Lines |
|------|----------------|:-----:|
| Auth/Login/Register | `endpoints/AUTH.md` | ~90 |
| Client/Profile | `endpoints/CLIENT.md` | ~80 |
| Savings Account | `endpoints/SAVINGS.md` | ~150 |
| Loan Account | `endpoints/LOAN.md` | ~120 |
| Beneficiary | `endpoints/BENEFICIARY.md` | ~130 |
| Transfer | `endpoints/TRANSFER.md` | ~90 |
| Guarantor | `endpoints/GUARANTOR.md` | ~100 |
| Notification | `endpoints/NOTIFICATION.md` | ~70 |
| Charges | `endpoints/CHARGES.md` | ~100 |
| Share Account | `endpoints/SHARE.md` | ~90 |
| Password/Settings | `endpoints/USER.md` | ~50 |
**Then for patterns:**
| Step | File | What to Find |
|------|------|--------------|
| 1b | `CLIENT_PATTERNS.md` | Service/Repository patterns |
| 1c | `ERROR_HANDLING.md` | Exception handling |
### Step 2: Dynamic Search (If Not Found)
**Search commands:**
```bash
# Search in service files
grep -r "@GET\|@POST\|@PUT\|@DELETE" core/network/src/commonMain/kotlin/**/services/
# Search for specific endpoint
grep -r "/beneficiaries" core/network/
# Search in design layer
grep -r "Endpoint" claude-product-cycle/design-spec-layer/features/*/API.md
# List all service files
ls core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/services/
```
**Claude Glob Patterns:**
```
core/network/**/services/*.kt
design-spec-layer/features/*/API.md
```
**External Reference:**
- [Swagger UI](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html#/) - Filter by `/self/`
### Step 3: Auto-Update Rules
| Scenario | Action |
|----------|--------|
| Found in static lookup | No update needed |
| Found via dynamic search | **ADD** to server layer docs |
| Manually implemented new API | **ADD** to server layer + design layer |
| API deprecated/removed | **REMOVE** from server layer |
**Update Checklist:**
- [ ] Add row to `API_INDEX.md` Quick Lookup table
- [ ] Add row to `API_INDEX.md` By Category table
- [ ] Add full documentation to `API_REFERENCE.md`
- [ ] Update `design-spec-layer/features/[feature]/API.md` (source)
---
## Update Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ NEW API DESIGNED │
│ └─→ Add to design-layer/features/*/API.md (SOURCE) │
├─────────────────────────────────────────────────────────────────┤
│ READY FOR CLIENT IMPLEMENTATION │
│ └─→ Add to server-layer/API_REFERENCE.md (DETAILS) │
│ └─→ Add row to server-layer/API_INDEX.md (INDEX) │
├─────────────────────────────────────────────────────────────────┤
│ CLIENT IMPLEMENTS │
│ └─→ Uses server-layer docs for implementation │
├─────────────────────────────────────────────────────────────────┤
│ FOUND DYNAMICALLY (not in docs) │
│ └─→ UPDATE server layer docs immediately │
└─────────────────────────────────────────────────────────────────┘
```
---
## How to Add New API
### Format for API_INDEX.md
```markdown
| /endpoint | METHOD | Purpose | `serviceMethod()` | [feature](link) |
```
### Format for API_REFERENCE.md
```markdown
### METHOD /endpoint
**Purpose**: Description
**Service**: `ServiceName.methodName()`
**Request**:
```json
{ ... }
```
**Response**:
```json
{ ... }
```
**DTO**: `DtoName`
```
---
## Service File Locations
```
core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/services/
├── AuthenticationService.kt
├── BeneficiaryService.kt
├── ClientChargeService.kt
├── ClientService.kt
├── GuarantorService.kt
├── LoanService.kt
├── NotificationService.kt
├── RegistrationService.kt
├── SavingAccountsService.kt
├── ShareAccountService.kt
├── ThirdPartyTransferService.kt
└── UserService.kt
```
---
## Related Files
### Server Layer (This Directory)
- [API_REFERENCE.md](API_REFERENCE.md) - Complete endpoint documentation
- [CLIENT_PATTERNS.md](CLIENT_PATTERNS.md) - Service/Repository patterns
- [ERROR_HANDLING.md](ERROR_HANDLING.md) - Exception handling reference
### Design Layer (Source of Truth)
- Feature API Docs: `design-spec-layer/features/*/API.md`
### Client Layer (Implementation)
- Service Implementations: `core/network/services/`
- Repository Layer: `core/data/repository/`
- Kotlin DTOs: `core/model/entity/`
---
## Changelog
| Date | Change |
|------|--------|
| 2025-01-05 | Restructured as part of modular server layer docs |
| 2025-01-05 | Created with merged content from FINERACT_API.md + feature API.md files |

View File

@ -0,0 +1,865 @@
# Fineract Self-Service API Reference
> **Purpose**: Complete endpoint documentation for client layer implementation
> **Derived from**: Design layer `features/*/API.md` files
> **Swagger UI**: [sandbox.mifos.community/swagger-ui](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html#/)
---
## Table of Contents
1. [Overview](#overview)
2. [Authentication](#authentication)
3. [Client](#client)
4. [Accounts](#accounts)
5. [Savings Account](#savings-account)
6. [Loan Account](#loan-account)
7. [Share Account](#share-account)
8. [Beneficiary](#beneficiary)
9. [Transfer](#transfer)
10. [Guarantor](#guarantor)
11. [Notification](#notification)
12. [Charges](#charges)
13. [User Settings](#user-settings)
---
## Overview
### Base URL
```
https://{server}/fineract-provider/api/v1/self/
```
### Servers
| Environment | Server |
|-------------|--------|
| Sandbox | `sandbox.mifos.community` |
| Demo | `tt.mifos.community` or `gsoc.mifos.community` |
### Headers (All Requests)
```
Authorization: Basic {base64(username:password)}
Fineract-Platform-TenantId: {tenant}
Content-Type: application/json
```
### Common Query Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `locale` | String | Response locale (default: "en") |
| `dateFormat` | String | Date format (default: "dd MMMM yyyy") |
| `associations` | String | Include related data |
---
## Authentication
### POST /authentication
**Purpose**: Login with username and password
**Service**: `AuthenticationService.authenticate()`
**Request**:
```json
{
"username": "string",
"password": "string"
}
```
**Response**:
```json
{
"userId": 123,
"username": "john_doe",
"clients": [456],
"isAuthenticated": true,
"base64EncodedAuthenticationKey": "encoded_key",
"officeName": "Head Office"
}
```
**DTO**: `User`
```kotlin
@Serializable
data class User(
val userId: Long,
val username: String?,
val clients: List<Long>,
val isAuthenticated: Boolean,
val base64EncodedAuthenticationKey: String?,
val officeName: String?,
)
```
---
### POST /registration
**Purpose**: Register new client
**Service**: `RegistrationService.register()`
**Request**:
```json
{
"accountNumber": "string",
"authenticationMode": "email",
"email": "user@example.com",
"firstName": "John",
"middleName": "M",
"lastName": "Doe",
"mobileNumber": "1234567890",
"password": "securePassword123",
"username": "john_doe"
}
```
**Response**:
```json
{
"requestId": "12345"
}
```
**DTO**: `RegisterPayload`
---
### POST /registration/user
**Purpose**: Verify OTP for registration
**Service**: `RegistrationService.verifyOtp()`
**Request**:
```json
{
"authenticationToken": "123456",
"requestId": "12345"
}
```
**Response**:
```json
{
"message": "User verified successfully"
}
```
---
## Client
### GET /clients
**Purpose**: Get client list for authenticated user
**Service**: `ClientService.clients()`
**Response**:
```json
{
"pageItems": [
{
"id": 1,
"accountNo": "000000001",
"displayName": "John Doe",
"officeId": 1,
"officeName": "Head Office"
}
]
}
```
**DTO**: `Page<Client>`
---
### GET /clients/{clientId}
**Purpose**: Get client details
**Service**: `ClientService.getClientDetails(clientId)`
**Response**:
```json
{
"id": 1,
"accountNo": "000000001",
"displayName": "John Doe",
"firstname": "John",
"lastname": "Doe",
"mobileNo": "1234567890",
"emailAddress": "john@example.com",
"dateOfBirth": [1990, 1, 15],
"officeId": 1,
"officeName": "Head Office"
}
```
**DTO**: `Client`
---
### GET /clients/{clientId}/images
**Purpose**: Get client profile image
**Service**: `ClientService.getClientImage(clientId)`
**Response**: Base64 encoded image or image URL
---
### GET /clients/{clientId}/accounts
**Purpose**: Get all accounts for a client
**Service**: `ClientService.getClientAccounts(clientId)`
**Response**:
```json
{
"savingsAccounts": [
{
"id": 12345,
"accountNo": "000000012345",
"productName": "Basic Savings",
"accountBalance": 1234.56,
"status": { "id": 300, "value": "Active" }
}
],
"loanAccounts": [
{
"id": 67890,
"accountNo": "000000067890",
"productName": "Personal Loan",
"loanBalance": 5000.00,
"status": { "id": 300, "value": "Active" }
}
],
"shareAccounts": []
}
```
**DTO**: `ClientAccounts`
---
## Savings Account
### GET /savingsaccounts/{accountId}
**Purpose**: Get savings account details with optional associations
**Service**: `SavingAccountsListService.getSavingsWithAssociations(accountId, associations)`
**Query Parameters**:
| Parameter | Type | Values |
|-----------|------|--------|
| `associations` | String | `transactions`, `charges` |
**Response**:
```json
{
"id": 12345,
"accountNo": "000000012345",
"depositType": { "id": 100, "value": "Savings" },
"clientId": 1,
"clientName": "John Doe",
"savingsProductId": 1,
"savingsProductName": "Basic Savings",
"status": {
"id": 300,
"code": "savingsAccountStatusType.active",
"value": "Active"
},
"currency": {
"code": "USD",
"displaySymbol": "$",
"decimalPlaces": 2
},
"summary": {
"totalDeposits": 5000.00,
"totalWithdrawals": 3765.44,
"accountBalance": 1234.56
},
"transactions": [...]
}
```
**DTO**: `SavingsWithAssociations`
---
### GET /savingsaccounts/template
**Purpose**: Get template for savings application
**Service**: `SavingAccountsListService.getSavingsAccountApplicationTemplate(clientId)`
**Query Parameters**:
| Parameter | Type | Required |
|-----------|------|----------|
| `clientId` | Long | Yes |
| `productId` | Long | No |
**Response**:
```json
{
"clientId": 1,
"clientName": "John Doe",
"productOptions": [
{
"id": 1,
"name": "Basic Savings",
"allowOverdraft": false
}
]
}
```
**DTO**: `SavingsAccountTemplate`
---
### POST /savingsaccounts
**Purpose**: Submit savings account application
**Service**: `SavingAccountsListService.submitSavingAccountApplication(payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"submittedOnDate": "29 December 2025"
}
```
**Response**:
```json
{
"officeId": 1,
"clientId": 1,
"savingsId": 12345,
"resourceId": 12345
}
```
**DTO**: `SavingsAccountApplicationPayload`
---
### PUT /savingsaccounts/{accountId}
**Purpose**: Update pending savings account
**Service**: `SavingAccountsListService.updateSavingsAccountUpdate(accountId, payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 2
}
```
**DTO**: `SavingsAccountUpdatePayload`
---
### POST /savingsaccounts/{savingsId}?command=withdrawnByApplicant
**Purpose**: Withdraw savings application
**Service**: `SavingAccountsListService.submitWithdrawSavingsAccount(savingsId, payload)`
**Request**:
```json
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "29 December 2025",
"note": "User requested withdrawal"
}
```
**DTO**: `SavingsAccountWithdrawPayload`
---
## Loan Account
### GET /loans/{loanId}
**Purpose**: Get loan account details with associations
**Service**: `LoanService.getLoanWithAssociations(loanId, associations)`
**Query Parameters**:
| Parameter | Values |
|-----------|--------|
| `associations` | `transactions`, `repaymentSchedule`, `charges` |
**Response**:
```json
{
"id": 67890,
"accountNo": "000000067890",
"clientId": 1,
"clientName": "John Doe",
"loanProductId": 1,
"loanProductName": "Personal Loan",
"principal": 10000.00,
"status": { "id": 300, "value": "Active" },
"summary": {
"principalDisbursed": 10000.00,
"principalPaid": 5000.00,
"principalOutstanding": 5000.00,
"interestCharged": 500.00,
"totalExpectedRepayment": 10500.00
},
"repaymentSchedule": {...},
"transactions": [...]
}
```
**DTO**: `LoanWithAssociations`
---
### GET /loanproducts
**Purpose**: Get available loan products
**Service**: `LoanService.getLoanProducts()`
**Response**:
```json
[
{
"id": 1,
"name": "Personal Loan",
"principal": 10000.00,
"interestRatePerPeriod": 12.0
}
]
```
**DTO**: `List<LoanProduct>`
---
### POST /loans
**Purpose**: Submit loan application
**Service**: `LoanService.createLoansAccount(payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"principal": 10000.00,
"loanTermFrequency": 12,
"loanTermFrequencyType": 2,
"numberOfRepayments": 12,
"repaymentEvery": 1,
"repaymentFrequencyType": 2,
"interestRatePerPeriod": 12.0,
"expectedDisbursementDate": "29 December 2025",
"submittedOnDate": "29 December 2025",
"locale": "en",
"dateFormat": "dd MMMM yyyy"
}
```
**DTO**: `LoansPayload`
---
### POST /loans/{loanId}?command=withdrawnByApplicant
**Purpose**: Withdraw loan application
**Service**: `LoanService.withdrawLoanAccount(loanId, payload)`
---
### GET /loans/{loanId}/template?templateType=repayment
**Purpose**: Get loan repayment template
**Service**: `LoanService.getLoanRepaymentTemplate(loanId)`
---
### GET /loans/{loanId}/transactions/{transactionId}
**Purpose**: Get loan transaction details
**Service**: `LoanService.getLoanAccountTransaction(loanId, transactionId)`
---
## Share Account
### GET /products/share
**Purpose**: Get available share products
**Service**: `ShareAccountService.getShareProducts()`
**Response**:
```json
[
{
"id": 1,
"name": "Common Shares",
"shortName": "CS",
"unitPrice": 100.00,
"currency": { "code": "USD", "displaySymbol": "$" }
}
]
```
---
### POST /shareaccounts
**Purpose**: Submit share account application
**Service**: `ShareAccountService.submitShareApplication(payload)`
---
### GET /shareaccounts/{accountId}
**Purpose**: Get share account details
**Service**: `ShareAccountService.getShareAccountDetails(accountId)`
---
## Beneficiary
### GET /beneficiaries/tpt
**Purpose**: Get list of beneficiaries
**Service**: `BeneficiaryService.beneficiaryList()`
**Response**:
```json
[
{
"id": 5001,
"name": "John Doe",
"officeName": "Head Office",
"clientName": "John Doe",
"accountType": { "id": 2, "value": "Savings Account" },
"accountNumber": "SA-0001234567",
"transferLimit": 10000.00
}
]
```
**DTO**: `Beneficiary`
---
### GET /beneficiaries/tpt/template
**Purpose**: Get beneficiary template (account type options)
**Service**: `BeneficiaryService.beneficiaryTemplate()`
**Response**:
```json
{
"accountTypeOptions": [
{ "id": 0, "value": "Share Account" },
{ "id": 1, "value": "Loan Account" },
{ "id": 2, "value": "Savings Account" }
]
}
```
**DTO**: `BeneficiaryTemplate`
---
### POST /beneficiaries/tpt
**Purpose**: Create new beneficiary
**Service**: `BeneficiaryService.createBeneficiary(payload)`
**Request**:
```json
{
"locale": "en",
"name": "John Doe",
"accountNumber": "SA-0001234567",
"accountType": 2,
"transferLimit": 10000,
"officeName": "Head Office"
}
```
**Response**:
```json
{
"resourceId": 5003
}
```
**DTO**: `BeneficiaryPayload`
---
### PUT /beneficiaries/tpt/{beneficiaryId}
**Purpose**: Update beneficiary
**Service**: `BeneficiaryService.updateBeneficiary(beneficiaryId, payload)`
**Request**:
```json
{
"name": "John Doe Updated",
"transferLimit": 15000
}
```
**DTO**: `BeneficiaryUpdatePayload`
---
### DELETE /beneficiaries/tpt/{beneficiaryId}
**Purpose**: Delete beneficiary
**Service**: `BeneficiaryService.deleteBeneficiary(beneficiaryId)`
---
## Transfer
### GET /accounttransfers/template
**Purpose**: Get transfer template with account options
**Service**: `SavingAccountsListService.accountTransferTemplate(fromAccountId, fromAccountType)`
**Query Parameters**:
| Parameter | Type |
|-----------|------|
| `fromAccountId` | Long |
| `fromAccountType` | Long (2 = Savings) |
**Response**:
```json
{
"fromAccountTypeOptions": [...],
"toAccountTypeOptions": [...],
"fromAccountOptions": [
{
"accountId": 12345,
"accountNo": "000000012345",
"accountType": { "id": 2, "value": "Savings Account" },
"clientId": 1,
"clientName": "John Doe"
}
],
"toAccountOptions": [...]
}
```
**DTO**: `AccountOptionsTemplate`
---
### POST /accounttransfers
**Purpose**: Execute account transfer
**Service**: `SavingAccountsListService.makeTransfer(payload)`
**Request**:
```json
{
"fromOfficeId": 1,
"fromClientId": 1,
"fromAccountType": 2,
"fromAccountId": 12345,
"toOfficeId": 1,
"toClientId": 2,
"toAccountType": 2,
"toAccountId": 12346,
"transferDate": "29 December 2025",
"transferAmount": 100.00,
"transferDescription": "Transfer to beneficiary",
"dateFormat": "dd MMMM yyyy",
"locale": "en"
}
```
**DTO**: `TransferPayload`
---
## Guarantor
### GET /loans/{loanId}/guarantors
**Purpose**: Get list of guarantors for a loan
**Service**: `GuarantorService.getGuarantorList(loanId)`
---
### GET /loans/{loanId}/guarantors/template
**Purpose**: Get guarantor template
**Service**: `GuarantorService.getGuarantorTemplate(loanId)`
---
### POST /loans/{loanId}/guarantors
**Purpose**: Add guarantor to loan
**Service**: `GuarantorService.addGuarantor(loanId, payload)`
---
### PUT /loans/{loanId}/guarantors/{guarantorId}
**Purpose**: Update guarantor
**Service**: `GuarantorService.updateGuarantor(loanId, guarantorId, payload)`
---
### DELETE /loans/{loanId}/guarantors/{guarantorId}
**Purpose**: Delete guarantor
**Service**: `GuarantorService.deleteGuarantor(loanId, guarantorId)`
---
## Notification
### GET /device/registration/client/{clientId}
**Purpose**: Get notification registration ID
**Service**: `NotificationService.getUserNotificationId(clientId)`
---
### POST /device/registration
**Purpose**: Register device for notifications
**Service**: `NotificationService.registerNotification(payload)`
**Request**:
```json
{
"clientId": 1,
"registrationId": "fcm_token_here"
}
```
---
### PUT /device/registration/{id}
**Purpose**: Update notification registration
**Service**: `NotificationService.updateRegisterNotification(id, payload)`
---
## Charges
### GET /clients/{clientId}/charges
**Purpose**: Get client charges
**Service**: `ClientChargeService.getClientChargeList(clientId)`
**Response**:
```json
{
"totalFilteredRecords": 3,
"pageItems": [
{
"clientId": 1,
"chargeId": 101,
"name": "Processing Fee",
"dueDate": [2025, 1, 15],
"amount": 50.00,
"amountPaid": 0.00,
"amountOutstanding": 50.00,
"penalty": false,
"isActive": true,
"paid": false
}
]
}
```
**DTO**: `Page<Charge>`
---
### GET /loans/{loanId}/charges
**Purpose**: Get loan charges
**Service**: `ClientChargeService.getChargeList("loans", loanId)`
---
### GET /savingsaccounts/{accountId}/charges
**Purpose**: Get savings account charges
**Service**: `ClientChargeService.getChargeList("savingsaccounts", accountId)`
---
## User Settings
### PUT /user/password
**Purpose**: Update user password
**Service**: `UserService.updatePassword(payload)`
**Request**:
```json
{
"newPassword": "newSecurePassword123",
"confirmPassword": "newSecurePassword123"
}
```
---
## Local-Only Features
These features do not require API calls:
| Feature | Storage | Notes |
|---------|---------|-------|
| QR Code | - | Local generation/scanning |
| Location | Static | Hardcoded branch locations |
| Passcode | DataStore | Local biometric/PIN |
---
## Error Response Format
All endpoints return errors in this format:
```json
{
"developerMessage": "Detailed error for debugging",
"httpStatusCode": "400",
"defaultUserMessage": "User-friendly message",
"userMessageGlobalisationCode": "error.msg.code",
"errors": []
}
```
See [ERROR_HANDLING.md](ERROR_HANDLING.md) for complete error handling guide.
---
## Related Files
- Quick Lookup: [API_INDEX.md](API_INDEX.md)
- Client Patterns: [CLIENT_PATTERNS.md](CLIENT_PATTERNS.md)
- Error Handling: [ERROR_HANDLING.md](ERROR_HANDLING.md)
- Design Layer: `design-spec-layer/features/*/API.md`
---
## Changelog
| Date | Change |
|------|--------|
| 2025-01-05 | Created - aggregated from design layer API.md files |

View File

@ -0,0 +1,517 @@
# Client Layer Implementation Patterns
> **Purpose**: Service and Repository implementation patterns for client layer
> **Derived from**: Design layer API.md files
> **Location**: `core/network/` (services), `core/data/` (repositories)
---
## Table of Contents
1. [Overview](#overview)
2. [Service Interface Pattern](#service-interface-pattern)
3. [Repository Interface Pattern](#repository-interface-pattern)
4. [Repository Implementation Pattern](#repository-implementation-pattern)
5. [DTO Patterns](#dto-patterns)
6. [DataState Pattern](#datastate-pattern)
7. [Flow vs Suspend Guidelines](#flow-vs-suspend-guidelines)
8. [Complete Examples](#complete-examples)
---
## Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ SERVICE (core/network/services/) │
│ └─→ Ktorfit interface with HTTP annotations │
│ └─→ Returns Flow<T> or HttpResponse │
├─────────────────────────────────────────────────────────────────┤
│ REPOSITORY INTERFACE (core/data/repository/) │
│ └─→ Defines contract for data operations │
│ └─→ Returns Flow<DataState<T>> or DataState<T>
├─────────────────────────────────────────────────────────────────┤
│ REPOSITORY IMPLEMENTATION (core/data/repositoryImpl/) │
│ └─→ Implements repository interface │
│ └─→ Handles error mapping and dispatcher context │
└─────────────────────────────────────────────────────────────────┘
```
---
## Service Interface Pattern
### Location
`core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/services/`
### Template
```kotlin
package org.mifos.mobile.core.network.services
import de.jensklingenberg.ktorfit.http.*
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface [Feature]Service {
// GET - List (returns typed Flow)
@GET(ApiEndPoints.[FEATURE])
fun get[Feature]List(): Flow<List<[Entity]>>
// GET - Single item (returns typed Flow)
@GET(ApiEndPoints.[FEATURE] + "/{id}")
fun get[Feature]Details(
@Path("id") id: Long,
): Flow<[Entity]>
// GET - With query params
@GET(ApiEndPoints.[FEATURE] + "/{id}")
fun get[Feature]WithAssociations(
@Path("id") id: Long,
@Query("associations") associations: String?,
): Flow<[EntityWithAssociations]>
// GET - Template
@GET(ApiEndPoints.[FEATURE] + "/template")
fun get[Feature]Template(): Flow<[Feature]Template>
// POST - Create (returns HttpResponse for error handling)
@POST(ApiEndPoints.[FEATURE])
suspend fun create[Feature](
@Body payload: [Feature]Payload?,
): HttpResponse
// PUT - Update
@PUT(ApiEndPoints.[FEATURE] + "/{id}")
suspend fun update[Feature](
@Path("id") id: Long,
@Body payload: [Feature]UpdatePayload?,
): HttpResponse
// DELETE - Remove
@DELETE(ApiEndPoints.[FEATURE] + "/{id}")
suspend fun delete[Feature](
@Path("id") id: Long,
): HttpResponse
// POST - With command
@POST(ApiEndPoints.[FEATURE] + "/{id}?command=action")
suspend fun [feature]Action(
@Path("id") id: Long,
@Body payload: [Action]Payload?,
): HttpResponse
}
```
### Key Rules
| Annotation | Usage | Return Type |
|------------|-------|-------------|
| `@GET` | Read operations | `Flow<T>` |
| `@POST` | Create operations | `suspend HttpResponse` |
| `@PUT` | Update operations | `suspend HttpResponse` |
| `@DELETE` | Delete operations | `suspend HttpResponse` |
| `@Path` | URL path variable | N/A |
| `@Query` | Query parameter | N/A |
| `@Body` | Request body | N/A |
### When to Use Flow vs HttpResponse
| Scenario | Return Type | Reason |
|----------|-------------|--------|
| GET typed response | `Flow<T>` | Direct deserialization |
| POST/PUT/DELETE | `suspend HttpResponse` | Need to handle errors manually |
| GET with error handling | `suspend HttpResponse` | Custom error extraction |
---
## Repository Interface Pattern
### Location
`core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repository/`
### Template
```kotlin
package org.mifos.mobile.core.data.repository
import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.common.DataState
interface [Feature]Repository {
// GET - List
fun get[Feature]List(): Flow<DataState<List<[Entity]>>>
// GET - Single
fun get[Feature]Details(id: Long): Flow<DataState<[Entity]>>
// GET - Template
fun get[Feature]Template(): Flow<DataState<[Feature]Template>>
// POST - Create
suspend fun create[Feature](payload: [Feature]Payload?): DataState<String>
// PUT - Update
suspend fun update[Feature](
id: Long?,
payload: [Feature]UpdatePayload?,
): DataState<String>
// DELETE - Remove
suspend fun delete[Feature](id: Long?): DataState<String>
}
```
### Key Rules
| Operation | Return Type | Pattern |
|-----------|-------------|---------|
| Read (list/single) | `Flow<DataState<T>>` | Streaming updates |
| Create/Update/Delete | `DataState<T>` | One-shot operation |
| Template fetching | `Flow<DataState<T>>` | May update |
---
## Repository Implementation Pattern
### Location
`core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/`
### Template
```kotlin
package org.mifos.mobile.core.data.repositoryImpl
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.mifos.mobile.core.common.DataState
import org.mifos.mobile.core.network.DataManager
import java.io.IOException
class [Feature]RepositoryImpl(
private val dataManager: DataManager,
private val ioDispatcher: CoroutineDispatcher,
) : [Feature]Repository {
// Pattern 1: Flow-based GET (for lists and details)
override fun get[Feature]List(): Flow<DataState<List<[Entity]>>> = flow {
try {
dataManager.[feature]Api.get[Feature]List()
.collect { response ->
emit(DataState.Success(response))
}
} catch (e: Exception) {
emit(DataState.Error(e, null))
}
}.flowOn(ioDispatcher)
// Pattern 2: Alternative Flow with map/catch
override fun get[Feature]Details(id: Long): Flow<DataState<[Entity]>> {
return dataManager.[feature]Api.get[Feature]Details(id)
.map { DataState.Success(it) }
.catch { emit(DataState.Error(it, null)) }
.flowOn(ioDispatcher)
}
// Pattern 3: Suspend-based POST/PUT/DELETE with error handling
override suspend fun create[Feature](payload: [Feature]Payload?): DataState<String> {
return withContext(ioDispatcher) {
try {
val response = dataManager.[feature]Api.create[Feature](payload)
DataState.Success(response.bodyAsText())
} catch (e: ClientRequestException) {
val errorMessage = extractErrorMessage(e.response)
DataState.Error(Exception(errorMessage), null)
} catch (e: IOException) {
DataState.Error(
Exception("Network error: ${e.message ?: "Please check your connection"}"),
null
)
} catch (e: ServerResponseException) {
DataState.Error(Exception("Server error: ${e.message}"), null)
}
}
}
// Pattern 4: Update with path parameter
override suspend fun update[Feature](
id: Long?,
payload: [Feature]UpdatePayload?,
): DataState<String> {
return withContext(ioDispatcher) {
try {
val response = dataManager.[feature]Api.update[Feature](id!!, payload)
DataState.Success(response.bodyAsText())
} catch (e: ClientRequestException) {
val errorMessage = extractErrorMessage(e.response)
DataState.Error(Exception(errorMessage), null)
} catch (e: IOException) {
DataState.Error(
Exception("Network error: ${e.message ?: "Please check your connection"}"),
null
)
} catch (e: ServerResponseException) {
DataState.Error(Exception("Server error: ${e.message}"), null)
}
}
}
// Pattern 5: Delete operation
override suspend fun delete[Feature](id: Long?): DataState<String> {
return withContext(ioDispatcher) {
try {
val response = dataManager.[feature]Api.delete[Feature](id!!)
DataState.Success(response.bodyAsText())
} catch (e: ClientRequestException) {
val errorMessage = extractErrorMessage(e.response)
DataState.Error(Exception(errorMessage), null)
} catch (e: IOException) {
DataState.Error(
Exception("Network error: ${e.message ?: "Please check your connection"}"),
null
)
} catch (e: ServerResponseException) {
DataState.Error(Exception("Server error: ${e.message}"), null)
}
}
}
}
```
---
## DTO Patterns
### Location
`core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/`
### Response DTO (Entity)
```kotlin
@Serializable
@Parcelize
data class [Entity](
val id: Long? = null,
val name: String? = null,
val status: Status? = null,
val createdDate: List<Int>? = null,
// All fields nullable with defaults
) : Parcelable
```
### Request DTO (Payload)
```kotlin
@Serializable
@Parcelize
data class [Feature]Payload(
val locale: String? = null,
val dateFormat: String? = null,
val name: String? = null,
val amount: Double? = null,
// Fields matching API request body
) : Parcelable
```
### Update DTO
```kotlin
@Serializable
data class [Feature]UpdatePayload(
val name: String? = null,
val amount: Int = 0,
// Only updatable fields
)
```
### Template DTO
```kotlin
@Serializable
@Parcelize
data class [Feature]Template(
val options: List<Option>? = null,
val defaultValue: String? = null,
) : Parcelable
```
### DTO Rules
| Rule | Example |
|------|---------|
| All fields nullable | `val name: String? = null` |
| Use default values | `val amount: Double = 0.0` |
| Add `@Serializable` | For JSON parsing |
| Add `@Parcelize` | For Android state saving |
| Implement `Parcelable` | For navigation args |
---
## DataState Pattern
### Definition
```kotlin
sealed class DataState<out T> {
data class Success<T>(val data: T) : DataState<T>()
data class Error(val exception: Throwable, val message: String?) : DataState<Nothing>()
data object Loading : DataState<Nothing>()
}
```
### Usage in ViewModel
```kotlin
class [Feature]ViewModel(
private val repository: [Feature]Repository,
) : ViewModel() {
private val _state = MutableStateFlow([Feature]State())
val state: StateFlow<[Feature]State> = _state.asStateFlow()
fun load[Feature]() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
repository.get[Feature]List().collect { result ->
when (result) {
is DataState.Success -> {
_state.update {
it.copy(
isLoading = false,
items = result.data,
)
}
}
is DataState.Error -> {
_state.update {
it.copy(
isLoading = false,
error = result.message,
)
}
}
is DataState.Loading -> {
_state.update { it.copy(isLoading = true) }
}
}
}
}
}
}
```
---
## Flow vs Suspend Guidelines
### Use Flow When
| Scenario | Example |
|----------|---------|
| Streaming data | `fun observeItems(): Flow<DataState<List<T>>>` |
| Real-time updates | WebSocket connections |
| List operations | `fun getList(): Flow<DataState<List<T>>>` |
| May emit multiple values | Pagination |
### Use Suspend When
| Scenario | Example |
|----------|---------|
| One-shot operations | `suspend fun create(): DataState<T>` |
| POST/PUT/DELETE | Write operations |
| Single response | `suspend fun getOnce(): DataState<T>` |
| Error handling needed | HTTP response inspection |
---
## Complete Examples
### Beneficiary Service
```kotlin
interface BeneficiaryService {
@GET(ApiEndPoints.BENEFICIARIES + "/tpt")
fun beneficiaryList(): Flow<List<Beneficiary>>
@GET(ApiEndPoints.BENEFICIARIES + "/tpt/template")
fun beneficiaryTemplate(): Flow<BeneficiaryTemplate>
@POST(ApiEndPoints.BENEFICIARIES + "/tpt")
suspend fun createBeneficiary(@Body payload: BeneficiaryPayload?): HttpResponse
@PUT(ApiEndPoints.BENEFICIARIES + "/tpt/{beneficiaryId}")
suspend fun updateBeneficiary(
@Path("beneficiaryId") beneficiaryId: Long,
@Body payload: BeneficiaryUpdatePayload?,
): HttpResponse
@DELETE(ApiEndPoints.BENEFICIARIES + "/tpt/{beneficiaryId}")
suspend fun deleteBeneficiary(@Path("beneficiaryId") beneficiaryId: Long): HttpResponse
}
```
### Beneficiary Repository Interface
```kotlin
interface BeneficiaryRepository {
fun beneficiaryTemplate(): Flow<DataState<BeneficiaryTemplate>>
suspend fun createBeneficiary(payload: BeneficiaryPayload?): DataState<String>
suspend fun updateBeneficiary(id: Long?, payload: BeneficiaryUpdatePayload?): DataState<String>
suspend fun deleteBeneficiary(id: Long?): DataState<String>
fun beneficiaryList(): Flow<DataState<List<Beneficiary>>>
}
```
---
## Koin Module Registration
### Location
`feature/[name]/di/[Feature]Module.kt`
```kotlin
val [Feature]Module = module {
viewModelOf(::[Feature]ViewModel)
}
```
### DataManager Access
```kotlin
// DataManager provides access to all services
class DataManager(
val authApi: AuthenticationService,
val beneficiaryApi: BeneficiaryService,
val clientApi: ClientService,
val savingsApi: SavingAccountsListService,
// ... other services
)
```
---
## Related Files
- Error Handling: [ERROR_HANDLING.md](ERROR_HANDLING.md)
- API Reference: [API_REFERENCE.md](API_REFERENCE.md)
- API Index: [API_INDEX.md](API_INDEX.md)
- Design Layer API.md: `design-spec-layer/features/*/API.md`
---
## Changelog
| Date | Change |
|------|--------|
| 2025-01-05 | Created with patterns from codebase analysis |

View File

@ -0,0 +1,415 @@
# Error Handling Reference
> **Purpose**: Exception types, error extraction, and HTTP status code handling
> **Derived from**: Design layer API.md files and repository implementations
> **Used by**: Client layer repository implementations
---
## Table of Contents
1. [Exception Types](#exception-types)
2. [Error Response Format](#error-response-format)
3. [Error Extraction](#error-extraction)
4. [HTTP Status Codes](#http-status-codes)
5. [Error Handling Patterns](#error-handling-patterns)
6. [User-Friendly Messages](#user-friendly-messages)
---
## Exception Types
### Ktor Client Exceptions
| Exception | Package | HTTP Codes | When Thrown |
|-----------|---------|------------|-------------|
| `ClientRequestException` | `io.ktor.client.plugins` | 400-499 | Client errors (bad request, unauthorized, not found) |
| `ServerResponseException` | `io.ktor.client.plugins` | 500-599 | Server errors (internal error, service unavailable) |
| `IOException` | `java.io` | N/A | Network errors (no connection, timeout) |
### Import Statements
```kotlin
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import java.io.IOException
```
---
## Error Response Format
### Fineract Standard Error Response
```json
{
"developerMessage": "Detailed technical error message for debugging",
"httpStatusCode": "400",
"defaultUserMessage": "User-friendly error message",
"userMessageGlobalisationCode": "error.msg.beneficiary.account.not.found",
"errors": [
{
"developerMessage": "Field-specific error details",
"defaultUserMessage": "Invalid account number format",
"userMessageGlobalisationCode": "validation.msg.accountNumber.invalid",
"parameterName": "accountNumber"
}
]
}
```
### Error Response DTO
```kotlin
@Serializable
data class ErrorResponse(
val developerMessage: String? = null,
val httpStatusCode: String? = null,
val defaultUserMessage: String? = null,
val userMessageGlobalisationCode: String? = null,
val errors: List<FieldError>? = null,
)
@Serializable
data class FieldError(
val developerMessage: String? = null,
val defaultUserMessage: String? = null,
val userMessageGlobalisationCode: String? = null,
val parameterName: String? = null,
)
```
---
## Error Extraction
### Basic Error Extraction
```kotlin
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json
private suspend fun extractErrorMessage(response: HttpResponse): String {
return try {
val body = response.bodyAsText()
val json = Json { ignoreUnknownKeys = true }
val errorResponse = json.decodeFromString<ErrorResponse>(body)
// Priority: defaultUserMessage > developerMessage > fallback
errorResponse.defaultUserMessage
?: errorResponse.developerMessage
?: "An error occurred"
} catch (e: Exception) {
"An error occurred"
}
}
```
### Field-Level Error Extraction
```kotlin
private suspend fun extractFieldErrors(response: HttpResponse): Map<String, String> {
return try {
val body = response.bodyAsText()
val json = Json { ignoreUnknownKeys = true }
val errorResponse = json.decodeFromString<ErrorResponse>(body)
errorResponse.errors?.associate { error ->
(error.parameterName ?: "unknown") to (error.defaultUserMessage ?: "Invalid value")
} ?: emptyMap()
} catch (e: Exception) {
emptyMap()
}
}
```
### Error Extraction with Logging
```kotlin
private suspend fun extractErrorMessageWithLogging(response: HttpResponse): String {
return try {
val body = response.bodyAsText()
val json = Json { ignoreUnknownKeys = true }
val errorResponse = json.decodeFromString<ErrorResponse>(body)
// Log developer message for debugging
Log.e("API_ERROR", "Status: ${errorResponse.httpStatusCode}")
Log.e("API_ERROR", "Developer: ${errorResponse.developerMessage}")
Log.e("API_ERROR", "User: ${errorResponse.defaultUserMessage}")
errorResponse.defaultUserMessage ?: "An error occurred"
} catch (e: Exception) {
Log.e("API_ERROR", "Failed to parse error: ${e.message}")
"An error occurred"
}
}
```
---
## HTTP Status Codes
### Client Errors (4xx)
| Code | Name | Description | User Message |
|------|------|-------------|--------------|
| 400 | Bad Request | Invalid request payload | "Please check your input" |
| 401 | Unauthorized | Invalid/expired token | "Please login again" |
| 403 | Forbidden | Insufficient permissions | "Access denied" |
| 404 | Not Found | Resource doesn't exist | "Not found" |
| 409 | Conflict | Duplicate resource | "Already exists" |
| 422 | Unprocessable | Validation failed | "Invalid data" |
### Server Errors (5xx)
| Code | Name | Description | User Message |
|------|------|-------------|--------------|
| 500 | Internal Server Error | Server bug | "Service unavailable" |
| 502 | Bad Gateway | Upstream error | "Service temporarily unavailable" |
| 503 | Service Unavailable | Server overloaded | "Service temporarily unavailable" |
| 504 | Gateway Timeout | Upstream timeout | "Request timed out" |
### Network Errors
| Error | Cause | User Message |
|-------|-------|--------------|
| UnknownHostException | No DNS resolution | "No internet connection" |
| ConnectException | Connection refused | "Unable to connect to server" |
| SocketTimeoutException | Request timeout | "Request timed out" |
| SSLException | Certificate error | "Secure connection failed" |
---
## Error Handling Patterns
### Pattern 1: Basic Try-Catch
```kotlin
override suspend fun createEntity(payload: EntityPayload?): DataState<String> {
return withContext(ioDispatcher) {
try {
val response = dataManager.entityApi.createEntity(payload)
DataState.Success(response.bodyAsText())
} catch (e: ClientRequestException) {
val errorMessage = extractErrorMessage(e.response)
DataState.Error(Exception(errorMessage), null)
} catch (e: IOException) {
DataState.Error(
Exception("Network error: ${e.message ?: "Please check your connection"}"),
null
)
} catch (e: ServerResponseException) {
DataState.Error(Exception("Server error: ${e.message}"), null)
}
}
}
```
### Pattern 2: With Status Code Handling
```kotlin
override suspend fun createEntity(payload: EntityPayload?): DataState<String> {
return withContext(ioDispatcher) {
try {
val response = dataManager.entityApi.createEntity(payload)
DataState.Success(response.bodyAsText())
} catch (e: ClientRequestException) {
when (e.response.status.value) {
400 -> DataState.Error(Exception("Invalid request"), null)
401 -> DataState.Error(Exception("Please login again"), null)
403 -> DataState.Error(Exception("Access denied"), null)
404 -> DataState.Error(Exception("Not found"), null)
409 -> DataState.Error(Exception("Already exists"), null)
else -> {
val errorMessage = extractErrorMessage(e.response)
DataState.Error(Exception(errorMessage), null)
}
}
} catch (e: IOException) {
DataState.Error(Exception("Network error"), null)
} catch (e: ServerResponseException) {
DataState.Error(Exception("Server error"), null)
}
}
}
```
### Pattern 3: Flow-Based Error Handling
```kotlin
override fun getEntityList(): Flow<DataState<List<Entity>>> = flow {
try {
dataManager.entityApi.getEntityList()
.collect { response ->
emit(DataState.Success(response))
}
} catch (e: Exception) {
emit(DataState.Error(e, e.message))
}
}.flowOn(ioDispatcher)
```
### Pattern 4: With Retry Logic
```kotlin
override suspend fun createEntityWithRetry(
payload: EntityPayload?,
maxRetries: Int = 3,
): DataState<String> {
var lastException: Exception? = null
repeat(maxRetries) { attempt ->
try {
val response = dataManager.entityApi.createEntity(payload)
return DataState.Success(response.bodyAsText())
} catch (e: IOException) {
lastException = e
delay(1000L * (attempt + 1)) // Exponential backoff
} catch (e: ServerResponseException) {
lastException = e
delay(1000L * (attempt + 1))
} catch (e: ClientRequestException) {
// Don't retry client errors
val errorMessage = extractErrorMessage(e.response)
return DataState.Error(Exception(errorMessage), null)
}
}
return DataState.Error(lastException ?: Exception("Unknown error"), null)
}
```
---
## User-Friendly Messages
### Message Mapping
```kotlin
object ErrorMessages {
fun fromHttpStatus(code: Int): String = when (code) {
400 -> "Please check your input and try again"
401 -> "Your session has expired. Please login again"
403 -> "You don't have permission to perform this action"
404 -> "The requested resource was not found"
409 -> "This item already exists"
422 -> "Please check the form for errors"
500 -> "Something went wrong. Please try again later"
502, 503 -> "Service is temporarily unavailable"
504 -> "The request timed out. Please try again"
else -> "An unexpected error occurred"
}
fun fromNetworkError(e: IOException): String = when {
e.message?.contains("Unable to resolve host") == true ->
"No internet connection"
e.message?.contains("timeout") == true ->
"Connection timed out"
e.message?.contains("Connection refused") == true ->
"Unable to connect to server"
else ->
"Network error. Please check your connection"
}
}
```
### Usage in Repository
```kotlin
catch (e: ClientRequestException) {
val userMessage = ErrorMessages.fromHttpStatus(e.response.status.value)
DataState.Error(Exception(userMessage), null)
}
catch (e: IOException) {
val userMessage = ErrorMessages.fromNetworkError(e)
DataState.Error(Exception(userMessage), null)
}
```
---
## Common Error Codes by Feature
### Authentication
| Code | Meaning | Response |
|------|---------|----------|
| 400 | Invalid credentials format | "Invalid username or password format" |
| 401 | Wrong credentials | "Incorrect username or password" |
| 403 | Account locked | "Your account has been locked" |
### Beneficiary
| Code | Meaning | Response |
|------|---------|----------|
| 400 | Invalid account number | "Account number not found" |
| 404 | Beneficiary not found | "Beneficiary not found" |
| 409 | Duplicate beneficiary | "Beneficiary already exists" |
### Transfer
| Code | Meaning | Response |
|------|---------|----------|
| 400 | Insufficient funds | "Insufficient balance" |
| 400 | Exceeds limit | "Transfer exceeds your limit" |
| 403 | Transfer not allowed | "Transfer not permitted" |
### Savings/Loan Account
| Code | Meaning | Response |
|------|---------|----------|
| 400 | Invalid application | "Application data is invalid" |
| 403 | Not eligible | "Not eligible for this product" |
| 404 | Account not found | "Account not found" |
---
## Testing Error Handling
### Unit Test Example
```kotlin
@Test
fun `createBeneficiary returns error on 400 response`() = runTest {
// Given
val errorResponse = """
{
"httpStatusCode": "400",
"defaultUserMessage": "Account not found"
}
""".trimIndent()
coEvery {
mockService.createBeneficiary(any())
} throws ClientRequestException(
MockHttpResponse(HttpStatusCode.BadRequest, errorResponse),
"Bad Request"
)
// When
val result = repository.createBeneficiary(payload)
// Then
assertTrue(result is DataState.Error)
assertEquals("Account not found", (result as DataState.Error).exception.message)
}
```
---
## Related Files
- Client Patterns: [CLIENT_PATTERNS.md](CLIENT_PATTERNS.md)
- API Reference: [API_REFERENCE.md](API_REFERENCE.md)
- API Index: [API_INDEX.md](API_INDEX.md)
---
## Changelog
| Date | Change |
|------|--------|
| 2025-01-05 | Created with error patterns from codebase analysis |

View File

@ -1,469 +0,0 @@
# Fineract Self-Service API Documentation
> **Server**: Apache Fineract (External - not managed by this project)
> **API Base**: `https://{server}/fineract-provider/api/v1/self/`
> **Documentation**: https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html
> **Last Updated**: 2025-12-26
---
## Overview
Mifos Mobile consumes the Apache Fineract Self-Service API. Unlike projects with custom backends (e.g., Supabase), we don't create or modify server-side code. This document serves as a reference for available endpoints.
**Key Point**: All endpoints are prefixed with `/self/` indicating they are self-service APIs for end-users (not back-office operations).
---
## Authentication
### POST `/authentication`
Authenticate a user and get access token.
**Request**:
```json
{
"username": "string",
"password": "string"
}
```
**Response**:
```json
{
"username": "string",
"userId": 1,
"base64EncodedAuthenticationKey": "string",
"authenticated": true,
"officeId": 1,
"officeName": "string",
"roles": [],
"permissions": [],
"clients": [1],
"isSelfServiceUser": true
}
```
**Kotlin**:
```kotlin
interface AuthenticationService {
@POST(ApiEndPoints.AUTHENTICATION)
suspend fun authenticate(@Body loginPayload: LoginPayload): User
}
```
---
## Client APIs
### GET `/clients`
Get list of clients for the authenticated user.
**Response**:
```json
{
"totalFilteredRecords": 1,
"pageItems": [
{
"id": 1,
"accountNo": "000000001",
"status": { "id": 300, "code": "clientStatusType.active", "value": "Active" },
"fullname": "John Doe",
"displayName": "John Doe",
"officeId": 1,
"officeName": "Head Office"
}
]
}
```
### GET `/clients/{clientId}`
Get specific client details.
### GET `/clients/{clientId}/accounts`
Get all accounts (savings, loans, shares) for a client.
**Query Parameters**:
- `fields`: Filter response fields (e.g., `savingsAccounts,loanAccounts`)
**Response**:
```json
{
"savingsAccounts": [
{
"id": 1,
"accountNo": "000000001",
"productId": 1,
"productName": "Savings Product",
"status": { "id": 300, "value": "Active" },
"currency": { "code": "USD", "displaySymbol": "$", "decimalPlaces": 2 },
"accountBalance": 1000.00
}
],
"loanAccounts": [],
"shareAccounts": []
}
```
### GET `/clients/{clientId}/images`
Get client profile image.
**Response**: Image data or HTTP 404 if not found.
### GET `/clients/{clientId}/charges`
Get charges associated with the client.
---
## Savings Account APIs
### GET `/savingsaccounts/{accountId}`
Get savings account details.
**Query Parameters**:
- `associations`: `all`, `transactions`, `charges`
**Response**:
```json
{
"id": 1,
"accountNo": "000000001",
"clientId": 1,
"clientName": "John Doe",
"savingsProductId": 1,
"savingsProductName": "Savings Product",
"status": { "id": 300, "value": "Active" },
"currency": { "code": "USD", "displaySymbol": "$" },
"accountBalance": 1000.00,
"summary": {
"totalDeposits": 5000.00,
"totalWithdrawals": 4000.00,
"availableBalance": 1000.00
},
"transactions": []
}
```
### GET `/savingsaccounts/template`
Get template for applying for savings account.
**Query Parameters**:
- `clientId`: Required
- `productId`: Optional (for specific product template)
### POST `/savingsaccounts`
Apply for a new savings account.
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"submittedOnDate": "01 January 2025",
"nominalAnnualInterestRate": 5.0,
"interestCompoundingPeriodType": 1,
"interestPostingPeriodType": 4,
"interestCalculationType": 1,
"interestCalculationDaysInYearType": 365
}
```
### PUT `/savingsaccounts/{accountId}`
Update a pending savings account application.
### POST `/savingsaccounts/{accountId}?command=withdrawnByApplicant`
Withdraw savings account application.
**Request**:
```json
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "01 January 2025",
"note": "Withdrawal reason"
}
```
---
## Loan Account APIs
### GET `/loans/{loanId}`
Get loan account details.
**Query Parameters**:
- `associations`: `all`, `repaymentSchedule`, `transactions`, `charges`, `guarantors`
### GET `/loans/template`
Get template for applying for loan.
**Query Parameters**:
- `clientId`: Required
- `productId`: Optional
- `templateType`: `individual`
### POST `/loans`
Apply for a new loan.
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"principal": 10000.00,
"loanTermFrequency": 12,
"loanTermFrequencyType": 2,
"numberOfRepayments": 12,
"repaymentEvery": 1,
"repaymentFrequencyType": 2,
"interestRatePerPeriod": 1.5,
"amortizationType": 1,
"interestType": 0,
"interestCalculationPeriodType": 1,
"transactionProcessingStrategyCode": "mifos-standard-strategy",
"expectedDisbursementDate": "01 January 2025",
"submittedOnDate": "01 January 2025",
"locale": "en",
"dateFormat": "dd MMMM yyyy"
}
```
### PUT `/loans/{loanId}`
Update a pending loan application.
### POST `/loans/{loanId}?command=withdrawnByApplicant`
Withdraw loan application.
### GET `/loans/{loanId}/guarantors`
Get loan guarantors.
### POST `/loans/{loanId}/guarantors`
Add a guarantor to loan.
### DELETE `/loans/{loanId}/guarantors/{guarantorId}`
Remove guarantor from loan.
---
## Beneficiary APIs
### GET `/beneficiaries/tpt`
Get list of third-party transfer beneficiaries.
**Response**:
```json
[
{
"id": 1,
"name": "Jane Doe",
"officeName": "Head Office",
"clientName": "Jane Doe",
"accountType": { "id": 2, "value": "Savings" },
"accountNumber": "000000002",
"transferLimit": 10000.00
}
]
```
### GET `/beneficiaries/tpt/template`
Get template for creating beneficiary.
### POST `/beneficiaries/tpt`
Create a new beneficiary.
**Request**:
```json
{
"locale": "en",
"name": "Jane Doe",
"accountNumber": "000000002",
"accountType": 2,
"transferLimit": 10000.00
}
```
### PUT `/beneficiaries/tpt/{beneficiaryId}`
Update beneficiary.
### DELETE `/beneficiaries/tpt/{beneficiaryId}`
Delete beneficiary.
---
## Transfer APIs
### GET `/accounttransfers/template`
Get template for account transfer.
**Query Parameters**:
- `fromAccountId`: Source account ID
- `fromAccountType`: 1 (Loan) or 2 (Savings)
### POST `/accounttransfers`
Make an account transfer.
**Request**:
```json
{
"fromOfficeId": 1,
"fromClientId": 1,
"fromAccountType": 2,
"fromAccountId": 1,
"toOfficeId": 1,
"toClientId": 2,
"toAccountType": 2,
"toAccountId": 2,
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"transferDate": "01 January 2025",
"transferAmount": 100.00,
"transferDescription": "Transfer to savings"
}
```
---
## Share Account APIs
### GET `/shareaccounts/{accountId}`
Get share account details.
### GET `/shareaccounts/template`
Get template for applying for shares.
### POST `/shareaccounts`
Apply for share account.
---
## User APIs
### GET `/user`
Get current user details.
### PUT `/user`
Update user profile.
**Request**:
```json
{
"username": "string",
"firstname": "string",
"lastname": "string",
"email": "string"
}
```
---
## Registration APIs
### POST `/registration`
Self-service user registration.
**Request**:
```json
{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "string",
"mobileNumber": "string",
"accountNumber": "string",
"password": "string",
"authenticationMode": "email"
}
```
### POST `/registration/user`
Verify registration.
**Request**:
```json
{
"requestId": "string",
"authenticationToken": "string"
}
```
---
## Error Responses
All error responses follow this format:
```json
{
"developerMessage": "Detailed error for developers",
"httpStatusCode": "400",
"defaultUserMessage": "User-friendly error message",
"userMessageGlobalisationCode": "error.msg.code",
"errors": [
{
"developerMessage": "Field-specific error",
"defaultUserMessage": "User message",
"userMessageGlobalisationCode": "error.msg.field.code",
"parameterName": "fieldName"
}
]
}
```
---
## HTTP Status Codes
| Code | Meaning |
|------|---------|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Validation error |
| 401 | Unauthorized - Invalid credentials |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found |
| 500 | Internal Server Error |
---
## Notes for Implementation
1. **Date Format**: Always use `dd MMMM yyyy` (e.g., "01 January 2025")
2. **Locale**: Always include `locale: "en"` in requests
3. **Tenant Header**: Required for all requests
4. **Authentication**: Use Basic Auth with base64 encoded credentials
5. **Associations**: Use `associations=all` to get complete data in one call

View File

@ -0,0 +1,156 @@
# Server Layer - Testing Status
> API contract and mock server testing documentation
---
## Overview
The server layer documents the Fineract API. Testing ensures our client-side code correctly consumes these endpoints.
---
## Testing Scope
| Component | Test Type | Purpose |
|-----------|-----------|---------|
| API Endpoints | Contract Tests | Verify request/response format |
| Error Responses | Error Handling Tests | Verify error parsing |
| Mock Responses | Fixture Tests | Test data for offline testing |
---
## API Contract Testing
### Endpoint Categories
| # | Category | Endpoints | Mock Responses | Status |
|:-:|----------|:---------:|:--------------:|:------:|
| 1 | AUTH | 4 | ⬜ | Not Started |
| 2 | CLIENT | 5 | ⬜ | Not Started |
| 3 | SAVINGS | 8 | ⬜ | Not Started |
| 4 | LOANS | 10 | ⬜ | Not Started |
| 5 | SHARES | 4 | ⬜ | Not Started |
| 6 | BENEFICIARY | 4 | ⬜ | Not Started |
| 7 | TRANSFER | 3 | ⬜ | Not Started |
| 8 | CHARGES | 3 | ⬜ | Not Started |
| 9 | NOTIFICATION | 2 | ⬜ | Not Started |
| 10 | USER | 3 | ⬜ | Not Started |
| 11 | GUARANTOR | 4 | ⬜ | Not Started |
**Legend**: ✅ Complete | ⬜ Not Started
---
## Mock Response Files
Location: `core/testing/src/commonMain/resources/api/`
```
api/
├── auth/
│ ├── login_success.json
│ ├── login_error.json
│ └── register_success.json
├── client/
│ ├── client_details.json
│ ├── client_accounts.json
│ └── client_image.json
├── savings/
│ ├── savings_list.json
│ ├── savings_detail.json
│ ├── savings_transactions.json
│ └── savings_template.json
├── loans/
│ ├── loan_list.json
│ ├── loan_detail.json
│ ├── loan_schedule.json
│ └── loan_transactions.json
├── beneficiary/
│ ├── beneficiary_list.json
│ ├── beneficiary_template.json
│ └── beneficiary_detail.json
├── transfer/
│ ├── transfer_template.json
│ └── transfer_success.json
└── common/
├── error_400.json
├── error_401.json
├── error_403.json
└── error_500.json
```
---
## Mock Server Setup
For integration testing, configure Ktor mock engine:
```kotlin
// Location: core/testing/src/commonMain/.../MockApiEngine.kt
class MockApiEngine {
fun create(): HttpClientEngine = MockEngine { request ->
when {
request.url.encodedPath.contains("/authentication") -> {
respondFromFile("api/auth/login_success.json")
}
request.url.encodedPath.contains("/clients") -> {
respondFromFile("api/client/client_details.json")
}
// ... more routes
}
}
}
```
---
## Error Response Testing
| Error Code | Scenario | Mock File |
|:----------:|----------|-----------|
| 400 | Bad Request | `error_400.json` |
| 401 | Unauthorized | `error_401.json` |
| 403 | Forbidden | `error_403.json` |
| 404 | Not Found | `error_404.json` |
| 500 | Server Error | `error_500.json` |
---
## Implementation Plan
### Phase 1: Mock Responses
1. Create JSON fixtures for all endpoints
2. Organize by feature category
3. Include success and error variants
### Phase 2: Mock Engine
1. Configure Ktor MockEngine
2. Map routes to fixtures
3. Handle query parameters
### Phase 3: Contract Tests
1. Verify request headers
2. Verify request body format
3. Verify response parsing
---
## Commands
```bash
# Check API documentation coverage
/gap-analysis server
# Plan mock response creation
/gap-planning server testing
```
---
## Related Files
- [API_INDEX.md](./API_INDEX.md) - All endpoints
- [API_REFERENCE.md](./API_REFERENCE.md) - Detailed API docs
- [ERROR_HANDLING.md](./ERROR_HANDLING.md) - Error patterns

View File

@ -0,0 +1,109 @@
# Authentication Endpoints
> **Category**: Authentication
> **Endpoints**: 3
> **Service**: `AuthenticationService`, `RegistrationService`
---
## POST /authentication
**Purpose**: Login with username and password
**Service**: `AuthenticationService.authenticate()`
**Request**:
```json
{
"username": "string",
"password": "string"
}
```
**Response**:
```json
{
"userId": 123,
"username": "john_doe",
"clients": [456],
"isAuthenticated": true,
"base64EncodedAuthenticationKey": "encoded_key",
"officeName": "Head Office"
}
```
**DTO**: `User`
```kotlin
@Serializable
data class User(
val userId: Long,
val username: String?,
val clients: List<Long>,
val isAuthenticated: Boolean,
val base64EncodedAuthenticationKey: String?,
val officeName: String?,
)
```
---
## POST /registration
**Purpose**: Register new client
**Service**: `RegistrationService.register()`
**Request**:
```json
{
"accountNumber": "string",
"authenticationMode": "email",
"email": "user@example.com",
"firstName": "John",
"middleName": "M",
"lastName": "Doe",
"mobileNumber": "1234567890",
"password": "securePassword123",
"username": "john_doe"
}
```
**Response**:
```json
{
"requestId": "12345"
}
```
**DTO**: `RegisterPayload`
---
## POST /registration/user
**Purpose**: Verify OTP for registration
**Service**: `RegistrationService.verifyOtp()`
**Request**:
```json
{
"authenticationToken": "123456",
"requestId": "12345"
}
```
**Response**:
```json
{
"message": "User verified successfully"
}
```
---
## Related
- Design Layer: [auth/API.md](../../design-spec-layer/features/auth/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)
- Errors: [ERROR_HANDLING.md](../ERROR_HANDLING.md)

View File

@ -0,0 +1,164 @@
# Beneficiary Endpoints
> **Category**: Beneficiary
> **Endpoints**: 5
> **Service**: `BeneficiaryService`
---
## GET /beneficiaries/tpt
**Purpose**: Get list of beneficiaries
**Service**: `BeneficiaryService.beneficiaryList()`
**Response**:
```json
[
{
"id": 5001,
"name": "John Doe",
"officeName": "Head Office",
"clientName": "John Doe",
"accountType": { "id": 2, "value": "Savings Account" },
"accountNumber": "SA-0001234567",
"transferLimit": 10000.00
}
]
```
**DTO**: `List<Beneficiary>`
```kotlin
@Serializable
@Parcelize
data class Beneficiary(
val id: Long? = null,
val name: String? = null,
val officeName: String? = null,
val clientName: String? = null,
val accountType: AccountType? = null,
val accountNumber: String? = null,
val transferLimit: Double? = null,
) : Parcelable
```
---
## GET /beneficiaries/tpt/template
**Purpose**: Get beneficiary template (account type options)
**Service**: `BeneficiaryService.beneficiaryTemplate()`
**Response**:
```json
{
"accountTypeOptions": [
{ "id": 0, "value": "Share Account" },
{ "id": 1, "value": "Loan Account" },
{ "id": 2, "value": "Savings Account" }
]
}
```
**DTO**: `BeneficiaryTemplate`
---
## POST /beneficiaries/tpt
**Purpose**: Create new beneficiary
**Service**: `BeneficiaryService.createBeneficiary(payload)`
**Request**:
```json
{
"locale": "en",
"name": "John Doe",
"accountNumber": "SA-0001234567",
"accountType": 2,
"transferLimit": 10000,
"officeName": "Head Office"
}
```
**Response**:
```json
{
"resourceId": 5003
}
```
**DTO**: `BeneficiaryPayload`
```kotlin
@Serializable
@Parcelize
data class BeneficiaryPayload(
val locale: String? = null,
val name: String? = null,
val accountNumber: String? = null,
val accountType: Int? = 0,
val transferLimit: Int? = 0,
val officeName: String? = null,
) : Parcelable
```
---
## PUT /beneficiaries/tpt/{beneficiaryId}
**Purpose**: Update beneficiary
**Service**: `BeneficiaryService.updateBeneficiary(beneficiaryId, payload)`
**Request**:
```json
{
"name": "John Doe Updated",
"transferLimit": 15000
}
```
**DTO**: `BeneficiaryUpdatePayload`
```kotlin
@Serializable
data class BeneficiaryUpdatePayload(
val name: String? = null,
val transferLimit: Int = 0,
)
```
**Note**: Only `name` and `transferLimit` can be updated.
---
## DELETE /beneficiaries/tpt/{beneficiaryId}
**Purpose**: Delete beneficiary
**Service**: `BeneficiaryService.deleteBeneficiary(beneficiaryId)`
**Response**:
```json
{
"resourceId": 5001
}
```
---
## Account Type Mapping
| ID | Code | Value |
|----|------|-------|
| 0 | accountType.share | Share Account |
| 1 | accountType.loan | Loan Account |
| 2 | accountType.savings | Savings Account |
---
## Related
- Design Layer: [beneficiary/API.md](../../design-spec-layer/features/beneficiary/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,140 @@
# Charges Endpoints
> **Category**: Charges
> **Endpoints**: 3
> **Service**: `ClientChargeService`
---
## GET /clients/{clientId}/charges
**Purpose**: Get client charges (paginated)
**Service**: `ClientChargeService.getClientChargeList(clientId)`
**Response**:
```json
{
"totalFilteredRecords": 3,
"pageItems": [
{
"clientId": 1,
"chargeId": 101,
"name": "Processing Fee",
"dueDate": [2025, 1, 15],
"chargeTimeType": { "id": 2, "value": "Specified due date" },
"chargeCalculationType": { "id": 1, "value": "Flat" },
"currency": {
"code": "USD",
"displaySymbol": "$",
"decimalPlaces": 2
},
"amount": 50.00,
"amountPaid": 0.00,
"amountOutstanding": 50.00,
"penalty": false,
"isActive": true,
"paid": false
}
]
}
```
**DTO**: `Page<Charge>`
```kotlin
@Serializable
@Parcelize
data class Charge(
val clientId: Int? = null,
val chargeId: Int? = null,
val name: String? = null,
val dueDate: ArrayList<Int?> = arrayListOf(),
val chargeTimeType: ChargeTimeType? = null,
val chargeCalculationType: ChargeCalculationType? = null,
val currency: Currency? = null,
val amount: Double = 0.0,
val amountPaid: Double = 0.0,
val amountWaived: Double = 0.0,
val amountWrittenOff: Double = 0.0,
val amountOutstanding: Double = 0.0,
val penalty: Boolean = false,
val isActive: Boolean = false,
val isChargePaid: Boolean = false,
val paid: Boolean = false,
val waived: Boolean = false,
) : Parcelable
```
---
## GET /loans/{loanId}/charges
**Purpose**: Get loan charges
**Service**: `ClientChargeService.getChargeList("loans", loanId)`
**Response**:
```json
[
{
"chargeId": 201,
"name": "Disbursement Fee",
"dueDate": [2024, 11, 1],
"chargeTimeType": { "id": 1, "value": "Disbursement" },
"amount": 100.00,
"amountPaid": 100.00,
"amountOutstanding": 0.00,
"penalty": false,
"paid": true
}
]
```
**DTO**: `List<Charge>`
---
## GET /savingsaccounts/{accountId}/charges
**Purpose**: Get savings account charges
**Service**: `ClientChargeService.getChargeList("savingsaccounts", accountId)`
**Response**:
```json
[
{
"chargeId": 301,
"name": "Monthly Service Fee",
"dueDate": [2025, 1, 31],
"chargeTimeType": { "id": 3, "value": "Monthly Fee" },
"amount": 5.00,
"amountPaid": 0.00,
"amountOutstanding": 5.00,
"penalty": false,
"paid": false
}
]
```
**DTO**: `List<Charge>`
---
## Charge Type Enum
```kotlin
enum class ChargeType(val type: String) {
CLIENT("clients"),
SAVINGS("savingsaccounts"),
LOAN("loans"),
SHARE("shareaccounts"),
}
```
---
## Related
- Design Layer: [client-charge/API.md](../../design-spec-layer/features/client-charge/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,108 @@
# Client Endpoints
> **Category**: Client
> **Endpoints**: 4
> **Service**: `ClientService`
---
## GET /clients
**Purpose**: Get client list for authenticated user
**Service**: `ClientService.clients()`
**Response**:
```json
{
"pageItems": [
{
"id": 1,
"accountNo": "000000001",
"displayName": "John Doe",
"officeId": 1,
"officeName": "Head Office"
}
]
}
```
**DTO**: `Page<Client>`
---
## GET /clients/{clientId}
**Purpose**: Get client details
**Service**: `ClientService.getClientDetails(clientId)`
**Response**:
```json
{
"id": 1,
"accountNo": "000000001",
"displayName": "John Doe",
"firstname": "John",
"lastname": "Doe",
"mobileNo": "1234567890",
"emailAddress": "john@example.com",
"dateOfBirth": [1990, 1, 15],
"officeId": 1,
"officeName": "Head Office"
}
```
**DTO**: `Client`
---
## GET /clients/{clientId}/images
**Purpose**: Get client profile image
**Service**: `ClientService.getClientImage(clientId)`
**Response**: Base64 encoded image or image URL
---
## GET /clients/{clientId}/accounts
**Purpose**: Get all accounts for a client
**Service**: `ClientService.getClientAccounts(clientId)`
**Response**:
```json
{
"savingsAccounts": [
{
"id": 12345,
"accountNo": "000000012345",
"productName": "Basic Savings",
"accountBalance": 1234.56,
"status": { "id": 300, "value": "Active" }
}
],
"loanAccounts": [
{
"id": 67890,
"accountNo": "000000067890",
"productName": "Personal Loan",
"loanBalance": 5000.00,
"status": { "id": 300, "value": "Active" }
}
],
"shareAccounts": []
}
```
**DTO**: `ClientAccounts`
---
## Related
- Design Layer: [home/API.md](../../design-spec-layer/features/home/API.md), [accounts/API.md](../../design-spec-layer/features/accounts/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,131 @@
# Guarantor Endpoints
> **Category**: Guarantor
> **Endpoints**: 5
> **Service**: `GuarantorService`
---
## GET /loans/{loanId}/guarantors
**Purpose**: Get list of guarantors for a loan
**Service**: `GuarantorService.getGuarantorList(loanId)`
**Response**:
```json
[
{
"id": 1,
"loanId": 67890,
"clientRelationshipType": { "id": 1, "value": "Spouse" },
"guarantorType": { "id": 1, "value": "External" },
"firstname": "Jane",
"lastname": "Doe",
"mobileNumber": "1234567890",
"status": true
}
]
```
**DTO**: `List<GuarantorPayload>`
---
## GET /loans/{loanId}/guarantors/template
**Purpose**: Get guarantor template (relationship types, guarantor types)
**Service**: `GuarantorService.getGuarantorTemplate(loanId)`
**Response**:
```json
{
"guarantorTypeOptions": [
{ "id": 1, "value": "External" },
{ "id": 2, "value": "Internal" }
],
"clientRelationshipTypeOptions": [
{ "id": 1, "value": "Spouse" },
{ "id": 2, "value": "Parent" },
{ "id": 3, "value": "Sibling" }
]
}
```
**DTO**: `GuarantorTemplatePayload`
---
## POST /loans/{loanId}/guarantors
**Purpose**: Add guarantor to loan
**Service**: `GuarantorService.addGuarantor(loanId, payload)`
**Request**:
```json
{
"guarantorTypeId": 1,
"clientRelationshipTypeId": 1,
"firstname": "Jane",
"lastname": "Doe",
"addressLine1": "123 Main St",
"city": "City",
"mobileNumber": "1234567890",
"locale": "en",
"dateFormat": "dd MMMM yyyy"
}
```
**Response**:
```json
{
"loanId": 67890,
"resourceId": 1
}
```
**DTO**: `GuarantorPayload`
---
## PUT /loans/{loanId}/guarantors/{guarantorId}
**Purpose**: Update guarantor
**Service**: `GuarantorService.updateGuarantor(loanId, guarantorId, payload)`
**Request**:
```json
{
"guarantorTypeId": 1,
"clientRelationshipTypeId": 2,
"firstname": "Jane",
"lastname": "Doe Updated",
"mobileNumber": "0987654321"
}
```
---
## DELETE /loans/{loanId}/guarantors/{guarantorId}
**Purpose**: Delete guarantor
**Service**: `GuarantorService.deleteGuarantor(loanId, guarantorId)`
**Response**:
```json
{
"loanId": 67890,
"resourceId": 1
}
```
---
## Related
- Design Layer: [guarantor/API.md](../../design-spec-layer/features/guarantor/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,160 @@
# Loan Account Endpoints
> **Category**: Loan Account
> **Endpoints**: 6
> **Service**: `LoanService`
---
## GET /loans/{loanId}
**Purpose**: Get loan account details with associations
**Service**: `LoanService.getLoanWithAssociations(loanId, associations)`
**Query Parameters**:
| Parameter | Values |
|-----------|--------|
| `associations` | `transactions`, `repaymentSchedule`, `charges` |
**Response**:
```json
{
"id": 67890,
"accountNo": "000000067890",
"clientId": 1,
"clientName": "John Doe",
"loanProductId": 1,
"loanProductName": "Personal Loan",
"principal": 10000.00,
"status": { "id": 300, "value": "Active" },
"summary": {
"principalDisbursed": 10000.00,
"principalPaid": 5000.00,
"principalOutstanding": 5000.00,
"interestCharged": 500.00,
"totalExpectedRepayment": 10500.00
},
"repaymentSchedule": {...},
"transactions": [...]
}
```
**DTO**: `LoanWithAssociations`
---
## GET /loanproducts
**Purpose**: Get available loan products
**Service**: `LoanService.getLoanProducts()`
**Response**:
```json
[
{
"id": 1,
"name": "Personal Loan",
"principal": 10000.00,
"interestRatePerPeriod": 12.0
}
]
```
**DTO**: `List<LoanProduct>`
---
## POST /loans
**Purpose**: Submit loan application
**Service**: `LoanService.createLoansAccount(payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"principal": 10000.00,
"loanTermFrequency": 12,
"loanTermFrequencyType": 2,
"numberOfRepayments": 12,
"repaymentEvery": 1,
"repaymentFrequencyType": 2,
"interestRatePerPeriod": 12.0,
"expectedDisbursementDate": "29 December 2025",
"submittedOnDate": "29 December 2025",
"locale": "en",
"dateFormat": "dd MMMM yyyy"
}
```
**DTO**: `LoansPayload`
---
## POST /loans/{loanId}?command=withdrawnByApplicant
**Purpose**: Withdraw loan application
**Service**: `LoanService.withdrawLoanAccount(loanId, payload)`
**Request**:
```json
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "29 December 2025",
"note": "User requested withdrawal"
}
```
---
## GET /loans/{loanId}/template?templateType=repayment
**Purpose**: Get loan repayment template
**Service**: `LoanService.getLoanRepaymentTemplate(loanId)`
**Response**:
```json
{
"loanId": 67890,
"date": [2025, 1, 15],
"amount": 875.00,
"principalPortion": 833.33,
"interestPortion": 41.67
}
```
**DTO**: `LoanRepaymentTemplate`
---
## GET /loans/{loanId}/transactions/{transactionId}
**Purpose**: Get loan transaction details
**Service**: `LoanService.getLoanAccountTransaction(loanId, transactionId)`
**Response**:
```json
{
"id": 1,
"type": { "id": 2, "value": "Repayment" },
"date": [2025, 1, 15],
"amount": 875.00,
"principalPortion": 833.33,
"interestPortion": 41.67
}
```
---
## Related
- Design Layer: [loan-account/API.md](../../design-spec-layer/features/loan-account/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,90 @@
# Notification Endpoints
> **Category**: Notification
> **Endpoints**: 3
> **Service**: `NotificationService`
---
## GET /device/registration/client/{clientId}
**Purpose**: Get notification registration ID for client
**Service**: `NotificationService.getUserNotificationId(clientId)`
**Response**:
```json
{
"id": 123,
"clientId": 1,
"registrationId": "fcm_token_here"
}
```
**DTO**: `NotificationRegisterPayload`
---
## POST /device/registration
**Purpose**: Register device for push notifications
**Service**: `NotificationService.registerNotification(payload)`
**Request**:
```json
{
"clientId": 1,
"registrationId": "fcm_token_here"
}
```
**Response**:
```json
{
"resourceId": 123
}
```
**DTO**: `NotificationRegisterPayload`
```kotlin
@Serializable
data class NotificationRegisterPayload(
val id: Long? = null,
val clientId: Long? = null,
val registrationId: String? = null,
)
```
---
## PUT /device/registration/{id}
**Purpose**: Update notification registration (refresh FCM token)
**Service**: `NotificationService.updateRegisterNotification(id, payload)`
**Request**:
```json
{
"clientId": 1,
"registrationId": "new_fcm_token_here"
}
```
**Response**:
```json
{
"resourceId": 123,
"changes": {
"registrationId": "new_fcm_token_here"
}
}
```
---
## Related
- Design Layer: [notification/API.md](../../design-spec-layer/features/notification/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,198 @@
# Savings Account Endpoints
> **Category**: Savings Account
> **Endpoints**: 7
> **Service**: `SavingAccountsListService`
---
## GET /savingsaccounts/{accountId}
**Purpose**: Get savings account details with optional associations
**Service**: `SavingAccountsListService.getSavingsWithAssociations(accountId, associations)`
**Query Parameters**:
| Parameter | Type | Values |
|-----------|------|--------|
| `associations` | String | `transactions`, `charges` |
**Response**:
```json
{
"id": 12345,
"accountNo": "000000012345",
"depositType": { "id": 100, "value": "Savings" },
"clientId": 1,
"clientName": "John Doe",
"savingsProductId": 1,
"savingsProductName": "Basic Savings",
"status": {
"id": 300,
"code": "savingsAccountStatusType.active",
"value": "Active"
},
"currency": {
"code": "USD",
"displaySymbol": "$",
"decimalPlaces": 2
},
"summary": {
"totalDeposits": 5000.00,
"totalWithdrawals": 3765.44,
"accountBalance": 1234.56
},
"transactions": [...]
}
```
**DTO**: `SavingsWithAssociations`
---
## GET /savingsaccounts/template
**Purpose**: Get template for savings application
**Service**: `SavingAccountsListService.getSavingsAccountApplicationTemplate(clientId)`
**Query Parameters**:
| Parameter | Type | Required |
|-----------|------|----------|
| `clientId` | Long | Yes |
| `productId` | Long | No |
**Response**:
```json
{
"clientId": 1,
"clientName": "John Doe",
"productOptions": [
{
"id": 1,
"name": "Basic Savings",
"allowOverdraft": false
}
]
}
```
**DTO**: `SavingsAccountTemplate`
---
## GET /savingsproducts
**Purpose**: Get available savings products
**Service**: `SavingAccountsListService.getSavingsProducts()`
**Response**:
```json
[
{
"id": 1,
"name": "Basic Savings",
"shortName": "BS",
"description": "Basic savings account"
}
]
```
---
## POST /savingsaccounts
**Purpose**: Submit savings account application
**Service**: `SavingAccountsListService.submitSavingAccountApplication(payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"submittedOnDate": "29 December 2025"
}
```
**Response**:
```json
{
"officeId": 1,
"clientId": 1,
"savingsId": 12345,
"resourceId": 12345
}
```
**DTO**: `SavingsAccountApplicationPayload`
---
## PUT /savingsaccounts/{accountId}
**Purpose**: Update pending savings account
**Service**: `SavingAccountsListService.updateSavingsAccountUpdate(accountId, payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 2
}
```
**DTO**: `SavingsAccountUpdatePayload`
---
## POST /savingsaccounts/{savingsId}?command=withdrawnByApplicant
**Purpose**: Withdraw savings application
**Service**: `SavingAccountsListService.submitWithdrawSavingsAccount(savingsId, payload)`
**Request**:
```json
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"withdrawnOnDate": "29 December 2025",
"note": "User requested withdrawal"
}
```
**DTO**: `SavingsAccountWithdrawPayload`
---
## GET /savingsaccounts/{accountId}/transactions/{transactionId}
**Purpose**: Get savings transaction details
**Service**: `SavingAccountsListService.getSavingsAccountTransactionDetails(accountId, transactionId)`
**Response**:
```json
{
"id": 1,
"transactionType": { "id": 1, "value": "Deposit" },
"accountId": 12345,
"date": [2025, 12, 28],
"amount": 100.00,
"runningBalance": 1234.56
}
```
**DTO**: `TransactionDetails`
---
## Related
- Design Layer: [savings-account/API.md](../../design-spec-layer/features/savings-account/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,115 @@
# Share Account Endpoints
> **Category**: Share Account
> **Endpoints**: 3
> **Service**: `ShareAccountService`
---
## GET /products/share
**Purpose**: Get available share products
**Service**: `ShareAccountService.getShareProducts()`
**Response**:
```json
[
{
"id": 1,
"name": "Common Shares",
"shortName": "CS",
"description": "Common equity shares",
"unitPrice": 100.00,
"currency": {
"code": "USD",
"displaySymbol": "$",
"decimalPlaces": 2
},
"totalShares": 10000,
"totalSharesIssued": 5000
}
]
```
**DTO**: `List<ShareProduct>`
---
## POST /shareaccounts
**Purpose**: Submit share account application
**Service**: `ShareAccountService.submitShareApplication(payload)`
**Request**:
```json
{
"clientId": 1,
"productId": 1,
"requestedShares": 10,
"applicationDate": "29 December 2025",
"locale": "en",
"dateFormat": "dd MMMM yyyy"
}
```
**Response**:
```json
{
"officeId": 1,
"clientId": 1,
"resourceId": 12345
}
```
**DTO**: `ShareAccountPayload`
---
## GET /shareaccounts/{accountId}
**Purpose**: Get share account details
**Service**: `ShareAccountService.getShareAccountDetails(accountId)`
**Response**:
```json
{
"id": 12345,
"accountNo": "000000001",
"clientId": 1,
"clientName": "John Doe",
"productId": 1,
"productName": "Common Shares",
"status": {
"id": 300,
"code": "shareAccountStatusType.active",
"value": "Active"
},
"timeline": {
"submittedOnDate": [2025, 12, 15],
"approvedDate": [2025, 12, 16],
"activatedDate": [2025, 12, 16]
},
"currency": {
"code": "USD",
"displaySymbol": "$"
},
"summary": {
"totalApprovedShares": 10,
"totalPendingShares": 0,
"totalShareValue": 1000.00
},
"charges": [...]
}
```
**DTO**: `ShareAccountDetails`
---
## Related
- Design Layer: [share-account/API.md](../../design-spec-layer/features/share-account/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,122 @@
# Transfer Endpoints
> **Category**: Transfer
> **Endpoints**: 2
> **Service**: `SavingAccountsListService`, `ThirdPartyTransferService`
---
## GET /accounttransfers/template
**Purpose**: Get transfer template with account options
**Service**: `SavingAccountsListService.accountTransferTemplate(fromAccountId, fromAccountType)`
**Query Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| `fromAccountId` | Long | Source account ID |
| `fromAccountType` | Long | Account type (2 = Savings) |
**Response**:
```json
{
"fromAccountTypeOptions": [
{ "id": 1, "value": "Loan Account" },
{ "id": 2, "value": "Savings Account" }
],
"toAccountTypeOptions": [
{ "id": 1, "value": "Loan Account" },
{ "id": 2, "value": "Savings Account" }
],
"fromAccountOptions": [
{
"accountId": 12345,
"accountNo": "000000012345",
"accountType": { "id": 2, "value": "Savings Account" },
"clientId": 1,
"clientName": "John Doe",
"officeId": 1,
"officeName": "Head Office"
}
],
"toAccountOptions": [...]
}
```
**DTO**: `AccountOptionsTemplate`
---
## POST /accounttransfers
**Purpose**: Execute account transfer
**Service**: `SavingAccountsListService.makeTransfer(payload)`
**Request**:
```json
{
"fromOfficeId": 1,
"fromClientId": 1,
"fromAccountType": 2,
"fromAccountId": 12345,
"toOfficeId": 1,
"toClientId": 2,
"toAccountType": 2,
"toAccountId": 12346,
"transferDate": "29 December 2025",
"transferAmount": 100.00,
"transferDescription": "Transfer to beneficiary",
"dateFormat": "dd MMMM yyyy",
"locale": "en"
}
```
**Response**:
```json
{
"savingsId": 12345,
"resourceId": 1,
"changes": {
"transferAmount": 100.00
}
}
```
**DTO**: `TransferPayload`
```kotlin
@Serializable
data class TransferPayload(
val fromOfficeId: Long? = null,
val fromClientId: Long? = null,
val fromAccountType: Long? = null,
val fromAccountId: Long? = null,
val toOfficeId: Long? = null,
val toClientId: Long? = null,
val toAccountType: Long? = null,
val toAccountId: Long? = null,
val transferDate: String? = null,
val transferAmount: Double? = null,
val transferDescription: String? = null,
val dateFormat: String? = null,
val locale: String? = null,
)
```
---
## Third Party Transfer
For transfers to beneficiaries, use:
- **Template**: `GET /accounttransfers/template?type=tpt`
- **Execute**: `POST /accounttransfers?type=tpt`
**Service**: `ThirdPartyTransferService`
---
## Related
- Design Layer: [transfer/API.md](../../design-spec-layer/features/transfer/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -0,0 +1,52 @@
# User Settings Endpoints
> **Category**: User Settings
> **Endpoints**: 1
> **Service**: `UserService`
---
## PUT /user/password
**Purpose**: Update user password
**Service**: `UserService.updatePassword(payload)`
**Request**:
```json
{
"newPassword": "newSecurePassword123",
"confirmPassword": "newSecurePassword123"
}
```
**Response**:
```json
{
"message": "Password updated successfully"
}
```
**DTO**: `UpdatePasswordPayload`
```kotlin
@Serializable
data class UpdatePasswordPayload(
val newPassword: String,
val confirmPassword: String,
)
```
---
## Notes
- Password must meet complexity requirements (server-configured)
- User must be authenticated to change password
- Old password may be required depending on server configuration
---
## Related
- Design Layer: [settings/API.md](../../design-spec-layer/features/settings/API.md)
- Patterns: [CLIENT_PATTERNS.md](../CLIENT_PATTERNS.md)

View File

@ -1,82 +1,235 @@
# Gap Analysis Dashboard Template
# Gap Analysis - Comprehensive Dashboard Template
## Mifos Mobile - Gap Analysis Dashboard
**App Version**: {{APP_VERSION}} | **Last Updated**: {{DATE}}
**Design System**: v2.0 (2025 Fintech Patterns)
Replace {{PLACEHOLDERS}} with real data from O(1) index files.
---
### 5-Layer Health Overview
```
╔══════════════════════════════════════════════════════════════════════════════╗
║ MIFOS MOBILE - GAP ANALYSIS (O(1) Lookup) ║
║ Last Updated: {{DATE}} ║
╠══════════════════════════════════════════════════════════════════════════════╣
## 5-Layer Health Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ Layer Progress Implemented Gaps Status │
├─────────────────────────────────────────────────────────────────────────────┤
│ 1. Design {{DESIGN_BAR}} {{DESIGN_PCT}}% {{DESIGN_DONE}}/17 {{DESIGN_GAPS}} {{DESIGN_STATUS}} │
│ 2. Server {{SERVER_BAR}} {{SERVER_PCT}}% {{SERVER_DONE}}/11 {{SERVER_GAPS}} {{SERVER_STATUS}} │
│ 3. Client {{CLIENT_BAR}} {{CLIENT_PCT}}% {{CLIENT_DONE}}/30 {{CLIENT_GAPS}} {{CLIENT_STATUS}} │
│ 4. Feature {{FEATURE_BAR}} {{FEATURE_PCT}}% {{FEATURE_DONE}}/63 {{FEATURE_GAPS}} {{FEATURE_STATUS}} │
│ 5. Platform {{PLATFORM_BAR}} {{PLATFORM_PCT}}% {{PLATFORM_DONE}}/4 {{PLATFORM_GAPS}} {{PLATFORM_STATUS}}│
├─────────────────────────────────────────────────────────────────────────────┤
│ OVERALL {{OVERALL_BAR}} {{OVERALL_PCT}}% │
└─────────────────────────────────────────────────────────────────────────────┘
---
## ✅ IMPLEMENTED (What's Complete)
### Design Layer ({{DESIGN_TOTAL}} features)
| # | Feature | SPEC | API | STATUS | Mockups | Figma | Tokens |
|:-:|---------|:----:|:---:|:------:|:-------:|:-----:|:------:|
{{DESIGN_ROWS}}
### Server Layer ({{SERVER_TOTAL}} endpoint categories)
| Category | Endpoints | Status | File |
|----------|:---------:|:------:|------|
{{SERVER_ROWS}}
### Client Layer ({{SERVICE_COUNT}} services, {{REPO_COUNT}} repositories)
| Component | Count | Status |
|-----------|:-----:|:------:|
| Services | {{SERVICE_COUNT}}/{{SERVICE_TOTAL}} | {{SERVICE_STATUS}} |
| Repositories | {{REPO_COUNT}}/{{REPO_TOTAL}} | {{REPO_STATUS}} |
| DI Modules | {{DI_COUNT}}/{{DI_TOTAL}} | {{DI_STATUS}} |
**Services**:
| # | Service | File | Feature |
|:-:|---------|------|---------|
{{SERVICE_ROWS}}
**Repositories**:
| # | Repository | Implementation | Service |
|:-:|------------|----------------|---------|
{{REPO_ROWS}}
### Feature Layer ({{MODULE_COUNT}} modules, {{SCREEN_COUNT}} screens)
| Component | Count | Status |
|-----------|:-----:|:------:|
| Modules | {{MODULE_COUNT}}/{{MODULE_TOTAL}} | {{MODULE_STATUS}} |
| ViewModels | {{VM_COUNT}}/{{VM_TOTAL}} | {{VM_STATUS}} |
| Screens | {{SCREEN_COUNT}}/{{SCREEN_TOTAL}} | {{SCREEN_STATUS}} |
| DI Modules | {{FEATURE_DI_COUNT}}/{{FEATURE_DI_TOTAL}} | {{FEATURE_DI_STATUS}} |
**Modules**:
| # | Module | Path | DI | VMs | Screens |
|:-:|--------|------|:--:|:---:|:-------:|
{{MODULE_ROWS}}
### Platform Layer ({{PLATFORM_COUNT}} platforms)
| Platform | Module | Build | Flavors | Status |
|----------|--------|:-----:|:-------:|:------:|
{{PLATFORM_ROWS}}
---
## ❌ GAPS (What Needs Work)
### P0 - Critical (Blocks Other Work)
| # | Gap | Layer | Impact | Plan Command |
|:-:|-----|-------|--------|--------------|
{{P0_ROWS}}
### P1 - High Priority (User-Facing)
| # | Gap | Layer | Impact | Plan Command |
|:-:|-----|-------|--------|--------------|
{{P1_ROWS}}
### P2 - Nice to Have (Polish)
| # | Gap | Layer | Impact | Plan Command |
|:-:|-----|-------|--------|--------------|
{{P2_ROWS}}
---
## 🛠️ AVAILABLE ACTIONS
### Create Plans (Based on Gaps Found)
| Priority | Gap | Command | Tasks |
|:--------:|-----|---------|:-----:|
{{PLAN_ROWS}}
### Implement Features
| Target | Command | What It Does |
|--------|---------|--------------|
| E2E feature | `/implement [feature]` | Full 5-layer implementation |
| Client only | `/client [feature]` | Network + Data layers |
| UI only | `/feature [feature]` | ViewModel + Screen |
| Design only | `/design [feature]` | Spec + Mockup |
### Verify Implementation
| Target | Command | What It Does |
|--------|---------|--------------|
| Any feature | `/verify [feature]` | Check implementation vs spec |
| All features | `/verify` | Full verification |
---
## 📊 O(1) PERFORMANCE GAINS
| Metric | Before (O(n)) | After (O(1)) | Improvement |
|--------|:-------------:|:------------:|:-----------:|
| Files to scan | 10-50 | 1-2 | **90% fewer** |
| Lines to read | 500-3000 | 60-200 | **80-95% less** |
| Tool calls | 3-5 | 1-2 | **60% fewer** |
| Context usage | High | Low | **~10x efficient** |
### How O(1) Works
```
┌─────────────────────────────────────────────────────────────────┐
│ PRODUCT LIFECYCLE HEALTH │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Design Layer {{DESIGN_BAR}} {{DESIGN_PCT}}% │
│ ├─ SPEC.md {{SPEC_BAR}} {{SPEC_PCT}}% │
│ ├─ MOCKUP.md {{MOCKUP_BAR}} {{MOCKUP_PCT}}% │
│ └─ API.md {{API_BAR}} {{API_PCT}}% │
│ │
│ 2. Server Layer {{SERVER_BAR}} {{SERVER_PCT}}% │
│ │
│ 3. Client Layer {{CLIENT_BAR}} {{CLIENT_PCT}}% │
│ ├─ Network {{NETWORK_BAR}} {{NETWORK_PCT}}% │
│ └─ Data {{DATA_BAR}} {{DATA_PCT}}% │
│ │
│ 4. Feature Layer {{FEATURE_BAR}} {{FEATURE_PCT}}% │
│ ├─ ViewModels {{VM_BAR}} {{VM_PCT}}% │
│ ├─ Screens {{SCREEN_BAR}} {{SCREEN_PCT}}% │
│ └─ v2.0 Match {{V2_BAR}} {{V2_PCT}}% │
│ │
│ 5. Platform Layer {{PLATFORM_BAR}} {{PLATFORM_PCT}}% │
│ ├─ Android {{ANDROID_BAR}} {{ANDROID_PCT}}% │
│ ├─ iOS {{IOS_BAR}} {{IOS_PCT}}% │
│ ├─ Desktop {{DESKTOP_BAR}} {{DESKTOP_PCT}}% │
│ └─ Web {{WEB_BAR}} {{WEB_PCT}}% │
│ │
│ OVERALL {{OVERALL_BAR}} {{OVERALL_PCT}}% │
│ │
└─────────────────────────────────────────────────────────────────┘
BEFORE (O(n) - Slow):
┌──────────────────────────────────────────────────────┐
│ Question: "Which features need mockups?" │
│ → Scan 17 feature dirs │
│ → Read 50+ files │
│ → 2000+ lines │
│ → 5+ tool calls │
│ → 30+ seconds │
└──────────────────────────────────────────────────────┘
AFTER (O(1) - Fast):
┌──────────────────────────────────────────────────────┐
│ Question: "Which features need mockups?" │
│ → Read MOCKUPS_INDEX.md │
│ → 1 file, ~100 lines │
│ → 1 tool call │
│ → <3 seconds
└──────────────────────────────────────────────────────┘
```
---
### Feature Matrix (All Layers)
## 📁 O(1) INDEX FILES
| # | Feature | Design | Server | Client | Feature | Platform |
|:-:|---------|:------:|:------:|:------:|:-------:|:--------:|
{{FEATURE_MATRIX_ROWS}}
**Legend**: ✅ Complete | ⚠️ Partial | ❌ Missing | `-` N/A
| Layer | Index File | Path | Lines | Use For |
|-------|------------|------|:-----:|---------|
| Feature | MODULES_INDEX.md | `feature-layer/` | ~120 | Find any module |
| Feature | SCREENS_INDEX.md | `feature-layer/` | ~180 | Find any screen |
| Design | FEATURES_INDEX.md | `design-spec-layer/` | ~100 | Check feature status |
| Design | MOCKUPS_INDEX.md | `design-spec-layer/` | ~100 | Check mockup status |
| Client | FEATURE_MAP.md | `client-layer/` | ~150 | Map feature → services |
| Server | API_INDEX.md | `server-layer/` | ~80 | Find any endpoint |
| Platform | LAYER_STATUS.md | `platform-layer/` | ~80 | Platform commands |
---
### Layer Commands
## 🎯 RECOMMENDED NEXT STEPS
| Layer | Command | Status |
|-------|---------|--------|
| Design | `/gap-analysis design` | {{DESIGN_STATUS}} |
| Server | `/gap-analysis server` | {{SERVER_STATUS}} |
| Client | `/gap-analysis client` | {{CLIENT_STATUS}} |
| Feature | `/gap-analysis feature` | {{FEATURE_STATUS}} |
| Platform | `/gap-analysis platform` | {{PLATFORM_STATUS}} |
{{NEXT_STEPS}}
---
### Critical Gaps (P0)
## 🔄 WORKFLOW CYCLE
{{P0_GAPS_TABLE}}
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ O(1) DEVELOPMENT CYCLE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ /gap-analysis ──────────────────────────────────────────────────────→ │
│ │ │
│ │ Shows ALL implemented + ALL gaps + ALL commands │
│ │ │
│ ▼ │
│ YOU DECIDE what to work on │
│ │ │
│ ├─→ /gap-planning [target] → Get step-by-step plan │
│ │ │
│ ├─→ /implement [target] → Execute implementation │
│ │ │
│ ├─→ /verify [target] → Confirm it works │
│ │ │
│ └─→ /gap-analysis → See updated status (loop) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### High Priority Gaps (P1)
{{P1_GAPS_TABLE}}
### Quick Wins (P2)
{{P2_GAPS_TABLE}}
╚══════════════════════════════════════════════════════════════════════════════╝
```
---
**Drill down**: `/gap-analysis [layer]` or `/gap-analysis [feature-name]`
## Template Variables Reference
| Variable | Source | Example |
|----------|--------|---------|
| {{DATE}} | Current date | 2025-01-05 |
| {{DESIGN_BAR}} | Progress bar | [████████░░] |
| {{DESIGN_PCT}} | Percentage | 80 |
| {{DESIGN_DONE}} | Completed count | 14 |
| {{DESIGN_GAPS}} | Gap count | 3 |
| {{DESIGN_STATUS}} | Status text | ⚠️ Mockups |
| {{DESIGN_ROWS}} | Table rows | From FEATURES_INDEX.md |
| {{SERVER_ROWS}} | Table rows | From API_INDEX.md |
| {{SERVICE_ROWS}} | Table rows | From FEATURE_MAP.md |
| {{REPO_ROWS}} | Table rows | From FEATURE_MAP.md |
| {{MODULE_ROWS}} | Table rows | From MODULES_INDEX.md |
| {{PLATFORM_ROWS}} | Table rows | From LAYER_STATUS.md |
| {{P0_ROWS}} | Critical gaps | From gap analysis |
| {{P1_ROWS}} | High priority gaps | From gap analysis |
| {{P2_ROWS}} | Nice to have gaps | From gap analysis |
| {{PLAN_ROWS}} | Planning commands | Generated from gaps |
| {{NEXT_STEPS}} | Recommendations | Generated from priorities |