[sheet-service] Management price & price slot, set config for any country

This commit is contained in:
Ittipat Lusuk 2026-06-17 11:32:41 +07:00
parent 6fc7cb5265
commit 7cb95aee83

328
main.py
View file

@ -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 = {}