# BASE-KONTRAKT — badanie kontraktu Base.com / Allegro dla VILMAL Data badania: 2026-09-16 · Krok 5 planu · Status: dokument badawczy, **żadnych zapisów do Base**. ## Źródła i oznaczenia | Znacznik | Znaczenie | |----------|-----------| | **[DOC]** | Potwierdzone w oficjalnej dokumentacji BaseLinker API, pobranej 2026-09-16 (`api.baselinker.com`, metoda `addInventoryProduct`). | | **[REPO]** | Potwierdzone w źródłach projektu: `dywanowy-bazar-ext/docs/*` i `vilmax-cockpit/src/*` (tylko do odczytu). | | **[TEST]** | Wymaga testu lub odczytu na docelowym koncie Base/Allegro — nie zgadywać. | | **[DECYZJA]** | Wymaga decyzji właściciela. | Metody słownikowe API (odczyt, [DOC]): `getInventories`, `getInventoryCategories`, `getInventoryManufacturers`, `getInventoryWarehouses`, `getInventoryPriceGroups`, `getInventoryIntegrations`, `getInventoryShippingTemplates`, `getInventoryExtraFields`, `getInventoryTags`, `getInventoryAvailableTextFieldKeys`, `addInventoryCategory`. --- ## 1. Tabela: wymaganie → pole/operacja Base.com → pole w modelu VILMAL Zapis: jedna metoda **`addInventoryProduct`** [DOC]. Format transportu: POST `connector.php`, pola `token`/`method`/`parameters` (parameters = JSON w form-urlencoded) [REPO `baselinker.js`]. Odpowiedź: `status`, `product_id`, `warnings.parameters` [DOC]. | Wymaganie | Pole / operacja Base.com [DOC] | Pole w modelu VILMAL | Status | |---|---|---|---| | Katalog docelowy | `inventory_id` (int; słownik `getInventories`) | fakt `base.inventory_id` | brak → checklista | | Aktualizacja zamiast tworzenia | `product_id` (tylko przy edycji) | brak pola — do dodania przy adapterze live | [TEST] po pierwszym zapisie | | Wariant produktu | `parent_id` (int, ID rodzica) | fakt `base.variant_model` + `base.parent_sku` | [DECYZJA] — rekomendacja §2 | | SKU | `sku` (varchar 50) | `commerce.sku_pattern` → `VIL-OSK-{L\|P}-TIL{kod}`; generowane w `offerContent` | OK | | EAN | `ean` (varchar 32); dodatkowe: `ean_additional[]` | `commerce.ean` / `commerce.ean_exception` | brak → checklista | | Stawka VAT | `tax_rate` (0–100; `-1`=ZW, `-0.02`=NP, `-0.03`=OO) | `commerce.vat_rate` (mapowanie „zw"→-1 w `mapTaxRate`) | OK [DOC] | | Waga | `weight` (kg, decimal 10,2) | `packages.total_weight_kg` | OK | | Wymiary | `height`, `width`, `length` (decimal 10,2) | `dimensions.height_with_cushions_cm`, `dimensions.width_cm`, `dimensions.depth_cm` | OK — uwaga: to wymiary **produktu**, paczki `packages.items` nie mają pola API | | Kategoria magazynu | `category_id` (int; kategoria musi istnieć wcześniej — `addInventoryCategory`) | fakt `base.category_id` | brak → checklista | | Producent | `manufacturer_id` (int; `getInventoryManufacturers`) | fakt `base.manufacturer_id` | brak → checklista | | Cena | `prices` = **mapa** `{priceGroupId: cenaBrutto}` (`getInventoryPriceGroups`) | `commerce.price_pln` + fakt `base.price_group_id` | OK wartość; **adapter musi emitować mapę, nie tablicę** (patrz §5) | | Stan magazynowy | `stock` = **mapa** `{"bl_[id]": ilość}` (`getInventoryWarehouses`; nie można przypisać do magazynów zewnętrznych) | `commerce.stock_quantity` + fakt `base.warehouse_id` | brak stanu → checklista; format mapy j.w. | | Nazwa | `text_fields.name` (limit 200 znaków) | `channels.main.title` per wariant | OK [DOC] | | Opis główny | `text_fields.description` | sekcje `channels.main` | OK | | Opis kanałowy Allegro | `text_fields."description|pl|allegro_"` **lub** `text_fields.description_extra1` + tag `[opis_dodatkowy1]` | `channels.allegro` (`channelText.allegro.targetField` w paczce) | [TEST] wybór mechanizmu — §3 | | Parametry | `text_fields.features` = mapa `{nazwa: wartość}` | `channels.allegro.extras.parameters` (Strona, Tkanina, Kod odcienia, wymiary…) | OK struktura; mapowanie na parametry Allegro [TEST] | | Kategoria marketplace Allegro | `text_fields."marketplace_category_id|allegro_"` (ID kategorii po stronie Allegro) | **brak pola w modelu** — potrzebny nowy fakt (propozycja `base.allegro_category_id`) | [DOC] pole istnieje; wartość → checklista | | Pola dodatkowe | `text_fields."extra_field_"` (`getInventoryExtraFields`) | brak — ocenić potrzebę | [TEST] | | Zdjęcia | `images` = mapa `{"0".."15": "url:…" \| "data:" \| ""}`; kanałowe klucze `"|allegro_"` | `channel_renditions` (profil `base_v1`, JPEG ≤1500 px q80) + kolejność `OFFER_ROLES` | OK materiał; transport §3 | | Wideo | `videos` = mapa `{"0".."5": "url:…" \| "data:" \| ""}`; klucze kanałowe jak w images | `assets` kind=`video` (klipy `use`, `care`) | pole [DOC]; propagacja → §4 | | Media per kanał | `media_options` = `{"allegro_": 0\|1\|2}` (0=domyślne, 1=osobne, 2=nadpisanie) | brak pola | [DECYZJA] domyślnie 0 | | Cennik wysyłki | `shipping_template` = `{"allegro_": ""}` (`getInventoryShippingTemplates`) | fakt `base.shipping_template_id` | brak → checklista | | Tagi | `tags` = tablica nazw (muszą istnieć; pusta tablica czyści) | brak pola | [TEST]/[DECYZJA] | | Relacje (crosssell itd.) | `relations[]` (typy: related, substitution, upsell, downsell, crosssell) | brak pola | poza zakresem etapu | | Zestaw | `is_bundle`, `bundle_products` | nie dotyczy OSKAR (`is_bundle:false`) | OK | | Dane GPSR | **brak pola w `addInventoryProduct`** — konfiguracja integracji/konta Allegro w panelu BL | fakty `gpsr.manufacturer`, `gpsr.responsible_person`, `gpsr.safety_information` (dowody dla operatora) | brak → checklista; lokalizacja w panelu [TEST] | --- ## 2. Rekomendacja modelu wariantów (parent_id, SKU/EAN, galerie) **Rekomendacja: 1 rodzic + 34 warianty przez `parent_id`.** - `parent_id` jest oficjalnym mechanizmem wariantów w magazynie Base [DOC]. Każdy wariant to osobny `product_id` z własnym SKU, EAN, ceną, stanem, `features` i **własną galerią 16 zdjęć**. - **Rodzic:** `text_fields.name` = „Narożnik OSKAR TILIA" (robocza nazwa z podglądu paczki), `sku` = `base.parent_sku`, kategoria/producent/VAT wspólne. Bez stanu i ceny albo z ceną bazową — [TEST] jak panel BL traktuje rodzica przy wystawianiu. - **Wariant (34×):** `parent_id` = `product_id` rodzica; `sku` = `VIL-OSK-{L|P}-TIL{kod}` (ASCII, ≤50 znaków — mieści się w varchar(50) [DOC]); `features` niosą atrybuty wariantu: `Strona`, `Tkanina`, `Kod odcienia`, `Kolor`, wymiary — te same nazwy co w `channels.allegro.extras.parameters`, żeby reguły mapowania BL działały 1:1. - **EAN:** docelowo własny EAN per wariant (34 kody) albo potwierdzony wyjątek. Decyzja właściciela: publikacja startowa **bez EAN** (`commerce.ean_exception`). Uwaga: zwolnienie z EAN na Allegro jest twardo potwierdzone tylko dla parametru `Stan=używany` [REPO `rozmowa_z_ai_base.com.md`] — dla **nowego** mebla wymaga testu, czy kategoria Allegro pozwala bez GTIN (`identifier_exists=false` po stronie integracji). To zostaje blockerem. - **Galerie:** 8 ról × 34 warianty = 8 zdjęć na wariant, mieści się w limicie 16 slotów. Renditions `base_v1` już gotowe (272/272). Kolejność slotów wg `OFFER_ROLES` (hero → detail), tak jak w podglądzie paczki. - **Alternatywa odrzucona:** 34 płaskie produkty bez rodzica — traci grupowanie wariantów i utrudnia ofertę wielowariantową Allegro. Oferta wielowariantowa Allegro powstaje po stronie BL z grupy wariantów — mechanizm [TEST] na docelowym koncie. --- ## 3. Limity zdjęć i transport mediów; ocena `description_extra*` ### Zdjęcia — limity [DOC] | Limit | Wartość | |---|---| | Liczba zdjęć | max **16 slotów**, klucze `"0"`–`"15"` | | Klucze kanałowe | `"|"`, np. `"3|allegro_123"` (działają z `media_options` 1/2) | | `url:` | max 1000 znaków URL | | `data:` | base64 **bez MIME** (`data:`), limit ~2 MB treści | | `""` | slot domyślny = usuwa zdjęcie; slot kanałowy = puste nadpisanie | | Aktualizacja | wysyłać **tylko zmieniane** sloty; pominięte zostają bez zmian; nie odsyłać URL-i z CDN Base (re-upload/ryzyko usunięcia) | **Transport dla VILMAL:** `data:` + base64 z plików `data/media/derived/renditions/base_v1/*.jpg` (JPEG ≤1500 px, q80 — typowo << 2 MB). Opcja `url:` odpada na starcie: panel VILMAL jest lokalny, nie wystawia publicznych URL-i. Adapter live czyta plik → base64 → `data:`. To samo podejście co `baselinkerImages.js` w cockpit [REPO]. ### `description_extra*` — ocena - **Konwencja dokumentów projektu [REPO]:** `description` = opis główny (tag `[opis]`), `description_extra1` = Allegro (`[opis_dodatkowy1]`), `description_extra2` = OLX (`[opis_dodatkowy2]`, plain text), `description_extra3` = WooCommerce, `description_extra4` = zapas (`BASE_TEMPLATES.md`, `AUTO-PUBLISH-BL.md`, szablon `szalbon_allegro` zna tagi do `[opis_dodatkowy4]`). - **Uwaga na rozjazd:** w kodzie cockpit `MARKETPLACE_TEXT_FIELD_MAP` ma allegro→`description_extra3`, woocommerce→`description_extra3`, olx→`description_extra4` — **inna konwencja niż docs**. Nie kopiować automatycznie; mapowanie tagów trzeba potwierdzić w szablonach **docelowego konta Vilmax** [TEST]. - **Ryzyko limitu:** dokumentacja stwierdza „name and short additional fields — limit 200 znaków" [DOC]. Niejasne, czy obejmuje `description_extra*`. **Wymaga testu** — jeśli extra-pola są limitowane, długi opis Allegro nie może tam mieszkać. - **Czystsza alternatywa [DOC]:** klucz per integracja `"description|pl|allegro_"` nadpisuje opis tylko dla tego kanału bez zajmowania extra-pól; wtedy szablon Allegro używa zwykłego `[opis]`. Podgląd paczki VILMAL już generuje `targetField: "description|pl|"`. - **Rekomendacja:** preferować `"description|pl|allegro_"` po potwierdzeniu klucza przez `getInventoryAvailableTextFieldKeys` na docelowym magazynie [TEST]; `description_extra1` jako fallback, jeśli szablon konta wymaga tagu `[opis_dodatkowy1]`. --- ## 4. Ścieżka wideo na Allegro przez integrację Base - **Pole API istnieje [DOC]:** `videos` — max **6 klipów**, pozycje `"0"`–`"5"` (wejście 0-based; `getInventoryProductsData` zwraca 1–6). Formaty **MP4, WEBM; max 15 MB** na plik. Wartości `url:`/`data:`/`""` jak przy images. Klucze kanałowe `"|allegro_"` + `media_options`. - **Ścieżka:** zatwierdzone klipy (`use`, `care`) → `addInventoryProduct.videos` (domyślna galeria albo slot kanałowy `allegro_`) → karta produktu w Base → publikacja oferty przez integrację Allegro (szablon/kanał). - **Granica dowodu:** API przyjmuje wideo do **galerii produktu w Base**. Czy i jak integracja przenosi je do oferty Allegro (sekcja „film" w ofercie) — **[TEST]**, wymaga zapisu testowego na docelowym koncie. Zgodnie z zasadą projektu: wideo w Base nie jest dowodem filmu na Allegro. - **Konsekwencje produkcyjne:** klip ≤15 MB → krótki, skompresowany MP4; format wyjściowy generatora (Kling) zweryfikować pod kodek/kontener przed zatwierdzeniem. Wideo pozostaje za osobną zgodą i limitem 5 USD — ten dokument niczego nie uruchamia. --- ## 5. Uwagi dla adaptera live (rozjazd podgląd ↔ API) Podgląd paczki (`offerPackage.ts`) jest abstrakcją dry-run. Adapter musi tłumaczyć: | Podgląd VILMAL | Wymagany format API [DOC] | |---|---| | `prices: [{priceGroupId, grossPln}]` | `prices: {"": }` | | `stock: [{warehouseId, quantity}]` | `stock: {"bl_": }` | | `images: [{position, role, sha256, …}]` | `images: {"": "data:"}` | | `videos: [{position, sha256, …}]` | `videos: {"": "data:"}` lub `url:` | | `channelText.allegro.title` | `text_fields."name|pl|allegro_"` (jeśli osobny tytuł kanałowy) [TEST] | | `parameters.parent_id = "{{BASE_PARENT_PRODUCT_ID}}"` | najpierw `addInventoryProduct` rodzica → `product_id` z odpowiedzi → potem warianty | Semantyka aktualizacji [DOC]: `product_id` w request = update; bez = create. `images`/`videos`/`tags`/`text_fields` mają semantykę „tylko przesłane klucze się zmieniają" (poza `tags`, gdzie przesłana lista zastępuje). --- ## 6. Definitywna checklista brakujących danych — do przekazania właścicielowi Wartości do odczytu z docelowego konta Base (Vilmax) lub decyzji. **Nie kopiować ID z konta Dywanowego Bazaru** (tam: inventory 62360, Allegro 24646, extra_field 20298/24062/24081 — inne konto, inne ID [REPO]). | # | Fakt VILMAL | Pole kontraktu | Skąd wziąć wartość | Status | |---|---|---|---|---| | 1 | `base.inventory_id` | `inventory_id` | `getInventories` na koncie Vilmax | [TEST] odczyt | | 2 | `base.category_id` | `category_id` | `getInventoryCategories` (lub `addInventoryCategory`); słownik jak `Kategorie_BaseLinker.csv`, ale **dla magazynu Vilmax** | [TEST] odczyt | | 3 | `base.manufacturer_id` | `manufacturer_id` | `getInventoryManufacturers` | [TEST] odczyt | | 4 | `base.warehouse_id` | klucz mapy `stock`: `"bl_"` | `getInventoryWarehouses` | [TEST] odczyt | | 5 | `base.price_group_id` | klucz mapy `prices` | `getInventoryPriceGroups` | [TEST] odczyt | | 6 | `base.variant_model` | strategia `parent_id` | rekomendacja §2 → akceptacja | [DECYZJA] | | 7 | `base.allegro_account` | `` = `allegro_` w kluczach `marketplace_category_id`, `description\|pl`, `images`, `videos`, `media_options`, `shipping_template` | `getInventoryIntegrations` | [TEST] odczyt | | 8 | `base.shipping_template_id` | `shipping_template` = `{"allegro_": ""}` | `getInventoryShippingTemplates` | [TEST] odczyt | | 9 | nowy fakt (prop. `base.allegro_category_id`) | `text_fields."marketplace_category_id\|allegro_"` | drzewo kategorii Allegro na koncie — kategoria liściowa mebli | [TEST] + [DECYZJA] | | 10 | `commerce.stock_quantity` | wartość mapy `stock` | właściciel / magazyn | [DECYZJA] | | 11 | `commerce.ean` lub `commerce.ean_exception` | `ean` (varchar 32) | 34 EAN-y wariantów **albo** potwierdzenie publikacji bez EAN w docelowej kategorii Allegro | [DECYZJA] + [TEST] | | 12 | `gpsr.manufacturer`, `gpsr.responsible_person`, `gpsr.safety_information` | **brak w API** — ustawienia integracji Allegro w panelu BL | dane producenta mebla (dokumenty) | [DECYZJA] dane + [TEST] lokalizacja pól | | 13 | parametry obowiązkowe kategorii Allegro | `text_fields.features` → reguły mapowania w BL | lista parametrów kategorii docelowej z konta | [TEST] | | 14 | mapowanie opisów kanałowych | `description_extra*` vs `"description\|pl\|allegro_"` | szablony docelowego konta + `getInventoryAvailableTextFieldKeys` | [TEST] | | 15 | tagi | `tags[]` (tylko istniejące nazwy) | `getInventoryTags` + decyzja jakie tagi | [TEST]/[DECYZJA] | | 16 | `base.parent_sku` | `sku` rodzica (varchar 50) | właściciel (np. `VIL-OSK-TIL`) | [DECYZJA] | | 17 | format klipów wideo | `videos` — MP4/WEBM ≤15 MB | wyjście generatora + kompresja | [TEST] po zgodzie na wideo | ### Poza zakresem `addInventoryProduct` (świadomie) Wystawienie oferty Allegro, warunki reklamacji/zwrotów, lokalizacja sprzedawcy, GPSR — konfiguracja konta/integracji w panelu BL, nie pola API produktu [REPO `AUTO-PUBLISH-BL.md`, `base-com-input-pack.md`]. Repozytorium nie wywołuje API marketplace; publikacja = panel BL lub reguły automatyczne. --- ## 7. Podsumowanie potwierdzeń - **[DOC] potwierdzone dziś:** pełna lista pól `addInventoryProduct` w tym `parent_id`, `videos` (6 × MP4/WEBM ≤15 MB), `images` (16 slotów, `url:`/`data:`), `media_options`, `shipping_template`, `marketplace_category_id|allegro_`, klucze `text_fields` z lang/source, formaty map `prices`/`stock`, limity (SKU 50, EAN 32, name 200, url 1000, data ~2 MB), metody słownikowe. - **[REPO] potwierdzone z projektu:** tagi szablonów `[opis]`/`[opis_dodatkowy1..4]`/`[pole_dodatkowe_*]`; konwencja extra1=Allegro, extra2=OLX, extra3=Woo; `Stan=używany` jako jedyny twardo potwierdzony wariant zwolnienia z EAN; ID konta Dywanowego Bazaru (nieprzenoszalne); transport `data:` + preflight jak w `baselinkerImages.js`. - **[TEST] wymaga docelowego konta:** wszystkie ID (inventory/category/manufacturer/warehouse/price group/integration/shipping template), limit znaków `description_extra*`, mechanizm opisu kanałowego, propagacja wideo do oferty Allegro, parametry obowiązkowe kategorii mebli, publikacja bez EAN dla nowego produktu, lokalizacja pól GPSR. - **[DECYZJA] właściciela:** model wariantów (rekomendacja parent+34), EAN/wyjątek, stan magazynowy, parent SKU, tagi, GPSR dane, zgoda na testowy zapis.