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 runContract models
Section titled “Contract models”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.
DocTypes
Section titled “DocTypes”- 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.
Client provisioning
Section titled “Client provisioning”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.
In-hours vs after-hours
Section titled “In-hours vs after-hours”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.
PSA features
Section titled “PSA features”- 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.
Automation
Section titled “Automation”- 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
Section titled “The GLPI side”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-UNCONTRACTEDagainst 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.
Idempotency
Section titled “Idempotency”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 quirks this depends on
Section titled “GLPI quirks this depends on”- GLPI 11 derives ProjectTask
effective_durationfromreal_start_date→real_end_date. A directeffective_durationinput is ignored. - Listing ProjectTasks via the API needs the profile to hold
projectREADALL without READMY. With READMY set, GLPI restricts lists to project managers and teams even for super-admins.
Scheduler
Section titled “Scheduler”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.
Install
Section titled “Install”A Frappe app, not a GLPI plugin. From the bench directory:
bench get-app https://github.com/bijstaan/glpi_billing.gitbench --site <your-site> install-app glpi_billingbench --site <your-site> migrateThen fill in GLPI Settings (GLPI URL, API tokens, default company, the business-hours calendar) and press Run Diagnostics.
Requires ERPNext (required_apps = ["erpnext"]).
Development
Section titled “Development”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):
python3 -m unittest glpi_billing.tests.test_ratingLicence
Section titled “Licence”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.