Customer 360: extended Client fields, auto-migrate, updated Rolodex CRUD/templates, QDRO routes/views, importer mapping
QDRO links appear in rolodex_view.html case rows and case.html header when QDRO data exists, matching legacy flows.
This commit is contained in:
@@ -105,6 +105,40 @@ def create_tables() -> None:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Lightweight migration: ensure new client columns exist (SQLite safe)
|
||||
try:
|
||||
inspector = inspect(engine)
|
||||
client_cols = {col['name'] for col in inspector.get_columns('clients')}
|
||||
client_required_sql = {
|
||||
'prefix': 'ALTER TABLE clients ADD COLUMN prefix VARCHAR(20)',
|
||||
'middle_name': 'ALTER TABLE clients ADD COLUMN middle_name VARCHAR(50)',
|
||||
'suffix': 'ALTER TABLE clients ADD COLUMN suffix VARCHAR(20)',
|
||||
'title': 'ALTER TABLE clients ADD COLUMN title VARCHAR(100)',
|
||||
'group': 'ALTER TABLE clients ADD COLUMN "group" VARCHAR(50)',
|
||||
'email': 'ALTER TABLE clients ADD COLUMN email VARCHAR(255)',
|
||||
'dob': 'ALTER TABLE clients ADD COLUMN dob DATE',
|
||||
'ssn': 'ALTER TABLE clients ADD COLUMN ssn VARCHAR(20)',
|
||||
'legal_status': 'ALTER TABLE clients ADD COLUMN legal_status VARCHAR(50)',
|
||||
'memo': 'ALTER TABLE clients ADD COLUMN memo TEXT'
|
||||
}
|
||||
client_alters = []
|
||||
for col_name, ddl in client_required_sql.items():
|
||||
if col_name not in client_cols:
|
||||
client_alters.append(ddl)
|
||||
if client_alters:
|
||||
with engine.begin() as conn:
|
||||
for ddl in client_alters:
|
||||
conn.execute(text(ddl))
|
||||
except Exception as e:
|
||||
try:
|
||||
from .logging_config import setup_logging
|
||||
import structlog
|
||||
setup_logging()
|
||||
_logger = structlog.get_logger(__name__)
|
||||
_logger.warning("sqlite_migration_clients_failed", error=str(e))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Seed default admin user after creating tables
|
||||
try:
|
||||
from .auth import seed_admin_user
|
||||
|
||||
149
app/main.py
149
app/main.py
@@ -29,7 +29,7 @@ import structlog
|
||||
from structlog import contextvars as structlog_contextvars
|
||||
|
||||
from .database import create_tables, get_db, get_database_url
|
||||
from .models import User, Case, Client, Phone, Transaction, Document, Payment, ImportLog
|
||||
from .models import User, Case, Client, Phone, Transaction, Document, Payment, ImportLog, Qdros
|
||||
from .auth import authenticate_user, get_current_user_from_session
|
||||
from .reporting import (
|
||||
build_phone_book_pdf,
|
||||
@@ -633,16 +633,37 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
result['errors'].append(f"Row {row_num}: Client with ID '{rolodex_id}' already exists")
|
||||
continue
|
||||
|
||||
# Parse DOB (YYYY-MM-DD or MM/DD/YY variants)
|
||||
dob_raw = row.get('DOB', '').strip()
|
||||
dob_val = None
|
||||
for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y"):
|
||||
if not dob_raw:
|
||||
break
|
||||
try:
|
||||
dob_val = datetime.strptime(dob_raw, fmt).date()
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
client = Client(
|
||||
rolodex_id=rolodex_id,
|
||||
prefix=row.get('Prefix', '').strip() or None,
|
||||
first_name=row.get('First', '').strip() or None,
|
||||
middle_initial=row.get('Middle', '').strip() or None,
|
||||
middle_name=row.get('Middle', '').strip() or None,
|
||||
last_name=row.get('Last', '').strip() or None,
|
||||
suffix=row.get('Suffix', '').strip() or None,
|
||||
title=row.get('Title', '').strip() or None,
|
||||
company=row.get('Title', '').strip() or None,
|
||||
address=row.get('A1', '').strip() or None,
|
||||
city=row.get('City', '').strip() or None,
|
||||
state=row.get('St', '').strip() or None,
|
||||
zip_code=row.get('Zip', '').strip() or None
|
||||
state=(row.get('Abrev', '').strip() or row.get('St', '').strip() or None),
|
||||
zip_code=row.get('Zip', '').strip() or None,
|
||||
group=row.get('Group', '').strip() or None,
|
||||
email=row.get('Email', '').strip() or None,
|
||||
dob=dob_val,
|
||||
ssn=row.get('SS#', '').strip() or None,
|
||||
legal_status=row.get('Legal_Status', '').strip() or None,
|
||||
memo=row.get('Memo', '').strip() or None,
|
||||
)
|
||||
|
||||
db.add(client)
|
||||
@@ -2298,6 +2319,9 @@ async def case_detail(
|
||||
|
||||
logger.info("case_detail", case_id=case_obj.id, file_no=case_obj.file_no)
|
||||
|
||||
# Determine if QDRO entries exist for this case's file number
|
||||
has_qdro = db.query(Qdros).filter(Qdros.file_no == case_obj.file_no).count() > 0
|
||||
|
||||
# Get any errors from session and clear them
|
||||
errors = request.session.pop("case_update_errors", None)
|
||||
|
||||
@@ -2322,6 +2346,7 @@ async def case_detail(
|
||||
"saved": saved,
|
||||
"errors": errors or [],
|
||||
"totals": totals,
|
||||
"has_qdro": has_qdro,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2658,13 +2683,23 @@ async def rolodex_view(client_id: int, request: Request, db: Session = Depends(g
|
||||
@app.post("/rolodex/create")
|
||||
async def rolodex_create(
|
||||
request: Request,
|
||||
prefix: str = Form(None),
|
||||
first_name: str = Form(None),
|
||||
middle_name: str = Form(None),
|
||||
last_name: str = Form(None),
|
||||
suffix: str = Form(None),
|
||||
title: str = Form(None),
|
||||
company: str = Form(None),
|
||||
address: str = Form(None),
|
||||
city: str = Form(None),
|
||||
state: str = Form(None),
|
||||
zip_code: str = Form(None),
|
||||
group: str = Form(None),
|
||||
email: str = Form(None),
|
||||
dob: str = Form(None),
|
||||
ssn: str = Form(None),
|
||||
legal_status: str = Form(None),
|
||||
memo: str = Form(None),
|
||||
rolodex_id: str = Form(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
@@ -2672,14 +2707,32 @@ async def rolodex_create(
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
# Parse date
|
||||
dob_dt = None
|
||||
if dob and dob.strip():
|
||||
try:
|
||||
dob_dt = datetime.strptime(dob.strip(), "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
dob_dt = None
|
||||
|
||||
client = Client(
|
||||
prefix=(prefix or "").strip() or None,
|
||||
first_name=(first_name or "").strip() or None,
|
||||
middle_name=(middle_name or "").strip() or None,
|
||||
last_name=(last_name or "").strip() or None,
|
||||
suffix=(suffix or "").strip() or None,
|
||||
title=(title or "").strip() or None,
|
||||
company=(company or "").strip() or None,
|
||||
address=(address or "").strip() or None,
|
||||
city=(city or "").strip() or None,
|
||||
state=(state or "").strip() or None,
|
||||
zip_code=(zip_code or "").strip() or None,
|
||||
group=(group or "").strip() or None,
|
||||
email=(email or "").strip() or None,
|
||||
dob=dob_dt,
|
||||
ssn=(ssn or "").strip() or None,
|
||||
legal_status=(legal_status or "").strip() or None,
|
||||
memo=(memo or "").strip() or None,
|
||||
rolodex_id=(rolodex_id or "").strip() or None,
|
||||
)
|
||||
db.add(client)
|
||||
@@ -2693,13 +2746,23 @@ async def rolodex_create(
|
||||
async def rolodex_update(
|
||||
client_id: int,
|
||||
request: Request,
|
||||
prefix: str = Form(None),
|
||||
first_name: str = Form(None),
|
||||
middle_name: str = Form(None),
|
||||
last_name: str = Form(None),
|
||||
suffix: str = Form(None),
|
||||
title: str = Form(None),
|
||||
company: str = Form(None),
|
||||
address: str = Form(None),
|
||||
city: str = Form(None),
|
||||
state: str = Form(None),
|
||||
zip_code: str = Form(None),
|
||||
group: str = Form(None),
|
||||
email: str = Form(None),
|
||||
dob: str = Form(None),
|
||||
ssn: str = Form(None),
|
||||
legal_status: str = Form(None),
|
||||
memo: str = Form(None),
|
||||
rolodex_id: str = Form(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
@@ -2711,13 +2774,27 @@ async def rolodex_update(
|
||||
if not client:
|
||||
raise HTTPException(status_code=404, detail="Client not found")
|
||||
|
||||
client.prefix = (prefix or "").strip() or None
|
||||
client.first_name = (first_name or "").strip() or None
|
||||
client.middle_name = (middle_name or "").strip() or None
|
||||
client.last_name = (last_name or "").strip() or None
|
||||
client.suffix = (suffix or "").strip() or None
|
||||
client.title = (title or "").strip() or None
|
||||
client.company = (company or "").strip() or None
|
||||
client.address = (address or "").strip() or None
|
||||
client.city = (city or "").strip() or None
|
||||
client.state = (state or "").strip() or None
|
||||
client.zip_code = (zip_code or "").strip() or None
|
||||
client.group = (group or "").strip() or None
|
||||
client.email = (email or "").strip() or None
|
||||
if dob and dob.strip():
|
||||
try:
|
||||
client.dob = datetime.strptime(dob.strip(), "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
pass
|
||||
client.ssn = (ssn or "").strip() or None
|
||||
client.legal_status = (legal_status or "").strip() or None
|
||||
client.memo = (memo or "").strip() or None
|
||||
client.rolodex_id = (rolodex_id or "").strip() or None
|
||||
|
||||
db.commit()
|
||||
@@ -3625,3 +3702,67 @@ async def api_list_ledger(
|
||||
items=items,
|
||||
pagination=Pagination(page=page, page_size=page_size, total=total, total_pages=total_pages),
|
||||
)
|
||||
|
||||
# ------------------------------
|
||||
# QDRO Views
|
||||
# ------------------------------
|
||||
|
||||
@app.get("/qdro/{file_no}")
|
||||
async def qdro_versions(
|
||||
request: Request,
|
||||
file_no: str,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
user = get_current_user_from_session(request.session)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
versions = (
|
||||
db.query(Qdros)
|
||||
.filter(Qdros.file_no == file_no)
|
||||
.order_by(Qdros.version.asc())
|
||||
.all()
|
||||
)
|
||||
return templates.TemplateResponse(
|
||||
"qdro.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user,
|
||||
"file_no": file_no,
|
||||
"versions": versions,
|
||||
"qdro": None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/qdro/{file_no}/{version}")
|
||||
async def qdro_detail(
|
||||
request: Request,
|
||||
file_no: str,
|
||||
version: str,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
user = get_current_user_from_session(request.session)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
q = db.query(Qdros).filter(Qdros.file_no == file_no, Qdros.version == version).first()
|
||||
if not q:
|
||||
return RedirectResponse(url=f"/qdro/{file_no}", status_code=302)
|
||||
|
||||
versions = (
|
||||
db.query(Qdros)
|
||||
.filter(Qdros.file_no == file_no)
|
||||
.order_by(Qdros.version.asc())
|
||||
.all()
|
||||
)
|
||||
return templates.TemplateResponse(
|
||||
"qdro.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": user,
|
||||
"file_no": file_no,
|
||||
"versions": versions,
|
||||
"qdro": q,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -42,9 +42,20 @@ class Client(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
rolodex_id = Column(String(20), unique=True, index=True)
|
||||
# Name and identity fields (modernized)
|
||||
prefix = Column(String(20))
|
||||
last_name = Column(String(50))
|
||||
first_name = Column(String(50))
|
||||
middle_initial = Column(String(10))
|
||||
middle_name = Column(String(50))
|
||||
suffix = Column(String(20)) # Jr, Sr, etc.
|
||||
title = Column(String(100)) # Job/role title
|
||||
group = Column(String(50)) # Legacy rolodex group
|
||||
email = Column(String(255))
|
||||
dob = Column(Date)
|
||||
ssn = Column(String(20))
|
||||
legal_status = Column(String(50))
|
||||
memo = Column(Text)
|
||||
company = Column(String(100))
|
||||
address = Column(String(255))
|
||||
city = Column(String(50))
|
||||
|
||||
@@ -130,6 +130,11 @@ Case {{ case.file_no if case else '' }} · Delphi Database
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if has_qdro %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/qdro/{{ case.file_no }}">
|
||||
QDRO
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
75
app/templates/qdro.html
Normal file
75
app/templates/qdro.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}QDRO · {{ file_no }}{% if qdro %} · {{ qdro.version }}{% endif %} · Delphi Database{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row g-3">
|
||||
<div class="col-12 d-flex align-items-center">
|
||||
<a class="btn btn-sm btn-outline-secondary me-2" href="/dashboard">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
Back
|
||||
</a>
|
||||
<h2 class="mb-0">QDRO</h2>
|
||||
<div class="ms-auto">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/qdro/{{ file_no }}">All Versions</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Versions</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="list-group list-group-flush">
|
||||
{% if versions and versions|length > 0 %}
|
||||
{% for v in versions %}
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href="/qdro/{{ v.file_no }}/{{ v.version }}">
|
||||
<span>Version {{ v.version }}</span>
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="list-group-item text-muted">No QDRO versions for this file.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Details</div>
|
||||
<div class="card-body">
|
||||
{% if qdro %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="text-muted small">File #</div><div class="fw-semibold">{{ qdro.file_no }}</div></div>
|
||||
<div class="col-md-6"><div class="text-muted small">Version</div><div class="fw-semibold">{{ qdro.version }}</div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="text-muted small">Plan Id</div><div>{{ qdro.plan_id or '' }}</div></div>
|
||||
<div class="col-md-6"><div class="text-muted small">Case Number</div><div>{{ qdro.case_number or '' }}</div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="text-muted small">Case Type</div><div>{{ qdro.case_type or '' }}</div></div>
|
||||
<div class="col-md-6"><div class="text-muted small">Section</div><div>{{ qdro.section or '' }}</div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="text-muted small">Judgment Date</div><div>{{ qdro.judgment_date if qdro.judgment_date else '' }}</div></div>
|
||||
<div class="col-md-6"><div class="text-muted small">Valuation Date</div><div>{{ qdro.valuation_date if qdro.valuation_date else '' }}</div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6"><div class="text-muted small">Married On</div><div>{{ qdro.married_on if qdro.married_on else '' }}</div></div>
|
||||
<div class="col-md-6"><div class="text-muted small">Percent Awarded</div><div>{{ qdro.percent_awarded or '' }}</div></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12"><div class="text-muted small">Judge</div><div>{{ qdro.judge or '' }}</div></div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted">Select a version on the left to view details.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
<form method="post" action="{{ '/rolodex/create' if not client else '/rolodex/' ~ client.id ~ '/update' }}">
|
||||
<div class="mb-2 text-muted small" id="fieldHelp" aria-live="polite">Focus a field to see help.</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-2">
|
||||
<label for="prefix" class="form-label">Prefix</label>
|
||||
<input type="text" class="form-control" id="prefix" name="prefix" value="{{ client.prefix if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="last_name" class="form-label">Last Name</label>
|
||||
<input type="text" class="form-control" id="last_name" name="last_name" data-help="Client last name (surname)." value="{{ client.last_name if client else '' }}">
|
||||
@@ -26,6 +30,18 @@
|
||||
<label for="first_name" class="form-label">First Name</label>
|
||||
<input type="text" class="form-control" id="first_name" name="first_name" data-help="Client given name." value="{{ client.first_name if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="middle_name" class="form-label">Middle</label>
|
||||
<input type="text" class="form-control" id="middle_name" name="middle_name" value="{{ client.middle_name if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="suffix" class="form-label">Suffix</label>
|
||||
<input type="text" class="form-control" id="suffix" name="suffix" placeholder="Jr/Sr" value="{{ client.suffix if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" value="{{ client.title if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="company" class="form-label">Company</label>
|
||||
<input type="text" class="form-control" id="company" name="company" data-help="Organization or employer (optional)." value="{{ client.company if client else '' }}">
|
||||
@@ -48,6 +64,30 @@
|
||||
<input type="text" class="form-control" id="zip_code" name="zip_code" data-help="5-digit ZIP or ZIP+4." value="{{ client.zip_code if client else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="group" class="form-label">Group</label>
|
||||
<input type="text" class="form-control" id="group" name="group" value="{{ client.group if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="{{ client.email if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="dob" class="form-label">DOB</label>
|
||||
<input type="date" class="form-control" id="dob" name="dob" value="{{ client.dob.strftime('%Y-%m-%d') if client and client.dob else '' }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="ssn" class="form-label">SS#</label>
|
||||
<input type="text" class="form-control" id="ssn" name="ssn" value="{{ client.ssn if client else '' }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="legal_status" class="form-label">Legal Status</label>
|
||||
<input type="text" class="form-control" id="legal_status" name="legal_status" value="{{ client.legal_status if client else '' }}">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="memo" class="form-label">Memo / Notes</label>
|
||||
<textarea class="form-control" id="memo" name="memo" rows="3">{{ client.memo if client else '' }}</textarea>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="rolodex_id" class="form-label">Legacy Rolodex Id</label>
|
||||
<input type="text" class="form-control" id="rolodex_id" name="rolodex_id" data-help="Legacy ID used for migration and lookup; may be alphanumeric." value="{{ client.rolodex_id if client else '' }}">
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted small">Name</div>
|
||||
<div class="fw-semibold">{{ client.last_name or '' }}, {{ client.first_name or '' }}</div>
|
||||
<div class="fw-semibold">{{ client.prefix or '' }} {{ client.first_name or '' }} {{ client.middle_name or '' }} {{ client.last_name or '' }} {{ client.suffix or '' }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted small">Company</div>
|
||||
@@ -52,6 +52,34 @@
|
||||
<div>{{ client.zip_code or '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">Group</div>
|
||||
<div>{{ client.group or '' }}</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">Email</div>
|
||||
<div>{{ client.email or '' }}</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">DOB</div>
|
||||
<div>{{ client.dob.strftime('%Y-%m-%d') if client.dob else '' }}</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">SS#</div>
|
||||
<div>{{ client.ssn or '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">Legal Status</div>
|
||||
<div>{{ client.legal_status or '' }}</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="text-muted small">Memo / Notes</div>
|
||||
<div>{{ client.memo or '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-primary" href="/rolodex/{{ client.id }}/edit">
|
||||
@@ -121,7 +149,15 @@
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Related Cases</div>
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>Related Cases</span>
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Case filters">
|
||||
{% set status_filter = request.query_params.get('status') or 'all' %}
|
||||
<a class="btn btn-outline-secondary {% if status_filter == 'all' %}active{% endif %}" href="?status=all">All</a>
|
||||
<a class="btn btn-outline-secondary {% if status_filter == 'open' %}active{% endif %}" href="?status=open">Open</a>
|
||||
<a class="btn btn-outline-secondary {% if status_filter == 'closed' %}active{% endif %}" href="?status=closed">Closed</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0 align-middle">
|
||||
@@ -131,23 +167,34 @@
|
||||
<th>Description</th>
|
||||
<th style="width: 90px;">Status</th>
|
||||
<th style="width: 110px;">Opened</th>
|
||||
<th class="text-end" style="width: 110px;">Actions</th>
|
||||
<th class="text-end" style="width: 150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if client.cases and client.cases|length > 0 %}
|
||||
{% for c in client.cases %}
|
||||
{% if status_filter == 'all' or (status_filter == 'open' and (c.status != 'closed')) or (status_filter == 'closed' and c.status == 'closed') %}
|
||||
<tr>
|
||||
<td>{{ c.file_no }}</td>
|
||||
<td>{{ c.description or '' }}</td>
|
||||
<td>{{ c.status or '' }}</td>
|
||||
<td>
|
||||
{% if c.status == 'closed' %}
|
||||
<span class="badge bg-secondary">Closed</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Open</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ c.open_date.strftime('%Y-%m-%d') if c.open_date else '' }}</td>
|
||||
<td class="text-end">
|
||||
<a class="btn btn-sm btn-outline-primary" href="/case/{{ c.id }}">
|
||||
<i class="bi bi-folder2-open"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/qdro/{{ c.file_no }}">
|
||||
QDRO
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-3">No related cases.</td></tr>
|
||||
|
||||
Reference in New Issue
Block a user