Skip to main content

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 BillingCycle configured
  • A BillingAccount with one or more subscriptions producing rated transactions (or an EDR source wired to mediation)
  • Role administration on your user (or equivalent) — the Job Studio endpoints require it

Step 1 — Open the Studio

  1. Navigate to Administration → Jobs in the sidebar.
  2. 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.

Category colours

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.

Why?

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.

  1. Locate the edge between RatedTransactionsJob and BillingRunJob.

  2. Click the small + button at the mid-point of the edge.

  3. The Insert Script Node modal opens with:

    • A code field (e.g. ArchiveRTsBeforeBilling)
    • A Java source editor pre-populated with a Script stub
  4. 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.)
}
}
  1. Submit. The studio calls POST /api/v1/jobs/graph/insert which:
    • Creates a ScriptInstance row with sourceTypeEnum = JAVA and compiles it on the fly
    • Creates a JobInstance of template ScriptingJob referencing the new ScriptInstance
    • Rewires: RatedTransactionsJob → ArchiveRTsBeforeBilling → BillingRunJob

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.

  1. Select M_JOB.
  2. Click Schedule in the properties panel.
  3. 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: *
  1. Save. The studio:
    • Creates or reuses a TimerEntity (convention: TIMER_M_JOB)
    • Calls PUT /api/v1/jobs/graph with the single-node payload setting timerEntity: { code: "TIMER_M_JOB" }
    • A clock badge appears on the node

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 (TIMER vs GUI vs API vs TRIGGER).
  • A failed run halts the chain (unless processNextJobOnError = true on 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:

JobReadsWrites
MediationJobCDR files, FileFormatEDR (OPEN)
UsageRatingJobEDR (OPEN), Subscription, PricePlanMatrixWalletOperation (OPEN)
RecurringRatingJobRecurringChargeInstance, CalendarWalletOperation (OPEN)
RatedTransactionsJobWalletOperation (OPEN)RatedTransaction (OPEN), WO (TREATED)
BillingRunJobBillingCycle, BillingAccount, RTBillingRun (DRAFT)
InvoicingJobBillingRun, RTInvoice (DRAFT), InvoiceLine, RT (BILLED)
PDFInvoiceGenerationJobInvoice, InvoiceConfigurationPDF attachment
XMLEInvoiceGenerationJobInvoice, EInvoiceConfigurationFactur-X/UBL XML
SendInvoiceJobInvoice + email recipientInvoice (SENT)
PaymentJobInvoice (unpaid), PaymentMethodPayment, 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:

  1. Shared DB entities — the real pipeline. MediationJob writes EDR rows, UsageRatingJob queries them. This is the durable, observable state.
  2. Persistent CFTs — configuration on each JobInstance, edited on the Edit Job page. Good for fixed knobs (batch size, concurrency).
  3. Runtime overrideJobInstanceInfoDto.customFields in POST /api/rest/job/execution populates runTimeCfValues for 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.

MethodPathPurpose
GET/api/v1/jobs/graphFull 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/connectWire source.followingJob = target
POST/api/v1/jobs/graph/insertInsert 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-detectedcyclePath: string[]
  • script-compile-errorcompileErrors: { line, column, message }[]
  • duplicate-codeconflictingCode: string
  • unknown-job-templatejobTemplate: string
  • job-not-foundcode: 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 followingJob field). If you need fan-out, split the work across two chains and schedule them independently.
  • Conditional branches: also not supported. Use processNextJobOnError or 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.