first commit

This commit is contained in:
2025-08-27 18:55:45 +02:00
commit 1dce2ebf2c
34 changed files with 52590 additions and 0 deletions

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

27
webapp/routes/archive.py Normal file
View File

@@ -0,0 +1,27 @@
# webapp/routes/archive.py
from __future__ import annotations
import time
from flask import Blueprint, render_template, redirect, url_for
from webapp.storage.orders import load_orders, update_order
bp = Blueprint("archive", __name__)
@bp.get("/archiv")
def archive_index():
"""Zeigt ausschließlich Aufträge mit status == 'archived'."""
archived = [o for o in load_orders() if o.get("status") == "archived"]
archived.sort(
key=lambda o: (o.get("archived_at") or o.get("done_at") or o.get("created_at") or ""),
reverse=True,
)
return render_template("archive.html", archived_orders=archived)
@bp.post("/archiv/add/<order_id>")
def archive_add(order_id: str):
"""
Markiert einen Auftrag als archiviert.
Danach BLEIBEN wir auf der Strukturen-Seite (kein Wechsel zur Archiv-Seite).
"""
now = time.strftime("%Y-%m-%dT%H:%M:%S%z", time.gmtime())
update_order(order_id, {"status": "archived", "archived_at": now})
return redirect(url_for("structures.structures")) # zurück zu /strukturen

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from flask import Blueprint, request, jsonify
from webapp.services.blueprints import (
resolve_blueprint_id,
resolve_blueprint_id_by_product_id,
)
bp = Blueprint("blueprints_api", __name__)
@bp.get("/api/blueprint_id")
def api_blueprint_id():
"""
Liefert die Blueprint-TypeID.
Query-Parameter:
- name: Produktname (z. B. "Structure Market Network")
- productTypeId: Produkt-TypeID (optional, wenn 'name' fehlt)
Antwort: { ok: true, blueprint_type_id: 12345 } oder { ok:false, error: "..." }
"""
name = (request.args.get("name") or "").strip()
pid = request.args.get("productTypeId")
bp_tid = None
if name:
bp_tid = resolve_blueprint_id(name)
elif pid:
try:
bp_tid = resolve_blueprint_id_by_product_id(int(pid))
except Exception:
bp_tid = None
if not bp_tid:
return jsonify({"ok": False, "error": "not-found"}), 404
return jsonify({"ok": True, "blueprint_type_id": int(bp_tid)})

293
webapp/routes/cookbook.py Normal file
View File

@@ -0,0 +1,293 @@
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from flask import Blueprint, request, jsonify
import time
# Services
from webapp.services.ccookbook import (
call_cookbook_build_cost,
call_eve_ref_materials,
)
# Storage
from webapp.storage.orders import (
load_orders, update_order,
cache_costs, cache_materials,
)
bp = Blueprint("cookbook", __name__, url_prefix="/api")
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%S%z", time.gmtime())
# ----------------------------- Helpers ----------------------------------------
def _arg(name: str, default: str = "") -> str:
v = request.args.get(name)
return v if v is not None else default
def _to_int(s: str, default: int = 0) -> int:
try:
return int(float(s))
except Exception:
return default
def _order_by_id(order_id: str) -> Dict[str, Any] | None:
for o in load_orders():
if o.get("id") == order_id:
return o
return None
# ----------------------------- Endpoints Raw --------------------------------
@bp.get("/cookbook/build_cost")
def api_build_cost():
"""Kosten via evecookbook (Preise, Gesamt etc.)."""
bps = request.args.getlist("blueprintTypeId")
if not bps and request.args.get("blueprintTypeId"):
bps = [request.args.get("blueprintTypeId")]
if not bps:
return jsonify({"error": "missing blueprintTypeId"}), 400
try:
data = call_cookbook_build_cost(
blueprint_type_ids=bps,
quantity=_to_int(_arg("quantity", "1")),
price_mode=_arg("priceMode", "buy"),
additional_costs=_to_int(_arg("additionalCosts", "0")),
base_me=_to_int(_arg("baseMe", "0")),
components_me=_to_int(_arg("componentsMe", "0")),
system=_arg("system", "Jita"),
facility_tax=_to_int(_arg("facilityTax", "0")),
industry_structure_type=_arg("industryStructureType", "Station"),
industry_rig=_arg("industryRig", "None"),
reaction_structure_type=_arg("reactionStructureType", "Athanor"),
reaction_rig=_arg("reactionRig", "None"),
reaction_flag=_arg("reactionFlag", ""),
blueprint_version=_arg("blueprintVersion", "tq"),
)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 502
@bp.get("/cookbook/materials")
def api_materials():
"""
Materialliste via EVE Ref.
Erwartet: blueprintTypeId, optional quantity/baseMe/industryStructureType.
"""
if not request.args.get("blueprintTypeId"):
return jsonify({"error": "missing blueprintTypeId"}), 400
try:
data = call_eve_ref_materials(dict(request.args))
# Sicherstellen, dass immer eine Array-Liste unter "materials" steht
mats = data.get("materials") or []
if isinstance(mats, dict):
mats = list(mats.values())
data["materials"] = mats
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 502
# ----------------------------- Endpoints Cache/Order ------------------------
@bp.post("/order/<order_id>/bp")
def api_set_bp(order_id: str):
payload = request.get_json(silent=True) or {}
bp_id = payload.get("blueprint_type_id")
if not bp_id:
return jsonify({"error": "missing blueprint_type_id"}), 400
ok = update_order(order_id, {"blueprint_type_id": int(bp_id), "last_updated": _now()})
return jsonify({"ok": bool(ok)})
@bp.post("/order/<order_id>/cache")
def api_cache(order_id: str):
payload = request.get_json(silent=True) or {}
ok = False
if "costs" in payload:
ok = cache_costs(order_id, payload["costs"])
if "materials" in payload:
ok = cache_materials(order_id, payload["materials"])
return jsonify({"ok": bool(ok)})
@bp.post("/order/<order_id>/refresh")
def api_refresh_one(order_id: str):
o = _order_by_id(order_id)
if not o:
return jsonify({"error": "order not found"}), 404
bp_id = o.get("blueprint_type_id")
if not bp_id:
return jsonify({"error": "blueprint_type_id missing"}), 400
# Basis-Parameter aus Order
p = {
"blueprintTypeId": str(bp_id),
"quantity": str(o.get("quantity", 1)),
"priceMode": "buy",
"additionalCosts": "0",
"baseMe": str(o.get("me", 0)),
"componentsMe": str(o.get("me", 0)),
"system": o.get("system", "Jita"),
"facilityTax": str(o.get("facility_tax", 0)),
"industryStructureType": o.get("industry_structure", "Station"),
"industryRig": o.get("industry_rig", "None"),
"reactionStructureType": o.get("reaction_structure", "Athanor"),
"reactionRig": o.get("reaction_rig", "None"),
"reactionFlag": "",
"blueprintVersion": "tq",
}
# Kosten
try:
costs = call_cookbook_build_cost([bp_id],
quantity=int(p["quantity"]),
price_mode=p["priceMode"],
additional_costs=int(p["additionalCosts"]),
base_me=int(p["baseMe"]),
components_me=int(p["componentsMe"]),
system=p["system"],
facility_tax=int(float(p["facilityTax"])),
industry_structure_type=p["industryStructureType"],
industry_rig=p["industryRig"],
reaction_structure_type=p["reactionStructureType"],
reaction_rig=p["reactionRig"],
reaction_flag=p["reactionFlag"],
blueprint_version=p["blueprintVersion"],
)
cache_costs(order_id, costs)
except Exception as e:
costs = {"error": str(e)}
# Materialien
try:
mats = call_eve_ref_materials(p)
if isinstance(mats.get("materials"), dict):
mats["materials"] = list(mats["materials"].values())
cache_materials(order_id, mats)
except Exception as e:
mats = {"error": str(e)}
return jsonify({"ok": True, "costs": costs, "materials": mats})
@bp.post("/orders/refresh_all")
def api_refresh_all():
orders = [o for o in load_orders() if o.get("status") == "open" and o.get("blueprint_type_id")]
done = 0
for o in orders:
try:
request.environ["werkzeug.server.shutdown"] # no-op
except Exception:
pass
try:
_ = api_refresh_one(o["id"])
done += 1
except Exception:
continue
return jsonify({"ok": True, "refreshed": done})
# ----------------------------- Endpoints Aggregation ------------------------
@bp.get("/cookbook/open_costs")
def api_open_costs():
"""
Aggregiert Kosten je Order (nutzt Cache; fehlt er, wird berechnet).
"""
items: List[Dict[str, Any]] = []
total = 0.0
for o in load_orders():
if o.get("status") != "open" or not o.get("blueprint_type_id"):
continue
entry: Dict[str, Any] = {
"structure": o.get("structure"),
"quantity": o.get("quantity"),
"me": o.get("me"),
"system": o.get("system"),
"industry_structure": o.get("industry_structure"),
"industry_rig": o.get("industry_rig"),
"reaction_structure": o.get("reaction_structure"),
"reaction_rig": o.get("reaction_rig"),
}
costs = o.get("cookbook")
if not costs:
try:
costs = call_cookbook_build_cost([o["blueprint_type_id"]],
quantity=int(o.get("quantity", 1)),
price_mode="buy",
additional_costs=0,
base_me=int(o.get("me", 0)),
components_me=int(o.get("me", 0)),
system=o.get("system", "Jita"),
facility_tax=int(float(o.get("facility_tax", 0))),
industry_structure_type=o.get("industry_structure", "Station"),
industry_rig=o.get("industry_rig", "None"),
reaction_structure_type=o.get("reaction_structure", "Athanor"),
reaction_rig=o.get("reaction_rig", "None"),
reaction_flag="",
blueprint_version="tq",
)
cache_costs(o["id"], costs)
except Exception as e:
entry["error"] = str(e)
items.append(entry)
continue
msg = costs.get("message") or costs
unit = msg.get("buildCostPerUnit") or msg.get("unitCost")
tot = msg.get("totalCost") or msg.get("total") or 0
entry["unit_cost"] = unit
entry["total_cost"] = tot
total += float(tot or 0)
items.append(entry)
return jsonify({"items": items, "total_cost": total, "refreshed_at": _now()})
@bp.get("/cookbook/open_materials")
def api_open_materials():
"""
Aggregiert Materialien über alle offenen Orders.
"""
agg: Dict[int, Dict[str, Any]] = {}
total_cost = 0.0
def _add(tid: int, name: str, qty: float, cost: float | None):
row = agg.setdefault(tid, {"type_id": tid, "name": name, "quantity": 0, "cost": 0})
row["quantity"] += float(qty or 0)
if cost is not None:
row["cost"] += float(cost or 0)
for o in load_orders():
if o.get("status") != "open" or not o.get("blueprint_type_id"):
continue
mats = o.get("materials")
if not mats:
try:
mats = call_eve_ref_materials({
"blueprintTypeId": str(o["blueprint_type_id"]),
"quantity": str(o.get("quantity", 1)),
"baseMe": str(o.get("me", 0)),
"industryStructureType": o.get("industry_structure", "Station"),
"facilityTax": str(o.get("facility_tax", 0)),
})
if isinstance(mats.get("materials"), dict):
mats["materials"] = list(mats["materials"].values())
cache_materials(o["id"], mats)
except Exception:
mats = {"materials": []}
for m in mats.get("materials") or []:
tid = int(m.get("type_id") or m.get("typeId") or 0)
if not tid:
continue
name = m.get("name") or f"Type {tid}"
qty = m.get("quantity") or m.get("qty") or m.get("amount") or 0
cost = m.get("cost") or m.get("cost_per_unit") or m.get("unit_cost")
_add(tid, name, qty, cost)
if cost:
total_cost += float(cost or 0)
items = sorted(agg.values(), key=lambda r: (r.get("cost") or 0), reverse=True)
return jsonify({"items": items, "total_cost": total_cost, "refreshed_at": _now()})

View File

@@ -0,0 +1,86 @@
from __future__ import annotations
import uuid, time
from typing import List, Dict, Any
from flask import Blueprint, render_template, request, redirect, url_for
from webapp.storage.orders import load_orders, add_order, mark_done
from webapp.services.blueprints import resolve_blueprint_id
bp = Blueprint("structures", __name__)
STRUCTURES = ["Athanor", "Raitaru", "Astrahus", "Fortizar", "Keepstar", "Tatara", "Sotiyo"]
SYSTEMS = ["Jita", "Perimeter", "Amarr", "Dodixie", "Rens", "Hek", "OGV-AS", "B-9C24", "K-6K16", "PUIG-F"]
IND_STRUCTS = ["Station", "Raitaru", "Azbel", "Sotiyo"]
IND_RIGS = ["None", "T1", "T2"]
REAC_STRUCTS= ["Athanor", "Tatara"]
REAC_RIGS = ["None", "T1", "T2"]
def _split_orders() -> Dict[str, List[Dict[str, Any]]]:
orders = load_orders()
open_orders = [o for o in orders if o.get("status") == "open"]
done_orders = [o for o in orders if o.get("status") == "done"]
open_orders.sort(key=lambda o: o.get("created_at",""), reverse=True)
done_orders.sort(key=lambda o: o.get("done_at","") or "", reverse=True)
return {"open": open_orders, "done": done_orders}
@bp.route("/strukturen", methods=["GET", "POST"])
def structures():
if request.method == "POST":
structure = request.form.get("structure","").strip()
quantity = int(request.form.get("quantity") or 1)
me = int(request.form.get("me") or 0)
system = request.form.get("system","Jita")
ind_struct= request.form.get("industry_structure","Station")
ind_rig = request.form.get("industry_rig","None")
reac_struct = request.form.get("reaction_structure","Athanor")
reac_rig = request.form.get("reaction_rig","None")
notes = request.form.get("notes","")
oid = uuid.uuid4().hex
now = time.strftime("%Y-%m-%dT%H:%M:%S%z", time.gmtime())
bp_id = resolve_blueprint_id(structure)
add_order({
"id": oid,
"status": "open",
"created_at": now,
"structure": structure,
"system": system,
"industry_structure": ind_struct,
"industry_rig": ind_rig,
"reaction_structure": reac_struct,
"reaction_rig": reac_rig,
"quantity": quantity,
"me": me,
"notes": notes,
"blueprint_type_id": bp_id
})
return redirect(url_for("structures.structures"))
parts = _split_orders()
return render_template(
"structures.html",
structures=STRUCTURES,
systems=SYSTEMS,
ind_structs=IND_STRUCTS,
ind_rigs=IND_RIGS,
reac_structs=REAC_STRUCTS,
reac_rigs=REAC_RIGS,
open_orders=parts["open"],
done_orders=parts["done"],
selected_system="Jita",
selected_ind_struct="Station",
selected_ind_rig="None",
selected_reac_struct="Athanor",
selected_reac_rig="None",
)
# ←← NEU/ WICHTIG: exakte Unterseiten-Route
@bp.get("/strukturen/mineralien")
def mineralien():
return render_template("minerals.html")
@bp.post("/strukturen/done/<order_id>")
def done(order_id: str):
mark_done(order_id)
return redirect(url_for("structures.structures"))