182 lines
5.2 KiB
Python
182 lines
5.2 KiB
Python
"""Persistent state for the order form (one JSON file in WORK_DIR volume).
|
|
|
|
Per project (keyed by Kód zakázky) we keep:
|
|
- nazev: Název zakázky
|
|
- last: {field: last-used value} → used to prefill
|
|
- hist: {field: {value: count}} → all values ever used, with counts
|
|
(so the UI can offer them, most-used
|
|
on top)
|
|
|
|
Plus global meta: last order number (for the +1 suggestion).
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
_STORE = Path(os.getenv("WORK_DIR", "/tmp/offer-to-order")) / "order_state.json"
|
|
_lock = threading.Lock()
|
|
|
|
# Fields remembered per project (Název is stored separately as "nazev").
|
|
STICKY = [
|
|
"misto_dodani", "kontaktni_osoba", "vystavil",
|
|
"splatnost", "zaruka", "zpusob_platby", "zpusob_odberu", "zpusob_dopravy",
|
|
"forma_objednavky", "fakturujte", "popis",
|
|
]
|
|
|
|
|
|
def _load() -> dict:
|
|
try:
|
|
d = json.loads(_STORE.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
d = {}
|
|
d.setdefault("projects", {})
|
|
d.setdefault("meta", {})
|
|
return d
|
|
|
|
|
|
def _save(d: dict) -> None:
|
|
_STORE.parent.mkdir(parents=True, exist_ok=True)
|
|
_STORE.write_text(json.dumps(d, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
def _normalize(rec: dict) -> dict:
|
|
"""Upgrade an old flat-style record to the {nazev,last,hist} shape."""
|
|
if "last" in rec or "hist" in rec:
|
|
rec.setdefault("last", {})
|
|
rec.setdefault("hist", {})
|
|
return rec
|
|
nazev = rec.get("nazev", "")
|
|
last, hist = {}, {}
|
|
for k in STICKY:
|
|
if rec.get(k) not in (None, ""):
|
|
last[k] = rec[k]
|
|
hist[k] = {rec[k]: 1}
|
|
return {"nazev": nazev, "last": last, "hist": hist}
|
|
|
|
|
|
# ── Projects ──
|
|
def list_projects() -> list[dict]:
|
|
projects = _load()["projects"]
|
|
return sorted(
|
|
[{"kod": k, "nazev": _normalize(v).get("nazev", "")}
|
|
for k, v in projects.items()],
|
|
key=lambda p: p["kod"].lower(),
|
|
)
|
|
|
|
|
|
def get_defaults(code: str) -> dict:
|
|
"""Prefill values (last used) + a _history map ranked most-used first."""
|
|
code = (code or "").strip()
|
|
if not code:
|
|
return {}
|
|
raw = _load()["projects"].get(code)
|
|
if not raw:
|
|
return {}
|
|
rec = _normalize(raw)
|
|
out = dict(rec["last"])
|
|
if rec.get("nazev"):
|
|
out["nazev_zakazky"] = rec["nazev"]
|
|
history = {}
|
|
for field, counts in rec.get("hist", {}).items():
|
|
ranked = sorted(counts.items(), key=lambda kv: -kv[1])
|
|
history[field] = [{"value": v, "count": c} for v, c in ranked]
|
|
out["_history"] = history
|
|
return out
|
|
|
|
|
|
def upsert_project(code: str, nazev: str) -> None:
|
|
code = (code or "").strip()
|
|
if not code:
|
|
return
|
|
with _lock:
|
|
d = _load()
|
|
rec = _normalize(d["projects"].get(code, {}))
|
|
rec["nazev"] = (nazev or "").strip()
|
|
d["projects"][code] = rec
|
|
_save(d)
|
|
|
|
|
|
def delete_project(code: str) -> None:
|
|
code = (code or "").strip()
|
|
with _lock:
|
|
d = _load()
|
|
if code in d["projects"]:
|
|
del d["projects"][code]
|
|
_save(d)
|
|
|
|
|
|
def delete_history_value(code: str, field: str, value: str) -> None:
|
|
"""Remove one remembered value from a project field's history."""
|
|
code = (code or "").strip()
|
|
if not code or not field:
|
|
return
|
|
with _lock:
|
|
d = _load()
|
|
raw = d["projects"].get(code)
|
|
if not raw:
|
|
return
|
|
rec = _normalize(raw)
|
|
counts = rec["hist"].get(field)
|
|
if counts and value in counts:
|
|
del counts[value]
|
|
if not counts:
|
|
rec["hist"].pop(field, None)
|
|
if rec["last"].get(field) == value:
|
|
remaining = rec["hist"].get(field)
|
|
if remaining:
|
|
rec["last"][field] = max(remaining, key=remaining.get)
|
|
else:
|
|
rec["last"].pop(field, None)
|
|
d["projects"][code] = rec
|
|
_save(d)
|
|
|
|
|
|
def save_defaults(code: str, order: dict) -> None:
|
|
"""Record sticky-field values for a project: update 'last' and bump the
|
|
frequency count in 'hist' for each non-empty value."""
|
|
code = (code or "").strip()
|
|
if not code:
|
|
return
|
|
with _lock:
|
|
d = _load()
|
|
rec = _normalize(d["projects"].get(code, {}))
|
|
if order.get("nazev_zakazky"):
|
|
rec["nazev"] = order["nazev_zakazky"]
|
|
for k in STICKY:
|
|
v = order.get(k)
|
|
if v in (None, ""):
|
|
continue
|
|
v = str(v)
|
|
rec["last"][k] = v
|
|
rec["hist"].setdefault(k, {})
|
|
rec["hist"][k][v] = rec["hist"][k].get(v, 0) + 1
|
|
d["projects"][code] = rec
|
|
_save(d)
|
|
|
|
|
|
# ── Order-number counter ──
|
|
def next_order_number() -> str:
|
|
return _increment(_load()["meta"].get("last_order_number") or "")
|
|
|
|
|
|
def _increment(s: str) -> str:
|
|
if not s:
|
|
return ""
|
|
m = re.search(r"(\d+)(\D*)$", s)
|
|
if not m:
|
|
return s
|
|
num = m.group(1)
|
|
return s[: m.start(1)] + str(int(num) + 1).zfill(len(num)) + m.group(2)
|
|
|
|
|
|
def remember_order_number(num: str) -> None:
|
|
num = (num or "").strip()
|
|
if not num:
|
|
return
|
|
with _lock:
|
|
d = _load()
|
|
d["meta"]["last_order_number"] = num
|
|
_save(d)
|