129 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import requests
 | 
						|
import json
 | 
						|
import logging
 | 
						|
import calendar
 | 
						|
from datetime import datetime, timedelta, timezone
 | 
						|
from fastapi import FastAPI, Depends, HTTPException, status
 | 
						|
 | 
						|
logger = logging.getLogger("uvicorn")
 | 
						|
 | 
						|
 | 
						|
class OdooAPIClient:
 | 
						|
    def __init__(self, odoo_url: str, db_name: str, secret_key: str, algorithm: str):
 | 
						|
        self.odoo_url = odoo_url
 | 
						|
        self.db_name = db_name
 | 
						|
        self.session = requests.Session()
 | 
						|
 | 
						|
    def login(self, username: str, password: str) -> dict:
 | 
						|
        auth_url = f"{self.odoo_url}/web/session/authenticate"
 | 
						|
        payload = {
 | 
						|
            "jsonrpc": "2.0",
 | 
						|
            "method": "call",
 | 
						|
            "params": {
 | 
						|
                "db": self.db_name,
 | 
						|
                "login": username,
 | 
						|
                "password": password
 | 
						|
            },
 | 
						|
            "id": 1
 | 
						|
        }
 | 
						|
        headers = {'Content-Type': 'application/json'}
 | 
						|
        response = self.session.post(auth_url, json=payload, headers=headers)
 | 
						|
 | 
						|
        if response.status_code != 200:
 | 
						|
            # logger.error(
 | 
						|
            #     f"Authentication failed with status code {response.status_code}")
 | 
						|
            raise Exception(
 | 
						|
                f"Authentication failed with status code {response.status_code}")
 | 
						|
 | 
						|
        # for cookie in response.cookies:
 | 
						|
        #     self._session_cookies[cookie.name] = cookie.value
 | 
						|
 | 
						|
        data = response.json()
 | 
						|
        if "error" in data:
 | 
						|
            raise HTTPException(
 | 
						|
                status_code=502,
 | 
						|
                detail=f"Odoo RPC error: {data['error'].get('message', 'Unknown error')}"
 | 
						|
            )
 | 
						|
        return data["result"]
 | 
						|
 | 
						|
    def get_hr_attendance_data(self, month=None, year=None, limit=None, domain=None):
 | 
						|
        if month is None:
 | 
						|
            month = datetime.now(timezone.utc).month
 | 
						|
        if year is None:
 | 
						|
            year = datetime.now(timezone.utc).year
 | 
						|
        if domain is None:
 | 
						|
            domain = []  # Default to no domain filter
 | 
						|
 | 
						|
        url = f"{self.odoo_url}/hr.attendance"
 | 
						|
        first_day_of_month = datetime(year, month, 1)
 | 
						|
        last_day_of_month = datetime(
 | 
						|
            year, month, calendar.monthrange(year, month)[1], 23, 59, 59)
 | 
						|
 | 
						|
        start_date_str = first_day_of_month.strftime('%Y-%m-%d %H:%M:%S')
 | 
						|
        end_date_str = last_day_of_month.strftime('%Y-%m-%d %H:%M:%S')
 | 
						|
 | 
						|
        domain.extend([
 | 
						|
            ('check_in', '>=', start_date_str),
 | 
						|
            ('check_in', '<=', end_date_str)
 | 
						|
        ])
 | 
						|
 | 
						|
        kwargs = {
 | 
						|
            "domain": domain,
 | 
						|
            "fields": ["id", "employee_id", "check_in", "check_out", "worked_hours", "attendance_reason_ids"],
 | 
						|
        }
 | 
						|
 | 
						|
        if limit is not None:
 | 
						|
            kwargs["limit"] = limit
 | 
						|
 | 
						|
        result = self.call_odoo_method(
 | 
						|
            "hr.attendance", "web_search_read", kwargs=kwargs)
 | 
						|
 | 
						|
        if "records" not in result:
 | 
						|
            # logger.error("Unexpected response format: 'records' not found")
 | 
						|
            raise HTTPException(
 | 
						|
                status_code=502,
 | 
						|
                detail="Unexpected response format: 'records' not found"
 | 
						|
            )
 | 
						|
        
 | 
						|
        sorted_data = sorted(result["records"], key=lambda x: x["check_in"])
 | 
						|
        return sorted_data
 | 
						|
 | 
						|
    def call_odoo_method(self, model: str, method: str, args: list = None, kwargs: dict = None):
 | 
						|
        if args is None:
 | 
						|
            args = []
 | 
						|
        if kwargs is None:
 | 
						|
            kwargs = {}
 | 
						|
 | 
						|
        url = f"{self.odoo_url}/web/dataset/call_kw/{model}/{method}"
 | 
						|
        headers = {'Content-Type': 'application/json'}
 | 
						|
        payload = {
 | 
						|
            "jsonrpc": "2.0",
 | 
						|
            "method": "call",
 | 
						|
            "params": {
 | 
						|
                "model": model,
 | 
						|
                "method": method,
 | 
						|
                "args": args,
 | 
						|
                "kwargs": kwargs
 | 
						|
            },
 | 
						|
            "id": 1
 | 
						|
        }
 | 
						|
        response = self.session.post(url, json=payload, headers=headers)
 | 
						|
        # response = requests.post(url, json=payload, headers=headers, cookies=self._session_cookies)
 | 
						|
 | 
						|
        if response.status_code != 200:
 | 
						|
            # logger.error(
 | 
						|
            #     f"Failed to call Odoo method {method} on model {model}: {response.status_code}")
 | 
						|
            raise HTTPException(
 | 
						|
                status_code=response.status_code,
 | 
						|
                detail=f"Failed to call Odoo method {method} on model {model}"
 | 
						|
            )
 | 
						|
        
 | 
						|
        data = response.json()
 | 
						|
        if "error" in data:
 | 
						|
            raise HTTPException(
 | 
						|
                status_code=502,
 | 
						|
                detail=f"Odoo RPC error: {data['error'].get('message', 'Unknown error')}"
 | 
						|
            )
 | 
						|
        return data["result"]
 | 
						|
 |