Cennik dostawcy w PDF albo Excelu: które podwyżki naprawdę podnoszą koszt Twoich wyrobów
Dostawca przysyła cennik na kilkaset pozycji, a pytanie brzmi, o ile droższy jest teraz konkretny wyrób. Ten workflow n8n czyta cennik w PDF, XLSX albo CSV, porównuje ceny z ostatnio zaakceptowanymi i przelicza zmiany na koszt materiałowy każdego wyrobu z BOM.
Autor
- Adrian HuniaWspółzałożyciel SEVENEDGE
- Opublikowano:
- Ostatnia weryfikacja:
- Wersja n8n:
- 2.8.4
W skrócie
Co robi
- Czyta cennik dostawcy w PDF, XLSX albo CSV wgrany przez formularz
- Sprawdza każdy kod i cenę odczytaną przez model w tekście PDF
- Sprowadza ceny do jednostki bazowej: opakowania, metry, kilogramy
- Pokazuje człowiekowi do zatwierdzenia tylko niepewne pozycje
- Liczy zmianę kosztu materiałowego każdego wyrobu na sztukę i rocznie
- Pisze brief do negocjacji wyłącznie z policzonych już liczb
Czego nie robi
- Nie czyta skanów PDF bez warstwy tekstowej
- Nie przelicza walut
- Nie obsługuje cen progowych zależnych od ilości
- Nie zastępuje ERP, który sam importuje cenniki
Czego wymaga
- n8n z Data Tables (sprawdzone na 2.8.4)
- Klucz API Anthropic
- Tabela BOM: wyrób, kod dostawcy, ilość, jednostka, wolumen
Cennik dostawcy w PDF albo Excelu: które podwyżki naprawdę podnoszą koszt Twoich wyrobów
Dostawca przysyła nowy cennik. Kilkaset pozycji, część w opakowaniach, część na metry, kilka kodów zmienionych bez słowa. Dział zakupów nie potrzebuje listy „co podrożało”. Potrzebuje odpowiedzi, o ile droższy jest teraz konkretny wyrób i ile to kosztuje rocznie.
Ten workflow n8n czyta cennik w PDF, XLSX albo CSV, porównuje go z cenami zaakceptowanymi ostatnim razem, przelicza zmiany na koszt materiałowy każdego wyrobu z BOM i na koniec daje krótki brief do rozmowy z dostawcą. Niepewne pozycje zatwierdza człowiek, zanim zmienią czyjekolwiek kalkulacje.

Dlaczego „wrzuć cennik do czata” nie wystarcza
Najprostszy pomysł to dać modelowi dwa cenniki i poprosić o porównanie. Przy kilkunastu pozycjach to nawet zadziała. Przy prawdziwym cenniku pękają trzy rzeczy.
Jednostki i opakowania
W styczniu wkręt M8x20 kosztował 34,00 zł za opakowanie 100 sztuk. W listopadzie ten sam wkręt jest w cenniku po 0,36 zł za sztukę. Porównanie „cena do ceny” pokazuje spadek o 99%. Faktycznie to podwyżka o 5,9%.
Model czasem to wychwyci, a czasem nie. A pomyłka w tę stronę jest najgorsza z możliwych, bo podwyżka wygląda jak okazja.
Wiersze, których nie było
Model czytający PDF potrafi pominąć wiersz albo dopisać taki, którego w dokumencie nie ma. Przy pełnym cenniku pominięty wiersz wygląda jak pozycja wycofana z oferty. Dopisany wygląda jak nowa pozycja z ceną z sufitu.
Model nie zna Twojego BOM
Nawet idealne porównanie cenników odpowiada na złe pytanie. Podwyżka farby proszkowej o 16,6% jest ważniejsza niż podwyżka zamka o 114%, jeśli farby zużywasz dwa kilogramy na wyrób, a zamek kupujesz raz na kwartał.
Zasada: model przepisuje, kod liczy
Cały workflow opiera się na podziale pracy. Model językowy robi tylko to, czego kod nie zrobi dobrze, czyli czyta nieuporządkowane dokumenty. Wszystko, co da się policzyć, liczy kod.
Excel i CSV
Model dostaje nazwy kolumn i kilka przykładowych wierszy. Zwraca tylko mapowanie: która kolumna to indeks, która cena, która jednostka. Wszystkie wiersze czyta potem kod.
Cennik na 5 000 pozycji kosztuje jedno wywołanie modelu, tyle samo co cennik na 20.
Model przepisuje wiersze dokładnie tak, jak są wydrukowane. Cena zostaje tekstem w rodzaju „1 234,50”. Niczego nie przelicza i nie zaokrągla.
Potem kod sprawdza każdy przepisany indeks i każdą cenę w oryginalnym tekście PDF.
To sprawdzenie jest najważniejszym elementem całości:
const squash = s => String(s || '').toUpperCase().replace(/\s+/g, '');
const haystack = squash(fullText);
const digitsOf = s => String(s || '').replace(/[^0-9]/g, '');
const numberTokens = new Set([
...(fullText.match(/\d+(?:[.,]\d+)?/g) || []),
...(fullText.match(/\d{1,3}(?:[ \u00a0.]\d{3})+(?:,\d+)?/g) || []),
].map(digitsOf));
if (!haystack.includes(squash(r.supplier_sku))) issues.push(`code "${r.supplier_sku}" not found in the PDF`);
if (!numberTokens.has(digitsOf(r.price))) issues.push(`price "${r.price}" not found in the PDF`);
W teście dorzuciliśmy do odpowiedzi modelu wiersz, którego w cenniku nie było. Nie przeszedł: trafił do zatwierdzenia z opisem, że ani kodu, ani ceny nie ma w dokumencie.
Jedna liczba do porównań: cena za jednostkę bazową
Każda cena jest sprowadzana do ceny za sztukę, metr, kilogram, litr, m² albo m³. Dopiero takie liczby są porównywane.
| W cenniku | Jednostka bazowa | Przelicznik |
|---|---|---|
| opak. (100 szt.) | szt. | cena ÷ 100 |
| 100 szt | szt. | cena ÷ 100 |
| mb, lm | m | 1 |
| g | kg | 0,001 |
| t | kg | 1000 |
| ml | l | 0,001 |
Rozmiar opakowania workflow bierze z kolumny „ilość w opakowaniu”, a jeśli jej nie ma, z opisu w rodzaju „opak. 100 szt.”. Ceny w formacie „1 234,50 zł” też parsuje kod, nie model.
Kiedy decyduje człowiek
Druga strona tego samego formularza pokazuje tylko pozycje, przy których automat nie powinien decydować sam. Każda ma powód i listę wyboru: zatwierdź albo odrzuć.
| Sytuacja | Dlaczego nie automatycznie |
|---|---|
| zmiana jednostki (np. rura z metrów na sztangi 6 m) | ceny są nieporównywalne |
| zmiana waluty | przelicznik kursowy to decyzja, nie arytmetyka |
| skok ceny o 40% i więcej | zwykle to błąd opakowania, czasem realna podwyżka |
| nowy kod podobny do zniknętego | dostawca mógł tylko przemianować indeks |
| wiersz niepotwierdzony w PDF | model mógł go zmyślić |
| pozycja zniknęła z pełnego cennika czytanego z PDF | mogła zostać pominięta przy czytaniu |

Odrzucona pozycja zachowuje starą cenę i trafia do dziennika zmian z decyzją. W teście skok ceny zamka z 6,50 na 13,90 zł został odrzucony i nie wpłynął na żadną kalkulację.
Wpływ na BOM
Tabela bom_lines trzyma jeden wiersz na komponent: wyrób, dostawca, indeks dostawcy, ilość na sztukę, jednostka i roczny wolumen. Dla każdego wyrobu kod liczy koszt materiałowy przed zmianą i po niej.
| Wyrób | Na sztuce | % | Rocznie |
|---|---|---|---|
| REG-200 Regał magazynowy | +16,64 zł | +3,51 | +9 984 zł |
| SZF-90 Szafka narzędziowa | −11,40 zł | −1,49 | −2 052 zł |
| WOZ-50 Wózek warsztatowy | +4,20 zł | +0,75 | +1 050 zł |
Przy regale największy udział mają farba (+11,52 zł) i profil 40x40 (+11,04 zł), częściowo zjedzone przez tańszą blachę (−7,00 zł). To jest informacja, z którą idzie się do dostawcy.
Kod wyłapuje też rzeczy, które psują kalkulację po cichu: komponent wycofany z oferty, BOM liczony w metrach przy cenie za sztangę, komponent bez ceny.
Brief do negocjacji
Na koniec model pisze krótki brief: co się zmieniło, które podwyżki mają znaczenie dla naszych wyrobów, jakie pytania zadać dostawcy i co poprawić u siebie. Dostaje wyłącznie policzone już liczby i ma zakaz liczenia nowych.
Use only the numbers in DATA. Every figure you mention must appear there; do not calculate new ones.
Brief jest dodatkiem, nie źródłem prawdy. Liczby w tabeli powyżej pochodzą z kodu i są takie same niezależnie od modelu.
Konfiguracja
Wszystkie progi siedzą w jednym node'cie Settings.
| Parametr | Wartość | Po co |
|---|---|---|
alertThresholdPct | 3 | od ilu procent podwyżka trafia na listę alertów |
reviewAbovePct | 40 | od ilu procent zmiana wymaga zatwierdzenia |
fuzzyMatchMin | 0,6 | podobieństwo opisu, przy którym podejrzewamy zmianę indeksu |
maxReviewRows | 25 | ile pozycji mieści się na stronie zatwierdzania |
defaultCurrency | PLN | waluta, gdy cennik jej nie podaje |
csvEncoding | utf8 | windows1250 dla starszych eksportów z polskich systemów |
Trzy tabele w n8n Data Tables (supplier_prices, price_change_log, bom_lines) zakłada osobna gałąź, którą uruchamia się raz. Pliki CSV z separatorem ; (typowe dla polskich eksportów, bo przecinek jest separatorem dziesiętnym) rozpoznaje sam.
Ograniczenia
Skany PDF nie są obsługiwane, potrzebna jest warstwa tekstowa albo wersja Excel/CSV.
Brak przeliczania walut. Pozycje w innej walucie niż domyślna są oznaczane i nie wchodzą do sum.
Ceny progowe (inna cena od 100 sztuk, inna od 1000) nie są modelowane. Przy mapowaniu kolumn model wybiera cenę bazową i zapisuje uwagę.
Dopasowanie zmienionych indeksów opiera się na podobieństwie opisów. Dostawca, który zmienia jednocześnie kod i opis, przejdzie jako „nowa pozycja” plus „wycofana pozycja”.
Pierwszy cennik od dostawcy niczego nie porównuje, tylko zapisuje punkt odniesienia.
Testowaliśmy na danych fikcyjnych na n8n 2.8.4, a odpowiedzi modelu były w teście podstawione. Cała logika poza modelem przeszła na instancji, ale nie mierzyliśmy, jak często Claude myli kolumny albo pomija wiersze na prawdziwych cennikach od różnych dostawców.
Kiedy to nie ma sensu
Przy kilku dostawcach ze stabilnymi cenami ręczne porównanie raz na kwartał zajmie mniej czasu niż utrzymanie tabeli BOM. Workflow zaczyna się opłacać, gdy cenniki przychodzą częściej niż raz w miesiącu, mają setki pozycji, a od kosztu materiałowego zależy cena, którą dajesz klientom. Jeśli ceny dostawców, BOM, zlecenia i magazyn mają żyć w jednym miejscu, to nie jest już workflow, tylko system do zarządzania produkcją.
Jak to zacytować
Adrian Hunia (2026). Cennik dostawcy w PDF albo Excelu: które podwyżki naprawdę podnoszą koszt Twoich wyrobów. SEVENEDGE. https://sevenedge.pl/pl/workflows/cenniki-dostawcow-koszt-bom (dostęp: 5 listopada 2026)
Źródła
- n8n docs — Data tablesdokumentacja n8n
- n8n docs — n8n Formdokumentacja n8n
- n8n docs — Extract From Filedokumentacja n8n
- n8n docs — Structured Output Parserdokumentacja n8n
Kto za tym stoi
Adrian Hunia
Współzałożyciel SEVENEDGE
Współzałożyciel SEVENEDGE. Projektuje i wdraża systemy biznesowe dla małych firm — od zarządzania produkcją po rejestrację czasu pracy. Pracuje na Next.js, FastAPI i PostgreSQL. SEVENEDGE działa bez podwykonawców: kod piszą dwaj właściciele.
Powiązane dokumenty
Inne przepływy, które opisaliśmy tak samo dokładnie.
- Każdemu agentowi trzeba powiedzieć, czego nie robiPoradniki o systemach wieloagentowych mówią o nadawaniu ról. Zestawiliśmy prompty z trzech naszych workflow i okazało się, że łączy je co innego: w każdym stoi zdanie o tym, czego dany agent robić nie ma.
- Co się psuje, gdy ktoś importuje Twój workflow n8nZgłosiliśmy workflow do biblioteki n8n i wrócił odrzucony przez jedną brakującą właściwość w JSON-ie. Pół roku później ten sam plik wysypał się na naszej instancji jeszcze trzy razy, z zupełnie innych powodów.


