Přeskočit na hlavní obsah

Faktura z klientského reportu

Sprint 92 přidal nový workflow: vystavte fakturu, která zrcadlí klientský report 1:1. Klient dostane fakturu identickou s reportem, který obdržel — žádný "wtf moment" typu "report měl 8 řádků, faktura má 30".

Pro koho to je

Typický český agency workflow:

  • Karel (account manager / projektový manažer) post-edituje raw výkazy do prezentačních řádků v Klientském reportu.
  • Michala (CFO / účetní) potřebuje vystavit fakturu identickou s tím, co viděl klient.

Před Sprint 92 musela Michala ručně proklikat 20+ entries v /invoicing, ručně sloučit popisky, manuálně dořešit sazby. Sprint 92 to dělá za jediný klik.

Workflow Karel + Michala

1. Karel pošle report

  1. Otevře Klientské reporty → zvolí klienta + období.
  2. Post-edituje řádky:
    • Sloučí "Schůzky" + "Telefonáty" + "E-maily" → "Komunikace s klientem 5h"
    • Sloučí "Backend tasks" + "Backend bugfixy" → "Backend development 12h"
    • Atd. — výsledných 8 prezentačních řádků z 30 raw výkazů.
  3. Pošle report e-mailem (PDF příloha).

2. Michala vidí signál na dashboardu

Na dashboardu se objeví widget "Reportováno klientovi, čeká na fakturaci" (visible pro accountant+ persony). Widget ukazuje seznam reportů, které byly odeslány, ale ještě nemají fakturu.

Stejný signál existuje:

  • Na /invoicing stránce jako thin emerald banner "X reportů čeká na fakturaci".
  • Na /uzavirky jako CTA "Vystavit fakturu" u relevantních řádků.

3. Michala vystavuje fakturu

  1. Klikne + Nová faktura (z dashboardu, banneru nebo deep-link z /uzavirky).
  2. CreateInvoiceModal se otevře. Pokud klient má unfaktur. report, smart-default přepne tab na "Z klientského reportu".
  3. Vidí preview řádků reportu + total částku.
  4. Volitelně klikne "Z času + nákladů" pro old workflow (Sprint 75 path) — tab je radio source switcher.
  5. Klikne Vytvořit fakturu → vznikne faktura s invoice_lines snapshotem 1:1.

4. Klient dostane fakturu

PDF, Fakturoid, iDoklad i ISDOC export renderují fakturu přesně podle 8 řádků z reportu. Klient vidí identický dokument jako report — žádné překvapení.

Co se přesně stane v databázi

Sprint 92 přidalo dvě klíčové struktury:

  1. invoices.created_from_report_id FK → client_reports. Označuje, že faktura byla vytvořena z reportu.
  2. invoice_lines tabulka — per-line snapshot z client_report_lines. 15 sloupců mirror client_report_lines shape.

Při createFromClientReport mutation:

  1. Server vezme report → načte řádky.
  2. Insertne fakturu (header).
  3. Per-line: insertne invoice_lines row se snapshotem (description, amount, durationMinutes, projectName, sortOrder, entityType, source_entry_ids, source_expense_ids).
  4. Atomicky (TOCTOU UPDATE WHERE) flipne source time_entries.status='invoiced' + expenses.status='invoiced' + invoice_id FK.
  5. Audit log + webhook.

Drift warning (Sprint 92.9)

Pokud Karel edited total se liší od source aggregates (např. sloučil řádky a snížil celkovou částku z 19 680 Kč na 13 380 Kč), CreateInvoiceModal ukáže warning:

⚠️ Report 19 680 Kč → Faktura 13 380 Kč (rozdíl −6 300 Kč). Karel upravil report tak, že celková částka neodpovídá sumě raw výkazů. ☐ Rozumím, že faktura nebude zrcadlit raw výkazy.

Submit je blokován, dokud Michala neoznačí checkbox. Audit log obsahuje dedikovaný report_drift entry pro forensics.

Proč drift warning?

Drift je legitimní (Karel může legitimně dát klientovi slevu sloučením řádků), ale Michala musí vědomě potvrdit, že je to OK. Bez warning by mohlo dojít k tichému under-billing — agentura by účtovala klientovi méně, než si zaslouží.

Sazby a hodiny v PDF

V PDF/Fakturoid/iDoklad/ISDOC se na řádcích z reportu zobrazí:

  • Pro time entries: jednotka "hod", počet hodin (durationMinutes / 60), Karel-edited description, amount.
  • Pro náklady: jednotka "ks", qty 1, popis nákladu, amount.
  • Worker + hourlyRate columns: prázdné (—). Klient vidí pouze klientskou prezentaci, ne interní detail (kdo dělal + jaká byla sazba).
To je intentional

Sprint 92 design decision: faktura z reportu = klient vidí přesně to, co viděl v reportu. Pokud chcete faktury s detailem worker + sazba, použijte Sprint 75 path "Z času + nákladů".

Aktualizace faktury z reportu (refresh)

Pokud Karel vytvořil revizi reportu (v2), můžete fakturu aktualizovat:

  1. Otevřete fakturu v /invoicing.
  2. Sekce "Vytvořeno z reportu" ukazuje read-only lines + tlačítko Aktualizovat z reportu.
  3. Klik → ConfirmDialog → řádky se přepíší podle latest revision (v2).

Refresh je explicit user action, ne automatický — Michala vědomě rozhoduje, zda fakturu upravit nebo nechat originální verzi.

Mirror invariant — co nelze (Sprint 92.5)

Faktura vytvořená z reportu je mirror snapshot — má vlastní invarianty:

  • ❌ Nelze přidat/odebrat entries pomocí addEntries/removeEntries (server vrací PRECONDITION_FAILED).
  • ❌ Nelze přidat/odebrat náklady pomocí addExpenses/removeExpenses.
  • ❌ Nelze zapnout Sprint 90 flat-fee mode (hideLineItems) ani slevu/override.

UI tyto sekce v EditInvoiceModal skrývá. Pokud chcete dělat manuální úpravy, musíte fakturu stornovat a vytvořit novou přes Sprint 75 path "Z času + nákladů".

Co se stane při stornu faktury (Sprint 92.9 P0-A)

Při invoices.cancel nebo delete:

  • ✅ Source time_entries se vrátí do status='approved' + invoice_id=NULL.
  • ✅ Source expenses se vrátí do status='approved' + invoice_id=NULL (Sprint 92.9 fix — předtím to byl DATA LOSS bug).
  • ✅ Audit log obsahuje reverse closure_context entry s trigger='invoice_cancelled'.
  • ✅ Linked client_report zůstává sent (storno faktury nesmazává report).

Existing invoice detection (Sprint 92.8)

Na /uzavirky Sprint 75 a Sprint 92 path coexist. Pokud existuje sent report → zobrazí se Sprint 92 button "Vystavit fakturu z reportu". Pokud existuje invoice s created_from_report_id → info badge "Faktura FV-XXX již existuje" + read-only.

To zabrání vystavení duplicitní faktury ze stejného reportu (DB partial UNIQUE invoices_from_report_unique + server pre-check).

CZ účetní compliance

  • §28 ZDPH (Sprint 92.9 P1-5): partial UNIQUE invoice_number_unique_per_org zajišťuje, že žádné dvě faktury v org nemají stejné číslo.
  • §29 ZDPH: faktura má povinný předmět plnění (description), DÚZP, VS — beze změny ze Sprint 75.

Přístup

Vystavit fakturu z reportu má každý plán s aktivním modulem Fakturace. Role:

  • Owner / Admin / Accountant (Sprint 92.5 P1-1): plný přístup.
  • Manager: scoped přes client_assignments (vidí jen reporty pro své klienty).
  • HR / Worker: žádný přístup.