> For the complete documentation index, see [llms.txt](https://docs.ak4y.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ak4y.com/scripts/ak4y-bossmenu/editable-files/sv_utils.lua.md).

# sv\_utils.lua

```lua
CORE = exports["ak4y-core"]
DutyPlayers = {}
Framework = "qb"

if GetResourceState("qbx_core") == "started" or Config.framework == "qbx"  then
    Framework = "qbx"
    lib.print.info("Detected Framework: QBOX")
elseif GetResourceState("es_extended") == "started" or Config.framework == "esx" then
    Framework = "esx"
    lib.print.info("Detected Framework: ESX")
elseif GetResourceState("qb-core") == "started" or Config.framework == "qb"  then
    Framework = "qb"
    lib.print.info("Detected Framework: QB")
end

-- For Ox Inventory Stash
-- This thread will register the stash for each job defined in Config.AccesCoords
CreateThread(function()
    if GetResourceState("ox_inventory") == "started" then
        for k,v in pairs(Config.BossMenus) do
            local stash = {
                id = k .. "-ak4y-stash",
                label = k .. "-ak4y-stash",
                slots = v.stash.slots,
                weight = v.stash.weight,
                groups = {[v.job] = 0},
                coords = v.coord,
            }
            exports.ox_inventory:RegisterStash(stash.id, stash.label, stash.slots, stash.weight, false, stash.groups, stash.coords)
        end
    end
end)

CORE:Register('ak4y-bossmenu:DeclineJob', function(source, job, senderid)
    local src = source
    local firstName = CORE:GetFirstName(src)
    local lastName = CORE:GetLastName(src)
    local fullname = firstName .. " " .. lastName
    local message = fullname .. " declined the job offer for " .. job
    TriggerClientEvent('ak4y-bossmenu:Notify', source, message, "error")
end)

CORE:Register('ak4y-bossmenu:AcceptJob', function(source, job, senderid)
    local src = source
    local firstName = CORE:GetFirstName(src)
    local lastName = CORE:GetLastName(src)
    local fullname = firstName .. " " .. lastName
    CORE:SetJob(src, job, 0)
    CreateLog(tonumber(senderid), Config.locales[Config.Locale].log.hired, fullname .. " hired to job.", "hire")
end)

CORE:Register('ak4y-bossmenu:GetInventoryItems', function(source, inventoryname)
    local src = source
    local data = {}
    local items = exports["ak4y-core"]:GetStashItems(inventoryname)
    local mostitem = {}

    for _, v in pairs(items) do
        if v and v.count then
            table.insert(mostitem, v)
        end
    end

    table.sort(mostitem, function(a, b)
        return (a.count or 0) > (b.count or 0)
    end)
    data["items"] = items
    data["mostitems"] = mostitem
    return data
end)

CORE:Register('ak4y-bossmenu:DemotePlayer', function(source, data)
    local src = source
    if CanDoPermission(src, "demote") then
        local citizenId = data.playerdata.identifier
        local isActive = CORE:GetPlayerByIdentifier(citizenId)
        if isActive then
            if Framework == "qbx" or Framework == "qb" then
                CORE:SetJob(isActive.PlayerData.source, data.playerdata.job, data.playerdata.gradelevel - 1)
                return true
            else
                CORE:SetJob(isActive.source, data.playerdata.job, data.playerdata.gradelevel - 1)
                return true
            end
        else
            if Framework == "qbx" or Framework == "qb" then
                local jobData = {
                    name = job,
                    label = job,
                    grade = {
                        level = data.playerdata.gradelevel - 1,
                        name = Config.BossMenus[job].Ranks[data.playerdata.gradelevel - 1].Label or "unknown"
                    }
                }
                local jsonJob = json.encode(jobData)
                local query = "UPDATE players SET job = '" .. jsonJob .. "' WHERE citizenid = '" .. citizenId .. "'"
                CORE:ExecuteSql(query)
            else
                local query = "UPDATE users SET job = '" .. job .. "', job_grade = '" .. data.playerdata.gradelevel - 1 .. "' WHERE identifier = '" .. citizenId .. "'"
                CORE:ExecuteSql(query)
            end
            return true
        end

        return false
    else
        return false
    end
end)

CORE:Register('ak4y-bossmenu:PromotePlayer', function(source, data)
    local src = source
    if CanDoPermission(src, "promote") then
        local citizenId = data.playerdata.identifier
        local isActive = CORE:GetPlayerByIdentifier(citizenId)
        if isActive then
            if Framework == "qbx" or Framework == "qb" then
                CORE:SetJob(isActive.PlayerData.source, data.playerdata.job, data.playerdata.gradelevel + 1)
                return true
            else
                CORE:SetJob(isActive.source, data.playerdata.job, data.playerdata.gradelevel + 1)
                return true
            end
        else
            if Framework == "qbx" or Framework == "qb" then
                local jobData = {
                    name = job,
                    label = job,
                    grade = {
                        level = data.playerdata.gradelevel - 1,
                        name = Config.BossMenus[job].Ranks[data.playerdata.gradelevel + 1].Label or "unknown"
                    }
                }
                local jsonJob = json.encode(jobData)
                local query = "UPDATE players SET job = '" .. jsonJob .. "' WHERE citizenid = '" .. citizenId .. "'"
                CORE:ExecuteSql(query)
            else
                local query = "UPDATE users SET job = '" .. job .. "', job_grade = '" .. data.playerdata.gradelevel + 1 .. "' WHERE identifier = '" .. citizenId .. "'"
                CORE:ExecuteSql(query)
            end
            return true
        end

        return false
    else
        return false
    end
end)

CORE:Register('ak4y-bossmenu:FirePlayer', function(source, data)
    local src = source
    if CanDoPermission(src, "fire") then
        local citizenId = data.playerdata.identifier
        local job = Config.UnemployedJob or "unemployed"
        local grade = Config.UnemployedGrade or 0

        local isActive = CORE:GetPlayerByIdentifier(citizenId)

        if isActive then
            if Framework == "qbx" or Framework == "qb" then
                CORE:SetJob(isActive.PlayerData.source, job, grade)
            else
                CORE:SetJob(isActive.source, job, grade)
                CORE:SetJob(isActive.source, job, grade)
            end
            CreateLog(src, Config.locales[Config.Locale].log.fired, data.playerdata.name .. " fired to job.", "fire")
            return true
        else
            if Framework == "qbx" or Framework == "qb" then
                local jobJson = json.encode({
                    name = job,
                    label = "Unemployed",
                    grade = {
                        name = "unemployed",
                        level = grade
                    },
                    isboss = false
                })
                CORE:ExecuteSql("UPDATE players SET job = '" .. jobJson .. "' WHERE citizenid = '" .. citizenId .. "'")
            else
                CORE:ExecuteSql("UPDATE users SET job = '" .. job .. "', job_grade = " .. grade .. " WHERE identifier = '" .. citizenId .. "'")
            end
            CreateLog(src, Config.locales[Config.Locale].log.fired, data.playerdata.name .. " fired to job.", "fire")
            return true
        end

        return false
    else
        return false
    end
end)

CreateThread(function()
    while Framework == nil do Wait(100) end

    while true do
        Wait((1000 * 60) * 5) -- Update every 5 minutes
        for _, playerId in ipairs(GetPlayers()) do
            local src = tonumber(playerId)
            local identifier = nil
            local onduty = false

            if Framework == "qb" then
                local Player = CORE:GetPlayer(src)
                if Player and Player.PlayerData.job and Player.PlayerData.job.onduty then
                    onduty = true
                    identifier = Player.PlayerData.citizenid
                end

            elseif Framework == "qbx" then
                local Player = CORE:GetPlayer(src)
                if Player and Player.PlayerData.job and Player.PlayerData.job.onduty then
                    onduty = true
                    identifier = Player.PlayerData.citizenid
                end

            elseif Framework == "esx" then
                local xPlayer = CORE:GetPlayer(src)
                if xPlayer and xPlayer.getJob() then
                    local job = xPlayer.getJob()
                    onduty = true
                    identifier = xPlayer.getIdentifier()
                end
            elseif Framework == "custom" then
                --Custom Framework here
            end

            if onduty and identifier then
                DutyPlayers[identifier] = (DutyPlayers[identifier] or 0) + 5
            end
        end
    end
end)

AddEventHandler('playerDropped', function(reason)
    local src = source
    local identifier = nil

    if Framework == "qb" then
        local Player = CORE:GetPlayer(src)
        if Player then
            identifier = Player.PlayerData.citizenid
        end

    elseif Framework == "qbx" then
        local Player = CORE:GetPlayer(src)
        if Player then
            identifier = Player.PlayerData.citizenid
        end

    elseif Framework == "esx" then
        local xPlayer = CORE:GetPlayer(src)
        if xPlayer then
            identifier = xPlayer.getIdentifier()
        end
    else
        --Custom Framework here
    end

    if identifier and DutyPlayers[identifier] then
        local timeToAdd = DutyPlayers[identifier]
        CORE:ExecuteSql("UPDATE ak4y_bossmenu SET time = time + '" .. timeToAdd .. "' WHERE identifier = '" .. identifier .. "'")

        DutyPlayers[identifier] = nil
    end
end)
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.ak4y.com/scripts/ak4y-bossmenu/editable-files/sv_utils.lua.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
