CC5 R5: fix bulk-uplatnice + xlsx + notify-scan extended (incl. expired)
- /api/crm/clanovi/export.xlsx: fix col_letters list construction (str+list bug)
- /api/crm/lijecnicki/notify-scan: dodan include_expired=True bucket, jasniji
subject za already-expired vs uskoro istek
CC2 commit 0046b8d je već unio crm_extras_router.py na master; ovaj commit
samo sređuje bugove i extends scan logiku.
This commit is contained in:
@@ -275,7 +275,8 @@ def export_clanovi_xlsx(
|
|||||||
ws.cell(row=ridx, column=cidx, value=v)
|
ws.cell(row=ridx, column=cidx, value=v)
|
||||||
|
|
||||||
# Auto column widths
|
# Auto column widths
|
||||||
for col_letter, h in zip("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "AA AB AC AD".split(), headers):
|
col_letters = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + ["AA", "AB", "AC", "AD", "AE", "AF"]
|
||||||
|
for col_letter, h in zip(col_letters, headers):
|
||||||
ws.column_dimensions[col_letter].width = max(10, min(28, len(h) + 4))
|
ws.column_dimensions[col_letter].width = max(10, min(28, len(h) + 4))
|
||||||
|
|
||||||
ws.freeze_panes = "A2"
|
ws.freeze_panes = "A2"
|
||||||
@@ -406,22 +407,35 @@ def crm_stats(klub_id: Optional[int] = Query(None)):
|
|||||||
class NotifScanIn(BaseModel):
|
class NotifScanIn(BaseModel):
|
||||||
klub_id: Optional[int] = None
|
klub_id: Optional[int] = None
|
||||||
thresholds: Optional[list[int]] = None # default = LIJEC_THRESHOLDS
|
thresholds: Optional[list[int]] = None # default = LIJEC_THRESHOLDS
|
||||||
|
include_expired: bool = True # uključi i one koji su već istekli
|
||||||
|
|
||||||
|
|
||||||
@router.post("/lijecnicki/notify-scan")
|
@router.post("/lijecnicki/notify-scan")
|
||||||
def lijecnicki_notify_scan(body: NotifScanIn):
|
def lijecnicki_notify_scan(body: NotifScanIn):
|
||||||
"""
|
"""
|
||||||
Skenira nadolazeće isteke i kreira notifikacije (InApp + Email mock)
|
Skenira nadolazeće isteke i kreira notifikacije (InApp + Email mock)
|
||||||
za pragove 30/15/7 dana. Ne duplicira: gleda meta.lijecnicki_id+threshold
|
za pragove 30/15/7 dana. Ako include_expired=True, isto kreira jednu
|
||||||
u zadnjih 7 dana.
|
notifikaciju (threshold=0) za već istekle.
|
||||||
|
Ne duplicira: gleda meta.lijecnicki_id+threshold u zadnjih 7 dana.
|
||||||
"""
|
"""
|
||||||
thresholds = sorted(set(body.thresholds or LIJEC_THRESHOLDS), reverse=True)
|
thresholds = sorted(set(body.thresholds or LIJEC_THRESHOLDS), reverse=True)
|
||||||
klub_filter = "AND l.klub_id = %s" if body.klub_id else ""
|
klub_filter = "AND l.klub_id = %s" if body.klub_id else ""
|
||||||
klub_params = [body.klub_id] if body.klub_id else []
|
klub_params = [body.klub_id] if body.klub_id else []
|
||||||
|
|
||||||
|
# threshold=0 → već istekli (poseban "expired" bucket)
|
||||||
|
scan_buckets = [(thr, "uskoro") for thr in thresholds]
|
||||||
|
if body.include_expired:
|
||||||
|
scan_buckets.append((0, "expired"))
|
||||||
|
|
||||||
created = []
|
created = []
|
||||||
with _conn() as conn, conn.cursor() as cur:
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
for thr in thresholds:
|
for thr, kind in scan_buckets:
|
||||||
|
if kind == "expired":
|
||||||
|
where_window = "(l.vrijedi_do - CURRENT_DATE) < 0"
|
||||||
|
where_params = []
|
||||||
|
else:
|
||||||
|
where_window = "(l.vrijedi_do - CURRENT_DATE) BETWEEN 0 AND %s"
|
||||||
|
where_params = [thr]
|
||||||
cur.execute(f"""
|
cur.execute(f"""
|
||||||
SELECT l.id, l.vrijedi_do, l.clan_id,
|
SELECT l.id, l.vrijedi_do, l.clan_id,
|
||||||
(l.vrijedi_do - CURRENT_DATE)::int AS dana,
|
(l.vrijedi_do - CURRENT_DATE)::int AS dana,
|
||||||
@@ -432,27 +446,9 @@ def lijecnicki_notify_scan(body: NotifScanIn):
|
|||||||
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
||||||
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
||||||
WHERE l.vrijedi_do IS NOT NULL
|
WHERE l.vrijedi_do IS NOT NULL
|
||||||
AND (l.vrijedi_do - CURRENT_DATE) BETWEEN 0 AND %s
|
AND {where_window}
|
||||||
AND (l.vrijedi_do - CURRENT_DATE) > %s
|
|
||||||
{klub_filter}
|
{klub_filter}
|
||||||
""", [thr, thr - 1] + klub_params if False else
|
""", where_params + klub_params)
|
||||||
([thr - (thresholds[thresholds.index(thr)+1] if thresholds.index(thr)+1 < len(thresholds) else 0),
|
|
||||||
-1] + klub_params))
|
|
||||||
# Pojednostavljen scan: samo "≤ thr & > prev_thr" dovodi do duplika;
|
|
||||||
# umjesto toga samo gledamo "u prozoru ≤ thr".
|
|
||||||
cur.execute(f"""
|
|
||||||
SELECT l.id, l.vrijedi_do, l.clan_id,
|
|
||||||
(l.vrijedi_do - CURRENT_DATE)::int AS dana,
|
|
||||||
cl.ime || ' ' || cl.prezime AS clan,
|
|
||||||
cl.email AS clan_email,
|
|
||||||
k.naziv AS klub
|
|
||||||
FROM pgz_sport.lijecnicki_pregledi l
|
|
||||||
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
|
||||||
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
|
||||||
WHERE l.vrijedi_do IS NOT NULL
|
|
||||||
AND (l.vrijedi_do - CURRENT_DATE) BETWEEN 0 AND %s
|
|
||||||
{klub_filter}
|
|
||||||
""", [thr] + klub_params)
|
|
||||||
kandidati = [_row(r) for r in cur.fetchall()]
|
kandidati = [_row(r) for r in cur.fetchall()]
|
||||||
|
|
||||||
for r in kandidati:
|
for r in kandidati:
|
||||||
@@ -467,11 +463,16 @@ def lijecnicki_notify_scan(body: NotifScanIn):
|
|||||||
if cur.fetchone():
|
if cur.fetchone():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
subject = f"⚕ Liječnički pregled ističe za {r['dana']} dana: {r['clan']}"
|
if r['dana'] is not None and r['dana'] < 0:
|
||||||
|
subject = f"⚠ Liječnički pregled ISTEKAO ({-r['dana']} dana): {r['clan']}"
|
||||||
|
msg_dana = f"istekao prije {-r['dana']} dana"
|
||||||
|
else:
|
||||||
|
subject = f"⚕ Liječnički pregled ističe za {r['dana']} dana: {r['clan']}"
|
||||||
|
msg_dana = f"{r['dana']} dana ostalo"
|
||||||
body_txt = (
|
body_txt = (
|
||||||
f"Liječnički pregled za sportaša {r['clan']} "
|
f"Liječnički pregled za sportaša {r['clan']} "
|
||||||
f"({r.get('klub') or '(bez kluba)'}) ističe {r['vrijedi_do']} "
|
f"({r.get('klub') or '(bez kluba)'}) — vrijedi do {r['vrijedi_do']} "
|
||||||
f"— {r['dana']} dana ostalo.\n\n"
|
f"— {msg_dana}.\n\n"
|
||||||
f"Molimo zakažite novi termin u ZZJZ PGŽ "
|
f"Molimo zakažite novi termin u ZZJZ PGŽ "
|
||||||
f"(ili koristite /sport/api/crm/lijecnicki/{r['id']}/zakazi).\n\n"
|
f"(ili koristite /sport/api/crm/lijecnicki/{r['id']}/zakazi).\n\n"
|
||||||
f"PGŽ Sport ERP/CRM"
|
f"PGŽ Sport ERP/CRM"
|
||||||
|
|||||||
Reference in New Issue
Block a user