mirror of
https://github.com/openMF/mifos-mobile.git
synced 2026-02-06 11:26:51 +00:00
docs: add testing documentation to claude-product-cycle (#3057)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
caa4285fa3
commit
4bb7e78ffd
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
197
.claude/commands/verify-tests.md
Normal file
197
.claude/commands/verify-tests.md
Normal 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
|
||||
169
claude-product-cycle/client-layer/FEATURE_MAP.md
Normal file
169
claude-product-cycle/client-layer/FEATURE_MAP.md
Normal 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 |
|
||||
192
claude-product-cycle/client-layer/TESTING_STATUS.md
Normal file
192
claude-product-cycle/client-layer/TESTING_STATUS.md
Normal 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
|
||||
116
claude-product-cycle/design-spec-layer/FEATURES_INDEX.md
Normal file
116
claude-product-cycle/design-spec-layer/FEATURES_INDEX.md
Normal 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 ✅ |
|
||||
152
claude-product-cycle/design-spec-layer/MOCKUPS_INDEX.md
Normal file
152
claude-product-cycle/design-spec-layer/MOCKUPS_INDEX.md
Normal 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 |
|
||||
125
claude-product-cycle/design-spec-layer/TESTING_STATUS.md
Normal file
125
claude-product-cycle/design-spec-layer/TESTING_STATUS.md
Normal 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
|
||||
113
claude-product-cycle/feature-layer/MODULES_INDEX.md
Normal file
113
claude-product-cycle/feature-layer/MODULES_INDEX.md
Normal 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 |
|
||||
266
claude-product-cycle/feature-layer/SCREENS_INDEX.md
Normal file
266
claude-product-cycle/feature-layer/SCREENS_INDEX.md
Normal 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 |
|
||||
323
claude-product-cycle/feature-layer/TESTING_STATUS.md
Normal file
323
claude-product-cycle/feature-layer/TESTING_STATUS.md
Normal 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
|
||||
195
claude-product-cycle/platform-layer/LAYER_GUIDE.md
Normal file
195
claude-product-cycle/platform-layer/LAYER_GUIDE.md
Normal 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
|
||||
102
claude-product-cycle/platform-layer/LAYER_STATUS.md
Normal file
102
claude-product-cycle/platform-layer/LAYER_STATUS.md
Normal 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 |
|
||||
279
claude-product-cycle/platform-layer/TESTING_STATUS.md
Normal file
279
claude-product-cycle/platform-layer/TESTING_STATUS.md
Normal 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
|
||||
109
claude-product-cycle/platform-layer/platforms/ANDROID.md
Normal file
109
claude-product-cycle/platform-layer/platforms/ANDROID.md
Normal 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
|
||||
109
claude-product-cycle/platform-layer/platforms/DESKTOP.md
Normal file
109
claude-product-cycle/platform-layer/platforms/DESKTOP.md
Normal 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
|
||||
134
claude-product-cycle/platform-layer/platforms/IOS.md
Normal file
134
claude-product-cycle/platform-layer/platforms/IOS.md
Normal 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
|
||||
135
claude-product-cycle/platform-layer/platforms/WEB.md
Normal file
135
claude-product-cycle/platform-layer/platforms/WEB.md
Normal 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
|
||||
551
claude-product-cycle/server-layer/API_INDEX.md
Normal file
551
claude-product-cycle/server-layer/API_INDEX.md
Normal 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 |
|
||||
865
claude-product-cycle/server-layer/API_REFERENCE.md
Normal file
865
claude-product-cycle/server-layer/API_REFERENCE.md
Normal 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 |
|
||||
517
claude-product-cycle/server-layer/CLIENT_PATTERNS.md
Normal file
517
claude-product-cycle/server-layer/CLIENT_PATTERNS.md
Normal 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 |
|
||||
415
claude-product-cycle/server-layer/ERROR_HANDLING.md
Normal file
415
claude-product-cycle/server-layer/ERROR_HANDLING.md
Normal 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 |
|
||||
@ -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
|
||||
156
claude-product-cycle/server-layer/TESTING_STATUS.md
Normal file
156
claude-product-cycle/server-layer/TESTING_STATUS.md
Normal 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
|
||||
109
claude-product-cycle/server-layer/endpoints/AUTH.md
Normal file
109
claude-product-cycle/server-layer/endpoints/AUTH.md
Normal 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)
|
||||
164
claude-product-cycle/server-layer/endpoints/BENEFICIARY.md
Normal file
164
claude-product-cycle/server-layer/endpoints/BENEFICIARY.md
Normal 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)
|
||||
140
claude-product-cycle/server-layer/endpoints/CHARGES.md
Normal file
140
claude-product-cycle/server-layer/endpoints/CHARGES.md
Normal 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)
|
||||
108
claude-product-cycle/server-layer/endpoints/CLIENT.md
Normal file
108
claude-product-cycle/server-layer/endpoints/CLIENT.md
Normal 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)
|
||||
131
claude-product-cycle/server-layer/endpoints/GUARANTOR.md
Normal file
131
claude-product-cycle/server-layer/endpoints/GUARANTOR.md
Normal 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)
|
||||
160
claude-product-cycle/server-layer/endpoints/LOAN.md
Normal file
160
claude-product-cycle/server-layer/endpoints/LOAN.md
Normal 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)
|
||||
90
claude-product-cycle/server-layer/endpoints/NOTIFICATION.md
Normal file
90
claude-product-cycle/server-layer/endpoints/NOTIFICATION.md
Normal 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)
|
||||
198
claude-product-cycle/server-layer/endpoints/SAVINGS.md
Normal file
198
claude-product-cycle/server-layer/endpoints/SAVINGS.md
Normal 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)
|
||||
115
claude-product-cycle/server-layer/endpoints/SHARE.md
Normal file
115
claude-product-cycle/server-layer/endpoints/SHARE.md
Normal 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)
|
||||
122
claude-product-cycle/server-layer/endpoints/TRANSFER.md
Normal file
122
claude-product-cycle/server-layer/endpoints/TRANSFER.md
Normal 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)
|
||||
52
claude-product-cycle/server-layer/endpoints/USER.md
Normal file
52
claude-product-cycle/server-layer/endpoints/USER.md
Normal 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)
|
||||
@ -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 |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user