Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, UploadFile, File, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import StreamingResponse | |
| from pydantic import BaseModel | |
| from docx import Document | |
| import pandas as pd | |
| import io | |
| import zipfile | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Manual Input සඳහා ආකෘතිය | |
| import os | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| NDA_TEMPLATE_PATH = os.path.join(BASE_DIR, "NDA_template.docx") | |
| OFFER_LETTER_TEMPLATE_PATH = os.path.join(BASE_DIR, "Offer_Letter_template.docx") | |
| class InternData(BaseModel): | |
| name_with_initials: str | |
| full_name: str | |
| home_address: str | |
| welcome_name: str | |
| start_date: str | |
| end_date: str | |
| nic: str | |
| department: str | |
| telephone_number: str | |
| supervisor_name: str | |
| supervisor_designation: str | |
| line_address: str | |
| def format_standard_date(date_val): | |
| if pd.isna(date_val) or not str(date_val).strip(): | |
| return "" | |
| try: | |
| dt = pd.to_datetime(date_val) | |
| return f"{dt.day} {dt.strftime('%B')} {dt.year}" | |
| except Exception: | |
| return str(date_val).strip().split(" ")[0] | |
| def format_ordinal_date(date_val): | |
| if pd.isna(date_val) or not str(date_val).strip(): | |
| return "" | |
| try: | |
| dt = pd.to_datetime(date_val) | |
| day = dt.day | |
| if 11 <= day <= 13: | |
| suffix = "th" | |
| else: | |
| suffix = {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th") | |
| day_str = f"{day}{suffix}" | |
| month_str = dt.strftime("%B") | |
| year_str = dt.strftime("%Y") | |
| return f"{day_str} day of {month_str} {year_str}" | |
| except Exception: | |
| return str(date_val).strip() | |
| def normalize_data(raw_data): | |
| def get_val(keys): | |
| for k in keys: | |
| if k in raw_data: | |
| val = raw_data[k] | |
| return clean_string_val(val) | |
| for rk in raw_data.keys(): | |
| rk_clean = str(rk).strip().lower().replace("_", "").replace(" ", "").replace("-", "") | |
| k_clean = str(k).strip().lower().replace("_", "").replace(" ", "").replace("-", "") | |
| if rk_clean == k_clean: | |
| val = raw_data[rk] | |
| return clean_string_val(val) | |
| return "" | |
| def clean_string_val(val): | |
| if pd.isna(val): | |
| return "" | |
| if isinstance(val, float) and val.is_integer(): | |
| return str(int(val)) | |
| return str(val).strip() | |
| return { | |
| "name_with_initials": get_val(["Name with initials", "Name_with_initials", "name_with_initials", "NameWithInitials", "name"]), | |
| "full_name": get_val(["Full Name", "Full_Name", "full_name", "FullName"]), | |
| "home_address": get_val(["Home Adress", "Home Address", "home_address", "Home_Adress", "address"]), | |
| "welcome_name": get_val(["welcome name", "welcome_name", "welcomeName"]), | |
| "start_date": get_val(["Start date", "Start_Date", "start_date", "StartDate"]), | |
| "end_date": get_val(["End date", "End_Date", "end_date", "EndDate"]), | |
| "nic": get_val(["NIC", "nic", "NIC Number", "nic_number"]), | |
| "department": get_val(["Department", "department"]), | |
| "telephone_number": get_val(["Telephone number", "Telephone_number", "telephone_number", "TelephoneNumber", "tel"]), | |
| "supervisor_name": get_val(["Supervisor name", "Supervisor_name", "supervisor_name", "SupervisorName"]), | |
| "supervisor_designation": get_val(["supervisor designation", "supervisor_designation", "supervisorDesignation", "Supervisor_Designation"]), | |
| "line_address": get_val(["line adress", "line address", "line_address", "Line_Adress", "Line_Address"]) | |
| } | |
| def fill_document(template_path, data): | |
| doc = Document(template_path) | |
| placeholders = { | |
| "NAME_WITH_INITIALS": data["name_with_initials"], | |
| "FULL_NAME": data["full_name"], | |
| "HOME_ADDRESS": data["home_address"], | |
| "WELCOME_NAME": data["welcome_name"], | |
| "START_DATE": format_standard_date(data["start_date"]), | |
| "START_DATE_ORDINAL": format_ordinal_date(data["start_date"]), | |
| "END_DATE": format_standard_date(data["end_date"]), | |
| "NIC": data["nic"], | |
| "DEPARTMENT": data["department"], | |
| "TELEPHONE": data["telephone_number"], | |
| "SUPERVISOR_NAME": data["supervisor_name"], | |
| "SUPERVISOR_DESIGNATION": data["supervisor_designation"], | |
| "LINE_ADDRESS": data["line_address"] | |
| } | |
| for p in doc.paragraphs: | |
| for run in p.runs: | |
| if run.text: | |
| for k, v in placeholders.items(): | |
| tag = f"{{{{{k}}}}}" | |
| if tag in run.text: | |
| run.text = run.text.replace(tag, str(v)) | |
| for table in doc.tables: | |
| for row in table.rows: | |
| for cell in row.cells: | |
| for p in cell.paragraphs: | |
| for run in p.runs: | |
| if run.text: | |
| for k, v in placeholders.items(): | |
| tag = f"{{{{{k}}}}}" | |
| if tag in run.text: | |
| run.text = run.text.replace(tag, str(v)) | |
| file_stream = io.BytesIO() | |
| doc.save(file_stream) | |
| file_stream.seek(0) | |
| return file_stream.getvalue() | |
| def create_zip_archive(data_list): | |
| zip_buffer = io.BytesIO() | |
| with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file: | |
| for raw_data in data_list: | |
| data = normalize_data(raw_data) | |
| if not data['full_name'] or data['full_name'] == 'nan': | |
| continue | |
| safe_name = data['full_name'].replace(" ", "_") | |
| ol_file = fill_document(OFFER_LETTER_TEMPLATE_PATH, data) | |
| nda_file = fill_document(NDA_TEMPLATE_PATH, data) | |
| zip_file.writestr(f"Offer_Letters/{safe_name}_Offer_Letter.docx", ol_file) | |
| zip_file.writestr(f"NDAs/{safe_name}_NDA.docx", nda_file) | |
| zip_buffer.seek(0) | |
| return zip_buffer | |
| async def generate_single(data: InternData): | |
| try: | |
| zip_buffer = create_zip_archive([data.dict()]) | |
| safe_name = data.full_name.replace(' ', '_') | |
| return StreamingResponse( | |
| zip_buffer, | |
| media_type="application/x-zip-compressed", | |
| headers={ | |
| "Content-Disposition": f"attachment; filename=Intern_Docs_{safe_name}.zip"} | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def generate_bulk(file: UploadFile = File(...)): | |
| if not file.filename.endswith(('.xlsx', '.xls')): | |
| raise HTTPException( | |
| status_code=400, detail="කරුණාකර Excel (.xlsx) ෆයිල් එකක් පමණක් Upload කරන්න.") | |
| try: | |
| contents = await file.read() | |
| df = pd.read_excel(io.BytesIO(contents)) | |
| data_list = df.to_dict(orient='records') | |
| zip_buffer = create_zip_archive(data_list) | |
| return StreamingResponse( | |
| zip_buffer, | |
| media_type="application/x-zip-compressed", | |
| headers={ | |
| "Content-Disposition": "attachment; filename=Bulk_Intern_Docs.zip"} | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=8080) | |