# shopPRO REST API

REST API do integracji z ordersPRO i innymi systemami zewnetrznymi.

## Autentykacja

Kazde zapytanie wymaga headera `X-Api-Key` z kluczem API.

```
X-Api-Key: {klucz_api}
```

Klucz przechowywany jest w `pp_settings` jako parametr `api_key`. API jest stateless (bez sesji).

## Format odpowiedzi

### Sukces (HTTP 200)
```json
{
  "status": "ok",
  "data": { ... }
}
```

### Blad
```json
{
  "status": "error",
  "code": "UNAUTHORIZED",
  "message": "Invalid or missing API key"
}
```

Kody bledow:
| Kod | HTTP | Opis |
|-----|------|------|
| `UNAUTHORIZED` | 401 | Brak lub nieprawidlowy klucz API |
| `BAD_REQUEST` | 400 | Brakujace lub niepoprawne parametry |
| `NOT_FOUND` | 404 | Nie znaleziono zasobu/endpointu/akcji |
| `METHOD_NOT_ALLOWED` | 405 | Nieprawidlowa metoda HTTP |
| `INTERNAL_ERROR` | 500 | Blad wewnetrzny serwera |

## Endpointy

### Zamowienia

#### Lista zamowien
```
GET api.php?endpoint=orders&action=list
```

Parametry filtrowania (opcjonalne):
| Parametr | Typ | Opis |
|----------|-----|------|
| `status` | int | Filtruj po statusie zamowienia |
| `paid` | int (0/1) | Filtruj po statusie platnosci |
| `date_from` | date (YYYY-MM-DD) | Zamowienia od daty |
| `date_to` | date (YYYY-MM-DD) | Zamowienia do daty |
| `updated_since` | datetime (YYYY-MM-DD HH:MM:SS) | Zamowienia zmodyfikowane od podanej daty (klucz do pollingu) |
| `number` | string | Szukaj po numerze zamowienia |
| `client` | string | Szukaj po imieniu, nazwisku lub emailu klienta |
| `page` | int | Numer strony (domyslnie 1) |
| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) |

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "items": [
      {
        "id": 42,
        "number": "2026/02/001",
        "date_order": "2026-02-19 10:30:00",
        "updated_at": "2026-02-19 12:00:00",
        "status": 4,
        "paid": 1,
        "client_name": "Jan",
        "client_surname": "Kowalski",
        "client_email": "jan@example.com",
        "client_phone": "111222333",
        "client_street": "Testowa 1",
        "client_postal_code": "00-000",
        "client_city": "Warszawa",
        "firm_name": null,
        "firm_nip": null,
        "transport": "Kurier DPD",
        "transport_cost": 15.00,
        "payment_method": "Przelew bankowy",
        "summary": 150.00
      }
    ],
    "total": 1,
    "page": 1,
    "per_page": 50
  }
}
```

#### Szczegoly zamowienia
```
GET api.php?endpoint=orders&action=get&id={order_id}
```

Zwraca pelne dane zamowienia z produktami i historia statusow.

#### Zmiana statusu zamowienia
```
PUT api.php?endpoint=orders&action=change_status&id={order_id}
Content-Type: application/json

{
  "status_id": 5,
  "send_email": true
}
```

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "order_id": 42,
    "status_id": 5,
    "changed": true
  }
}
```

#### Oznacz jako oplacone
```
PUT api.php?endpoint=orders&action=set_paid&id={order_id}
```

Opcjonalnie w body: `{"send_email": true}`

#### Oznacz jako nieoplacone
```
PUT api.php?endpoint=orders&action=set_unpaid&id={order_id}
```

### Produkty

#### Lista produktow
```
GET api.php?endpoint=products&action=list
```

Parametry filtrowania (opcjonalne):
| Parametr | Typ | Opis |
|----------|-----|------|
| `search` | string | Szukaj po nazwie, EAN lub SKU |
| `status` | int (0/1) | Filtruj po statusie (1 = aktywny, 0 = nieaktywny) |
| `promoted` | int (0/1) | Filtruj po promocji |
| `attribute_{id}` | int | Filtruj po atrybucie — `attribute_1=3` oznacza atrybut 1 = wartosc 3 (wiele filtrow AND) |
| `sort` | string | Sortuj po: id, name, price_brutto, status, promoted, quantity (domyslnie id) |
| `sort_dir` | string | Kierunek: ASC lub DESC (domyslnie DESC) |
| `page` | int | Numer strony (domyslnie 1) |
| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) |

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "items": [
      {
        "id": 1,
        "sku": "PROD-001",
        "ean": "5901234123457",
        "name": "Produkt testowy",
        "price_brutto": 99.99,
        "price_brutto_promo": null,
        "price_netto": 81.29,
        "price_netto_promo": null,
        "quantity": 10,
        "status": 1,
        "promoted": 0,
        "vat": 23,
        "weight": 0.5,
        "main_image": "product1.jpg",
        "date_add": "2026-01-15 10:00:00",
        "date_modify": "2026-02-19 12:00:00"
      }
    ],
    "total": 1,
    "page": 1,
    "per_page": 50
  }
}
```

#### Szczegoly produktu
```
GET api.php?endpoint=products&action=get&id={product_id}
```

Zwraca pelne dane produktu z jezykami, zdjeciami, kategoriami i atrybutami.

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "id": 1,
    "sku": "PROD-001",
    "ean": "5901234123457",
    "price_brutto": 99.99,
    "price_brutto_promo": null,
    "price_netto": 81.29,
    "price_netto_promo": null,
    "quantity": 10,
    "status": 1,
    "promoted": 0,
    "vat": 23,
    "weight": 0.5,
    "stock_0_buy": 0,
    "custom_label_0": null,
    "set_id": null,
    "product_unit_id": 1,
    "producer_id": 3,
    "producer_name": "Nike",
    "date_add": "2026-01-15 10:00:00",
    "date_modify": "2026-02-19 12:00:00",
    "languages": {
      "pl": {
        "name": "Produkt testowy",
        "short_description": "Krotki opis",
        "description": "<p>Pelny opis produktu</p>",
        "meta_description": null,
        "meta_keywords": null,
        "meta_title": null,
        "seo_link": "produkt-testowy",
        "copy_from": null,
        "warehouse_message_zero": null,
        "warehouse_message_nonzero": null,
        "tab_name_1": null,
        "tab_description_1": null,
        "tab_name_2": null,
        "tab_description_2": null,
        "canonical": null,
        "security_information": null
      }
    },
    "images": [
      {"id": 1, "src": "product1.jpg", "alt": "Zdjecie produktu"}
    ],
    "categories": [1, 5],
    "attributes": [
      {
        "attribute_id": 1,
        "attribute_type": 1,
        "attribute_names": {"pl": "Kolor", "en": "Color"},
        "value_id": 3,
        "value_names": {"pl": "Czerwony", "en": "Red"}
      }
    ],
    "custom_fields": [
      {"name": "Napis na koszulce", "type": "text", "is_required": 1}
    ],
    "variants": [
      {
        "id": 101,
        "permutation_hash": "1-3|2-5",
        "sku": "PROD-001-RED-L",
        "ean": null,
        "price_brutto": 109.99,
        "price_brutto_promo": null,
        "price_netto": 89.42,
        "price_netto_promo": null,
        "quantity": 5,
        "stock_0_buy": 0,
        "weight": 0.5,
        "status": 1,
        "attributes": [
          {"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}},
          {"attribute_id": 2, "attribute_names": {"pl": "Rozmiar"}, "value_id": 5, "value_names": {"pl": "L"}}
        ]
      }
    ]
  }
}
```

#### Tworzenie produktu
```
POST api.php?endpoint=products&action=create
Content-Type: application/json

{
  "price_brutto": 99.99,
  "vat": 23,
  "quantity": 10,
  "status": 1,
  "sku": "PROD-001",
  "ean": "5901234123457",
  "weight": 0.5,
  "languages": {
    "pl": {
      "name": "Nowy produkt",
      "description": "<p>Opis produktu</p>"
    }
  },
  "categories": [1, 5],
  "products_related": [10, 20],
  "custom_fields": [
    {"name": "Napis na koszulce", "type": "text", "is_required": 1}
  ]
}
```

Wymagane: `languages` (min. 1 jezyk z `name`) oraz `price_brutto`.
`custom_fields` — opcjonalne; kazdy element wymaga `name`, `type` (domyslnie `text`), `is_required` (0/1).

Odpowiedz (HTTP 201):
```json
{
  "status": "ok",
  "data": {
    "id": 42
  }
}
```

#### Aktualizacja produktu
```
PUT api.php?endpoint=products&action=update&id={product_id}
Content-Type: application/json

{
  "price_brutto": 129.99,
  "status": 1,
  "languages": {
    "pl": {
      "name": "Zaktualizowana nazwa"
    }
  }
}
```

Partial update — wystarczy przeslac tylko zmienione pola. Pola nieprzeslane zachowuja aktualna wartosc.

Odpowiedz: pelne dane produktu (jak w `get`).

### Warianty produktow

#### Lista wariantow produktu
```
GET api.php?endpoint=products&action=variants&id={product_id}
```

Zwraca warianty produktu nadrzednego wraz z dostepnymi atrybutami.

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "product_id": 1,
    "available_attributes": [
      {
        "id": 1,
        "type": 1,
        "status": 1,
        "names": {"pl": "Kolor", "en": "Color"},
        "values": [
          {"id": 3, "names": {"pl": "Czerwony", "en": "Red"}, "is_default": 0, "impact_on_the_price": null},
          {"id": 4, "names": {"pl": "Niebieski", "en": "Blue"}, "is_default": 0, "impact_on_the_price": 10.0}
        ]
      }
    ],
    "variants": [
      {
        "id": 101,
        "permutation_hash": "1-3",
        "sku": "PROD-001-RED",
        "ean": null,
        "price_brutto": 109.99,
        "price_brutto_promo": null,
        "price_netto": 89.42,
        "price_netto_promo": null,
        "quantity": 5,
        "stock_0_buy": 0,
        "weight": 0.5,
        "status": 1,
        "attributes": [
          {"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}}
        ]
      }
    ]
  }
}
```

#### Tworzenie wariantu
```
POST api.php?endpoint=products&action=create_variant&id={product_id}
Content-Type: application/json

{
  "attributes": {"1": 3, "2": 5},
  "sku": "PROD-001-RED-L",
  "ean": "5901234123458",
  "price_brutto": 109.99,
  "quantity": 5,
  "weight": 0.5
}
```

Wymagane: `attributes` (mapa attribute_id -> value_id, min. 1). Kombinacja atrybutow musi byc unikalna.

Odpowiedz (HTTP 201): pelne dane wariantu.

#### Aktualizacja wariantu
```
PUT api.php?endpoint=products&action=update_variant&id={variant_id}
Content-Type: application/json

{
  "sku": "PROD-001-RED-XL",
  "price_brutto": 119.99,
  "quantity": 3
}
```

Partial update — mozna zmienic: sku, ean, price_brutto, price_netto, price_brutto_promo, price_netto_promo, quantity, stock_0_buy, weight, status.

Odpowiedz: pelne dane wariantu.

#### Usuwanie wariantu
```
DELETE api.php?endpoint=products&action=delete_variant&id={variant_id}
```

Odpowiedz:
```json
{
  "status": "ok",
  "data": {"id": 101, "deleted": true}
}
```

### Kategorie

#### Lista kategorii
```
GET api.php?endpoint=categories&action=list
```

Zwraca plaska liste wszystkich aktywnych kategorii w domyslnym jezyku sklepu.

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "categories": [
      {"id": 1, "parent_id": null, "title": "Kategoria glowna"},
      {"id": 3, "parent_id": 1, "title": "Podkategoria A"},
      {"id": 5, "parent_id": 1, "title": "Podkategoria B"}
    ]
  }
}
```

Pola odpowiedzi:
| Pole | Typ | Opis |
|------|-----|------|
| `id` | int | ID kategorii |
| `parent_id` | int\|null | ID kategorii nadrzednej (null = kategoria glowna) |
| `title` | string | Nazwa w domyslnym jezyku; fallback na inny jezyk jesli brak tlumaczenia |

---

### Slowniki

#### Lista statusow zamowien
```
GET api.php?endpoint=dictionaries&action=statuses
```

Odpowiedz:
```json
{
  "status": "ok",
  "data": [
    {"id": 0, "name": "Nowe"},
    {"id": 1, "name": "Oplacone"},
    {"id": 4, "name": "W realizacji"},
    {"id": 6, "name": "Wyslane"}
  ]
}
```

#### Lista metod transportu
```
GET api.php?endpoint=dictionaries&action=transports
```

#### Lista metod platnosci
```
GET api.php?endpoint=dictionaries&action=payment_methods
```

#### Lista atrybutow
```
GET api.php?endpoint=dictionaries&action=attributes
```

Zwraca aktywne atrybuty z wartosciami i wielojezycznymi nazwami.

#### Znajdz lub utworz producenta
```
POST api.php?endpoint=dictionaries&action=ensure_producer
Content-Type: application/json

{
  "name": "Nike"
}
```

Zwraca istniejacego producenta po nazwie lub tworzy nowego. Uzyc przed tworzeniem produktu, jesli producent moze nie istniec.

Odpowiedz:
```json
{
  "status": "ok",
  "data": {
    "id": 5,
    "created": false
  }
}
```

`created: true` gdy producent zostal nowo dodany, `false` gdy juz istnial.

Odpowiedz:
```json
{
  "status": "ok",
  "data": [
    {
      "id": 1,
      "type": 1,
      "status": 1,
      "names": {"pl": "Kolor", "en": "Color"},
      "values": [
        {"id": 3, "names": {"pl": "Czerwony"}, "is_default": 0, "impact_on_the_price": null},
        {"id": 4, "names": {"pl": "Niebieski"}, "is_default": 1, "impact_on_the_price": 10.0}
      ]
    }
  ]
}
```

## Polling

Aby pobierac tylko nowe/zmienione zamowienia, uzyj parametru `updated_since`:

```
GET api.php?endpoint=orders&action=list&updated_since=2026-02-19 12:00:00
```

Kolumna `updated_at` w `pp_shop_orders` jest aktualizowana automatycznie przy kazdej modyfikacji zamowienia (zmiana statusu, platnosci, edycja danych, tworzenie zamowienia).

## Konfiguracja

Klucz API ustawia sie w panelu admina w ustawieniach sklepu lub bezposrednio w bazie:

```sql
INSERT INTO pp_settings (param, value) VALUES ('api_key', 'twoj-klucz-api');
-- lub
UPDATE pp_settings SET value = 'twoj-klucz-api' WHERE param = 'api_key';
```

## Architektura

- Entry point: `api.php`
- Router: `\api\ApiRouter` (`autoload/api/ApiRouter.php`)
- Kontrolery: `autoload/api/Controllers/`
  - `OrdersApiController` — zamowienia (5 akcji)
  - `ProductsApiController` — produkty (8 akcji: list, get, create, update, variants, create_variant, update_variant, delete_variant)
  - `DictionariesApiController` — slowniki (5 akcji: statuses, transports, payment_methods, attributes, ensure_producer)
  - `CategoriesApiController` — kategorie (1 akcja: list)
