wFirma

OficjalnyDarmowy

Wystawianie faktur w wFirma na podstawie zamówień, pobieranie PDF, stany magazynowe przy produktach i mapowanie metod płatności.

@core·Wiktor KoźmińskiBrak recenzji7 firm z instalacjąAktualizacja 17 października 2026
Zainstaluj w Orbito Otwiera panel Orbito na karcie modułu
Licencja
Bezpłatny
Wersja
1.23.2

Moduł wFirma (core.wfirma)

Integracja księgowa z wFirma (API api2.wfirma.pl): wystawianie dokumentów sprzedaży na podstawie zamówień, pobieranie PDF-ów oraz rezerwacje magazynowe. Port modułu wfirma ze starego systemu (ordershub) na SDK @orbito/api-lib.

Stany przy produktach

wFirma zgłasza źródło widoku przez zbierający hook views.productInventory i udostępnia odczyt inventory.stock.getBySkus. Neutralne sloty przy SKU w tabeli produktów, na karcie produktu, w tabeli SKU wewnętrznych i przy pozycjach otwartego zamówienia korzystają z kontraktu, wskazując ID każdej instancji. Integracja nie zna implementacji konsumenta; kilka kont pokazuje osobne stany, bez sumowania.

Odczyt dotyczy tylko widocznych SKU, firmy z general.companyId oraz magazynów wybranych w sekcji „wFirma — podgląd stanów magazynowych” (inventory.warehouseIds). Każdy magazyn pokazuje się w osobnym wierszu z nazwą, bez sumowania między nimi. Pusty wybór oznacza brak odczytów API i prośbę o wybór magazynów. Ustawienie issue.warehouse dotyczy wystawiania dokumentów i nie steruje podglądem stanów. Lista magazynów jest pobierana kontraktem inventory.warehouses.list wskazującym bieżącą instancję. Błąd pobrania jest widoczny; zapisane ID pozostają w formularzu. Równoczesne odczyty wierszy są grupowane przez 40 ms w partie do 50 SKU. Cache działa 60 sekund (błąd transportu: 10 sekund), osobno dla instancji, firmy, magazynu i aktualnych danych dostępowych. Nie ma timera odświeżania stanów, skanowania katalogu ani automatycznych ponowień. Zapytania API mają timeout 8 s, a kompletność wyniku jest sprawdzana przez paginację filtrowanego goods/find.

Widok pokazuje „Dostępne: X szt. · Zarezerwowane: Y szt.” (count i reserved), z jednostką z API. Nie odejmuje rezerwacji. Brak wartości, także przy usługach, pokazuje jako „—”; duplikaty SKU, brak kartoteki i błąd pobrania mają osobne komunikaty. Żadnego z tych stanów nie zastępuje zero. Przy jednym źródle nie pokazuje nazwy konta, ID magazynu ani godziny; przy kilku źródłach zachowuje przy każdej parze liczb etykietę konta. Stan nie blokuje pakowania ani wystawiania dokumentów. Jeśli produkt ma wewnętrzne SKU, wyświetla także „(X wew.)” przy dostępności i rezerwacjach. Liczba oznacza pełne produkty/zestawy: minimum ilorazów stanu składnika i wymaganej ilości, zaokrąglone w dół. Powtarzające się składniki są łączone po SKU. Brak wiarygodnego stanu dowolnego składnika oznacza „—”. Wyliczenie odbywa się osobno dla magazynu i konta. Powiązania SKU pochodzą z kontraktu products.resolveBySkus; kilka katalogów wymaga jednoznacznej instancji. W zamówieniu obejmuje widok pakowania i księgowy, sumuje zapotrzebowanie powtarzających się SKU oraz mnoży ilości składników przez liczbę zestawów. Oba widoki korzystają z jednego odczytu każdego SKU. Główna lista zamówień nie odpytuje stanów.

Moduł rejestruje się jako serwis fakturowania odpowiadając na hook core.invoicing.getService (emitowany przez moduł core.invoicing):

{
    name: 'wFirma',
    moduleCode: 'core/wfirma',
    quickIssueMethod: 'quickIssue',
    quickIssueOptionsMethod: 'getQuickIssueOptions',
    quickIssueParamsMethod: 'getQuickIssueParamsForm',
    standardIssueFormMethod: 'getStandardIssueForm',
    standardIssueMethod: 'standardIssue',
    getInvoiceFileMethod: 'getInvoiceFile',
}

Para standardIssueFormMethod / standardIssueMethod obsługuje ręczne wystawianie: hub prosi moduł o gotowy formularz OVE, a potem oddaje mu z powrotem wypełniony document.

Konfiguracja (bloki config rdzenia)

  • general (połączenie): companyId, accessKey (SECRET), secretKey (SECRET).

Po podaniu kompletu kluczy formularz pokazuje INFO (format: markdown, treść z src/connection-info.js) z danymi firmy oraz tabelami magazynów i serii numeracji (bez szablonu numeracji); walidacja zapisu sprawdza połączenie (companies/get).

  • issue (wystawianie): defaultDocumentType, defaultSerieId, deliveryMethodName,

deliverySKU, shippingTaxRate, constTaxRate, contratorBinding, autoSend, productsBinding, missingSkuStrategy, warehouse, nameTemplate, documentAlwaysPaid, defaultPaymentMethod, priceType, comments. Wartości tak/nie to stringi 'yes'/'no' (spójne z extraOptions warunków core.invoicing).

Strategia braku SKU (1.19.0)

issue.missingSkuStrategy („Co robić w momencie braku SKU”, słownik dictionaries.missingSkuStrategies) decyduje, co się dzieje, gdy przy włączonym productsBinding pozycja produktowa ma SKU, którego wFirma nie zna (findGoodByCode → null): abort — przerwij wystawianie (błąd WFIRMA_SKU_MISSING, nic nie idzie do wFirmy), report — wystaw i zgłoś problem wfirma.missingSku:<orderId>, continue (domyślna, także przy braku wartości) — wystaw i tylko zaloguj ostrzeżenie. Pozycje bez SKU i pozycja dostawy nie podlegają strategii. Opcja jest wyłącznie globalna — parametry warunku core.invoicing jej nie nadpisują. Respektują ją quickIssue i standardIssue; błąd API przy wyszukiwaniu towaru przerywa wystawianie niezależnie od strategii. Rezerwacje magazynowe działają bez zmian.

Klucz aplikacji wFirma — sekret platformy (1.18.0)

Klucz aplikacji (appKey, nagłówek każdego żądania do api2.wfirma.pl) identyfikuje aplikację Orbito w wFirmie, a nie firmę — nie siedzi w kodzie ani w konfiguracji firmy. Moduł deklaruje go w module.json (secrets: [{ name: 'WFIRMA_APP_KEY' }]), rdzeń wstrzykuje go do kontenera jako zmienną środowiskową (źródło: Secrets store repozytorium modułów), a lokalnie omm dev ładuje go z .env (wzór: .env.example; .env jest w .gitignore i nie trafia do paczki).

  • Odczyt jest w jednym miejscu, src/config.js (createWfirmaApi); WfirmaApi dostaje

klucz jawnie w konstruktorze (new WfirmaApi(accessKey, secretKey, companyId, appKey)).

  • Brak zmiennej: każde utworzenie klienta rzuca błąd z kodem WFIRMA_APP_KEY_MISSING,

a przy starcie kontenera moduł zgłasza problem problemKey: 'app-key' (bez linku — użytkownik firmy odsyłany jest do administratora platformy). Start z kluczem kasuje to zgłoszenie (Problems.resolve). Zmiana sekretu wchodzi z restartem kontenera.

  • Operator, przed aktualizacją do 1.18.0: omm secrets set WFIRMA_APP_KEY — inaczej

po aktualizacji moduł przestanie łączyć się z wFirmą.

Akcje bloków konfiguracji (SDK >= 3.3.0)

Przyciski renderowane przez frontend obok formularza bloku:

BlokAkcjaStylOpis
generaltestConnectionPRIMARYOdpytuje companies/get i pokazuje nazwę/ID firmy; needReload: false (nic nie zmienia)
generalclearCredentialsDANGER + confirmKasuje general.companyId/accessKey/secretKey; przeładowanie bloku (domyślne needReload) cofa formularz do pustych pól
issuecheckIssueSettingsDEFAULTSprawdza, czy zapisana seria i magazyn nadal istnieją w wFirma; niezgodność zwraca success: false z listą problemów

Akcje bloku general są zadeklarowane jako funkcja (actualConfig) => actions — bez kompletu danych dostępowych zwracają pusty zestaw, więc przyciski w ogóle się nie pokazują. Blok issue używa wariantu statycznego (obiekt walidowany już przy starcie modułu).

Uwierzytelnienie wFirma: nagłówki accessKey/secretKey + stały appKey aplikacji. Cały ruch HTTP idzie przez rdzeń (Core.fetch), z twardym timeoutem RPC 30 s.

Metody

MetodaOpis
getQuickIssueOptions (orderId, + overrides)Opcje szybkiego wystawienia (kontrakt core.invoicing); zwraca jedną opcję wskazującą method: 'quickIssue'
quickIssue (orderId, + overrides)Wystawia dokument w wFirma: kontrahent (dane do faktury lub klient), gotowy podział cen i rabatów zapisany przez Orders, koszt dostawy oraz WDT dla UE; zwraca dane dokumentu (kontrakt quickIssueMethod core.invoicing — rejestr prowadzi hub); chronione lockiem per orderId
getInvoiceFile (issuerData, + invoiceId?/orderId?/documentNo?)PDF dokumentu w kontrakcie InvoiceFile ({contentBase64, mimeType: 'application/pdf'}); nazwy pliku nie ustawiamy — buduje ją hub z numeru dokumentu. Wymaga issuerData.invoiceId (ID w wFirma) — hub core.invoicing przekazuje dane wpisu ze swojego rejestru
getStandardIssueForm (orderId, + overrides)Schemat OVE formularza ręcznego wystawienia dokumentu (kontrakt standardIssueFormMethod); zwraca {view} budowany przez src/standard-issue-form-view.js, prefillowany nabywcą i pozycjami policzonymi tak jak w quickIssue. Bez konfiguracji / bez połączenia zwraca widok z komunikatem
standardIssue (orderId, document, + overrides)Wystawia dokument z danych wpisanych ręcznie w formularzu (kontrakt standardIssueMethod): kontrahent i pozycje wprost z document, wiązanie kontrahenta po NIP i WDT jak w quickIssue; chronione lockiem per orderId
getQuickIssueParamsForm (conditionId, values?)Schemat OVE formularza parametrów wystawiania per warunek core.invoicing (kontrakt quickIssueParamsMethod); zwraca {view} budowany przez src/params-form-view.js, zapisem zajmuje się conditions.setExtraOptions huba. Bez konfiguracji / bez połączenia zwraca widok z komunikatem
getSeriesForDocumentType (documentType?)Serie numeracji z wFirma ([{id, label}], etykieta = nazwa serii)
createReservationForOrder (orderId)Rezerwacja magazynowa warehouse_document_r z opisem R_<orderId> (idempotentna); wymaga productsBinding: yes i powiązania wszystkich produktów z towarami
issueInvoiceFromReservation (orderId, reservationId?, warehouseId?, seriesId?, documentType?, paymentMethod?)Wystawia fakturę z rezerwacji R powiązanej z zamówieniem, zapisuje ją w Orbito i zamyka wyłącznie tę rezerwację; trwały zapis operacji blokuje ponowne wystawienie
finishOrderReservations (orderId)Ustawia status finished rezerwacjom zamówienia (wołane też best-effort po quickIssue)
removeOrderReservations (orderId)Usuwa rezerwacje zamówienia we wszystkich magazynach
dictionaries.*Źródła danych dla dropdownów (documentTypes, paymentMethods, taxRates, yesNo, priceTypes, invoiceSeries, missingSkuStrategies, warehouses); etykiety to mapy { pl, en } (poza nazwami z wFirmy: warehouses, nazwy serii, stawki procentowe)
payment.mappings (query?, onlyUnmapped?, page?, perPage?, sortBy?, sortDirection?)Lista mapowań etykiet płatności (patrz „Mapowanie płatności”); zwraca results (każdy wiersz z paymentMethodLabel i unmappedCount) i pagination
payment.updateMapping (_id, paymentMethod?)Przypisuje etykiecie metodę płatności wFirmy; pusta wartość czyści przypisanie (metoda domyślna). Błędy: INVALID_PAYMENT_METHOD (kod spoza słownika), PAYMENT_MAPPING_NOT_FOUND

Faktura do rezerwacji w automatyzacji

Akcja „wFirma: Wystaw fakturę do rezerwacji” (core/wfirma:wfirma_issue_invoice_from_reservation) wymaga klucza zdarzenia core/orders:ORDER_ID. Można ją dodać po akcji zakładającej rezerwację albo przy zmianie statusu zamówienia, które ma już dokument R w wFirma.

Wybierz instancję wFirma i magazyn (ID w akcji lub issue.warehouse tej instancji). Puste reservationId wybiera jedyną rezerwację z dokładnym opisem R_<orderId>; brak lub kilka dokumentów przerywa wykonanie. Przy kilku rezerwacjach podaj ID jednej z nich. Wskazany dokument musi być typu R, mieć status pending i należeć do tego zamówienia. Nie można w ten sposób fakturować ręcznej rezerwacji bez powiązania z zamówieniem Orbito.

Faktura zachowuje nazwy, ilości, jednostki, ceny i powiązania towarów rezerwacji. VAT pochodzi z kartoteki danego towaru, jeżeli nie występuje na pozycji R. Nabywca pochodzi z rezerwacji, a płatności i ewentualna skonfigurowana usługa dostawy z zamówienia. Suma i waluta muszą zgadzać się z zamówieniem; niezgodność przerywa wystawienie bez zmiany cen rezerwacji. Jednostki przeliczeniowe wymagają ręcznego wystawienia. Można wybrać fakturę VAT (normal) lub bez VAT (bill), serię i metodę płatności. Seria proformy nie przechodzi na fakturę. Akcja nie wysyła faktury klientowi (auto_send: 0).

Oficjalne API wFirma udostępnia odczyt R, dodawanie faktury i zmianę statusu R, ale nie opisuje osobnej akcji konwersji ani zapisu natywnej relacji faktura–R. Moduł tworzy fakturę z danych rezerwacji i zachowuje powiązanie w opisie faktury, id_external oraz issuerData.reservationId i issuerData.reservationNo w rejestrze Orbito; nie zapisuje niedokumentowanego pola parent w wFirma.

Kolejność: trwały wpis pending w lokalnej kolekcji reservation_invoice_operations → faktura w wFirma → zapis odpowiedzi → rejestr Orbito przez hook → finished wskazanej R. Powtórzenie zakończonej operacji zwraca zapisaną fakturę. Błąd rejestracji lub zamknięcia pozwala ponowić tylko te kroki. Timeout, restart albo błąd zapisu wyniku po rozpoczęciu wystawiania blokuje kolejne wystawienie i wymaga sprawdzenia wFirma. Błąd kończy wykonanie automatyzacji i trafia do rejestru problemów firmy. Zamówienie z istniejącym dokumentem sprzedaży (poza proformą) jest blokowane przed nową operacją.

Ręczne wystawianie dokumentu (document)

Formularz i metoda wystawiająca są w tym samym module, więc kształt dokumentu jest kontraktem wewnętrznym wFirmy — hub przenosi go nieprzezroczyście:

{
    customer: { name, taxId?, email?, address, postCode, city, country /* ISO2 */ },
    priceType: 'brutto',
    items: [{ name, sku?, qty, unitGrossPrice, vat /* '23'|'8'|'5'|'0'|'zw' */ }],
    currency, // ISO3
    documentType, seriesId?, warehouseId?, paymentMethod?, note?,
}

Pozycje są edytowane w natywnej tabeli OVE. Typ dokumentu i metoda płatności pochodzą ze słowników modułu, magazyn z API wFirma, a seria jest pobierana z API dla aktualnie wybranego typu dokumentu i przeładowywana po jego zmianie. Wartości początkowe — razem z notatką — pochodzą z konfiguracji bloku issue. Notatka konfiguracyjna jest przed pokazaniem formularza rozwijana danymi bieżącego zamówienia. Wybory użytkownika są przekazywane w document i mają pierwszeństwo przed konfiguracją podczas wystawienia.

Dokument z zamówienia jest liczony od brutto, zarówno w quickIssue, jak i w nowym formularzu ręcznym. Cena jednostkowa ma 2 miejsca po przecinku; reszta z podziału na wiele sztuk tworzy osobny wiersz tego samego SKU z zachowaniem sumy i ilości. Przed utworzeniem kontrahenta i dokumentu backend porównuje kwotę oraz walutę z aktualnym zamówieniem. Zmienione pozycje o innej sumie są odrzucane. Starszy payload bez priceType nadal oznacza netto (unitNetPrice), ale przechodzi tę samą kontrolę. Jeżeli API zwróci inną kwotę, dokument jest rejestrowany z prawdziwym ID i kwotą, a problem WFIRMA_INVOICE_AMOUNT_MISMATCH wymaga weryfikacji; nie ma ponowienia.

Dokument waliduje jawnie handler standardIssue (validateDocument z src/issue/document.js), bo rdzeń trzyma schemat params wyłącznie na potrzeby dokumentacji metody i nie sprawdza go w czasie wykonania; params.document wskazuje ten sam DOCUMENT_SCHEMA, żeby opis i walidacja nie mogły się rozjechać. Błąd wraca do formularza jako komunikat po polsku z numerem pozycji i nazwą pola.

Pozycje mogą przyjść z kwotami wyliczonymi w przeglądarce (netValue, taxValue, grossValue) — walidacja odcina je razem z innymi nieznanymi polami (strict: 'remove'), konwertuje ilości i ceny ze stringów i podnosi kody kraju oraz waluty do wielkich liter; na dokument idą tylko ilość, cena i stawka. Stała stawka VAT ani szablon nazwy pozycji z konfiguracji nie nadpisują tego, co wybrał użytkownik.

Mapowanie płatności

Zamówienia trzymają płatność jako tekst z integracji (Za pobraniem, PayU, …), a dokument w wFirmie wymaga kodu ze słownika paymentMethods (src/common.js). Tłumaczenie etykiet prowadzi teraz wFirma (core.orders ukrył własną stronę mapowania — pole paymentCode w zamówieniu zostaje, ale nie ma wpływu na wystawianie dokumentów).

Etykiety trafiają do tabeli mapowań z dwóch źródeł:

  • hook event:order:created z core.orders (handleOrderCreated w src/payment-mapping.js,

rejestrowany w index.js) — payload to pełne zamówienie; handler połyka własne błędy, inaczej wyjątek wróciłby do outboxa core.orders jako błąd dostawy i zdarzenie byłoby ponawiane w kółko;

  • przy wystawianiu dokumentu (quickIssue/standardIssue, przez withOrderPaymentMethod)

— tak ujawniają się też etykiety zamówień sprzed wdrożenia hooka; rejestracja idzie z licznikiem 0, żeby nie zawyżać statystyki.

Nowa etykieta trafia do rejestru problemów firmy (WFIRMA_PAYMENT_UNMAPPED, patrz „Zgłaszanie problemów”), żeby nie czekała na przypisanie w ciszy — dokumenty do czasu przypisania idą z metodą domyślną.

Strona ustawień „wFirma — mapowanie płatności” (Ustawienia → Zamówienia) pozwala przypisać każdej etykiecie metodę płatności wFirmy. Rejestruje ją Pages.registerPages w src/pages.js; sekcję orders rejestruje moduł core.orders, od którego wFirma zależy — wskazujemy ją tylko kluczem w placement.section. Dane trzyma kolekcja payment_mappings:

PoleZnaczenie
keyznormalizowana etykieta (trim, zbicie białych znaków, lowercase) — unikalny klucz
labeloryginalna etykieta z zamówienia (do wyświetlenia)
paymentMethodkod metody wFirmy albo null (brak przypisania = metoda domyślna)
seenCountliczba zamówień z tą etykietą
firstSeenAt / lastSeenAtpierwsze / ostatnie wystąpienie etykiety
updatedAt / updatedBykiedy i kto ostatnio zmienił przypisanie

Metodę płatności na dokumencie wybiera priorytet: jawny parametr warunku/automatyzacji (paymentMethod w params) > mapowanie etykiety płatności zamówienia > issue.defaultPaymentMethod > transfer. Brak przypisania etykiety nie blokuje fakturowania — dokument dostaje metodę domyślną. Zmiana mapowania nie dotyka już wystawionych dokumentów, działa wyłącznie naprzód.

Struktura wystawiania (src/issue/*)

Logika wystawiania dokumentu jest rozbita na helpery — poza submit.js wszystkie są czyste (bez importu SDK), żeby dało się je testować bez rdzenia i reużyć w obu ścieżkach wystawiania. Metody quickIssue i standardIssue to już tylko lock, pobranie zamówienia, getApi i złożenie helperów.

PlikOdpowiedzialność
settings.jsresolveIssueSettings(params, issueSettings) — jeden obiekt ustawień: parametry wywołania wygrywają z konfiguracją bloku issue, reszta z domyślnych. Rozróżnia priceType (deklaracja na dokumencie) i linePriceType (historyczny tryb liczenia cen pozycji; ścieżki dokumentów z zamówień wymuszają brutto). ISSUE_OPTION_PARAMS — komplet extraOptions warunku jako schemat params, wspólny dla quickIssue, standardIssue i getStandardIssueForm. W quickIssue/standardIssue params przechodzą wcześniej przez withOrderPaymentMethod (src/payment-mapping.js), który dokłada paymentMethod z mapowania etykiety zamówienia, jeśli parametr nie przyszedł jawnie — patrz „Mapowanie płatności”
contractor.jsbuildContractorFromOrder(order), detectTaxIdType(taxId, country), bindContractorByNip(api, contractor, log) → {contractor, paymentDays}, applyWdt(contractor, invoicecontents), EU_COUNTRIES
order-lines.jsbuildOrderLines(order, settings, log) → neutralne linie {kind, name, sku, qty, unitPrice, taxRate, vatCode, product}: konsumuje wersjonowany priceAllocation z Orders (podział brutto po rabacie), wybiera cenę netto/brutto i dodaje dostawę; starsze zamówienia bez snapshotu zachowują dotychczasowy algorytm zgodności
gross-order-lines.jsWylicza linie brutto, sprawdza snapshot i zachowuje grosze przez podział ilości na ceny do 2 miejsc.
order-amount.jsKontrola kwoty i waluty przed efektem API; nie ufa sumom z formularza.
catalog-names.jsNazwy główne przez products.resolveBySkus (effectiveName, niezależna od trybu wyświetlania katalogu). Obejmuje faktyczne składniki zestawu; zachowuje ceny i pozostałe dane zamówienia. Brak lub niejednoznaczny dostawca blokuje operację przed wystawieniem. SKU nieobecne w katalogu zachowuje nazwę z zamówienia.
invoice-contents.jstoInvoiceContents(lines, {constTaxRate, nameTemplate, resolveGoodId}) → invoicecontents wFirmy; toWfirmaVat(taxRate) (0.23 → '23', kody typu 'zw' bez zmian)
payload.jsbuildInvoicePayload({contractor, invoicecontents, settings, order, paymentDays, currency, description}) (currency i description nadpisują wartości wyliczane z zamówienia — dokument ręczny), formatDate(date), toIssuedDocument(wfirmaInvoice, documentType, currency)
document.jsKontrakt document: DOCUMENT_SCHEMA + validateDocument(document) (walidacja i sanityzacja w miejscu, komunikaty po polsku) oraz oba kierunki mapowania — toDocumentCustomer/toGrossDocumentItem (prefill brutto; toDocumentItem dla zgodności netto), toContractor/toDocumentLines (dane z formularza na dokument), emptyDocumentItem()
goods.jsbindGoods(api, lines, invoicecontents, settings) — wspólne wiązanie pozycji z towarami wFirmy (produkty po SKU, koszt dostawy po SKU z ustawień); ustawia good_id i zwraca unikalne SKU pozycji produktowych bez towaru w kartotece. Błąd API leci dalej
missing-sku.jsMISSING_SKU_STRATEGY i assertSkusBound(missingSkus, strategy) — przy abort rzuca WFIRMA_SKU_MISSING przed wysyłką dokumentu
submit.jssubmitInvoice(api, payload, {orderId, documentType, currency, ctx, lines, settings, missingSkus}) — addInvoice, diagnostyka SKU po błędzie, best-effort zgłoszenie/skasowanie problemu brakujących SKU, best-effort domknięcie rezerwacji (nieudane trafia do rejestru problemów) i zwrotka IssuedDocument; jedyny plik src/issue/* sięgający po SDK
stock-diagnostics.jsPo nieudanym wystawieniu sprawdza każde unikalne SKU pozycji dokumentu w tym samym koncie i magazynie wFirmy, sumuje ilości, rozróżnia brak kartoteki, niewystarczający stan, niejednoznaczne SKU i niedostępny odczyt. Uwzględnia składniki zestawów i skonfigurowane SKU dostawy.

Przed wdrożeniem nazw katalogowych dostawca products.resolveBySkus musi udostępniać effectiveName. Starsza odpowiedź dla dopasowanego SKU blokuje wystawienie z komunikatem o brakującej głównej nazwie i wersji dostawcy.

Diagnostyka wykonuje wyłącznie odczyty i nie ponawia wystawienia. Pokazuje stan odczytany po błędzie; nie dowodzi przyczyny odrzucenia dokumentu, jeśli stan zmienił się w międzyczasie. Nie odejmuje rezerwacji od pola count, ponieważ mogą dotyczyć tego samego zamówienia. Usługi nie podlegają kontroli ilości. API wFirmy — goods definiuje count jako ilość produktu w aktywnym magazynie lub wskazanym przez warehouse_id. Problem z automatyzacji zawiera diagnostykę po polsku i angielsku, do limitu SDK 2000 znaków; pełny wynik pozostaje w logu modułu. Zwykłe problemy zachowują limit 300 znaków.

Słowniki formularza (src/dictionaries/{taxRates,countries,currencies}.js) są wbudowane w schemat statycznie — select stawki VAT jest w każdym wierszu, więc optionsSource oznaczałby N zapytań RPC na jeden formularz. Serie i magazyny są pobierane raz podczas budowania formularza, a nie osobno dla każdej kontrolki.

Wiązanie pozycji z towarami wchodzi do toInvoiceContents callbackiem, więc sam mapper nie zna API wFirmy.

Testy (npm test, node --test) obejmują helpery, snapshoty payloadu addInvoice dla kompletu wariantów wystawiania (tests/fixtures/quick-issue-snapshots.js — spisane z kodu sprzed wydzielenia helperów, pilnują, że refaktory nie zmieniają kwot), schemat formularza ręcznego wystawiania oraz payload i walidację standardIssue.

Akcje automatyzacji

  • wfirma_issue_document — wystawia dokument (parametry: alwaysIssue, warehouseId,

serieId, deliverySKU, paymentMethod); gałęzie Sukces/Błąd. Duplikaty sprawdza w centralnym rejestrze core.invoicing (widzi dokumenty wszystkich modułów księgowych); po wystawieniu rejestruje dokument przez invoices.register. Wymaga włączonego modułu core/invoicing.

  • wfirma_create_reservation — tworzy rezerwację; gałęzie Powodzenie/Niepowodzenie.
  • wfirma_remove_reservations — usuwa rezerwacje (błędy połykane).

Zgłaszanie problemów (src/problems.js)

Operacje, których niepowodzenie użytkownik musi zobaczyć i naprawić, trafiają do rejestru problemów firmy (Problems, SDK >= 3.9.0): Ustawienia → Problemy i licznik dzwonka.

Moduł zgłasza wyłącznie to, czego nie widzi hub core.invoicing. Wystawienie zlecone przez router huba (front, quickIssue/standardIssue) zgłasza hub — moduł tego nie dubluje, bo jedna nieudana próba zostawiłaby użytkownikowi dwa wpisy na liście.

OperacjaproblemKeycodeKiedy
Wystawienie z automatyzacjiwfirma.issue:<orderId>WFIRMA_ISSUE_FAILEDakcja wfirma_issue_document woła methods/quickIssue z pominięciem huba, więc nikt inny o błędzie nie wie
Zapis w rejestrze hubawfirma.register:<orderId>WFIRMA_REGISTER_FAILEDdokument jest w wFirmie, ale invoices.register nie przeszedł — w Orbito go nie widać
Rezerwacja magazynowawfirma.reservation:<orderId>WFIRMA_RESERVATION_FAILEDcreateReservationForOrder (brak powiązania towarów, nieznany towar, błąd API)
Domknięcie rezerwacjiwfirma.reservationFinish:<orderId>WFIRMA_RESERVATION_FINISH_FAILEDrezerwacje zostały otwarte po wystawieniu dokumentu i blokują stan magazynowy
Brakujące SKUwfirma.missingSku:<orderId>WFIRMA_SKU_MISSINGstrategia report: dokument wystawiono z pozycjami bez towaru w kartotece wFirmy — hub o tym nie wie, więc zgłasza moduł niezależnie od ścieżki; każde inne udane wystawienie tego zamówienia kasuje zgłoszenie
Nieprzypisana etykieta płatnościwfirma.paymentMapping:<klucz etykiety>WFIRMA_PAYMENT_UNMAPPEDw zamówieniach pojawiła się nowa nazwa płatności bez przypisanej metody wFirmy — dokumenty dostają metodę domyślną

Zgłoszenia operacji na zamówieniu dostają link: { module: 'core/orders', path: 'details/<orderId>' } — odwołanie do strony szczegółów zamówienia z core.orders, z którego front składa adres — i entityName: <numer zamówienia>. Zgłoszenie nieprzypisanej etykiety dotyczy danych modułu, a nie pojedynczego zamówienia, więc prowadzi na własną stronę mapowania ({ module: 'core/wfirma', path: 'payment-mapping' }), a entityName to etykieta płatności.

Etykietę zgłaszamy tylko przy jej pierwszym wystąpieniu (nowy wpis w payment_mappings); przypisanie metody na stronie mapowań kasuje zgłoszenie (Problems.resolve). Świadome wyczyszczenie przypisania nie zgłasza problemu ponownie — użytkownik wybrał wtedy metodę domyślną. Etykiety zebrane przed wersją 1.9.0 moduł zgłasza raz, przy pierwszym włączeniu po aktualizacji (reportUnmappedPaymentLabelsOnce w Core.onEnabled, znacznik paymentMapping.unmappedReported w konfiguracji); wpisy z updatedAt pomija, bo użytkownik już przy nich był.

desc jest mapą językową { pl, en }, a nie stringiem: zespół firmy bywa wielojęzyczny, a wszyscy widzą te same problemy, więc tłumaczenie siedzi w rekordzie (front wybiera wartość po locale); każda wartość jest przycinana osobno do 300 znaków. Klucze są deterministyczne, a udana próba tej samej operacji kasuje wpis (Problems.resolve), więc lista pokazuje stan bieżący, a nie historię. Zgłoszenie nigdy nie zmienia przepływu błędu — wyjątek leci dalej, a akcja automatyzacji nadal idzie swoją gałęzią błędu.

Błędy bez skutku biznesowego zostają w logu: usuwanie nieistniejących rezerwacji (wfirma_remove_reservations) i nieudany zapis statystyki etykiet płatności.

Model danych

Moduł prowadzi jedną własną kolekcję, payment_mappings (dotąd nie miał żadnej) — patrz „Mapowanie płatności”.

Rejestru dokumentów moduł nie prowadzi sam — wystawione dokumenty trafiają do centralnego rejestru modułu core.invoicing (kolekcja invoices huba): przez router quickIssue huba (front) lub invoices.register (akcja automatyzacji). W issuerData wpisu ląduje invoiceId — ID dokumentu w wFirma, klucz do pobrania PDF.

Event event:core.wfirma.invoice.issued został zastąpiony przez event:core.invoicing.invoice.issued ({orderId, invoiceId, moduleCode, documentNo}) emitowany przez hub przy zapisie do rejestru.

TODO — braki względem starego systemu / orbito-v2

  • Migracja starych wpisów: jeśli moduł wystawiał już dokumenty do lokalnej kolekcji

invoices (przed przeniesieniem rejestru do core.invoicing), stare wpisy trzeba przenieść do rejestru huba (dodając moduleCode: 'core/wfirma') — inaczej deduplikacja w wfirma_issue_document ich nie zobaczy, a PDF-y nie będą dostępne przez invoices.getFile.

  • Brak core:problems i EventLog w v2 — zamiennik: Log.error + hook

event:core.invoicing.invoice.issued.

  • Brak multi-instancji — jedno konto wFirma per firma (1 kontener = 1 moduł = 1 firma).
  • wFirma nie odpytuje katalogu podczas wystawiania: Products ustala rozbicie, Orders zapisuje

historyczny snapshot pozycji, a wFirma odwzorowuje jego SKU. Pola niewpisane do snapshotu (np. starsze {producent}/{marka}) renderują się jako pusty string.

  • Legacy createInvoiceForOrder — nieprzeniesione (zastąpione przez quickIssue).
  • Ograniczenia formularzy: w blokach konfiguracji nadal brak zależnego selecta serii

od typu dokumentu w locie (lista serii ładuje się dla domyślnego typu) i brak warunkowej widoczności pól. Formularz parametrów per warunek (getQuickIssueParamsForm) tego ograniczenia nie ma — jest budowany ręcznie w OVE, więc seria przeładowuje się po zmianie typu.

Do weryfikacji z rdzeniem

  • Które pola options honoruje komenda FETCH rdzenia (method/headers/body — zakładamy

kontrakt natywnego fetch).

  • Limit rozmiaru ramki RPC dla dużych PDF-ów (getInvoiceFile przenosi plik dwukrotnie,

base64 dokłada +33%; routing przez core.invoicing.invoices.getFile dokłada kolejny hop).

  • Renderowanie konwencji options: {module, method, ...} w blokach konfiguracyjnych

przez frontend (potwierdzone tylko dla parametrów akcji automatyzacji). Plan B: pola jako STRING + twarda walidacja w validate bloku. Formularza parametrów warunku to nie dotyczy — używa optionsSource widoku select OVE.

Daty — strefa czasowa firmy (1.17.0)

Dzień wystawienia faktury, konwersji proformy i rezerwacji oraz data zamówienia w opisie ({data_zamowienia}) są liczone w strefie czasowej firmy (Core.getCompany().timezone, domyślnie Europe/Warsaw) przez Time z @orbito/api-lib >= 4.3.0 — wcześniej na sztywno w Europe/Warsaw (lokalny src/warsaw-time.js, usunięty).

Każdy handler (quickIssue, standardIssue, getStandardIssueForm, issueInvoiceFromProforma, createReservationForOrder) pobiera strefę raz, przez Time.forCompany(), i przekazuje helpery w dół jako argument time (buildInvoicePayload, buildInvoiceFromProformaPayload, parseCustomNotesTemplate). Termin płatności to dni kalendarzowe od „dziś” w strefie firmy, także przy zmianie czasu letniego/zimowego. Zmiana nie wykonuje migracji istniejących danych.

Wycofanie udostępniania nazw Zamówieniom (1.13.8)

Wycofano zmiany commita 3ab1092 dotyczące nazw płatności i ich udostępniania Zamówieniom. Moduł nie dostarcza już hooka orders.paymentDisplayNames. Zachowano wcześniejszy sposób mapowania metod dla dokumentów księgowych; zapisane przypisania nie są usuwane ani zmieniane. Własne nazwy marketplace są konfigurowane niezależnie w Mirakl 0.1.18.

Stan płatności w rejestrze dokumentów

Zwrotka wystawionego dokumentu zawiera isPaid zgodne z paymentstate wFirmy (lub z kwotami total i alreadypaid, jeśli stan nie jest podany). Odczyt invoicing.issuer.getInvoicePaymentStatus, ogłoszony jako getPaymentStatusMethod, pobiera dokument z bieżącego konta przez invoices/get. Zwraca { isPaid, paidAmount } i nie wystawia dokumentów ani nie dodaje wpłat. Ogłasza też paymentIdentityVerification: true. Dla historycznych faktur expectedDocument: { documentNo, amount, currency } wymaga porównania danych z dokumentem pobranym po zewnętrznym ID. Odpowiedź zawiera identityVerified; niezgodny dokument nie zwraca statusu płatności do migracji konta. Dokumentacja pól: https://doc.wfirma.pl/ — paymentstate, alreadypaid, total.