Improve Rolodex imports, display, and add repair script
This commit is contained in:
103
app/main.py
103
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, Qdros
|
||||
from .models import User, Case, Client, Phone, Transaction, Document, Payment, ImportLog, Qdros, LegacyFile
|
||||
from .auth import authenticate_user, get_current_user_from_session
|
||||
from .reporting import (
|
||||
build_phone_book_pdf,
|
||||
@@ -578,7 +578,13 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
result = {
|
||||
'success': 0,
|
||||
'errors': [],
|
||||
'total_rows': 0
|
||||
'total_rows': 0,
|
||||
'memo_imported': 0,
|
||||
'memo_missing': 0,
|
||||
'email_imported': 0,
|
||||
'email_missing': 0,
|
||||
'skipped_duplicates': 0,
|
||||
'encoding_used': None,
|
||||
}
|
||||
|
||||
expected_fields = {
|
||||
@@ -606,6 +612,7 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
|
||||
try:
|
||||
f, used_encoding = open_text_with_fallbacks(file_path)
|
||||
result['encoding_used'] = used_encoding
|
||||
with f as file:
|
||||
reader = csv.DictReader(file)
|
||||
|
||||
@@ -630,7 +637,13 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
# Check for existing client
|
||||
existing = db.query(Client).filter(Client.rolodex_id == rolodex_id).first()
|
||||
if existing:
|
||||
result['errors'].append(f"Row {row_num}: Client with ID '{rolodex_id}' already exists")
|
||||
result['skipped_duplicates'] += 1
|
||||
logger.warning(
|
||||
"rolodex_import_duplicate",
|
||||
row=row_num,
|
||||
rolodex_id=rolodex_id,
|
||||
file=file_path,
|
||||
)
|
||||
continue
|
||||
|
||||
# Parse DOB (YYYY-MM-DD or MM/DD/YY variants)
|
||||
@@ -645,6 +658,21 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
email_val = row.get('Email', '').strip() or None
|
||||
memo_val = row.get('Memo', '')
|
||||
memo_clean = memo_val.strip() if memo_val is not None else ''
|
||||
memo_val_clean = memo_clean or None
|
||||
|
||||
if email_val:
|
||||
result['email_imported'] += 1
|
||||
else:
|
||||
result['email_missing'] += 1
|
||||
|
||||
if memo_val_clean:
|
||||
result['memo_imported'] += 1
|
||||
else:
|
||||
result['memo_missing'] += 1
|
||||
|
||||
client = Client(
|
||||
rolodex_id=rolodex_id,
|
||||
prefix=row.get('Prefix', '').strip() or None,
|
||||
@@ -659,20 +687,41 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
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,
|
||||
email=email_val,
|
||||
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,
|
||||
memo=memo_val_clean,
|
||||
)
|
||||
|
||||
db.add(client)
|
||||
result['success'] += 1
|
||||
|
||||
logger.info(
|
||||
"rolodex_import_row",
|
||||
row=row_num,
|
||||
rolodex_id=rolodex_id,
|
||||
email_present=bool(email_val),
|
||||
memo_present=bool(memo_val_clean),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
result['errors'].append(f"Row {row_num}: {str(e)}")
|
||||
|
||||
db.commit()
|
||||
logger.info(
|
||||
"rolodex_import_complete",
|
||||
file=file_path,
|
||||
encoding=used_encoding,
|
||||
total_rows=result['total_rows'],
|
||||
success=result['success'],
|
||||
memo_imported=result['memo_imported'],
|
||||
memo_missing=result['memo_missing'],
|
||||
email_imported=result['email_imported'],
|
||||
email_missing=result['email_missing'],
|
||||
skipped_duplicates=result['skipped_duplicates'],
|
||||
errors=len(result['errors']),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("rolodex_import_failed", file=file_path, error=str(e))
|
||||
@@ -757,7 +806,11 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
result = {
|
||||
'success': 0,
|
||||
'errors': [],
|
||||
'total_rows': 0
|
||||
'total_rows': 0,
|
||||
'client_linked': 0,
|
||||
'client_missing': 0,
|
||||
'encoding_used': None,
|
||||
'skipped_duplicates': 0,
|
||||
}
|
||||
|
||||
expected_fields = {
|
||||
@@ -795,6 +848,7 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
f = None
|
||||
try:
|
||||
f, used_encoding = open_text_with_fallbacks(file_path)
|
||||
result['encoding_used'] = used_encoding
|
||||
reader = csv.DictReader(f)
|
||||
|
||||
headers = reader.fieldnames or []
|
||||
@@ -816,7 +870,13 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
# Check for existing case
|
||||
existing = db.query(Case).filter(Case.file_no == file_no).first()
|
||||
if existing:
|
||||
result['errors'].append(f"Row {row_num}: Case with file number '{file_no}' already exists")
|
||||
result['skipped_duplicates'] += 1
|
||||
logger.warning(
|
||||
"files_import_duplicate",
|
||||
row=row_num,
|
||||
file_no=file_no,
|
||||
file=file_path,
|
||||
)
|
||||
continue
|
||||
|
||||
# Find client by ID
|
||||
@@ -825,8 +885,16 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
if client_id:
|
||||
client = db.query(Client).filter(Client.rolodex_id == client_id).first()
|
||||
if not client:
|
||||
result['client_missing'] += 1
|
||||
logger.warning(
|
||||
"files_import_missing_client",
|
||||
row=row_num,
|
||||
file_no=file_no,
|
||||
legacy_client_id=client_id,
|
||||
)
|
||||
result['errors'].append(f"Row {row_num}: Client with ID '{client_id}' not found")
|
||||
continue
|
||||
result['client_linked'] += 1
|
||||
|
||||
case = Case(
|
||||
file_no=file_no,
|
||||
@@ -835,16 +903,35 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]:
|
||||
case_type=row.get('File_Type', '').strip() or None,
|
||||
description=row.get('Regarding', '').strip() or None,
|
||||
open_date=parse_date(row.get('Opened', '')),
|
||||
close_date=parse_date(row.get('Closed', ''))
|
||||
close_date=parse_date(row.get('Closed', '')),
|
||||
)
|
||||
|
||||
db.add(case)
|
||||
result['success'] += 1
|
||||
|
||||
logger.info(
|
||||
"files_import_row",
|
||||
row=row_num,
|
||||
file_no=file_no,
|
||||
client_id=client.id if client else None,
|
||||
status=case.status,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
result['errors'].append(f"Row {row_num}: {str(e)}")
|
||||
|
||||
db.commit()
|
||||
logger.info(
|
||||
"files_import_complete",
|
||||
file=file_path,
|
||||
encoding=used_encoding,
|
||||
total_rows=result['total_rows'],
|
||||
success=result['success'],
|
||||
client_linked=result['client_linked'],
|
||||
client_missing=result['client_missing'],
|
||||
skipped_duplicates=result['skipped_duplicates'],
|
||||
errors=len(result['errors']),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
result['errors'].append(f"Import failed: {str(e)}")
|
||||
|
||||
@@ -59,7 +59,13 @@
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">Email</div>
|
||||
<div>{{ client.email or '' }}</div>
|
||||
<div>
|
||||
{% if client.email %}
|
||||
<a href="mailto:{{ client.email }}">{{ client.email }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">No email</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-muted small">DOB</div>
|
||||
@@ -77,7 +83,13 @@
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="text-muted small">Memo / Notes</div>
|
||||
<div>{{ client.memo or '' }}</div>
|
||||
<div>
|
||||
{% if client.memo %}
|
||||
{{ client.memo }}
|
||||
{% else %}
|
||||
<span class="text-muted">No notes available</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -171,8 +183,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if client.cases and client.cases|length > 0 %}
|
||||
{% for c in client.cases %}
|
||||
{% set sorted_cases = client.cases | sort(attribute='open_date', reverse=True) %}
|
||||
{% if sorted_cases and sorted_cases|length > 0 %}
|
||||
{% for c in sorted_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>
|
||||
@@ -184,7 +197,7 @@
|
||||
<span class="badge bg-success">Open</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ c.open_date.strftime('%Y-%m-%d') if c.open_date else '' }}</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>
|
||||
@@ -197,7 +210,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-3">No related cases.</td></tr>
|
||||
<tr><td colspan="5" class="text-center text-muted py-3">No related cases linked.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user