""" check_callback_edit.py -- offline: a callback date/time can be set from the Edit form, and it reads back as the exact wall-clock time that was typed (user, 2026-09-24). uv run python scripts/check_callback_edit.py No database, no network -- throwaway in-memory SQLite. Covers: * a Callback Scheduled recorded WITHOUT a time (what the old production button did) leaves callback_at empty * UpdateLeadInput now accepts callback_at, and update_lead() saves it * LeadOut serializes it as naive wall-clock ISO (no "Z"/offset) -- it used to go through _dt_utc, which made the browser shift a typed 3:00 PM to 8:00 PM in PKT * an unrelated edit does NOT clear an existing callback_at (set-only, like every field on that model) """ from __future__ import annotations import sys from datetime import datetime from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from sqlalchemy import create_engine # noqa: E402 from sqlalchemy.orm import sessionmaker # noqa: E402 from sqlalchemy.pool import StaticPool # noqa: E402 import app.models # noqa: E402,F401 from app.crud import leads as crud_leads # noqa: E402 from app.database import Base # noqa: E402 from app.schemas.lead import LeadOut, UpdateLeadInput # noqa: E402 FAILED = 0 def check(label: str, ok: bool, detail: str = "") -> None: global FAILED FAILED += 0 if ok else 1 print(f" [{'ok ' if ok else 'FAIL'}] {label}" + ("" if ok else f" {detail}")) def main() -> int: engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) Base.metadata.create_all(engine) db = sessionmaker(bind=engine)() lead = crud_leads.add_lead_dict(db, {"company_name": "Callback Co"}) crud_leads.apply_call_outcome(db, lead, "callback_scheduled") # no callback_at, like the old button print("\nCallback recorded without a time") check("callback_at is empty", lead.callback_at is None) check("last_call_outcome is callback_scheduled", lead.last_call_outcome == "callback_scheduled") print("\nSet a time from the Edit form (UpdateLeadInput.callback_at)") typed = datetime(2026, 9, 25, 15, 0, 0) lead = crud_leads.update_lead(db, lead, UpdateLeadInput(callback_at=typed)) check("callback_at saved as typed", lead.callback_at == typed, str(lead.callback_at)) out = LeadOut.from_model(lead) check("LeadOut.callback_at is naive wall-clock ISO", out.callback_at == "2026-09-25T15:00:00", out.callback_at) check("no Z / UTC offset on it", "Z" not in out.callback_at and "+" not in out.callback_at) check("stage untouched by setting it", lead.pipeline_stage == "followup_due", lead.pipeline_stage) print("\nUnrelated edit leaves the callback alone") lead = crud_leads.update_lead(db, lead, UpdateLeadInput(subject="New subject")) check("callback_at still set", lead.callback_at == typed, str(lead.callback_at)) print(f"\n{'FAILED: ' + str(FAILED) if FAILED else 'All checks passed.'}") return 1 if FAILED else 0 if __name__ == "__main__": sys.exit(main())