#!/usr/bin/env bash # ===================================================================== # seed-orders-returns.sh — demo sales orders in mixed states, shipments, invoices, voided orders # and RETURNS (RMA + customer return receipt) for one merchant org, with demo photos attached to # orders and returns. Everything goes through the app's routes; attachments use the app's Strapi # attachment model (POST /api/attachments// → ad-attachment, then Strapi upload with # ref/refId/field=attachment). # # ADMIN_USER=demo ADMIN_PASS=… MERCHANT_SHORT=kazaar CONSIGNEES=/root/demo-setup/kazaar/consignees.json ./seed-orders-returns.sh # # Status mix per order number n — MIX=default (n % 10): 0 draft · 1 prepared (IP) · 2 completed then VOIDED · # 3,4 completed, not yet shipped (picking) · 5…9 completed + shipped · 5,6 also invoiced. # MIX=shipped ("mostly shipped, some in progress, very few voided"): n%25==7 voided (4 %) · n%10==0 draft · # n%10==1 prepared · n%10==2 completed/picking · n%20==13 shipment generated + printed, no tracking yet # ("In Bearbeitung") · everything else shipped WITH a dummy DHL tracking number, parcel type, shipping date # and IsCommissioned ("Verschickt"); n%10 in 5,6 also invoiced. # Lines 1–5 per order, qty 1–3, order dates spread over the last 30 days, consignees cycle through # CONSIGNEES (anonymised list from build-consignees.py: name/street/postal/city/countryId/email). # # Env (defaults): BASE [http://127.0.0.1:3001], ORDERS [100], RETURNS [8], ATTACH_EVERY [10 = photo on # every 10th order], PREFIX [-ORD], MIX [default|shipped], PAKET_TYPES [1000204,1000205,1000206,1000213 # = Paket S/M/L/XL products rotated per shipment], TRACKING_PREFIX [0034043416 → 20-digit DHL-style numbers], # REASONS_FILE [lines "reason|rmaTypeId"; default = the fragrance list below], CLIENT_ID/ROLE_ID/ORG_ID/WH_ID (sign-in context), # PRICE_LIST_ID [1000003], PLV_GROSS [1000003], DOCTYPE_SO [1000032], DOCTYPE_RMA [1000031 Customer Return # Material], DOCTYPE_CR [1000015 MM Customer Return], RMA_TYPE_RETURN [1000001 Rückgabe], RMA_TYPE_BOUNCE # [1000000 Rückläufer], LOCATOR_TYPE_RET [1000004 Retouren], SHIPPER_ID [1000000], CURRENCY_ID [102], # UOM_ID [100], PAYMENT_TERM_ID [1000000], TAX_ID [1000000], BP_GROUP_STD [1000000], # IMG_DIR [/root/demo-setup/images/] product image cache, CATALOG [/root/demo-setup//catalog.json] # (for product images), PHOTO_DIR [/root/demo-setup/photos/], STRAPI_UPLOAD [http://127.0.0.1:1337/api/upload], # STRAPI_TOKEN [STRAPITOKEN from .env]. # Idempotent: orders by POReference, consignees by name, RMAs by name, uploads by file name. # ===================================================================== set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" BASE="${BASE:-http://127.0.0.1:3001}"; ADMIN_USER="${ADMIN_USER:?}"; ADMIN_PASS="${ADMIN_PASS:?}"; MERCHANT_SHORT="${MERCHANT_SHORT:?}" CONSIGNEES="${CONSIGNEES:?path to consignees.json}" ORDERS="${ORDERS:-100}"; RETURNS="${RETURNS:-8}"; ATTACH_EVERY="${ATTACH_EVERY:-10}"; PREFIX="${PREFIX:-$(echo "$MERCHANT_SHORT" | tr a-z A-Z)-ORD}" MIX="${MIX:-default}"; PAKET_TYPES="${PAKET_TYPES:-1000204,1000205,1000206,1000213}"; TRACKING_PREFIX="${TRACKING_PREFIX:-0034043416}"; REASONS_FILE="${REASONS_FILE:-}" CLIENT_ID="${CLIENT_ID:-1000000}"; ROLE_ID="${ROLE_ID:-1000000}"; ORG_ID="${ORG_ID:-1000000}"; WH_ID="${WH_ID:-1000000}" PRICE_LIST_ID="${PRICE_LIST_ID:-1000003}"; PLV_GROSS="${PLV_GROSS:-1000003}"; DOCTYPE_SO="${DOCTYPE_SO:-1000032}" DOCTYPE_RMA="${DOCTYPE_RMA:-1000031}"; DOCTYPE_CR="${DOCTYPE_CR:-1000015}"; RMA_TYPE_RETURN="${RMA_TYPE_RETURN:-1000001}"; RMA_TYPE_BOUNCE="${RMA_TYPE_BOUNCE:-1000000}" LOCATOR_TYPE_RET="${LOCATOR_TYPE_RET:-1000004}"; SHIPPER_ID="${SHIPPER_ID:-1000000}"; CURRENCY_ID="${CURRENCY_ID:-102}" UOM_ID="${UOM_ID:-100}"; PAYMENT_TERM_ID="${PAYMENT_TERM_ID:-1000000}"; TAX_ID="${TAX_ID:-1000000}"; BP_GROUP_STD="${BP_GROUP_STD:-1000000}" IMG_DIR="${IMG_DIR:-/root/demo-setup/images/$MERCHANT_SHORT}"; CATALOG="${CATALOG:-/root/demo-setup/$MERCHANT_SHORT/catalog.json}"; PHOTO_DIR="${PHOTO_DIR:-/root/demo-setup/photos/$MERCHANT_SHORT}" STRAPI_UPLOAD="${STRAPI_UPLOAD:-http://127.0.0.1:1337/api/upload}" STRAPI_TOKEN="${STRAPI_TOKEN:-$(grep -E '^STRAPITOKEN=' /opt/erp-nuxt-frontend/.env | cut -d= -f2- | tr -d "'\"")}" TODAY="$(date +%Y-%m-%d)" JAR="$(mktemp)"; trap 'rm -f "$JAR"' EXIT; mkdir -p "$PHOTO_DIR" say() { printf '\n== %s\n' "$*"; } api() { if [ $# -ge 3 ]; then curl -sS -b "$JAR" -c "$JAR" -X "$1" "$BASE$2" -H 'Content-Type: application/json' -d "$3"; else curl -sS -b "$JAR" -c "$JAR" -X "$1" "$BASE$2"; fi; } odata() { local enc; enc="$(jq -rn --arg s "\$filter=$2&\$top=${3:-50}" '$s|@uri')"; api GET "/api/filters/$1/$enc"; } first_id() { jq -r 'if type=="array" then .[0].id elif has("records") then .records[0].id else empty end // empty' 2>/dev/null; } ok() { local st; st="$(echo "$1" | jq -r '.status // 200' 2>/dev/null || echo 500)"; [ "$st" = 200 ] || { echo " FAILED: $(echo "$1" | cut -c1-300)" >&2; return 1; }; } esc() { printf "%s" "$1" | sed "s/'/''/g"; } dayago() { date -d "-$1 days" +%Y-%m-%d 2>/dev/null || date -v-"$1"d +%Y-%m-%d; } dayplus() { date -d "+$1 days" +%Y-%m-%d 2>/dev/null || date -v+"$1"d +%Y-%m-%d; } # attach
— creates/gets the ad-attachment, uploads the file once attach() { local table="$1" rid="$2" uid="$3" file="$4" fname; fname="$(basename "$file")" local a; a="$(api POST "/api/attachments/$table/$rid" "$(jq -n --arg u "$uid" '{uid:$u}')")" local sid; sid="$(echo "$a" | jq -r '.Strapi_Attachment_ID // empty')" [ -n "$sid" ] || { echo " attachment record failed for $table/$rid: $(echo "$a" | cut -c1-160)"; return 0; } if curl -sSg -H "Authorization: Bearer $STRAPI_TOKEN" "$STRAPI_UPLOAD/files?filters[name][\$eq]=$fname&pagination[limit]=1" | jq -e 'type=="array" and length > 0' >/dev/null 2>&1; then return 0; fi local up; up="$(curl -sS -X POST "$STRAPI_UPLOAD" -H "Authorization: Bearer $STRAPI_TOKEN" -F "files=@$file;type=image/jpeg" -F "ref=api::ad-attachment.ad-attachment" -F "refId=$sid" -F "field=attachment")" echo "$up" | jq -e '.[0].id' >/dev/null 2>&1 && echo " attached $fname -> $table $rid" || echo " upload failed for $fname: $(echo "$up" | cut -c1-160)" } photo() { python3 "$HERE/make-demo-photo.py" "$@" >/dev/null; } img_for() { local f; f="$(jq -r --arg v "$1" '.[] | select(.value==$v) | .imageUrl // empty' "$CATALOG" 2>/dev/null | head -1)"; [ -n "$f" ] && f="$IMG_DIR/$(printf '%s' "$f" | md5sum | cut -c1-16).img"; [ -s "${f:-/nonexistent}" ] && echo "$f" || true; } # --- 0. sign in ------------------------------------------------------------------------------------------- say "sign in as $ADMIN_USER" login="$(api POST /api/idempiere-auth/login "$(jq -n --arg u "$ADMIN_USER" --arg p "$ADMIN_PASS" '{userName:$u,password:$p,selectRole:true}')")" echo "$login" | jq -e '.token' >/dev/null || { echo "login failed" >&2; exit 1; } api POST /api/idempiere-auth/select-role "$(jq -n --argjson c "$CLIENT_ID" --argjson r "$ROLE_ID" --argjson o "$ORG_ID" --argjson w "$WH_ID" '{clientId:$c,roleId:$r,organizationId:$o,warehouseId:$w,language:"de_DE"}')" >/dev/null ME="$(api GET /api/idempiere-auth/authenticated | jq -r '.user.id // empty')"; [ -n "$ME" ] || { echo "session not authenticated" >&2; exit 1; } # --- 1. merchant context ----------------------------------------------------------------------------------- say "merchant org $MERCHANT_SHORT" MORG="$(odata ad_org "Value eq '$MERCHANT_SHORT'" | first_id)"; [ -n "$MORG" ] || { echo "org $MERCHANT_SHORT not found" >&2; exit 1; } MWH="$(odata m_warehouse "AD_Org_ID eq $MORG" | first_id)" RETLOC="$(odata m_locator "M_Warehouse_ID eq $MWH and Value eq 'RET-01-01'" | first_id)" if [ -z "$RETLOC" ]; then res="$(api POST /api/materials/locators/store "$(jq -n --argjson o "$MORG" --argjson w "$MWH" --argjson t "$LOCATOR_TYPE_RET" '{organizationId:$o, warehouseId:$w, value:"RET-01-01", priorityNo:90, isActive:true, isDefault:false, x:"RET", y:"01", z:"01", locatorTypeId:$t}')")"; ok "$res"; RETLOC="$(echo "$res" | jq -r .id)" fi api GET "/api/filters/m_product/$(jq -rn --arg s "\$filter=AD_Org_ID eq $MORG and IsActive eq true&\$top=2000" '$s|@uri')" | jq -r '.records[] | "\(.id)|\(.Value)|\(.Name | gsub("\\|"; "/"))"' > "$PHOTO_DIR/products.txt" # names may contain "|" (VONROC) api GET "/api/filters/m_productprice/$(jq -rn --arg s "\$filter=M_PriceList_Version_ID eq $PLV_GROSS and AD_Org_ID eq $MORG&\$top=3000" '$s|@uri')" | jq -r '.records[] | "\(.M_Product_ID.id)|\(.PriceStd)"' > "$PHOTO_DIR/prices.txt" join -t'|' <(sort -t'|' -k1,1 "$PHOTO_DIR/products.txt") <(sort -t'|' -k1,1 "$PHOTO_DIR/prices.txt") | awk -F'|' '$4+0 > 0' > "$PHOTO_DIR/articles.txt" NART="$(wc -l < "$PHOTO_DIR/articles.txt")"; echo " org $MORG warehouse $MWH return bin $RETLOC, $NART priced articles" [ "$NART" -gt 0 ] || { echo "no priced articles" >&2; exit 1; } pick() { sed -n "$(( ($1 * 7919 + $2 * 104729) % NART + 1 ))p" "$PHOTO_DIR/articles.txt"; } # --- 2. consignees ------------------------------------------------------------------------------------------ NC="$(jq length "$CONSIGNEES")"; say "$NC consignees from $CONSIGNEES" CUST_BP=(); CUST_LOC=(); created=0 for i in $(seq 0 $((NC-1))); do c="$(jq -c ".[$i]" "$CONSIGNEES")"; name="$(echo "$c" | jq -r .name)" bp="$(odata c_bpartner "AD_Org_ID eq $MORG and Name eq '$(esc "$name")'" | first_id)" if [ -z "$bp" ]; then loc="$(api POST /api/settings/locations/store "$(echo "$c" | jq -c --argjson o "$MORG" '{organizationId:$o, address1:.street, postal:.postal, city:.city, countryId:.countryId, isActive:true, isValid:true}')")"; ok "$loc"; locid="$(echo "$loc" | jq -r .id)" res="$(api POST /api/partners/partners/store "$(echo "$c" | jq -c --argjson o "$MORG" --argjson g "$BP_GROUP_STD" --argjson pl "$PRICE_LIST_ID" '{organizationId:$o, name:.name, EMail:.email, isActive:true, isCustomer:true, isVendor:false, partnerGroupId:$g, priceListId:$pl, languageId:"de_DE"}')")"; ok "$res"; bp="$(echo "$res" | jq -r .id)" bl="$(api POST "/api/partners/partners/$bp/locations" "$(jq -n --argjson o "$MORG" --argjson bp "$bp" --argjson l "$locid" --arg n "$name" '{organizationId:$o, partnerId:$bp, locationId:$l, name:$n, isShipTo:true, isBillTo:true, isPayFrom:true, isRemitTo:true, isActive:true}')")"; ok "$bl"; created=$((created+1)) fi CUST_BP+=("$bp"); CUST_LOC+=("$(odata c_bpartner_location "C_BPartner_ID eq $bp" | first_id)") done echo " $created created, ${#CUST_BP[@]} available" # --- 3. orders ------------------------------------------------------------------------------------------------ say "$ORDERS orders" SHIP_CUST=(); SHIP_ORDERS=(); INV_ORDERS=(); counts="" for n in $(seq 1 "$ORDERS"); do ref="$(printf '%s-%04d' "$PREFIX" $((1000 + n)))"; ci=$(( (n * 37) % ${#CUST_BP[@]} )); bp="${CUST_BP[$ci]}"; bpl="${CUST_LOC[$ci]}" if [ "$MIX" = shipped ]; then if [ $(( n % 25 )) = 7 ]; then action=VO; else case $(( n % 10 )) in 0) action=DR;; 1) action=PR;; 2) action=CO;; *) action=SHIP;; esac; fi else case $(( n % 10 )) in 0) action=DR;; 1) action=PR;; 2) action=VO;; 3|4) action=CO;; *) action=SHIP;; esac fi d="$(dayago $(( (n * 13) % 30 )))"; dp="$(dayplus $(( n % 4 )))" oid="$(odata c_order "AD_Org_ID eq $MORG and POReference eq '$ref'" | first_id)" if [ -z "$oid" ]; then nl=$(( (n * 7) % 5 + 1 )) ol="$(for k in $(seq 1 $nl); do IFS='|' read -r pid val pname price <<<"$(pick "$n" "$k")"; q=$(( (n + k) % 3 + 1 )); jq -n --argjson p "$pid" --argjson q "$q" --argjson pr "$price" --argjson ln "$((k*10))" \ --arg d "$d" --arg dp "$dp" --argjson u "$UOM_ID" --argjson w "$MWH" --argjson cur "$CURRENCY_ID" --argjson bp "$bp" --argjson bpl "$bpl" --argjson tax "$TAX_ID" \ '{line:$ln, isActive:true, dateOrdered:$d, datePromised:$dp, qtyOrdered:$q, qtyEntered:$q, qtyReserved:0, qtyDelivered:0, qtyInvoiced:0, qtyLostSales:0, priceActual:$pr, priceEntered:$pr, priceList:$pr, priceLimit:$pr, discount:0, lineNetAmt:($pr*$q), M_Product_ID:{id:$p}, C_UOM_ID:{id:$u}, C_Tax_ID:{id:$tax}, M_Warehouse_ID:{id:$w}, C_Currency_ID:{id:$cur}, C_BPartner_ID:{id:$bp}, C_BPartner_Location_ID:{id:$bpl}}'; done | jq -s .)" res="$(api POST /api/orders/store "$(jq -n --argjson o "$MORG" --argjson dt "$DOCTYPE_SO" --arg d "$d" --arg dp "$dp" --argjson bp "$bp" --argjson bpl "$bpl" \ --argjson w "$MWH" --argjson pl "$PRICE_LIST_ID" --argjson cur "$CURRENCY_ID" --argjson sh "$SHIPPER_ID" --arg ref "$ref" --argjson ol "$ol" --argjson pt "$PAYMENT_TERM_ID" \ '{organizationId:$o, isActive:true, isSOTrx:true, docTypeTargetId:$dt, docStatusId:"DR", dateOrdered:$d, datePromised:$dp, dateAcct:$d, partnerId:$bp, partnerLocationId:$bpl, billPartnerId:$bp, billPartnerLocationId:$bpl, warehouseId:$w, priceListId:$pl, currencyId:$cur, paymentTermId:$pt, deliveryRuleId:"A", deliveryViaRuleId:"D", invoiceRuleId:"D", priorityRuleId:"5", paymentRuleId:"P", shipperId:$sh, poReference:$ref, description:"Demo-Bestellung Onlineshop", orderLines:$ol}')")"; ok "$res"; oid="$(echo "$res" | jq -r .id)"; printf ' %3d/%d %s (%s) %d lines -> %s\n' "$n" "$ORDERS" "$ref" "$oid" "$nl" "$action" fi rec="$(odata c_order "C_Order_ID eq $oid")"; st="$(echo "$rec" | jq -r '.records[0].DocStatus.id // "DR"')"; uid="$(echo "$rec" | jq -r '.records[0].uid')" case "$action" in PR) [ "$st" = DR ] && { da="$(api POST "/api/orders/$oid/document-action" '{"documentAction":"PR"}')"; ok "$da"; } ;; CO|SHIP) [ "$st" = DR ] && { da="$(api POST "/api/orders/$oid/document-action" '{"documentAction":"CO"}')"; ok "$da"; } ;; VO) [ "$st" = DR ] && { da="$(api POST "/api/orders/$oid/document-action" '{"documentAction":"CO"}')"; ok "$da"; st=CO; } [ "$st" = CO ] && { da="$(api POST "/api/orders/$oid/document-action" '{"documentAction":"VO"}')"; ok "$da"; } ;; esac [ "$action" = SHIP ] && { SHIP_CUST+=("$ci"); SHIP_ORDERS+=("$n|$oid|$d"); }; [ $(( n % 10 )) = 5 ] || [ $(( n % 10 )) = 6 ] && INV_ORDERS+=("$oid") if [ $(( n % ATTACH_EVERY )) = 1 ]; then IFS='|' read -r pid val pname price <<<"$(pick "$n" 1)"; f="$PHOTO_DIR/order-$ref-kundenfoto.jpg"; im="$(img_for "$val")" [ -s "$f" ] || photo "$f" ${im:+--image "$im"} "Kundenanfrage zu Bestellung $ref" "Artikel: $pname" "Foto vom Kunden per E-Mail erhalten" attach C_Order "$oid" "$uid" "$f" fi done # --- 4. shipments (one generate per customer with open completed orders) + invoices -------------------------------------- say "shipments" for ci in $(printf '%s\n' "${SHIP_CUST[@]}" | sort -un); do bp="${CUST_BP[$ci]}" [ -n "$(odata c_order "AD_Org_ID eq $MORG and C_BPartner_ID eq $bp and DocStatus eq 'CO' and IsDelivered eq false" | first_id)" ] || continue res="$(api POST /api/processes/inouts/generate "$(jq -n --argjson o "$MORG" --argjson w "$MWH" --argjson bp "$bp" '{organizationId:$o, warehouseId:$w, docStatusId:"CO", partnerId:$bp}')")"; ok "$res" || true echo " customer $bp: $(echo "$res" | jq -r '.summary // .logs[0].log // "ok"' | cut -c1-60)" done if [ "$MIX" = shipped ]; then say "tracking numbers / printed shipments" IFS=',' read -r -a PT <<<"$PAKET_TYPES"; shipped=0; printed=0 for rec in "${SHIP_ORDERS[@]}"; do IFS='|' read -r n oid d <<<"$rec" io="$(odata m_inout "C_Order_ID eq $oid and MovementType eq 'C-' and DocStatus eq 'CO'" | jq -r '.records[0]')"; ioid="$(echo "$io" | jq -r '.id // empty')"; [ -n "$ioid" ] || continue if [ $(( n % 20 )) = 13 ]; then # picked + label printed, carrier not yet scanned → "In Bearbeitung" # iDempiere refuses IsPrinted on a processed record via REST ("Cannot update IsPrinted on processed record") → SQL [ "$(echo "$io" | jq -r '.IsPrinted // false')" = true ] || sudo -u postgres psql -q -d idempiere -c "set search_path to adempiere, public" -c "update adempiere.m_inout set isprinted='Y', updated=now() where m_inout_id=$ioid" 2>/dev/null || echo " isPrinted SQL failed for $ioid" # search_path: ES trigger printed=$((printed+1)); continue fi [ -n "$(echo "$io" | jq -r '.TrackingNo // empty')" ] && { shipped=$((shipped+1)); continue; } trk="$(printf '%s%010d' "$TRACKING_PREFIX" $(( (n * 7919 + oid) % 10000000000 )))" pt="${PT[$(( n % ${#PT[@]} ))]}"; sd="$(date -d "$d +1 day" +%Y-%m-%dT10:00:00Z 2>/dev/null || date -j -v+1d -f %Y-%m-%d "$d" +%Y-%m-%dT10:00:00Z)" # REST wants yyyy-MM-dd'T'HH:mm:ss'Z' res="$(api PUT /api/inouts/tracking "$(jq -n --argjson id "$ioid" --arg t "$trk" --arg sd "$sd" --argjson pt "$pt" '{id:$id, trackingNo:$t, isCommissioned:true, shippingDate:$sd, paketTypeId:$pt}')")" ok "$res" && shipped=$((shipped+1)) || true done echo " $shipped shipments with tracking number, $printed printed (in progress)" fi say "invoices" for oid in "${INV_ORDERS[@]}"; do io="$(odata m_inout "C_Order_ID eq $oid and MovementType eq 'C-' and DocStatus eq 'CO'" | first_id)"; [ -n "$io" ] || continue [ -z "$(odata c_invoice "C_Order_ID eq $oid" | first_id)" ] || continue res="$(api POST /api/processes/c_invoice_generate "$(jq -n --argjson o "$MORG" --argjson io "$io" --arg d "$TODAY" '{organizationId:$o, inOutId:$io, docAction:"CO", dateInvoiced:$d, consolidateDocument:false}')")"; ok "$res" || true; echo " order $oid invoiced" done # --- 5. returns: RMA (completed) + customer return receipt (completed) + photos ------------------------------------------ say "$RETURNS returns" [ -n "$REASONS_FILE" ] && mapfile -t REASONS < <(grep -v '^\s*$' "$REASONS_FILE" | sed "s/|RETURN$/|$RMA_TYPE_RETURN/; s/|BOUNCE$/|$RMA_TYPE_BOUNCE/") || REASONS=("Widerruf – Duft gefällt nicht|$RMA_TYPE_RETURN" "Flasche beim Transport beschädigt|$RMA_TYPE_RETURN" "Unzustellbar – Annahme verweigert|$RMA_TYPE_BOUNCE" "Falscher Artikel bestellt|$RMA_TYPE_RETURN" "Zerstäuber defekt|$RMA_TYPE_RETURN" "Adresse unbekannt – Rückläufer|$RMA_TYPE_BOUNCE" "Doppelt bestellt|$RMA_TYPE_RETURN" "Geschenk nicht gewünscht|$RMA_TYPE_RETURN") r=0 for io in $(odata m_inout "AD_Org_ID eq $MORG and IsSOTrx eq true and MovementType eq 'C-' and DocStatus eq 'CO'" 200 | jq -r '.records[] | .id' | awk 'NR % 3 == 1'); do r=$((r+1)); [ "$r" -le "$RETURNS" ] || break IFS='|' read -r reason rtype <<<"${REASONS[$(( (r-1) % ${#REASONS[@]} ))]}"; rname="$PREFIX-RMA-$(printf '%02d' "$r")" iorec="$(odata m_inout "M_InOut_ID eq $io" | jq -r '.records[0]')"; bp="$(echo "$iorec" | jq -r .C_BPartner_ID.id)"; bpl="$(echo "$iorec" | jq -r .C_BPartner_Location_ID.id)"; oid="$(echo "$iorec" | jq -r .C_Order_ID.id)" iol="$(odata m_inoutline "M_InOut_ID eq $io" | jq -r '.records[0]')"; iolid="$(echo "$iol" | jq -r .id)"; pid="$(echo "$iol" | jq -r .M_Product_ID.id)" pline="$(grep "^$pid|" "$PHOTO_DIR/articles.txt" | head -1)"; pval="$(echo "$pline" | cut -d'|' -f2)"; pname="$(echo "$pline" | cut -d'|' -f3)"; price="$(echo "$pline" | cut -d'|' -f4)"; price="${price:-0}" rma="$(odata m_rma "AD_Org_ID eq $MORG and Name eq '$rname'" | first_id)" if [ -z "$rma" ]; then res="$(api POST /api/rma/store "$(jq -n --argjson o "$MORG" --argjson bp "$bp" --argjson io "$io" --argjson oid "$oid" --argjson dt "$DOCTYPE_RMA" --argjson rt "$rtype" --arg n "$rname" --arg desc "$reason" --argjson iol "$iolid" --argjson pr "$price" --argjson cur "$CURRENCY_ID" --argjson me "$ME" \ '{organizationId:$o, isActive:true, isSOTrx:true, name:$n, description:$desc, partnerId:$bp, inOutId:$io, orderId:$oid, docTypeId:$dt, rmaTypeId:$rt, currencyId:$cur, amt:$pr, salesRepId:$me, rmaLines:[{AD_Org_ID:{id:$o, tableName:"AD_Org"}, line:10, isActive:true, qty:1, amt:$pr, description:$desc, M_InOutLine_ID:{id:$iol, tableName:"M_InOutLine"}}]}')")"; ok "$res" || continue; rma="$(echo "$res" | jq -r .id)" echo " RMA $rname ($rma) for shipment $io: $reason" fi rrec="$(odata m_rma "M_RMA_ID eq $rma")"; rst="$(echo "$rrec" | jq -r '.records[0].DocStatus.id // "DR"')"; ruid="$(echo "$rrec" | jq -r '.records[0].uid')"; rdoc="$(echo "$rrec" | jq -r '.records[0].DocumentNo')" # an existing RMA defines shipment/product/customer for the receipt (the shipment list above shifts between runs) rio="$(echo "$rrec" | jq -r '.records[0].InOut_ID.id // empty')" if [ -n "$rio" ] && [ "$rio" != "$io" ]; then io="$rio"; iorec="$(odata m_inout "M_InOut_ID eq $io" | jq -r '.records[0]')"; bp="$(echo "$iorec" | jq -r .C_BPartner_ID.id)"; bpl="$(echo "$iorec" | jq -r .C_BPartner_Location_ID.id)" pid="$(odata m_rmaline "M_RMA_ID eq $rma" 5 | jq -r '.records[0].M_Product_ID.id // empty')"; [ -n "$pid" ] || pid="$(odata m_inoutline "M_InOut_ID eq $io" | jq -r '.records[0].M_Product_ID.id')" pline="$(grep "^$pid|" "$PHOTO_DIR/articles.txt" | head -1)"; pval="$(echo "$pline" | cut -d'|' -f2)"; pname="$(echo "$pline" | cut -d'|' -f3)" fi if [ "$rst" = DR ] || [ "$rst" = IP ]; then da="$(api POST "/api/rma/$rma/document-action" '{"documentAction":"CO"}')"; ok "$da" || continue; echo " RMA $rdoc completed"; fi rl="$(odata m_rmaline "M_RMA_ID eq $rma" 5 | first_id)" # (no '+' in filters: the generic filter route turns it into a space) → by RMA + customer-return doc type cr="$(odata m_inout "M_RMA_ID eq $rma and C_DocType_ID eq $DOCTYPE_CR and DocStatus eq 'CO'" | first_id)" if [ -z "$cr" ]; then res="$(api POST /api/inouts/store "$(jq -n --argjson o "$MORG" --argjson w "$MWH" --argjson bp "$bp" --argjson bpl "$bpl" --argjson rma "$rma" --argjson dt "$DOCTYPE_CR" --arg d "$TODAY" --arg desc "Retoure $rdoc – $reason" \ --argjson rl "$rl" --argjson p "$pid" --argjson loc "$RETLOC" --argjson u "$UOM_ID" \ '{organizationId:$o, isActive:true, isSOTrx:true, movementTypeId:"C+", docTypeId:$dt, rmaId:$rma, partnerId:$bp, partnerLocationId:$bpl, warehouseId:$w, movementDate:$d, dateAcct:$d, description:$desc, inOutLines:[{AD_Org_ID:{id:$o, tableName:"AD_Org"}, line:10, isActive:true, movementQty:1, qtyEntered:1, M_RMALine_ID:{id:$rl, tableName:"M_RMALine"}, M_Product_ID:{id:$p, tableName:"M_Product"}, M_Locator_ID:{id:$loc, tableName:"M_Locator"}, C_UOM_ID:{id:$u, tableName:"C_UOM"}}]}')")"; ok "$res" || continue; cr="$(echo "$res" | jq -r .id)" da="$(api POST "/api/inouts/$cr/document-action" '{"documentAction":"CO"}')"; ok "$da" || true; echo " return receipt $cr -> $(echo "$da" | jq -r '.DocStatus.identifier // .DocStatus.id // "?"')" fi cruid="$(odata m_inout "M_InOut_ID eq $cr" | jq -r '.records[0].uid')" f1="$PHOTO_DIR/rma-$rname-artikel.jpg"; f2="$PHOTO_DIR/rma-$rname-paket.jpg"; im="$(img_for "$pval")" [ -s "$f1" ] || photo "$f1" ${im:+--image "$im"} "Retoure $rdoc – Wareneingang" "Artikel: $pname" "Grund: $reason" [ -s "$f2" ] || photo "$f2" --parcel "$rdoc" "Retourenpaket $rdoc" "Zustand bei Annahme dokumentiert" f3="$PHOTO_DIR/rma-$rname-wareneingang-$cr.jpg"; [ -s "$f3" ] || cp "$f2" "$f3" attach M_RMA "$rma" "$ruid" "$f1"; attach M_RMA "$rma" "$ruid" "$f2"; attach M_InOut "$cr" "$cruid" "$f3" done say "done — org $MORG: $ORDERS orders, $r returns, photos in $PHOTO_DIR"