maybe good
This commit is contained in:
665
app/api/documents.py
Normal file
665
app/api/documents.py
Normal file
@@ -0,0 +1,665 @@
|
||||
"""
|
||||
Document Management API endpoints - QDROs, Templates, and General Documents
|
||||
"""
|
||||
from typing import List, Optional, Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import or_, func, and_, desc, asc, text
|
||||
from datetime import date, datetime
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
|
||||
from app.database.base import get_db
|
||||
from app.models.qdro import QDRO
|
||||
from app.models.files import File as FileModel
|
||||
from app.models.rolodex import Rolodex
|
||||
from app.models.lookups import FormIndex, FormList, Footer, Employee
|
||||
from app.models.user import User
|
||||
from app.auth.security import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# Pydantic schemas
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class QDROBase(BaseModel):
|
||||
file_no: str
|
||||
version: str = "01"
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
status: str = "DRAFT"
|
||||
created_date: Optional[date] = None
|
||||
approved_date: Optional[date] = None
|
||||
filed_date: Optional[date] = None
|
||||
participant_name: Optional[str] = None
|
||||
spouse_name: Optional[str] = None
|
||||
plan_name: Optional[str] = None
|
||||
plan_administrator: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class QDROCreate(QDROBase):
|
||||
pass
|
||||
|
||||
|
||||
class QDROUpdate(BaseModel):
|
||||
version: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[date] = None
|
||||
approved_date: Optional[date] = None
|
||||
filed_date: Optional[date] = None
|
||||
participant_name: Optional[str] = None
|
||||
spouse_name: Optional[str] = None
|
||||
plan_name: Optional[str] = None
|
||||
plan_administrator: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class QDROResponse(QDROBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("/qdros/{file_no}", response_model=List[QDROResponse])
|
||||
async def get_file_qdros(
|
||||
file_no: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get QDROs for specific file"""
|
||||
qdros = db.query(QDRO).filter(QDRO.file_no == file_no).order_by(QDRO.version).all()
|
||||
return qdros
|
||||
|
||||
|
||||
@router.get("/qdros/", response_model=List[QDROResponse])
|
||||
async def list_qdros(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
status_filter: Optional[str] = Query(None),
|
||||
search: Optional[str] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""List all QDROs with filtering"""
|
||||
query = db.query(QDRO)
|
||||
|
||||
if status_filter:
|
||||
query = query.filter(QDRO.status == status_filter)
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
or_(
|
||||
QDRO.file_no.contains(search),
|
||||
QDRO.title.contains(search),
|
||||
QDRO.participant_name.contains(search),
|
||||
QDRO.spouse_name.contains(search),
|
||||
QDRO.plan_name.contains(search)
|
||||
)
|
||||
)
|
||||
|
||||
qdros = query.offset(skip).limit(limit).all()
|
||||
return qdros
|
||||
|
||||
|
||||
@router.post("/qdros/", response_model=QDROResponse)
|
||||
async def create_qdro(
|
||||
qdro_data: QDROCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Create new QDRO"""
|
||||
qdro = QDRO(**qdro_data.model_dump())
|
||||
|
||||
if not qdro.created_date:
|
||||
qdro.created_date = date.today()
|
||||
|
||||
db.add(qdro)
|
||||
db.commit()
|
||||
db.refresh(qdro)
|
||||
|
||||
return qdro
|
||||
|
||||
|
||||
@router.get("/qdros/{file_no}/{qdro_id}", response_model=QDROResponse)
|
||||
async def get_qdro(
|
||||
file_no: str,
|
||||
qdro_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get specific QDRO"""
|
||||
qdro = db.query(QDRO).filter(
|
||||
QDRO.id == qdro_id,
|
||||
QDRO.file_no == file_no
|
||||
).first()
|
||||
|
||||
if not qdro:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="QDRO not found"
|
||||
)
|
||||
|
||||
return qdro
|
||||
|
||||
|
||||
@router.put("/qdros/{file_no}/{qdro_id}", response_model=QDROResponse)
|
||||
async def update_qdro(
|
||||
file_no: str,
|
||||
qdro_id: int,
|
||||
qdro_data: QDROUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update QDRO"""
|
||||
qdro = db.query(QDRO).filter(
|
||||
QDRO.id == qdro_id,
|
||||
QDRO.file_no == file_no
|
||||
).first()
|
||||
|
||||
if not qdro:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="QDRO not found"
|
||||
)
|
||||
|
||||
# Update fields
|
||||
for field, value in qdro_data.model_dump(exclude_unset=True).items():
|
||||
setattr(qdro, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(qdro)
|
||||
|
||||
return qdro
|
||||
|
||||
|
||||
@router.delete("/qdros/{file_no}/{qdro_id}")
|
||||
async def delete_qdro(
|
||||
file_no: str,
|
||||
qdro_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Delete QDRO"""
|
||||
qdro = db.query(QDRO).filter(
|
||||
QDRO.id == qdro_id,
|
||||
QDRO.file_no == file_no
|
||||
).first()
|
||||
|
||||
if not qdro:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="QDRO not found"
|
||||
)
|
||||
|
||||
db.delete(qdro)
|
||||
db.commit()
|
||||
|
||||
return {"message": "QDRO deleted successfully"}
|
||||
|
||||
|
||||
# Enhanced Document Management Endpoints
|
||||
|
||||
# Template Management Schemas
|
||||
class TemplateBase(BaseModel):
|
||||
"""Base template schema"""
|
||||
form_id: str
|
||||
form_name: str
|
||||
category: str = "GENERAL"
|
||||
content: str = ""
|
||||
variables: Optional[Dict[str, str]] = None
|
||||
|
||||
class TemplateCreate(TemplateBase):
|
||||
pass
|
||||
|
||||
class TemplateUpdate(BaseModel):
|
||||
form_name: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
variables: Optional[Dict[str, str]] = None
|
||||
|
||||
class TemplateResponse(TemplateBase):
|
||||
active: bool = True
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Document Generation Schema
|
||||
class DocumentGenerateRequest(BaseModel):
|
||||
"""Request to generate document from template"""
|
||||
template_id: str
|
||||
file_no: str
|
||||
output_format: str = "PDF" # PDF, DOCX, HTML
|
||||
variables: Optional[Dict[str, Any]] = None
|
||||
|
||||
class DocumentResponse(BaseModel):
|
||||
"""Generated document response"""
|
||||
document_id: str
|
||||
file_name: str
|
||||
file_path: str
|
||||
size: int
|
||||
created_at: datetime
|
||||
|
||||
# Document Statistics
|
||||
class DocumentStats(BaseModel):
|
||||
"""Document system statistics"""
|
||||
total_templates: int
|
||||
total_qdros: int
|
||||
templates_by_category: Dict[str, int]
|
||||
recent_activity: List[Dict[str, Any]]
|
||||
|
||||
|
||||
@router.get("/templates/", response_model=List[TemplateResponse])
|
||||
async def list_templates(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
category: Optional[str] = Query(None),
|
||||
search: Optional[str] = Query(None),
|
||||
active_only: bool = Query(True),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""List available document templates"""
|
||||
query = db.query(FormIndex)
|
||||
|
||||
if active_only:
|
||||
query = query.filter(FormIndex.active == True)
|
||||
|
||||
if category:
|
||||
query = query.filter(FormIndex.category == category)
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
or_(
|
||||
FormIndex.form_name.contains(search),
|
||||
FormIndex.form_id.contains(search)
|
||||
)
|
||||
)
|
||||
|
||||
templates = query.offset(skip).limit(limit).all()
|
||||
|
||||
# Enhanced response with template content
|
||||
results = []
|
||||
for template in templates:
|
||||
template_lines = db.query(FormList).filter(
|
||||
FormList.form_id == template.form_id
|
||||
).order_by(FormList.line_number).all()
|
||||
|
||||
content = "\n".join([line.content or "" for line in template_lines])
|
||||
|
||||
results.append({
|
||||
"form_id": template.form_id,
|
||||
"form_name": template.form_name,
|
||||
"category": template.category,
|
||||
"content": content,
|
||||
"active": template.active,
|
||||
"created_at": template.created_at,
|
||||
"variables": _extract_variables_from_content(content)
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@router.post("/templates/", response_model=TemplateResponse)
|
||||
async def create_template(
|
||||
template_data: TemplateCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Create new document template"""
|
||||
# Check if template already exists
|
||||
existing = db.query(FormIndex).filter(FormIndex.form_id == template_data.form_id).first()
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Template with this ID already exists"
|
||||
)
|
||||
|
||||
# Create form index entry
|
||||
form_index = FormIndex(
|
||||
form_id=template_data.form_id,
|
||||
form_name=template_data.form_name,
|
||||
category=template_data.category,
|
||||
active=True
|
||||
)
|
||||
db.add(form_index)
|
||||
|
||||
# Create form content lines
|
||||
content_lines = template_data.content.split('\n')
|
||||
for i, line in enumerate(content_lines, 1):
|
||||
form_line = FormList(
|
||||
form_id=template_data.form_id,
|
||||
line_number=i,
|
||||
content=line
|
||||
)
|
||||
db.add(form_line)
|
||||
|
||||
db.commit()
|
||||
db.refresh(form_index)
|
||||
|
||||
return {
|
||||
"form_id": form_index.form_id,
|
||||
"form_name": form_index.form_name,
|
||||
"category": form_index.category,
|
||||
"content": template_data.content,
|
||||
"active": form_index.active,
|
||||
"created_at": form_index.created_at,
|
||||
"variables": template_data.variables or {}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/templates/{template_id}", response_model=TemplateResponse)
|
||||
async def get_template(
|
||||
template_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get specific template with content"""
|
||||
template = db.query(FormIndex).filter(FormIndex.form_id == template_id).first()
|
||||
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found"
|
||||
)
|
||||
|
||||
# Get template content
|
||||
template_lines = db.query(FormList).filter(
|
||||
FormList.form_id == template_id
|
||||
).order_by(FormList.line_number).all()
|
||||
|
||||
content = "\n".join([line.content or "" for line in template_lines])
|
||||
|
||||
return {
|
||||
"form_id": template.form_id,
|
||||
"form_name": template.form_name,
|
||||
"category": template.category,
|
||||
"content": content,
|
||||
"active": template.active,
|
||||
"created_at": template.created_at,
|
||||
"variables": _extract_variables_from_content(content)
|
||||
}
|
||||
|
||||
|
||||
@router.put("/templates/{template_id}", response_model=TemplateResponse)
|
||||
async def update_template(
|
||||
template_id: str,
|
||||
template_data: TemplateUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update document template"""
|
||||
template = db.query(FormIndex).filter(FormIndex.form_id == template_id).first()
|
||||
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found"
|
||||
)
|
||||
|
||||
# Update form index
|
||||
if template_data.form_name:
|
||||
template.form_name = template_data.form_name
|
||||
if template_data.category:
|
||||
template.category = template_data.category
|
||||
|
||||
# Update content if provided
|
||||
if template_data.content is not None:
|
||||
# Delete existing content lines
|
||||
db.query(FormList).filter(FormList.form_id == template_id).delete()
|
||||
|
||||
# Add new content lines
|
||||
content_lines = template_data.content.split('\n')
|
||||
for i, line in enumerate(content_lines, 1):
|
||||
form_line = FormList(
|
||||
form_id=template_id,
|
||||
line_number=i,
|
||||
content=line
|
||||
)
|
||||
db.add(form_line)
|
||||
|
||||
db.commit()
|
||||
db.refresh(template)
|
||||
|
||||
# Get updated content
|
||||
template_lines = db.query(FormList).filter(
|
||||
FormList.form_id == template_id
|
||||
).order_by(FormList.line_number).all()
|
||||
|
||||
content = "\n".join([line.content or "" for line in template_lines])
|
||||
|
||||
return {
|
||||
"form_id": template.form_id,
|
||||
"form_name": template.form_name,
|
||||
"category": template.category,
|
||||
"content": content,
|
||||
"active": template.active,
|
||||
"created_at": template.created_at,
|
||||
"variables": _extract_variables_from_content(content)
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/templates/{template_id}")
|
||||
async def delete_template(
|
||||
template_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Delete document template"""
|
||||
template = db.query(FormIndex).filter(FormIndex.form_id == template_id).first()
|
||||
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found"
|
||||
)
|
||||
|
||||
# Delete content lines
|
||||
db.query(FormList).filter(FormList.form_id == template_id).delete()
|
||||
|
||||
# Delete template
|
||||
db.delete(template)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Template deleted successfully"}
|
||||
|
||||
|
||||
@router.post("/generate/{template_id}")
|
||||
async def generate_document(
|
||||
template_id: str,
|
||||
request: DocumentGenerateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Generate document from template"""
|
||||
# Get template
|
||||
template = db.query(FormIndex).filter(FormIndex.form_id == template_id).first()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="Template not found")
|
||||
|
||||
# Get file information
|
||||
file_obj = db.query(FileModel).options(
|
||||
joinedload(FileModel.owner)
|
||||
).filter(FileModel.file_no == request.file_no).first()
|
||||
|
||||
if not file_obj:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
# Get template content
|
||||
template_lines = db.query(FormList).filter(
|
||||
FormList.form_id == template_id
|
||||
).order_by(FormList.line_number).all()
|
||||
|
||||
template_content = "\n".join([line.content or "" for line in template_lines])
|
||||
|
||||
# Prepare merge variables
|
||||
merge_vars = {
|
||||
"FILE_NO": file_obj.file_no,
|
||||
"CLIENT_FIRST": file_obj.owner.first if file_obj.owner else "",
|
||||
"CLIENT_LAST": file_obj.owner.last if file_obj.owner else "",
|
||||
"CLIENT_FULL": f"{file_obj.owner.first or ''} {file_obj.owner.last}".strip() if file_obj.owner else "",
|
||||
"MATTER": file_obj.regarding or "",
|
||||
"OPENED": file_obj.opened.strftime("%B %d, %Y") if file_obj.opened else "",
|
||||
"ATTORNEY": file_obj.empl_num or "",
|
||||
"TODAY": date.today().strftime("%B %d, %Y")
|
||||
}
|
||||
|
||||
# Add any custom variables from the request
|
||||
if request.variables:
|
||||
merge_vars.update(request.variables)
|
||||
|
||||
# Perform variable substitution
|
||||
merged_content = _merge_template_variables(template_content, merge_vars)
|
||||
|
||||
# Generate document file
|
||||
document_id = str(uuid.uuid4())
|
||||
file_name = f"{template.form_name}_{file_obj.file_no}_{date.today().isoformat()}"
|
||||
|
||||
if request.output_format.upper() == "PDF":
|
||||
file_path = f"/app/exports/{document_id}.pdf"
|
||||
file_name += ".pdf"
|
||||
# Here you would implement PDF generation
|
||||
# For now, create a simple text file
|
||||
with open(f"/app/exports/{document_id}.txt", "w") as f:
|
||||
f.write(merged_content)
|
||||
file_path = f"/app/exports/{document_id}.txt"
|
||||
elif request.output_format.upper() == "DOCX":
|
||||
file_path = f"/app/exports/{document_id}.docx"
|
||||
file_name += ".docx"
|
||||
# Implement DOCX generation
|
||||
with open(f"/app/exports/{document_id}.txt", "w") as f:
|
||||
f.write(merged_content)
|
||||
file_path = f"/app/exports/{document_id}.txt"
|
||||
else: # HTML
|
||||
file_path = f"/app/exports/{document_id}.html"
|
||||
file_name += ".html"
|
||||
html_content = f"<html><body><pre>{merged_content}</pre></body></html>"
|
||||
with open(file_path, "w") as f:
|
||||
f.write(html_content)
|
||||
|
||||
file_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
|
||||
|
||||
return {
|
||||
"document_id": document_id,
|
||||
"file_name": file_name,
|
||||
"file_path": file_path,
|
||||
"size": file_size,
|
||||
"created_at": datetime.now()
|
||||
}
|
||||
|
||||
|
||||
@router.get("/categories/")
|
||||
async def get_template_categories(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get available template categories"""
|
||||
categories = db.query(FormIndex.category).distinct().all()
|
||||
return [cat[0] for cat in categories if cat[0]]
|
||||
|
||||
|
||||
@router.get("/stats/summary")
|
||||
async def get_document_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get document system statistics"""
|
||||
# Template statistics
|
||||
total_templates = db.query(FormIndex).filter(FormIndex.active == True).count()
|
||||
total_qdros = db.query(QDRO).count()
|
||||
|
||||
# Templates by category
|
||||
category_stats = db.query(
|
||||
FormIndex.category,
|
||||
func.count(FormIndex.form_id)
|
||||
).filter(FormIndex.active == True).group_by(FormIndex.category).all()
|
||||
|
||||
categories_dict = {cat[0] or "Uncategorized": cat[1] for cat in category_stats}
|
||||
|
||||
# Recent QDRO activity
|
||||
recent_qdros = db.query(QDRO).order_by(desc(QDRO.updated_at)).limit(5).all()
|
||||
recent_activity = [
|
||||
{
|
||||
"type": "QDRO",
|
||||
"file_no": qdro.file_no,
|
||||
"status": qdro.status,
|
||||
"updated_at": qdro.updated_at.isoformat() if qdro.updated_at else None
|
||||
}
|
||||
for qdro in recent_qdros
|
||||
]
|
||||
|
||||
return {
|
||||
"total_templates": total_templates,
|
||||
"total_qdros": total_qdros,
|
||||
"templates_by_category": categories_dict,
|
||||
"recent_activity": recent_activity
|
||||
}
|
||||
|
||||
|
||||
@router.get("/file/{file_no}/documents")
|
||||
async def get_file_documents(
|
||||
file_no: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get all documents associated with a specific file"""
|
||||
# Get QDROs for this file
|
||||
qdros = db.query(QDRO).filter(QDRO.file_no == file_no).order_by(desc(QDRO.updated_at)).all()
|
||||
|
||||
# Format response
|
||||
documents = [
|
||||
{
|
||||
"id": qdro.id,
|
||||
"type": "QDRO",
|
||||
"title": f"QDRO v{qdro.version}",
|
||||
"status": qdro.status,
|
||||
"created_date": qdro.created_date.isoformat() if qdro.created_date else None,
|
||||
"updated_at": qdro.updated_at.isoformat() if qdro.updated_at else None,
|
||||
"file_no": qdro.file_no
|
||||
}
|
||||
for qdro in qdros
|
||||
]
|
||||
|
||||
return {
|
||||
"file_no": file_no,
|
||||
"documents": documents,
|
||||
"total_count": len(documents)
|
||||
}
|
||||
|
||||
|
||||
def _extract_variables_from_content(content: str) -> Dict[str, str]:
|
||||
"""Extract variable placeholders from template content"""
|
||||
import re
|
||||
variables = {}
|
||||
|
||||
# Find variables in format {{VARIABLE_NAME}}
|
||||
matches = re.findall(r'\{\{([^}]+)\}\}', content)
|
||||
for match in matches:
|
||||
var_name = match.strip()
|
||||
variables[var_name] = f"Placeholder for {var_name}"
|
||||
|
||||
# Find variables in format ^VARIABLE
|
||||
matches = re.findall(r'\^([A-Z_]+)', content)
|
||||
for match in matches:
|
||||
variables[match] = f"Placeholder for {match}"
|
||||
|
||||
return variables
|
||||
|
||||
|
||||
def _merge_template_variables(content: str, variables: Dict[str, Any]) -> str:
|
||||
"""Replace template variables with actual values"""
|
||||
merged = content
|
||||
|
||||
# Replace {{VARIABLE}} format
|
||||
for var_name, value in variables.items():
|
||||
merged = merged.replace(f"{{{{{var_name}}}}}", str(value or ""))
|
||||
merged = merged.replace(f"^{var_name}", str(value or ""))
|
||||
|
||||
return merged
|
||||
Reference in New Issue
Block a user