forked from Yara724/api
Track the tester and env template in git so a new tenant can be exercised without a YARA claim; keep filled client env files (secrets, national codes) ignored. Co-authored-by: Cursor <cursoragent@cursor.com>
1112 lines
37 KiB
Bash
Executable File
1112 lines
37 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Standalone Fanavaran claim-flow tester (NO YARA).
|
|
#
|
|
# Same stage order as the app:
|
|
# 1) Auth (GetAppToken → Login)
|
|
# 2) Policy inquiry → PolicyId
|
|
# 3) GEN.03 base claim
|
|
# 4) Driver inquiry → DriverId + resolve InsuranceCorpId from Persian caption
|
|
# 5) GEN.12 damage case
|
|
# 6) GEN.07 attachments (one multipart request per file)
|
|
# 7) GEN.08 expertise
|
|
#
|
|
# Plate / chassis / VIN / policy document fields are NOT inquired here —
|
|
# put them in the env file (Tejarat/ESG may be unavailable on the host).
|
|
#
|
|
# Usage:
|
|
# cp scripts/data/fanavaran-flow.env.example scripts/data/fanavaran-flow.moallem.env
|
|
# # fill section A
|
|
# ./scripts/fanavaran-flow-test.sh --env scripts/data/fanavaran-flow.moallem.env
|
|
#
|
|
# The script writes section B (CLAIM_ID, POLICY_ID, …) back into the same env
|
|
# file after each Fanavaran response. Re-run to resume; already-set ids are skipped.
|
|
# authenticationToken is reused from files/fanavaran-auth/<client>/tokens.env until
|
|
# Asia/Tehran midnight. Pass --force-login to ignore the cache.
|
|
#
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
STAGES="all"
|
|
ENV_FILE=""
|
|
PREVIEW_ONLY=0
|
|
ASSUME_YES=0
|
|
FORCE_LOGIN=0
|
|
OUT_DIR=""
|
|
TOKEN_CACHE_FILE=""
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
scripts/fanavaran-flow-test.sh --env <file> [options]
|
|
|
|
Fanavaran-only multi-stage submit. No YARA / no plate-provider inquiry.
|
|
|
|
Options:
|
|
--env <file> Required. Copy scripts/data/fanavaran-flow.env.example and fill section A.
|
|
Section B (CLAIM_ID, …) is written back into this file as stages succeed.
|
|
--stages <list> all | base | damage | attachments | expertise
|
|
or comma list (default: all).
|
|
--yes Submit without confirmation prompts
|
|
--preview-only Build payloads + run inquiries; do not POST creates/uploads
|
|
--force-login Ignore cached authenticationToken and call GetAppToken+Login
|
|
--out <dir> Artifact directory (default: files/fanavaran-flow/<client>/<ts>)
|
|
-h, --help
|
|
|
|
Auth token is cached in files/fanavaran-auth/<client>/tokens.env until Asia/Tehran
|
|
midnight (same as the Nest app). Re-runs reuse it; do not Login every invocation.
|
|
|
|
Stage sequence matches the app (soft-skip when id already present):
|
|
auth → policy → GEN.03 → driver+corp resolve → GEN.12 → GEN.07 → GEN.08
|
|
USAGE
|
|
}
|
|
|
|
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
|
info() { printf '\n==> %s\n' "$*"; }
|
|
ok() { printf 'OK: %s\n' "$*"; }
|
|
warn() { printf 'WARN: %s\n' "$*" >&2; }
|
|
|
|
need_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
|
|
}
|
|
|
|
require_var() {
|
|
local name="$1"
|
|
local hint="${2:-}"
|
|
if [[ -z "${!name:-}" ]]; then
|
|
if [[ -n "$hint" ]]; then
|
|
die "Missing required env var: $name ($hint)"
|
|
fi
|
|
die "Missing required env var: $name"
|
|
fi
|
|
}
|
|
|
|
# Prompt if empty (for section A values you may fill mid-run), then persist.
|
|
ask_var() {
|
|
local name="$1"
|
|
local question="$2"
|
|
local current="${!name:-}"
|
|
if [[ -n "$current" ]]; then
|
|
return 0
|
|
fi
|
|
local answer=""
|
|
if [[ -t 0 ]]; then
|
|
read -r -p "$question: " answer || true
|
|
fi
|
|
[[ -n "$answer" ]] || die "Missing required env var: $name — set it in $ENV_FILE or type it when asked."
|
|
persist_env "$name" "$answer"
|
|
}
|
|
|
|
persist_env() {
|
|
local key="$1"
|
|
local val="${2-}"
|
|
if [[ $# -lt 2 ]]; then
|
|
val="${!key:-}"
|
|
fi
|
|
printf -v "$key" '%s' "$val"
|
|
export "$key"
|
|
[[ -n "$val" ]] || return 0
|
|
[[ -n "${ENV_FILE:-}" && -f "$ENV_FILE" ]] || return 0
|
|
ENV_UPSERT_FILE="$ENV_FILE" ENV_UPSERT_KEY="$key" ENV_UPSERT_VAL="$val" node <<'NODE'
|
|
const fs = require("fs");
|
|
const file = process.env.ENV_UPSERT_FILE;
|
|
const key = process.env.ENV_UPSERT_KEY;
|
|
const val = process.env.ENV_UPSERT_VAL ?? "";
|
|
function quote(v) {
|
|
if (v === "") return "";
|
|
if (/^[0-9A-Za-z._/-]+$/.test(v)) return v;
|
|
return JSON.stringify(v);
|
|
}
|
|
let text = fs.readFileSync(file, "utf8");
|
|
if (!text.endsWith("\n")) text += "\n";
|
|
const lines = text.split("\n");
|
|
const re = new RegExp("^\\s*#?\\s*" + key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*=");
|
|
const assignment = key + "=" + quote(val);
|
|
let found = false;
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (re.test(lines[i])) {
|
|
lines[i] = assignment;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
const idx = lines.findIndex((l) => l.includes("B) FILLED BY SCRIPT"));
|
|
if (idx >= 0) {
|
|
let insertAt = idx + 1;
|
|
while (
|
|
insertAt < lines.length &&
|
|
(lines[insertAt].startsWith("#") || lines[insertAt].trim() === "")
|
|
) {
|
|
insertAt++;
|
|
}
|
|
lines.splice(insertAt, 0, assignment);
|
|
} else {
|
|
lines.push("", "# filled by script", assignment);
|
|
}
|
|
}
|
|
fs.writeFileSync(file, lines.join("\n"));
|
|
NODE
|
|
ok "Saved $key=$val → $ENV_FILE"
|
|
}
|
|
|
|
load_env_file() {
|
|
local file="$1"
|
|
[[ -f "$file" ]] || die "Env file not found: $file"
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line="${line#"${line%%[![:space:]]*}"}"
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
|
|
local key="${BASH_REMATCH[1]}"
|
|
local val="${BASH_REMATCH[2]}"
|
|
if [[ "$val" =~ ^\"(.*)\"$ ]]; then val="${BASH_REMATCH[1]}"; fi
|
|
if [[ "$val" =~ ^\'(.*)\'$ ]]; then val="${BASH_REMATCH[1]}"; fi
|
|
# Env file wins for empty CLI; do not override non-empty exported vars
|
|
if [[ -z "${!key:-}" ]]; then
|
|
printf -v "$key" '%s' "$val"
|
|
export "$key"
|
|
fi
|
|
fi
|
|
done < "$file"
|
|
}
|
|
|
|
confirm_submit() {
|
|
local label="$1"
|
|
if [[ "$PREVIEW_ONLY" -eq 1 ]]; then
|
|
info "Preview-only: skip submit for $label"
|
|
return 1
|
|
fi
|
|
if [[ "$ASSUME_YES" -eq 1 ]]; then
|
|
return 0
|
|
fi
|
|
local a=""
|
|
read -r -p "Submit $label now? [y/N]: " a || true
|
|
[[ "${a,,}" == "y" || "${a,,}" == "yes" ]]
|
|
}
|
|
|
|
stage_wanted() {
|
|
local name="$1"
|
|
[[ "$STAGES" == "all" ]] && return 0
|
|
local p
|
|
IFS=',' read -r -a parts <<< "$STAGES"
|
|
for p in "${parts[@]}"; do
|
|
p="$(echo "$p" | tr -d '[:space:]')"
|
|
[[ "$p" == "$name" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
extract_header() {
|
|
local header_name="$1"
|
|
local header_file="$2"
|
|
awk -F': ' -v wanted="$header_name" '
|
|
tolower($1) == tolower(wanted) {
|
|
gsub(/\r/, "", $2)
|
|
print $2
|
|
exit
|
|
}
|
|
' "$header_file"
|
|
}
|
|
|
|
json_get() {
|
|
local file="$1"
|
|
local expr="$2"
|
|
node -e '
|
|
const fs = require("fs");
|
|
const file = process.argv[1];
|
|
const expr = process.argv[2];
|
|
const raw = fs.readFileSync(file, "utf8");
|
|
let data;
|
|
try { data = JSON.parse(raw); } catch (e) {
|
|
console.error("Invalid JSON in " + file);
|
|
process.exit(2);
|
|
}
|
|
let out;
|
|
try {
|
|
out = Function("data", "return (" + expr + ");")(data);
|
|
} catch (e) {
|
|
console.error(String(e));
|
|
process.exit(3);
|
|
}
|
|
if (out === undefined || out === null) process.stdout.write("");
|
|
else if (typeof out === "object") process.stdout.write(JSON.stringify(out));
|
|
else process.stdout.write(String(out));
|
|
' "$file" "$expr"
|
|
}
|
|
|
|
pretty_json_file() {
|
|
local file="$1"
|
|
node -e '
|
|
const fs = require("fs");
|
|
const p = process.argv[1];
|
|
try {
|
|
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n");
|
|
} catch { /* leave raw */ }
|
|
' "$file"
|
|
}
|
|
|
|
save_ids() {
|
|
mkdir -p "$OUT_DIR"
|
|
cat > "$OUT_DIR/ids.env" <<EOF
|
|
CLAIM_ID='${CLAIM_ID:-}'
|
|
CLAIM_NO='${CLAIM_NO:-}'
|
|
POLICY_ID='${POLICY_ID:-}'
|
|
DRIVER_ID='${DRIVER_ID:-}'
|
|
INSURANCE_CORP_ID_NUM='${INSURANCE_CORP_ID_NUM:-}'
|
|
DMG_CASE_ID='${DMG_CASE_ID:-}'
|
|
EXPERTISE_ID='${EXPERTISE_ID:-}'
|
|
EOF
|
|
}
|
|
|
|
apply_client_auth_defaults() {
|
|
case "$FANAVARAN_CLIENT" in
|
|
tejaratno)
|
|
APP_NAME="${APP_NAME:-fanhab}"
|
|
APP_SECRET="${APP_SECRET:-5Fa@N#A2B}"
|
|
FANAVARAN_USERNAME="${FANAVARAN_USERNAME:-fanhabUser}"
|
|
FANAVARAN_PASSWORD="${FANAVARAN_PASSWORD:-Fan#@2U\$3er}"
|
|
CORP_ID="${CORP_ID:-3539}"
|
|
CONTRACT_ID="${CONTRACT_ID:-263}"
|
|
LOCATION="${LOCATION:-100}"
|
|
;;
|
|
parsian)
|
|
APP_NAME="${APP_NAME:-ParsianService}"
|
|
APP_SECRET="${APP_SECRET:-P@r30@n\$erv!ce}"
|
|
FANAVARAN_USERNAME="${FANAVARAN_USERNAME:-ParsianServiceUser}"
|
|
FANAVARAN_PASSWORD="${FANAVARAN_PASSWORD:-P@r30@n123}"
|
|
CORP_ID="${CORP_ID:-543}"
|
|
CONTRACT_ID="${CONTRACT_ID:-28}"
|
|
LOCATION="${LOCATION:-210050}"
|
|
;;
|
|
moallem)
|
|
APP_NAME="${APP_NAME:-ItTalie}"
|
|
APP_SECRET="${APP_SECRET:-itT@l!3@api}"
|
|
FANAVARAN_USERNAME="${FANAVARAN_USERNAME:-itTalieUser}"
|
|
FANAVARAN_PASSWORD="${FANAVARAN_PASSWORD:-itT@l!3@user}"
|
|
CORP_ID="${CORP_ID:-5650}"
|
|
CONTRACT_ID="${CONTRACT_ID:-304}"
|
|
LOCATION="${LOCATION:-30900}"
|
|
;;
|
|
*)
|
|
die "Unknown FANAVARAN_CLIENT=$FANAVARAN_CLIENT (parsian|tejaratno|moallem)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
apply_shared_defaults() {
|
|
ACCIDENT_CITY_ID="${ACCIDENT_CITY_ID:-701}"
|
|
ACCIDENT_REPORT_TYPE_ID="${ACCIDENT_REPORT_TYPE_ID:-155}"
|
|
ACCIDENT_VEHICLE_USED_ID="${ACCIDENT_VEHICLE_USED_ID:-1}"
|
|
COMPENSATION_REFERENCE_ID="${COMPENSATION_REFERENCE_ID:-167}"
|
|
CULPRIT_LICENCE_TYPE_ID="${CULPRIT_LICENCE_TYPE_ID:-2}"
|
|
CULPRIT_TYPE_ID="${CULPRIT_TYPE_ID:-337}"
|
|
ACCIDENT_CAUSE_ID="${ACCIDENT_CAUSE_ID:-6}"
|
|
DMG_CASE_TYPE_ID="${DMG_CASE_TYPE_ID:-175}"
|
|
DMG_HISTORY_STATUS="${DMG_HISTORY_STATUS:-5214}"
|
|
PLAQUE_KIND_ID="${PLAQUE_KIND_ID:-8}"
|
|
PLAQUE_SAMPLE_ID="${PLAQUE_SAMPLE_ID:-10}"
|
|
DRIVER_IS_INSURER="${DRIVER_IS_INSURER:-0}"
|
|
if [[ "$DRIVER_IS_INSURER" == "1" || "${DRIVER_IS_INSURER,,}" == "true" ]]; then
|
|
DRIVER_IS_OWNER="${DRIVER_IS_OWNER:-1}"
|
|
else
|
|
DRIVER_IS_OWNER="${DRIVER_IS_OWNER:-0}"
|
|
fi
|
|
FAULT_PERCENT="${FAULT_PERCENT:-100}"
|
|
ESTIMATE_AMOUNT="${ESTIMATE_AMOUNT:-1000}"
|
|
INSPECTION_PLACE_ID="${INSPECTION_PLACE_ID:-282}"
|
|
DROP_AMOUNT_STATUS="${DROP_AMOUNT_STATUS:-5458}"
|
|
ACCIDENT_LEVEL="${ACCIDENT_LEVEL:-5456}"
|
|
ACCIDENT_LOCATION_ADDRESS="${ACCIDENT_LOCATION_ADDRESS:-استان تهران شهر تهران}"
|
|
CULPRIT_LICENCE_NO="${CULPRIT_LICENCE_NO:-${FANAVARAN_DUMMY_LICENCE_NO:-9705463515}}"
|
|
CULPRIT_LICENCE_ISSU_DATE="${CULPRIT_LICENCE_ISSU_DATE:-1394/10/13}"
|
|
LICENCE_NO="${LICENCE_NO:-$CULPRIT_LICENCE_NO}"
|
|
DESC="${DESC:-موارد آسیب دیده خودرو}"
|
|
REPAIR_WAGE="${REPAIR_WAGE:-0}"
|
|
COMPONENT_REPLACEMENT_COST="${COMPONENT_REPLACEMENT_COST:-0}"
|
|
WASTE_VALUE="${WASTE_VALUE:-0}"
|
|
DROP_AMOUNT_ADDITIONS="${DROP_AMOUNT_ADDITIONS:-0}"
|
|
COMPONENT_REPLACEMENT_TAX="${COMPONENT_REPLACEMENT_TAX:-0}"
|
|
INSURANCE_CORP_CAPTION="${INSURANCE_CORP_CAPTION:-${INSURANCE_CORP_ID:-}}"
|
|
|
|
export \
|
|
ACCIDENT_CITY_ID ACCIDENT_REPORT_TYPE_ID ACCIDENT_VEHICLE_USED_ID \
|
|
COMPENSATION_REFERENCE_ID CULPRIT_LICENCE_TYPE_ID CULPRIT_TYPE_ID \
|
|
ACCIDENT_CAUSE_ID DMG_CASE_TYPE_ID DMG_HISTORY_STATUS \
|
|
PLAQUE_KIND_ID PLAQUE_SAMPLE_ID DRIVER_IS_OWNER FAULT_PERCENT \
|
|
ESTIMATE_AMOUNT INSPECTION_PLACE_ID DROP_AMOUNT_STATUS ACCIDENT_LEVEL \
|
|
ACCIDENT_LOCATION_ADDRESS CULPRIT_LICENCE_NO CULPRIT_LICENCE_ISSU_DATE \
|
|
LICENCE_NO DESC REPAIR_WAGE COMPONENT_REPLACEMENT_COST WASTE_VALUE \
|
|
DROP_AMOUNT_ADDITIONS COMPONENT_REPLACEMENT_TAX DRIVER_IS_INSURER \
|
|
INSURANCE_CORP_CAPTION INSURANCE_CORP_ID \
|
|
CLAIM_EXPERT_ID EXPERTISE_CLAIM_EXPERT_ID CLAIM_FILE_TYPE_ID \
|
|
ACCIDENT_DATE ACCIDENT_TIME GUILTY_NATIONAL_CODE \
|
|
DRIVER_NATIONAL_CODE DRIVER_BIRTH_YEAR DRIVER_BIRTH_MONTH DRIVER_BIRTH_DAY \
|
|
VEHICLE_KIND_ID DMG_SECTION_ID DMG_ASSESSMENT_DATE INSPECTION_TIME \
|
|
FANAVARAN_CLIENT CORP_ID CONTRACT_ID LOCATION CURL_PROXY
|
|
}
|
|
|
|
auth_fingerprint() {
|
|
printf '%s' "${APP_NAME}|${FANAVARAN_USERNAME}|${CORP_ID}|${CONTRACT_ID}|${LOCATION}"
|
|
}
|
|
|
|
next_tehran_midnight_ms() {
|
|
node <<'NODE'
|
|
const nowMs = Date.now();
|
|
const parts = Object.fromEntries(
|
|
new Intl.DateTimeFormat("en-US", {
|
|
timeZone: "Asia/Tehran",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
hourCycle: "h23",
|
|
})
|
|
.formatToParts(new Date(nowMs))
|
|
.filter((p) => p.type !== "literal")
|
|
.map((p) => [p.type, p.value]),
|
|
);
|
|
const msIntoDay =
|
|
((Number(parts.hour) * 60 + Number(parts.minute)) * 60 + Number(parts.second)) *
|
|
1000;
|
|
const msPerDay = 24 * 60 * 60 * 1000;
|
|
const remaining = msPerDay - msIntoDay;
|
|
process.stdout.write(String(nowMs + (remaining === 0 ? msPerDay : remaining)));
|
|
NODE
|
|
}
|
|
|
|
write_token_cache() {
|
|
mkdir -p "$(dirname "$TOKEN_CACHE_FILE")"
|
|
local expires_at
|
|
expires_at="$(next_tehran_midnight_ms)"
|
|
cat > "$TOKEN_CACHE_FILE" <<EOF
|
|
FANAVARAN_CLIENT='$FANAVARAN_CLIENT'
|
|
FANAVARAN_BASE_URL='$FANAVARAN_BASE_URL'
|
|
FANAVARAN_BIME_URL='$FANAVARAN_BIME_URL'
|
|
APP_TOKEN='$APP_TOKEN'
|
|
AUTHENTICATION_TOKEN='$AUTHENTICATION_TOKEN'
|
|
CORP_ID='$CORP_ID'
|
|
CONTRACT_ID='$CONTRACT_ID'
|
|
LOCATION='$LOCATION'
|
|
AUTH_EXPIRES_AT='$expires_at'
|
|
AUTH_FINGERPRINT='$(auth_fingerprint)'
|
|
EOF
|
|
cp "$TOKEN_CACHE_FILE" "$OUT_DIR/tokens.env" 2>/dev/null || true
|
|
ok "Cached authenticationToken until Tehran midnight (expiresAt=$expires_at)"
|
|
}
|
|
|
|
load_cached_token() {
|
|
[[ -f "$TOKEN_CACHE_FILE" ]] || return 1
|
|
local cached_token="" cached_expires="" cached_fp="" cached_app=""
|
|
cached_token="$(
|
|
awk -F= '/^AUTHENTICATION_TOKEN=/{val=$2; gsub(/^'\''|'\''$/, "", val); print val; exit}' "$TOKEN_CACHE_FILE"
|
|
)"
|
|
cached_expires="$(
|
|
awk -F= '/^AUTH_EXPIRES_AT=/{val=$2; gsub(/^'\''|'\''$/, "", val); print val; exit}' "$TOKEN_CACHE_FILE"
|
|
)"
|
|
cached_fp="$(
|
|
awk -F= '/^AUTH_FINGERPRINT=/{val=$2; gsub(/^'\''|'\''$/, "", val); print val; exit}' "$TOKEN_CACHE_FILE"
|
|
)"
|
|
cached_app="$(
|
|
awk -F= '/^APP_TOKEN=/{val=$2; gsub(/^'\''|'\''$/, "", val); print val; exit}' "$TOKEN_CACHE_FILE"
|
|
)"
|
|
[[ -n "$cached_token" ]] || return 1
|
|
if [[ -n "$cached_fp" && "$cached_fp" != "$(auth_fingerprint)" ]]; then
|
|
warn "Cached token fingerprint mismatch (credentials/location changed) — will re-login"
|
|
return 1
|
|
fi
|
|
local now_ms
|
|
now_ms="$(node -e 'process.stdout.write(String(Date.now()))')"
|
|
if [[ -n "$cached_expires" && "$cached_expires" -gt "$now_ms" ]]; then
|
|
AUTHENTICATION_TOKEN="$cached_token"
|
|
APP_TOKEN="$cached_app"
|
|
export AUTHENTICATION_TOKEN APP_TOKEN
|
|
cp "$TOKEN_CACHE_FILE" "$OUT_DIR/tokens.env" 2>/dev/null || true
|
|
ok "Reusing cached authenticationToken until Tehran midnight"
|
|
return 0
|
|
fi
|
|
warn "Cached authenticationToken expired at Tehran midnight — will re-login"
|
|
return 1
|
|
}
|
|
|
|
# Optional SOCKS/HTTP proxy (Termius dynamic port forward → socks5://127.0.0.1:PORT).
|
|
curl_proxy_args() {
|
|
local proxy="${CURL_PROXY:-${ALL_PROXY:-}}"
|
|
if [[ -n "$proxy" ]]; then
|
|
printf '%s' "--proxy $proxy"
|
|
fi
|
|
}
|
|
|
|
fanavaran_auth_fail() {
|
|
local headers_file="$1"
|
|
local body_file="$2"
|
|
local step="$3"
|
|
local code msg
|
|
code="$(awk 'NR==1 {print $2}' "$headers_file" 2>/dev/null || true)"
|
|
msg="$(json_get "$body_file" 'data.Message || data.message || ""')"
|
|
if [[ -n "$msg" ]]; then
|
|
die "Fanavaran $step failed (HTTP ${code:-?}): $msg"
|
|
fi
|
|
die "Fanavaran $step failed (HTTP ${code:-?}) — see $headers_file / $body_file"
|
|
}
|
|
|
|
fanavaran_login() {
|
|
FANAVARAN_BASE_URL="${FANAVARAN_BASE_URL:-https://apimanager.iraneit.com/BimeApiManager/api}"
|
|
FANAVARAN_BIME_URL="${FANAVARAN_BIME_URL:-$FANAVARAN_BASE_URL/BimeApi/v2.0}"
|
|
TOKEN_CACHE_FILE="${TOKEN_CACHE_FILE:-$ROOT_DIR/files/fanavaran-auth/$FANAVARAN_CLIENT/tokens.env}"
|
|
|
|
if [[ -n "${CURL_PROXY:-${ALL_PROXY:-}}" ]]; then
|
|
ok "Using proxy: ${CURL_PROXY:-$ALL_PROXY}"
|
|
fi
|
|
|
|
if [[ "$FORCE_LOGIN" -ne 1 ]] && load_cached_token; then
|
|
return 0
|
|
fi
|
|
|
|
local app_headers="$OUT_DIR/get-app-token.headers"
|
|
local app_body="$OUT_DIR/get-app-token.body.json"
|
|
local login_headers="$OUT_DIR/login.headers"
|
|
local login_body="$OUT_DIR/login.body.json"
|
|
|
|
info "1) Auth — GetAppToken ($FANAVARAN_CLIENT) (no valid cache)"
|
|
# shellcheck disable=SC2046
|
|
curl -sS -D "$app_headers" -o "$app_body" \
|
|
$(curl_proxy_args) \
|
|
-X POST "$FANAVARAN_BASE_URL/EITAuthentication/GetAppToken" \
|
|
-H "appname: $APP_NAME" \
|
|
-H "secret: $APP_SECRET" \
|
|
-H "Content-Length: 0"
|
|
|
|
APP_TOKEN="$(extract_header apptoken "$app_headers")"
|
|
[[ -n "$APP_TOKEN" ]] || fanavaran_auth_fail "$app_headers" "$app_body" "GetAppToken"
|
|
|
|
info "1) Auth — Login"
|
|
# shellcheck disable=SC2046
|
|
curl -sS -D "$login_headers" -o "$login_body" \
|
|
$(curl_proxy_args) \
|
|
-X POST "$FANAVARAN_BASE_URL/EITAuthentication/Login" \
|
|
-H "appToken: $APP_TOKEN" \
|
|
-H "userName: $FANAVARAN_USERNAME" \
|
|
-H "password: $FANAVARAN_PASSWORD" \
|
|
-H "Content-Length: 0"
|
|
|
|
AUTHENTICATION_TOKEN="$(extract_header authenticationtoken "$login_headers")"
|
|
if [[ -z "$AUTHENTICATION_TOKEN" ]]; then
|
|
AUTHENTICATION_TOKEN="$(json_get "$login_body" 'data.authenticationToken || data.authenticationtoken || data.authentication_token || ""')"
|
|
fi
|
|
if [[ -z "$AUTHENTICATION_TOKEN" ]]; then
|
|
local msg
|
|
msg="$(json_get "$login_body" 'data.Message || data.message || ""')"
|
|
if [[ "$msg" == *"مجدد تلاش"* ]] && load_cached_token; then
|
|
warn "Login asked to retry later — reused still-valid cached token"
|
|
return 0
|
|
fi
|
|
fanavaran_auth_fail "$login_headers" "$login_body" "Login"
|
|
fi
|
|
ok "authenticationToken acquired"
|
|
write_token_cache
|
|
}
|
|
|
|
fv_curl_json() {
|
|
local method="$1"
|
|
local url="$2"
|
|
local out_body="$3"
|
|
local data_file="${4:-}"
|
|
local headers_file="${out_body%.json}.headers"
|
|
if [[ -n "$data_file" ]]; then
|
|
# shellcheck disable=SC2046
|
|
curl -sS -D "$headers_file" -o "$out_body" \
|
|
$(curl_proxy_args) \
|
|
-X "$method" "$url" \
|
|
-H "authenticationToken: $AUTHENTICATION_TOKEN" \
|
|
-H "CorpId: $CORP_ID" \
|
|
-H "ContractId: $CONTRACT_ID" \
|
|
-H "Location: $LOCATION" \
|
|
-H "Content-Type: application/json" \
|
|
--data @"$data_file"
|
|
else
|
|
# shellcheck disable=SC2046
|
|
curl -sS -D "$headers_file" -o "$out_body" \
|
|
$(curl_proxy_args) \
|
|
-X "$method" "$url" \
|
|
-H "authenticationToken: $AUTHENTICATION_TOKEN" \
|
|
-H "CorpId: $CORP_ID" \
|
|
-H "ContractId: $CONTRACT_ID" \
|
|
-H "Location: $LOCATION" \
|
|
-H "Content-Type: application/json"
|
|
fi
|
|
pretty_json_file "$out_body" || true
|
|
local code
|
|
code="$(awk 'NR==1 {print $2}' "$headers_file")"
|
|
printf 'HTTP %s → %s\n' "$code" "$out_body"
|
|
}
|
|
|
|
# ---------- inquiries ----------
|
|
inquire_policy_id() {
|
|
if [[ -n "${POLICY_ID:-}" ]]; then
|
|
ok "Using POLICY_ID=$POLICY_ID from env (skip inquiry)"
|
|
return 0
|
|
fi
|
|
ask_var GUILTY_NATIONAL_CODE "Guilty insurer national code (policy inquiry)"
|
|
|
|
local out="$OUT_DIR/policy-inquiry.json"
|
|
info "2) Policy inquiry — NationalCode=$GUILTY_NATIONAL_CODE"
|
|
fv_curl_json GET \
|
|
"$FANAVARAN_BIME_URL/common/Policies/inquiry-my-policies?InsuranceLineId=5&NationalCode=${GUILTY_NATIONAL_CODE}" \
|
|
"$out"
|
|
|
|
POLICY_ID="$(node -e '
|
|
const fs = require("fs");
|
|
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
const rows = Array.isArray(data) ? data : (data?.value || data?.data || []);
|
|
if (!Array.isArray(rows) || !rows.length) { console.log(""); process.exit(0); }
|
|
const withId = rows
|
|
.map(r => ({ id: Number(r.PolicyId), end: String(r.EndDate || "").trim() }))
|
|
.filter(x => Number.isFinite(x.id) && x.id > 0 && x.end);
|
|
if (!withId.length) { console.log(""); process.exit(0); }
|
|
withId.sort((a, b) => (a.end < b.end ? 1 : a.end > b.end ? -1 : 0));
|
|
console.log(String(withId[0].id));
|
|
' "$out")"
|
|
|
|
[[ -n "$POLICY_ID" ]] || die "Policy inquiry returned no PolicyId. Inspect $out or set POLICY_ID in env."
|
|
ok "Selected PolicyId=$POLICY_ID (latest EndDate in response)"
|
|
persist_env POLICY_ID
|
|
save_ids
|
|
}
|
|
|
|
inquire_driver_id() {
|
|
if [[ -n "${DRIVER_ID:-}" ]]; then
|
|
ok "Using DRIVER_ID=$DRIVER_ID from env (skip inquiry)"
|
|
return 0
|
|
fi
|
|
ask_var DRIVER_NATIONAL_CODE "Damaged-party driver national code"
|
|
ask_var DRIVER_BIRTH_YEAR "Driver Jalali birth year"
|
|
ask_var DRIVER_BIRTH_MONTH "Driver Jalali birth month (1-12)"
|
|
ask_var DRIVER_BIRTH_DAY "Driver Jalali birth day (1-31)"
|
|
|
|
local out="$OUT_DIR/driver-inquiry.json"
|
|
local url="$FANAVARAN_BIME_URL/common/parties/inquiry-by-unique-identifier"
|
|
url+="?NationalCode=${DRIVER_NATIONAL_CODE}"
|
|
url+="&BirthYear=${DRIVER_BIRTH_YEAR}&BirthMonth=${DRIVER_BIRTH_MONTH}&BirthDay=${DRIVER_BIRTH_DAY}"
|
|
|
|
info "4a) Driver inquiry"
|
|
fv_curl_json GET "$url" "$out"
|
|
|
|
local target_role=166
|
|
if [[ "$DRIVER_IS_INSURER" == "1" || "${DRIVER_IS_INSURER,,}" == "true" ]]; then
|
|
target_role=161
|
|
fi
|
|
|
|
DRIVER_ID="$(node -e '
|
|
const fs = require("fs");
|
|
const target = Number(process.argv[2]);
|
|
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
const rows = Array.isArray(data) ? data : (data?.value || data?.data || []);
|
|
if (!Array.isArray(rows) || !rows.length) { console.log(""); process.exit(0); }
|
|
const match = rows.find(r => Number(r.RoleId) === target);
|
|
if (match) console.log(String(match.Id));
|
|
else {
|
|
console.error("No RoleId=" + target + "; rows: " + rows.map(r => "Id=" + r.Id + "/RoleId=" + r.RoleId).join(", "));
|
|
console.log("");
|
|
}
|
|
' "$out" "$target_role")"
|
|
|
|
[[ -n "$DRIVER_ID" ]] || die "Driver inquiry failed. Inspect $out or set DRIVER_ID in env."
|
|
ok "DriverId=$DRIVER_ID (RoleId=$target_role)"
|
|
persist_env DRIVER_ID
|
|
save_ids
|
|
}
|
|
|
|
resolve_insurance_corp_id() {
|
|
if [[ -n "${INSURANCE_CORP_ID_NUM:-}" ]]; then
|
|
ok "Using INSURANCE_CORP_ID_NUM=$INSURANCE_CORP_ID_NUM from env"
|
|
return 0
|
|
fi
|
|
# Numeric INSURANCE_CORP_ID is already the Fanavaran Id — skip caption lookup.
|
|
if [[ "${INSURANCE_CORP_ID:-}" =~ ^[0-9]+$ ]]; then
|
|
persist_env INSURANCE_CORP_ID_NUM "$INSURANCE_CORP_ID"
|
|
ok "Using numeric INSURANCE_CORP_ID=$INSURANCE_CORP_ID_NUM"
|
|
save_ids
|
|
return 0
|
|
fi
|
|
ask_var INSURANCE_CORP_ID "Persian insurance-corp caption"
|
|
if [[ "${INSURANCE_CORP_ID:-}" =~ ^[0-9]+$ ]]; then
|
|
persist_env INSURANCE_CORP_ID_NUM "$INSURANCE_CORP_ID"
|
|
ok "Using numeric INSURANCE_CORP_ID=$INSURANCE_CORP_ID_NUM"
|
|
save_ids
|
|
return 0
|
|
fi
|
|
INSURANCE_CORP_CAPTION="$INSURANCE_CORP_ID"
|
|
export INSURANCE_CORP_CAPTION
|
|
|
|
local out="$OUT_DIR/insurance-corp.json"
|
|
info "4b) Resolve InsuranceCorpId from caption: $INSURANCE_CORP_CAPTION"
|
|
fv_curl_json GET \
|
|
"$FANAVARAN_BIME_URL/common/code-list/insurance-corp" \
|
|
"$out"
|
|
|
|
INSURANCE_CORP_ID_NUM="$(node -e '
|
|
const fs = require("fs");
|
|
const caption = String(process.argv[2] || "").replace(/\s+/g, " ").toLowerCase().trim();
|
|
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
const rows = Array.isArray(data) ? data : (data?.value || data?.data || []);
|
|
if (!Array.isArray(rows)) { console.log(""); process.exit(0); }
|
|
const match = rows.find((c) => {
|
|
if (Number(c?.IsActive) !== 1) return false;
|
|
if (typeof c?.Id !== "number") return false;
|
|
const n = String(c.Caption || "").replace(/\s+/g, " ").toLowerCase().trim();
|
|
return n.includes(caption) || caption.includes(n);
|
|
});
|
|
if (match) console.log(String(match.Id));
|
|
else {
|
|
console.error("No match. Active captions:");
|
|
rows.filter(c => Number(c?.IsActive) === 1).forEach(c => console.error(" - " + c.Caption + " (" + c.Id + ")"));
|
|
console.log("");
|
|
}
|
|
' "$out" "$INSURANCE_CORP_CAPTION")"
|
|
|
|
[[ -n "$INSURANCE_CORP_ID_NUM" ]] || die "Could not resolve InsuranceCorpId for caption. Set INSURANCE_CORP_ID_NUM or fix INSURANCE_CORP_ID."
|
|
ok "InsuranceCorpId=$INSURANCE_CORP_ID_NUM"
|
|
persist_env INSURANCE_CORP_ID_NUM
|
|
save_ids
|
|
}
|
|
|
|
# ---------- payload builders ----------
|
|
build_base_payload() {
|
|
ask_var CLAIM_EXPERT_ID "GEN.03 ClaimExpertId (مسئول پرونده مالی)"
|
|
ask_var ACCIDENT_DATE "AccidentDate Jalali YYYY/MM/DD"
|
|
ask_var ACCIDENT_TIME "AccidentTime HH:mm"
|
|
require_var POLICY_ID
|
|
|
|
node <<'NODE' >"$OUT_DIR/base-payload.json"
|
|
const fs = require("fs");
|
|
const e = process.env;
|
|
const n = (k) => Number(e[k]);
|
|
const payload = {
|
|
AccidentCityId: n("ACCIDENT_CITY_ID"),
|
|
AccidentReportTypeId: n("ACCIDENT_REPORT_TYPE_ID"),
|
|
AccidentVehicleUsedId: n("ACCIDENT_VEHICLE_USED_ID"),
|
|
ClaimExpertId: n("CLAIM_EXPERT_ID"),
|
|
CompensationReferenceId: n("COMPENSATION_REFERENCE_ID"),
|
|
CulpritLicenceTypeId: n("CULPRIT_LICENCE_TYPE_ID"),
|
|
CulpritTypeId: n("CULPRIT_TYPE_ID"),
|
|
AccidentCauseId: n("ACCIDENT_CAUSE_ID"),
|
|
AccidentDate: e.ACCIDENT_DATE,
|
|
AnnouncementDate: e.ACCIDENT_DATE,
|
|
DocReceivedDate: e.ACCIDENT_DATE,
|
|
AccidentTime: e.ACCIDENT_TIME,
|
|
AccidentLocationAddress: e.ACCIDENT_LOCATION_ADDRESS,
|
|
EstimateAmount: n("ESTIMATE_AMOUNT"),
|
|
ActualPremium: null,
|
|
ArchiveNo: null,
|
|
AuthorityCulpritId: null,
|
|
AccidentCulpritId: null,
|
|
ClaimCompletionDate: null,
|
|
CostSeparationToDmgSections: null,
|
|
CouponNo: null,
|
|
CourtArchiveNo: null,
|
|
CustomerFaultPercent: null,
|
|
CulpritLicenceCityId: null,
|
|
CulpritLicenceCountryId: null,
|
|
CulpritLicenceForeignCityName: null,
|
|
CulpritLicenceIssuDate: e.CULPRIT_LICENCE_ISSU_DATE,
|
|
CulpritLicenceNo: e.CULPRIT_LICENCE_NO,
|
|
DamagedCount: 1,
|
|
DmgAssessorFirstCreationTime: null,
|
|
EntryDate: null,
|
|
GlassBreakReasonId: null,
|
|
IsLicenseMatchWithVehicleKind: 1,
|
|
HasOtherCulprit: 0,
|
|
IsAccidentOutOfBorder: 0,
|
|
IsFatalAccident: 0,
|
|
IsOwnerChanged: null,
|
|
IsPlaqueChanged: 0,
|
|
IsSurplusArticleEighthLaw: null,
|
|
PlaqueCityId: null,
|
|
PlaqueKindId: null,
|
|
PlaqueLeftNo: null,
|
|
PlaqueMiddleCodeId: null,
|
|
PlaqueNo: null,
|
|
PlaqueRightNo: null,
|
|
PlaqueSampleId: null,
|
|
PlaqueSerial: null,
|
|
PoliceOfficerId: 1,
|
|
PoliceReportDesc: null,
|
|
PoliceReportSeri: null,
|
|
PoliceReportSerial: null,
|
|
PolicyId: n("POLICY_ID"),
|
|
PreviousPolicyEndDate: "",
|
|
StatusChangeDate: null,
|
|
TrackingCode: null,
|
|
IsLicenseReplacement: 0,
|
|
UnknownCulpritCauseId: null,
|
|
};
|
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
NODE
|
|
ok "Wrote $OUT_DIR/base-payload.json"
|
|
}
|
|
|
|
build_damage_payload() {
|
|
require_var DRIVER_ID
|
|
require_var INSURANCE_CORP_ID_NUM
|
|
ask_var VEHICLE_KIND_ID "VehicleKindId (from this tenant vehicle-kinds lookup)"
|
|
ask_var LICENCE_NO "Damaged driver LicenceNo"
|
|
LICENCE_ISSU_DATE="${LICENCE_ISSU_DATE:-${DRIVER_BIRTH_YEAR}/${DRIVER_BIRTH_MONTH}/${DRIVER_BIRTH_DAY}}"
|
|
require_var LICENCE_ISSU_DATE
|
|
|
|
# Optional plate / inquiry fields — empty → null
|
|
export PLAQUE_LEFT_NO="${PLAQUE_LEFT_NO:-}"
|
|
export PLAQUE_RIGHT_NO="${PLAQUE_RIGHT_NO:-}"
|
|
export PLAQUE_SERIAL="${PLAQUE_SERIAL:-}"
|
|
export PLAQUE_MIDDLE_CODE_ID="${PLAQUE_MIDDLE_CODE_ID:-}"
|
|
export PLAQUE_NO="${PLAQUE_NO:-}"
|
|
export CHASSIS_NO="${CHASSIS_NO:-}"
|
|
export MOTOR_NO="${MOTOR_NO:-}"
|
|
export VIN="${VIN:-}"
|
|
export POLICY_NO="${POLICY_NO:-}"
|
|
export POLICY_CI_NUMBER="${POLICY_CI_NUMBER:-}"
|
|
export BEGIN_DATE="${BEGIN_DATE:-}"
|
|
export END_DATE="${END_DATE:-}"
|
|
export BUILT_YEAR="${BUILT_YEAR:-}"
|
|
export LICENCE_ISSU_DATE DESC DRIVER_ID INSURANCE_CORP_ID_NUM VEHICLE_KIND_ID LICENCE_NO
|
|
|
|
node <<'NODE' >"$OUT_DIR/damage-payload.json"
|
|
const e = process.env;
|
|
const numOrNull = (v) => {
|
|
if (v === undefined || v === null || String(v).trim() === "") return null;
|
|
const n = Number(v);
|
|
return Number.isFinite(n) ? n : null;
|
|
};
|
|
const strOrNull = (v) => {
|
|
if (v === undefined || v === null || String(v).trim() === "") return null;
|
|
return String(v);
|
|
};
|
|
const hasPlate = Boolean(strOrNull(e.PLAQUE_LEFT_NO) || strOrNull(e.PLAQUE_NO));
|
|
const payload = {
|
|
BeginDate: strOrNull(e.BEGIN_DATE),
|
|
BuiltYear: numOrNull(e.BUILT_YEAR),
|
|
ChassisNo: strOrNull(e.CHASSIS_NO),
|
|
DmgHistoryStatus: Number(e.DMG_HISTORY_STATUS),
|
|
Desc: e.DESC,
|
|
DmgCaseTypeId: Number(e.DMG_CASE_TYPE_ID),
|
|
DriverId: Number(e.DRIVER_ID),
|
|
EndDate: strOrNull(e.END_DATE),
|
|
EstimateAmount: Number(e.ESTIMATE_AMOUNT),
|
|
FaultPercent: Number(e.FAULT_PERCENT),
|
|
InsuranceCorpId: Number(e.INSURANCE_CORP_ID_NUM),
|
|
DriverIsOwner: Number(e.DRIVER_IS_OWNER),
|
|
LicenceCityId: null,
|
|
LicenceCountryId: null,
|
|
LicenceForeignCityName: null,
|
|
LicenceIssuDate: e.LICENCE_ISSU_DATE,
|
|
LicenceNo: e.LICENCE_NO,
|
|
LicenceTypeId: Number(e.CULPRIT_LICENCE_TYPE_ID),
|
|
MotorNo: strOrNull(e.MOTOR_NO),
|
|
OwnerId: null,
|
|
PlaqueCityId: null,
|
|
PlaqueKindId: hasPlate ? Number(e.PLAQUE_KIND_ID) : null,
|
|
PlaqueLeftNo: strOrNull(e.PLAQUE_LEFT_NO),
|
|
PlaqueMiddleCodeId: numOrNull(e.PLAQUE_MIDDLE_CODE_ID),
|
|
PlaqueNo: strOrNull(e.PLAQUE_NO),
|
|
PlaqueRightNo: strOrNull(e.PLAQUE_RIGHT_NO),
|
|
PlaqueSampleId: hasPlate ? Number(e.PLAQUE_SAMPLE_ID) : null,
|
|
PlaqueSerial: strOrNull(e.PLAQUE_SERIAL),
|
|
PolicyNo: strOrNull(e.POLICY_NO),
|
|
PreviousPolicyEndDate: strOrNull(e.END_DATE) || "",
|
|
VehicleKindId: Number(e.VEHICLE_KIND_ID),
|
|
VIN: strOrNull(e.VIN),
|
|
AccidentVehicleUsedId: Number(e.ACCIDENT_VEHICLE_USED_ID),
|
|
PolicyCINumber: strOrNull(e.POLICY_CI_NUMBER),
|
|
};
|
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
NODE
|
|
ok "Wrote $OUT_DIR/damage-payload.json"
|
|
}
|
|
|
|
build_expertise_payload() {
|
|
ask_var EXPERTISE_CLAIM_EXPERT_ID "GEN.08 ExpertiseClaimExpertId (ارزیاب)"
|
|
ask_var DMG_SECTION_ID "DmgSectionId (car-components Id)"
|
|
ask_var DMG_ASSESSMENT_DATE "DmgAssessmentDate Jalali YYYY/MM/DD"
|
|
ask_var INSPECTION_TIME "InspectionTime HH:mm"
|
|
|
|
export DAMAGED_VEHICLE_CURRENT_PRICE="${DAMAGED_VEHICLE_CURRENT_PRICE:-}"
|
|
|
|
node <<'NODE' >"$OUT_DIR/expertise-payload.json"
|
|
const e = process.env;
|
|
const numOrNull = (v) => {
|
|
if (v === undefined || v === null || String(v).trim() === "") return null;
|
|
const n = Number(v);
|
|
return Number.isFinite(n) ? n : null;
|
|
};
|
|
const payload = {
|
|
ClaimExpertId: Number(e.EXPERTISE_CLAIM_EXPERT_ID),
|
|
ComponentReplacementCost: Number(e.COMPONENT_REPLACEMENT_COST),
|
|
DmgAssessmentDate: e.DMG_ASSESSMENT_DATE,
|
|
DmgCaseId: Number(e.DMG_CASE_ID),
|
|
InspectionPlaceId: Number(e.INSPECTION_PLACE_ID),
|
|
InspectionTime: e.INSPECTION_TIME,
|
|
RepairWage: Number(e.REPAIR_WAGE),
|
|
WasteValue: Number(e.WASTE_VALUE),
|
|
WentDistanceByExpert: null,
|
|
DropAmountStatus: Number(e.DROP_AMOUNT_STATUS),
|
|
DropAmountAdditionsDeductions: Number(e.DROP_AMOUNT_ADDITIONS || 0),
|
|
ComponentReplacementTaxAndToll: Number(e.COMPONENT_REPLACEMENT_TAX || 0),
|
|
DamagedVehicleCurrentPrice: numOrNull(e.DAMAGED_VEHICLE_CURRENT_PRICE),
|
|
DmgSections: [{
|
|
Desc: e.DESC || "manual test",
|
|
DmgSectionId: Number(e.DMG_SECTION_ID),
|
|
AccidentLevel: Number(e.ACCIDENT_LEVEL),
|
|
ComponentReplacementCost: Number(e.COMPONENT_REPLACEMENT_COST),
|
|
RepairWage: Number(e.REPAIR_WAGE),
|
|
WasteValue: Number(e.WASTE_VALUE),
|
|
}],
|
|
};
|
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
NODE
|
|
ok "Wrote $OUT_DIR/expertise-payload.json"
|
|
}
|
|
|
|
# ---------- stages ----------
|
|
run_base() {
|
|
if [[ -n "${CLAIM_ID:-}" ]]; then
|
|
ok "CLAIM_ID=$CLAIM_ID already set — skip GEN.03 (same as app soft-skip)"
|
|
return 0
|
|
fi
|
|
inquire_policy_id
|
|
build_base_payload
|
|
if confirm_submit "GEN.03 base claim"; then
|
|
info "3) POST GEN.03 base claim"
|
|
fv_curl_json POST \
|
|
"$FANAVARAN_BIME_URL/car/third-party-car-financial-claims" \
|
|
"$OUT_DIR/base-submit.json" \
|
|
"$OUT_DIR/base-payload.json"
|
|
CLAIM_ID="$(json_get "$OUT_DIR/base-submit.json" 'data.Id ?? data.id ?? ""')"
|
|
CLAIM_NO="$(json_get "$OUT_DIR/base-submit.json" 'data.ClaimNo ?? data.claimNo ?? ""')"
|
|
[[ -n "$CLAIM_ID" ]] || die "GEN.03 response missing Id — see $OUT_DIR/base-submit.json"
|
|
ok "claimId=$CLAIM_ID claimNo=${CLAIM_NO:-?}"
|
|
persist_env CLAIM_ID
|
|
persist_env CLAIM_NO
|
|
save_ids
|
|
elif [[ "$PREVIEW_ONLY" -eq 1 ]]; then
|
|
ok "Preview-only: base payload ready (no CLAIM_ID)"
|
|
else
|
|
die "Base claim not submitted and CLAIM_ID unset — cannot continue."
|
|
fi
|
|
}
|
|
|
|
run_damage() {
|
|
require_var CLAIM_ID "from GEN.03 or set in env to resume"
|
|
if [[ -n "${DMG_CASE_ID:-}" ]]; then
|
|
ok "DMG_CASE_ID=$DMG_CASE_ID already set — skip GEN.12"
|
|
return 0
|
|
fi
|
|
inquire_driver_id
|
|
resolve_insurance_corp_id
|
|
build_damage_payload
|
|
if confirm_submit "GEN.12 damage case"; then
|
|
info "5) POST GEN.12 dmg-cases"
|
|
fv_curl_json POST \
|
|
"$FANAVARAN_BIME_URL/car/third-party-car-financial-claims/${CLAIM_ID}/dmg-cases" \
|
|
"$OUT_DIR/damage-submit.json" \
|
|
"$OUT_DIR/damage-payload.json"
|
|
DMG_CASE_ID="$(json_get "$OUT_DIR/damage-submit.json" 'data.Id ?? data.id ?? ""')"
|
|
[[ -n "$DMG_CASE_ID" ]] || die "GEN.12 response missing Id — see $OUT_DIR/damage-submit.json"
|
|
ok "dmgCaseId=$DMG_CASE_ID"
|
|
persist_env DMG_CASE_ID
|
|
save_ids
|
|
elif [[ "$PREVIEW_ONLY" -eq 1 ]]; then
|
|
ok "Preview-only: damage payload ready (no DMG_CASE_ID)"
|
|
else
|
|
die "Damage case not submitted and DMG_CASE_ID unset — cannot continue."
|
|
fi
|
|
}
|
|
|
|
attachment_file_list() {
|
|
# ATTACHMENT_FILES=a.jpg,b.jpg or single ATTACHMENT_FILE=
|
|
local raw="${ATTACHMENT_FILES:-${ATTACHMENT_FILE:-}}"
|
|
[[ -n "$raw" ]] || return 1
|
|
local f
|
|
IFS=',' read -r -a files <<< "$raw"
|
|
for f in "${files[@]}"; do
|
|
f="$(echo "$f" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
[[ -n "$f" ]] && printf '%s\n' "$f"
|
|
done
|
|
}
|
|
|
|
run_attachments() {
|
|
require_var CLAIM_ID
|
|
ask_var CLAIM_FILE_TYPE_ID "ClaimFileTypeId (must exist in this tenant file-types lookup)"
|
|
if [[ -z "${ATTACHMENT_FILE:-}" && -z "${ATTACHMENT_FILES:-}" ]]; then
|
|
ask_var ATTACHMENT_FILE "Path to an image file to upload"
|
|
fi
|
|
|
|
local files=()
|
|
while IFS= read -r line; do
|
|
files+=("$line")
|
|
done < <(attachment_file_list || true)
|
|
|
|
if [[ "${#files[@]}" -eq 0 ]]; then
|
|
die "Set ATTACHMENT_FILE or ATTACHMENT_FILES (comma-separated paths) in env"
|
|
fi
|
|
|
|
local idx=0
|
|
local path fname meta out headers
|
|
for path in "${files[@]}"; do
|
|
idx=$((idx + 1))
|
|
[[ -f "$path" ]] || die "Attachment not found: $path"
|
|
fname="$(basename "$path")"
|
|
meta="$OUT_DIR/attachment-${idx}-content.json"
|
|
out="$OUT_DIR/attachment-${idx}-submit.json"
|
|
headers="${out%.json}.headers"
|
|
|
|
node -e '
|
|
const fs = require("fs");
|
|
fs.writeFileSync(process.argv[3], JSON.stringify({
|
|
FileName: process.argv[1],
|
|
FileTypeId: Number(process.argv[2]),
|
|
Files: [{ FileName: process.argv[1], FileTypeId: Number(process.argv[2]) }],
|
|
}) + "\n");
|
|
' "$fname" "$CLAIM_FILE_TYPE_ID" "$meta"
|
|
|
|
if confirm_submit "GEN.07 attachment #$idx ($fname)"; then
|
|
info "6) POST GEN.07 files — $fname (FileTypeId=$CLAIM_FILE_TYPE_ID)"
|
|
# Let curl set multipart boundary; do not set Content-Type manually.
|
|
# shellcheck disable=SC2046
|
|
curl -sS -D "$headers" -o "$out" \
|
|
$(curl_proxy_args) \
|
|
-X POST "$FANAVARAN_BIME_URL/car/third-party-car-financial-claims/${CLAIM_ID}/files" \
|
|
-H "authenticationToken: $AUTHENTICATION_TOKEN" \
|
|
-H "CorpId: $CORP_ID" \
|
|
-H "ContractId: $CONTRACT_ID" \
|
|
-H "Location: $LOCATION" \
|
|
-F "content=$(cat "$meta");type=application/json" \
|
|
-F "files=@${path}"
|
|
pretty_json_file "$out" || true
|
|
local code
|
|
code="$(awk 'NR==1 {print $2}' "$headers")"
|
|
printf 'HTTP %s → %s\n' "$code" "$out"
|
|
fi
|
|
done
|
|
}
|
|
|
|
run_expertise() {
|
|
require_var CLAIM_ID
|
|
require_var DMG_CASE_ID
|
|
build_expertise_payload
|
|
if confirm_submit "GEN.08 expertise"; then
|
|
info "7) POST GEN.08 expertise"
|
|
fv_curl_json POST \
|
|
"$FANAVARAN_BIME_URL/car/third-party-car-financial-claims/${CLAIM_ID}/expertise" \
|
|
"$OUT_DIR/expertise-submit.json" \
|
|
"$OUT_DIR/expertise-payload.json"
|
|
EXPERTISE_ID="$(json_get "$OUT_DIR/expertise-submit.json" 'data.Id ?? data.id ?? ""')"
|
|
[[ -n "$EXPERTISE_ID" ]] || warn "Could not parse Expertise Id — see $OUT_DIR/expertise-submit.json"
|
|
ok "expertiseId=${EXPERTISE_ID:-?}"
|
|
persist_env EXPERTISE_ID
|
|
save_ids
|
|
fi
|
|
}
|
|
|
|
# ---------- argv ----------
|
|
need_cmd curl
|
|
need_cmd node
|
|
need_cmd awk
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--env) ENV_FILE="${2:-}"; shift 2 ;;
|
|
--stages) STAGES="${2:-}"; shift 2 ;;
|
|
--out) OUT_DIR="${2:-}"; shift 2 ;;
|
|
--yes) ASSUME_YES=1; shift ;;
|
|
--preview-only) PREVIEW_ONLY=1; shift ;;
|
|
--force-login) FORCE_LOGIN=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
yara|fanavaran)
|
|
die "YARA mode removed. This script is Fanavaran-only. Use: --env <file>"
|
|
;;
|
|
*) die "Unknown option: $1 (see --help)" ;;
|
|
esac
|
|
done
|
|
|
|
[[ -n "$ENV_FILE" ]] || die "--env <file> is required"
|
|
[[ -f "$ENV_FILE" ]] || die "Env file not found: $ENV_FILE"
|
|
ENV_FILE="$(cd "$(dirname "$ENV_FILE")" && pwd)/$(basename "$ENV_FILE")"
|
|
load_env_file "$ENV_FILE"
|
|
|
|
FANAVARAN_CLIENT="${FANAVARAN_CLIENT:-${CLIENT:-}}"
|
|
FANAVARAN_CLIENT="${FANAVARAN_CLIENT,,}"
|
|
ask_var FANAVARAN_CLIENT "Fanavaran client (parsian|tejaratno|moallem)"
|
|
FANAVARAN_CLIENT="${FANAVARAN_CLIENT,,}"
|
|
|
|
apply_client_auth_defaults
|
|
apply_shared_defaults
|
|
|
|
STAGES="$(echo "$STAGES" | tr '[:upper:]' '[:lower:]' | tr -d ' ')"
|
|
TS="$(date +%Y%m%d-%H%M%S)"
|
|
OUT_DIR="${OUT_DIR:-$ROOT_DIR/files/fanavaran-flow/$FANAVARAN_CLIENT/$TS}"
|
|
mkdir -p "$OUT_DIR"
|
|
cp "$ENV_FILE" "$OUT_DIR/input.env" 2>/dev/null || true
|
|
|
|
info "Fanavaran-only flow | client=$FANAVARAN_CLIENT | stages=$STAGES"
|
|
info "Env file (section B will be updated in-place): $ENV_FILE"
|
|
info "Output: $OUT_DIR"
|
|
|
|
fanavaran_login
|
|
|
|
# Soft-ensure order like the app: later stages create earlier ones if ids missing
|
|
if stage_wanted base; then
|
|
run_base
|
|
elif stage_wanted damage || stage_wanted attachments || stage_wanted expertise; then
|
|
if [[ -z "${CLAIM_ID:-}" ]]; then
|
|
if [[ "$PREVIEW_ONLY" -eq 1 ]]; then
|
|
warn "CLAIM_ID missing — preview-only cannot soft-ensure GEN.03 submit; set CLAIM_ID to preview later stages"
|
|
else
|
|
warn "CLAIM_ID missing — running GEN.03 first (soft-ensure)"
|
|
run_base
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if stage_wanted damage; then
|
|
if [[ -n "${CLAIM_ID:-}" || "$PREVIEW_ONLY" -eq 0 ]]; then
|
|
run_damage
|
|
fi
|
|
elif stage_wanted expertise; then
|
|
if [[ -z "${DMG_CASE_ID:-}" ]]; then
|
|
if [[ "$PREVIEW_ONLY" -eq 1 ]]; then
|
|
warn "DMG_CASE_ID missing — set it to preview expertise"
|
|
else
|
|
warn "DMG_CASE_ID missing — running GEN.12 first (soft-ensure)"
|
|
run_damage
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if stage_wanted attachments; then
|
|
if [[ -n "${CLAIM_ID:-}" ]]; then
|
|
run_attachments
|
|
else
|
|
warn "Skip attachments: CLAIM_ID required"
|
|
fi
|
|
fi
|
|
|
|
if stage_wanted expertise; then
|
|
if [[ -n "${CLAIM_ID:-}" && -n "${DMG_CASE_ID:-}" ]]; then
|
|
run_expertise
|
|
elif [[ "$PREVIEW_ONLY" -eq 1 && -n "${DMG_CASE_ID:-}" ]]; then
|
|
run_expertise
|
|
else
|
|
warn "Skip expertise: need CLAIM_ID and DMG_CASE_ID (or run full flow)"
|
|
fi
|
|
fi
|
|
|
|
save_ids
|
|
info "Done. Section B of $ENV_FILE now holds Fanavaran ids for resume."
|
|
if [[ -f "$OUT_DIR/ids.env" ]]; then
|
|
printf 'Ids:\n'
|
|
cat "$OUT_DIR/ids.env"
|
|
fi
|