Problems
Moduł Problems to rejestr problemów firmy (od SDK 3.8.0). Problem to sytuacja, o której użytkownik musi wiedzieć i którą zwykle musi naprawić: faktura nie została wystawiona, rezerwacja towaru się nie założyła, przewoźnik odrzucił paczkę. Użytkownik widzi te zgłoszenia w Ustawienia → Problemy oraz na liczniku dzwonka w nagłówku panelu.
Nie jest to zamiennik Log. Logi służą diagnostyce — zapisuj w nich każdy istotny krok i każdy błąd techniczny. Problem zgłaszaj tylko wtedy, gdy nieudana operacja ma dla użytkownika skutek biznesowy i to on musi coś z tym zrobić (poprawić dane kontrahenta, uzupełnić konfigurację, ponowić wysyłkę).
const { Problems } = require('@orbito/api-lib');
Problems.report({ problemKey?, code, desc, link?, entityName? }) // -> { _id, created } albo null
Problems.resolve({ problemKey }) // -> { removed } albo null
Parametry Problems.report:
| Parametr | Typ | Wymagany | Opis |
|---|---|---|---|
problemKey | string | Nie | Klucz deduplikacji w obrębie tej instalacji modułu, max 200 znaków. Bez niego każde zgłoszenie tworzy nowy wpis na liście |
code | string | Tak | Kod typu problemu nadawany przez moduł, np. WFIRMA_ISSUE_FAILED (1–100 znaków). Pokazywany jako badge, służy też do filtrowania listy |
desc | string | object | Tak | Opis dla użytkownika, z przyczyną niepowodzenia. Zwykły string (1–2000 znaków) albo mapa językowa { pl: '...', en: '...' } — klucze w formacie kodu języka (^[a-z]{2}(-[A-Z]{2})?$), każda wartość 1–2000 znaków, min. 1 klucz |
link | object | Nie | Strona modułu, do której prowadzi przycisk „otwórz” — podajesz ją tak samo jak w akcji navigate w OVE. Gotowy adres składa frontend, bo moduł nie zna jego routingu |
link.module | string | Tak (w link) | Kod modułu, do którego należy strona, w formacie <vendor>/<nazwa>, np. core/orders (max 100 znaków) |
link.path | string | Tak (w link) | Ścieżka strony w tym module, np. details/65ab12ff (1–500 znaków). Wiodący / jest opcjonalny |
entityName | string | Nie | Etykieta encji, której dotyczy problem, pokazywana użytkownikowi, np. numer zamówienia (max 200 znaków; od SDK 3.9.0, zastępuje dawne pole entity) |
entityName służy wyłącznie do wyświetlenia — do nawigacji służy link.
Zalecana forma desc — mapa językowa (od SDK 3.9.0)
Zespół firmy może być wielojęzyczny, a wszyscy widzą te same problemy, dlatego zalecamy przekazywać desc jako mapę { pl, en, ... } — podając oba języki. Rdzeń przechowuje mapę 1:1 — nie tłumaczy ani nie normalizuje jej zawartości — a panel pokazuje wersję w języku aktualnie zalogowanego użytkownika, z fallbackiem locale → en → pl → pierwsza dostępna wartość (funkcja i18nText we froncie). Zwykły string wciąż działa i jest pokazywany każdemu użytkownikowi bez tłumaczenia.
Źródło zgłoszenia i deduplikacja po problemKey
Nie przekazujesz informacji o module ani o instalacji — rdzeń dopisuje je sam na podstawie tożsamości połączenia kontenera, więc moduł zgłasza i kasuje wyłącznie własne problemy.
Ponowny report z tym samym problemKey z tej samej instalacji nie tworzy drugiego wpisu: aktualizuje istniejący (code, desc, link, entityName), odświeża czas ostatniego zgłoszenia i cofa status do waiting — nawet jeśli użytkownik zdążył oznaczyć problem jako rozwiązany lub odrzucony. Problems.resolve kasuje wpis o tym kluczu całkowicie. Zakresem deduplikacji jest instalacja, więc dwa sklepy na tej samej integracji zgłaszające ten sam klucz mają osobne wpisy.
Metody nigdy nie odrzucają Promise
Zgłoszenie problemu dzieje się zwykle w bloku catch i nie może przesłonić pierwotnego błędu, dlatego report i resolve — tak samo jak Log — nie rzucają wyjątku. Błąd transportu lub walidacji jest logowany na console.error przy ORBITO_DEBUG=1, a metoda zwraca null. Nie opieraj więc logiki modułu na tym, że zgłoszenie się powiodło.
Problems.report
Zgłasza problem do rejestru firmy. Zwraca { _id, created }, gdzie created jest false, gdy zaktualizowany został istniejący wpis o tym samym problemKey.
const { Problems } = require('@orbito/api-lib');
await Problems.report({
problemKey: `wfirma.issue:${order._id}`,
code: 'WFIRMA_ISSUE_FAILED',
desc: {
pl: `Nie udało się wystawić faktury do zamówienia ${order.number}: ${err.message}`,
en: `Failed to issue invoice for order ${order.number}: ${err.message}`
},
link: { module: 'core/orders', path: `details/${order._id}` },
entityName: order.number
});
Zgłoszenie bez problemKey to zdarzenie jednorazowe — każde wywołanie dodaje kolejny wiersz na liście. Używaj go tylko wtedy, gdy problem naprawdę nie ma powtarzalnej tożsamości (np. wynik jednorazowego importu):
await Problems.report({
code: 'IMPORT_PARTIAL',
desc: `Import zakończony: ${imported.length} pozycji wgranych, ${skipped.length} pominiętych`,
link: { module: 'core/orders', path: 'imports' }
});
Problems.resolve
Kasuje problem zgłoszony wcześniej przez tę instalację pod danym problemKey. Zwraca { removed }; removed jest false, gdy nie było czego kasować.
await Problems.resolve({ problemKey: `wfirma.issue:${order._id}` });
Nieznany klucz to cichy no-op, więc nie sprawdzaj wcześniej, czy coś było zgłoszone — po prostu wołaj resolve po każdej udanej próbie.
Wzorzec: report w catch, resolve po sukcesie
Rejestr ma pokazywać aktualny stan, a nie historię — dlatego zgłoszenie i skasowanie zawsze idą w parze i używają tego samego, deterministycznego klucza. Dzięki temu problem znika z listy sam, gdy ponowienie operacji się uda:
const { Problems } = require('@orbito/api-lib');
async function issueInvoice(order) {
const problemKey = `wfirma.issue:${order._id}`;
try {
await wfirma.issue(order);
// Udana próba kasuje wcześniejsze zgłoszenie - bez sprawdzania, czy istniało.
await Problems.resolve({ problemKey });
} catch (err) {
await Problems.report({
problemKey,
code: 'WFIRMA_ISSUE_FAILED',
desc: {
pl: `Nie udało się wystawić faktury do zamówienia ${order.number}: ${err.message}`,
en: `Failed to issue invoice for order ${order.number}: ${err.message}`
},
link: { module: 'core/orders', path: `details/${order._id}` },
entityName: order.number
});
throw err; // zgłoszenie problemu nie zastępuje obsługi błędu
}
}
Dobre praktyki
Klucz deduplikacji musi być deterministyczny – zbuduj go z nazwy operacji i identyfikatora encji. Losowy klucz zamienia rejestr w drugi dziennik zdarzeń: każda nieudana próba zostawia kolejny wiersz, a resolve nie ma czego skasować.
// ❌ Nigdy
await Problems.report({ problemKey: randomUUID(), code: 'WFIRMA_ISSUE_FAILED', desc });
// ✅ Zamiast tego
await Problems.report({ problemKey: `wfirma.issue:${order._id}`, code: 'WFIRMA_ISSUE_FAILED', desc });
Zgłaszaj tylko to, co użytkownik ma zobaczyć i naprawić – nieudane wystawienie dokumentu, nienadaną przesyłkę, niewysłany mail. Błędy techniczne bez skutku biznesowego (nieudane ponowienie, które zaraz się powiodło, chwilowy timeout) zostaw logom. Wyjątkiem jest brak sekretu aplikacji w process.env — moduł nie powinien wtedy padać, tylko zgłosić problem, żeby użytkownik wiedział, dlaczego integracja nie działa (brakujące dane użytkownika uzupełnia on sam w konfiguracji, klucze aplikacji ustawia autor modułu).
// ❌ Nigdy
catch (err) {
await Problems.report({ code: 'FETCH_FAILED', desc: 'Timeout zapytania do API' });
}
// ✅ Zamiast tego
catch (err) {
Log.warn('Timeout zapytania do API, ponawiam', { attempt });
}
link wskazuje stronę modułu, a nie gotowy adres – rdzeń przyjmuje wyłącznie parę { module, path }, dokładnie jak akcja navigate w OVE. Adres w przeglądarce składa z niej frontend, bo routing panelu jest jego wewnętrzną sprawą — moduł nie może go zgadywać ani podawać linku na zewnątrz Orbito. Przycisk „otwórz” ma zaprowadzić użytkownika do miejsca, w którym naprawi problem.
// ❌ Nigdy
link: 'https://wfirma.pl/invoices/123'
link: `/p/core_orders/details/${order._id}`
// ✅ Zamiast tego
link: { module: 'core/orders', path: `details/${order._id}` }
desc pisz dla użytkownika, nie dla siebie – jedno zdanie po polsku mówiące, co się nie udało i dlaczego. Komunikat pierwotnego błędu dołącz w skróconej formie; stack trace zostaw w Log.error.
// ❌ Nigdy
desc: err.stack
// ✅ Zamiast tego
desc: `Nie udało się wystawić faktury do zamówienia ${order.number}: ${err.message}`.slice(0, 300)
Każdy code powinien znaczyć jedno – to po nim użytkownik filtruje listę, a Ty rozpoznajesz zgłoszenia swojego modułu. Używaj stałego zestawu kodów w UPPER_SNAKE_CASE (WFIRMA_ISSUE_FAILED, INPOST_LABEL_FAILED), a nie treści błędu przepisanej na kod.
