#!/usr/bin/env bash # ===================================================================== # seed-demo-data.sh — fill a fresh LogShip instance (clone of the golden template) with a # small, realistic demo dataset THROUGH THE APP'S OWN ROUTES (so every record looks exactly # like one created in the UI): one fulfilment merchant with its org/warehouse/bins, 10 # products with prices + stock, 6 consignees, 6 sales orders in mixed states, shipments # and one invoice. # # BASE=http://127.0.0.1:3001 ADMIN_USER=demo ADMIN_PASS=… MERCHANT_PASS=… ./seed-demo-data.sh # # Env (defaults in brackets): BASE [http://127.0.0.1:3001], ADMIN_USER/ADMIN_PASS (staff admin # login), MERCHANT_PASS (login password of the merchant user `mustershop`), CLIENT_ID [1000000], # ROLE_ID [1000000 = " Admin"], ORG_ID [1000000 operator org], WH_ID [1000000], # PRICE_LIST_ID [1000003 Verkauf Brutto], PLV_NET [1000002], PLV_GROSS [1000003], # DOCTYPE_SO [1000032 Standard Order], DOCTYPE_INV [1000023 Physical Inventory], # SHIPPER_ID [1000000 DHL], LOCATOR_TYPE_SHELF [1000002], BP_GROUP_MERCHANT [1000001], # BP_GROUP_STD [1000000], COUNTRY_ID [101], CURRENCY_ID [102 EUR], UOM_ID [100 Each], # PAYMENT_TERM_ID [1000000 Immediate], TAX_ID [1000000 19 %]. # # Idempotent: every step looks the record up by name/reference first and skips it when it # exists, so the script can be re-run after a partial failure. Never resubmits a create. # Needs curl + jq. Run it ON the instance (localhost) or anywhere that reaches BASE. # ===================================================================== set -euo pipefail BASE="${BASE:-http://127.0.0.1:3001}" ADMIN_USER="${ADMIN_USER:?ADMIN_USER required}"; ADMIN_PASS="${ADMIN_PASS:?ADMIN_PASS required}" MERCHANT_PASS="${MERCHANT_PASS:?MERCHANT_PASS required}" 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_NET="${PLV_NET:-1000002}"; PLV_GROSS="${PLV_GROSS:-1000003}" DOCTYPE_SO="${DOCTYPE_SO:-1000032}"; DOCTYPE_INV="${DOCTYPE_INV:-1000023}"; SHIPPER_ID="${SHIPPER_ID:-1000000}" LOCATOR_TYPE_SHELF="${LOCATOR_TYPE_SHELF:-1000002}"; BP_GROUP_MERCHANT="${BP_GROUP_MERCHANT:-1000001}"; BP_GROUP_STD="${BP_GROUP_STD:-1000000}" COUNTRY_ID="${COUNTRY_ID:-101}"; CURRENCY_ID="${CURRENCY_ID:-102}"; UOM_ID="${UOM_ID:-100}"; PAYMENT_TERM_ID="${PAYMENT_TERM_ID:-1000000}"; TAX_ID="${TAX_ID:-1000000}" TODAY="$(date +%Y-%m-%d)"; TOMORROW="$(date -d tomorrow +%Y-%m-%d 2>/dev/null || date -v+1d +%Y-%m-%d)" JAR="$(mktemp)"; trap 'rm -f "$JAR"' EXIT say() { printf '\n== %s\n' "$*"; } api() { # api METHOD PATH [JSON] 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() { # odata MODEL "" -> records json (generic filter endpoint) local enc; enc="$(jq -rn --arg s "\$filter=$2&\$top=50" '$s|@uri')" api GET "/api/filters/$1/$enc" } first_id() { jq -r '(.records // .)[0].id // empty'; } ok() { # ok JSON -> exits 1 with the message when status != 200 / no id local st; st="$(echo "$1" | jq -r '.status // 200')" if [ "$st" != 200 ]; then echo " FAILED: $(echo "$1" | jq -c '{status, message, detail}' 2>/dev/null || echo "$1")" >&2; exit 1; fi } # --- 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: $(echo "$login" | cut -c1-300)" >&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 api GET /api/idempiere-auth/authenticated | jq -e '.user.id' >/dev/null || { echo "session not authenticated" >&2; exit 1; } echo " ok" # --- 1. merchant (org + warehouse + user + role) ----------------------------------------------- say "merchant 'Muster Shop GmbH' (org mustershop)" M_NAME="Muster Shop GmbH"; M_SHORT="mustershop" MBP="$(odata c_bpartner "Name eq '$M_NAME'" | first_id)" if [ -n "$MBP" ]; then MORG="$(odata ad_org "Value eq '$M_SHORT'" | first_id)" MWH="$(odata m_warehouse "AD_Org_ID eq $MORG" | first_id)" echo " exists: bpartner $MBP org $MORG warehouse $MWH" else res="$(api POST /api/admin/merchant-onboarding/process "$(jq -n --arg n "$M_NAME" --arg s "$M_SHORT" --arg p "$MERCHANT_PASS" \ --argjson g "$BP_GROUP_MERCHANT" --argjson pl "$PRICE_LIST_ID" --argjson sh "$SHIPPER_ID" --argjson c "$COUNTRY_ID" '{ companyName:$n, ceoName:"Lena Muster", shortName:$s, isFulfillmentCustomer:true, taxId:"DE811223344", companyEmail:"logistik@mustershop.example", street:"Hanauer Landstraße 150", zip:"60314", city:"Frankfurt am Main", countryId:$c, phoneWired:"+49 69 1234567-0", partnerGroupId:$g, priceListId:$pl, shipperOrderDefaultId:$sh, deliveryViaRuleId:"D", password:$p, fulfillmentOrderBaseprice:1.99, fulfillmentOrderPickprice:0.35, fulfillmentOrderQtyPickfree:1, fulfillmentOrderReturnBaseprice:2.49, fulfillmentOrderReturnPickprice:0.35, shelfRentLargePrice:5.5, shelfRentSmallPrice:3.9, rentPalettSpacePrice:12.9, fulfillmentServiceHourlyRate:39, paketSPrice:0.45, paketMPrice:0.65, paketLPrice:0.95, paketXLPrice:1.45, logshipMonthlyFee:99 }')")" echo "$res" | jq -r '.report[] | " step \(.step) \(.entity): \(.status) \(.id // "") \(.message // "")"' ok "$res" MBP="$(echo "$res" | jq -r .ctx.bpartnerId)"; MORG="$(echo "$res" | jq -r .ctx.newOrgId)"; MWH="$(echo "$res" | jq -r .ctx.warehouseId)" echo " created: bpartner $MBP org $MORG warehouse $MWH role $(echo "$res" | jq -r .ctx.newRoleId)" fi [ -n "$MORG" ] && [ -n "$MWH" ] || { echo "merchant org/warehouse unresolved" >&2; exit 1; } # --- 2. bins ------------------------------------------------------------------------------------- say "bins in warehouse $MWH" BINS=(); for aisle in 01 02; do for pos in 01 02 03 04 05; do BINS+=("AA-$aisle-$pos"); done; done LOC_IDS=() for b in "${BINS[@]}"; do id="$(odata m_locator "M_Warehouse_ID eq $MWH and Value eq '$b'" | first_id)" if [ -z "$id" ]; then IFS=- read -r x y z <<<"$b" res="$(api POST /api/materials/locators/store "$(jq -n --argjson o "$MORG" --argjson w "$MWH" --arg v "$b" --arg x "$x" --arg y "$y" --arg z "$z" --argjson t "$LOCATOR_TYPE_SHELF" \ '{organizationId:$o, warehouseId:$w, value:$v, priorityNo:50, isActive:true, isDefault:false, x:$x, y:$y, z:$z, locatorTypeId:$t}')")" ok "$res"; id="$(echo "$res" | jq -r .id)"; echo " created $b ($id)" fi LOC_IDS+=("$id") done echo " ${#LOC_IDS[@]} bins" # --- 3. products ----------------------------------------------------------------------------------- say "products (org $MORG)" # name|net|gross|ean|weight kg|L|W|H cm|stock PRODUCTS=( "Edelstahl Trinkflasche 750 ml|16.72|19.90|4260123450017|0.32|26|7|7|180" "Bambus Schneidebrett 40x30|20.92|24.90|4260123450024|1.10|40|30|2|95" "Yoga-Matte Premium 6 mm|33.53|39.90|4260123450031|1.25|61|15|15|60" "LED Schreibtischlampe dimmbar|41.93|49.90|4260123450048|0.90|45|18|12|42" "Bio-Baumwoll T-Shirt Unisex M|16.72|19.90|4260123450055|0.18|30|22|2|200" "Kaffeebohnen Hausröstung 1 kg|15.04|17.90|4260123450062|1.02|25|10|8|150" "Bluetooth Lautsprecher Mini|29.33|34.90|4260123450079|0.45|12|8|8|75" "Reisekissen Memory-Schaum|21.76|25.90|4260123450086|0.35|30|28|12|88" "Gewürz-Set 6 Gläser|24.29|28.90|4260123450093|0.95|22|15|9|64" "Notizbuch A5 Hardcover|8.32|9.90|4260123450109|0.30|21|15|2|320" ) PROD_IDS=(); PROD_GROSS=(); PROD_STOCK=() i=0 for p in "${PRODUCTS[@]}"; do IFS='|' read -r name net gross ean wt l w h stock <<<"$p" id="$(odata m_product "AD_Org_ID eq $MORG and Name eq '$name'" | first_id)" if [ -z "$id" ]; then res="$(curl -sS -b "$JAR" -c "$JAR" -X POST "$BASE/api/materials/products/quick-store" \ -F "name=$name" -F "price=$gross" -F "organizationId=$MORG" \ -F "priceListVersionIds=[$PLV_NET,$PLV_GROSS]" -F "priceListVersionPrices={\"$PLV_NET\":$net,\"$PLV_GROSS\":$gross}")" ok "$res"; id="$(echo "$res" | jq -r '.id // .product.id')" # master data the quick modal does not take (fail-soft) api PUT /api/materials/products/update "$(jq -n --argjson id "$id" --argjson o "$MORG" --arg ean "$ean" --argjson wt "$wt" --argjson l "$l" --argjson w "$w" --argjson h "$h" \ '{id:$id, organizationId:$o, upc:$ean, weight:$wt, length:$l, width:$w, height:$h}')" >/dev/null || echo " (dims/ean for $name not set)" echo " created $name ($id) prices: $(echo "$res" | jq -c '[.prices[]? | .ok]')" fi PROD_IDS+=("$id"); PROD_GROSS+=("$gross"); PROD_STOCK+=("$stock"); i=$((i+1)) done echo " ${#PROD_IDS[@]} products" # --- 4. initial stock (physical inventory, completed) -------------------------------------------- say "initial stock" INV_DESC="Erstbestand Demo" inv="$(odata m_inventory "M_Warehouse_ID eq $MWH and Description eq '$INV_DESC'" | first_id)" if [ -n "$inv" ]; then echo " exists ($inv)"; else lines="$(for n in "${!PROD_IDS[@]}"; do jq -n --argjson p "${PROD_IDS[$n]}" --argjson l "${LOC_IDS[$n]}" --argjson q "${PROD_STOCK[$n]}" --argjson ln "$(( (n+1)*10 ))" \ '{M_Product_ID:{id:$p}, M_Locator_ID:{id:$l}, qtyCount:$q, qtyBook:0, line:$ln}'; done | jq -s .)" res="$(api POST /api/materials/stock-takes/store "$(jq -n --argjson o "$MORG" --argjson w "$MWH" --argjson d "$DOCTYPE_INV" --arg dt "$TODAY" --arg desc "$INV_DESC" --argjson lines "$lines" \ '{organizationId:$o, warehouseId:$w, docTypeId:$d, movementDate:$dt, isActive:true, description:$desc, inventoryLines:$lines}')")" ok "$res"; inv="$(echo "$res" | jq -r .id)" res="$(api POST "/api/materials/stock-takes/$inv/document-action" '{"documentAction":"CO"}')"; ok "$res" echo " inventory $inv completed: $(echo "$res" | jq -r '.DocStatus.identifier // .docStatus // "?"')" fi # --- 5. consignees ------------------------------------------------------------------------------------ say "consignees" # name|street|zip|city|email CUSTOMERS=( "Anna Berger|Lindenstraße 12|10969|Berlin|anna.berger@example.de" "Jonas Keller|Schellingstraße 44|80799|München|jonas.keller@example.de" "Miriam Hoffmann|Große Bleichen 8|20354|Hamburg|m.hoffmann@example.de" "Tobias Wagner|Kaiserstraße 77|60329|Frankfurt am Main|t.wagner@example.de" "Sarah Neumann|Ehrenstraße 25|50672|Köln|sarah.neumann@example.de" "Felix Brandt|Königstraße 3|70173|Stuttgart|felix.brandt@example.de" ) CUST_BP=(); CUST_LOC=() for c in "${CUSTOMERS[@]}"; do IFS='|' read -r name street zip city email <<<"$c" bp="$(odata c_bpartner "AD_Org_ID eq $MORG and Name eq '$name'" | first_id)" if [ -z "$bp" ]; then loc="$(api POST /api/settings/locations/store "$(jq -n --argjson o "$MORG" --arg a "$street" --arg z "$zip" --arg c "$city" --argjson co "$COUNTRY_ID" \ '{organizationId:$o, address1:$a, postal:$z, city:$c, countryId:$co, isActive:true, isValid:true}')")"; ok "$loc"; locid="$(echo "$loc" | jq -r .id)" res="$(api POST /api/partners/partners/store "$(jq -n --argjson o "$MORG" --arg n "$name" --arg e "$email" --argjson g "$BP_GROUP_STD" --argjson pl "$PRICE_LIST_ID" \ '{organizationId:$o, name:$n, EMail:$e, 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" echo " created $name ($bp)" fi bpl="$(odata c_bpartner_location "C_BPartner_ID eq $bp" | first_id)" CUST_BP+=("$bp"); CUST_LOC+=("$bpl") done # --- 6. sales orders ---------------------------------------------------------------------------------- say "sales orders" # ref|customer idx|action|lines "prodIdx:qty,prodIdx:qty" ORDERS=( "SHOP-10021|0|CO|0:1,4:2" "SHOP-10022|1|CO|2:1" "SHOP-10023|2|CO|3:1,6:1,9:3" "SHOP-10024|3|CO|1:1,8:1" "SHOP-10025|4|PR|5:2,7:1" "SHOP-10026|5|DR|9:5" ) ORDER_IDS=() for o in "${ORDERS[@]}"; do IFS='|' read -r ref ci action lines <<<"$o" oid="$(odata c_order "AD_Org_ID eq $MORG and POReference eq '$ref'" | first_id)" if [ -z "$oid" ]; then bp="${CUST_BP[$ci]}"; bpl="${CUST_LOC[$ci]}" ol="$(n=0; for pair in ${lines//,/ }; do pi="${pair%%:*}"; q="${pair##*:}"; n=$((n+10)); jq -n --argjson p "${PROD_IDS[$pi]}" --argjson q "$q" --argjson pr "${PROD_GROSS[$pi]}" --argjson ln "$n" \ --arg d "$TODAY" --arg dp "$TOMORROW" --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 "$TODAY" --arg dp "$TOMORROW" --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)"; echo " created $ref ($oid)" fi # apply the target document action when the order is still a draft (re-runs continue here) st="$(odata c_order "C_Order_ID eq $oid" | jq -r '.records[0].DocStatus.id // "DR"')" if [ "$action" != DR ] && [ "$st" = DR ]; then da="$(api POST "/api/orders/$oid/document-action" "$(jq -n --arg a "$action" '{documentAction:$a}')")"; ok "$da" echo " $ref -> $action: $(echo "$da" | jq -r '.DocStatus.identifier // .DocStatus.id // "?"')" fi ORDER_IDS+=("$oid") done # --- 7. shipments for the first three completed orders --------------------------------------------------- say "shipments" for ci in 0 1 2; do bp="${CUST_BP[$ci]}" if [ -n "$(odata m_inout "C_BPartner_ID eq $bp and IsSOTrx eq true" | first_id)" ]; then echo " exists for customer $bp"; continue; fi 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"; echo " generated for customer $bp: $(echo "$res" | jq -r '.summary // .logs[0].log // "ok"' | cut -c1-120)" done # --- 8. one invoice (first shipment) --------------------------------------------------------------------- say "invoice" if [ -n "$(odata c_invoice "AD_Org_ID eq $MORG and IsSOTrx eq true" | first_id)" ]; then echo " exists"; else io="$(odata m_inout "C_BPartner_ID eq ${CUST_BP[0]} and IsSOTrx eq true" | first_id)" 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"; echo " $(echo "$res" | jq -r '.summary // .logs[0].log // "ok"' | cut -c1-160)" fi say "done — org $MORG / warehouse $MWH / products ${#PROD_IDS[@]} / orders ${#ORDER_IDS[@]}"