[sheet-service] Management price & price slot, set config for any country
This commit is contained in:
parent
6fc7cb5265
commit
7cb95aee83
1 changed files with 394 additions and 190 deletions
584
main.py
584
main.py
|
|
@ -69,32 +69,105 @@ COUNTRY_MAPPING = {
|
|||
"ltu": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_LTU"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_LTU"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_LTU"),
|
||||
"price": os.getenv("SHEET_PRICE_LTU")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_LTU"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_LTU"),
|
||||
"price": os.getenv("DOC_ID_PRICE_LTU")
|
||||
}
|
||||
},
|
||||
"rou": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_ROU"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_ROU"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_ROU"),
|
||||
"price": os.getenv("SHEET_PRICE_ROU")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_ROU"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_ROU"),
|
||||
"price": os.getenv("DOC_ID_PRICE_ROU")
|
||||
}
|
||||
},
|
||||
"hkg": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_HKG"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_HKG"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_HKG")
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_HKG"),
|
||||
"price": os.getenv("SHEET_PRICE_HKG")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_HKG"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_HKG")
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_HKG"),
|
||||
"price": os.getenv("DOC_ID_PRICE_HKG")
|
||||
}
|
||||
},
|
||||
"sgp": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_SGP"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_SGP"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_SGP")
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_SGP"),
|
||||
"price": os.getenv("SHEET_PRICE_SGP")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_SGP"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_SGP")
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_SGP"),
|
||||
"price": os.getenv("DOC_ID_PRICE_SGP")
|
||||
}
|
||||
},
|
||||
"gbr": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_GBR"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_GBR"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_GBR"),
|
||||
"price": os.getenv("SHEET_PRICE_GBR")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_GBR"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_GBR"),
|
||||
"price": os.getenv("DOC_ID_PRICE_GBR")
|
||||
}
|
||||
},
|
||||
"aus": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_AUS"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_AUS"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_AUS"),
|
||||
"price": os.getenv("SHEET_PRICE_AUS")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_AUS"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_AUS"),
|
||||
"price": os.getenv("DOC_ID_PRICE_AUS")
|
||||
}
|
||||
},
|
||||
"uae_dubai": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_UAE_DUBAI"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_UAE_DUBAI"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_UAE_DUBAI"),
|
||||
"price": os.getenv("SHEET_PRICE_UAE_DUBAI")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_UAE_DUBAI"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_UAE_DUBAI"),
|
||||
"price": os.getenv("DOC_ID_PRICE_UAE_DUBAI")
|
||||
}
|
||||
},
|
||||
"mys": {
|
||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_MYS"),
|
||||
"sheets": {
|
||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_MYS"),
|
||||
"name-desc-v2": os.getenv("SHEET_NAME_DESC_V2_MYS"),
|
||||
"price": os.getenv("SHEET_PRICE_MYS")
|
||||
},
|
||||
"grist_doc_id": {
|
||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_MYS"),
|
||||
"name-desc-v2": os.getenv("DOC_ID_NAME_DESC_V2_MYS"),
|
||||
"price": os.getenv("DOC_ID_PRICE_MYS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -278,6 +351,7 @@ def redis_message_handler():
|
|||
catalog = values.get("catalog", "")
|
||||
catalog_name = values.get("catalog_name", "")
|
||||
content = values.get("content", [])
|
||||
option = values.get("option", "")
|
||||
srv_name = payload.get("srv_name", "")
|
||||
|
||||
user_id = user_info.get("uid")
|
||||
|
|
@ -317,7 +391,7 @@ def redis_message_handler():
|
|||
print(f"[{SERVICE_NAME}] Heartbeat Failed: {catalog} | User: {user_id}")
|
||||
continue
|
||||
|
||||
print(f"[{SERVICE_NAME}] Heartbeat success: {catalog} | User: {user_id}")
|
||||
# print(f"[{SERVICE_NAME}] Heartbeat success: {catalog} | User: {user_id}")
|
||||
|
||||
elif channel == EXIT_CHANNEL:
|
||||
if not catalog:
|
||||
|
|
@ -355,7 +429,7 @@ def redis_message_handler():
|
|||
if res.status_code == 200:
|
||||
print(f"[{SERVICE_NAME}] Get catalog success | User: {user_id}")
|
||||
|
||||
print(payload)
|
||||
# print(payload)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -386,13 +460,14 @@ def redis_message_handler():
|
|||
continue
|
||||
|
||||
try:
|
||||
print(f"[{SERVICE_NAME}] Add menu received | User: {user_id} | Content: {content}")
|
||||
handle_add_menu(country, catalog, content)
|
||||
print(f"[{SERVICE_NAME}] Add menu success: {catalog} | User: {user_id}")
|
||||
# print(f"[{SERVICE_NAME}] Add menu success: {catalog} | User: {user_id}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Add menu error: {e}")
|
||||
|
||||
elif channel == ADD_CATALOG_CHANNEL:
|
||||
if not (catalog_name, catalog):
|
||||
if not (catalog_name and catalog):
|
||||
print(f"[{SERVICE_NAME}] Missing required parameters | Channel: {channel} | User: {user_id}")
|
||||
continue
|
||||
|
||||
|
|
@ -448,13 +523,15 @@ def redis_message_handler():
|
|||
traceback.print_exc()
|
||||
|
||||
elif channel == GET_PRICE_CHANNEL:
|
||||
if not (content):
|
||||
print(f"[{SERVICE_NAME}] Missing required parameters | Channel: {channel}")
|
||||
continue
|
||||
|
||||
try:
|
||||
handle_get_price(country, user_id, content)
|
||||
print(f"[{SERVICE_NAME}] Get price menu success: {catalog} | User: {user_id}")
|
||||
if (option and option == "PriceSlot"):
|
||||
handle_get_price_slot(country, user_id)
|
||||
else:
|
||||
if not (content):
|
||||
raise Exception (f"[{SERVICE_NAME}] Missing required parameters | Channel: {channel}")
|
||||
|
||||
handle_get_price(country, user_id, content)
|
||||
# print(f"[{SERVICE_NAME}] Get price menu success: {catalog} | User: {user_id}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Get price menu error: {e}")
|
||||
|
||||
|
|
@ -464,8 +541,7 @@ def redis_message_handler():
|
|||
continue
|
||||
|
||||
try:
|
||||
handle_add_price(country, content)
|
||||
print(f"[{SERVICE_NAME}] Add price menu success: {catalog} | User: {user_id}")
|
||||
handle_add_price(country, content, option)
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Add price menu error: {e}")
|
||||
|
||||
|
|
@ -475,8 +551,7 @@ def redis_message_handler():
|
|||
continue
|
||||
|
||||
try:
|
||||
handle_update_price(country, content)
|
||||
print(f"[{SERVICE_NAME}] Update price menu success: {catalog} | User: {user_id}")
|
||||
handle_update_price(country, content, option)
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Update price menu error: {e}")
|
||||
|
||||
|
|
@ -486,7 +561,7 @@ def redis_message_handler():
|
|||
continue
|
||||
|
||||
try:
|
||||
handle_delete_price(country, content)
|
||||
handle_delete_price(country, content, option)
|
||||
print(f"[{SERVICE_NAME}] Delete price menu success: {catalog} | User: {user_id}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Delete price menu error: {e}")
|
||||
|
|
@ -524,7 +599,7 @@ def find_grist_table_id(doc_id, catalog_suffix):
|
|||
print(f"Error: {e}")
|
||||
return None
|
||||
|
||||
def send_stream_notification(msg_type: str, content: any, batch_id: str, current_chunk: int, total_chunks: int, total_items: int, user_id: str):
|
||||
def send_stream_notification(msg_type: str, content: any, batch_id: str, current_chunk: int, total_chunks: int, total_items: int, user_id: str, ref: str):
|
||||
"""
|
||||
msg_type: "start", "chunk", "end", "error"
|
||||
"""
|
||||
|
|
@ -543,7 +618,7 @@ def send_stream_notification(msg_type: str, content: any, batch_id: str, current
|
|||
"total_chunks": total_chunks,
|
||||
"total_items": total_items,
|
||||
"to": user_id,
|
||||
"ref": "catalog",
|
||||
"ref": ref,
|
||||
"content": content
|
||||
}
|
||||
}
|
||||
|
|
@ -565,7 +640,7 @@ def send_stream_notification(msg_type: str, content: any, batch_id: str, current
|
|||
|
||||
def process_and_stream_sheet_data(country: str, catalog: str, user_id: str):
|
||||
batch_id = str(uuid.uuid4())
|
||||
send_stream_notification("start", {"message": f"Start fetching catalog: {catalog}"}, batch_id, 0, 0, 0, user_id)
|
||||
send_stream_notification("start", {"message": f"Start fetching catalog: {catalog}"}, batch_id, 0, 0, 0, user_id, "catalog")
|
||||
|
||||
try:
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
|
|
@ -665,13 +740,14 @@ def process_and_stream_sheet_data(country: str, catalog: str, user_id: str):
|
|||
start_idx = i * CHUNK_SIZE
|
||||
end_idx = start_idx + CHUNK_SIZE
|
||||
chunk_data = final_result[start_idx:end_idx]
|
||||
send_stream_notification("chunk", chunk_data, batch_id, i + 1, total_chunks, total_items, user_id)
|
||||
send_stream_notification("chunk", chunk_data, batch_id, i + 1, total_chunks, total_items, user_id, "catalog")
|
||||
|
||||
send_stream_notification("end", {"message": "All data sent successfully"}, batch_id, total_chunks, total_chunks, total_items, user_id)
|
||||
send_stream_notification("end", {"message": "All data sent successfully"}, batch_id, total_chunks, total_chunks, total_items, user_id, "catalog")
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
send_stream_notification("error", {"error_detail": str(e)}, batch_id, 0, 0, 0, user_id)
|
||||
send_stream_notification("error", {"error_detail": str(e)}, batch_id, 0, 0, 0, user_id, "catalog")
|
||||
raise Exception (f"Stream data got error: {e}")
|
||||
|
||||
# get catalog
|
||||
def get_catalogs(country: str):
|
||||
|
|
@ -750,156 +826,160 @@ def to_grist_record(row_list):
|
|||
return {"fields": {chr(65 + i): val for i, val in enumerate(row_list)}}
|
||||
|
||||
def handle_add_menu(country: str, catalog: str, content: list):
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
if not config:
|
||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
return
|
||||
try:
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
if not config:
|
||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
return
|
||||
|
||||
grist_docs = config.get("grist_doc_id", {})
|
||||
has_nl = "new-layout" in grist_docs
|
||||
grist_docs = config.get("grist_doc_id", {})
|
||||
has_nl = "new-layout" in grist_docs
|
||||
|
||||
doc_nl = grist_docs.get("new-layout")
|
||||
doc_nv2 = grist_docs.get("new-layout-v2")
|
||||
doc_nd = grist_docs.get("name-desc-v2")
|
||||
doc_nl = grist_docs.get("new-layout")
|
||||
doc_nv2 = grist_docs.get("new-layout-v2")
|
||||
doc_nd = grist_docs.get("name-desc-v2")
|
||||
|
||||
nl_table_id = find_grist_table_id(doc_nl, catalog) if has_nl else None
|
||||
nv2_table_id = find_grist_table_id(doc_nv2, catalog)
|
||||
nd_table_id = "Name_desc_v2"
|
||||
nl_table_id = find_grist_table_id(doc_nl, catalog) if has_nl else None
|
||||
nv2_table_id = find_grist_table_id(doc_nv2, catalog)
|
||||
nd_table_id = "Name_desc_v2"
|
||||
|
||||
if has_nl and not nl_table_id:
|
||||
print(f"[{SERVICE_NAME}] Table for {catalog} not found in doc NL")
|
||||
return
|
||||
if not nv2_table_id:
|
||||
print(f"[{SERVICE_NAME}] Table for {catalog} not found in doc NV2")
|
||||
return
|
||||
if has_nl and not nl_table_id:
|
||||
print(f"[{SERVICE_NAME}] Table for {catalog} not found in doc NL")
|
||||
return
|
||||
if not nv2_table_id:
|
||||
print(f"[{SERVICE_NAME}] Table for {catalog} not found in doc NV2")
|
||||
return
|
||||
|
||||
def norm_val(v): return v if v not in [None, ""] else "-"
|
||||
def norm_val(v): return v if v not in [None, ""] else "-"
|
||||
|
||||
nd_existing_data = fetch_grist_table_data(doc_nd, nd_table_id)
|
||||
existing_keys = set()
|
||||
if nd_existing_data:
|
||||
for row_obj in nd_existing_data:
|
||||
row = row_obj["fields"]
|
||||
key = row[0]
|
||||
if key:
|
||||
existing_keys.add(key)
|
||||
|
||||
# for THA
|
||||
existing_nl_codes = set()
|
||||
if has_nl and nl_table_id:
|
||||
nl_data = fetch_grist_table_data(doc_nl, nl_table_id)
|
||||
if nl_data:
|
||||
for row_obj in nl_data:
|
||||
nd_existing_data = fetch_grist_table_data(doc_nd, nd_table_id)
|
||||
existing_keys = set()
|
||||
if nd_existing_data:
|
||||
for row_obj in nd_existing_data:
|
||||
row = row_obj["fields"]
|
||||
codes = tuple(row[i] if i < len(row) else "-" for i in [6, 7, 8, 10, 11, 12])
|
||||
existing_nl_codes.add(codes)
|
||||
key = row[0]
|
||||
if key:
|
||||
existing_keys.add(key)
|
||||
|
||||
nv2_data = fetch_grist_table_data(doc_nv2, nv2_table_id)
|
||||
existing_nv2_codes = set()
|
||||
if nv2_data:
|
||||
for row_obj in nv2_data:
|
||||
row = row_obj["fields"]
|
||||
is_name = row[1] if len(row) > 1 else ""
|
||||
if is_name == "name":
|
||||
codes = (norm_val(row[8] if len(row) > 8 else "-"),
|
||||
norm_val(row[9] if len(row) > 9 else "-"),
|
||||
norm_val(row[10] if len(row) > 10 else "-"))
|
||||
existing_nv2_codes.add(codes)
|
||||
# for THA
|
||||
existing_nl_codes = set()
|
||||
if has_nl and nl_table_id:
|
||||
nl_data = fetch_grist_table_data(doc_nl, nl_table_id)
|
||||
if nl_data:
|
||||
for row_obj in nl_data:
|
||||
row = row_obj["fields"]
|
||||
codes = tuple(row[i] if i < len(row) else "-" for i in [6, 7, 8, 10, 11, 12])
|
||||
existing_nl_codes.add(codes)
|
||||
|
||||
nl_records = []
|
||||
nv2_records = []
|
||||
nd_records = []
|
||||
nv2_data = fetch_grist_table_data(doc_nv2, nv2_table_id)
|
||||
existing_nv2_codes = set()
|
||||
if nv2_data:
|
||||
for row_obj in nv2_data:
|
||||
row = row_obj["fields"]
|
||||
is_name = row[1] if len(row) > 1 else ""
|
||||
if is_name == "name":
|
||||
codes = (norm_val(row[8] if len(row) > 8 else "-"),
|
||||
norm_val(row[9] if len(row) > 9 else "-"),
|
||||
norm_val(row[10] if len(row) > 10 else "-"))
|
||||
existing_nv2_codes.add(codes)
|
||||
|
||||
for item in content:
|
||||
cells = item.get("cells", [])
|
||||
if not cells:
|
||||
continue
|
||||
|
||||
payload_lang = item.get("payload", {})
|
||||
lang_name = payload_lang.get("lang_name", ["", "", "", ""])
|
||||
lang_desc = payload_lang.get("lang_desc", ["", "", "", ""])
|
||||
nl_records = []
|
||||
nv2_records = []
|
||||
nd_records = []
|
||||
|
||||
# ==========================================
|
||||
# New-Layout (THA)
|
||||
# ==========================================
|
||||
if has_nl:
|
||||
current_nl_tuple = tuple(norm_val(get_val(cells, i)) for i in [6,7,8,10,11,12])
|
||||
if current_nl_tuple not in existing_nl_codes:
|
||||
nl_records.append(to_grist_record(cells))
|
||||
existing_nl_codes.add(current_nl_tuple)
|
||||
for item in content:
|
||||
cells = item.get("cells", [])
|
||||
if not cells:
|
||||
continue
|
||||
|
||||
payload_lang = item.get("payload", {})
|
||||
lang_name = payload_lang.get("lang_name", ["", "", "", ""])
|
||||
lang_desc = payload_lang.get("lang_desc", ["", "", "", ""])
|
||||
|
||||
# ==========================================
|
||||
# New-Layout-V2 (All country)
|
||||
# ==========================================
|
||||
hot_code = f"{get_val(cells, 6)},{get_val(cells, 10)}"
|
||||
cold_code = f"{get_val(cells, 7)},{get_val(cells, 11)}"
|
||||
blend_code = f"{get_val(cells, 8)},{get_val(cells, 12)}"
|
||||
current_nv2_tuple = (hot_code, cold_code, blend_code)
|
||||
# ==========================================
|
||||
# New-Layout (THA)
|
||||
# ==========================================
|
||||
if has_nl:
|
||||
current_nl_tuple = tuple(norm_val(get_val(cells, i)) for i in [6,7,8,10,11,12])
|
||||
if current_nl_tuple not in existing_nl_codes:
|
||||
nl_records.append(to_grist_record(cells))
|
||||
existing_nl_codes.add(current_nl_tuple)
|
||||
|
||||
if current_nv2_tuple not in existing_nv2_codes:
|
||||
name_row = ["", "name", get_val(cells, 3), get_val(cells, 2), get_val(lang_name, 0), get_val(lang_name, 1), get_val(lang_name, 2), get_val(lang_name, 3),
|
||||
hot_code, cold_code, blend_code, "", "", "", "", "", "", "",
|
||||
get_val(cells, 14), get_val(cells, 15), get_val(cells, 16), get_val(cells, 17), get_val(cells, 18)]
|
||||
|
||||
desc_row = ["", "desc", get_val(cells, 5), get_val(cells, 4), get_val(lang_desc, 0), get_val(lang_desc, 1), get_val(lang_desc, 2), get_val(lang_desc, 3),
|
||||
"||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "", "", "", "", "", "", "",
|
||||
# ==========================================
|
||||
# New-Layout-V2 (All country)
|
||||
# ==========================================
|
||||
hot_code = f"{get_val(cells, 6)},{get_val(cells, 10)}"
|
||||
cold_code = f"{get_val(cells, 7)},{get_val(cells, 11)}"
|
||||
blend_code = f"{get_val(cells, 8)},{get_val(cells, 12)}"
|
||||
current_nv2_tuple = (hot_code, cold_code, blend_code)
|
||||
|
||||
if current_nv2_tuple not in existing_nv2_codes:
|
||||
name_row = ["", "name", get_val(cells, 3), get_val(cells, 2), get_val(lang_name, 0), get_val(lang_name, 1), get_val(lang_name, 2), get_val(lang_name, 3),
|
||||
hot_code, cold_code, blend_code, "", "", "", "", "", "", "",
|
||||
get_val(cells, 14), get_val(cells, 15), get_val(cells, 16), get_val(cells, 17), get_val(cells, 18)]
|
||||
|
||||
desc_row = ["", "desc", get_val(cells, 5), get_val(cells, 4), get_val(lang_desc, 0), get_val(lang_desc, 1), get_val(lang_desc, 2), get_val(lang_desc, 3),
|
||||
"||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "", "", "", "", "", "", "",
|
||||
"-", "-", "-", "-", "-"]
|
||||
|
||||
img_row = ["", "img", get_val(cells, 9), "-", "-", "-", "-", "-", get_val(cells, 13),
|
||||
"||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "", "", "", "", "", "", "",
|
||||
"-", "-", "-", "-", "-"]
|
||||
|
||||
blank_row = [""] * 23
|
||||
|
||||
nv2_records.extend([to_grist_record(name_row), to_grist_record(desc_row), to_grist_record(img_row), to_grist_record(blank_row)])
|
||||
existing_nv2_codes.add(current_nv2_tuple)
|
||||
|
||||
# ==========================================
|
||||
# Name-desc-V2 (All country)
|
||||
# ==========================================
|
||||
product_code_indices = [6, 7, 8, 10, 11, 12]
|
||||
|
||||
img_row = ["", "img", get_val(cells, 9), "-", "-", "-", "-", "-", get_val(cells, 13),
|
||||
"||||||||||||||||||||||||||", "||||||||||||||||||||||||||", "", "", "", "", "", "", "",
|
||||
"-", "-", "-", "-", "-"]
|
||||
|
||||
blank_row = [""] * 23
|
||||
for p_idx in product_code_indices:
|
||||
code = get_val(cells, p_idx)
|
||||
if code == "-" or not code:
|
||||
continue
|
||||
|
||||
nv2_records.extend([to_grist_record(name_row), to_grist_record(desc_row), to_grist_record(img_row), to_grist_record(blank_row)])
|
||||
existing_nv2_codes.add(current_nv2_tuple)
|
||||
menu_name_key = f"MENU.{code}.name"
|
||||
|
||||
if menu_name_key in existing_keys:
|
||||
continue
|
||||
|
||||
# ==========================================
|
||||
# Name-desc-V2 (All country)
|
||||
# ==========================================
|
||||
product_code_indices = [6, 7, 8, 10, 11, 12]
|
||||
|
||||
for p_idx in product_code_indices:
|
||||
code = get_val(cells, p_idx)
|
||||
if code == "-" or not code:
|
||||
continue
|
||||
parts = code.split('-')
|
||||
drink_type = "UNKNOWN"
|
||||
drink_type_th = ""
|
||||
|
||||
menu_name_key = f"MENU.{code}.name"
|
||||
|
||||
if menu_name_key in existing_keys:
|
||||
continue
|
||||
if len(parts) >= 3:
|
||||
type_id = parts[2]
|
||||
if type_id == "01":
|
||||
drink_type = "HOT"
|
||||
drink_type_th = "ร้อน"
|
||||
elif type_id == "02":
|
||||
drink_type = "ICED"
|
||||
drink_type_th = "เย็น"
|
||||
elif type_id == "03":
|
||||
drink_type = "SMOOTHIE"
|
||||
drink_type_th = "ปั่น"
|
||||
|
||||
parts = code.split('-')
|
||||
drink_type = "UNKNOWN"
|
||||
drink_type_th = ""
|
||||
name_en = get_val(cells, 3, "")
|
||||
name_th = get_val(cells, 2, "")
|
||||
|
||||
if len(parts) >= 3:
|
||||
type_id = parts[2]
|
||||
if type_id == "01":
|
||||
drink_type = "HOT"
|
||||
drink_type_th = "ร้อน"
|
||||
elif type_id == "02":
|
||||
drink_type = "ICED"
|
||||
drink_type_th = "เย็น"
|
||||
elif type_id == "03":
|
||||
drink_type = "SMOOTHIE"
|
||||
drink_type_th = "ปั่น"
|
||||
if drink_type == "SMOOTHIE":
|
||||
prefix = f"{name_en} {drink_type}".strip()
|
||||
else:
|
||||
prefix = f"{drink_type} {name_en}".strip()
|
||||
|
||||
name_en = get_val(cells, 3, "")
|
||||
name_th = get_val(cells, 2, "")
|
||||
prefix_th = f"{name_th} {drink_type_th}".strip() if drink_type_th else name_th
|
||||
|
||||
if drink_type == "SMOOTHIE":
|
||||
prefix = f"{name_en} {drink_type}".strip()
|
||||
else:
|
||||
prefix = f"{drink_type} {name_en}".strip()
|
||||
nd_name_row = [menu_name_key, get_val(cells, 9), prefix, prefix_th, get_val(lang_name, 0), get_val(lang_name, 1), get_val(lang_name, 2), get_val(lang_name, 3)]
|
||||
nd_desc_row = [f"MENU.{code}.desc", "-", get_val(cells, 5), get_val(cells, 4), get_val(lang_desc, 0), get_val(lang_desc, 1), get_val(lang_desc, 2), get_val(lang_desc, 3)]
|
||||
|
||||
prefix_th = f"{name_th} {drink_type_th}".strip() if drink_type_th else name_th
|
||||
|
||||
nd_name_row = [menu_name_key, get_val(cells, 9), prefix, prefix_th, get_val(lang_name, 0), get_val(lang_name, 1), get_val(lang_name, 2), get_val(lang_name, 3)]
|
||||
nd_desc_row = [f"MENU.{code}.desc", "-", get_val(cells, 5), get_val(cells, 4), get_val(lang_desc, 0), get_val(lang_desc, 1), get_val(lang_desc, 2), get_val(lang_desc, 3)]
|
||||
|
||||
nd_records.extend([to_grist_record(nd_name_row), to_grist_record(nd_desc_row)])
|
||||
existing_keys.add(menu_name_key)
|
||||
nd_records.extend([to_grist_record(nd_name_row), to_grist_record(nd_desc_row)])
|
||||
existing_keys.add(menu_name_key)
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] handle_add_menu error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
||||
|
||||
|
|
@ -915,11 +995,11 @@ def handle_add_menu(country: str, catalog: str, content: list):
|
|||
json={"records": records}
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
print(f"Error adding to {table_id}: {resp.text}")
|
||||
print(f"[{SERVICE_NAME}] Error adding to {table_id}: {resp.text}")
|
||||
except Exception as e:
|
||||
print(f"Request failed for {table_id}: {e}")
|
||||
print(f"[{SERVICE_NAME}] Request failed for {table_id}: {e}")
|
||||
|
||||
# NL (เฉพาะ THA)
|
||||
# NL (THA)
|
||||
if has_nl and nl_records:
|
||||
add_records_to_grist(doc_nl, nl_table_id, nl_records)
|
||||
|
||||
|
|
@ -1654,14 +1734,23 @@ def handle_get_price(country: str, user_id: str, content: list):
|
|||
|
||||
if res.status_code == 200:
|
||||
print(f"[{SERVICE_NAME}] Price data sent to user: {user_id} | Products: {len(result)}")
|
||||
print(f"[{SERVICE_NAME}] Get price data to user: {user_id} | data: {payload}")
|
||||
else:
|
||||
# print(f"[{SERVICE_NAME}] Response error: {res}")
|
||||
error_detail = res.text
|
||||
try:
|
||||
error_detail = res.json()
|
||||
except:
|
||||
pass
|
||||
print(f"[{SERVICE_NAME}] Notify server response: {res.status_code} | {error_detail}")
|
||||
|
||||
print(result)
|
||||
# print(result)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Get price error: {e}")
|
||||
traceback.print_exc()
|
||||
# print(f"[{SERVICE_NAME}] Get price error: {e}")
|
||||
# traceback.print_exc()
|
||||
|
||||
if FRONTEND_NOTIFY_URL:
|
||||
payload = {
|
||||
|
|
@ -1682,22 +1771,117 @@ def handle_get_price(country: str, user_id: str, content: list):
|
|||
json=payload,
|
||||
)
|
||||
|
||||
raise
|
||||
raise Exception(f"[{SERVICE_NAME}] Get price error: {e}")
|
||||
|
||||
def handle_get_price_slot(country: str, user_id: str):
|
||||
batch_id = str(uuid.uuid4())
|
||||
|
||||
try:
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
if not config:
|
||||
raise Exception(f"Country {country} not found")
|
||||
|
||||
grist_docs = config.get("grist_doc_id", {})
|
||||
doc_price = grist_docs.get("price")
|
||||
|
||||
if not doc_price:
|
||||
raise Exception(f"Price doc not found for country {country}")
|
||||
|
||||
def handle_add_price(country: str, content: list):
|
||||
# 1. ดึงรายชื่อ Table ทั้งหมดใน Document Price
|
||||
all_tables = get_all_grist_tables(doc_price)
|
||||
|
||||
# 2. ค้นหา Table ที่เข้าเงื่อนไข PriceSlot[index]
|
||||
price_slot_pattern = re.compile(r"^PriceSlot[1-9]\d*$")
|
||||
matching_tables = []
|
||||
|
||||
for t_id in all_tables:
|
||||
table_name = reconstruct_table_name(t_id)
|
||||
if price_slot_pattern.match(table_name) or price_slot_pattern.match(t_id):
|
||||
matching_tables.append({
|
||||
"id": t_id,
|
||||
"name": table_name if price_slot_pattern.match(table_name) else t_id
|
||||
})
|
||||
|
||||
total_tables = len(matching_tables)
|
||||
|
||||
send_stream_notification(
|
||||
"start",
|
||||
{"message": f"Start fetching {total_tables} price slots"},
|
||||
batch_id, 0, total_tables, total_tables, user_id, "price"
|
||||
)
|
||||
|
||||
if total_tables == 0:
|
||||
send_stream_notification("end", {"message": "No PriceSlot tables found"}, batch_id, 0, 0, 0, user_id, "price")
|
||||
return
|
||||
|
||||
headers_grist = {"Authorization": f"Bearer {GRIST_API_KEY}"}
|
||||
|
||||
for current_idx, table_info in enumerate(matching_tables, start=1):
|
||||
t_id = table_info["id"]
|
||||
t_name = table_info["name"]
|
||||
|
||||
meta_url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/{t_id}/columns"
|
||||
resp_meta = request_with_retry("GET", meta_url, headers=headers_grist)
|
||||
|
||||
header = []
|
||||
if resp_meta.status_code == 200:
|
||||
columns = resp_meta.json().get("columns", [])
|
||||
sorted_cols = sorted(columns, key=lambda c: col_to_index(c.get("id", "A")))
|
||||
header = [c.get("fields", {}).get("label", "") for c in sorted_cols]
|
||||
|
||||
table_data = fetch_grist_table_data(doc_price, t_id)
|
||||
|
||||
payload = []
|
||||
for row_obj in table_data:
|
||||
row_id = row_obj["id"]
|
||||
fields = row_obj["fields"]
|
||||
|
||||
cells = []
|
||||
for col_idx, val in enumerate(fields):
|
||||
cells.append({
|
||||
"value": str(val) if val else "",
|
||||
"coord": {
|
||||
"row": row_id,
|
||||
"col": col_idx + 1
|
||||
}
|
||||
})
|
||||
|
||||
payload.append({
|
||||
"row_index": row_id,
|
||||
"cells": cells
|
||||
})
|
||||
|
||||
chunk_data = [{
|
||||
"sheet": t_name,
|
||||
"header": header,
|
||||
"payload": payload
|
||||
}]
|
||||
|
||||
send_stream_notification("chunk", chunk_data, batch_id, current_idx, total_tables, total_tables, user_id, "price")
|
||||
|
||||
send_stream_notification(
|
||||
"end",
|
||||
{"message": "All price slots sent successfully"},
|
||||
batch_id, total_tables, total_tables, total_tables, user_id, "price"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
send_stream_notification("error", {"error_detail": str(e)}, batch_id, 0, 0, 0, user_id, "price")
|
||||
|
||||
def handle_add_price(country: str, content: list, option: str = ""):
|
||||
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
if not config:
|
||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
|
||||
grist_docs = config.get("grist_doc_id", {})
|
||||
doc_price = grist_docs.get("price")
|
||||
|
||||
if not doc_price:
|
||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
|
||||
table_id = option if option else "Price"
|
||||
|
||||
records = []
|
||||
for item in content:
|
||||
|
|
@ -1706,11 +1890,10 @@ def handle_add_price(country: str, content: list):
|
|||
records.append(to_grist_record(cells))
|
||||
|
||||
if not records:
|
||||
print(f"[{SERVICE_NAME}] No price records to add")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] No price records to add")
|
||||
|
||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
||||
url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/Price/records"
|
||||
url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/{table_id}/records"
|
||||
|
||||
try:
|
||||
resp = request_with_retry(
|
||||
|
|
@ -1724,10 +1907,10 @@ def handle_add_price(country: str, content: list):
|
|||
else:
|
||||
print(f"[{SERVICE_NAME}] Error adding price records: {resp.text}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Request failed for add price: {e}")
|
||||
raise Exception (f"[{SERVICE_NAME}] Request failed for add price: {e}")
|
||||
|
||||
|
||||
def handle_update_price(country: str, content: list):
|
||||
def handle_update_price(country: str, content: list, option: str = ""):
|
||||
"""
|
||||
Update price to Grist
|
||||
content: [{"row_index": 361, "cells": [{"value": "x", "coord": {...}}, ...]}, ...]
|
||||
|
|
@ -1741,8 +1924,9 @@ def handle_update_price(country: str, content: list):
|
|||
doc_price = grist_docs.get("price")
|
||||
|
||||
if not doc_price:
|
||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
|
||||
table_id = option if option else "Price"
|
||||
|
||||
records_to_update = []
|
||||
|
||||
|
|
@ -1764,11 +1948,10 @@ def handle_update_price(country: str, content: list):
|
|||
})
|
||||
|
||||
if not records_to_update:
|
||||
print(f"[{SERVICE_NAME}] No price records to update")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] No price records to update")
|
||||
|
||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
||||
update_url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/Price/records"
|
||||
update_url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/{table_id}/records"
|
||||
|
||||
# Group by field keys (Grist PATCH requirement)
|
||||
grouped_updates = {}
|
||||
|
|
@ -1791,33 +1974,32 @@ def handle_update_price(country: str, content: list):
|
|||
else:
|
||||
print(f"[{SERVICE_NAME}] Price update failed: {resp.text}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Request failed for update price: {e}")
|
||||
raise Exception (f"[{SERVICE_NAME}] Request failed for update price: {e}")
|
||||
|
||||
|
||||
def handle_delete_price(country: str, content: list):
|
||||
def handle_delete_price(country: str, content: list, option: str = ""):
|
||||
"""
|
||||
content: [{"target_id": 389}, {"target_id": 393}]
|
||||
"""
|
||||
config = COUNTRY_MAPPING.get(country)
|
||||
if not config:
|
||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Country {country} config not found")
|
||||
|
||||
grist_docs = config.get("grist_doc_id", {})
|
||||
doc_price = grist_docs.get("price")
|
||||
|
||||
if not doc_price:
|
||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||
|
||||
table_id = option if option else "Price"
|
||||
|
||||
target_ids = [int(item.get("target_id")) for item in content if item.get("target_id")]
|
||||
|
||||
if not target_ids:
|
||||
print(f"[{SERVICE_NAME}] Payload to delete price is empty")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Payload to delete price is empty")
|
||||
|
||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
||||
del_url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/Price/records/delete"
|
||||
del_url = f"{GRIST_URL.rstrip('/')}/api/docs/{doc_price}/tables/{table_id}/records/delete"
|
||||
|
||||
try:
|
||||
resp = request_with_retry(
|
||||
|
|
@ -1828,7 +2010,7 @@ def handle_delete_price(country: str, content: list):
|
|||
)
|
||||
print(f"[{SERVICE_NAME}] Deleted Price IDs: {target_ids} | Status: {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Delete price error: {e}")
|
||||
raise Exception (f"[{SERVICE_NAME}] Delete price error: {e}")
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
|
@ -2131,8 +2313,7 @@ def get_column_letter(n):
|
|||
|
||||
def sync_sheets_to_grist(country_key):
|
||||
if country_key not in COUNTRY_MAPPING:
|
||||
print(f"Error: Country '{country_key}' not found.")
|
||||
return
|
||||
raise Exception (f"[{SERVICE_NAME}] Error: Country '{country_key}' not found.")
|
||||
|
||||
config = COUNTRY_MAPPING[country_key]
|
||||
spreadsheet_id = config["spreadsheet_id"]
|
||||
|
|
@ -2144,17 +2325,17 @@ def sync_sheets_to_grist(country_key):
|
|||
|
||||
for sheet_key, sheet_name in sheets.items():
|
||||
if not sheet_name:
|
||||
continue
|
||||
raise Exception (f"[{SERVICE_NAME}] Error: Sheet: '{sheet_name}' not found.")
|
||||
|
||||
doc_id = grist_doc_ids.get(sheet_key)
|
||||
if not doc_id:
|
||||
continue
|
||||
raise Exception (f"[{SERVICE_NAME}] Error: DocumentId: '{doc_id}' not found.")
|
||||
|
||||
try:
|
||||
worksheet = spreadsheet.worksheet(sheet_name)
|
||||
all_values = worksheet.get_all_values()
|
||||
if not all_values:
|
||||
continue
|
||||
raise
|
||||
|
||||
header = all_values[0]
|
||||
data_rows = all_values[1:]
|
||||
|
|
@ -2163,11 +2344,34 @@ def sync_sheets_to_grist(country_key):
|
|||
process_new_layout_sheet(doc_id, header, data_rows)
|
||||
elif sheet_key == "name-desc-v2":
|
||||
upload_to_grist_self_hosted(doc_id, "name-desc-v2", header, data_rows)
|
||||
# elif sheet_key == "price":
|
||||
# upload_to_grist_self_hosted(doc_id, "price", header, data_rows)
|
||||
elif sheet_key == "price":
|
||||
upload_to_grist_self_hosted(doc_id, "price", header, data_rows)
|
||||
|
||||
all_worksheets = spreadsheet.worksheets()
|
||||
|
||||
price_slot_pattern = re.compile(r"^PriceSlot[1-9]\d*$")
|
||||
|
||||
for ws in all_worksheets:
|
||||
ws_name = ws.title
|
||||
|
||||
if price_slot_pattern.match(ws_name):
|
||||
try:
|
||||
slot_values = ws.get_all_values()
|
||||
if not slot_values:
|
||||
raise
|
||||
|
||||
slot_header = slot_values[0]
|
||||
slot_data = slot_values[1:]
|
||||
|
||||
upload_to_grist_self_hosted(doc_id, ws_name, slot_header, slot_data)
|
||||
|
||||
except Exception as slot_err:
|
||||
raise Exception (f"[{SERVICE_NAME}] Error processing dynamic sheet {ws_name}: {slot_err}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[{SERVICE_NAME}] Error processing sheet {sheet_name}: {e}")
|
||||
raise Exception (f"[{SERVICE_NAME}] Error processing sheet {sheet_name}: {e}")
|
||||
|
||||
def process_new_layout_sheet(doc_id, header, data_rows):
|
||||
table_groups = {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue