#!/usr/bin/env python3 """Cari task di Odoo berdasarkan nama dan project.""" import json import urllib.request import urllib.error import argparse BASE_URL = "https://odoo.aplikasi.web.id" PROJECT_MAP = { "CPONE": 123, "IBL": 186, "Support Pramita": 70, "SAS": 92, "Support Kedungdoro": 77, } def resolve_project(value: str) -> int: if value in PROJECT_MAP: return PROJECT_MAP[value] try: return int(value) except ValueError: names = ", ".join(PROJECT_MAP.keys()) raise argparse.ArgumentTypeError( f"Project '{value}' tidak dikenali. Pilihan: {names}, atau masukkan ID angka." ) def build_headers(session_id: str) -> dict: return { "accept": "*/*", "accept-language": "en-US,en;q=0.9,id;q=0.8", "cache-control": "no-cache", "content-type": "application/json", "origin": BASE_URL, "pragma": "no-cache", "referer": f"{BASE_URL}/web", "user-agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/148.0.0.0 Safari/537.36" ), "cookie": f"frontend_lang=en_US; cids=1; session_id={session_id}; tz=Asia/Jakarta", } def search_task(session_id: str, name: str, project_id: int, limit: int = 8) -> list: payload = { "id": 1, "jsonrpc": "2.0", "method": "call", "params": { "model": "project.task", "method": "name_search", "args": [], "kwargs": { "name": name, "operator": "ilike", "args": [ "&", "&", "&", ["company_id", "=", 1], ["project_id.allow_timesheets", "=", True], ["stage_id.fold", "=", False], ["project_id", "=", project_id], ], "limit": limit, "context": { "lang": "en_US", "tz": "Asia/Jakarta", "uid": 41, "allowed_company_ids": [1], "params": {"menu_id": 274, "action": 394, "cids": 1}, "is_timesheet": 1, "default_project_id": project_id, "hr_timesheet_display_remaining_hours": True, }, }, }, } url = f"{BASE_URL}/web/dataset/call_kw/project.task/name_search" body = json.dumps(payload).encode("utf-8") req = urllib.request.Request(url, data=body, headers=build_headers(session_id), method="POST") try: with urllib.request.urlopen(req) as resp: response = json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: raise RuntimeError(f"HTTP {e.code}: {e.read().decode()}") from e if "error" in response: err = response["error"] raise RuntimeError(f"Odoo error [{err.get('code')}]: {err.get('message')}") return response.get("result", []) def main(): project_names = ", ".join(PROJECT_MAP.keys()) parser = argparse.ArgumentParser(description="Cari task Odoo berdasarkan nama dan project") parser.add_argument("--session-id", required=True, help="session_id cookie") parser.add_argument("--name", required=True, help="Kata kunci pencarian task, e.g. '[FHM28052601]'") parser.add_argument("--project-id", required=True, type=resolve_project, help=f"Nama project ({project_names}) atau ID angka") parser.add_argument("--limit", default=8, type=int, help="Maksimal hasil yang ditampilkan (default: 8)") args = parser.parse_args() print(f"Mencari task '{args.name}' di project {args.project_id}...\n") results = search_task( session_id=args.session_id, name=args.name, project_id=args.project_id, limit=args.limit, ) if not results: print("Tidak ada task yang ditemukan.") return print(f"Ditemukan {len(results)} task:\n") for task_id, task_name in results: print(f" ID: {task_id:<8} | {task_name}") if __name__ == "__main__": main()