Skip to main content

Approval Workflow

Phase 2

The approval workflow API endpoints are implemented in Phase 2. The status state machine and transitions are already enforced in Phase 1.

After finalization, invoices enter the approval workflow where managers review and approve or decline them.

Approval Flow

Status Transitions

Approve

Transitions: needs_reviewapproved

  • Uploads PDF to Cloudflare R2 (best-effort, before transaction — failure doesn't block approval)
  • Sets approved_at to current timestamp
  • Sets approved_by to the approving user
  • Stores r2_storage_key and r2_public_url if R2 upload succeeds
  • Logs a status_changed event
  • Triggers async PlanFact shipment sync (goroutine, 30s timeout — stores planfact_shipment_id + planfact_deal_id on success, logs planfact_sync_failed event on failure)
  • Sends Telegram notification with R2 PDF link when available (background goroutine)

Decline

Transitions: needs_reviewdeclined

  • Sets declined_at to current timestamp
  • Sets declined_by to the declining user
  • Records decline_reason explaining why
  • Logs a status_changed event with reason in metadata

Send

Transitions: approvedsent

  • Sets sent_at to current timestamp
  • Logs a status_changed event
  • Local (UZS): via Didox.uz (Phase 4)
  • International: via email with PDF attachment (Phase 4)

Accept

Transitions: sentaccepted

  • Logs a status_changed event

Reject

Transitions: sentrejected

  • Sets rejected_at to current timestamp
  • Sets rejected_by to the rejecting user
  • Records reject_reason explaining why (required — enforced by RejectModal in frontend)
  • Logs a status_changed event with reason in metadata

Mark as Paid

Transitions: acceptedpaid

  • Sets paid_at to current timestamp
  • Logs a status_changed event

Regenerate (after decline, reject, or approve)

Transitions: declineddraft, rejecteddraft, approveddraft

  • Re-fetches latest worklog data from ClickHouse
  • Recalculates financials
  • Resets status to draft for re-editing
  • Clears approved_at, approved_by, and html_override fields
  • Preserves edit history from previous version

PlanFact Sync Retry

If the async PlanFact sync fails during approval (e.g., PlanFact API is down), it can be retried manually:

POST /api/v1/invoices/:id/sync-planfact
  • Only works on approved or sent invoices
  • Runs synchronously (returns when sync completes or fails)
  • Frontend shows a "Retry" button in the Integrations card for unsynced invoices
  • Returns 400 if PlanFact service is not configured or invoice is in wrong status

Telegram Notifications (Phase 2)

Optional Telegram bot integration for approval notifications:

On approval, the backend sends a Telegram notification in a background goroutine:

  1. Fetches the invoice PDF from the database
  2. If PDF is available, sends it as a document attachment with a caption
  3. If PDF is unavailable or document send fails, falls back to a text message
  4. Notification failures are logged but do not affect the approval result

Telegram configuration via environment variables (INVOICE_TELEGRAM_BOT_TOKEN, INVOICE_TELEGRAM_CHAT_ID). When both are set, the Notifier is initialized at startup and injected into the invoice service. When either is empty, notifications are silently disabled (nil notifier).

Review Checklist

When reviewing an invoice, managers typically verify:

  1. Line items match work performed — correct issues listed
  2. Hours are reasonable — no unexpectedly high or low values
  3. Overtime is justified (SUP) — hours genuinely exceeded the limit
  4. Rates are correct — multipliers applied appropriately
  5. Total amount is accurate — matches financial calculations
  6. Client details are correct — right company, right period