Spaces:
Running on Zero
Running on Zero
| import gradio as gr | |
| import pandas as pd | |
| import requests | |
| import json | |
| import pdfplumber | |
| import re | |
| import spaces | |
| from processor import process_invoices_backend | |
| from google_sheet_sync import ( | |
| fetch_all_from_sheet, push_rules_to_sheet, push_template_file_to_sheet, | |
| load_template_bytes_from_sheet | |
| ) | |
| from pdf_engine import extract_header_value | |
| WEB_APP_URL = "https://script.google.com/macros/s/AKfycbxxKu82K0V3VFn7tRlj3ddzB7Wy-iPyrupAV9tZzjBdhcXZPWFIXodJv6D03M-X_KSn/exec" | |
| SPREADSHEET_ID = "1GJlIl_ANRPs4lID1aZ4WZiWVg_m5AQd7cuQSx6LCnxw" | |
| def fetch_shipper_names(): | |
| try: | |
| data = fetch_all_from_sheet() | |
| if isinstance(data, dict) and "shippers" in data: | |
| names = list(data["shippers"].keys()) | |
| if names: | |
| return gr.update(choices=names, value=None) | |
| json_db_url = f"https://docs.google.com/spreadsheets/d/{SPREADSHEET_ID}/gviz/tq?tqx=out:csv&sheet=Shipper_JSON_Database" | |
| df_json = pd.read_csv(json_db_url) | |
| if not df_json.empty and "ShipperName" in df_json.columns: | |
| names = df_json["ShipperName"].dropna().tolist() | |
| if names: | |
| return gr.update(choices=names, value=None) | |
| except Exception: | |
| pass | |
| return gr.update(choices=[], value=None) | |
| def process_invoice_action(shipper, main_inv, gst_inv, deec_decl): | |
| if not shipper: | |
| return None, "⚠️ Kripya pehle shipper chunein!" | |
| sheet_data = fetch_all_from_sheet() | |
| shippers_dict = sheet_data.get("shippers", {}) if sheet_data else {} | |
| shipper_info = shippers_dict.get(shipper, {}) | |
| file_bytes, filename_or_err = process_invoices_backend(shipper, shipper_info, main_inv, gst_inv, deec_decl) | |
| if file_bytes is None: | |
| return None, f"❌ Error: {filename_or_err}" | |
| import tempfile | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") | |
| tmp.write(file_bytes) | |
| tmp.close() | |
| return tmp.name, f"🎉 Success! File '{filename_or_err}' taiyar hai." | |
| def process_rate_pdf(file_obj): | |
| if file_obj is None: | |
| return "Kripya PDF file upload karein!" | |
| try: | |
| with pdfplumber.open(file_obj.name) as pdf: | |
| text = "" | |
| for page in pdf.pages: | |
| t = page.extract_text() | |
| if t: text += t + "\n" | |
| date_match = re.search(r"w\.e\.f[\s\.:]*([\d]{2}[\-\/][\d]{2}[\-\/][\d]{4})", text, re.IGNORECASE) | |
| rate_date = date_match.group(1).strip() if date_match else "N/A" | |
| return f"🎉 Rate PDF Successfully Parsed! w.e.f: {rate_date}" | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def verify_password(pwd): | |
| if pwd == "CKJ": | |
| return gr.update(visible=False), gr.update(visible=True) | |
| else: | |
| return gr.update(visible=True), gr.update(visible=False) | |
| def verify_admin(pwd): | |
| if pwd == "TEST": | |
| return gr.update(visible=False), gr.update(visible=True) | |
| else: | |
| return gr.update(visible=True), gr.update(visible=False) | |
| def on_shipper_select(shipper): | |
| if shipper and shipper.strip(): | |
| return gr.update(visible=True) | |
| return gr.update(visible=False) | |
| def on_file_upload(file_obj): | |
| if file_obj is not None: | |
| return gr.update(visible=True) | |
| return gr.update(visible=False) | |
| custom_css = """ | |
| .creator-card { | |
| background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); | |
| padding: 10px; | |
| border-radius: 8px; | |
| color: white; | |
| text-align: center; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.15); | |
| margin-bottom: 8px; | |
| margin-top: 6px; | |
| max-width: 220px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .creator-name { | |
| font-size: 15px; | |
| font-weight: 700; | |
| margin-top: 4px; | |
| margin-bottom: 2px; | |
| } | |
| .creator-title { | |
| font-size: 10px; | |
| color: #d1d8e0; | |
| letter-spacing: 1px; | |
| text-transform: uppercase; | |
| font-weight: 600; | |
| } | |
| """ | |
| with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo: | |
| # 🔒 1. Global Password Lock Screen | |
| with gr.Column(visible=True) as login_screen: | |
| with gr.Row(): | |
| gr.Column(scale=1) | |
| with gr.Column(scale=2): | |
| gr.Markdown("<br><br><h2 style='text-align: center;'>🚢 CK Export Invoice Processor Pro</h2>") | |
| gr.Markdown("<p style='text-align: center; color: gray;'>Kripya aage badhne ke liye app ka password darj karein.</p>") | |
| pass_input = gr.Textbox(label="Password darj karein:", type="password") | |
| unlock_btn = gr.Button("Unlock App", variant="primary") | |
| gr.Column(scale=1) | |
| # 🚀 2. Main Application Layout | |
| with gr.Column(visible=False) as main_app: | |
| with gr.Row(): | |
| # 📌 Left Sidebar | |
| with gr.Column(scale=1, min_width=240): | |
| try: | |
| gr.Image("ck_photo.jpg", show_label=False, interactive=False, width=210) | |
| except: | |
| gr.Markdown("*(Chetan Joshi Photo)*") | |
| gr.HTML(""" | |
| <div class="creator-card"> | |
| <div class="creator-name">Chetan Joshi</div> | |
| <div class="creator-title">📞 +91 98253 06898</div> | |
| <hr style="border-color: rgba(255,255,255,0.2); margin: 4px 0;"> | |
| <p style='font-size: 9px; color: #f1d8e6; margin: 0;'> | |
| <b>CK Export Invoice Pro v2.0</b><br> | |
| Enterprise Automation & Precision. | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| gr.Number(value=109.8, label="EUR", interactive=False) | |
| gr.Number(value=128.15, label="GBP", interactive=False) | |
| gr.Number(value=94.8, label="USD", interactive=False) | |
| gr.HTML("<div style='text-align: center; margin-top: 6px; margin-bottom: 8px;'><span style='font-size: 15px; font-weight: 800; color: #00cec9; background: rgba(0, 206, 201, 0.1); padding: 4px 8px; border-radius: 6px; display: inline-block;'>📅 w.e.f: N/A</span></div>") | |
| gr.Markdown("---") | |
| gr.Markdown("##### 💱 Customs Exchange Rates") | |
| pdf_rate_input = gr.File(label="Upload Rate PDF", file_types=[".pdf"]) | |
| rate_output = gr.Textbox(label="Status", interactive=False) | |
| pdf_rate_input.change(fn=process_rate_pdf, inputs=pdf_rate_input, outputs=rate_output) | |
| # 📌 Right Main Dashboard | |
| with gr.Column(scale=3): | |
| with gr.Column(visible=True) as user_dashboard: | |
| gr.Markdown("# 🚢 CK Export Invoice Processor") | |
| gr.Markdown("---") | |
| gr.Markdown("### 📥 Invoice Processing Zone (Multi-Document)") | |
| shipper_dropdown = gr.Dropdown( | |
| choices=[], | |
| label="किस शिपर का इनवॉइस प्रोसेस करना है?", | |
| interactive=True, | |
| value=None | |
| ) | |
| with gr.Column(visible=False) as upload_section: | |
| gr.Markdown("### 📄 Upload Invoices & Supporting Documents") | |
| with gr.Row(): | |
| main_inv_file = gr.File(label="मुख्य इनवॉइस (PDF / Excel) #1") | |
| gst_inv_file = gr.File(label="GST Invoice #1 (PDF/Excel)") | |
| deec_decl_file = gr.File(label="DEEC Decl. #1 (PDF/Excel)") | |
| with gr.Column(visible=False) as process_section: | |
| process_btn = gr.Button("🚀 Process & Generate Excel (ZeroGPU)", variant="primary") | |
| output_file = gr.File(label="📥 Download Generated Excel") | |
| output_status = gr.Textbox(label="Processing Status") | |
| main_inv_file.change(fn=on_file_upload, inputs=main_inv_file, outputs=process_section) | |
| process_btn.click( | |
| fn=process_invoice_action, | |
| inputs=[shipper_dropdown, main_inv_file, gst_inv_file, deec_decl_file], | |
| outputs=[output_file, output_status] | |
| ) | |
| shipper_dropdown.change(fn=on_shipper_select, inputs=shipper_dropdown, outputs=upload_section) | |
| gr.Markdown("---") | |
| with gr.Accordion("🛠️ Admin Settings Access", open=False): | |
| admin_pwd_input = gr.Textbox(label="Admin password darj karein:", type="password") | |
| admin_login_btn = gr.Button("Login Karein") | |
| # Admin Mode View with Live Google Sheet Binding | |
| with gr.Column(visible=False) as admin_dashboard: | |
| gr.Markdown("# 🛠️ CK Export Processor - Admin Mode") | |
| admin_back_btn = gr.Button("🚪 Log Out Admin", variant="primary") | |
| admin_tabs = gr.Radio( | |
| ["i. 🏢 Add Shipper Name & Setup", "iii. 🌍 Global Masters & Common Dictionaries"], | |
| label="📋 Admin settings chunein:", | |
| value="i. 🏢 Add Shipper Name & Setup" | |
| ) | |
| with gr.Column() as admin_content_box: | |
| gr.Markdown("### 🏢 Add Shipper Name & No-Code Visual Mapping Builder") | |
| admin_shipper_dropdown = gr.Dropdown(choices=[], label="1. कॉन्फ़िगर करने के लिए शिपर चुनें:", interactive=True) | |
| gr.Markdown("---") | |
| gr.Markdown("### 📁 2. टेम्पलेट फ़ाइल अपलोड (Full Job Excel Template)") | |
| with gr.Row(): | |
| tpl_file_input = gr.File(label="Blank Full Job Excel Format File (Template) चुनें", file_types=[".xlsx", ".xls"]) | |
| tpl_save_btn = gr.Button("🚀 Save Template to Google Sheet", variant="primary") | |
| tpl_status_box = gr.Textbox(label="Template Status", interactive=False) | |
| gr.Markdown("---") | |
| gr.Markdown("### 🧪 3. Sample PDF Upload & Text Viewer") | |
| sample_pdf_input = gr.File(label="टेस्ट करने के लिए सैंपल इनवॉइस PDF अपलोड करें", file_types=[".pdf"]) | |
| sample_text_output = gr.Textbox(label="Extracted PDF Text Preview", lines=6, interactive=False) | |
| def load_sample_pdf_text(file_obj): | |
| if file_obj is None: | |
| return "कोई फाइल अपलोड नहीं की गई।" | |
| try: | |
| with pdfplumber.open(file_obj.name) as pdf: | |
| txt = "".join([p.extract_text() or "" for p in pdf.pages]) | |
| return txt[:3000] + "\n...(Text truncated)..." if len(txt) > 3000 else txt | |
| except Exception as e: | |
| return f"Error reading PDF: {str(e)}" | |
| sample_pdf_input.change(fn=load_sample_pdf_text, inputs=sample_pdf_input, outputs=sample_text_output) | |
| gr.Markdown("---") | |
| gr.Markdown("### ⚡ 4. Smart Test & Save Generator (Box & Position)") | |
| with gr.Row(): | |
| test_target_input = gr.Textbox(label="1. टारगेट वैल्यू / फील्ड नाम:") | |
| test_kw_input = gr.Textbox(label="2. मुख्य कीवर्ड:") | |
| test_pos_dropdown = gr.Dropdown(["Right (आगे)", "📦 Extract Inside Box (डब्बे के अंदर का टेक्स्ट)", "Below (नीचे)"], label="3. दिशा / तरीका:", value="Right (आगे)") | |
| test_index_number = gr.Number(value=1, label="4. Index:") | |
| test_run_btn = gr.Button("🚀 Run Live Single Field Inspection", variant="primary") | |
| test_result_output = gr.Textbox(label="Inspection Result", interactive=False) | |
| def run_live_inspection(shipper_name, sample_file, target_field, keyword, direction, index_val): | |
| if not sample_file: | |
| return "⚠️ कृपया पहले सैंपल PDF अपलोड करें!" | |
| if not keyword: | |
| return "⚠️ कृपया मुख्य कीवर्ड दर्ज करें!" | |
| try: | |
| with pdfplumber.open(sample_file.name) as pdf: | |
| pdf_text = "".join([p.extract_text() or "" for p in pdf.pages]) | |
| pdf_lines = pdf_text.split("\n") | |
| # Call core extraction engine | |
| res = extract_header_value(pdf_lines, pdf_text, keyword, direction, "Exact Word", "", "None", field_label=target_field) | |
| return f"🎯 Result Found: {res}" if res else "❌ No value found for this keyword/direction." | |
| except Exception as e: | |
| return f"Error during inspection: {str(e)}" | |
| test_run_btn.click( | |
| fn=run_live_inspection, | |
| inputs=[admin_shipper_dropdown, sample_pdf_input, test_target_input, test_kw_input, test_pos_dropdown, test_index_number], | |
| outputs=test_result_output | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### 🛠️ 5. Header Fields Mapping & Regex Rules") | |
| header_rules_table = gr.Dataframe( | |
| headers=["Field Name", "Source Doc", "Keyword", "Cell", "Prompt", "Result Ex"], | |
| datatype=["str", "str", "str", "str", "str", "str"], | |
| row_count=5, | |
| col_count=6, | |
| label="Header Rules Configuration", | |
| interactive=True | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### 📋 6. Dynamic Item Table Rules & Mapping") | |
| parser_selector = gr.Dropdown(["parser_welspun", "parser_polycab", "parser_bkt", "parser_vapi_welspun"], label="इस शिपर के लिए आइटम पार्सर चुनें:", value="parser_welspun") | |
| item_rules_table = gr.Dataframe( | |
| headers=["Item Field Name", "Excel Col", "Source Type", "Extraction Rule / Keyword", "Result Example"], | |
| datatype=["str", "str", "str", "str", "str"], | |
| row_count=5, | |
| col_count=5, | |
| label="Item Table Rules Configuration", | |
| interactive=True | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### ⚙️ 7. IGST & Lut Configuration") | |
| with gr.Row(): | |
| lut_keywords_input = gr.Textbox(label="LUT Keywords (कॉमा से अलग करें):", value="LUT, UNDER LUT, UNDER BOND") | |
| paid_keywords_input = gr.Textbox(label="Paid Keywords (कॉमा से अलग करें):", value="SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.") | |
| save_all_rules_btn = gr.Button("💾 Save All Rules & Sync to Google Sheet", variant="primary", size="lg") | |
| save_status_output = gr.Textbox(label="Sync Status", interactive=False) | |
| # 🔄 Live Data Populator when Shipper is selected in Admin | |
| def on_admin_shipper_change(shipper_name): | |
| if not shipper_name: | |
| return "", [], [], "parser_welspun", "LUT, UNDER LUT, UNDER BOND", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST." | |
| sheet_data = fetch_all_from_sheet() | |
| shippers_dict = sheet_data.get("shippers", {}) if sheet_data else {} | |
| s_info = shippers_dict.get(shipper_name, {}) | |
| # 1. Template Status | |
| has_tpl = load_template_bytes_from_sheet(shipper_name) is not None | |
| tpl_msg = "✅ Template Excel File is Uploaded & Saved in Google Sheet." if has_tpl else "❌ No template uploaded for this shipper." | |
| # 2. Header Rules Table Data | |
| mapping_rules = s_info.get("mapping_rules", {}) | |
| h_rows = [] | |
| for f_name, f_val in mapping_rules.items(): | |
| h_rows.append([ | |
| f_name, | |
| f_val.get("logic", "Main Invoice"), | |
| f_val.get("keyword", ""), | |
| f_val.get("cell", ""), | |
| f_val.get("ai_prompt", ""), | |
| f_val.get("result_example", "") | |
| ]) | |
| if not h_rows: | |
| h_rows = [["", "", "", "", "", ""]] | |
| # 3. Item Table Rules Data | |
| item_rules = s_info.get("item_table_rules", {}) | |
| i_rows = [] | |
| for i_name, i_val in item_rules.items(): | |
| i_rows.append([ | |
| i_name, | |
| i_val.get("col", ""), | |
| i_val.get("type", "PDF Row Item"), | |
| i_val.get("rule", ""), | |
| i_val.get("result_example", "") | |
| ]) | |
| if not i_rows: | |
| i_rows = [["", "", "", "", ""]] | |
| # 4. Parser Name | |
| p_name = s_info.get("item_table_rule_name", "parser_welspun") | |
| # 5. IGST Config | |
| igst_cfg = s_info.get("igst_config", {}) | |
| lut_kw = igst_cfg.get("lut_keywords", "LUT, UNDER LUT, UNDER BOND") | |
| paid_kw = igst_cfg.get("paid_keywords", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.") | |
| return tpl_msg, h_rows, i_rows, p_name, lut_kw, paid_kw | |
| admin_shipper_dropdown.change( | |
| fn=on_admin_shipper_change, | |
| inputs=admin_shipper_dropdown, | |
| outputs=[tpl_status_box, header_rules_table, item_rules_table, parser_selector, lut_keywords_input, paid_keywords_input] | |
| ) | |
| # Event Handlers & Initial Data Load | |
| def update_admin_shippers(): | |
| names_update = fetch_shipper_names() | |
| return names_update, names_update | |
| demo.load(fn=update_admin_shippers, outputs=[shipper_dropdown, admin_shipper_dropdown]) | |
| unlock_btn.click(fn=verify_password, inputs=pass_input, outputs=[login_screen, main_app]) | |
| pass_input.submit(fn=verify_password, inputs=pass_input, outputs=[login_screen, main_app]) | |
| admin_login_btn.click(fn=verify_admin, inputs=admin_pwd_input, outputs=[user_dashboard, admin_dashboard]) | |
| admin_back_btn.click(fn=lambda: (gr.update(visible=True), gr.update(visible=False)), outputs=[user_dashboard, admin_dashboard]) | |
| if __name__ == "__main__": | |
| demo.launch() |