SC Yazılım olarak, 10 yılı aşkın süredir işletmelere dijital pazarlama, SEO ve marka yönetimi alanlarında sonuç odaklı çözümler sunuyoruz.

LATEST NEWS
iletişim
— # app/main.py import os from datetime import date, datetime, timedelta from fastapi import FastAPI, Query, Request from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse from fastapi.templating import Jinja2Templates from dotenv import load_dotenv from openpyxl import Workbook from openpyxl.utils import get_column_letter from app.db import fetch_all, fetch_one load_dotenv() app = FastAPI(title=”Panel Rapor”, version=”1.0.0″) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEMPLATES_DIR = os.path.join(BASE_DIR, “templates”) templates = Jinja2Templates(directory=TEMPLATES_DIR) def _date_start_dt(d: date) -> datetime: return datetime(d.year, d.month, d.day, 0, 0, 0) def _date_end_dt_exclusive(d: date) -> datetime: return datetime(d.year, d.month, d.day, 0, 0, 0) + timedelta(days=1) def _first_day_of_month(dt: datetime) -> datetime: return datetime(dt.year, dt.month, 1, 0, 0, 0) def _add_months(dt: datetime, months: int) -> datetime: y = dt.year + (dt.month – 1 + months) // 12 m = (dt.month – 1 + months) % 12 + 1 return datetime(y, m, 1, 0, 0, 0) # ========================= # ✅ YOUR INNER SELECT (RAPOR 1) # ========================= BASE_INNER_SQL = “”” SELECT ( SELECT MAX(TO_NUMBER(pv.VALUETEXTTUR)) FROM CONTENTCC_C_PROPERTYVALUE pv JOIN CONTENTCC_C_TPROPERTYVALUE tpv ON pv.PROPERTYVALUEID = tpv.PROPERTYVALUEID WHERE pv.PROPERTYID = 19 AND tpv.TITLEID = tl.TITLEID ) AS HEDEFKODU, (pl.NAME || ‘,’ || adr.DISTRICTNAME || ‘,’ || adr.CITYNAME) AS MUSTERI, ppb.DATECREATED, srv.TIMESTARTS AS SERVICETIMESTARTS, ppb.DATEMODIFIED, ( SELECT InvoiceCode FROM ( SELECT i.Code AS InvoiceCode, ROW_NUMBER() OVER (ORDER BY ip.InvoiceID DESC) rn FROM Finance_InvoicePackage ip JOIN Finance_Invoice i ON i.InvoiceID = ip.InvoiceID WHERE ip.TradingPackageID = ppb.PackageID ) WHERE rn = 1 ) AS INVOICECODE, CASE ppb.DELIVERYVALIDATIONTYPE WHEN 1 THEN ‘Kart Okutuldu’ WHEN 0 THEN ‘Kart Okutulmadı’ ELSE NULL END AS DELIVERYVALIDATIONTYPE_TEXT, ppb.BARCODE, CASE ppb.SHIPPINGSTATUS WHEN 0 THEN ‘Sevk Bekliyor’ WHEN 1 THEN ‘Sevk Kontrolü Yapıldı’ WHEN 2 THEN ‘Sevk Başladı’ WHEN 3 THEN ‘Transfer Edildi’ WHEN 4 THEN ‘Teslim Edildi’ WHEN 5 THEN ‘Teslim Edilemedi’ WHEN 99 THEN ‘İptal Edildi’ ELSE NULL END AS SHIPPINGSTATUS_TEXT, CASE srv.ISEXPRESSSERVICE WHEN 1 THEN ‘Hızlı Teslimat’ WHEN 0 THEN ‘Servis’ ELSE NULL END AS SERVICETYPE_TEXT, reg.NAMETUR AS PACKAGEREGIONNAMETUR, srv.NAMETUR AS SERVICENAMETUR, wh.NAMETUR AS PACKAGEWAREHOUSENAMETUR, dep.NAMETUR AS DEPARTUREMANAGERNAMETUR, arr.NAMETUR AS ARRIVALMANAGERNAMETUR, drv.NAME AS SERVICEDRIVERNAME, veh.PLATE AS SERVICEVEHICLEPLATE, ( SELECT DeliveredPoint FROM ( SELECT sp.DeliveredPoint, ROW_NUMBER() OVER (ORDER BY sp.DeliveredTime DESC) rn FROM Transportation_ServicePackage sp WHERE sp.PackagePackBarcodeID = ppb.PackagePackBarcodeID AND sp.IsDelivered = 1 ) WHERE rn = 1 ) AS DELIVEREDLOCATION FROM TRS_PACKAGEPACKBARCODE ppb JOIN TRANSPORTATION_PACKAGE pkg ON pkg.PACKAGEID = ppb.PACKAGEID LEFT JOIN TRANSPORTATION_REGION reg ON reg.REGIONID = pkg.REGIONID LEFT JOIN TRANSPORTATION_SERVICE srv ON srv.SERVICEID = ppb.SERVICEID LEFT JOIN TRANSPORTATION_VEHICLE veh ON veh.VEHICLEID = srv.VEHICLEID LEFT JOIN COLLABORATION_NODE wh ON wh.NODEID = pkg.WAREHOUSEID LEFT JOIN COLLABORATION_NODE dep ON dep.NODEID = ppb.DEPARTUREMANAGERID LEFT JOIN COLLABORATION_NODE arr ON arr.NODEID = ppb.ARRIVALMANAGERID LEFT JOIN COLLABORATION_EMPLOYEE drv ON drv.EMPLOYEEID = srv.DRIVERID LEFT JOIN TRADING_LINK pl ON pl.LINKID = pkg.LINKID INNER JOIN TRADING_LINK tl ON tl.LINKID = pkg.LINKID LEFT JOIN CONTACTCC_CONTACT c ON c.CONTACTID = pl.CONTACTID LEFT JOIN CONTACTCC_ADDRESS adr ON adr.ADDRESSID = c.DEFAULTADDRESSID WHERE pl.NAME NOT LIKE ‘%All%’ AND reg.NAMETUR IS NOT NULL AND reg.NAMETUR <> ‘NULL’ AND ppb.DATECREATED >= :start_date AND ppb.DATECREATED < :end_date """ def filtered_outer_sql(with_order: bool) -> str: where = “”” WHERE 1=1 AND (:barcode IS NULL OR LOWER(t.barcode) LIKE LOWER(:barcode_like)) AND (:invoice IS NULL OR NVL(LOWER(t.invoicecode),”) = LOWER(:invoice)) AND (:status IS NULL OR t.shippingstatus_text = :status) AND (:driver IS NULL OR LOWER(NVL(t.servicedrivername,”)) LIKE LOWER(:driver_like)) AND (:region IS NULL OR LOWER(NVL(t.packageregionnametur,”)) LIKE LOWER(:region_like)) AND (:warehouse IS NULL OR LOWER(NVL(t.packagewarehousenametur,”)) LIKE LOWER(:warehouse_like)) AND (:departure IS NULL OR LOWER(NVL(t.departuremanagernametur,”)) LIKE LOWER(:departure_like)) AND (:card IS NULL OR t.deliveryvalidationtype_text = :card) AND (:service IS NULL OR LOWER(NVL(t.servicenametur,”)) LIKE LOWER(:service_like)) “”” order = ” ORDER BY t.datecreated DESC ” if with_order else “” return f””” SELECT * FROM ( {BASE_INNER_SQL} ) t {where} {order} “”” def count_sql() -> str: return f”SELECT COUNT(1) AS total FROM ( {filtered_outer_sql(with_order=False)} )” def paged_sql() -> str: return f””” SELECT * FROM ( {filtered_outer_sql(with_order=True)} ) OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY “”” @app.get(“/”, response_class=HTMLResponse) @app.get(“/rapor”, response_class=HTMLResponse) def rapor_page(request: Request): return templates.TemplateResponse(“rapor.html”, {“request”: request}) # ✅ NEW PAGE: RAPOR 2 @app.get(“/rapor2”, response_class=HTMLResponse) def rapor2_page(request: Request): return templates.TemplateResponse(“rapor2.html”, {“request”: request}) @app.get(“/api/health”) def health(): try: row = fetch_one(“SELECT 1 AS ok FROM dual”) return {“db”: “ok”, “ok”: row.get(“ok”, 1) if row else 1} except Exception as e: return JSONResponse(status_code=500, content={“db”: “fail”, “error”: str(e)}) def build_params( start: date, end: date, barcode: str | None, invoice: str | None, status: str | None, driver: str | None, region: str | None, warehouse: str | None, departure: str | None, card: str | None, service: str | None, ) -> dict: start_dt = _date_start_dt(start) end_dt = _date_end_dt_exclusive(end) barcode = (barcode or “”).strip() or None invoice = (invoice or “”).strip() or None status = (status or “”).strip() or None driver = (driver or “”).strip() or None region = (region or “”).strip() or None warehouse = (warehouse or “”).strip() or None departure = (departure or “”).strip() or None card = (card or “”).strip() or None service = (service or “”).strip() or None return { “start_date”: start_dt, “end_date”: end_dt, “barcode”: barcode, “barcode_like”: f”{barcode}%” if barcode else None, “invoice”: invoice, “status”: status, “driver”: driver, “driver_like”: f”%{driver}%” if driver else None, “region”: region, “region_like”: f”%{region}%” if region else None, “warehouse”: warehouse, “warehouse_like”: f”%{warehouse}%” if warehouse else None, “departure”: departure, “departure_like”: f”%{departure}%” if departure else None, “card”: card, “service”: service, “service_like”: f”%{service}%” if service else None, } @app.get(“/api/rapor”) def rapor_data( start: date = Query(default_factory=date.today), end: date = Query(default_factory=date.today), barcode: str | None = None, invoice: str | None = None, status: str | None = None, driver: str | None = None, region: str | None = None, warehouse: str | None = None, departure: str | None = None, card: str | None = None, service: str | None = None, page: int = 1, page_size: int = 50, ): try: if page < 1: page = 1 if page_size < 1: page_size = 50 if page_size > 500: page_size = 500 params = build_params(start, end, barcode, invoice, status, driver, region, warehouse, departure, card, service) total_row = fetch_one(count_sql(), params) total = int(total_row[“total”]) if total_row and total_row.get(“total”) is not None else 0 params_items = dict(params) params_items[“offset”] = (page – 1) * page_size params_items[“limit”] = page_size rows = fetch_all(paged_sql(), params_items) kpi_sql = f””” SELECT shippingstatus_text AS durum, COUNT(1) AS adet FROM ( {filtered_outer_sql(with_order=False)} ) GROUP BY shippingstatus_text ORDER BY COUNT(1) DESC “”” kpi_rows = fetch_all(kpi_sql, params) kpis = [] for r in kpi_rows: adet = int(r.get(“adet”) or 0) yuzde = round((adet / total) * 100, 2) if total else 0 kpis.append({“durum”: r.get(“durum”), “adet”: adet, “yuzde”: yuzde}) return { “range”: {“start”: str(start), “end”: str(end)}, “page”: page, “page_size”: page_size, “total”: total, “kpis”: kpis, “rows”: rows, } except Exception as e: return JSONResponse(status_code=500, content={“error”: str(e)}) @app.get(“/api/rapor/group”) def rapor_group( start: date = Query(default_factory=date.today), end: date = Query(default_factory=date.today), barcode: str | None = None, invoice: str | None = None, status: str | None = None, driver: str | None = None, region: str | None = None, warehouse: str | None = None, departure: str | None = None, card: str | None = None, service: str | None = None, group_by: str = “service”, ): try: params = build_params(start, end, barcode, invoice, status, driver, region, warehouse, departure, card, service) group_map = { “service”: “t.servicenametur”, “customer”: “t.musteri”, “region”: “t.packageregionnametur”, “warehouse”: “t.packagewarehousenametur”, “departure”: “t.departuremanagernametur”, “card”: “t.deliveryvalidationtype_text”, “status”: “t.shippingstatus_text”, “driver”: “t.servicedrivername”, } col = group_map.get(group_by, “t.servicenametur”) sql = f””” SELECT NVL({col}, ‘-‘) AS label, COUNT(1) AS adet FROM ( {filtered_outer_sql(with_order=False)} ) t GROUP BY {col} ORDER BY COUNT(1) DESC “”” items_raw = fetch_all(sql, params) total = sum(int(x.get(“adet”) or 0) for x in items_raw) or 0 items = [] for x in items_raw[:200]: adet = int(x.get(“adet”) or 0) yuzde = round((adet / total) * 100, 2) if total else 0 items.append({“label”: x.get(“label”), “adet”: adet, “yuzde”: yuzde}) return {“group_by”: group_by, “total”: total, “items”: items} except Exception as e: return JSONResponse(status_code=500, content={“error”: str(e)}) # ============================================================ # ✅ TREND KPI (unchanged from your “updated” version) # ============================================================ @app.get(“/api/rapor/trend-kpi”) def rapor_trend_kpi( months: int = Query(default=6, ge=1, le=24), start: date | None = None, end: date | None = None, ): try: if start and end: use_start = _date_start_dt(start) use_end = _date_end_dt_exclusive(end) else: now = datetime.now() cur_month = _first_day_of_month(now) start_month = _add_months(cur_month, -(months – 1)) end_month_excl = _add_months(cur_month, 1) use_start = start_month use_end = end_month_excl sql = “”” SELECT TO_CHAR(TRUNC(ppb.DATECREATED, ‘MM’), ‘YYYY-MM’) AS ay, SUM(CASE WHEN ppb.SHIPPINGSTATUS = 4 THEN 1 ELSE 0 END) AS teslim_edildi, SUM(CASE WHEN ppb.SHIPPINGSTATUS = 0 THEN 1 ELSE 0 END) AS sevk_bekliyor, SUM(CASE WHEN ppb.DELIVERYVALIDATIONTYPE = 0 THEN 1 ELSE 0 END) AS kart_okutulmadi FROM TRS_PACKAGEPACKBARCODE ppb WHERE ppb.DATECREATED >= :start_dt AND ppb.DATECREATED < :end_dt GROUP BY TRUNC(ppb.DATECREATED, 'MM') ORDER BY TRUNC(ppb.DATECREATED, 'MM') """ rows = fetch_all(sql, {"start_dt": use_start, "end_dt": use_end}) def pct(curr: int, prev: int): if prev == 0: return None return round(((curr - prev) / prev) * 100, 2) out = [] prev = None for r in rows: cur = { "ay": r.get("ay"), "teslim_edildi": int(r.get("teslim_edildi") or 0), "sevk_bekliyor": int(r.get("sevk_bekliyor") or 0), "kart_okutulmadi": int(r.get("kart_okutulmadi") or 0), } if prev: cur["degisim"] = { "teslim_edildi_pct": pct(cur["teslim_edildi"], prev["teslim_edildi"]), "sevk_bekliyor_pct": pct(cur["sevk_bekliyor"], prev["sevk_bekliyor"]), "kart_okutulmadi_pct": pct(cur["kart_okutulmadi"], prev["kart_okutulmadi"]), } else: cur["degisim"] = { "teslim_edildi_pct": None, "sevk_bekliyor_pct": None, "kart_okutulmadi_pct": None, } out.append(cur) prev = cur return {"months": out} except Exception as e: return JSONResponse(status_code=500, content={"error": str(e)}) @app.get("/api/trend-kpi") def trend_kpi_alias( months: int = Query(default=6, ge=1, le=24), start: date | None = None, end: date | None = None, ): return rapor_trend_kpi(months=months, start=start, end=end) def _make_xlsx(headers: list[str], rows: list[dict], sheet_name: str = "Rapor") -> bytes: wb = Workbook() ws = wb.active ws.title = sheet_name ws.append(headers) for r in rows: ws.append([r.get(h) for h in headers]) for i, h in enumerate(headers, start=1): max_len = len(h) for rr in rows[:300]: val = rr.get(h) if val is None: continue s = str(val) if len(s) > max_len: max_len = len(s) ws.column_dimensions[get_column_letter(i)].width = max(12, min(48, max_len + 2)) import io bio = io.BytesIO() wb.save(bio) return bio.getvalue() @app.get(“/api/rapor/export.xlsx”) def export_xlsx( start: date = Query(default_factory=date.today), end: date = Query(default_factory=date.today), barcode: str | None = None, invoice: str | None = None, status: str | None = None, driver: str | None = None, region: str | None = None, warehouse: str | None = None, departure: str | None = None, card: str | None = None, service: str | None = None, ): try: params = build_params(start, end, barcode, invoice, status, driver, region, warehouse, departure, card, service) sql = filtered_outer_sql(with_order=True) rows = fetch_all(sql, params) headers = [ “datecreated”, “hedefkodu”, “musteri”, “barcode”, “invoicecode”, “shippingstatus_text”, “servicetype_text”, “packageregionnametur”, “servicenametur”, “packagewarehousenametur”, “departuremanagernametur”, “arrivalmanagernametur”, “servicedrivername”, “servicevehicleplate”, “deliveredlocation”, “deliveryvalidationtype_text”, “servicetimestarts”, “datemodified”, ] data = _make_xlsx(headers, rows, sheet_name=”Sevkiyat”) filename = f”sevkiyat_{start}_{end}.xlsx” return StreamingResponse( iter([data]), media_type=”application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”, headers={“Content-Disposition”: f’attachment; filename=”{filename}”‘}, ) except Exception as e: return JSONResponse(status_code=500, content={“error”: str(e)}) @app.get(“/api/export.xlsx”) def export_xlsx_alias( start: date = Query(default_factory=date.today), end: date = Query(default_factory=date.today), barcode: str | None = None, invoice: str | None = None, status: str | None = None, driver: str | None = None, region: str | None = None, warehouse: str | None = None, departure: str | None = None, card: str | None = None, service: str | None = None, ): return export_xlsx( start=start, end=end, barcode=barcode, invoice=invoice, status=status, driver=driver, region=region, warehouse=warehouse, departure=departure, card=card, service=service ) # ============================================================ # ✅ RAPOR 2: “Lokasyonu Tanımsız Ürünler” # – /rapor2 -> rapor2.html # – /api/rapor2 -> JSON # – /api/rapor2/export.xlsx -> Excel # ============================================================ RAPOR2_BASE_SQL = “”” SELECT CI.ITEMID AS urunkodu, CI.NAMETUR AS urunadi, CN.NAMETUR AS depo, LOK.DISPLAYNAME AS lokasyon, SUM(SP.QUANTITY) AS stok, CC.NAMETUR AS baskin_sinif FROM CATALOG_ITEM CI INNER JOIN STOCK_PARTY SP ON CI.ITEMID = SP.ITEMID LEFT JOIN CATALOG_CLASS CC ON CI.DOMINANTCLASSID = CC.CLASSID INNER JOIN COLLABORATION_NODE CN ON SP.WAREHOUSEID = CN.NODEID LEFT JOIN ( SELECT DISTINCT CII.ITEMID, SC.WAREHOUSEID, SC.DISPLAYNAME FROM CATALOG_INVENTORYITEM CII INNER JOIN STOCK_STOCKINVENTORYITEM SSII ON CII.INVENTORYITEMID = SSII.INVENTORYITEMID INNER JOIN STOCK_CONTAINER SC ON SSII.EXITCONTAINERID = SC.CONTAINERID ) LOK ON CI.ITEMID = LOK.ITEMID AND SP.WAREHOUSEID = LOK.WAREHOUSEID WHERE LOK.ITEMID IS NULL GROUP BY CI.ITEMID, CI.NAMETUR, CN.NAMETUR, LOK.DISPLAYNAME, CC.NAMETUR HAVING (SUM(SP.QUANTITY) > 0) “”” def rapor2_filtered_sql(with_order: bool) -> str: where = “”” WHERE 1=1 AND (:urunkodu IS NULL OR LOWER(t.urunkodu) LIKE LOWER(:urunkodu_like)) AND (:urunadi IS NULL OR LOWER(t.urunadi) LIKE LOWER(:urunadi_like)) AND (:depo IS NULL OR LOWER(t.depo) LIKE LOWER(:depo_like)) AND (:baskin_sinif IS NULL OR LOWER(NVL(t.baskin_sinif,”)) LIKE LOWER(:baskin_sinif_like)) “”” order = ” ORDER BY t.stok DESC ” if with_order else “” return f””” SELECT * FROM ( {RAPOR2_BASE_SQL} ) t {where} {order} “”” def rapor2_count_sql() -> str: return f”SELECT COUNT(1) AS total FROM ( {rapor2_filtered_sql(with_order=False)} )” def rapor2_paged_sql() -> str: return f””” SELECT * FROM ( {rapor2_filtered_sql(with_order=True)} ) OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY “”” def rapor2_params( urunkodu: str | None, urunadi: str | None, depo: str | None, baskin_sinif: str | None, ) -> dict: urunkodu = (urunkodu or “”).strip() or None urunadi = (urunadi or “”).strip() or None depo = (depo or “”).strip() or None baskin_sinif = (baskin_sinif or “”).strip() or None return { “urunkodu”: urunkodu, “urunkodu_like”: f”{urunkodu}%” if urunkodu else None, “urunadi”: urunadi, “urunadi_like”: f”%{urunadi}%” if urunadi else None, “depo”: depo, “depo_like”: f”%{depo}%” if depo else None, “baskin_sinif”: baskin_sinif, “baskin_sinif_like”: f”%{baskin_sinif}%” if baskin_sinif else None, } @app.get(“/api/rapor2”) def rapor2_data( urunkodu: str | None = None, urunadi: str | None = None, depo: str | None = None, baskin_sinif: str | None = None, page: int = 1, page_size: int = 50, ): try: if page < 1: page = 1 if page_size < 1: page_size = 50 if page_size > 500: page_size = 500 params = rapor2_params(urunkodu, urunadi, depo, baskin_sinif) total_row = fetch_one(rapor2_count_sql(), params) total = int(total_row[“total”]) if total_row and total_row.get(“total”) is not None else 0 params_items = dict(params) params_items[“offset”] = (page – 1) * page_size params_items[“limit”] = page_size rows = fetch_all(rapor2_paged_sql(), params_items) return { “page”: page, “page_size”: page_size, “total”: total, “rows”: rows, } except Exception as e: return JSONResponse(status_code=500, content={“error”: str(e)}) @app.get(“/api/rapor2/export.xlsx”) def rapor2_export_xlsx( urunkodu: str | None = None, urunadi: str | None = None, depo: str | None = None, baskin_sinif: str | None = None, ): try: params = rapor2_params(urunkodu, urunadi, depo, baskin_sinif) rows = fetch_all(rapor2_filtered_sql(with_order=True), params) headers = [“urunkodu”, “urunadi”, “depo”, “lokasyon”, “stok”, “baskin_sinif”] data = _make_xlsx(headers, rows, sheet_name=”Lokasyonsuz_Urunler”) filename = f”lokasyonsuz_urunler_{date.today()}.xlsx” return StreamingResponse( iter([data]), media_type=”application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”, headers={“Content-Disposition”: f’attachment; filename=”{filename}”‘}, ) except Exception as e: return JSONResponse(status_code=500, content={“error”: str(e)}) base html {% extends “base.html” %} {% block title %}Sevkiyat Raporu{% endblock %} {% block page_title %}Sevkiyat Raporu{% endblock %} {% block nav_rapor_active %}active{% endblock %} {% block head_extra %} {% endblock %} {% block content %}