Cesta k dostatočne dobrému softvéru pre faktúry bola dosť tŕnistá, povedal by som. Rok a pol som používal vlastnoručne hostovanú inštanciu Craterapp. Crater fungoval takmer dobre. Nemal žiadne zásadné problémy, povedal by som. Ale mal veľa menších problémov, ktoré ma veľmi otravovali. Dalo sa to prežiť, ale hej, toto je administratívna práca. Zvyčajne nie je veľmi zábavná, je nudná a zdĺhavá. Používanie softvéru, ktorý to robí ešte bolestivejším, hoci len trochu, bolo pre mňa smrťou tisícimi rezmi.

Nejakú dobu som hľadal riešenie tohto problému. Vyskúšal som fakturačnú platformu Revolut Business. Tá bola veľmi obmedzená na funkcie. Jedinou výhodou bolo automatické párovanie platieb, čo je fajn, ale pri malom objeme faktúr to nebolo rozhodujúce. Potom som zistil, že Paypal má tiež pomerne vyspelú fakturačnú platformu a chvíľu som sa s ňou pohrával. Takmer by som sa tam usadil, nebyť odporúčania od priateľa.

Prichádza faktury-online.com #

Pred rokom som začal používať https://faktury-online.com na správu faktúr a cenových ponúk. Spočiatku som bol skeptický, ani si nepamätám prečo. Ale keďže je to robené slovenským tímom, okamžite som sa zamiloval, pretože mali vyriešené všetky byrokratické prekážky nášho štátu, na rozdiel od všetkých vyššie spomínaných možností, ktoré boli buď veľmi všeobecné alebo dokonca optimalizované pre USA.

Ako ubehol rok, nenašiel som žiadnu zásadnú prekážku v tomto softvéri. Bol len jeden háčik: nemal som zálohu dát, okrem PDF-iek, ktoré uchovávam v elektronickej forme a účtovník má vo svojom archíve. Poznámka: nemám žiadne prepojenie s faktury-online.com a nikdy som od nich nedostal žiadne sponzorstvo. Ak sa vám páčia, jednoducho ich používajte a plaťte im, prvý rok je zadarmo.

Späť k problému zálohovania. Ponúkajú zálohovací servis, ktorý stojí zhruba dvojnásobok základného poplatku za používanie, ak sa nemýlim. Stále je to v nízkych desiatkach eur za celý rok, takže vôbec nie drahé, ale ponúkajú aj API. Po zvážení jeho schopností som sa rozhodol, že zálohu si urobím sám cez API.

Záloha cez API #

Tento recept potreboval tieto ingrediencie:

  1. skript, buď v PHP alebo JS, ktorý zavolá API a získa všetky dáta
  2. spôsob uloženia dát, najlepšie ako JSON súbor
  3. spôsob spustenia skriptu buď pri zmene alebo aspoň periodicky

Začnite inštaláciou potrebných závislostí:

npm i @actions/core @actions/github @vercel/ncc

Teraz aktualizujte package.json tak, aby obsahoval build:

  "scripts": {
    "build": "ncc build index.js --license licenses.txt"
  },

Teraz vytvorte index.js, ktorý bude skutočne volať API:

const fs = require("fs").promises
const path = require("path")

const key = process.env.API_KEY
const email = process.env.EMAIL
const baseUrl = process.env.BASE_URL

let phpSessionId

async function fetchData(endpoint, code = "") {
  const data = JSON.stringify({ key, email, code })
  const url = `${baseUrl}/${endpoint}?data=${encodeURIComponent(data)}`
  const options = {
    method: "GET",
    credentials: "include",
    headers: phpSessionId ? { Cookie: `PHPSESSID=${phpSessionId}` } : {},
  }

  try {
    const response = await fetch(url, options)

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`)
    }

    const setCookieHeader = response.headers.get("set-cookie")

    if (setCookieHeader) {
      const matches = setCookieHeader.match(/PHPSESSID=([^;]+)/)
      if (matches) {
        phpSessionId = matches[1]
      }
    }

    return await response.json()
  } catch (error) {
    console.error(error)
  }
}

async function writeOutputToFile(data, filePath, sortBy) {
  try {
    data.sort((a, b) => b[sortBy].localeCompare(a[sortBy]))
    await fs.writeFile(filePath, JSON.stringify(data, null, 2))
    console.log("Output written to:", path.resolve(filePath))
  } catch (error) {
    console.error("Error writing to file:", error)
  }
}

;(async () => {
  await fetchData("init")

  const invoicesList = await fetchData("list/created")
  const invoicesOutput = await Promise.all(
    invoicesList.invoices.map(async invoice =>
      fetchData("status", invoice.code)
    )
  )

  await writeOutputToFile(
    invoicesOutput,
    "invoices.json",
    "invoice_number"
  )

  const offersList = await fetchData("cp-list/created")
  const offersOutput = await Promise.all(
    offersList.offers.map(async offer =>
      fetchData("cp-status", offer.code)
    )
  )

  await writeOutputToFile(offersOutput, "offers.json", "offer_number")
})()

Pokračujte s .github/workflows/main.yml, aby sa celý proces mohol automatizovať:

name: Faktury-online.com backup

on:
  workflow_dispatch:
  schedule:
    - cron: "15 0 1 * *"

jobs:
  run:
    runs-on: ubuntu-latest

    permissions:
      contents: write

    env:
      BASE_URL: ${{ secrets.BASE_URL }}
      API_KEY: ${{ secrets.API_KEY }}
      EMAIL: ${{ secrets.EMAIL }}
    steps:
      - uses: actions/checkout@v4

      - run: node dist/index.js

      - uses: stefanzweifel/git-auto-commit-action@v5

A nakoniec nakonfigurujte GitHub Action secrets pre API_KEY, EMAIL a BASE_URL, tieto nájdete na <faktury-online.com>. Taktiež je potrebné povoliť write oprávnenia pre GitHub actions, keďže toto vytvára automatické commity.

Niekoľko ďalších poznámok:

  • ak vykonáte akékoľvek zmeny v index.js, najprv spustite npm run build
  • spúšťa sa na začiatku mesiaca alebo manuálne
  • je možné to spustiť do istej miery manuálne cez act workflow_dispatch, ale vyžaduje GITHUB_TOKEN

Toto bola skvelá príležitosť využiť silu Github Actions. Mám k dispozícii veľa voľných minút GitHub Actions, ktoré len tak sedia a mohli by robiť nejakú užitočnú automatizáciu. Ale doteraz som sa nenaučil, ako ich používať, tak som sa cez víkend prinútil to urobiť. Užite si to!

Odkazy #