-
-
-
-
Phase 1 — Billing Statement Export (near-term) Sztab already has time tracking (SZ-71). The missing piece is a billing rate per user per project, which is a small schema addition. With that in place, Sztab can generate a monthly billing statement: grouped by calendar month, with hours X rate and subtotals - exportable as PDF or CSV. This is the "copy-paste into your invoicing system" path, and it's buildable quickly.
Phase 2 — Native Invoice Entity (medium-term) Sztab generates and stores invoices directly: line items pulled from time entries, a Draft => Sent => Paid status lifecycle, PDF rendering, and a shareable link. This makes Sztab the source of truth for contractor-to-Tigase billing - which is exactly our use case.
Phase 3 — QBO Sync (longer-term) As discussed separately, a provider-agnostic bookkeeping integration layer (QBO first, extensible to Xero etc.) would push finalized invoices out to the accounting system automatically. No double-entry.
Starting with Phase 1 as a near-term addition: it has immediate practical value and the groundwork is already there.
-
Task Estimate Flyway migration 1–2h BillingStatementService3–4h BillingStatementController(JSON + CSV)2h PDF rendering (server-side) 4–6h Multi-project consolidated statement 3–4h Frontend — rate field on membership UI 2h Frontend — billing statement page 4–5h Frontend — download wiring 1–2h Total ~20–26h -
Implementation notes (Phase 1)
repository layer: monthlyBilling() in TimeEntryRepository does one JPQL query, one row per (project, year, month). has userId, username, projectId, projectName, year, month, totalMinutes. billing rate is NOT in this query - it lives in project_memberships and dragging it into a GROUP BY is more trouble than its worth.
service layer (BillingStatementServiceImpl.generate):
- resolve project scope: caller passes projectIds or we fall back to findActiveProjectsForUser
- fire monthlyBilling() with resolved list
- for each row: single findActiveMembership call (not two - rate and currency both come from the same membership lookup), compute amount = (totalMinutes / 60) * rate, build BillingLineItem
wrap it all in BillingStatement with grandTotal.
notes:
- billing rate/currency on project_memberships, per user per project, set by owner/maintainer
- no rate configured = amount is zero, currency defaults USD
-
Implementation Sequence:
This is the sequence:
- Phase I: Create billing statement — computed from time entries × rate; this is not stored
- Implement invoice lifecycle: Native invoice entity - generate from billing statement, store it, Implement lifecycle: Draft => Sent => Paid lifecycle, Made visible through REST using shareable token
- Phase 2: Invoice export — render stored invoice as PDF (renderer registry, pluggable formats)
- Phase 3: QBO Sync
-
Phase I Complete: Sztab can now compute a monthly billing statement from logged time entries. Each project membership carries a billing rate (hourly, per user). Sztab uses these two to produce a breakdown of hours worked times rate, grouped by month and project. This can be exported as PDF or CSV and used as the basis for preparing an invoice in any external system.
Phase 1 — Billing statement (report, not persisted)
- V25: added
billing_rate_per_hour,billing_currencytoproject_members ProjectMemberentity + DTO updated with billing fieldsUpdateProjectMemberDtoextended with optional billing fieldsProjectMemberService.updateMembersignature extended to carry billing fieldsTimeEntryRepository.monthlyBilling()— new JPQL query, one row per (project, year, month)BillingStatement+BillingLineItemDTOsBillingStatementService+ impl — resolves project scope, joins billing rate from membership, computes hours x rate per line item (single membership lookup per row)BillingStatementController—GET /api/projects/{projectId}/billing/statementConsolidatedBillingController—GET /api/billing/statement/consolidated(no project scope, spans all user memberships)
- V25: added
-
Invoice Lifecycle defined and implemented:
InvoiceStatus enum: DRAFT, SENT, PAID Invoice entity: status, sent_at, paid_at fields InvoiceService.markSent: transitions DRAFT => SENT, records sentAt InvoiceService.markPaid: transitions SENT => PAID, records paidAt InvoiceController: PATCH /api/invoices/{id}/sent, PATCH /api/invoices/{id}/paid -
UI for billing and invoicing:
Frontend additions
- two type files (billing.ts, invoice.ts)
- two API modules (billing.ts, invoices.ts),
- and two pages (InvoiceListPage, InvoiceDetailPage).
Existing files modified:
- App.tsx (routes),
- Sidebar.tsx (nav item),
- ProjectMembersPage.tsx (billing rate field on member edit).
-
rksuma@Ramakrishnans-MacBook-Pro sztab % git add pom.xml backend/pom.xml query-cli/pom.xml query-dsl/pom.xml git commit -m "SZ-78: merge to wolnosc, resolve pom version conflicts" git push origin wolnosc [wolnosc b48e963] SZ-78: merge to wolnosc, resolve pom version conflicts Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 12 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 337 bytes | 337.00 KiB/s, done. Total 2 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0) To https://tigase.dev/sztab.git 8fe3015..b48e963 wolnosc -> wolnosc rksuma@Ramakrishnans-MacBook-Pro sztab % -
-
Phase 3 - QBO Sync - https://tigase.dev/sztab/~issues/158
| Type |
New Feature
|
| Priority |
Normal
|
| Assignee | |
| Version |
none
|
| Sprints |
n/a
|
| Customer |
n/a
|
There are a few features I would like to have to avoid work duplication:
This is probably more distant work and we would need to have some API or REST API. Not sure what would be the best way to approach. Every bookkeeping system has different APIs. We now use QBO, but maybe there is a way to prepare it in more generic way to allow for easier integration with other CPA systems.