[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
328
main.py
328
main.py
|
|
@ -69,32 +69,105 @@ COUNTRY_MAPPING = {
|
||||||
"ltu": {
|
"ltu": {
|
||||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_LTU"),
|
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_LTU"),
|
||||||
"sheets": {
|
"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")
|
"price": os.getenv("SHEET_PRICE_LTU")
|
||||||
},
|
},
|
||||||
"grist_doc_id": {
|
"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")
|
"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": {
|
"hkg": {
|
||||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_HKG"),
|
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_HKG"),
|
||||||
"sheets": {
|
"sheets": {
|
||||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_HKG"),
|
"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": {
|
"grist_doc_id": {
|
||||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_HKG"),
|
"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": {
|
"sgp": {
|
||||||
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_SGP"),
|
"spreadsheet_id": os.getenv("SPREAD_SHEET_ID_SGP"),
|
||||||
"sheets": {
|
"sheets": {
|
||||||
"new-layout-v2": os.getenv("SHEET_NEW_LAYOUT_V2_SGP"),
|
"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": {
|
"grist_doc_id": {
|
||||||
"new-layout-v2": os.getenv("DOC_ID_NEW_LAYOUT_V2_SGP"),
|
"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 = values.get("catalog", "")
|
||||||
catalog_name = values.get("catalog_name", "")
|
catalog_name = values.get("catalog_name", "")
|
||||||
content = values.get("content", [])
|
content = values.get("content", [])
|
||||||
|
option = values.get("option", "")
|
||||||
srv_name = payload.get("srv_name", "")
|
srv_name = payload.get("srv_name", "")
|
||||||
|
|
||||||
user_id = user_info.get("uid")
|
user_id = user_info.get("uid")
|
||||||
|
|
@ -317,7 +391,7 @@ def redis_message_handler():
|
||||||
print(f"[{SERVICE_NAME}] Heartbeat Failed: {catalog} | User: {user_id}")
|
print(f"[{SERVICE_NAME}] Heartbeat Failed: {catalog} | User: {user_id}")
|
||||||
continue
|
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:
|
elif channel == EXIT_CHANNEL:
|
||||||
if not catalog:
|
if not catalog:
|
||||||
|
|
@ -355,7 +429,7 @@ def redis_message_handler():
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
print(f"[{SERVICE_NAME}] Get catalog success | User: {user_id}")
|
print(f"[{SERVICE_NAME}] Get catalog success | User: {user_id}")
|
||||||
|
|
||||||
print(payload)
|
# print(payload)
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -386,13 +460,14 @@ def redis_message_handler():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
print(f"[{SERVICE_NAME}] Add menu received | User: {user_id} | Content: {content}")
|
||||||
handle_add_menu(country, catalog, 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:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Add menu error: {e}")
|
print(f"[{SERVICE_NAME}] Add menu error: {e}")
|
||||||
|
|
||||||
elif channel == ADD_CATALOG_CHANNEL:
|
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}")
|
print(f"[{SERVICE_NAME}] Missing required parameters | Channel: {channel} | User: {user_id}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -448,13 +523,15 @@ def redis_message_handler():
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
elif channel == GET_PRICE_CHANNEL:
|
elif channel == GET_PRICE_CHANNEL:
|
||||||
if not (content):
|
|
||||||
print(f"[{SERVICE_NAME}] Missing required parameters | Channel: {channel}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
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)
|
handle_get_price(country, user_id, content)
|
||||||
print(f"[{SERVICE_NAME}] Get price menu success: {catalog} | User: {user_id}")
|
# print(f"[{SERVICE_NAME}] Get price menu success: {catalog} | User: {user_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Get price menu error: {e}")
|
print(f"[{SERVICE_NAME}] Get price menu error: {e}")
|
||||||
|
|
||||||
|
|
@ -464,8 +541,7 @@ def redis_message_handler():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle_add_price(country, content)
|
handle_add_price(country, content, option)
|
||||||
print(f"[{SERVICE_NAME}] Add price menu success: {catalog} | User: {user_id}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Add price menu error: {e}")
|
print(f"[{SERVICE_NAME}] Add price menu error: {e}")
|
||||||
|
|
||||||
|
|
@ -475,8 +551,7 @@ def redis_message_handler():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle_update_price(country, content)
|
handle_update_price(country, content, option)
|
||||||
print(f"[{SERVICE_NAME}] Update price menu success: {catalog} | User: {user_id}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Update price menu error: {e}")
|
print(f"[{SERVICE_NAME}] Update price menu error: {e}")
|
||||||
|
|
||||||
|
|
@ -486,7 +561,7 @@ def redis_message_handler():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle_delete_price(country, content)
|
handle_delete_price(country, content, option)
|
||||||
print(f"[{SERVICE_NAME}] Delete price menu success: {catalog} | User: {user_id}")
|
print(f"[{SERVICE_NAME}] Delete price menu success: {catalog} | User: {user_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Delete price menu error: {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}")
|
print(f"Error: {e}")
|
||||||
return None
|
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"
|
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_chunks": total_chunks,
|
||||||
"total_items": total_items,
|
"total_items": total_items,
|
||||||
"to": user_id,
|
"to": user_id,
|
||||||
"ref": "catalog",
|
"ref": ref,
|
||||||
"content": content
|
"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):
|
def process_and_stream_sheet_data(country: str, catalog: str, user_id: str):
|
||||||
batch_id = str(uuid.uuid4())
|
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:
|
try:
|
||||||
config = COUNTRY_MAPPING.get(country)
|
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
|
start_idx = i * CHUNK_SIZE
|
||||||
end_idx = start_idx + CHUNK_SIZE
|
end_idx = start_idx + CHUNK_SIZE
|
||||||
chunk_data = final_result[start_idx:end_idx]
|
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:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
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
|
# get catalog
|
||||||
def get_catalogs(country: str):
|
def get_catalogs(country: str):
|
||||||
|
|
@ -750,6 +826,7 @@ def to_grist_record(row_list):
|
||||||
return {"fields": {chr(65 + i): val for i, val in enumerate(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):
|
def handle_add_menu(country: str, catalog: str, content: list):
|
||||||
|
try:
|
||||||
config = COUNTRY_MAPPING.get(country)
|
config = COUNTRY_MAPPING.get(country)
|
||||||
if not config:
|
if not config:
|
||||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
||||||
|
|
@ -900,6 +977,9 @@ def handle_add_menu(country: str, catalog: str, content: list):
|
||||||
|
|
||||||
nd_records.extend([to_grist_record(nd_name_row), to_grist_record(nd_desc_row)])
|
nd_records.extend([to_grist_record(nd_name_row), to_grist_record(nd_desc_row)])
|
||||||
existing_keys.add(menu_name_key)
|
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"}
|
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}
|
json={"records": records}
|
||||||
)
|
)
|
||||||
if resp.status_code != 200:
|
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:
|
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:
|
if has_nl and nl_records:
|
||||||
add_records_to_grist(doc_nl, nl_table_id, 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:
|
if res.status_code == 200:
|
||||||
print(f"[{SERVICE_NAME}] Price data sent to user: {user_id} | Products: {len(result)}")
|
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
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Get price error: {e}")
|
# print(f"[{SERVICE_NAME}] Get price error: {e}")
|
||||||
traceback.print_exc()
|
# traceback.print_exc()
|
||||||
|
|
||||||
if FRONTEND_NOTIFY_URL:
|
if FRONTEND_NOTIFY_URL:
|
||||||
payload = {
|
payload = {
|
||||||
|
|
@ -1682,23 +1771,118 @@ def handle_get_price(country: str, user_id: str, content: list):
|
||||||
json=payload,
|
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())
|
||||||
|
|
||||||
def handle_add_price(country: str, content: list):
|
try:
|
||||||
|
|
||||||
config = COUNTRY_MAPPING.get(country)
|
config = COUNTRY_MAPPING.get(country)
|
||||||
if not config:
|
if not config:
|
||||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
raise Exception(f"Country {country} not found")
|
||||||
return
|
|
||||||
|
|
||||||
grist_docs = config.get("grist_doc_id", {})
|
grist_docs = config.get("grist_doc_id", {})
|
||||||
doc_price = grist_docs.get("price")
|
doc_price = grist_docs.get("price")
|
||||||
|
|
||||||
if not doc_price:
|
if not doc_price:
|
||||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
raise Exception(f"Price doc not found for country {country}")
|
||||||
|
|
||||||
|
# 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
|
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:
|
||||||
|
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:
|
||||||
|
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||||
|
|
||||||
|
table_id = option if option else "Price"
|
||||||
|
|
||||||
records = []
|
records = []
|
||||||
for item in content:
|
for item in content:
|
||||||
cells = item.get("cells", [])
|
cells = item.get("cells", [])
|
||||||
|
|
@ -1706,11 +1890,10 @@ def handle_add_price(country: str, content: list):
|
||||||
records.append(to_grist_record(cells))
|
records.append(to_grist_record(cells))
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
print(f"[{SERVICE_NAME}] No price records to add")
|
raise Exception (f"[{SERVICE_NAME}] No price records to add")
|
||||||
return
|
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
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:
|
try:
|
||||||
resp = request_with_retry(
|
resp = request_with_retry(
|
||||||
|
|
@ -1724,10 +1907,10 @@ def handle_add_price(country: str, content: list):
|
||||||
else:
|
else:
|
||||||
print(f"[{SERVICE_NAME}] Error adding price records: {resp.text}")
|
print(f"[{SERVICE_NAME}] Error adding price records: {resp.text}")
|
||||||
except Exception as e:
|
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
|
Update price to Grist
|
||||||
content: [{"row_index": 361, "cells": [{"value": "x", "coord": {...}}, ...]}, ...]
|
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")
|
doc_price = grist_docs.get("price")
|
||||||
|
|
||||||
if not doc_price:
|
if not doc_price:
|
||||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||||
return
|
|
||||||
|
table_id = option if option else "Price"
|
||||||
|
|
||||||
records_to_update = []
|
records_to_update = []
|
||||||
|
|
||||||
|
|
@ -1764,11 +1948,10 @@ def handle_update_price(country: str, content: list):
|
||||||
})
|
})
|
||||||
|
|
||||||
if not records_to_update:
|
if not records_to_update:
|
||||||
print(f"[{SERVICE_NAME}] No price records to update")
|
raise Exception (f"[{SERVICE_NAME}] No price records to update")
|
||||||
return
|
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
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)
|
# Group by field keys (Grist PATCH requirement)
|
||||||
grouped_updates = {}
|
grouped_updates = {}
|
||||||
|
|
@ -1791,33 +1974,32 @@ def handle_update_price(country: str, content: list):
|
||||||
else:
|
else:
|
||||||
print(f"[{SERVICE_NAME}] Price update failed: {resp.text}")
|
print(f"[{SERVICE_NAME}] Price update failed: {resp.text}")
|
||||||
except Exception as e:
|
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}]
|
content: [{"target_id": 389}, {"target_id": 393}]
|
||||||
"""
|
"""
|
||||||
config = COUNTRY_MAPPING.get(country)
|
config = COUNTRY_MAPPING.get(country)
|
||||||
if not config:
|
if not config:
|
||||||
print(f"[{SERVICE_NAME}] Country {country} config not found")
|
raise Exception (f"[{SERVICE_NAME}] Country {country} config not found")
|
||||||
return
|
|
||||||
|
|
||||||
grist_docs = config.get("grist_doc_id", {})
|
grist_docs = config.get("grist_doc_id", {})
|
||||||
doc_price = grist_docs.get("price")
|
doc_price = grist_docs.get("price")
|
||||||
|
|
||||||
if not doc_price:
|
if not doc_price:
|
||||||
print(f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
raise Exception (f"[{SERVICE_NAME}] Price doc not found for country {country}")
|
||||||
return
|
|
||||||
|
table_id = option if option else "Price"
|
||||||
|
|
||||||
target_ids = [int(item.get("target_id")) for item in content if item.get("target_id")]
|
target_ids = [int(item.get("target_id")) for item in content if item.get("target_id")]
|
||||||
|
|
||||||
if not target_ids:
|
if not target_ids:
|
||||||
print(f"[{SERVICE_NAME}] Payload to delete price is empty")
|
raise Exception (f"[{SERVICE_NAME}] Payload to delete price is empty")
|
||||||
return
|
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {GRIST_API_KEY}", "Content-Type": "application/json"}
|
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:
|
try:
|
||||||
resp = request_with_retry(
|
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}")
|
print(f"[{SERVICE_NAME}] Deleted Price IDs: {target_ids} | Status: {resp.status_code}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{SERVICE_NAME}] Delete price error: {e}")
|
raise Exception (f"[{SERVICE_NAME}] Delete price error: {e}")
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
@ -2131,8 +2313,7 @@ def get_column_letter(n):
|
||||||
|
|
||||||
def sync_sheets_to_grist(country_key):
|
def sync_sheets_to_grist(country_key):
|
||||||
if country_key not in COUNTRY_MAPPING:
|
if country_key not in COUNTRY_MAPPING:
|
||||||
print(f"Error: Country '{country_key}' not found.")
|
raise Exception (f"[{SERVICE_NAME}] Error: Country '{country_key}' not found.")
|
||||||
return
|
|
||||||
|
|
||||||
config = COUNTRY_MAPPING[country_key]
|
config = COUNTRY_MAPPING[country_key]
|
||||||
spreadsheet_id = config["spreadsheet_id"]
|
spreadsheet_id = config["spreadsheet_id"]
|
||||||
|
|
@ -2144,17 +2325,17 @@ def sync_sheets_to_grist(country_key):
|
||||||
|
|
||||||
for sheet_key, sheet_name in sheets.items():
|
for sheet_key, sheet_name in sheets.items():
|
||||||
if not sheet_name:
|
if not sheet_name:
|
||||||
continue
|
raise Exception (f"[{SERVICE_NAME}] Error: Sheet: '{sheet_name}' not found.")
|
||||||
|
|
||||||
doc_id = grist_doc_ids.get(sheet_key)
|
doc_id = grist_doc_ids.get(sheet_key)
|
||||||
if not doc_id:
|
if not doc_id:
|
||||||
continue
|
raise Exception (f"[{SERVICE_NAME}] Error: DocumentId: '{doc_id}' not found.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
worksheet = spreadsheet.worksheet(sheet_name)
|
worksheet = spreadsheet.worksheet(sheet_name)
|
||||||
all_values = worksheet.get_all_values()
|
all_values = worksheet.get_all_values()
|
||||||
if not all_values:
|
if not all_values:
|
||||||
continue
|
raise
|
||||||
|
|
||||||
header = all_values[0]
|
header = all_values[0]
|
||||||
data_rows = all_values[1:]
|
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)
|
process_new_layout_sheet(doc_id, header, data_rows)
|
||||||
elif sheet_key == "name-desc-v2":
|
elif sheet_key == "name-desc-v2":
|
||||||
upload_to_grist_self_hosted(doc_id, "name-desc-v2", header, data_rows)
|
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":
|
elif sheet_key == "price":
|
||||||
upload_to_grist_self_hosted(doc_id, "price", header, data_rows)
|
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:
|
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):
|
def process_new_layout_sheet(doc_id, header, data_rows):
|
||||||
table_groups = {}
|
table_groups = {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue