#!/usr/bin/env python3 """Upload satu atau banyak timesheet entry ke Odoo sekaligus.""" import json import urllib.request import urllib.error import argparse from datetime import date 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 upload_timesheet(session_id: str, entry: dict) -> dict: payload = { "id": 1, "jsonrpc": "2.0", "method": "call", "params": { "model": "account.analytic.line", "method": "create", "args": [entry], "kwargs": { "context": { "lang": "en_US", "tz": "Asia/Jakarta", "uid": entry["user_id"], "allowed_company_ids": [1], "params": {"menu_id": 274, "action": 394, "cids": 1}, "is_timesheet": 1, } }, }, } url = f"{BASE_URL}/web/dataset/call_kw/account.analytic.line/create" 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 def main(): today = date.today().isoformat() parser = argparse.ArgumentParser( description="Upload timesheet ke Odoo. Semua parameter (kecuali --session-id) " "bisa diisi lebih dari satu nilai untuk upload banyak entry sekaligus.", formatter_class=argparse.RawTextHelpFormatter, ) parser.add_argument("--session-id", required=True, help="session_id cookie (satu nilai)") parser.add_argument("--name", required=True, nargs="+", help="Deskripsi pekerjaan") parser.add_argument("--task-id", required=True, nargs="+", type=int, help="ID task di Odoo") parser.add_argument("--unit-amount", required=True, nargs="+", type=float, help="Jam (0.5 = 30 menit)") parser.add_argument("--user-id", required=True, nargs="+", type=int, help="ID user Odoo") parser.add_argument("--employee-id", required=True, nargs="+", type=int, help="ID employee Odoo") parser.add_argument("--project-id", required=True, nargs="+", type=resolve_project, help="Nama project (CPONE, IBL, 'Support Pramita', SAS) atau ID angka") parser.add_argument("--date", nargs="+", default=[today], help=f"Tanggal YYYY-MM-DD (default: {today})") args = parser.parse_args() # Pastikan semua array punya panjang sama lengths = { "name": len(args.name), "task-id": len(args.task_id), "unit-amount": len(args.unit_amount), "user-id": len(args.user_id), "employee-id": len(args.employee_id), "project-id": len(args.project_id), } unique = set(lengths.values()) # Kalau --date cuma 1, broadcast ke semua entry dates = args.date if len(dates) == 1: dates = dates * max(unique) else: lengths["date"] = len(dates) unique = set(lengths.values()) if len(unique) != 1: mismatches = ", ".join(f"--{k}={v}" for k, v in lengths.items()) parser.error(f"Jumlah nilai tiap parameter harus sama.\nSekarang: {mismatches}") entries = list(zip( args.name, args.task_id, args.unit_amount, args.user_id, args.employee_id, args.project_id, dates, )) total = len(entries) print(f"Akan mengupload {total} entry timesheet...\n") for i, (name, task_id, unit_amount, user_id, employee_id, project_id, entry_date) in enumerate(entries, 1): entry = { "name": name, "date": entry_date, "unit_amount": unit_amount, "user_id": user_id, "task_id": task_id, "project_id": project_id, "employee_id": employee_id, } print(f"[{i}/{total}] '{name}' | task={task_id} | {unit_amount}h | {entry_date}") result = upload_timesheet(args.session_id, entry) new_id = result.get("result") print(f" Sukses — timesheet ID: {new_id}") print(f"\nSelesai. {total} entry berhasil diupload.") if __name__ == "__main__": main()