> ## Documentation Index
> Fetch the complete documentation index at: https://mythicframework.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Jobs - Exports

> Job management component API for employment, duty status, and permissions

The Jobs component manages character employment, on-duty status, and job-based permissions. Characters can have **multiple jobs simultaneously**, stored as an array on the character.

## Overview

Access via `Jobs` (server-side only).

<CardGroup cols={2}>
  <Card title="Multiple Jobs" icon="briefcase">
    Characters can hold multiple jobs at once (Jobs array)
  </Card>

  <Card title="Grade System" icon="ranking-star">
    Each job has workplaces and grades with levels
  </Card>

  <Card title="On-Duty Status" icon="toggle-on">
    Clock in/out system per job
  </Card>

  <Card title="Permissions" icon="shield-check">
    Job-based access control via Permissions sub-component
  </Card>
</CardGroup>

<Warning>
  **Server-Side Only:** All job operations must be performed on the server. Never attempt to modify job data from the client.
</Warning>

<Warning>
  **Important:** Characters have a `Jobs` array (plural), NOT a single `Job` field. Each entry in the array is a job object with `Id`, `WorkplaceId`, `GradeId`, and `GradeLevel`.
</Warning>

***

## Job Data Structure

Characters store jobs as an array:

```lua theme={null}
-- Character's Jobs field (accessed via char:GetData('Jobs'))
{
    {
        Id = "police",               -- Job identifier
        WorkplaceId = "lspd",        -- Workplace within the job
        GradeId = "officer",         -- Grade/rank identifier
        GradeLevel = 1               -- Numeric grade level
    },
    {
        Id = "mechanic",
        WorkplaceId = "bennys",
        GradeId = "employee",
        GradeLevel = 0
    }
}
```

***

## Job Management

### Get

Get a job definition by its ID.

```lua theme={null}
Jobs:Get(jobId)
```

<ParamField path="jobId" type="string" required>
  Job identifier (e.g., 'police', 'ems', 'mechanic')
</ParamField>

<ResponseField name="jobDefinition" type="table|nil">
  Job definition data or `nil` if job doesn't exist
</ResponseField>

**Examples:**

```lua theme={null}
-- Get job definition
local jobDef = Jobs:Get('police')

if jobDef then
    print('Job found:', jobDef.Name)
end
```

***

### GiveJob

Assign a job to a character. Characters can have multiple jobs.

```lua theme={null}
Jobs:GiveJob(stateId, jobId, workplaceId, gradeId)
```

<ParamField path="stateId" type="number" required>
  Character SID
</ParamField>

<ParamField path="jobId" type="string" required>
  Job identifier (e.g., 'police', 'ems', 'mechanic')
</ParamField>

<ParamField path="workplaceId" type="string" optional>
  Workplace identifier within the job
</ParamField>

<ParamField path="gradeId" type="string" optional>
  Grade/rank identifier within the workplace
</ParamField>

**Examples:**

```lua theme={null}
-- Give a character the police job
local char = Fetch:Source(source):GetData('Character')
local stateId = char:GetData('SID')

Jobs:GiveJob(stateId, 'police', 'lspd', 'officer')

-- Give a second job (characters can have multiple)
Jobs:GiveJob(stateId, 'mechanic', 'bennys', 'employee')

-- Hire player callback
Callbacks:RegisterServerCallback('jobs:hire', function(source, data, cb)
    local player = Fetch:Source(source)
    if not player then return cb(false) end

    local char = player:GetData('Character')
    if not char then return cb(false) end

    local stateId = char:GetData('SID')
    Jobs:GiveJob(stateId, data.jobId, data.workplaceId, data.gradeId)

    Notification:Success(source, 'You have been hired!')
    cb(true)
end)
```

***

### RemoveJob

Remove a job from a character.

```lua theme={null}
Jobs:RemoveJob(stateId, jobId)
```

<ParamField path="stateId" type="number" required>
  Character SID
</ParamField>

<ParamField path="jobId" type="string" required>
  Job identifier to remove
</ParamField>

**Examples:**

```lua theme={null}
-- Fire a character from their police job
local char = Fetch:Source(source):GetData('Character')
local stateId = char:GetData('SID')

Jobs:RemoveJob(stateId, 'police')

-- Fire player callback
Callbacks:RegisterServerCallback('jobs:fire', function(source, data, cb)
    Jobs:RemoveJob(data.targetSID, data.jobId)

    -- Notify target if online
    local targetPlayer = Fetch:SID(data.targetSID)
    if targetPlayer then
        Notification:Error(targetPlayer:GetData('Source'), 'You have been fired!')
    end

    cb(true)
end)
```

***

## Permissions

The `Jobs.Permissions` sub-component handles job-based access checks.

### HasJob

Check if a player has a specific job.

```lua theme={null}
Jobs.Permissions:HasJob(source, jobId, ...)
```

<ParamField path="source" type="number" required>
  Player server source
</ParamField>

<ParamField path="jobId" type="string" required>
  Job identifier to check. Can pass multiple job IDs as additional arguments.
</ParamField>

<ResponseField name="hasJob" type="boolean">
  `true` if the player has the specified job
</ResponseField>

**Examples:**

```lua theme={null}
-- Check if player is police
if Jobs.Permissions:HasJob(source, 'police') then
    print('Player is police')
end

-- Check if player has any emergency job
if Jobs.Permissions:HasJob(source, 'police', 'ems') then
    print('Player is emergency services')
end

-- Gate access by job
Callbacks:RegisterServerCallback('police:openDoor', function(source, data, cb)
    if not Jobs.Permissions:HasJob(source, 'police') then
        return cb(false, 'Not authorized')
    end

    -- Open the door
    Doors:SetLock(data.doorId, false)
    cb(true)
end)
```

***

### GetJobs

Get all jobs for a player.

```lua theme={null}
Jobs.Permissions:GetJobs(source)
```

<ParamField path="source" type="number" required>
  Player server source
</ParamField>

<ResponseField name="jobs" type="table">
  Array of job objects the player has
</ResponseField>

**Examples:**

```lua theme={null}
-- Get all of a player's jobs
local playerJobs = Jobs.Permissions:GetJobs(source)

for _, job in ipairs(playerJobs) do
    print('Job:', job.Id, 'Grade:', job.GradeId)
end
```

***

## Duty System

The `Jobs.Duty` sub-component manages on-duty/off-duty status.

### On

Clock a player in (set on-duty) for a specific job.

```lua theme={null}
Jobs.Duty:On(source, jobId)
```

<ParamField path="source" type="number" required>
  Player server source
</ParamField>

<ParamField path="jobId" type="string" required>
  Job identifier to clock in for
</ParamField>

**Examples:**

```lua theme={null}
-- Clock in for police duty
Jobs.Duty:On(source, 'police')

-- Clock in with validation
Callbacks:RegisterServerCallback('jobs:clockIn', function(source, data, cb)
    if not Jobs.Permissions:HasJob(source, data.jobId) then
        return cb(false, 'You do not have this job')
    end

    Jobs.Duty:On(source, data.jobId)

    Notification:Success(source, 'You are now on duty')
    cb(true)
end)
```

***

### Off

Clock a player out (set off-duty) for a specific job.

```lua theme={null}
Jobs.Duty:Off(source, jobId)
```

<ParamField path="source" type="number" required>
  Player server source
</ParamField>

<ParamField path="jobId" type="string" required>
  Job identifier to clock out of
</ParamField>

**Examples:**

```lua theme={null}
-- Clock out of police duty
Jobs.Duty:Off(source, 'police')

-- Clock out with cleanup
Callbacks:RegisterServerCallback('jobs:clockOut', function(source, data, cb)
    Jobs.Duty:Off(source, data.jobId)

    -- Remove job equipment
    RemoveJobEquipment(source, data.jobId)

    Notification:Info(source, 'You are now off duty')
    cb(true)
end)
```

***

### GetDutyData

Get duty information for a specific job, including all on-duty players.

```lua theme={null}
Jobs.Duty:GetDutyData(jobId)
```

<ParamField path="jobId" type="string" required>
  Job identifier
</ParamField>

<ResponseField name="dutyData" type="table">
  Duty data including `DutyPlayers` array

  ```lua theme={null}
  {
      DutyPlayers = { source1, source2, ... }
  }
  ```
</ResponseField>

**Examples:**

```lua theme={null}
-- Get on-duty police officers
local dutyData = Jobs.Duty:GetDutyData('police')
local onDutyPolice = dutyData.DutyPlayers

print('On-duty police:', #onDutyPolice)

-- Notify all on-duty police
function NotifyPolice(message)
    local dutyData = Jobs.Duty:GetDutyData('police')

    for _, src in ipairs(dutyData.DutyPlayers) do
        Notification:Info(src, message)
    end
end

-- Check if any police are on duty
function AnyPoliceOnDuty()
    local dutyData = Jobs.Duty:GetDutyData('police')
    return #dutyData.DutyPlayers > 0
end

-- Dispatch system
function DispatchCall(callType, location)
    local dutyData = Jobs.Duty:GetDutyData('police')

    for _, src in ipairs(dutyData.DutyPlayers) do
        TriggerClientEvent('mythic-dispatch:client:NewCall', src, {
            type = callType,
            location = location,
            timestamp = os.time()
        })
    end
end
```

***

## Accessing Character Jobs Directly

You can also access a character's jobs through the DataStore:

```lua theme={null}
-- Get character's jobs array
local player = Fetch:Source(source)
local char = player:GetData('Character')
local jobs = char:GetData('Jobs')  -- Returns array of job objects

-- Check if character has a specific job
for _, job in ipairs(jobs) do
    if job.Id == 'police' then
        print('Character is police')
        print('Workplace:', job.WorkplaceId)
        print('Grade:', job.GradeId, 'Level:', job.GradeLevel)
    end
end
```

<Note>
  While you can read `char:GetData('Jobs')` directly, always use `Jobs:GiveJob()` and `Jobs:RemoveJob()` to modify jobs, and `Jobs.Permissions:HasJob()` for checking job access. Do NOT use `char:SetData('Jobs', ...)` directly.
</Note>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Use Permissions for Access Control" icon="shield-check">
    **Always use `Jobs.Permissions:HasJob()` for access checks:**

    ```lua theme={null}
    -- ✅ Good: Use permissions component
    if Jobs.Permissions:HasJob(source, 'police') then
        -- Allow access
    end

    -- ❌ Bad: Manually checking Jobs array
    local char = Fetch:Source(source):GetData('Character')
    local jobs = char:GetData('Jobs')
    for _, j in ipairs(jobs) do
        if j.Id == 'police' then ... end
    end
    ```
  </Accordion>

  <Accordion title="Clean Up on Clock Out" icon="broom">
    ```lua theme={null}
    function CleanClockOut(source, jobId)
        -- Clock out
        Jobs.Duty:Off(source, jobId)

        -- Remove job items
        RemoveJobItems(source)

        -- End active tasks
        EndActiveTasks(source)

        -- Remove job vehicles
        RemoveJobVehicles(source)
    end
    ```
  </Accordion>

  <Accordion title="Check Multiple Jobs" icon="list-check">
    ```lua theme={null}
    -- Check if player has any emergency service job
    if Jobs.Permissions:HasJob(source, 'police', 'ems', 'fire') then
        -- Player is emergency services
    end
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Jobs - Events" icon="bolt" href="/api/jobs/events">
    Job-related events
  </Card>

  <Card title="Jobs - Configuration" icon="gear" href="/api/jobs/configuration">
    Job definitions and setup
  </Card>

  <Card title="Characters API" icon="user" href="/api/characters/exports">
    Character management
  </Card>

  <Card title="Job System Guide" icon="book" href="/features/jobs/overview">
    Complete job system guide
  </Card>
</CardGroup>

<Tip>
  **Job Integration:** Use `Jobs.Permissions:HasJob(source, jobId)` for access checks and `Jobs.Duty:GetDutyData(jobId).DutyPlayers` to find on-duty players. These are the two most commonly used patterns.
</Tip>
