Gonçalo Rodrigues 0f58a51c6d feat(finance): org Phases 2-5 — events, requests, ledger, analysis, report (#20)
Phase 2 — Event planning:
- Create/edit/delete/submit events per fiscal year
- Budget lines (income + expense) with planned amounts
- Admin review flow: approve / reject / comment
- Post-mortem feedback comments on closed years

Phase 3 — Transaction requests:
- 5 types: reimbursement, purchase_order, cash_advance, income, budget_transfer
- Full status machine with append-only StatusLog audit trail
- PO delivery sub-form (actual vendor, amount, invoice note)
- Cash advance settlement sub-form (spent + returned)
- Ledger view per fiscal year with income/expense summary
- Bank CSV import: parse → preview → confirm → ledger entries

Phase 4 — Plan vs actual analysis:
- Variance table by event (planned vs actual income/expense)
- Variance table by team

Phase 5 — Year-end report:
- Executive summary with net variance
- Per-event section: budget lines + team feedback comments
- Feedback form available on closed fiscal years

Supporting changes:
- New store methods: getLedgerEntries, createLedgerEntry, updateLedgerEntry,
  getAttachments, createAttachment
- New template funcs: dateInput, statusColor, varColor
- parseEuroAmount + parseBankCSV helpers in handler_org.go

Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:58:47 +01:00

86 lines
4.2 KiB
HTML

{{define "content"}}
{{$d := .}}
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px; flex-wrap:wrap; gap:12px;">
<div>
<div style="font-size:12px; color:var(--text3); margin-bottom:4px;">
<a href="/orgs" style="color:var(--text3); text-decoration:none;">Orgs</a> /
<a href="/orgs/{{$d.Org.Slug}}" style="color:var(--text3); text-decoration:none;">{{$d.Org.Name}}</a> /
{{$d.FiscalYear.Label}} — Events
</div>
<h1>Events</h1>
</div>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.FiscalYear.ID}}/analysis" class="btn btn-outline btn-sm">Analysis</a>
{{if ne (print $d.FiscalYear.Status) "closed"}}
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.FiscalYear.ID}}/events/new" class="btn btn-primary btn-sm">+ New event</a>
{{end}}
</div>
</div>
<!-- Year status banner -->
<div style="display:flex; gap:8px; align-items:center; margin-bottom:20px;">
<span style="font-size:12px; color:var(--text3);">Fiscal year:</span>
<span style="font-weight:600;">{{$d.FiscalYear.Label}}</span>
<span style="font-size:11px; font-weight:700; padding:2px 8px; border-radius:20px;
{{if eq (print $d.FiscalYear.Status) "active"}}background:rgba(74,222,128,0.12); color:var(--green);
{{else if eq (print $d.FiscalYear.Status) "closed"}}background:var(--bg3); color:var(--text3);
{{else}}background:rgba(251,191,36,0.1); color:#fbbf24;{{end}}">
{{$d.FiscalYear.Status}}
</span>
<span style="font-size:12px; color:var(--text3);">{{dateShort $d.FiscalYear.StartDate}} — {{dateShort $d.FiscalYear.EndDate}}</span>
</div>
{{if $d.Events}}
<div class="card animate-on-scroll" style="padding:0; overflow:hidden;">
<table>
<thead>
<tr>
<th>Event</th>
<th>Teams</th>
<th>Dates</th>
<th style="text-align:right;">Planned income</th>
<th style="text-align:right;">Planned expense</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{{range $d.Events}}
<tr>
<td style="font-weight:600;">{{.Event.Name}}</td>
<td style="font-size:12px; color:var(--text3);">
{{if .Teams}}{{range .Teams}}<span style="display:inline-block; padding:1px 6px; border-radius:4px; background:var(--bg3); margin-right:3px; font-size:11px;">{{.Name}}</span>{{end}}{{else}}—{{end}}
</td>
<td style="font-size:12px; color:var(--text2);">{{dateShort .Event.DateStart}} — {{dateShort .Event.DateEnd}}</td>
<td style="text-align:right; color:var(--green); font-size:13px;">{{cents .TotalIncome}}</td>
<td style="text-align:right; color:var(--red); font-size:13px;">{{cents .TotalExpense}}</td>
<td>
{{if eq (print .Event.Status) "approved"}}
<span style="font-size:11px; font-weight:700; padding:2px 8px; border-radius:20px; background:rgba(74,222,128,0.12); color:var(--green);">approved</span>
{{else if eq (print .Event.Status) "review"}}
<span style="font-size:11px; font-weight:700; padding:2px 8px; border-radius:20px; background:rgba(251,191,36,0.1); color:#fbbf24;">review</span>
{{else if eq (print .Event.Status) "rejected"}}
<span style="font-size:11px; font-weight:700; padding:2px 8px; border-radius:20px; background:rgba(239,68,68,0.12); color:var(--red);">rejected</span>
{{else}}
<span style="font-size:11px; font-weight:700; padding:2px 8px; border-radius:20px; background:var(--bg3); color:var(--text3);">draft</span>
{{end}}
</td>
<td style="text-align:right;">
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.FiscalYear.ID}}/events/{{.Event.ID}}" class="btn btn-outline btn-sm">View</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="card empty-state animate-on-scroll">
<div style="font-size:36px; margin-bottom:12px;">🗓️</div>
<h3>No events yet</h3>
<p style="margin-bottom:16px;">Plan events with budgets for teams to execute this fiscal year.</p>
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.FiscalYear.ID}}/events/new" class="btn btn-primary">Create first event</a>
</div>
{{end}}
{{end}}