I feel like with the amount of time I spent fixing this, this would be the lowest I'd be willing to go unfortunately. The thread will probably cost more to be honest, this will be the lowest it'll be available from me.too much :'( can you lower the price please ? and do you have cl and sv employee ?
Feel free to wait until others have confirmed its authenticity before spending yours!
cl-employees & sv-employees are both available and work without errors, however they are obfuscated.
Here are the cleaned .lua for those if you'd like to replace them:
sv-employees.lua
Code:
-- Employee/role helpers & dealership employee management (server)
---@param src number -- player source
---@param dealershipId string -- dealership name/id
---@param roleFilter string|table|nil -- exact role or one-of list to require (optional)
---@param allowAdmin boolean|nil -- if true, server admins pass automatically
---@return string|boolean -- "server_admin" | "owner" | employee role string | false
local function IsEmployee(src, dealershipId, roleFilter, allowAdmin)
-- Admin bypass
if allowAdmin then
if Framework.Server.IsAdmin(src) then
DebugPrint(src .. " returned as server admin")
return "server_admin"
end
end
-- Identify player
local identifier = Framework.Server.GetPlayerIdentifier(src)
-- Owner check
local ownerRow = MySQL.single.await(
"SELECT name FROM dealership_data WHERE name = ? AND owner_id = ?",
{ dealershipId, identifier }
)
if ownerRow then
return "owner"
end
-- Employee row (if any)
local employeeRow = MySQL.single.await(
"SELECT * FROM dealership_employees WHERE identifier = ? AND dealership = ?",
{ identifier, dealershipId }
)
if not employeeRow then
DebugPrint(identifier .. " is not an employee at " .. dealershipId, "debug")
return false
end
-- Optional role filtering
if roleFilter then
local t = type(roleFilter)
if t == "string" then
if roleFilter ~= employeeRow.role then
return false
end
elseif t == "table" then
if not IsItemInList(roleFilter, employeeRow.role) then
return false
end
end
end
DebugPrint(identifier .. " is an employee at " .. dealershipId .. " with role " .. employeeRow.role, "debug")
return employeeRow.role
end
IsEmployee = IsEmployee
-- Callback: ensure caller is an employee at their nearest dealership (within distance)
lib.callback.register("jg-dealerships:server:employee-nearest-dealership", function(src)
local nearestId, distance = GetCurrentDealershipLocation(GetEntityCoords(GetPlayerPed(src)))
local cfg = Config.DealershipLocations[nearestId]
local maxDistance = cfg.directSaleDistance or 50
if distance > maxDistance then
Framework.Server.Notify(src, Locale.dealershipTooFarAway, "error")
return { error = true }
end
local roleOrFalse = IsEmployee(src, nearestId)
if not roleOrFalse then
Framework.Server.Notify(src, Locale.employeePermissionsError, "error")
return { error = true }
end
return nearestId
end)
-- Callback: list all employees of a dealership (manager/admin only)
lib.callback.register("jg-dealerships:server:get-employees", function(src, dealershipId)
local ok = IsEmployee(src, dealershipId, "manager", true)
if not ok then
Framework.Server.Notify(src, Locale.employeePermissionsError, "error")
return { error = true }
end
local rows = MySQL.query.await(
"SELECT * FROM dealership_employees WHERE dealership = ? ORDER BY joined DESC",
{ dealershipId }
)
return rows
end)
-- Event: ask a target player to confirm being hired
RegisterNetEvent("jg-dealerships:server:request-hire-employee", function(payload)
local src = source
payload.requesterId = src
local ok = IsEmployee(src, payload.dealershipId, "manager", true)
if not ok then
Framework.Server.Notify(src, Locale.employeePermissionsError, "error")
return
end
TriggerClientEvent("jg-dealerships:client:show-confirm-employment", payload.playerId, payload)
end)
-- Event: target player rejected the offer
RegisterNetEvent("jg-dealerships:server:employee-hire-rejected", function(targetSrc)
Framework.Server.Notify(targetSrc, Locale.employeeRejectedMsg, "error")
end)
-- Event: finalize hire (insert row, optionally set job, notify + webhook)
RegisterNetEvent("jg-dealerships:server:hire-employee", function(data)
local src = source
local locCfg = Config.DealershipLocations[data.dealershipId]
local newIdentifier = Framework.Server.GetPlayerIdentifier(data.playerId)
DebugPrint(("Hiring employee %s(%s) at %s with role %s"):format(
newIdentifier, data.playerId, data.dealershipId, data.role
), "debug")
MySQL.insert.await(
"INSERT INTO dealership_employees (identifier, dealership, role) VALUES (?, ?, ?)",
{ newIdentifier, data.dealershipId, data.role }
)
-- Optional job assignment from config
if locCfg.job then
DebugPrint(("Setting job for %s(%s) to %s with role %s"):format(
newIdentifier, data.playerId, locCfg.job, data.role
), "debug")
Framework.Server.PlayerSetJob(data.playerId, locCfg.job, data.role)
end
-- Webhook/notify
local pInfo = Framework.Server.GetPlayerInfo(data.playerId)
SendWebhook(
src,
Webhooks.Dealership,
"Dealership: Employee Hired",
"success",
{
{ key = "Dealership", value = data.dealershipId },
{ key = "Employee", value = (pInfo and pInfo.name) or newIdentifier },
{ key = "Role", value = data.role },
}
)
Framework.Server.Notify(data.requesterId, Locale.employeeHiredMsg, "success")
TriggerClientEvent("jg-dealerships:client:update-blips-text-uis", src)
end)
-- Event: fire an employee (manager/admin only), set unemployed, notify + webhook
RegisterNetEvent("jg-dealerships:server:fire-employee", function(identifier, dealershipId)
local src = source
local ok = IsEmployee(src, dealershipId, "manager", true)
if not ok then
Framework.Server.Notify(src, Locale.employeePermissionsError, "error")
return
end
MySQL.insert.await(
"DELETE FROM dealership_employees WHERE identifier = ? AND dealership = ?",
{ identifier, dealershipId }
)
local onlineId = Framework.Server.GetPlayerFromIdentifier(identifier)
if onlineId then
Framework.Server.PlayerSetJob(onlineId, "unemployed", 0)
Framework.Server.Notify(
onlineId,
(string.gsub(Locale.firedNotification, "%%{value}", dealershipId)),
"error"
)
TriggerClientEvent("jg-dealerships:client:update-blips-text-uis", onlineId)
else
Framework.Server.PlayerSetJobOffline(identifier, "unemployed", 0)
end
local pInfo = Framework.Server.GetPlayerInfoFromIdentifier(identifier)
SendWebhook(
src,
Webhooks.Dealership,
"Dealership: Employee Fired",
"danger",
{
{ key = "Dealership", value = dealershipId },
{ key = "Employee", value = (pInfo and pInfo.name) or identifier },
}
)
end)
-- Event: update employee role (manager/admin only), update job grade, webhook
RegisterNetEvent("jg-dealerships:server:update-employee-role", function(identifier, dealershipId, newRole)
local src = source
local ok = IsEmployee(src, dealershipId, "manager", true)
if not ok then
Framework.Server.Notify(src, Locale.employeePermissionsError, "error")
return
end
local locCfg = Config.DealershipLocations[dealershipId]
MySQL.insert.await(
"UPDATE dealership_employees SET role = ? WHERE identifier = ? AND dealership = ?",
{ newRole, identifier, dealershipId }
)
local onlineId = Framework.Server.GetPlayerFromIdentifier(identifier)
if onlineId then
if locCfg.job then
Framework.Server.PlayerSetJob(onlineId, locCfg.job, newRole)
TriggerClientEvent("jg-dealerships:client:update-blips-text-uis", onlineId)
end
else
Framework.Server.PlayerSetJobOffline(identifier, locCfg.job, newRole)
end
local pInfo = Framework.Server.GetPlayerInfoFromIdentifier(identifier)
SendWebhook(
src,
Webhooks.Dealership,
"Dealership: Employee Updated",
nil,
{
{ key = "Dealership", value = dealershipId },
{ key = "Employee", value = (pInfo and pInfo.name) or identifier },
{ key = "New role", value = newRole },
}
)
end)
Let me know if you need anything else!