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
- Otevře Klientské reporty → zvolí klienta + období.
- 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ů.
- 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
/invoicingstránce jako thin emerald banner "X reportů čeká na fakturaci". - Na
/uzavirkyjako CTA "Vystavit fakturu" u relevantních řádků.
3. Michala vystavuje fakturu
- Klikne + Nová faktura (z dashboardu, banneru nebo deep-link z /uzavirky).
- CreateInvoiceModal se otevře. Pokud klient má unfaktur. report, smart-default přepne tab na "Z klientského reportu".
- Vidí preview řádků reportu + total částku.
- Volitelně klikne "Z času + nákladů" pro old workflow (Sprint 75 path) — tab je radio source switcher.
- Klikne Vytvořit fakturu → vznikne faktura s
invoice_linessnapshotem 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:
invoices.created_from_report_idFK →client_reports. Označuje, že faktura byla vytvořena z reportu.invoice_linestabulka — per-line snapshot zclient_report_lines. 15 sloupců mirrorclient_report_linesshape.
Při createFromClientReport mutation:
- Server vezme report → načte řádky.
- Insertne fakturu (header).
- Per-line: insertne
invoice_linesrow se snapshotem (description, amount, durationMinutes, projectName, sortOrder, entityType, source_entry_ids, source_expense_ids). - Atomicky (TOCTOU UPDATE WHERE) flipne source
time_entries.status='invoiced'+expenses.status='invoiced'+invoice_idFK. - 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.
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).
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:
- Otevřete fakturu v
/invoicing. - Sekce "Vytvořeno z reportu" ukazuje read-only lines + tlačítko Aktualizovat z reportu.
- 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_entriesse vrátí dostatus='approved'+invoice_id=NULL. - ✅ Source
expensesse vrátí dostatus='approved'+invoice_id=NULL(Sprint 92.9 fix — předtím to byl DATA LOSS bug). - ✅ Audit log obsahuje reverse
closure_contextentry strigger='invoice_cancelled'. - ✅ Linked
client_reportzů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_orgzajišť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.