name: izinga-backend-developer description: "Use when working on the iZinga backend in ijudi-api: Java 17 and Kotlin on Spring Boot 3, Spring Web REST APIs, JWT/OAuth2 resource server, MongoDB with Spring Data Mongo, Spring AI MCP server tools, and domain flows for users, stores, orders, drivers, WhatsApp/push messaging, store menus, Yoco payments, and recon payouts. Includes architecture-safe workflows for scanning modules, fixing bugs, and adding features without breaking existing contracts." argument-hint: "Backend task goal, affected domain, and target module"
iZinga Backend Developer Skill
Outcome
Use this skill to make safe, architecture-aligned backend changes in ijudi-api.
This skill helps you:
- Map the correct module before editing.
- Preserve API contracts and shared models.
- Implement features and bug fixes with minimal cross-module risk.
- Validate with focused checks before finishing.
When To Use
Use this skill for:
- Kotlin and Java 17 Spring Boot 3 changes.
- Spring Web controllers, services, validation, JSON payload changes.
- Spring Data Mongo repositories and query logic.
- JWT and OAuth2 resource server behavior.
- MCP tool additions and MCP server debugging.
- iZinga domain work: order management, users, stores, user roles, store type, WhatsApp, push notifications, order stages, drivers, store menu sync, Yoco payment, recon payouts.
Do not use this skill for:
- Frontend Angular work.
- Large schema redesign without migration planning.
Project Architecture Snapshot
Repository root is multi-module Maven with Spring Boot parent.
Core modules and responsibilities:
- izinga-ordermanager:
- Runtime API host and main integration hub.
- Entry point: src/main/java/io/curiousoft/izinga/ordermanagement/IjudiApplication.java
- Domain APIs: order, store, device, promotion, restricted regions, shopping-list.
- Security and JWT/OAuth2 resource server config.
- MCP server wiring via McpConfig using
MethodToolCallbackProvider.builder().toolObjects(...). - MCP transport endpoints exposed by Spring AI:
GET /sseandPOST /mcp/message.
- izinga-commons:
- Shared models, enums, repositories, and validators.
- MongoDB shared entities and reusable data contracts.
- izinga-usermanagement:
- User profile CRUD, pending approvals, user config, wallet pass.
- User config templates are stored in
userTypeConfigand now support role mapping viaUserConfig.userRole. - MCP tools for user lookup/create/location-based search.
- izinga-recon:
- Payout and payout bundles (shop and messenger), CSV exports, bank config.
- MCP tool for payouts lookup.
- izinga-messaging:
- WhatsApp webhook and forwarding, Firebase push and Firestore integration, SMS support, AI agent config classes.
- izinga-yoco-pay:
- Yoco payment initiation, webhook finalization, refunds.
- izinga-documentmanagement:
- Document upload/delete and S3 integration.
- izinga-qrcode-generator:
- Tip QR and promo code management.
- store-menu-to-izinga-menu:
- Store menu ingestion/update utility (AWS/serverless style integration module).
- In sandbox clones this submodule is often not initialized; local reactor builds may require a temporary
store-menu-to-izinga-menu/pom.xmlstub.
Key Domain Contracts
Shared enums and models are in izinga-commons and must be treated as stable contracts:
- Profile roles: CUSTOMER, STORE_ADMIN, STORE, MESSENGER, MESSENGER_ADMIN, ADMIN.
- Store type: FOOD, CLOTHING, SALON, CAR_WASH, MOVERS, TIPS, LICENSING, PARTS.
- Order stages: STAGE_0_CUSTOMER_NOT_PAID through STAGE_7_ALL_PAID, CANCELLED.
Messenger admin ownership contract:
- Messenger assignment is resolved via
UserProfile.tag["messengerAdminId"]. - Team-scoped reads must only return messengers/orders/payouts that map to the authenticated messenger admin.
Primary API surfaces:
- /order, /store, /user, /user-config, /recon, /document, /yoco/payment, /whatsapp/webhook.
Delivery pricing contract:
GET /order/delivery-priceremains stable and returnsDeliveryPriceEstimateDto.Ratesnow supports additive per-vehicle distance pricing fields:ratePerKmBike,ratePerKmCar,ratePerKmBakkie,ratePerKmTruck.- Per-vehicle fallback order for distance rate is: vehicle-specific
ratePerKm*-> store-levelratePerKm-> global defaultratePerKm. - Vehicle resolution is based on existing category labels (
Bike Delivery Driver,Small/Medium Vehicle Driver,Bakkie Delivery Driver,Truck Delivery Driver). Preserve these labels for compatibility unless a coordinated contract change is requested.
User config contract:
UserConfigdocuments are keyed bynameand patched via/user-config/{userType}.UserConfig.userRoleis an additive optionalProfileRolesmapping and must be preserved on update paths.UserConfigService.updatekeeps the persisted key (name) and updates label/mandatoryFields/optionalFields/userRole.
Recent additive query surfaces:
/user?role=MESSENGER&messengerAdminId=<id>for messenger admin team lookup./order?messengerAdminId=<id>&allStages=<bool>for team orders./recon/payout?payoutType=MESSENGER&messengerAdminId=<id>&fromDate=<iso>&toDate=<iso>&messengerId=<optional>for team payouts and drill-down./order/{orderId}/nextstage?latitude=<double>&longitude=<double>— optional location params appended to every stage advance. Params arerequired = false; omit for backward-compatible no-location calls.
Messenger Order API
Controller: izinga-ordermanager/.../orders/MessengerOrderController.java
Base path: /messenger/order
All responses are wrapped in MessengerOrderDto so fees reflect the actual messenger payout after iZinga commission deduction.
Endpoints:
GET /messenger/order?messengerId=<id>&allStages=<bool>— list orders for an assigned messenger.GET /messenger/order?messengerAdminId=<id>&allStages=<bool>— list orders for all messengers in a team. UsesfindOrdersByMessengerAdminId(returns rawOrder) and wraps each innew MessengerOrderDto(order, izingaCommissionPerc).GET /messenger/order/{id}— single order with commission deducted. CallsfindOrderForMessenger.GET /messenger/order/{orderId}/nextstage?latitude=<double>&longitude=<double>— advance stage, optionally with GPS coordinates.PATCH /messenger/order/{orderId}/quote— accept or reject a delivery quote.
Key design notes:
findOrderByMessengerIdalready returnsMessengerOrderDtoinstances internally; cast directly.findOrdersByMessengerAdminIdreturns rawOrder; wrap explicitly withnew MessengerOrderDto(o, izingaCommissionPerc).- Do NOT return raw
Orderfrom this controller — always wrap so commission is applied.
Scheduled Jobs
Class: izinga-ordermanager/.../service/SchedulerService.java
notifyUnregisteredWhatsappUsers
- Status: implemented but currently disabled — the
@Scheduledannotation is commented out. - Intended schedule: daily at 08:00 UTC (
cron = "* 0 8 * * *"). - Logic:
- Queries
WhatsappSessionRepo.findByLastMessageDateBetween(yesterday, today)for sessions active the previous day. - For each session, prepends
+tosession.getFrom()and callsuserProfileRepo.findByMobileNumber(from). - If no registered user is found, sends re-engagement landing options via
smsNotificationService.sendLandingOptions(from, null, null). - Tracks
[sentCount, errorCount]and logs a summary in the finally block.
- Queries
- Enabling: remove the comment prefix from
@Scheduled(cron = "* 0 8 * * *")on the method. - Dependency: requires
WhatsappSessionRepoinjected intoSchedulerService(already wired).
WhatsappSessionRepo — additive query method
File: izinga-messaging/.../repo/WhatsappSessionRepo.java
List<WhatsappSession> findByLastMessageDateBetween(LocalDate start, LocalDate end)— finds sessions whoselastMessageDatefalls within the given date range. Used by the scheduler job above.- Note:
WhatsappSession.lastMessageDateis stored asInstantin the model but this derived query uses Spring Data's between semantics — verify index if session volume grows.
AI Correction Feedback Loop
Controller: izinga-messaging/.../whatsapp/FirestoreToWhatsappController.java
Endpoint: GET /forward/chatSession/{cSId}/message/{msgId}
Purpose: When a human agent writes a correction message in a Firestore chat session and calls this endpoint, it:
- Loads
ChatSessionandFireStoreMessagefrom Firestore (404 if either missing). Guards non-TEXT message type with 400. - Normalizes
customerMobileNumber(0prefix →+27). - Sends the human-written correction text (
msg.getMessage()) directly to the customer via WhatsApp.AiCustomerServiceAgentis NOT involved — the human text is forwarded as-is. - On success — Correction Step A: calls
ConversationHistoryService.recordHumanCorrection(normalizedPhone, customerName, messageText)— atomic@Transactionalmethod that gets-or-creates the conversation and appends[HUMAN CORRECTION] <text>as an assistant message. - On success — Correction Step B: calls
AiAgentConfigService.appendHumanCorrection("driver_support", normalizedPhone, messageText)— null-safe method that loads the active config, creates the## Human Correction Logsection if absent, appends the entry, and saves (version incremented). - Returns 200 with
{status: sent, response: <WhatsApp body>}.
Injected services: FirestoreService, WhatsAppService, WhatsappConfig, ConversationHistoryService, AiAgentConfigService. AiCustomerServiceAgent is intentionally NOT injected.
Refactored service methods (current):
ConversationHistoryService.recordHumanCorrection(String phone, String name, String messageText)— atomic@Transactional: callsgetOrCreateConversationthenaddAssistantMessage.AiAgentConfigService.appendHumanCorrection(String agentName, String phone, String messageText)— null-safe load → prompt guard → section create/append →saveAgentConfig. Returns early if no active config found.
Safety: Both correction steps run AFTER the successful WhatsApp send and are wrapped in independent try/catch blocks. A failure in either does NOT affect the WhatsApp delivery response.
Agent name constant: AGENT_NAME = "driver_support".
Correction log format in system prompt:
## Human Correction Log
The following corrections were made by human agents. Apply these as guidance when similar questions arise:
- [<Instant>] Customer <normalizedTo>: <message text>
- [<Instant>] Customer <normalizedTo>: <message text>
Order Location History Feature
Model Changes (izinga-commons)
Order.kt now carries:
var statusHistory: MutableList<OrderStatusHistory>? = mutableListOf()
fun addStatusHistory(stage: OrderStage?, lati: Double? = null, longi: Double? = null) {
// appends OrderStatusHistory(stage, OrderStatusLocation(lati, longi)) if lati/longi non-null else location=null
}
fun addStatusHistory(stage: OrderStage?) { addStatusHistory(stage, null, null) } // Java-callable bridge overload
Data classes at end of Order.kt:
data class OrderStatusHistory(var stage: OrderStage? = null, var location: OrderStatusLocation? = null)
data class OrderStatusLocation(var lati: Double? = null, var longi: Double? = null)
Stored as an embedded MongoDB subdocument array. Last item in the list is the most recent stage.
Stage-Change Coverage
addStatusHistory() is called at every stage-change site:
| Location | Stage Recorded |
|---|---|
OrderServiceImpl.startOrder() |
STAGE_0_CUSTOMER_NOT_PAID |
OrderServiceImpl.finishOder() — online-paid branch |
STAGE_1_WAITING_STORE_CONFIRM |
OrderServiceImpl.finishOder() — already-paid branch |
STAGE_7_ALL_PAID |
OrderServiceImpl.progressNextStage() |
computed next stage (with optional lat/lng) |
OrderServiceImpl.cancelOrder() |
CANCELLED |
CustomerOrderEventHandler |
STAGE_7_ALL_PAID on STAGE_6 auto-complete |
MessengerOrderEventHandler |
STAGE_7_ALL_PAID on incentive order creation |
Interface Contract
OrderService.kt carries both overloads:
fun progressNextStage(orderId: String?): Order?
fun progressNextStage(orderId: String?, latitude: Double?, longitude: Double?): Order?
Kotlin ↔ Java Interop Rule
Kotlin default parameters do NOT generate Java-callable overloads automatically. When a Kotlin method with default parameters is called from Java, always add an explicit bridge overload:
// Primary (Kotlin and Java callers passing all args)
fun addStatusHistory(stage: OrderStage?, lati: Double? = null, longi: Double? = null) { ... }
// Bridge (Java callers omitting lat/lng)
fun addStatusHistory(stage: OrderStage?) { addStatusHistory(stage, null, null) }
Without @JvmOverloads or explicit bridge, Java callers get compile errors.
Standard Workflow
1. Understand The Request And Domain
- Identify business domain first: users, stores, orders, payments, messaging, recon, docs, QR.
- Identify whether change is API behavior, model shape, persistence, security, or integration.
2. Map To The Correct Module
- Start from controller location and trace to service and repository.
- If a model is in izinga-commons, treat it as cross-module impact.
3. Contract Impact Check (Required)
Before editing, answer:
- Will request/response JSON change?
- Will enum values or stage transitions change?
- Will repository query semantics change?
- Will security requirements change for /v2 APIs or MCP endpoints?
If yes, apply backward-compatible change where possible:
- Prefer additive fields over renames/removals.
- Keep endpoint paths and HTTP methods stable unless explicitly requested.
- Preserve existing query parameter behavior.
4. Implement Minimal Safe Change
- Follow current style in that module (Java or Kotlin).
- Keep logic in service layer, not controller-heavy.
- Reuse shared validators/utilities where available.
- For Mongo changes, preserve repository method naming conventions and index assumptions.
5. Domain Decision Points
User management:
- If lookup is by phone, normalize SA numbers consistently (0, +27, 27 patterns).
- If create/update user, preserve role-based behavior and profile approval flow.
- For messenger admin features, use additive filters and resolve team members by
tag.messengerAdminId. - For
/user-config, keepnameas the stable id and treatuserRoleas additive role metadata (do not rename/remove existing user types).
Store management:
- Keep owner role/store linkage and markup behavior intact.
- Respect location/range filtering semantics.
Order management:
- Preserve stage transition rules and cancellation constraints.
- Keep payment reversal and notification side effects intact.
- Handle quote acceptance without bypassing sanity checks.
- Preserve existing
messengerIdquery behavior while addingmessengerAdminIdteam scope. - For delivery pricing, keep per-vehicle rate-per-km behavior additive: resolve vehicle-specific rates first, then generic fallback, without changing endpoint contracts.
Payments and recon:
- Yoco webhook signature validation must stay strict.
- Recon payouts should remain separated by payout type (SHOP or MESSENGER).
- For
MESSENGERpayouts withmessengerAdminId, aggregate only managed messenger payout rows.
Messaging:
- WhatsApp webhook verification and payload handling must preserve event publishing behavior.
- Push and Firestore integrations should fail safely and log clearly.
MCP server:
- New MCP capabilities should be exposed via @Tool methods in service classes.
- Ensure service is registered through McpConfig toolObjects.
- Keep tool names descriptive and stable once published.
6. Validation Checklist
Run focused validation before completion:
- Compile affected module and dependencies:
- ./mvnw -pl
-am test
- ./mvnw -pl
- For API changes, check controller mappings and serialization.
- For security changes, confirm protected vs public endpoint behavior.
- For MCP changes:
- confirm /sse and /mcp/message reachability
- verify new tool appears and executes.
- For messenger admin scope:
- confirm cross-admin access is denied.
- confirm non-admin role cannot query team endpoints.
- confirm existing single-user query paths remain unchanged.
7. Write Tests With 100% Coverage (Mandatory)
Every implementation task MUST include tests. Task is not complete until tests are written and pass.
Coverage requirements:
- Every new public method in a service or controller must have a corresponding test.
- Cover the happy path AND all error/null/boundary branches in the same method.
- For service methods: mock all collaborators; test the service logic in isolation.
- For controller methods: use
@WebMvcTestor direct method call; assert HTTP status and body shape. - For scheduled jobs: test each branch (no sessions, registered user skip, null-from skip, error handling).
Test conventions in ijudi-api:
izinga-messaginguses JUnit 5 with Mockito:@ExtendWith(MockitoExtension.class),@Mock, constructor injection in@BeforeEach.- Other modules may use JUnit 4:
@RunWith(MockitoJUnitRunner.class),@Mock,@InjectMocksor constructor injection in@Before. Match the convention already used in that module's test directory. - Use
mock(ClassName.class)when the class has no no-arg constructor. - Place tests under
src/test/java/mirroring the main package path. - Name the test class
<ClassName>Test.javain the same package as the class under test. - Verify behaviour with
verify(mock).method(...), not just that no exception is thrown. - Use
when(...).thenReturn(...)for all collaborator stubs, never rely on default mock behaviour.
Coverage verification:
- After writing tests, run:
./mvnw -pl <module> -am test - All tests must pass before marking the task done.
- If a test cannot be written for a branch (e.g., private method reached only via integration), note it explicitly in the output.
8. Completion Criteria
Task is complete only when:
- Correct module and layers were updated.
- No accidental contract breaks on shared JSON or enums.
- Tests written for every new or changed method with 100% branch coverage.
- All module tests pass:
./mvnw -pl <module> -am test - Any new endpoint/tool is documented in code comments or module docs.
Bug Fix Playbook
- Reproduce from controller input to service output.
- Locate failing branch and domain invariant violated.
- Patch smallest change in service/repo/model layer.
- Write or update tests covering the fixed branch AND any adjacent untested branches.
- Re-run module checks:
./mvnw -pl <module> -am test - Verify no cross-module regression in shared contracts.
Feature Playbook
- Confirm existing endpoint can be extended additively.
- Add DTO/field/logic with backward compatibility.
- Keep persistence and validation aligned.
- Add role/security checks if needed.
- Add MCP tool exposure only if feature is intended for AI tools.
- Write tests: happy path + all error branches + null/boundary cases.
- Validate:
./mvnw -pl <module> -am test— all tests must pass.
Known Architectural Guardrails
Recommended Prompt Patterns
Quick Start Commands
- ./mvnw clean install -DskipTests
- ./mvnw -pl izinga-ordermanager -am test
- ./mvnw -pl izinga-usermanagement -am test
- ./mvnw -pl izinga-recon -am test
Related Skills
- meta-templates-developer: Use for WhatsApp Business template creation, update, listing, and sending via Meta Graph API and curl. Reference this skill for all template management workflows and troubleshooting.
File Structure Landmarks
- Parent modules list: pom.xml
- Security: izinga-ordermanager/src/main/java/io/curiousoft/izinga/ordermanagement/security
- MCP config: izinga-ordermanager/src/main/java/io/curiousoft/izinga/ordermanagement/McpConfig.java
- Shared domain and repos: izinga-commons/src/main/kotlin/io/curiousoft/izinga/commons
- User APIs: izinga-usermanagement/src/main/kotlin/io/curiousoft/izinga/usermanagement
- Recon and payouts: izinga-recon/src/main/kotlin/io/curiousoft/izinga/recon
- Messaging and WhatsApp: izinga-messaging/src/main/java/io/curiousoft/izinga/messaging
- Payments: izinga-yoco-pay/src/main/kotlin/io/curiousoft/izinga/yocopay