- inventar.md: 38 Artikel aus handschriftlicher Liste erfasst (Lebensmittel, Medikamente, Ausrüstung, Hygiene, Energie) - import-inventar.py: Python-Skript zum einmaligen PUT /api/inventory (UTF-8-safe via ensure_ascii=True, kein PowerShell-Encoding-Problem) - import-inventar.ps1: PowerShell-Variante (Umlaute via [char]-Variablen)
173 lines
8.4 KiB
Python
173 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Importiert das Bollwerk-Vorratsinventar in den Server (einmaliger PUT).
|
||
Verwendet App-Default-Kategorien (IDs 1-7) und Default-Location Keller (ID 1).
|
||
|
||
Aufruf:
|
||
python import-inventar.py
|
||
python import-inventar.py --url http://localhost:8080
|
||
"""
|
||
import json
|
||
import getpass
|
||
import time
|
||
import uuid
|
||
import argparse
|
||
import urllib.request
|
||
import urllib.error
|
||
|
||
BASE_URL = "https://bollwerk.online"
|
||
ADMIN_USER = "admin"
|
||
|
||
|
||
def api(method, path, body=None, token=None):
|
||
url = BASE_URL + path
|
||
# ensure_ascii=True kodiert alle Umlaute als \uXXXX – kein Encoding-Problem
|
||
data = json.dumps(body, ensure_ascii=True).encode("ascii") if body else None
|
||
headers = {"Content-Type": "application/json"}
|
||
if token:
|
||
headers["Authorization"] = f"Bearer {token}"
|
||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||
with urllib.request.urlopen(req) as resp:
|
||
return json.loads(resp.read().decode("utf-8"))
|
||
|
||
|
||
def new_item(name, cat_id, qty, unit, expiry=None, kcal=None, notes=""):
|
||
now = int(time.time() * 1000)
|
||
return {
|
||
"id": str(uuid.uuid4()),
|
||
"name": name,
|
||
"categoryId": cat_id,
|
||
"quantity": float(qty),
|
||
"unit": unit,
|
||
"unitPrice": 0.0,
|
||
"kcalPerUnit": kcal,
|
||
"expiryDate": expiry,
|
||
"locationId": 1,
|
||
"notes": notes or "",
|
||
"lastUpdated": now,
|
||
}
|
||
|
||
|
||
# App-Default-Kategorien (IDs aus SeedDatabaseUseCase)
|
||
CATEGORIES = [
|
||
{"id": 1, "name": "Lebensmittel"},
|
||
{"id": 2, "name": "Wasser"},
|
||
{"id": 3, "name": "Medikamente"},
|
||
{"id": 4, "name": "Ausrüstung"},
|
||
{"id": 5, "name": "Hygiene"},
|
||
{"id": 6, "name": "Energie & Licht"},
|
||
{"id": 7, "name": "Dokumente"},
|
||
]
|
||
|
||
# App-Default-Lagerort
|
||
LOCATIONS = [{"id": 1, "name": "Keller"}]
|
||
|
||
ITEMS = [
|
||
# --- Lebensmittel: Getreide & Hülsenfrüchte (cat 1) ---------------------
|
||
new_item("Dinkelkorn", 1, 16, "Beutel", None, None, "Jahrgang 2019, vakuumiert"),
|
||
new_item("Weizen (Naturland)", 1, 36, "kg", None, None, "Jahrgang 2023"),
|
||
new_item("Dinkel", 1, 40, "kg", None, None, "Jahrgang 2023"),
|
||
new_item("Bio-Weizen", 1, 18, "kg", None, None, "Jahrgang 2019"),
|
||
new_item("Bio-Weizen (Neu)", 1, 200, "kg", None, None, "Jahrgang 05/2026, Kunststoffbehälter, O2-Absorber, Trockenmittel, lose luftdicht"),
|
||
new_item("Grüne Erbsen", 1, 10, "kg", None, None, "Jahrgang 2019"),
|
||
new_item("Gelbe Schälerbsen", 1, 10, "kg", None, None, "Jahrgang 2019"),
|
||
new_item("Berglinsen (1 kg)", 1, 1, "Pkg.", "2021-12-31", None, "MHD abgelaufen"),
|
||
new_item("Berglinsen (500 g)", 1, 4, "Pkg.", "2023-12-31", None, "MHD abgelaufen"),
|
||
new_item("Weiße Bohnen", 1, 2, "Pkg.", "2022-12-31", None, "MHD abgelaufen"),
|
||
new_item("Jodsalz", 1, 25, "kg", None, None, ""),
|
||
|
||
# --- Lebensmittel: Konserven (cat 1) ------------------------------------
|
||
new_item("Kondensmilch gezuckert (Dovgan)", 1, 88, "Dosen", None, None, "MHD prüfen"),
|
||
new_item("Corned Beef (Exeter)", 1, 48, "Dosen", None, None, "MHD prüfen"),
|
||
new_item("Geschälte Tomaten", 1, 5, "Dosen", None, None, "2x 2,5 kg, MHD abgelaufen"),
|
||
new_item("Tomatenmark (200 g)", 1, 24, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Sardinen", 1, 5, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Heringsfilet in Tomatensoße", 1, 11, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Thunfisch groß (900 g)", 1, 3, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Thunfisch klein", 1, 9, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Brathering Filet", 1, 2, "Dosen", None, None, "MHD abgelaufen"),
|
||
new_item("Dosenbrot", 1, 6, "Dosen", None, None, "MHD abgelaufen"),
|
||
|
||
# --- Lebensmittel: Öle & Fette (cat 1) ----------------------------------
|
||
new_item("Olivenöl (750 ml)", 1, 3, "Flaschen", "2022-12-31", None, "MHD abgelaufen"),
|
||
new_item("Avocadoöl (250 ml)", 1, 3, "Flaschen", "2026-12-31", None, "MHD 2026, prüfen"),
|
||
new_item("Sonnenblumenöl (1 L)", 1, 3, "Flaschen", "2021-12-31", None, "MHD abgelaufen"),
|
||
new_item("Sonnenblumenöl (500 ml)", 1, 1, "Flaschen", "2023-12-31", None, "MHD abgelaufen"),
|
||
|
||
# --- Lebensmittel: Getränke (cat 1) -------------------------------------
|
||
new_item("Instant-Kaffee", 1, 12, "Stk.", "2024-12-31", None, "MHD abgelaufen"),
|
||
new_item("Trek'N'Eat Peronin", 1, 8, "Stk.", "2024-12-31", None, "MHD abgelaufen"),
|
||
|
||
# --- Lebensmittel: Notfallrationen (cat 1) ------------------------------
|
||
new_item("Conserva-Set", 1, 30, "Manntage", None, 2300, "à 2300 kcal/Tag, conserva.de"),
|
||
new_item("NRG-5 Katadyn (Neubestand)", 1, 96, "Tagesrationen", "2036-04-30", 2300, "4x24, Produktion April 2026"),
|
||
new_item("NRG-5 Katadyn (Altbestand)", 1, 19, "Tagesrationen", "2029-01-31", 2300, "Produktion 2019, 5 Rationen verbraucht"),
|
||
|
||
# --- Medikamente (cat 3) ------------------------------------------------
|
||
new_item("Vitamine", 3, 960, "Stk.", None, None, "3x240 + 2x120 Tabletten"),
|
||
|
||
# --- Ausrüstung (cat 4) -------------------------------------------------
|
||
new_item("PMR-Funkgerät (Midland G9 Pro)", 4, 2, "Stk.", None, None, ""),
|
||
|
||
# --- Hygiene (cat 5) ----------------------------------------------------
|
||
new_item("Shampoo Mini", 5, 50, "Stk.", None, None, "à 20 ml"),
|
||
new_item("Ethanol", 5, 30, "Liter", None, None, ""),
|
||
new_item("Isopropanol", 5, 4, "Liter", None, None, ""),
|
||
new_item("Methanol", 5, 1, "Liter", None, None, ""),
|
||
new_item("Desinfektionsmittel", 5, 5, "Liter", None, None, ""),
|
||
|
||
# --- Energie & Licht (cat 6) --------------------------------------------
|
||
new_item("Petroleum", 6, 50, "Liter", None, None, ""),
|
||
]
|
||
|
||
|
||
def main():
|
||
global BASE_URL
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--url", default=BASE_URL)
|
||
parser.add_argument("--user", default=ADMIN_USER)
|
||
args = parser.parse_args()
|
||
|
||
BASE_URL = args.url.rstrip("/")
|
||
|
||
password = getpass.getpass(f"Admin-Passwort für '{args.user}' @ {BASE_URL}: ")
|
||
|
||
print("Logge ein...", end=" ", flush=True)
|
||
auth = api("POST", "/api/auth/login", {"username": args.user, "password": password})
|
||
token = auth["accessToken"]
|
||
print("OK")
|
||
|
||
print("Prüfe vorhandenes Inventar...", end=" ", flush=True)
|
||
existing = api("GET", "/api/inventory", token=token)
|
||
count = len(existing.get("items", []))
|
||
print(f"{count} Artikel vorhanden.")
|
||
|
||
if count > 0:
|
||
confirm = input(f" WARNUNG: {count} Artikel vorhanden. Überschreiben? (ja/nein): ")
|
||
if confirm.strip().lower() != "ja":
|
||
print("Abgebrochen.")
|
||
return
|
||
|
||
inventory = {
|
||
"version": 1,
|
||
"categories": CATEGORIES,
|
||
"locations": LOCATIONS,
|
||
"items": ITEMS,
|
||
"settings": [],
|
||
"deletedItemIds": [],
|
||
}
|
||
|
||
print(f"Lade {len(ITEMS)} Artikel hoch...", end=" ", flush=True)
|
||
result = api("PUT", "/api/inventory", inventory, token=token)
|
||
print("OK")
|
||
|
||
print()
|
||
print("Fertig!")
|
||
print(f" Artikel : {len(result.get('items', []))}")
|
||
print(f" Kategorien : {len(result.get('categories', []))}")
|
||
print(f" Lagerorte : {len(result.get('locations', []))}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|