File size: 9,475 Bytes
ee9c648
 
 
 
51ce156
ee9c648
3f6afb3
 
 
 
51ce156
 
 
ee9c648
 
 
 
 
 
3ae5fff
ee9c648
 
 
 
 
3f6afb3
ee9c648
ad11b7e
3f6afb3
 
 
 
 
 
 
 
 
 
 
 
 
 
ad11b7e
 
 
 
5ead0e5
ad11b7e
 
3f6afb3
ad11b7e
 
 
3f6afb3
5ead0e5
ad11b7e
 
 
78f2a85
ad11b7e
3f6afb3
78f2a85
 
 
ad11b7e
3f6afb3
ad11b7e
 
 
3f6afb3
ad11b7e
 
 
3f6afb3
ad11b7e
 
ee9c648
51ce156
78f2a85
 
 
 
 
 
 
51ce156
 
 
 
 
 
 
 
 
 
78f2a85
3f6afb3
ad11b7e
 
 
3f6afb3
 
 
 
 
ad11b7e
 
 
3f6afb3
ad11b7e
 
 
3f6afb3
78f2a85
3ae5fff
51ce156
 
 
 
78f2a85
3ae5fff
3f6afb3
 
5ead0e5
 
 
78f2a85
3ae5fff
3f6afb3
5ead0e5
 
 
 
3ae5fff
5ead0e5
3f6afb3
ad11b7e
 
3f6afb3
5ead0e5
 
 
 
51ce156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ead0e5
 
ad11b7e
78f2a85
5ead0e5
 
ad11b7e
5ead0e5
 
 
 
 
 
 
 
3ae5fff
37fa25e
 
5ead0e5
37fa25e
 
 
 
257c894
84f0a8f
37fa25e
5ead0e5
37fa25e
 
78f2a85
 
37fa25e
 
 
 
78f2a85
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
import gradio as gr
import pandas as pd
from datetime import datetime
import pytz
import re

# Updated Gradio app for YunExpress:
# - New output headers (as provided)
# - Email -> Email mapping
# - FOBPrice1 = 2 (replaces UnitPrice1)
# - ZIP padding fix for US 3- and 4-digit ZIPs (no leading apostrophe; sanitize non [A-Za-z0-9 ] chars)
# - RoutingCode logic: US/NO/FR -> HK-ASS-PF; others -> HKTHZXR
# - Save Excel with ZipCode column set to Text (@) to preserve leading zeros without apostrophes

def process_file(file):
    file_name = file.name.lower()
    try:
        if file_name.endswith(('.xls', '.xlsx', '.xlsm')):
            # Read data from the "YunExpress" sheet
            df = pd.read_excel(file, sheet_name="YunExpress")
        else:
            return f"Unsupported file format: {file_name}", None
    except Exception as e:
        return f"Error reading file: {e}", None

    # === New output headers (exact order) ===
    output_headers = [
        "CustomerOrderNo.", "RoutingCode", "Trackingnumber", "AdditionalServices",
        "ShipmentProtectionPlusService", "CustomDeclaredValue", "SignatureService",
        "VatNumber", "EoriNumber", "IossCode", "ManufactureSalesName",
        "UnifiedSocialCreditCode", "CountryCode", "Name", "CertificateCode", "Company",
        "Street", "City", "Province/State", "ZipCode", "phone", "HouseNumber", "Email",
        "PackageNumber", "PackageWeight", "SenderFiastName", "SenderCompany",
        "SenderStreet", "SenderCity", "SenderProvince", "SenderPostalCode",
        "SenderCountry", "SenderTelephone", "SenderEmail", "SenderUSCI", "PlatformName",
        "PlatformProvince", "PlatformAddress", "PlatformPostalCode", "PlatformPhoneNumber",
        "PlatformEmail", "EcommercePlatformCode", "SalesPlatformLink", "CurrencyCode",
        "SKU1", "ItemDescription1", "ForeignItemDescription1", "DeclaredQuantity1",
        "FOBPrice1", "SellingPrice1", "UnitWeight1", "HsCode1", "Remarks1", "SalesLink1",
        "Materials1", "Use1", "Brand1", "ModelType1", "Specs1", "FabricCreationMethod1",
        "ManufacturerID1", "ManufacturerName1", "ManufacturerCountry1",
        "ManufacturerState1", "ManufacturerCity1", "ManufacturerPostalCode1",
        "ManufacturerAddress1", "CargoCategory", "PaymentPlatform", "PaymentAccount",
        "PaymentTransactionNumber"
    ]

    # Initialize empty output DataFrame
    output_df = pd.DataFrame("", index=range(len(df)), columns=output_headers)

    # 1) Order Number -> CustomerOrderNo. (prefix "LB")
    if "Order Number" in df.columns:
        output_df["CustomerOrderNo."] = "LB" + df["Order Number"].astype(str)

    # 2) Name: First + Last + Company -> Name
    if {"Shipping First Name", "Shipping Last Name", "Shipping Company"}.issubset(df.columns):
        first = df["Shipping First Name"].fillna("").astype(str).str.strip()
        last  = df["Shipping Last Name"].fillna("").astype(str).str.strip()
        comp  = df["Shipping Company"].fillna("").astype(str).str.strip()
        output_df["Name"] = (first + " " + last + " " + comp).str.replace(r"\s+", " ", regex=True).str.strip()

    # 3) Street: Address 1 + Address 2 -> Street
    addr1 = df.get("Shipping Address 1", pd.Series([""] * len(df), index=df.index)).fillna("").astype(str).str.strip()
    addr2 = df.get("Shipping Address 2", pd.Series([""] * len(df), index=df.index)).fillna("").astype(str).str.strip()
    output_df["Street"] = (addr1 + " " + addr2).str.replace(r"\s+", " ", regex=True).str.strip()

    # 4) City
    if "Shipping City" in df.columns:
        output_df["City"] = df["Shipping City"]

    # 5) Province/State
    if "Shipping Province" in df.columns:
        output_df["Province/State"] = df["Shipping Province"]

    # 6) CountryCode (before ZIP handling)
    if "Shipping Country Code" in df.columns:
        output_df["CountryCode"] = df["Shipping Country Code"]

    # 7) ZipCode (sanitize; US 3/4-digit -> pad to 5; do NOT add apostrophe)
    if "Shipping ZIP" in df.columns:
        zip_raw = (
            df["Shipping ZIP"]
            .astype(str)
            .str.strip()
            .str.replace(r"\.0$", "", regex=True)  # clean "1234.0"
        )
        # Keep only letters, digits, spaces (avoid "only letters, numbers and spaces" API error)
        zip_clean = zip_raw.str.replace(r"[^A-Za-z0-9 ]+", "", regex=True)

        mask_us  = output_df["CountryCode"].eq("US")
        # For US: if 3 or 4 numeric digits -> zero-fill to 5
        mask_3_4_digits = zip_clean.str.fullmatch(r"\d{3,4}")
        zip_padded = zip_clean.where(~(mask_us & mask_3_4_digits), zip_clean.str.zfill(5))

        # Do NOT add any leading apostrophe
        output_df["ZipCode"] = zip_padded

    # 8) phone
    if "Shipping Address Phone" in df.columns:
        output_df["phone"] = df["Shipping Address Phone"]

    # 9) NEW: Email -> Email (direct mapping)
    if "Email" in df.columns:
        output_df["Email"] = df["Email"]

    # 10) PackageWeight: Total Weight (g) -> kg
    if "Total Weight" in df.columns:
        output_df["PackageWeight"] = df["Total Weight"] / 1000

    # 11) DeclaredQuantity1: sum Quantity per Order Number
    if "Order Number" in df.columns and "Quantity" in df.columns:
        output_df["DeclaredQuantity1"] = df.groupby("Order Number")["Quantity"].transform("sum")

    # Fixed defaults & RoutingCode
    mask = output_df["CustomerOrderNo."].astype(str).str.len() > 0

    # RoutingCode: HK-ASS-PF if US/NO/FR else HKTHZXR
    mask_us_no_fr = mask & output_df["CountryCode"].isin(["US", "NO", "FR"])
    mask_other    = mask & ~output_df["CountryCode"].isin(["US", "NO", "FR"])
    output_df.loc[mask_us_no_fr, "RoutingCode"] = "HK-ASS-PF"
    output_df.loc[mask_other,    "RoutingCode"] = "HKTHZXR"

    # Pricing / descriptions / weights
    output_df.loc[mask, "FOBPrice1"]               = 2          # CHANGED from UnitPrice1=2
    output_df.loc[mask, "CurrencyCode"]            = "USD"
    output_df.loc[mask, "ItemDescription1"]        = "Eye Cosmetic Accessories"
    output_df.loc[mask, "ForeignItemDescription1"] = "Eye Cosmetic Accessories"
    output_df.loc[mask, "UnitWeight1"]             = 0.02

    # EU AdditionalServices = v1
    EU_COUNTRIES = {"AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI",
                    "FR","HR","HU","IE","IT","LT","LU","LV","MT","NL",
                    "PL","PT","RO","SE","SI","SK","GR"}
    mask_eu = mask & output_df["CountryCode"].isin(EU_COUNTRIES)
    output_df.loc[mask_eu, "AdditionalServices"] = "v1"

    # Remove duplicates by CustomerOrderNo.
    output_df = output_df.drop_duplicates(subset=["CustomerOrderNo."], keep="first")

    # Output filename (HK date)
    hk_tz = pytz.timezone("Asia/Hong_Kong")
    today_hk = datetime.now(hk_tz).strftime("%y%m%d")
    output_file_name = f"yunexpress {today_hk}.xlsx"

    # Save to Excel with ZipCode column forced to Text (@) using xlsxwriter
    try:
        import xlsxwriter
        from xlsxwriter.utility import xl_col_to_name

        with pd.ExcelWriter(output_file_name, engine="xlsxwriter") as writer:
            output_df.to_excel(writer, index=False, sheet_name="Sheet1")
            workbook  = writer.book
            worksheet = writer.sheets["Sheet1"]

            # Text format to preserve leading zeros without adding apostrophes
            text_fmt = workbook.add_format({"num_format": "@"})

            # Locate ZipCode column and set entire column to text
            zip_col_idx = output_df.columns.get_loc("ZipCode")  # 0-based index
            col_letter = xl_col_to_name(zip_col_idx)
            worksheet.set_column(f"{col_letter}:{col_letter}", None, text_fmt)
    except Exception:
        # Fallback to default writer if xlsxwriter not available
        output_df.to_excel(output_file_name, index=False)

    return output_df, output_file_name


# Gradio interface
with gr.Blocks(title="Shipping - YunExpress") as demo:
    gr.Markdown("# Shipping - YunExpress")
    with gr.Row():
        file_input = gr.File(label="Upload Excel File with a YunExpress sheet")
        process_button = gr.Button("Process Data")
    with gr.Row():
        output_data = gr.DataFrame()
        output_file_component = gr.File(label="Download Processed File")
    process_button.click(fn=process_file, inputs=[file_input], outputs=[output_data, output_file_component])

    # Links to other tools
    gr.HTML(
        """
        <div style="text-align: center; font-size: 16px; margin-top: 20px;">
            <h3>Shipping Tools</h3>
            <a href="https://huggingface.co/spaces/leadingbridge/shipping-dhl-e-commerce">DHL</a> | 
            <a href="https://huggingface.co/spaces/leadingbridge/shipping-ec-ship">EC-Ship</a> | 
            <a href="https://huggingface.co/spaces/leadingbridge/shipping-fedex">Fedex</a> | 
            <a href="https://huggingface.co/spaces/leadingbridge/shipping-UPS">UPS</a><br> |
            <a href="https://huggingface.co/spaces/leadingbridge/shipping-yunexpress">Yunexpress</a>
        </div>
        <div style="text-align: center; font-size: 16px; margin-top: 20px;">
            <h3>Administration Tools</h3>
            <a href="https://huggingface.co/spaces/leadingbridge/email-template">Email Template</a> | 
            <a href="https://huggingface.co/spaces/leadingbridge/product-feeding">Google Merchant</a> | 
            <a href="https://huggingface.co/spaces/leadingbridge/tss">Order Processing</a>
        </div>
        """
    )

demo.launch()