PayMe Guide
PM GuideProjects

Billing

Fixed-price vs time-and-materials billing, billing status tracking, and how amounts flow from FiC.

Billing

Billing types

Fixed price (fixed)

The client pays an agreed amount regardless of hours used. This is the default.

  • Budget health compares burn % vs completion % — burning fast with low completion triggers alerts.
  • quotedAmountNet is locked at project creation.
  • Overspend directly erodes margin.

Time and materials (time_and_materials)

The client is invoiced for actual hours logged at an agreed rate.

  • Budget health is not alarmed on fixed-style overruns — the model is different.
  • trackedCost (sum of member.pricePerHour × logged hours) is the billable amount.
  • invoicedAmountNet tracks what has actually been invoiced.

Billing status

StatusMeaning
executingWork in progress
to_invoiceWork complete, invoice not yet issued
invoicedInvoice issued in FiC

Transition executing → to_invoice happens when you mark a project as completed. Update to invoiced manually once the invoice is created in FiC.

FiC amounts

When a project is created from a FiC quote, PayMe captures:

  • quotedAmountNet — net amount from the quote line item
  • sourceQuoteId, sourceQuoteItemId — links back to FiC for cross-reference
  • ficEntityId — the FiC client entity

invoicedAmountNet is updated when you manually record invoiced amounts, or via FiC invoice sync.

Quote drafts

Creating a draft saves a quote in FiC. Email delivery is a separate action.

Review the client, period, quantities and net unit prices before creating the draft. The company default_hourly_rate and member pricePerHour inform the wizard pricing preview. Changing the billing period updates the preview. The draft endpoint uses the approved line items exactly as submitted.

A linked FiC client is required. PayMe reads that client's billing details from FiC. Tax uses the client's configured default VAT, or the company's quote item default when the client has none. Missing tax configuration stops creation.

Draft API

POST /api/pm/quotes/draft requires a PM or admin session.

type QuoteDraftRequest = {
  description: string;
  ficEntityId: number;
  clientName?: string;
  items: {
    name: string;
    quantity: number;
    netPrice: number;
    description?: string;
    periodLabel?: string;
  }[];
  notes?: string;
  periodLabel?: string;
  projectId?: string;
};

type QuoteDraftResult = {
  ok: true;
  quoteId: number;
  source: "fic";
  totalNet: number;
  warning?: string;
};
  • ficEntityId is a positive integer. FiC's current client name is authoritative; clientName is optional preview context.
  • description becomes both the internal and visible quote subject. It accepts 1 to 2,000 characters after trimming.
  • items accepts 1 to 200 lines. Names accept 1 to 500 characters, quantities must be positive finite numbers, and net unit prices must be finite and nonnegative.
  • Optional line descriptions accept up to 2,000 characters. Period labels accept 1 to 200 characters and are appended to the corresponding line description or document notes. Notes accept up to 10,000 characters.
  • projectId, when provided, must be a UUID. Unknown fields are rejected.
  • totalNet is the sum of quantity × netPrice, before tax. All prices are net EUR; FiC receives currency: { id: "EUR" } and use_gross_prices: false.

Success returns HTTP 200 with a positive quoteId. Invalid input returns HTTP 400. Configuration and FiC errors use the standard { error, status, code?, detail? } response; there is no local-storage substitute. The FiC connection needs entity.clients:r for client lookup and issued_documents.quotes:a for creation.

If project document linkage fails after FiC has created the quote, the response still contains its ID and a warning. The server logs fic_quote_project_link_failed with the exact project/document identity and repair operation. Repair that link without creating another quote. If FiC returns no valid ID, creation reports failure; inspect FiC before retrying because the document may already exist.

Net cost

netCost = sum(member.pricePerHour × logged hours across all members)

This is the internal cost of the project. Compare against quotedAmountNet to see margin. Available in the Client Profitability report.

On this page