wFirma
OficjalnyDarmowyWystawianie faktur w wFirma na podstawie zamówień, pobieranie PDF, stany magazynowe przy produktach i mapowanie metod płatności.
- Licencja
- Bezpłatny
- Wersja
- 1.23.2
- Kategoria
- Finanse i księgowość
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);WfirmaApidostaje
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:
| Blok | Akcja | Styl | Opis |
|---|---|---|---|
general | testConnection | PRIMARY | Odpytuje companies/get i pokazuje nazwę/ID firmy; needReload: false (nic nie zmienia) |
general | clearCredentials | DANGER + confirm | Kasuje general.companyId/accessKey/secretKey; przeładowanie bloku (domyślne needReload) cofa formularz do pustych pól |
issue | checkIssueSettings | DEFAULT | Sprawdza, 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
| Metoda | Opis |
|---|---|
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:createdz core.orders (handleOrderCreatedwsrc/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, przezwithOrderPaymentMethod)
— 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:
| Pole | Znaczenie |
|---|---|
key | znormalizowana etykieta (trim, zbicie białych znaków, lowercase) — unikalny klucz |
label | oryginalna etykieta z zamówienia (do wyświetlenia) |
paymentMethod | kod metody wFirmy albo null (brak przypisania = metoda domyślna) |
seenCount | liczba zamówień z tą etykietą |
firstSeenAt / lastSeenAt | pierwsze / ostatnie wystąpienie etykiety |
updatedAt / updatedBy | kiedy 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.
| Plik | Odpowiedzialność |
|---|---|
settings.js | resolveIssueSettings(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.js | buildContractorFromOrder(order), detectTaxIdType(taxId, country), bindContractorByNip(api, contractor, log) → {contractor, paymentDays}, applyWdt(contractor, invoicecontents), EU_COUNTRIES |
order-lines.js | buildOrderLines(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.js | Wylicza linie brutto, sprawdza snapshot i zachowuje grosze przez podział ilości na ceny do 2 miejsc. |
order-amount.js | Kontrola kwoty i waluty przed efektem API; nie ufa sumom z formularza. |
catalog-names.js | Nazwy 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.js | toInvoiceContents(lines, {constTaxRate, nameTemplate, resolveGoodId}) → invoicecontents wFirmy; toWfirmaVat(taxRate) (0.23 → '23', kody typu 'zw' bez zmian) |
payload.js | buildInvoicePayload({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.js | Kontrakt 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.js | bindGoods(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.js | MISSING_SKU_STRATEGY i assertSkusBound(missingSkus, strategy) — przy abort rzuca WFIRMA_SKU_MISSING przed wysyłką dokumentu |
submit.js | submitInvoice(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.js | Po 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łęziePowodzenie/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.
| Operacja | problemKey | code | Kiedy |
|---|---|---|---|
| Wystawienie z automatyzacji | wfirma.issue:<orderId> | WFIRMA_ISSUE_FAILED | akcja wfirma_issue_document woła methods/quickIssue z pominięciem huba, więc nikt inny o błędzie nie wie |
| Zapis w rejestrze huba | wfirma.register:<orderId> | WFIRMA_REGISTER_FAILED | dokument jest w wFirmie, ale invoices.register nie przeszedł — w Orbito go nie widać |
| Rezerwacja magazynowa | wfirma.reservation:<orderId> | WFIRMA_RESERVATION_FAILED | createReservationForOrder (brak powiązania towarów, nieznany towar, błąd API) |
| Domknięcie rezerwacji | wfirma.reservationFinish:<orderId> | WFIRMA_RESERVATION_FINISH_FAILED | rezerwacje zostały otwarte po wystawieniu dokumentu i blokują stan magazynowy |
| Brakujące SKU | wfirma.missingSku:<orderId> | WFIRMA_SKU_MISSING | strategia 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ści | wfirma.paymentMapping:<klucz etykiety> | WFIRMA_PAYMENT_UNMAPPED | w 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:problemsiEventLogw 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 przezquickIssue). - 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
optionshonoruje komendaFETCHrdzenia (method/headers/body — zakładamy
kontrakt natywnego fetch).
- Limit rozmiaru ramki RPC dla dużych PDF-ów (
getInvoiceFileprzenosi 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.
