Job Studio — Configure and Run Your Billing Pipeline
The Job Configuration Studio is a visual canvas that shows every job in your tenant as a node, renders chained jobs left-to-right, and lets you run, edit, schedule, and extend them without leaving the UI.
This tutorial walks you through building the canonical Belgium billing chain from scratch, running it, watching it execute live, inserting a custom Java step, and scheduling nightly runs.
Prerequisites
- A tenant with at least one
BillingCycleconfigured - A
BillingAccountwith one or more subscriptions producing rated transactions (or an EDR source wired to mediation) - Role
administrationon your user (or equivalent) — the Job Studio endpoints require it
Step 1 — Open the Studio
- Navigate to Administration → Jobs in the sidebar.
- Click the Open Studio button in the top action bar.
You land at /administration/jobs/studio. The canvas shows every JobInstance currently in your tenant. If you have existing chains, they appear as left-to-right rows; isolated jobs sit as loose nodes at the bottom.
Nodes are tinted by category so you can read the pipeline at a glance: MEDIATION green, RATING blue, INVOICING purple, PAYMENT amber, DUNNING red, UTILS grey. Hover any node to see its class name and last execution status.
Step 2 — Seed the Full Billing Chain
In the toolbar, click Seed Full Billing Chain. Confirm the prompt.
The studio creates 10 JobInstance rows and wires them as a linear chain:
M_JOB
↓ MediationJob — parse CDR files → EDRs
UsageRatingJob
↓ rate open EDRs → WalletOperations
RecurringRatingJob
↓ rate subscription fees
RatedTransactionsJob
↓ aggregate WOs → RatedTransactions
BillingRunJob
↓ create the BillingRun
InvoicingJob
↓ generate Invoices
PDFInvoiceGenerationJob
↓ render PDF
XMLEInvoiceGenerationJob
↓ Factur-X / UBL XML
SendInvoiceJob
↓ email dispatch
PaymentJob
Each edge carries a small badge:
- ✓ (green) — trigger next job even on errors (
processNextJobOnError = true) - ⚠ (amber) — skip next job if this one produced errors (default; safer)
Step 3 — Tune a Job's Configuration
Click the InvoicingJob node. The right-side properties panel shows:
- Description — editable
- processNextJobOnError / disabled toggles
- Run / Schedule / History action buttons
- A Data flow card documenting what this job reads, writes, and how to configure it
For persistent parameter edits (e.g. selecting a specific billingCycle), click the job's code in the panel header to jump to the full Edit Job page. The studio is for topology and operation — granular parameter editing stays on the existing EditJob screen.
The studio's PUT /api/v1/jobs/graph treats Custom Field values as topology-only (preserved as-is). CF edits go through the legacy /api/rest/job/:code endpoint so the two concerns stay cleanly separated.
Step 4 — Run the Chain
Click the inline Run button on the M_JOB node (or use Run from the properties panel).
A modal opens with a dynamically generated form — one field per Custom Field Template declared by MediationJob (input directory, file format, one-file-per-job, etc.). The form reads the CFT schema from POST /api/rest/v2/generic/all/customFieldTemplate?appliesTo=JobInstance_MediationJob.
Fill in your runtime overrides (or leave blank to use the persisted values on the JobInstance) and click Run.
Watch the canvas:
- M_JOB's badge turns RUNNING (subtle pulse on the node border).
- The inline counter shows
nbItemsProcessed / nbItemsToProcess, updated every 2 seconds. - When M_JOB completes, its badge turns green and UsageRatingJob immediately turns RUNNING — the backend auto-triggered it via
followingJob. - The cascade continues through the chain.
Under the hood the studio polls POST /api/rest/v2/generic/all/jobExecutionResultImpl every 2 s while any tracked job is RUNNING, stops 3 s after the whole chain reaches a terminal state.
Step 5 — Insert a Custom Step
Say you want to archive a file after RatedTransactionsJob but before BillingRunJob — a bespoke Java step with no dedicated Job class.
-
Locate the edge between RatedTransactionsJob and BillingRunJob.
-
Click the small + button at the mid-point of the edge.
-
The Insert Script Node modal opens with:
- A
codefield (e.g.ArchiveRTsBeforeBilling) - A Java source editor pre-populated with a
Scriptstub
- A
-
Edit the source:
import org.meveo.service.script.Script;
import org.meveo.admin.exception.BusinessException;
import java.util.Map;
public class ArchiveRTsBeforeBilling extends Script {
@Override
public void execute(Map<String, Object> params) throws BusinessException {
log.info("Archiving rated transactions snapshot before billing run");
// Add your archival logic here (copy to S3, call external API, etc.)
}
}
- Submit. The studio calls
POST /api/v1/jobs/graph/insertwhich:- Creates a
ScriptInstancerow withsourceTypeEnum = JAVAand compiles it on the fly - Creates a
JobInstanceof templateScriptingJobreferencing the newScriptInstance - Rewires:
RatedTransactionsJob → ArchiveRTsBeforeBilling → BillingRunJob
- Creates a
The new node appears in the chain with a dashed border and code-tag icon marking it as a script job. Compile errors surface inline — fix and retry.
Step 6 — Schedule the Chain
Only the root node needs a schedule — the rest auto-trigger.
- Select M_JOB.
- Click Schedule in the properties panel.
- Use the Cron Builder to pick a cadence — daily at 02:00 for nightly billing is typical:
second: 0
minute: 0
hour: 2
dayOfMonth: *
month: *
dayOfWeek: *
- Save. The studio:
- Creates or reuses a
TimerEntity(convention:TIMER_M_JOB) - Calls
PUT /api/v1/jobs/graphwith the single-node payload settingtimerEntity: { code: "TIMER_M_JOB" } - A clock badge appears on the node
- Creates or reuses a
Clear the schedule by opening the same modal and clicking Clear schedule — the PUT sends timerEntity: {} (empty object = clear, per the Billerang null-vs-empty rule).
Step 7 — Observe and Recover
When a chain runs unattended:
- Visit the studio any time — the last execution status is on every node (green ✓ / red ✗ / amber ⏸).
- Click a node and open History to see the last 50 executions with duration, item counts, and launcher (
TIMERvsGUIvsAPIvsTRIGGER). - A failed run halts the chain (unless
processNextJobOnError = trueon that link). Fix the underlying data issue, then Run the failed node — the chain continues automatically from that point forward.
Data Flow Reference
The studio documents per-job data flow in the properties panel. The canonical map covers:
| Job | Reads | Writes |
|---|---|---|
| MediationJob | CDR files, FileFormat | EDR (OPEN) |
| UsageRatingJob | EDR (OPEN), Subscription, PricePlanMatrix | WalletOperation (OPEN) |
| RecurringRatingJob | RecurringChargeInstance, Calendar | WalletOperation (OPEN) |
| RatedTransactionsJob | WalletOperation (OPEN) | RatedTransaction (OPEN), WO (TREATED) |
| BillingRunJob | BillingCycle, BillingAccount, RT | BillingRun (DRAFT) |
| InvoicingJob | BillingRun, RT | Invoice (DRAFT), InvoiceLine, RT (BILLED) |
| PDFInvoiceGenerationJob | Invoice, InvoiceConfiguration | PDF attachment |
| XMLEInvoiceGenerationJob | Invoice, EInvoiceConfiguration | Factur-X/UBL XML |
| SendInvoiceJob | Invoice + email recipient | Invoice (SENT) |
| PaymentJob | Invoice (unpaid), PaymentMethod | Payment, AccountOperation, Invoice (PAID) |
Use this table to reason about side effects before inserting a custom script at a specific position.
How Values Flow Between Jobs
Three mechanisms coexist:
- Shared DB entities — the real pipeline.
MediationJobwritesEDRrows,UsageRatingJobqueries them. This is the durable, observable state. - Persistent CFTs — configuration on each
JobInstance, edited on the Edit Job page. Good for fixed knobs (batch size, concurrency). - Runtime override —
JobInstanceInfoDto.customFieldsinPOST /api/rest/job/executionpopulatesrunTimeCfValuesfor that one execution. Surfaced by the Run modal's dynamic form.
There is intentionally no output → input mapping layer. Jobs are plain batch stages reading and writing via the database; the studio exposes this clearly rather than adding a second, parallel pipeline vocabulary.
REST API Reference
All endpoints require role administration.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/jobs/graph | Full graph (nodes + edges + embedded timer and last-execution summaries) |
| PUT | /api/v1/jobs/graph?deleteMissing=<bool> | Atomic save of the graph |
| POST | /api/v1/jobs/graph/connect | Wire source.followingJob = target |
| POST | /api/v1/jobs/graph/insert | Insert a node between A and A.followingJob (optionally with inline Java source for ScriptingJob) |
| DELETE | /api/v1/jobs/graph/nodes/{code} | Delete a node; rewire predecessor → successor |
Errors follow RFC 7807 (application/problem+json) with extension fields:
cycle-detected→cyclePath: string[]script-compile-error→compileErrors: { line, column, message }[]duplicate-code→conflictingCode: stringunknown-job-template→jobTemplate: stringjob-not-found→code: string
Null-vs-empty rule on PUT: null or absent = preserve existing; empty value for the field's type ("", [], {}) = clear.
Troubleshooting
The studio loads but shows no nodes
Check that your user has the administration role. The five /api/v1/jobs/graph… endpoints are role-gated. A 403 surfaces as an empty canvas with a toast error.
I clicked Run but the node didn't turn RUNNING
Give it up to 5 seconds — the polling hook has a grace window before assuming the job hasn't started. If still nothing, the backend likely rejected the run (e.g. the job is already running, or a required CFT is missing). Check the notifications drawer for the API response.
Cycle error when I drag a connection
The backend enforces cycle detection up to depth 20. The problem+json body contains the full cyclePath — the alert message shows it, so you can see exactly which nodes formed the cycle.
My custom script doesn't compile
The studio surfaces compileErrors inline in the Insert Script modal. Fix the source and re-submit — the ScriptInstance and JobInstance are only persisted if compilation succeeds (the backend wraps both in a single transaction).
What's Next
- Parallel chains: not supported today (linear only, matches the backend's single
followingJobfield). If you need fan-out, split the work across two chains and schedule them independently. - Conditional branches: also not supported. Use
processNextJobOnErroror a ScriptingJob with explicit if/else routing logic. - Output → input bindings: intentionally absent. Stick to the shared-DB-entities model.
If you need any of these, open an enhancement request in the Billerang backlog.