Robotyzacja na punktach ACD typu „Import dokumentów zakupu z KSeF” – przykład

image_pdfimage_print

Zastosowanie

Skrypt RPA umożliwia automatyzację procesu pobierania faktur z platformy Krajowego Systemu e-Faktur KSeF bezpośrednio do systemu Comarch BPM. Rozwiązanie to eliminuje konieczność ręcznego importu, zapewniając ciągłość zasilania punktów ACD nowymi dokumentami oraz odpowiednią reakcję na limity wydajnościowe serwerów ministerialnych.

Zawartość przykładu

  • Skrypt C#: Implementacja przygotowana w języku C# z wykorzystaniem obiektów globalnych systemu (Globals.ACD, Globals.Common).

Kliknij, aby pobrać przykład

Uwaga
Funkcjonalność obsługi limitów ministerialnych (reakcja na błąd 429) jest wspierana od wersji Comarch BPM 2026.0.0.2.

Zasada działania

Głównym zadaniem skryptu jest nawiązanie połączenia z serwerami ministerialnymi i przekazanie dokumentów do zdefiniowanego punktu ACD oraz określonego typu obiegu.

  • Konfiguracja Punktu ACD: Możliwość wskazania konkretnej skrzynki odbiorczej (np. Dokumenty z KSeF – (A)FZ.
  • Automatyczne przypisanie do obiegu: Zaimportowane faktury trafiają do konkretnego procesu, np. „Faktura kosztowa”.
  • Mechanizm Retry (Ponawianie): Odporność na błędy komunikacji poprzez automatyczne podejmowanie do 3 prób importu w przypadku problemów.
  • Obsługa limitów (Błąd 429): W przypadku przeciążenia serwerów KSeF, skrypt wstrzymuje pracę na 5 minut przed kolejną próbą.

Skrypt działa w oparciu o pętlę sterowaną odpowiedziami z API KSeF:

  • Inicjalizacja: Resetuje poprzednie ustawienia filtrów metodą ResetKSeFFilters(), aby uniknąć konfliktów w sesji Globals.ACD.
  • Konfiguracja celu: Ustawia Punkt ACD oraz Obieg jako parametry kontekstu importu.
  • Import i weryfikacja: Wywołuje metodę ImportKSeFDocuments() i analizuje wynik.
    • Jeśli wynik wynosi 0, import zakończył się sukcesem.
    • Jeśli wynik wynosi -2, skrypt rozpoznaje błąd 429 (przekroczenie limitu) i uruchamia pauzę przed ponowieniem.

Parametry konfiguracyjne

Zmienna Opis
maxRetries Maksymalna liczba prób w przypadku wystąpienia błędów tymczasowych (domyślnie: 3).
baseWaitTimeSeconds Podstawowy czas oczekiwania (90s) używany do wyliczania pauzy po otrzymaniu limitu żądań.
punktACD Nazwa punktu wejścia dla dokumentów w systemie.
nazwaObiegu Nazwa procesu, w którym pojawią się zaimportowane faktury.

Techniczne aspekty kodu

  • Logowanie Startu: Skrypt rejestruje postępy w Globals.Common.Trace, co jest kluczowe dla monitorowania pracy automatu w logach systemowych.
  • Exponential Backoff: Skrypt stosuje wykładniczy wzrost czasu oczekiwania. Przy kolejnych próbach po błędzie 429 czas pauzy wynosi odpowiednio: 90s, 180s i 360s. Dzięki temu automat „cierpliwie” czeka na odblokowanie dostępu do API.
  • Bezpieczeństwo sesji: W kodzie przewidziano opcjonalne resetowanie filtrów (ResetKSeFFilters) oraz możliwość wymuszenia konkretnego zakresu dat pobierania faktur (SetKSeFFilters), co zapobiega pobieraniu duplikatów.
  • Ochrona przed zapętleniem: Jeśli skrypt napotka nieznany błąd (inny niż limit żądań), przerywa pętlę (break), umożliwiając administratorowi analizę kodu błędu w logach.

Konfiguracja przykładu

Aby skrypt mógł poprawnie realizować automatyczny import, niezbędna jest odpowiednia konfiguracja:

  • Utworzenie punktu ACD – Import dokumentów zakupu z KSeF – gotowe przykłady (Punkt ACD, Typ obiegu), z których można skorzystać: Przykłady procesów biznesowych
  • Wskazanie w pliku BMP.exe.config w kluczu RPAFolderPath, w sekcji <appSettings> ścieżki do folderu z Comarch BPM Desktop, w którym system przechowuje skompilowane skrypty RPA.
Przyklad
Przykład wpisu: <add key=”RPAFolderPath” value=”C:\Comarch BPM\RPA” />.
Uwaga
Brak poprawnie zdefiniowanej ścieżki uniemożliwi przekompilowanie i zapisanie skryptu C#.
  • W oknie Konfiguracji automatycznego trybu pracy:
    • Dodanie skryptu: Skrypt należy wkleić w zakładce „Edytor skryptów”.
    • Kompilacja: Niezbędne jest użycie przycisku [Kompiluj i zapisz], aby system zweryfikował poprawność kodu i przygotował go do wykonania przez RPA.
    • Zapis: Po poprawnej kompilacji wymagane jest zapisanie ustawień punktu za pomocą przycisku zapisz32 [Zapisz].
Przekompilowany Skrypt C#

Gotowy Skrypt C#

using System;
using System.Threading;
using System.Net;
using System.Net.Mail;

// --- KONFIGURACJA AUTOMATU ---
int lastErrorId = 0;
ACDError[] errorsArray = null;
string nazwaPunktuKSeF = "Dokumenty z KseF - (A)FZ"; 
string nazwaObiegu = "Faktura kosztowa (Elementy)_KSEF"; // <-- WPISZ TUTAJ NAZWĘ OBIEGU

Globals.ACD.Show();
Globals.Common.Trace("=== START: Automatyczny nadzór KSeF (Cykl 5 min) ===");

try
{
    while (true)
    {
        // 1. Monitorowanie błędów na punkcie ACD
        Globals.ACD.GetErrorList(ref errorsArray);
        if (errorsArray != null && errorsArray.Length > 0)
        {
            if (lastErrorId != errorsArray[0].Id)
            {
                lastErrorId = errorsArray[0].Id;
                string errorMsg = "Błąd techniczny ACD: " + errorsArray[0].Message;
                Globals.Common.Trace(errorMsg);
                SendMail("Alert ACD", errorMsg); 
            }
        }

        // 2. Ustawienie punktu ACD
        Globals.ACD.SetPoint(nazwaPunktuKSeF, 1);

        // --- NOWA METODA: Ustawienie typu obiegu ---
        int flowResult = Globals.ACD.SetDocumentFlow(nazwaObiegu);
        if (flowResult == 0)
        {
            Globals.Common.Trace("Pomyślnie ustawiono obieg: " + nazwaObiegu);
        }
        else if (flowResult == -2)
        {
            Globals.Common.Trace("BŁĄD: Obieg '" + nazwaObiegu + "' nie istnieje!");
        }
        else
        {
            Globals.Common.Trace("BŁĄD: Nieoczekiwany błąd przy ustawianiu obiegu (kod: " + flowResult + ")");
        }

        // 3. Proces importu z KSeF z obsługą limitów 
        try 
        {
            Globals.Common.Trace("Próba pobrania dokumentów z KSeF...");
            
            int maxRetries = 3;
            int retryCount = 0;
            int waitTimeSeconds = 300; // 5 minut
            bool importSuccess = false;

            while (!importSuccess && retryCount < maxRetries)
            {
                int importResult = Globals.ACD.ImportKSeFDocuments();

                if (importResult == 0)
                {
                    Globals.Common.Trace("Import faktur z KSeF zakończony sukcesem");
                    importSuccess = true;

                    Globals.ACD.RefreshList();
                    // Sprawdzenie czy są jakiekolwiek dokumenty na liście
                    if (Globals.ACD.SelectDocument(0) >= 0)
                    {
                        Globals.Common.Trace("Wykryto dokumenty. Generowanie do DMS...");
                        Globals.ACD.GenerateDMSDocuments();
                        Globals.Common.Trace("Generowanie zakończone pomyślnie.");
                    }
                    else
                    {
                        Globals.Common.Trace("Brak nowych dokumentów w KSeF.");
                    }
                }
                else if (importResult == -2)
                {
                    retryCount++;

                    string retryMsg = $"Przekroczono limit żądań KSeF (429 Too Many Requests). " +
                                      $"Próba {retryCount}/{maxRetries}. " +
                                      $"Oczekiwanie {waitTimeSeconds} sekund (5 minut) przed ponowną próbą...";
                    Globals.Common.Trace(retryMsg);

                    if (retryCount < maxRetries)
                    {
                        Thread.Sleep(waitTimeSeconds * 1000);
                    }
                    else
                    {
                        string failMsg = "Osiągnięto maksymalną liczbę prób. " +
                                        "Import faktur z KSeF nie powiódł się. " +
                                        "Spróbuj ponownie później.";
                        Globals.Common.Trace(failMsg);
                        SendMail("KSeF Rate Limit", failMsg);
                    }
                }
                else
                {
                    string statusMsg = $"Błąd importu faktur z KSeF. Kod błędu: {importResult}";
                    Globals.Common.Trace(statusMsg);
                    SendMail("Błąd połączenia KSeF", statusMsg);
                    break;
                }
            }
        }
        catch (Exception exIn)
        {
            Globals.Common.Trace("Błąd podczas pętli importu: " + exIn.Message);
        }

        // --- USPRAWNIONE OCZEKIWANIE ---
        Globals.Common.Trace("Cykl zakończony. Następne sprawdzenie za 5 minut...");
        
        for (int i = 0; i < 300; i++) 
        {
            Thread.Sleep(1000); // Czekaj 1 sekundę (umożliwia responsywność interfejsu)
        }
    }
}
catch (Exception ex)
{
    Globals.Common.Trace("KRYTYCZNY BŁĄD SKRYPTU: " + ex.Message);
    SendMail("KSeF Fatal Error", ex.Message);
}

// --- FUNKCJA WYSYŁAJĄCA POWIADOMIENIA E-MAIL ---
string SendMail(string temat, string tresc)
{
    try
    {
        MailMessage mail = new MailMessage();
        mail.From = new MailAddress ("XXX@firma-klienta.pl"); // Adres e-mail nadawcy (automatu)
        mail.To.Add ("YYY@firma-klienta.pl");           // Adres e-mail do powiadomień o błędach
        mail.Subject = "DMS KSeF Alert: " + temat;
        mail.Body = "Zdarzenie zarejestrowane przez automat KSeF:\n\n" + tresc;
        mail.BodyEncoding = System.Text.Encoding.UTF8;

        SmtpClient smtp = new SmtpClient  ("smtp.serwer-klienta.pl"); // Serwer SMTP klienta
        smtp.Port = 587;   // Port serwera (standardowo 587 lub 465)
        smtp.Credentials = new NetworkCredential ("użytkownik", "hasło"); //Podać login i hasło
        smtp.EnableSsl = true;

        smtp.Send(mail);
        return "";
    }
    catch (Exception ex) 
    { 
        Globals.Common.Trace("Błąd wysyłki powiadomienia e-mail: " + ex.Message);
        return ex.Message; 
    }
}
Rozpoczynasz pracę z Comarch BPM (dawniej DMS) i chcesz dowiedzieć się, jak korzystać z programu? A może masz już podstawową wiedzę o Comarch BPM (dawniej DMS) i chcesz dowiedzieć się więcej?

Sprawdź Szkolenia Comarch BPM!

 

Czy ten artykuł był pomocny?