> For the complete documentation index, see [llms.txt](https://codeforge.gitbook.io/codeforge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codeforge.gitbook.io/codeforge/sleek-series/sleek-multijob.md).

# SLEEK MULTIJOB

Multi-job system with Job Centre UI, whitelist company panels, duty tracking and Discord webhook logging.

**Frameworks:** ESX / QBCore (auto-detected)\
**SQL:** oxmysql · mysql-async · ghmattimysql (auto-detected)\
**Target:** ox\_target · qb-target (auto-detected, optional)

***

### Requirements

| Dependency                                       | Notes                                        |
| ------------------------------------------------ | -------------------------------------------- |
| FiveM server build **5848+**                     | Lua 5.4 support                              |
| **OneSync**                                      | Required                                     |
| **es\_extended** or **qb-core**                  | Framework                                    |
| **oxmysql**, **mysql-async** or **ghmattimysql** | SQL wrapper                                  |
| ox\_target / qb-target                           | Optional — only if `Config.UseTarget = true` |

***

### Installation

1. Place `forge-multijob` in your `resources` folder.
2. Add `ensure forge-multijob` to your `server.cfg` **after** your framework and SQL resource.
3. Start the server. The resource auto-creates all required database tables on first boot — no manual SQL import needed.

> The `SQL/` folder contains reference SQL files for ESX and QBCore if you prefer to run them manually, but auto-migration handles everything.

***

### Configuration

All config files are in `shared/config.lua` (client + server) and `server/config_server.lua` (server only).

#### Runtime Detection

Framework, SQL library and target system are detected automatically at startup. Override manually if needed:

```lua
Config.Framework    = 'esx'       -- 'esx' or 'qbcore'
Config.SQLWrapper   = 'oxmysql'   -- 'oxmysql', 'mysql-async', 'ghmattimysql'
Config.TargetSystem = 'ox_target' -- 'ox_target', 'qb-target', or nil
```

#### Multijob Settings

| Option                   | Default | Description                                                                                                |
| ------------------------ | ------- | ---------------------------------------------------------------------------------------------------------- |
| `Config.MaxJobs`         | `3`     | Total job slots per player (including active job). Set to `1` to disable multijob.                         |
| `Config.Type`            | `1`     | **Type 1:** Slots are empty — admin must assign with `/setjob`. **Type 2:** All slots unlocked from start. |
| `Config.AllowDeleteJobs` | `true`  | Allow players to remove jobs from slots (trash icon in F7 switcher).                                       |

#### Interaction

| Option               | Default           | Description                                                        |
| -------------------- | ----------------- | ------------------------------------------------------------------ |
| `Config.UseTarget`   | `false`           | `true` = 3D target interaction, `false` = floating text + keybind. |
| `Config.DefaultKey`  | `'F7'`            | Keybind to open the job switcher popup.                            |
| `Config.AdminGroups` | `{'admin','god'}` | Permission groups for `/setjob` commands.                          |

#### Debug Mode

```lua
Config.Debug = false
```

Set to `true` to enable verbose console logging (info, debug, success messages). Warnings and errors always print regardless. Recommended `false` for production.

***

### Features

#### Job Centre (NPC Menu)

Players interact with an NPC (or target) to open the Job Centre. Available jobs are shown as cards. Clicking a job places it in an available slot.

* Search bar to filter jobs.
* Whitelist jobs are visually separated from civilian jobs when `Config.WhitelistJobs.Enabled = true`.
* UI theme is configurable via `Config.UI.mainColor` and `Config.UI.secondColor`.

#### Job Switcher (F7)

Press `F7` (configurable) anywhere to open the job switcher popup.

* Shows all job slots with the active job highlighted.
* Click a slot to swap it to the active position.
* Trash icon to remove a job from a slot (if enabled).
* Whitelist / Civilian categories shown if enabled.

#### Whitelist Panel

Adds interactive company profiles for whitelist jobs inside the Job Centre. Enable in config:

```lua
Config.WhitelistPanel = {
    Enabled = true,
    MinGradeManage = 3,    -- min grade to manage profiles
    MaxAnnouncements = 5,
    MaxPositions = 10,
}
```

**For players:**

* View company profiles, announcements and open positions.
* Submit job applications for positions.
* Track application status (pending / accepted / denied).

**For bosses** (grade >= `MinGradeManage` in their active job):

* Edit company profile (description + contact info).
* Create/delete announcements with badge presets (HIRING, URGENT, NEWS, etc.).
* Create/delete positions with grade selector (salary auto-fills from framework).
* Open/close positions.
* Review applications (accept / deny) — applicant gets notified if online.

#### Adding Whitelist Jobs

Add jobs to the whitelist list in config:

```lua
Config.WhitelistJobs = {
    Enabled = true,
    Jobs = {
        ['police']    = true,
        ['ambulance'] = true,
        ['mechanic']  = true,
        -- add more here
    }
}
```

The Whitelist Panel uses the same list. Jobs must exist in your framework's job database.

***

### Duty System

Track on/off duty status and total hours. Toggled from the F7 switcher.

```lua
Config.Duty = {
    Enabled = true,
    TrackedJobs = { 'police', 'ambulance', 'mechanic' },
}
```

Only jobs listed in `TrackedJobs` show the duty toggle.

**Persistence:**

* Sessions are saved to the `forge_duty` table.
* Timer is saved on: duty toggle off, player disconnect, resource stop and server restart.
* No data is lost on crashes or restarts.

#### Off-Duty Prefix (ESX only)

```lua
Config.Duty.UseOffPrefix = true
Config.Duty.OffPrefix = 'off_'
```

When enabled, going off-duty changes the player's active job from `police` to `off_police`. The off-duty job **must exist** in your ESX `jobs` table. QBCore uses its native `SetJobDuty` and ignores this setting.

#### Boss Stats

Bosses can view duty statistics for their department:

| Option                  | Description                             |
| ----------------------- | --------------------------------------- |
| `BossStats.Enabled`     | Master toggle                           |
| `BossStats.Command`     | Chat command (default: `/dutystats`)    |
| `BossStats.MinGrade`    | Minimum grade to access                 |
| `BossStats.Locations`   | Optional coordinate-based access points |
| `BossStats.UseNeonBoss` | Integrate as a tab in neon-boss UI      |

Stats show: employee name, total duty time, last duty date, online/on-duty status. Bosses can reset individual employee times.

***

### Admin Commands

Commands are dynamically created based on `Config.MaxJobs`:

| Command                       | Description               |
| ----------------------------- | ------------------------- |
| `/setjob1 [id] [job] [grade]` | Set player's extra slot 1 |
| `/setjob2 [id] [job] [grade]` | Set player's extra slot 2 |
| `/setjobN ...`                | Up to `MaxJobs - 1`       |

Only players in `Config.AdminGroups` can use these commands.

***

### Discord Webhooks

Server-side webhook system. Configure in `server/config_server.lua`:

```lua
ServerConfig.Webhooks = {
    Enabled = true,
    BotName = 'Forge Multijob',
    URLs = {
        Duty         = 'https://discord.com/api/webhooks/...',
        Management   = 'https://discord.com/api/webhooks/...',
        Applications = 'https://discord.com/api/webhooks/...',
        Jobs         = 'https://discord.com/api/webhooks/...',
    },
}
```

Each category can point to a **different Discord channel**. Leave a URL empty (`''`) to disable that category.

#### Event Categories

| Category         | Events                                                                                    |
| ---------------- | ----------------------------------------------------------------------------------------- |
| **Duty**         | DutyOn, DutyOff                                                                           |
| **Management**   | AnnouncementNew, AnnouncementDel, PositionNew, PositionDel, PositionToggle, ProfileUpdate |
| **Applications** | AppSubmit, AppAccepted, AppDenied                                                         |
| **Jobs**         | JobSelect, JobSwap, JobRemove, JobAdminSet                                                |

Toggle individual events in `ServerConfig.Webhooks.Events`:

```lua
Events = {
    DutyOn = true,
    DutyOff = true,
    JobSelect = false,  -- disable this specific event
    -- ...
}
```

#### Embed Colours

Customise embed colours (decimal format) in `ServerConfig.Webhooks.Colors`.

***

### Locations

Add multiple Job Centre locations in `Config.Locations`. Each location supports:

```lua
{
    position = vector3(x, y, z),
    blip = { enabled = true, id = 369, color = 17, scale = 0.7, name = 'Job Center' },
    npc  = { enabled = true, coords = vector3(x, y, z), heading = 209.0, model = 's_m_m_dockwork_01', range = 1.5 },
    marker = { enabled = false, type = 2, colorR = 222, colorG = 186, colorB = 77, alpha = 255 },
}
```

* **blip**: Map blip.
* **npc**: Ped that triggers the menu (or target zone if `UseTarget` is true).
* **marker**: Ground marker (disabled by default).

***

### Translations

Built-in language packs: `en`, `es`. Set with:

```lua
Config.Locale = 'en'
```

Add new languages by adding a table to `Config.LangPacks`:

```lua
Config.LangPacks['fr'] = {
    ['ui.job_center'] = 'CENTRE D\'EMPLOI',
    -- ...
}
```

***

### UI Theme

```lua
Config.UI = {
    mainColor   = '#00c5ff',   -- primary accent
    secondColor = '#285a69',   -- secondary accent
}
```

Changes apply to buttons, glows, and highlights across the entire UI.

***

### Exports

| Export                                       | Side   | Description                                         |
| -------------------------------------------- | ------ | --------------------------------------------------- |
| `ReloadPlayerJobs(src)`                      | Server | Reload a player's jobs from DB and update their UI. |
| `IsDutyNeonBossEnabled()`                    | Server | Returns `true` if neon-boss integration is active.  |
| `GetDutyStats(src, jobName)`                 | Server | Get duty stats for a job (for external resources).  |
| `ResetEmployeeDutyTime(identifier, jobName)` | Server | Reset an employee's duty time.                      |

***

### Database

All tables are auto-created on first start. No manual SQL needed.

| Table                         | Purpose                                                    |
| ----------------------------- | ---------------------------------------------------------- |
| `users.jobs` / `players.jobs` | Extra job slots (LONGTEXT column added to framework table) |
| `forge_duty`                  | Duty time tracking per player per job                      |
| `forge_wl_profiles`           | Whitelist company profiles                                 |
| `forge_wl_announcements`      | Company announcements                                      |
| `forge_wl_positions`          | Open positions with grade & salary                         |
| `forge_wl_applications`       | Player applications                                        |

***

### Troubleshooting

| Issue                             | Solution                                                                                             |
| --------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `unexpected symbol near '<\239>'` | File was saved with BOM encoding. Re-save config files as **UTF-8 without BOM**.                     |
| Jobs not loading                  | Check console for `[ERROR]` messages. Enable `Config.Debug = true` for verbose logging.              |
| Off-prefix warning                | Create the off-duty jobs in your ESX `jobs` table (e.g. `off_police`) or set `UseOffPrefix = false`. |
| Webhooks not sending              | Verify URLs in `server/config_server.lua`. Check the console for `[WARN] Webhook failed` messages.   |
| Target not working                | Ensure ox\_target or qb-target is started **before** forge-multijob and `Config.UseTarget = true`.   |
