Cyklicznie wystawianie faktur w Infakt za pomocą N8N

Spis treści

Wstęp

Co miesiąc ten sam rytuał. Otwierasz arkusz, sprawdzasz kto ma abonament, wystawiasz faktury. Ręcznie. Jedna po drugiej. Czasem zapomnisz, czasem pomylisz datę. Brzmi znajomo? U nas tak było. Różni klienci, różne usługi, różne daty rozpoczęcia usług. Każdego miesiąca traciliśmy cenny czas na mechaniczne czynności. Postanowiłem to zmienić i stworzyłem prosty workflow w n8n, który całkowicie wyeliminował ten problem.

W tym poradniku przeprowadzę Cię przez proces tworzenia automatyzacji, która sprawdzi, komu należy wystawić fakturę, wygeneruje ją automatycznie, wyśle do klienta profesjonalnego maila z linkiem do płatności online i zaktualizuje arkusz z historią faktur. Brzmi skomplikowanie? Spokojnie, rozbijemy to na proste kroki.

Narzędzia, których użyjemy

  • n8n – platforma do automatyzacji workflow
  • Google Sheets – do przechowywania danych o klientach i usługach
  • Infakt API – do generowania faktur
  • Mailgun – do wysyłania maili

Wymagania wstępne

Zanim przejdziemy do konfiguracji, upewnij się, że masz:

  1. Konto w n8n (możesz użyć wersji cloud lub self-hosted).
  2. Arkusz Google Sheets z danymi o klientach i usługach.
  3. Konto w Infakt z dostępem do API (potrzebny klucz API).
  4. Konto w Mailgun (lub inna usługa do wysyłania maili) (potrzebny klucz API).
  5. Podstawową znajomość n8n – jeśli jesteś początkujący, nie martw się, wyjaśnię wszystko krok po kroku.

Konfiguracja Arkusza Google Sheets

Pierwszym krokiem jest przygotowanie arkusza Google Sheets, który będzie przechowywał dane o Twoich klientach i usługach. Arkusz powinien mieć dwie zakładki:

  1. Usługi – tu przechowujemy informacje o klientach i ich usługach.
  2. Historia_faktur – tu będziemy zapisywać historię wystawionych faktur.

Struktura zakładki „Usługi”

Oto jak powinna wyglądać zakładka „Usługi”:

  • ID – unikalny identyfikator usługi.
  • Nazwa_firmy – nazwa firmy klienta.
  • E_mail – adres email klienta.
  • Nazwa_usługi – nazwa usługi z placeholderami, np. „Usługa testowa – abonament %month% %year%”.
  • Cena_netto – cena netto usługi.
  • VAT – stawka VAT (np. 23).
  • Cykl – cykl rozliczeniowy (miesięczny lub roczny).
  • Data_rozpoczęcia – data rozpoczęcia usługi.
  • Ostatnia_faktura – data ostatniej wystawionej faktury.
  • Status – status usługi (aktywna lub nieaktywna).

Przykład danych w Google Sheets

Zrzut ekranu przedstawia arkusz Google Sheets z zakładką „Usługi”.
Ten arkusz jest podstawą automatyzacji, dostarczając dane do generowania faktur.

Konfiguracja n8n

Teraz przejdziemy do n8n, gdzie stworzymy workflow automatyzujący cały proces. Workflow składa się z 11 node-ów, które wykonują poszczególne zadania: od sprawdzenia daty, przez wystawienie faktury, po wysłanie maila do klienta.

Oto jak wygląda gotowy workflow:

Zrzut ekranu przedstawia workflow „Automatyczne wystawianie faktur w Dogtronic” w n8n.

Krok 1 – utwórz nowy workflow

Zaloguj się do n8n i utwórz nowy workflow. Nazwij go np. „Automatyczne wystawianie faktur”.

Krok 2 – dodaj node Schedule Trigger

Ten node uruchomi workflow codziennie o 8:00 rano.

  • Typ: Schedule Trigger.
  • Konfiguracja: Ustaw Trigger at na 8:00 AM.
  • Cel: Codzienne sprawdzanie, czy należy wystawić nowe faktury.

Krok 3 – dodaj node Google Sheets (get_invoices)

Ten node pobierze dane z arkusza „Usługi”.

  • Typ: Google Sheets.
  • Operacja: Read.
  • Document ID: ID Twojego arkusza Google Sheets.
  • Sheet Name: „Usługi”.
  • Autentykacja: Użyj OAuth2 dla Google Sheets.
  • Cel: Pobranie listy usług i klientów z arkusza.

Krok 4 – dodaj node Code (check_date)

Ta noda zawiera skrypt JavaScript, który sprawdzi, dla których usług minął miesiąc lub rok od ostatniej faktury i przygotuje dane do wystawienia nowych faktur.

Przykładowy kod JavaScript:



const today = new Date();
// Ustawiamy godzinę na koniec dnia dla precyzyjniejszego porównania
today.setHours(23, 59, 59, 999);

const items = $input.all();
const invoicesToCreate = [];

// Pobierz aktualny miesiąc i rok
const currentMonth = today.toLocaleString('pl-PL', { month: 'long' }); // styczeń, luty, etc.
const currentYear = today.getFullYear(); // 2025

// Funkcja do bezpiecznego obliczania następnej daty faktury
function calculateNextInvoiceDate(lastInvoiceDate, cycle) {
  const lastDate = new Date(lastInvoiceDate);
  
  if (cycle === 'miesięczny') {
    // Pobierz dzień miesiąca z ostatniej faktury
    const dayOfMonth = lastDate.getDate();
    
    // Utwórz nową datę o miesiąc później
    const nextDate = new Date(lastDate.getFullYear(), lastDate.getMonth() + 1, 1);
    
    // Sprawdź ile dni ma następny miesiąc
    const daysInNextMonth = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate();
    
    // Ustaw dzień - jeśli oryginalny dzień jest większy niż liczba dni w następnym miesiącu,
    // ustaw na ostatni dzień miesiąca
    const targetDay = Math.min(dayOfMonth, daysInNextMonth);
    nextDate.setDate(targetDay);
    
    return nextDate;
  } else if (cycle === 'roczny') {
    const nextDate = new Date(lastDate);
    nextDate.setFullYear(nextDate.getFullYear() + 1);
    return nextDate;
  }
  
  return null;
}

// Funkcja do formatowania daty dla logów
function formatDate(date) {
  return date.toISOString().split('T')[0];
}

console.log(`=== SPRAWDZANIE FAKTUR NA DZIEŃ: ${formatDate(today)} ===`);

for (const item of items) {
  const data = item.json;
  
  console.log(`\n--- Sprawdzanie usługi: ${data.Nazwa_usługi} (ID: ${data.ID}) ---`);
  
  if (data.Status !== 'aktywna') {
    console.log(`Status: ${data.Status} - pomijam`);
    continue;
  }
  
  if (!data.Ostatnia_faktura) {
    console.log(`Brak daty ostatniej faktury - pomijam`);
    continue;
  }
  
  const lastInvoice = new Date(data.Ostatnia_faktura);
  const startDate = new Date(data.Data_rozpoczęcia);
  
  console.log(`Ostatnia faktura: ${formatDate(lastInvoice)}`);
  console.log(`Cykl: ${data.Cykl}`);
  
  // Sprawdź czy data ostatniej faktury jest prawidłowa
  if (isNaN(lastInvoice.getTime())) {
    console.log(` Nieprawidłowa data ostatniej faktury: ${data.Ostatnia_faktura} - pomijam`);
    continue;
  }
  
  const nextInvoiceDate = calculateNextInvoiceDate(lastInvoice, data.Cykl);
  
  if (!nextInvoiceDate) {
    console.log(` Nieobsługiwany cykl: ${data.Cykl} - pomijam`);
    continue;
  }
  
  console.log(` Następna faktura powinna być: ${formatDate(nextInvoiceDate)}`);
  console.log(` Dzisiaj: ${formatDate(today)}`);
  console.log(` Czy czas na fakturę? ${nextInvoiceDate <= today ? 'TAK' : 'NIE'}`);
  
  if (nextInvoiceDate <= today) {
    let serviceName;
    
    // Sprawdź czy istnieje numer zamówienia
    if (data.Numer_zamowienia && data.Numer_zamowienia.trim() !== '') {
      // Jeśli jest numer zamówienia, użyj tego formatu
      serviceName = `Usługi zgodnie zamówieniem nr ${data.Numer_zamowienia}`;
    } else {
      // Jeśli nie ma numeru zamówienia, użyj standardowej nazwy z placeholderami
      // Pobierz miesiąc i rok z daty następnej faktury
      const invoiceMonth = nextInvoiceDate.toLocaleString('pl-PL', { month: 'long' });
      const invoiceYear = nextInvoiceDate.getFullYear();

      // Zamień %month% i %year% w nazwie usługi
      serviceName = data.Nazwa_usługi
        .replace('%month%', invoiceMonth)
        .replace('%year%', invoiceYear);
    }
    
    const invoice = {
      serviceId: data.ID,
      clientId: data.Klient_ID,
      client_company_name: data.Nazwa_firmy,
      client_email: data.E_mail,
      serviceName: serviceName,
      price: data.Cena_netto,
      vat: data.VAT,
      cycle: data.Cykl,
      // Dodaj dodatkowe informacje do debugowania
      lastInvoiceDate: formatDate(lastInvoice),
      nextInvoiceDate: formatDate(nextInvoiceDate),
      todayDate: formatDate(today)
    };
    
    invoicesToCreate.push(invoice);
    console.log(`FAKTURA DO UTWORZENIA dla ${data.Nazwa_firmy}`);
  } else {
    console.log(`Jeszcze nie czas na fakturę`);
  }
}



if (invoicesToCreate.length > 0) {
  console.log(`\n Lista faktur do utworzenia:`);
  invoicesToCreate.forEach((invoice, index) => {
    console.log(`${index + 1}. ${invoice.client_company_name} - ${invoice.serviceName}`);
  });
}

return invoicesToCreate.map(invoice => ({ json: invoice }));

Dynamiczne nazwy usług – mały trick, duża oszczędność
Zamiast ręcznie edytować nazwę usługi co miesiąc, używam placeholderów:
W arkuszu wpisuję: Usługa testowa - abonament %month% %year%.
System automatycznie zamienia to na: Usługa testowa – abonament czerwiec 2025.

Cel: Sprawdzenie, które usługi wymagają wystawienia faktury i przygotowanie danych z dynamiczną nazwą usługi (np. "Usługa testowa - abonament czerwiec 2025").

Krok 5 – Dodaj node If

Ten node sprawdzi, czy istnieją FV do wystawienia.

  • Warunek: Sprawdź, czy client_email istnieje i nie jest pusty.
  • Cel: Upewnienie się, że faktura zostanie wysłana tylko do klientów z poprawnym adresem email.

Krok 6 – dodaj node Split in Batches

Ten node podzieli listę faktur do wystawienia na pojedyncze elementy, aby przetwarzać je po kolei.

  • Konfiguracja: Ustaw batch size na 1.
  • Cel: Przetwarzanie faktur jedna po drugiej z opóźnieniem, aby uniknąć problemów z numeracją faktur w Infakt.

Krok 7 – dodaj nodę HTTP Request (create_invoice)

Ten node wyśle żądanie POST do API Infakt w celu utworzenia nowej faktury.

  • URL: https://api.infakt.pl/v3/invoices.json.
  • Metoda: POST.
  • Autentykacja: Header Auth z kluczem API Infakt.
  • Body (JSON):
{
  "invoice": {
    "kind": "vat",
    "number": null,
    "sell_date": "{{$json.nextInvoiceDate}}",
    "invoice_date": "{{$json.nextInvoiceDate}}",
    "payment_to": "{{DateTime.fromISO($json.nextInvoiceDate).plus({days: 7}).toFormat('yyyy-MM-dd')}}",
    "client_company_name": "{{ $json.client_company_name }}",
    "services": [
      {
        "name": "{{$json.serviceName}}",
        "net_price": "{{Math.round($json.price * 100)}}",
        "unit_net_price": "{{Math.round($json.price * 100)}}",
        "tax_symbol": "{{$json.vat}}",
        "quantity": 1
      }
    ]
  }
}
Cel: Utworzenie nowej faktury w Infakt z dynamicznymi danymi.

Krok 8 – Dodaj node Google Sheets (update_sheet)

Po wystawieniu faktury zaktualizuj arkusz „Usługi” z nową datą ostatniej faktury.

  • Operacja: Update.
  • Sheet Name: Usługi.
  • Columns to update: Ostatnia_faktura z wartością {{ $json.nextInvoiceDate }}.
  • Matching column: ID z wartością {{ $json.serviceId }}.
  • Cel: Aktualizacja daty ostatniej faktury w arkuszu.

Krok 9 – Dodaj node Google Sheets (add_to_history)

Dodaj rekord o wystawionej fakturze do zakładki Historia_faktur.

  • Operacja: Append.
  • Sheet Name: „Historia_faktur”.
  • Columns:
    • ID: {{ $('create_invoice').item.json.uuid }}.
    • Data_dodania: {{DateTime.now().toFormat('yyyy-MM-dd H:mm')}}.
    • Klient_ID: {{ $json.client_company_name }}.
    • Numer_faktury: {{ $('create_invoice').item.json.number }}.
    • Data_wystawienia: {{ $('create_invoice').item.json.invoice_date }}.
    • Kwota_netto: {{ $('create_invoice').item.json.net_price / 100 }}.
    • Status: {{ $('create_invoice').item.json.status }}.
    • Url: {{ $('create_invoice').item.json.extensions.payments.link }}.
  • Cel: Zapisanie historii wystawionych faktur.

Krok 10 – Dodaj node HTTP Request (download_invoice)

Pobierz PDF faktury z Infakt.

  • URL: https://api.infakt.pl/v3/invoices/{{ $('create_invoice').item.json.uuid }}/pdf.json?document_type=original.
  • Metoda: GET.
  • Autentykacja: Header Auth z kluczem API Infakt.
  • Cel: Pobranie PDF faktury do załączenia w mailu.

Krok 11 – Dodaj node Mailgun (send_mail)

Wyślij maila do klienta z fakturą i linkiem do płatności.

  • From: Twój adres skrzynki nadawczej.
  • To: {{ $json.client_email }}.
  • CC: Adres np. zarządu.
  • Subject: Nowa faktura do zapłaty ({{ $('create_invoice').item.json.number }}).
  • HTML Body: Profesjonalnie sformatowany mail z danymi faktury i przyciskiem „ZAPŁAĆ TERAZ”.
  • Attachments: PDF faktury.

Przykład maila wysłanego do klienta:

Przykład wiadomość e-mail, którą otrzymuje klient.

Fragment HTML maila:

<p>Dzień dobry,</p>
<p>W załączeniu przesyłamy fakturę:</p>
<ul>
  <li><strong>Numer faktury:</strong> {{ $('create_invoice').item.json.number }}</li>
  <li><strong>Usługa:</strong> {{ $json.serviceName }}</li>
  <li><strong>Data wystawienia:</strong> {{ $('create_invoice').item.json.invoice_date }}</li>
  <li><strong>Termin płatności:</strong> {{ $('create_invoice').item.json.payment_date }}</li>
  <li><strong>Kwota do zapłaty:</strong> {{ $('create_invoice').item.json.gross_price / 100 }} PLN</li>
</ul>
<div class="payment-section">
  <p>Zapłać wygodnie i bezpiecznie online:</p>
  <a href="{{ $('add_to_history').item.json.Url }}" style="display: inline-block; background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; margin: 15px 0; text-align: center;">ZAPŁAĆ TERAZ</a>
  <p><small>Bezpieczne płatności obsługiwane przez Infakt.pl</small></p>
</div>
<p>Dziękujemy za terminową płatność i dotychczasową współpracę! 🙏</p>
<p>W razie pytań jesteśmy do Państwa dyspozycji.</p>
<hr>
<p><small>Ta wiadomość została wygenerowana automatycznie. Prosimy nie odpowiadać na ten email. W razie wątpliwości prosimy o kontakt na adres hello@dogtronic.io</small></p>
Cel: Wysłanie maila do klienta z fakturą i linkiem do płatności.

Krok 12 – Dodaj node Wait

Wprowadź opóźnienie 1 sekundy przed przetworzeniem kolejnej faktury.

  • Amount: 1 second.
  • Cel: Zapobieganie problemom z numeracją faktur w Infakt przez zbyt szybkie wysyłanie żądań.

Testowanie Workflow

Po skonfigurowaniu wszystkich nodów, przetestuj workflow z danymi testowymi:

  1. Upewnij się, że w arkuszu „Usługi” masz co najmniej jeden wiersz z datą ostatniej faktury, która wymaga wystawienia nowej faktury.
  2. Uruchom workflow manualnie, aby sprawdzić, czy faktura jest poprawnie tworzona i wysyłana.
  3. Sprawdź logi w n8n, aby upewnić się, że nie ma błędów.
  4. Zweryfikuj, czy arkusz „Historia_faktur” został zaktualizowany.
  5. Sprawdź, czy mail został wysłany poprawnie z załącznikiem PDF.

Typowe problemy i rozwiązania:

  • Błąd autentykacji API: Upewnij się, że klucze API są poprawne.
  • Nieprawidłowe daty: Sprawdź format dat w arkuszu i w kodzie JavaScript.
  • Problemy z numeracją faktur: Upewnij się, że node Wait jest skonfigurowana poprawnie.

Podsumowanie

Gratulacje! Właśnie stworzyłeś automatyzację, która oszczędzi Ci masę czasu i zminimalizuje ryzyko błędów w procesie wystawiania faktur. Workflow działa sam, bez Twojej interwencji, a Ty możesz skupić się na ważniejszych zadaniach.

Korzyści

  • Zero pomyłek w datach i kwotach.
  • Automatyczne wysyłanie maili z linkiem do płatności.
  • Pełna historia faktur w arkuszu.
  • Zarząd na bieżąco z informacjami (dzięki CC w mailu).

Zachęcam do dostosowania tego workflow do swoich specyficznych potrzeb. Możesz dodać więcej logiki, np. obsługę różnych walut czy automatyczne przypomnienia o zaległych płatnościach.

Chcesz więcej automatyzacji? Sprawdź nasz wpis o automatycznym oznaczaniu opłaconych faktur.


Dodatkowe zasoby

Pamiętaj, że ten poradnik jest punktem wyjścia. Automatyzacja to potężne narzędzie, które możesz rozwijać i dostosowywać do swoich potrzeb.

Powodzenia 🐶.

Marek Golan

CEO Dogtronic, Prezes ESTA Cluster, dumny opiekun Borysa. Ma prawie 20 lat doświadczenia w branży IT zarówno jako programista jak i właściciel firm technologicznych.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

osiemnaście + szesnaście =

Przekształć
swoje pomysły
w rzeczywistość

Skontaktuj się z nami i pozwól nam sprawdzić jak możemy Ci pomóc.

Najciekawsze treści na Blogu

Zobacz wszystkie
💬 Porozmawiaj z AI!
🎉 Świetnie! Zaraz nasz agent AI do Ciebie zadzwoni.
❌ Wystąpił błąd podczas wysyłania formularza. Spróbuj ponownie.

🤖 AI Agent

Hej! Zostaw swoje dane, a nasz inteligentny agent zadzwoni i pokaże Ci swoje możliwości. To tylko zajmie chwilę! 🚀

Imię jest wymagane
Wprowadź poprawny numer telefonu
Wprowadź poprawny adres email
Zgoda na przetwarzanie danych jest wymagana