InfraForge Docs

InfraNotes Core · v2

Welcome

Select a document from the sidebar to read it.

Frontend Handoff: Invoice, Comments, Clients, Reminders – Fixes and How-To

This guide summarizes recent backend fixes and how to consume the affected endpoints from the frontend. Includes payloads and curl examples verified locally.


What changed (backend)

  • Client categories/tags

    • Added: GET /api/clients/{id}/categories, GET /api/clients/{id}/tags.
    • Enforcement: strict team membership; access only if the request context team matches the client’s team_id or the user owns the client.
  • Contacts

    • Confirmed correct endpoints for edit/delete are resource-based: GET|PUT|DELETE /api/contacts/{id}.
    • Use ETag on update: send If-Match with the value from the latest GET /api/contacts/{id}.
  • Comments

    • Added: GET /api/comments/search?q=...&invoice_id=...&limit=....
    • Fixed: comment creation now sets content_type and priority defaults before validation (avoids 500 on missing content_type).
  • Invoices

    • Create: Added fallback totals calculation and invoice number generation when enterprise calculators are not configured (keeps creation working in dev/local).
  • Reminders

    • Verified template create/list and schedule/send endpoints end-to-end. If a missing table occurs in another env, run the migrations (see cmd below).

Frontend usage: quick reference

  • Clients

    • List categories: GET /api/clients/{clientId}/categories → returns []string.
    • List tags: GET /api/clients/{clientId}/tags → returns []string.
  • Contacts

    • Get: GET /api/contacts/{contactId} → read ETag header.
    • Update: PUT /api/contacts/{contactId} with header If-Match: "<ETag>".
    • Delete: DELETE /api/contacts/{contactId}.
  • Comments

    • List by invoice: GET /api/invoices/{invoiceId}/comments.
    • Create: POST /api/invoices/{invoiceId}/comments with body at minimum { "content": "...", "content_type": "text" }.
    • Search: GET /api/comments/search?q=term&invoice_id={invoiceId}&limit=50.
  • Invoices (create)

    • POST /api/invoices body minimal example:
      {
        "client_id": 123,
        "issue_date": "2025-01-15T00:00:00Z",
        "due_date": "2025-02-15T00:00:00Z",
        "currency": "USD",
        "items": [
          { "description": "Consulting", "quantity": 1, "unit_price": 100 }
        ]
      }
      
  • Reminders

    • List templates: GET /api/invoice-reminders/templates.
    • Create template: POST /api/invoice-reminders/templates with fields: name, description, trigger_type, trigger_days, subject_template, body_template, is_active, is_default.
    • Schedule: POST /api/invoices/{invoiceId}/reminders/schedule with { "template_id": <id> }.
    • Send manual: POST /api/invoices/{invoiceId}/reminders/send with { "template_id": <id> }.
    • History: GET /api/invoices/{invoiceId}/reminders.

Tested flows (locally)

  • Comments (works)

    1. Create client → create invoice → create comment → search comments.
    2. Threads and stats endpoints return 200.
      Notes: Creation fails only if an invalid/nonexistent invoice_id is used.
  • Reminders (works)

    1. Create client → create invoice → create template → schedule → send manual → history shows record.
      Notes: If another environment is missing the templates table, run migrations.
  • Clients

    • GET categories/tags return 200 with [] when none exist.
    • Team enforcement active.

Curl examples (copy/paste)

Replace credentials, then run:

# 1) Login
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"test@example.com","password":"TestPassword123!"}' \
  | python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("access_token") or j.get("token") or "")')
echo "Token: ${TOKEN:0:16}..."

# 2) Create client
CLIENT_ID=$(curl -s -X POST http://localhost:8080/api/clients \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"type":"client","category":"organization","name":"UI Flow Client","email":"ui.client@example.com"}' \
  | python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo CLIENT_ID=$CLIENT_ID

# 3) Create invoice (dynamic ISO dates)
ISSUE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
DUE=$(date -u -v+30d +%Y-%m-%dT%H:%M:%SZ)
INVOICE_ID=$(curl -s -X POST http://localhost:8080/api/invoices \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"client_id":'"$CLIENT_ID"',"issue_date":"'"$ISSUE"'","due_date":"'"$DUE"'","currency":"USD","items":[{"description":"Consulting","quantity":1,"unit_price":100}]}' \
  | python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo INVOICE_ID=$INVOICE_ID

# 4) Create comment
curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/comments \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"content":"Test via curl","content_type":"text"}' | head -n 1

# 5) Search comments
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/api/comments/search?q=Test&invoice_id=$INVOICE_ID&limit=5" | head -c 300; echo

# 6) Reminders: create template, schedule, send, history
TEMPLATE_ID=$(curl -s -X POST http://localhost:8080/api/invoice-reminders/templates \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"Before Due Date","description":"3 days before due","trigger_type":"before_due","trigger_days":3,"subject_template":"Payment Reminder: Invoice {{.InvoiceNumber}} Due Soon","body_template":"Dear {{.ClientName}},\nYour invoice {{.InvoiceNumber}} is due on {{.DueDate}}.\nAmount: {{.Currency}} {{.TotalAmount}}.\n\nRegards,\n{{.CompanyName}}","is_active":true,"is_default":false}' \
  | python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo TEMPLATE_ID=$TEMPLATE_ID

curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/reminders/schedule \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"template_id":'"$TEMPLATE_ID"'}' | head -n 1

curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/reminders/send \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"template_id":'"$TEMPLATE_ID"'}' | head -n 1

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/invoices/$INVOICE_ID/reminders | head -c 400; echo

Postman suites updated (FYI)

  • postman/client_management.postman_collection.json: added list categories/tags; contacts update now uses If-Match.
  • postman/invoice_comments.postman_collection.json: added login/setup; create uses content_type: "text"; added search request.
  • postman/invoice_reminders.postman_collection.json: added login/setup; create/list template; schedule/send/history.