feat: time tracking + billing — hourly rates, time entries, invoices (P1)
Database: time_entries, billing_rates, invoices tables with RLS.
Backend: CRUD services+handlers for time entries, billing rates, invoices.
- Time entries: list/create/update/delete, summary by case/user/month
- Billing rates: upsert with auto-close previous, current rate lookup
- Invoices: create with auto-number (RE-YYYY-NNN), status transitions
(draft->sent->paid, cancellation), link time entries on invoice create
API: 11 new endpoints under /api/time-entries, /api/billing-rates, /api/invoices
Frontend: Zeiterfassung tab on case detail, /abrechnung overview with filters,
/abrechnung/rechnungen list+detail with status actions, billing rates settings
Also: resolved merge conflicts between audit-trail and role-based branches,
added missing types (Notification, AuditLogResponse, NotificationPreferences)
This commit is contained in:
@@ -10,49 +10,34 @@ import (
|
||||
)
|
||||
|
||||
type mockTenantLookup struct {
|
||||
<<<<<<< HEAD
|
||||
tenantID *uuid.UUID
|
||||
err error
|
||||
hasAccess bool
|
||||
accessErr error
|
||||
||||||| 82878df
|
||||
tenantID *uuid.UUID
|
||||
err error
|
||||
=======
|
||||
tenantID *uuid.UUID
|
||||
role string
|
||||
err error
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
role string
|
||||
}
|
||||
|
||||
func (m *mockTenantLookup) FirstTenantForUser(ctx context.Context, userID uuid.UUID) (*uuid.UUID, error) {
|
||||
return m.tenantID, m.err
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
func (m *mockTenantLookup) VerifyAccess(ctx context.Context, userID, tenantID uuid.UUID) (bool, error) {
|
||||
return m.hasAccess, m.accessErr
|
||||
}
|
||||
|
||||
||||||| 82878df
|
||||
=======
|
||||
func (m *mockTenantLookup) GetUserRole(ctx context.Context, userID, tenantID uuid.UUID) (string, error) {
|
||||
if m.role != "" {
|
||||
return m.role, m.err
|
||||
}
|
||||
return "associate", m.err
|
||||
if m.hasAccess {
|
||||
return "associate", m.err
|
||||
}
|
||||
return "", m.err
|
||||
}
|
||||
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
func TestTenantResolver_FromHeader(t *testing.T) {
|
||||
tenantID := uuid.New()
|
||||
<<<<<<< HEAD
|
||||
tr := NewTenantResolver(&mockTenantLookup{hasAccess: true})
|
||||
||||||| 82878df
|
||||
tr := NewTenantResolver(&mockTenantLookup{})
|
||||
=======
|
||||
tr := NewTenantResolver(&mockTenantLookup{role: "partner"})
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
tr := NewTenantResolver(&mockTenantLookup{hasAccess: true, role: "partner"})
|
||||
|
||||
var gotTenantID uuid.UUID
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -101,7 +86,7 @@ func TestTenantResolver_FromHeader_NoAccess(t *testing.T) {
|
||||
|
||||
func TestTenantResolver_DefaultsToFirst(t *testing.T) {
|
||||
tenantID := uuid.New()
|
||||
tr := NewTenantResolver(&mockTenantLookup{tenantID: &tenantID})
|
||||
tr := NewTenantResolver(&mockTenantLookup{tenantID: &tenantID, role: "owner"})
|
||||
|
||||
var gotTenantID uuid.UUID
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user