Files
foodlinkk-command-center/cockpit/app/services/nas_folders.py
T
2026-06-09 10:41:13 +00:00

105 lines
4.3 KiB
Python

"""NAS folder structure per client and project — geen alles-op-een-hoop."""
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import Any, Optional
from app.db import execute, fetch_one
NAS_ROOT = Path(os.getenv("NAS_CLIENTS_ROOT", "/data/nas-clients"))
PROJECT_SUBDIRS = ("photos", "documents", "packaging", "exports", "briefs")
PROJECT_TYPES = [
{"id": "packaging", "label_nl": "Verpakking & label", "label_en": "Packaging & label", "icon": "📦", "nas_sub": "packaging"},
{"id": "retail_listing", "label_nl": "Retail listing / schap", "label_en": "Retail listing", "icon": "🏪", "nas_sub": "documents"},
{"id": "recipe", "label_nl": "Recept & productontwikkeling", "label_en": "Recipe & R&D", "icon": "🍱", "nas_sub": "briefs"},
{"id": "marketing", "label_nl": "Marketing campagne", "label_en": "Marketing campaign", "icon": "📣", "nas_sub": "exports"},
{"id": "halal", "label_nl": "Halal certificering", "label_en": "Halal certification", "icon": "☪️", "nas_sub": "documents"},
{"id": "sourcing", "label_nl": "Sourcing & import", "label_en": "Sourcing & import", "icon": "🚢", "nas_sub": "documents"},
{"id": "crm", "label_nl": "Klant & partnership", "label_en": "Client & partnership", "icon": "🤝", "nas_sub": "documents"},
{"id": "research", "label_nl": "Marktonderzoek", "label_en": "Market research", "icon": "🔬", "nas_sub": "briefs"},
{"id": "general", "label_nl": "Algemeen project", "label_en": "General project", "icon": "📁", "nas_sub": "documents"},
]
def slugify(name: str, max_len: int = 48) -> str:
s = re.sub(r"[^a-zA-Z0-9]+", "-", (name or "project").strip().lower()).strip("-")
return (s[:max_len] or "project")
def _write_meta(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
def ensure_client_folder(client_id: int, client_name: str) -> str:
"""Maak NAS-map per klant: clients/{slug}/ met submappen."""
slug = slugify(client_name)
root = NAS_ROOT / slug
for sub in ("projects", "photos", "documents", "inbox"):
(root / sub).mkdir(parents=True, exist_ok=True)
meta = {
"client_id": client_id,
"client_name": client_name,
"slug": slug,
"structure": ["projects", "photos", "documents", "inbox"],
}
_write_meta(root / "client.json", meta)
rel = str(root)
execute("UPDATE clients SET nas_folder = %s WHERE id = %s", (rel, client_id))
return rel
def ensure_project_folder(
project_id: int,
project_name: str,
client_id: Optional[int],
client_name: Optional[str],
project_type: str = "general",
) -> dict[str, Any]:
"""Projectmap onder klant: clients/{client}/projects/{project}/"""
client_root: Path | None = None
if client_id and client_name:
row = fetch_one("SELECT nas_folder FROM clients WHERE id = %s", (client_id,))
if row and row.get("nas_folder"):
client_root = Path(row["nas_folder"])
else:
client_root = Path(ensure_client_folder(client_id, client_name))
else:
client_root = NAS_ROOT / "_geen-klant"
client_root.mkdir(parents=True, exist_ok=True)
proj_slug = f"{project_id}-{slugify(project_name)}"
proj_root = client_root / "projects" / proj_slug
for sub in PROJECT_SUBDIRS:
(proj_root / sub).mkdir(parents=True, exist_ok=True)
type_info = next((t for t in PROJECT_TYPES if t["id"] == project_type), PROJECT_TYPES[-1])
_write_meta(
proj_root / "project.json",
{
"project_id": project_id,
"name": project_name,
"client_id": client_id,
"project_type": project_type,
"folders": list(PROJECT_SUBDIRS),
"primary_sub": type_info.get("nas_sub", "documents"),
},
)
nas_path = str(proj_root)
client_rel = str(client_root)
execute(
"""UPDATE cockpit_projects SET nas_path = %s, nas_client_root = %s, updated_at = NOW() WHERE id = %s""",
(nas_path, client_rel, project_id),
)
return {"nas_path": nas_path, "nas_client_root": client_rel, "project_slug": proj_slug}
def list_types() -> list[dict[str, Any]]:
return list(PROJECT_TYPES)