File size: 20,983 Bytes
cd4fbc8
1aea4b2
 
 
cd4fbc8
 
 
11f8f1a
dc5a437
6305eba
 
dc5a437
 
1aea4b2
65b0e58
 
1aea4b2
f8257dd
 
 
 
bd2f480
 
 
 
f8257dd
 
 
bd2f480
 
 
f8257dd
 
bd2f480
22af0ee
11f8f1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd4fbc8
 
 
 
1aea4b2
cd4fbc8
 
 
 
 
 
 
 
8cc8786
1aea4b2
cd4fbc8
1aea4b2
8cc8786
 
 
 
 
 
f8257dd
 
 
 
 
 
218f318
 
bd2f480
 
218f318
 
 
bd2f480
 
218f318
e195f58
 
 
bd2f480
 
e195f58
 
bd2f480
 
 
 
218f318
 
e195f58
 
bd2f480
e195f58
bd2f480
e195f58
 
 
bd2f480
e195f58
 
 
 
 
 
 
 
1b305ec
218f318
8cc8786
 
e195f58
 
 
 
8cc8786
 
e195f58
8cc8786
bd18427
8cc8786
e195f58
 
bd18427
bd2f480
e195f58
bd2f480
e195f58
 
 
 
 
 
 
bd2f480
11f8f1a
e195f58
 
 
 
 
 
 
 
 
 
 
bd2f480
e195f58
 
 
8cc8786
e195f58
8cc8786
 
0077308
e195f58
4e3a0fd
f8257dd
 
a035fef
4e3a0fd
f8257dd
bd2f480
f8257dd
218f318
 
f8257dd
4e3a0fd
218f318
 
 
 
 
 
 
 
11f8f1a
 
 
218f318
 
11f8f1a
 
 
 
 
218f318
 
4e3a0fd
 
 
 
 
e195f58
6305eba
4e3a0fd
 
f8257dd
4e3a0fd
 
 
 
 
 
9eb6e67
 
dc5a437
 
 
011ed36
dc5a437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6305eba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc5a437
 
 
 
 
 
 
6305eba
 
dc5a437
 
 
 
 
 
 
 
 
 
6305eba
 
dc5a437
 
 
 
 
 
 
 
 
 
8cc8786
6305eba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd2f480
dc5a437
 
 
 
 
bd2f480
8cc8786
 
4e3a0fd
f8257dd
4e3a0fd
cd4fbc8
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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)

@spaces.GPU
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."

@spaces.GPU
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()