Skip to content

GLPI Billing

ERPNext app that turns work recorded in GLPI (ticket tasks, ticket costs, managed CIs) into ERPNext Sales Invoices, driven by per-customer MSP contract rules.

Work completed (GLPI ticket tasks / costs / project tasks / CI & user counts)
-> Time & expense engine (Billing Run: Fetch from GLPI)
-> Contract rule match (Fixed Fee | Block Hours | Time & Materials |
Per Device | Per User | Project Hourly |
Project Fixed Price)
-> Automated billing run (monthly scheduler, or a manual Billing Run)
-> One draft Sales Invoice per customer per run

Clients hold any mix of contracts — retainer plus per-user licensing plus project work, for instance.

  • Labor routing. Ticket labor bills under the client’s labor-bearing contract with the lowest Labor Routing Priority: Block Hours, T&M, or a Fixed Fee / Per Device / Per User contract with Covers Ticket Labor ticked. Unticking that on a Fixed Fee makes it a pure retainer and labor falls through to the next contract.
  • Per Device / Per User. Active GLPI CIs or active user accounts counted per type × unit price each period.
  • Licensing. Resold third-party subscriptions. Each line references a real ERPNext Item, so each product keeps its own accounting treatment, with a client-specific unit price. Quantity is either a fixed seat count or synced each run from GLPI Software Licenses: seat totals (number) summed over all non-deleted SoftwareLicense records whose parent Software name matches the line’s GLPI Software Name (blank = the ERPNext item name). Installation counts are not used. Unlimited licences (number = -1) contribute 0 — bill those with a fixed quantity.
  • Project Hourly. Completed GLPI project tasks bill effective duration × contract rate, scoped to one GLPI project ID or 0 for all the client’s projects. Completion-triggered, so late finishes catch up next run.
  • Project Fixed Price. Bills once when the GLPI project reaches 100%.

One invoice per customer per run, combining all contract lines.

  • GLPI Settings (single) — GLPI URL and API tokens, default company, and the business-hours calendar (working days plus workday window) separating in-hours from after-hours work.
  • MSP Contract — maps a GLPI entity to an ERPNext Customer and one billing model with its parameters: monthly fee, block allotment and overage rate, rate tiers, per-device unit prices, expense pass-through, after-hours pricing.
  • Billing Run — one billing period. Fetch from GLPI pulls unbilled time, expenses and device counts and classifies each entry against contract rules. Create Invoices produces one draft Sales Invoice per contract and registers every consumed GLPI task or cost in GLPI Billed Task.

ERPNext is the master. A customer becomes a GLPI client either by ticking Is GLPI Client on the Customer (saving finds or creates a GLPI entity named after it and stores the ID), or by creating an MSP Contract with GLPI Entity ID left at 0, which provisions the entity and flags the customer if needed.

Onboarding is therefore: create Customer, create MSP Contract.

Every GLPI task is stamped In-Hours or After-Hours from its timestamp against the GLPI Settings calendar (default Mon–Fri 08:00–18:00).

Model After-hours behaviour
Fixed Fee / Per Device Covered, unless After-Hours Hourly Rate is set, then flat at that rate (item MSP-AFTER-HOURS)
Block Hours Draws the prepaid balance at the After-Hours Multiplier (1.5× means one real hour consumes 1.5 block hours); overage bills at overage rate × multiplier
Time & Materials Advance Tiers (default): bills at tier rate × multiplier and advances the tier position. Separate Rate: flat at the After-Hours Hourly Rate, or first tier × multiplier if unset, outside the tier ladder

Entries are split at tier and balance boundaries so each run row carries one pure rate, and invoice lines are grouped by (item, rate, time class), so customers see separate in-hours and after-hours lines.

  • Contract terms. Start and end dates; contracts outside their term neither bill nor route labor. Partial-period overlap prorates recurring charges by active days when Prorate Partial Periods is on.
  • Approval workflow. With Require Approval Before Invoicing, runs go Draft → Fetched → Approved → Invoiced. Between fetch and approval you can write off individual time entries (excluded from billing, still registered so they never re-fetch) and add adjustments (± per contract, on MSP-ADJUSTMENT; negative needs Allow Negative Rates in Selling Settings).
  • Invoicing. Per-contract tax template and payment terms days; optional auto-submit and auto-email of the PDF.
  • Costs and margins. Technician Cost Rate stamps an internal cost on every time entry. Reports: Client Profitability, Block Hours Burn-Down, Unbilled Work (including unmatched labor).
  • Calendar. Holiday List support (holiday work counts as after-hours) and a per-contract client timezone offset applied before classification.
  • Sync. Customer renames propagate to the GLPI entity; disabling a customer deactivates its contracts; Contacts linked to GLPI clients are provisioned as GLPI users.
  • Quote to contract. Create → MSP Contract button on Quotation.
  • SLA push. Response and resolution hours on a contract create an SLM and SLAs in GLPI.
  • Scheduler hardening. The monthly cron notifies System Managers on success, review-needed or failure. Invoicing isolates per-customer failures: rollback to savepoint, error recorded on the run, other customers unaffected.
  • Contract watchdog (Mondays 06:00) auto-renews expired contracts with Auto Renew, extending the term and applying Renewal Price Uplift % to every rate, and raises alerts for lapsed contracts and ones ending within Renewal Notice (days).
  • MSP Plans — templates holding a full pricing, renewal and SLA configuration. Create → Contract from this Plan, or one-call onboarding via glpi_billing.api.onboard_client.
  • Block top-ups — Sell Block Top-Up on Block Hours contracts: submitted invoice (MSP-BLOCK-TOPUP) plus an immediate balance credit.
  • Count drift alerts — every unit and licence quantity is compared with the last invoiced run; deltas ≥ Count Drift Alert % are flagged for review.
  • Billed-status writeback — invoiced tickets get a private GLPI followup naming the invoice and run.
  • Diagnostics — Run Diagnostics on GLPI Settings checks the API session, project-task visibility, service items, scheduler, holiday calendar and the negative-rate setting.

The GLPI-side integration is the separate glpientitle plugin: ticket billing sidebar, entitlement display, lapsed-contract banners and uncontracted-work enforcement. It reads this app’s glpi_billing.entitle_api.entitlement endpoint.

No monetary amounts are ever shown in GLPI. The entitlement API returns money-free summaries; currency detail stays in ERPNext.

Uncontracted-work policy (a GLPI Settings default with a per-Customer override) is enforced by that plugin through pre_item_add hooks on followups and tasks:

  • Bill at Uncontracted Rate — responses allowed. The first work log auto-posts a public reply warning that work is outside contract and billed at the Uncontracted Hourly Rate; runs classify the labor Uncontracted and bill it on MSP-UNCONTRACTED against the customer’s invoice with no contract. The logic is symmetric: when coverage is restored and work is logged again, a public correction posts automatically, and a later lapse gets a fresh notice.
  • Require Customer Approval — technician responses are blocked until the ticket is approved through GLPI’s native validation. With a Contact flagged Is MSP Approver, the request is auto-created and targeted at them.
  • Block Responses — followups and tasks are rejected with an explanatory error.

That plugin fails open if ERPNext is unreachable, so enforcement never breaks the service desk.

Every GLPI ticket task or cost consumed by an invoice run gets a GLPI Billed Task row (Task-<id> / Cost-<id>). Fetch skips anything already registered, so overlapping or re-run periods never double-bill. Unmatched entries (no contract for the entity) appear in the run but are not registered, so they are picked up once a contract exists.

  • GLPI 11 derives ProjectTask effective_duration from real_start_date → real_end_date. A direct effective_duration input is ignored.
  • Listing ProjectTasks via the API needs the profile to hold project READALL without READMY. With READMY set, GLPI restricts lists to project managers and teams even for super-admins.

0 2 1 * * — on the 1st of each month, creates and executes a Billing Run for the previous calendar month, skipped if a run for that period already exists.

A Frappe app, not a GLPI plugin. From the bench directory:

Terminal window
bench get-app https://github.com/bijstaan/glpi_billing.git
bench --site <your-site> install-app glpi_billing
bench --site <your-site> migrate

Then fill in GLPI Settings (GLPI URL, API tokens, default company, the business-hours calendar) and press Run Diagnostics.

Requires ERPNext (required_apps = ["erpnext"]).

The compose file bind-mounts glpi_billing/ into every Python service, so code changes need only docker compose -p erpnext restart backend scheduler queue-short queue-long. Rebuild the image and run bench migrate when DocType, report or workspace JSONs change.

Rating maths lives in glpi_billing/rating.py (pure, no frappe):

Terminal window
python3 -m unittest glpi_billing.tests.test_rating

GPL-3.0-or-later, the same licence as ERPNext. This is a Frappe app requiring ERPNext (required_apps = ["erpnext"]) and built on its doctypes, so it is a derivative work. See LICENSE.