import urllib from google.oauth2.service_account import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"] def get_spreatsheed_service(credentials): return build("sheets", "v4", credentials=credentials, cache_discovery=False) def get_google_creds(): creds = None creds = Credentials.from_service_account_file("credentials.json", scopes=SCOPES) return creds def process_spreadsheet_data(rows): processed_data = [] for row in rows: processed_row = convert_spreadsheet_data(row) processed_data.append(processed_row) if processed_row else None return processed_data from datetime import datetime from datetime import datetime def convert_spreadsheet_data(data, default_year=None): """ Convert spreadsheet row into structured data. Expected keys: day, date, plan, actual """ # Skip header or invalid rows if not data.get("day") or not data["day"].isdigit(): return None result = {} # Convert day result["day"] = int(data["day"]) # Determine default year if default_year is None: default_year = datetime.now().year date_str = data.get("date", "").strip() # ---------- DATE HANDLING ---------- # Accept formats like: "Nov 20", "Dec 3", "Jan 1" parsed_date = None if date_str: try: parsed_date = datetime.strptime(f"{date_str} {default_year}", "%b %d %Y") except ValueError: try: parsed_date = datetime.strptime(f"{date_str} {default_year}", "%B %d %Y") except: parsed_date = None # YEAR ROLLOVER (Dec → Jan next year) if parsed_date and parsed_date.month == 1 and "Dec" in data.get("date", ""): parsed_date = parsed_date.replace(year=default_year + 1) result["date"] = parsed_date # ---------- PERCENT HANDLING ---------- def parse_percent(value): if not value: return 0.0 v = value.strip().replace(",", ".").replace("%", "") try: return float(v) / 100.0 except: return 0.0 result["plan"] = parse_percent(data.get("plan", "0")) result["actual"] = parse_percent(data.get("actual", "0")) # Gap calculation result["gap"] = result["actual"] - result["plan"] return result def fetch_all_sections(service, spreadsheet_id, sheet_name): # Fetch a wide range including columns A–L result = service.spreadsheets().values().get( spreadsheetId=spreadsheet_id, range=f"{sheet_name}!A5:M5000" ).execute() values = result.get("values", []) if not values: raise ValueError("No data found in sheet") data = [] current_section = None current_subsystem = None for row in values: # Pad missing columns to avoid index errors row += [""] * (13 - len(row)) colA, colB, colC, colD, colE, colF, colG, colH, colI, colJ, colK, colL, colM = row # Detect a SECTION — bold blue rows in Column C if colC and not colD and not colE: current_section = colC.strip() current_subsystem = None continue # Detect a SUBSYSTEM — indented header in Column D if colD and not colE: current_subsystem = colD.strip() continue # Detect a TASK — Column E populated if colE: task = colE.strip() pic = colF.strip() start_date = indo_formatted_date(colG.strip()) finish_date = indo_formatted_date(colH.strip()) duration = colI.strip() plan = colK.strip() actual = colL.strip() gap = colM.strip() data.append({ "system": current_section, "subsystem": current_subsystem, "task": task, "PIC": pic, "start_date": start_date, "end_date": finish_date, "duration": int(duration), "plan": plan, "actual": actual, "gap": gap }) return data def indo_formatted_date(date_str, base_year=2025): """ Convert short date like 'Nov 20', '30-Dec', 'Jan 1' into: 'Rabu, November 20, 2025' If month is January, year becomes 2026. """ # Month mappings eng_to_indo_month = { "Jan": "Januari", "Feb": "Februari", "Mar": "Maret", "Apr": "April", "May": "Mei", "Jun": "Juni", "Jul": "Juli", "Aug": "Agustus", "Sep": "September", "Oct": "Oktober", "Nov": "November", "Dec": "Desember" } indo_days = { 0: "Senin", 1: "Selasa", 2: "Rabu", 3: "Kamis", 4: "Jumat", 5: "Sabtu", 6: "Minggu" } # Normalize formats ("30-Dec" → "Dec 30") if "-" in date_str: d, m = date_str.split("-") date_str = f"{m} {d}" # Parse using English abbreviation try: dt = datetime.strptime(f"{date_str} {base_year}", "%b %d %Y") except: return None # Handle year rollover (Jan -> next year) if dt.month == 1: dt = dt.replace(year=base_year + 1) # Convert to Indonesian components day_name = indo_days[dt.weekday()] month_name = eng_to_indo_month[dt.strftime("%b")] return f"{day_name}, {month_name} {dt.day}, {dt.year}"