import oracledb import csv import io import logging from fastapi import HTTPException from .. import database logger = logging.getLogger(__name__) def get_mappings(search: str = "", page: int = 1, per_page: int = 50): """Get paginated mappings with optional search.""" if database.pool is None: raise HTTPException(status_code=503, detail="Oracle unavailable") offset = (page - 1) * per_page with database.pool.acquire() as conn: with conn.cursor() as cur: # Build WHERE clause where = "" params = {} if search: where = """WHERE (UPPER(at.sku) LIKE '%' || UPPER(:search) || '%' OR UPPER(at.codmat) LIKE '%' || UPPER(:search) || '%' OR UPPER(na.denumire) LIKE '%' || UPPER(:search) || '%')""" params["search"] = search # Count total count_sql = f""" SELECT COUNT(*) FROM ARTICOLE_TERTI at LEFT JOIN nom_articole na ON na.codmat = at.codmat {where} """ cur.execute(count_sql, params) total = cur.fetchone()[0] # Get page data_sql = f""" SELECT at.sku, at.codmat, na.denumire, at.cantitate_roa, at.procent_pret, at.activ, TO_CHAR(at.data_creare, 'YYYY-MM-DD HH24:MI') as data_creare FROM ARTICOLE_TERTI at LEFT JOIN nom_articole na ON na.codmat = at.codmat {where} ORDER BY at.sku, at.codmat OFFSET :offset ROWS FETCH NEXT :per_page ROWS ONLY """ params["offset"] = offset params["per_page"] = per_page cur.execute(data_sql, params) columns = [col[0].lower() for col in cur.description] rows = [dict(zip(columns, row)) for row in cur.fetchall()] return { "mappings": rows, "total": total, "page": page, "per_page": per_page, "pages": (total + per_page - 1) // per_page } def create_mapping(sku: str, codmat: str, cantitate_roa: float = 1, procent_pret: float = 100): """Create a new mapping.""" if database.pool is None: raise HTTPException(status_code=503, detail="Oracle unavailable") with database.pool.acquire() as conn: with conn.cursor() as cur: cur.execute(""" INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ, data_creare, id_util_creare) VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, SYSDATE, -3) """, {"sku": sku, "codmat": codmat, "cantitate_roa": cantitate_roa, "procent_pret": procent_pret}) conn.commit() return {"sku": sku, "codmat": codmat} def update_mapping(sku: str, codmat: str, cantitate_roa: float = None, procent_pret: float = None, activ: int = None): """Update an existing mapping.""" if database.pool is None: raise HTTPException(status_code=503, detail="Oracle unavailable") sets = [] params = {"sku": sku, "codmat": codmat} if cantitate_roa is not None: sets.append("cantitate_roa = :cantitate_roa") params["cantitate_roa"] = cantitate_roa if procent_pret is not None: sets.append("procent_pret = :procent_pret") params["procent_pret"] = procent_pret if activ is not None: sets.append("activ = :activ") params["activ"] = activ if not sets: return False sets.append("data_modif = SYSDATE") set_clause = ", ".join(sets) with database.pool.acquire() as conn: with conn.cursor() as cur: cur.execute(f""" UPDATE ARTICOLE_TERTI SET {set_clause} WHERE sku = :sku AND codmat = :codmat """, params) conn.commit() return cur.rowcount > 0 def delete_mapping(sku: str, codmat: str): """Soft delete (set activ=0).""" return update_mapping(sku, codmat, activ=0) def import_csv(file_content: str): """Import mappings from CSV content. Returns summary.""" if database.pool is None: raise HTTPException(status_code=503, detail="Oracle unavailable") reader = csv.DictReader(io.StringIO(file_content)) created = 0 updated = 0 errors = [] with database.pool.acquire() as conn: with conn.cursor() as cur: for i, row in enumerate(reader, 1): try: sku = row.get("sku", "").strip() codmat = row.get("codmat", "").strip() cantitate = float(row.get("cantitate_roa", "1") or "1") procent = float(row.get("procent_pret", "100") or "100") if not sku or not codmat: errors.append(f"Row {i}: missing sku or codmat") continue # Try update first, insert if not exists (MERGE) cur.execute(""" MERGE INTO ARTICOLE_TERTI t USING (SELECT :sku AS sku, :codmat AS codmat FROM DUAL) s ON (t.sku = s.sku AND t.codmat = s.codmat) WHEN MATCHED THEN UPDATE SET cantitate_roa = :cantitate_roa, procent_pret = :procent_pret, activ = 1, data_modif = SYSDATE WHEN NOT MATCHED THEN INSERT (sku, codmat, cantitate_roa, procent_pret, activ, data_creare, id_util_creare) VALUES (:sku, :codmat, :cantitate_roa, :procent_pret, 1, SYSDATE, -3) """, {"sku": sku, "codmat": codmat, "cantitate_roa": cantitate, "procent_pret": procent}) # Check if it was insert or update by rowcount created += 1 # We count total processed except Exception as e: errors.append(f"Row {i}: {str(e)}") conn.commit() return {"processed": created, "errors": errors} def export_csv(): """Export all mappings as CSV string.""" if database.pool is None: raise HTTPException(status_code=503, detail="Oracle unavailable") output = io.StringIO() writer = csv.writer(output) writer.writerow(["sku", "codmat", "cantitate_roa", "procent_pret", "activ"]) with database.pool.acquire() as conn: with conn.cursor() as cur: cur.execute(""" SELECT sku, codmat, cantitate_roa, procent_pret, activ FROM ARTICOLE_TERTI ORDER BY sku, codmat """) for row in cur: writer.writerow(row) return output.getvalue() def get_csv_template(): """Return empty CSV template.""" output = io.StringIO() writer = csv.writer(output) writer.writerow(["sku", "codmat", "cantitate_roa", "procent_pret"]) writer.writerow(["EXAMPLE_SKU", "EXAMPLE_CODMAT", "1", "100"]) return output.getvalue()