#!/usr/bin/env bash # OMNL Fineract — Reverse a journal entry by referenceNumber: find JE and post opposite debits/credits. # Usage: REFERENCE_NUMBER= bash scripts/omnl/omnl-je-reverse-by-reference.sh # Optional: DRY_RUN=1 to print payload only. Reversal is a new JE with same officeId/date, swapped debits/credits, comment "REVERSAL: ". # See OPERATING_RAILS.md and OFFICE_20_DR_RUNBOOK.md. set -euo pipefail REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" REFERENCE_NUMBER="${REFERENCE_NUMBER:-}" DRY_RUN="${DRY_RUN:-0}" if [ -z "$REFERENCE_NUMBER" ]; then echo "Set REFERENCE_NUMBER (e.g. SAMAMA-20-20260224-HO)" >&2 exit 1 fi if [ -f "${REPO_ROOT}/omnl-fineract/.env" ]; then set +u source "${REPO_ROOT}/omnl-fineract/.env" 2>/dev/null || true set -u elif [ -f "${REPO_ROOT}/.env" ]; then set +u source "${REPO_ROOT}/.env" 2>/dev/null || true set -u fi BASE_URL="${OMNL_FINERACT_BASE_URL:-}" TENANT="${OMNL_FINERACT_TENANT:-omnl}" USER="${OMNL_FINERACT_USER:-app.omnl}" PASS="${OMNL_FINERACT_PASSWORD:-}" TRANSACTION_DATE="${TRANSACTION_DATE:-$(date +%Y-%m-%d)}" if [ -z "$BASE_URL" ] || [ -z "$PASS" ]; then echo "Set OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD" >&2 exit 1 fi CURL_OPTS=(-s -S -H "Fineract-Platform-TenantId: ${TENANT}" -H "Content-Type: application/json" -u "${USER}:${PASS}") # Try to find JE by referenceNumber (Fineract may support referenceNumber in list or search) # GET journalentries often takes fromDate, toDate, officeId - we may need to fetch and filter from_date=$(date -d "${TRANSACTION_DATE} -30 days" +%Y-%m-%d 2>/dev/null || echo "2020-01-01") je_list=$(curl "${CURL_OPTS[@]}" "${BASE_URL}/journalentries?fromDate=${from_date}&toDate=${TRANSACTION_DATE}" 2>/dev/null) || je_list="[]" je_match=$(echo "$je_list" | jq --arg ref "$REFERENCE_NUMBER" ' (if type == "array" then . else (.pageItems // .) end) | if type == "array" then . else [] end | map(select(.referenceNumber == $ref or .reference_number == $ref)) | .[0] ' 2>/dev/null) if [ -z "$je_match" ] || [ "$je_match" = "null" ]; then echo "No journal entry found with referenceNumber=$REFERENCE_NUMBER. List API may not support reference filter; check UI or run audit packet to get JE id." >&2 echo "To reverse manually: 1) GET journalentries or use UI to find JE id and its debits/credits. 2) POST a new JE with officeId/transactionDate, comments=\"REVERSAL: $REFERENCE_NUMBER\", debits=original credits, credits=original debits." >&2 exit 1 fi office_id=$(echo "$je_match" | jq -r '.officeId // .office.id // 1') debits=$(echo "$je_match" | jq -c '.debits // .debitAccounts // []') credits=$(echo "$je_match" | jq -c '.credits // .creditAccounts // []') amount=$(echo "$je_match" | jq -r '.debits[0].amount // .credits[0].amount // 0') # Swap: reversal debits = original credits, reversal credits = original debits rev_debits="$credits" rev_credits="$debits" reversal_ref="REV-${REFERENCE_NUMBER}" body=$(jq -n \ --argjson officeId "$office_id" \ --arg transactionDate "$TRANSACTION_DATE" \ --arg comments "REVERSAL: $REFERENCE_NUMBER" \ --arg referenceNumber "$reversal_ref" \ --arg dateFormat "yyyy-MM-dd" \ --arg locale "en" \ --arg currencyCode "USD" \ --argjson revDebits "$rev_debits" \ --argjson revCredits "$rev_credits" \ '{ officeId: $officeId, transactionDate: $transactionDate, dateFormat: $dateFormat, locale: $locale, currencyCode: $currencyCode, comments: $comments, referenceNumber: $referenceNumber, debits: $revDebits, credits: $revCredits }') if [ "$DRY_RUN" = "1" ]; then echo "DRY_RUN: would POST reversal for $REFERENCE_NUMBER (officeId=$office_id amount=$amount)" >&2 echo "$body" | jq '.' >&2 exit 0 fi out=$(curl "${CURL_OPTS[@]}" -X POST -d "$body" "${BASE_URL}/journalentries" 2>/dev/null) if echo "$out" | jq -e '.resourceId // .officeId' >/dev/null 2>&1; then echo "Posted reversal: $reversal_ref for original $REFERENCE_NUMBER" >&2 echo "$out" | jq '.' >&2 else echo "POST reversal failed: $out" >&2 exit 1 fi