Metadata-Version: 2.4
Name: unergy-odoo
Version: 0.3.0
Summary: Lightweight Odoo XML-RPC client with buit-in payroll novedades support
License-File: LICENSE
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# unergy-odoo

Lightweight Odoo XML-RPC client for Python. No Django required.

## Installation

```bash
# Local install (editable)
pip install -e /path/to/unergy-odoo

# With uv
uv add /path/to/unergy-odoo
```

## Configuration

Credentials are resolved from environment variables by default:

```bash
export ODOO_HOST="https://your-instance.odoo.com"
export ODOO_DB="your-database"
export ODOO_USERNAME="user@example.com"
export ODOO_PASSWORD="your-api-key"
```

Any constructor accepts explicit overrides that take priority over env vars:

```python
odoo = Odoo("hr.payslip", host="https://...", db="mydb", username="u", password="p")
```

## Usage

### `Odoo` — model client

```python
from unergy_odoo import Odoo

# Query records
payslips = Odoo("hr.payslip").filter(
    fields=["name", "state", "employee_id"],
    filter=[["state", "=", "done"]],
    limit=50,
)

# Get a single record (raises Odoo.DoesNotExist if not found)
employee = Odoo("hr.employee").get(filter=[["identification_id", "=", "1234567890"]])

# Count
total = Odoo("hr.contract").count(filter=[["state", "=", "open"]])

# Create / update / delete
record_id = Odoo("res.partner").create({"name": "Acme", "email": "acme@example.com"})
Odoo("res.partner").update([record_id], {"phone": "+57 300 000 0000"})
Odoo("res.partner").delete([record_id])
```

### `OdooExplorer` — read-only introspection

Useful for discovering models, fields, and relations without risking writes.

```python
from unergy_odoo import OdooExplorer

explorer = OdooExplorer()

# Find models by keyword
explorer.search_models("payslip")
explorer.search_models("nomina")

# Inspect fields
explorer.model_fields("hr.payslip")
explorer.model_fields("hr.payslip", field_type="selection")

# Inspect relational fields only
explorer.model_relations("hr.payslip")

# Count records (with optional domain)
explorer.count_records("hr.payslip")
explorer.count_records("hr.contract", [["state", "=", "open"]])

# Fetch sample records
explorer.sample("hr.payslip", limit=2)
explorer.sample("hr.payslip", fields=["name", "state", "employee_id"])
```

### `OdooManager` — base connection

Use directly when you need raw `execute_kw` access.

```python
from unergy_odoo import OdooManager

mgr = OdooManager()
uid = mgr.authenticate()
result = mgr._exec(mgr.db, uid, mgr.password, "hr.payslip", "search_count", [[]])
```

## Novedades (payroll attachments)

High-level dataclasses for registering `hr.salary.attachment` records in Odoo.

### `BonoGimnasio`

```python
from datetime import date
from unergy_odoo.novedades import BonoGimnasio

bono = BonoGimnasio(
    identification="1234567890",
    description="GIMNASIO JOHN DOE 2026-04-15 #10",
    monthly_amount=80_000,
    date_start=date(2026, 4, 1),
    type_payment="second_half",
)
odoo_id = bono.register()
```

### `Viatico`

```python
from datetime import date
from unergy_odoo.novedades import Viatico

viatico = Viatico(
    identification="1234567890",
    description="Viático viaje Bogotá",
    monthly_amount=150_000,
    date_start=date(2026, 4, 1),
    type_payment="first_half",
    attachments=["soporte.pdf"],        # str, Path, or file-like object
)
odoo_id = viatico.register()

# Fixed total amount (single payment)
viatico = Viatico(
    identification="1234567890",
    description="Viático viaje Medellín",
    monthly_amount=0,
    date_start=date(2026, 4, 1),
    type_payment="monthly",
    total_amount=300_000,
    has_total_amount=True,
    date_end=date(2026, 4, 30),
)
```

#### `type_payment` values

| Value | Quincena |
|---|---|
| `monthly` | Mensual |
| `first_half` | Primera quincena |
| `second_half` | Segunda quincena |
| `both_fortnight` | Ambas quincenas |

### Adding new novedad types

Subclass `NovedadBase`, set `DEDUCTION_TYPE`, and override `_payload()` for any extra fields:

```python
from dataclasses import dataclass, field
from unergy_odoo.novedades import NovedadBase

@dataclass
class AuxilioTransporte(NovedadBase):
    DEDUCTION_TYPE: str = field(init=False, default="AUX_TRANSPORTE")

    def _payload(self) -> dict:
        return {}
```

### Credential overrides per novedad

If you need different credentials for a specific novedad instance:

```python
bono = BonoGimnasio(
    ...,
    odoo_host="https://staging.odoo.com",
    odoo_db="staging-db",
    odoo_username="test@example.com",
    odoo_password="staging-key",
)
```
