#!/usr/bin/env bash # top-up-stock: physical inventory for every stocked article of an org that has qty on hand <= LOW # (zero articles get a fresh bin, low ones are counted up in their existing bin); KEEP_ZERO articles # (highest search keys) stay at zero so the demo still shows "out of stock". set -euo pipefail; export LANG=C.UTF-8 BASE=http://127.0.0.1:3001; ORG="${ORG:?}"; WH="${WH:?}"; LOW="${LOW:-4}"; KEEP_ZERO="${KEEP_ZERO:-6}"; DOCTYPE_INV=1000023; TODAY="$(date +%Y-%m-%d)" ADMIN_PASS="$(sed -n 's/^password: //p' /root/demo-credentials.txt | head -1)" JAR="$(mktemp)"; trap 'rm -f "$JAR"' EXIT api() { curl -sS -b "$JAR" -c "$JAR" -X "$1" "$BASE$2" -H 'Content-Type: application/json' -d "$3"; } api POST /api/idempiere-auth/login "$(jq -n --arg p "$ADMIN_PASS" '{userName:"demo",password:$p,selectRole:true}')" | jq -e .token >/dev/null api POST /api/idempiere-auth/select-role '{"clientId":1000000,"roleId":1000000,"organizationId":1000000,"warehouseId":1000000,"language":"de_DE"}' >/dev/null psql() { sudo -u postgres psql -At -F'|' -d idempiere -c "$1" 2>/dev/null; } mapfile -t BINS < <(psql "select m_locator_id from adempiere.m_locator where m_warehouse_id=$WH and value like 'VR-%' order by value") # product | current onhand | locator (existing one for low stock, else '') mapfile -t ROWS < <(psql "select p.m_product_id, coalesce(s.q,0)::int, coalesce(s.loc::text,''), p.value from adempiere.m_product p left join (select m_product_id, sum(qtyonhand) q, max(m_locator_id) loc from adempiere.m_storageonhand group by 1) s using(m_product_id) where p.ad_org_id=$ORG and p.isstocked='Y' and p.isactive='Y' and coalesce(s.q,0) <= $LOW order by (coalesce(s.q,0)=0) desc, p.value desc") echo "candidates: ${#ROWS[@]} (keeping the first $KEEP_ZERO zero-stock articles at zero)" LINES=(); n=0; kept=0 for r in "${ROWS[@]}"; do IFS='|' read -r pid q loc val <<<"$r" if [ "$q" = 0 ] && [ "$kept" -lt "$KEEP_ZERO" ]; then kept=$((kept+1)); echo " keep zero: $val"; continue; fi target=$(( 3 + (pid * 7919) % 38 )); [ "$q" != 0 ] && target=$(( q + 6 + (pid * 31) % 20 )) [ -n "$loc" ] && [ "$q" != 0 ] || loc="${BINS[$(( n % ${#BINS[@]} ))]}" LINES+=("$(jq -nc --argjson p "$pid" --argjson l "$loc" --argjson c "$target" --argjson b "$q" '{M_Product_ID:{id:$p}, M_Locator_ID:{id:$l}, qtyCount:$c, qtyBook:$b}')"); n=$((n+1)) done echo "lines: ${#LINES[@]}" chunk=0 for start in $(seq 0 100 $(( ${#LINES[@]} - 1 ))); do chunk=$((chunk+1)); body="$(printf '%s\n' "${LINES[@]:$start:100}" | jq -s 'to_entries | map(.value + {line: ((.key+1)*10), isActive:true})')" res="$(api POST /api/materials/stock-takes/store "$(jq -n --argjson o "$ORG" --argjson w "$WH" --argjson d "$DOCTYPE_INV" --arg dt "$TODAY" --arg desc "Bestandsauffüllung VONROC ($TODAY) #$chunk" --argjson lines "$body" \ '{organizationId:$o, warehouseId:$w, docTypeId:$d, movementDate:$dt, isActive:true, description:$desc, inventoryLines:$lines}')")" inv="$(echo "$res" | jq -r '.id // empty')"; [ -n "$inv" ] || { echo "store failed: $(echo "$res" | cut -c1-300)"; exit 1; } da="$(api POST "/api/materials/stock-takes/$inv/document-action" '{"documentAction":"CO"}')" echo " inventory $inv: $(echo "$da" | jq -r '.DocStatus.identifier // .DocStatus.id // .message // "?"')" done psql "select case when coalesce(s.q,0)<=0 then 'zero' when s.q<5 then '1-4' else '5+' end, count(*) from adempiere.m_product p left join (select m_product_id, sum(qtyonhand) q from adempiere.m_storageonhand group by 1) s using(m_product_id) where p.ad_org_id=$ORG and p.isstocked='Y' group by 1"