# 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`.   |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://codeforge.gitbook.io/codeforge/sleek-series/sleek-multijob.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
