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)
|
||||
|
||||
# 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.freeze_panes = "A2"
|
||||
@@ -406,22 +407,35 @@ def crm_stats(klub_id: Optional[int] = Query(None)):
|
||||
class NotifScanIn(BaseModel):
|
||||
klub_id: Optional[int] = None
|
||||
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")
|
||||
def lijecnicki_notify_scan(body: NotifScanIn):
|
||||
"""
|
||||
Skenira nadolazeće isteke i kreira notifikacije (InApp + Email mock)
|
||||
za pragove 30/15/7 dana. Ne duplicira: gleda meta.lijecnicki_id+threshold
|
||||
u zadnjih 7 dana.
|
||||
za pragove 30/15/7 dana. Ako include_expired=True, isto kreira jednu
|
||||
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)
|
||||
klub_filter = "AND l.klub_id = %s" 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 = []
|
||||
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"""
|
||||
SELECT l.id, l.vrijedi_do, l.clan_id,
|
||||
(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.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
|
||||
AND (l.vrijedi_do - CURRENT_DATE) > %s
|
||||
AND {where_window}
|
||||
{klub_filter}
|
||||
""", [thr, thr - 1] + klub_params if False else
|
||||
([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)
|
||||
""", where_params + klub_params)
|
||||
kandidati = [_row(r) for r in cur.fetchall()]
|
||||
|
||||
for r in kandidati:
|
||||
@@ -467,11 +463,16 @@ def lijecnicki_notify_scan(body: NotifScanIn):
|
||||
if cur.fetchone():
|
||||
continue
|
||||
|
||||
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 = (
|
||||
f"Liječnički pregled za sportaša {r['clan']} "
|
||||
f"({r.get('klub') or '(bez kluba)'}) ističe {r['vrijedi_do']} "
|
||||
f"— {r['dana']} dana ostalo.\n\n"
|
||||
f"({r.get('klub') or '(bez kluba)'}) — vrijedi do {r['vrijedi_do']} "
|
||||
f"— {msg_dana}.\n\n"
|
||||
f"Molimo zakažite novi termin u ZZJZ PGŽ "
|
||||
f"(ili koristite /sport/api/crm/lijecnicki/{r['id']}/zakazi).\n\n"
|
||||
f"PGŽ Sport ERP/CRM"
|
||||
|
||||
Reference in New Issue
Block a user