File size: 13,111 Bytes
31f24ea
 
0d43588
373fe70
 
 
0d43588
31f24ea
 
 
 
a8a7cff
31f24ea
373fe70
 
 
 
 
 
 
 
 
 
31f24ea
373fe70
31f24ea
373fe70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31f24ea
373fe70
31f24ea
0d43588
373fe70
 
 
0d43588
373fe70
28b18b7
373fe70
 
 
 
 
 
 
 
 
 
 
48bea4f
373fe70
48bea4f
373fe70
73446ee
373fe70
 
 
73446ee
373fe70
 
 
 
 
 
 
 
 
48bea4f
373fe70
73446ee
373fe70
 
0d43588
373fe70
48bea4f
373fe70
48bea4f
373fe70
 
 
 
 
 
 
0d43588
373fe70
 
 
 
48bea4f
373fe70
 
 
 
48bea4f
373fe70
 
 
48bea4f
0d43588
373fe70
48bea4f
373fe70
 
 
48bea4f
 
 
 
373fe70
 
48bea4f
0d43588
373fe70
48bea4f
373fe70
48bea4f
373fe70
 
48bea4f
373fe70
 
0d43588
373fe70
 
 
 
 
 
 
0d43588
373fe70
 
0d43588
373fe70
 
 
 
73446ee
373fe70
0d43588
373fe70
 
 
 
0d43588
373fe70
 
 
 
0d43588
373fe70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d43588
373fe70
 
0d43588
373fe70
 
 
 
0d43588
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
import os
import json
import pdfplumber
import io
from io import BytesIO
import re
import gradio as gr

from pdf_engine import detect_igst_status, extract_header_value
from google_sheet_sync import (
    fetch_all_from_sheet, push_rules_to_sheet, push_template_file_to_sheet, 
    load_template_bytes_from_sheet
)
import requests

LOCAL_DATA_DIR = "local_shipper_data"
TEMPLATES_DIR = os.path.join(LOCAL_DATA_DIR, "templates")

def ensure_local_directories():
    if not os.path.exists(LOCAL_DATA_DIR):
        os.makedirs(LOCAL_DATA_DIR)
    if not os.path.exists(TEMPLATES_DIR):
        os.makedirs(TEMPLATES_DIR)

def fetch_data_from_google_sheet():
    try:
        data = fetch_all_from_sheet()
        if isinstance(data, dict) and data:
            return data
    except Exception as e:
        pass
    return {}

def load_shipper_database():
    ensure_local_directories()
    sheet_data = fetch_data_from_google_sheet()
    shippers_dict = sheet_data.get("shippers", {}) if isinstance(sheet_data, dict) else {}
    if shippers_dict:
        return shippers_dict
    else:
        json_path = os.path.join(LOCAL_DATA_DIR, "shippers_rules.json")
        if os.path.exists(json_path):
            try:
                with open(json_path, "r", encoding="utf-8") as f:
                    saved_data = json.load(f)
                    if isinstance(saved_data, dict) and saved_data:
                        return saved_data
            except Exception:
                pass
    return {}

def save_shipper_database_to_sheet(shipper_database):
    ensure_local_directories()
    json_path = os.path.join(LOCAL_DATA_DIR, "shippers_rules.json")
    try:
        with open(json_path, "w", encoding="utf-8") as f:
            json.dump(shipper_database, f, indent=4)
        shippers_payload = {}
        for s_name, s_data in shipper_database.items():
            shippers_payload[s_name] = {
                "mapping_rules": s_data.get("mapping_rules", {}),
                "item_table_rules": s_data.get("item_table_rules", {}),
                "item_table_rule_name": s_data.get("item_table_rule_name", "parser_welspun"),
                "igst_config": s_data.get("igst_config", {})
            }
        push_rules_to_sheet(shippers_payload)
        return True, "🎉 Rules successfully saved and synced to Google Sheet!"
    except Exception as e:
        return False, f"Save Error: {str(e)}"

def render_shipper_data():
    db = load_shipper_database()
    shippers_list = sorted(list(db.keys()))

    gr.Markdown("### 🏢 Add Shipper Name & No-Code Visual Mapping Builder")
    gr.Markdown("कीवर्ड, बॉक्स एक्सट्रैक्शन और पोजीशन के जरिए स्मार्ट टेस्ट और सेव टूल।")
    
    # 1. Shipper Selection Dropdown (Jab tak select na ho, niche ka kuch nahi dikhega)
    selected_shipper_dropdown = gr.Dropdown(
        choices=shippers_list, 
        label="1. कॉन्फ़िगर करने के लिए शिपर चुनें:", 
        value=None, 
        interactive=True
    )

    # Config Section Wrapper (Initially Hidden)
    with gr.Column(visible=False) as config_container:
        shipper_title_md = gr.Markdown("### ⚙️ शिपर प्रोफाइल:")
        
        # 📁 1. Template File Section
        gr.Markdown("---")
        gr.Markdown("### 📁 1. टेम्पलेट फ़ाइल अपलोड (Full Job Excel Template)")
        
        with gr.Row() as tpl_upload_row:
            tpl_file_input = gr.File(label="Blank Full Job Excel Format File (Template) चुनें", file_types=[".xlsx", ".xls"])
            save_tpl_btn = gr.Button("🚀 Save Template to Google Sheet", variant="primary")
            
        with gr.Row(visible=False) as tpl_status_row:
            tpl_status_txt = gr.Textbox(label="Template Status", interactive=False, scale=3)
            replace_tpl_file = gr.File(label="🔄 Replace Template", file_types=[".xlsx", ".xls"], scale=2)
            confirm_replace_btn = gr.Button("🚀 Confirm & Replace", variant="secondary")
            delete_tpl_btn = gr.Button("🗑️ Delete Template", variant="stop")
            
        tpl_action_msg = gr.Textbox(label="Status Message", interactive=False)

        # 🧪 2. Sample PDF Upload & Text Inspector
        gr.Markdown("---")
        gr.Markdown("### 🧪 2. Sample PDF Upload & Text Viewer")
        with gr.Row():
            test_pdf_input = gr.File(label="टेस्ट करने के लिए सैंपल इनवॉइस PDF अपलोड करें", file_types=[".pdf"], scale=1)
            pdf_preview_box = gr.Textbox(label="Extracted PDF Text Preview", lines=4, scale=2, interactive=False)

        # ⚡ 3. Smart Test & Save Generator
        gr.Markdown("---")
        gr.Markdown("### ⚡ 3. Smart Test & Save Generator (Box & Position)")
        with gr.Row():
            target_val_box = gr.Textbox(label="1. टारगेट वैल्यू:")
            keyword_box = gr.Textbox(label="2. मुख्य कीवर्ड:")
            direction_radio = gr.Radio(["Right (आगे)", "Below (नीचे)", "📦 Extract Inside Box (डब्बा)"], label="3. दिशा / तरीका:", value="Right (आगे)")
            offset_num = gr.Number(value=1, label="4. Index:")

        test_run_btn = gr.Button("🧪 Test Extraction First", variant="secondary")
        test_output_box = gr.Textbox(label="Test Result", interactive=False)

        target_field_dropdown = gr.Dropdown(choices=[], label="5. यह लॉजिक किस हेडर फील्ड पर सेव करना है?", interactive=True)
        save_tested_rule_btn = gr.Button("💾 Confirm & Save to Shipper Rule", variant="primary")

        # 🛠️ 4. Header Fields Mapping Rules Table
        gr.Markdown("---")
        gr.Markdown("### 🛠️ 4. Header Fields Mapping & Regex Rules")
        header_rules_dataframe = gr.Dataframe(
            headers=["Field Name", "Source Doc", "Keyword", "Cell", "Prompt", "Result Ex", "Local Python/Regex Logic"],
            datatype=["str", "str", "str", "str", "str", "str", "str"],
            row_count=5,
            col_count=7,
            interactive=True,
            label="Header Mapping Rules"
        )

        # 📋 5. Dynamic Item Table Rules & Mapping
        gr.Markdown("---")
        gr.Markdown("### 📋 5. Dynamic Item Table Rules & Mapping")
        parser_dropdown = gr.Dropdown(["parser_welspun", "parser_polycab", "parser_bkt", "parser_vapi_welspun"], label="इस शिपर के लिए आइटम पार्सर चुनें:")
        item_rules_dataframe = 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,
            interactive=True,
            label="Item Table Rules"
        )

        # ⚙️ 6. IGST & Lut Configuration
        gr.Markdown("---")
        gr.Markdown("### ⚙️ 6. IGST & Lut Configuration")
        with gr.Row():
            lut_kws_box = gr.Textbox(label="LUT Keywords (कॉमा से अलग करें):")
            paid_kws_box = gr.Textbox(label="Paid Keywords (कॉमा से अलग करें):")

        save_all_btn = gr.Button("💾 Save All Rules & Sync to Google Sheet", variant="primary", size="lg")
        sync_status_box = gr.Textbox(label="Sync Status", interactive=False)

    # --- Event Handlers & Dynamic Binding ---
    def on_shipper_select(shipper_name):
        if not shipper_name:
            return (
                gr.update(visible=False), "", gr.update(visible=True), gr.update(visible=False), 
                "", [], [], "parser_welspun", "", "", gr.update(choices=[])
            )
        
        current_db = load_shipper_database()
        s_info = current_db.get(shipper_name, {})
        
        # Template Check
        t_bytes = load_template_bytes_from_sheet(shipper_name)
        has_tpl = t_bytes is not None and len(t_bytes) > 0
        tpl_msg = f"✅ 'Full Job Excel Format File (Template)' गूगल शीट पर अपलोडेड एवं सुरक्षित है।" if has_tpl else "ℹ️ इस शिपर के लिए अभी कोई टेम्पलेट अपलोड नहीं की गई है।"
        
        # Header Rules Rows
        mapping_rules = s_info.get("mapping_rules", {})
        h_rows = [[f, v.get("logic", "Main Invoice"), v.get("keyword", ""), v.get("cell", ""), v.get("ai_prompt", ""), v.get("result_example", ""), v.get("extracted_logic", "")] for f, v in mapping_rules.items()]
        if not h_rows: h_rows = [["", "Main Invoice", "", "", "", "", ""]]
        
        # Item Rules Rows
        item_rules = s_info.get("item_table_rules", {})
        i_rows = [[i, v.get("col", "K"), v.get("type", "PDF Row Item"), v.get("rule", ""), v.get("result_example", "")] for i, v in item_rules.items()]
        if not i_rows: i_rows = [["", "K", "PDF Row Item", "", ""]]
        
        parser_name = s_info.get("item_table_rule_name", "parser_welspun")
        igst_cfg = s_info.get("igst_config", {})
        lut_val = igst_cfg.get("lut_keywords", "LUT, UNDER LUT, UNDER BOND")
        paid_val = igst_cfg.get("paid_keywords", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.")
        
        field_choices = list(mapping_rules.keys())
        
        return (
            gr.update(visible=True), 
            f"### ⚙️ शिपर प्रोफाइल: **{shipper_name}**",
            gr.update(visible=not has_tpl),
            gr.update(visible=has_tpl),
            tpl_msg,
            h_rows,
            i_rows,
            parser_name,
            lut_val,
            paid_val,
            gr.update(choices=field_choices, value=field_choices[0] if field_choices else None)
        )

    selected_shipper_dropdown.change(
        fn=on_shipper_select,
        inputs=selected_shipper_dropdown,
        outputs=[
            config_container, shipper_title_md, tpl_upload_row, tpl_status_row, 
            tpl_status_txt, header_rules_dataframe, item_rules_dataframe, 
            parser_dropdown, lut_kws_box, paid_kws_box, target_field_dropdown
        ]
    )

    # Template Save & Replace Handlers
    def save_tpl_action(shipper_name, file_obj):
        if not shipper_name or not file_obj:
            return "❌ कृपया शिपर चुनें और फाइल अपलोड करें!"
        with open(file_obj.name, "rb") as f:
            success = push_template_file_to_sheet(shipper_name, f.read())
        return "🎉 टेम्पलेट सफलतापूर्वक गूगल शीट पर सेव हो गई!" if success else "❌ अपलोड फेल हो गया!"

    save_tpl_btn.click(fn=save_tpl_action, inputs=[selected_shipper_dropdown, tpl_file_input], outputs=tpl_action_msg)
    confirm_replace_btn.click(fn=save_tpl_action, inputs=[selected_shipper_dropdown, replace_tpl_file], outputs=tpl_action_msg)
    
    def delete_tpl_action(shipper_name):
        if not shipper_name: return "❌ शिपर नहीं चुना गया।"
        push_template_file_to_sheet(shipper_name, b"")
        return "🗑️ टेम्पलेट डिलीट हो गई!"
    delete_tpl_btn.click(fn=delete_tpl_action, inputs=[selected_shipper_dropdown], outputs=tpl_action_msg)

    # Save All Rules Handler
    def save_all_rules(shipper_name, h_data, i_data, parser_val, lut_val, paid_val):
        if not shipper_name: return "❌ शिपर नहीं चुना गया।"
        db = load_shipper_database()
        s_info = db.setdefault(shipper_name, {})
        
        # Map Header Rules
        new_mapping = {}
        for row in h_data:
            if row and row[0]:
                new_mapping[row[0]] = {
                    "logic": row[1], "keyword": row[2], "cell": row[3], 
                    "ai_prompt": row[4], "result_example": row[5], "extracted_logic": row[6]
                }
        s_info["mapping_rules"] = new_mapping
        
        # Map Item Rules
        new_items = {}
        for row in i_data:
            if row and row[0]:
                new_items[row[0]] = {
                    "col": row[1], "type": row[2], "rule": row[3], "result_example": row[4]
                }
        s_info["item_table_rules"] = new_items
        s_info["item_table_rule_name"] = parser_val
        s_info["igst_config"] = {"lut_keywords": lut_val, "paid_keywords": paid_val}
        
        _, msg = save_shipper_database_to_sheet(db)
        return msg

    save_all_btn.click(
        fn=save_all_rules,
        inputs=[selected_shipper_dropdown, header_rules_dataframe, item_rules_dataframe, parser_dropdown, lut_kws_box, paid_kws_box],
        outputs=sync_status_box
    )