# Integrations

koja-mdt talks to the rest of your server in three ways:

| Way | Where | Use it for |
|---|---|---|
| **Editable bridges** | `editable/` (not escrowed) | Replace how the MDT jails, fines, impounds, bills, reads framework data or reads the phone number. |
| **Exports** | [server](/new/mdt/api/server-exports) / [client](/new/mdt/api/client-exports) | Let your scripts create calls, citations, warrants, report sentences, read records. |
| **Hooks and events** | [hooks](/new/mdt/api/hooks) / [events](/new/mdt/api/events) | Validate or mirror MDT actions in your own systems. |

:::hint{type="warning" title="Keep your changes when updating"}
Files in `editable/` are yours. Back them up before updating the resource and merge new adapters from the changelog by hand.
:::

## Editable files

| File | Side | Contents |
|---|---|---|
| `editable/server/framework.lua` | server | `Bridge`: citizen, vehicle, license, roster and offline job queries for ESX (`Esx`), QBCore / Qbox (`Qb`) and a `Custom` stub. The active implementation follows `KojaLib.Framework`. |
| `editable/server/integrations.lua` | server | `Integrations`: jail and impound adapters, fines, society payments, medical billing, citizen photos and the integration events. |
| `editable/client/integrations.lua` | client | `ClientIntegrations`: death check, open conditions, phone number, notifications. |

## Jail systems

When an officer processes a suspect (or a judge applies a sentence) the MDT calls `Integrations.Jail(target, months, reason, officerSrc, context)`. It picks an adapter based on `Config.Processing.jailSystem`:

| Value | Behaviour |
|---|---|
| `'auto'` | The first started resource in the adapter list: `koja-police`, `pickle_prisons`, `rcore_prison`, `xt-prison`, `qb-prison`, `esx_jail`. |
| `'<resource>'` | Only that adapter. |
| `'none'` | No game jail; the MDT only records the sentence (custody tab, release timer). |

When no adapter handles the request the MDT fires `koja-mdt:server:jailPlayer` with `target, months, reason, officerSrc` and still records the sentence.

::::tabs
:::tab{title="Add an adapter"}
Add an entry to `jailAdapters` in `editable/server/integrations.lua`. `jail` must return `false` on failure; `release` and `isJailed` are optional (early release from the MDT and automatic release tracking use them).

```lua title="editable/server/integrations.lua"
{
    resource = 'my-prison',
    jail = function(target, months, reason)
        return exports['my-prison']:Jail(target, months, reason) ~= false
    end,
    release = function(target, identifier, reason)
        return exports['my-prison']:Release(identifier) ~= false
    end,
    isJailed = function(target)
        return exports['my-prison']:IsJailed(target) == true
    end,
},
```

Set `Config.Processing.jailSystem = 'my-prison'` or keep `'auto'`.
:::

:::tab{title="Use the event"}
Leave the adapters alone and listen to the fallback event. The MDT already records the sentence; report the end of it.

```lua title="my-prison/server.lua"
AddEventHandler('koja-mdt:server:jailPlayer', function(target, months, reason)
    StartSentence(target, months, reason)
end)

AddEventHandler('koja-mdt:server:releasePlayer', function(target, identifier, reason)
    EndSentence(identifier)
end)

function OnSentenceEnded(src, escaped)
    exports['koja-mdt']:ReportRelease(src, escaped and 'escaped' or 'released', { by = 'My Prison' })
end
```
:::

:::tab{title="Report only"}
Your prison script jails people on its own (for example a judge NPC). Report what happened so the MDT custody tab, criminal record and timers stay correct.

```lua title="my-prison/server.lua"
exports['koja-mdt']:ReportJail(src, months, { reason = reason, facility = 'Bolingbroke', officer = officerSrc })
exports['koja-mdt']:ReportRelease(src, 'released', { by = 'Bolingbroke' })
```
:::
::::

The record shows the adapter in `system`. Every 30 seconds the MDT closes records whose time is up (`Config.Custody.autoRelease`, skipped for systems listed in `Config.Custody.trackedSystems`), and for adapters with `isJailed` it also closes records of online inmates the adapter no longer holds.

## Fines and money

Processing, citations paid in game and impound fees all call `Integrations.Fine(target, amount, reason, officerSrc)`. The default implementation:

- takes the money from `Config.Processing.fineAccount` (default `bank`), falling back to cash when the account is short;
- allows a negative balance when `Config.Processing.allowNegativeBalance = true`;
- pays the amount to `Config.Processing.fineSociety` through `Integrations.PaySociety` (esx_addonaccount, Renewed-Banking, qb-banking or qb-management).

Replace the function body to use your own banking. It must return `true` only when the money was taken.

```lua title="editable/server/integrations.lua"
function Integrations.Fine(target, amount, reason, officerSrc)
    if amount <= 0 then return true end
    local ok = exports['my-bank']:Charge(target, amount, reason)
    if ok then exports['my-bank']:AddToSociety('police', amount) end
    return ok == true
end
```

Medical bills use `Integrations.ChargeMedicalBill(target, amount, reason, medicSrc, bill)` and `Integrations.PayMedicalSociety(amount)` (`Config.Ems.billing.account` and `Config.Ems.billing.society`). When koja-ambulance is running with `billing = 'ambulance'` the bill is sent as an ambulance invoice instead.

If your bank charges citizens itself, mark citations paid with the export:

```lua title="server.lua"
exports['koja-mdt']:PayCitation(citationId, { author = 'Maze Bank' })
```

## Impound and garages

MDT impounds call `Integrations.Impound(plate, data)` with `Config.Custody.impoundSystem`:

| Value | Behaviour |
|---|---|
| `'auto'` | The first available adapter: `koja-police`, then `garage` (sets the stored / state column of `owned_vehicles` or `player_vehicles`). |
| `'<adapter id>'` | Only that adapter. |
| `'none'` / `'mdt'` | No garage change; the MDT only keeps the impound record. |

Every MDT impound also fires `koja-mdt:server:impoundVehicle` and every MDT release `koja-mdt:server:releaseImpound`. To add your garage, add an entry to `impoundAdapters`:

```lua title="editable/server/integrations.lua"
{
    id = 'my-garage',
    resource = 'my-garage',
    impound = function(plate, data)
        return exports['my-garage']:SetImpounded(plate, true, data.fee) ~= false
    end,
    release = function(plate, data)
        return exports['my-garage']:SetImpounded(plate, false) ~= false
    end,
},
```

A garage or tow script that impounds on its own reports it, and checks the MDT before handing a car out:

```lua title="my-garage/server.lua"
exports['koja-mdt']:ReportImpound(plate, { reason = 'Abandoned', fee = 750, hours = 12, lot = 'davis' })
if exports['koja-mdt']:IsImpounded(plate) then return end
```

## Phones

| Need | Use |
|---|---|
| Caller number on 911 calls | `ClientIntegrations.GetPhoneNumber()` in `editable/client/integrations.lua`. Return the player's number from your phone resource. |
| 911 / 311 from a phone app | Client `SendDispatch({ code = '911', message = text })`, or server `CreateDispatch` when your phone already runs server side. |
| Warrant or citation app | Server exports `GetCitizenWarrants`, `HasActiveWarrant`, `GetUnpaidCitations`, `GetOutstandingTotal`, `PayCitation`. |
| MDT inbox badge | `GetUnreadMailCount(src)` and the client `koja-mdt:client:notification` event. |

```lua title="editable/client/integrations.lua"
function ClientIntegrations.GetPhoneNumber()
    return exports['my-phone']:GetNumber()
end
```

## Other dispatch systems

| Goal | How |
|---|---|
| Feed calls from another script into the MDT | `CreateDispatch(data)`, optionally with your own codes from `RegisterDispatchCode`. |
| Mirror MDT calls to another dispatch, a Discord channel or a CAD | Listen to `koja-mdt:dispatch:create`, `koja-mdt:dispatch:close`, `koja-mdt:dispatch:assign` or register after hooks. |
| Filter or reroute calls | A `dispatch:create` before hook can change `departments`, `priority`, `title` or cancel the call. |
| Use another dispatch only | Set `Config.Dispatch.enabled = false`; `CreateDispatch` then returns `nil`. |

```lua title="server.lua"
exports['koja-mdt']:registerHook('dispatch:create', function(call)
    exports['other-cad']:PushCall({ id = call.id, code = call.code, title = call.title, coords = call.coords })
end)
```

koja-police and koja-ambulance are detected automatically (`Config.Police`, `Config.Ems.ambulance`): their dispatch, duty and billing are linked without code.

## Frameworks

ESX, QBCore and Qbox work out of the box. When `KojaLib.Framework` is anything else, the MDT uses the `Custom` table in `editable/server/framework.lua`. Fill it for your framework:

| Function | Returns |
|---|---|
| `SearchCitizens(query, limit)` | `{ identifier, firstname, lastname, dob, gender, phone }[]` |
| `GetCitizen(identifier)` | one citizen or `nil` |
| `GetCitizensByIdentifiers(list)` | `{ [identifier] = 'Full Name' }` |
| `SearchVehicles(query, limit)` / `GetVehicle(plate)` / `GetVehiclesByPlates(plates)` | `{ plate, model, owner, ownerName }` |
| `GetCitizenVehicles(identifier)` | vehicles of the citizen |
| `GetLicenses(identifier)` / `GrantLicense(identifier, type)` / `RevokeLicense(identifier, type)` | license list / `boolean` |
| `GetRoster(jobs)` | `{ identifier, name, job, grade }[]` of every member of the jobs |
| `GetOfflineJob(identifier)` | `{ name, grade, fullname }` or `nil` |

`Bridge.ChargeOffline(identifier, amount, account, reason)` is optional: when present, citations can be paid from the web portal while the citizen is offline.

`Integrations.CitizenImage(identifier)` can return a photo URL for citizens that have no MDT photo (for example from your character creator).

## koja-lib

koja-mdt requires koja-lib and uses it for everything framework specific: player identifiers, jobs and grades (`KojaLib.Server.GetPlayerIdentifier`, `GetJobInfo`, `GetJobs`), money (`getMoney`, `removeMoney`), job changes (`SetJob`, `SetOfflineJob`), notifications (`KojaLib.Server.SendNotify`, `KojaLib.Client.SendNotify`) and Discord webhooks (`LogMessage`). `KojaLib.Framework` (`esx`, `qb`, `custom`) selects the bridge. The same helpers are available in the editable files, so a custom adapter does not need to know the framework:

```lua title="editable/server/integrations.lua"
function Integrations.CitizenImage(identifier)
    local src = KojaLib.Server.GetSourceByIdentifier(identifier)
    return src and exports['my-mugshots']:Get(src) or nil
end
```
