Exports
Public exports registered by nx_employeemanagement.
nx_employeemanagement registers one public client export and three
public server exports. The server exports let your other scripts post a
row to a company's ledger, check a rank permission, and run a business's
payroll. Hiring, firing, invoicing and day-to-day payroll go through the
tablet UI.
Server
Company transactions
A wholesale app, a shop, a fuel station or any other script can add a row to a company's transaction list. The row shows in the tablet's Society Funds ledger and in nx_computer's Employees app under Company funds.
exports.nx_employeemanagement:AddCompanyTransactionserverPost one row to a company's ledger. By default it only records the row, for when your script has already moved the money. Pass move = true to have the money moved too, through the same society account backend the tablet uses. A debit the account can't cover then fails cleanly and no row is written.
Parameters
jobstring— Job name of the company, for example "mechanic". Must be a framework job with grades, or a custom job with a society account. "unemployed" is refused.tx.amountnumber— Positive amount. Rounded to Config.Payroll.MoneyDecimals (whole numbers on ESX by default). Capped by Config.Exports.Transactions.MaxAmount.tx.labelstring— What the row says, up to 120 characters. Line breaks and tabs become spaces. tx.description is accepted as an alias.tx.typestring— purchase, expense, fee, tax or withdraw (money out); sale, income or deposit (money in); refund, transfer or other (set tx.direction). Defaults to other.tx.direction'in'|'out'|nil— Overrides the direction the type implies. Required for refund, transfer and other.tx.referencestring|nil— Your own id for the row, such as an order number. Up to 64 characters from A-Z, 0-9 and - _ . : # /. A second call with the same reference from the same resource returns the first row and moves nothing, so retries are safe.tx.actornumber|string|nil— The player behind it: a server id (resolved to their identifier and character name) or a framework identifier.tx.actorNamestring|nil— Name shown on the row, up to 64 characters. Overrides the name resolved from tx.actor.moveboolean|nil— true moves the money too. Default false: record only.
Returns
true, { id, balance, moved, duplicate, direction, amount } | false, errorCode-- Wholesale app: the company pays for an order from its own account.
-- The money moves and the row is written together.
local ok, res = exports.nx_employeemanagement:AddCompanyTransaction('mechanic', {
amount = 1250,
label = ('Wholesale order #%s'):format(orderId),
type = 'purchase',
reference = ('wholesale-%s'):format(orderId), -- safe to retry
actor = source, -- the employee who placed it
}, true)
if not ok then
-- res is the error code, e.g. 'insufficient_funds'
return TriggerClientEvent('ox_lib:notify', source, {
type = 'error', description = 'The company cannot afford this order.'
})
end
print(('order logged as row %d, company balance now %s'):format(res.id, res.balance))If your script already moved the money itself, leave move out and only
the row is written:
exports.nx_employeemanagement:AddCompanyTransaction('burgershot', {
amount = 86.50,
label = 'Delivery fee: Vespucci supply run',
type = 'fee',
})What each mode does
move = false (default) | move = true | |
|---|---|---|
| Ledger row | written | written |
| Company balance | untouched | credited or debited |
| Not enough money | not checked | false, 'insufficient_funds', no row |
| Account used | none | the society backend in Config.Payroll.Society.Backend (nx_realbanking, esx_addonaccount, qb-banking, qb-management, Renewed-Banking, fd_banking, or the database table) |
The row is written first and the money moves second. If the move fails, the row is deleted again, so a failed debit never leaves a ledger line.
With nx_realbanking as the backend, the label is passed as the bank
transaction's reason and { source = 'employees', resource, category, reference } as its metadata. nx_realbanking keeps a transaction row only
for accounts it owns itself. A society account stored in esx_addonaccount
or qb-management shows the same balance in the bank, and this ledger is
where the rows live.
Rows posted this way do not count toward
Config.Payroll.Limits.WithdrawPerDayNonOwner. That cap is for bosses
taking cash out, and a supplier order is not a cash withdrawal.
Any server resource can call this export unless you list the ones you
trust in Config.Exports.Transactions.AllowedResources. With
move = true it moves company money, so never call it straight from a
client event without your own check that the player is allowed to spend
for that company.
Error codes
| Code | Meaning |
|---|---|
not_allowed_caller | Not called from a server resource, or the resource isn't in AllowedResources |
ledger_disabled | Config.Exports.Transactions.Enabled = false |
ledger_unavailable | The ledger columns are missing (the database migration failed on start; check the server console) |
invalid_args | job or tx has the wrong type, or tx.direction is not in/out |
unknown_job | No such job, or unemployed |
society_unavailable | A custom job with no society account, or the society backend is unreachable |
invalid_amount | Not a positive finite number |
limit_exceeded | Over MaxAmount |
invalid_label | Empty, over 120 characters, or not valid UTF-8 |
invalid_type | tx.type is not one of the listed types |
direction_required | refund, transfer or other without tx.direction |
invalid_reference | Over 64 characters or contains other characters |
rate_limited | Over Config.RateLimits.ledger for this resource and job |
insufficient_funds | move = true and the company can't cover the debit |
db_error | Database failure; nothing was written |
Every posted row also fires the server event
nx_employeemanagement:server:societyChanged with
{ job, id, direction, amount, category, resource, moved }. Rows are
also written to the audit log and to the Discord webhook when one is
configured.
Rank permissions
exports.nx_employeemanagement:HasRankPermissionserverWhether a grade of a job holds a permission key from the Permissions tab. Boss grades always do. A non-boss grade has it only when it was granted there, and never while Config.Permissions.Enabled is false. Bad arguments return false, never nil.
Parameters
jobstring— Job name.gradenumber— Grade level.keystring— A key from Config.Permissions.Keys: 'employees', 'management', 'payroll', 'bossActions' or 'society'.
Returns
boolean-- Only let grades with the society grant pay supplier invoices
local job = player.job
if not exports.nx_employeemanagement:HasRankPermission(job.name, job.grade, 'society') then
return false, 'not_authorized'
endPayroll runs
exports.nx_employeemanagement:RunPayrollserverPay every employee of one business, current or former, for the hours they are owed, right now. It is the same run AutoPay does on payday, with the same society debit, offline queueing and pay notifications. Any server resource can call it, so do your own permission check first (an admin command, a boss check).
Parameters
jobstring— Job name of the business. Must be a job in tablet payroll mode.
Returns
true, summary | false, reason-- Admin command: pay out the mechanics now.
-- restricted = true, so only players with the command.paymechanics ACE can run it.
RegisterCommand('paymechanics', function()
local ok, res = exports.nx_employeemanagement:RunPayroll('mechanic')
if not ok then
return print('payroll not run: ' .. res) -- e.g. 'no_employees'
end
print(('paid %d, queued %d, total %s (%s)'):format(res.paid, res.queued, res.total, res.status))
end, true)On success summary holds:
| Field | Meaning |
|---|---|
runId | Id of the payroll run |
job | The job that was paid |
status | done, partial (some employees could not be paid) or failed |
employees | Employees who had hours owed |
paid | Paid straight into an online player's account |
queued | Paid to an offline player, delivered on their next login |
settled | Owed hours that came to $0 and were closed |
insufficient | Not paid because the society account ran short |
failed | Not paid because of an error |
skipped | Nothing to pay after all |
total | Total amount paid and queued |
| Reason | Meaning |
|---|---|
invalid_job | Not a valid job name, or unemployed |
payroll_disabled | Config.Payroll.Enabled = false |
framework_mode | The job is in 'framework' payroll mode, so the framework pays it |
no_employees | Nobody in this job has hours owed |
error | The run hit an error; details are in the server console |
Employees who could not be paid keep their owed hours, so the next run or payday pays them.
Client
exports.nx_employeemanagement:useJobTabletclientOpen the employee management tablet UI for the local player. Boss access is resolved through Config.IsBossRank against the per-job Config.AllowedRanks table, and granular rank grants are applied on top. Non-boss employees see the self-service dashboard when Config.SelfService.Enabled is true. Bound to the jobtablet inventory item through the ox_inventory client export hook. The export opens the UI in every Config.Tablet.access.mode, including 'zone'. It is a no-op while Config.LBTablet.Enabled is true.
-- Open the tablet from any client script
exports.nx_employeemanagement:useJobTablet()The same entry point is used by:
- The
jobtabletinventory item (viaclient.exportinox_inventory, or viaCreateUseableItemon QBCore). - The
/employeelistcommand and itsConfig.UIKeykeybind. - The server-side
nx_employeemanagement:openTabletevent that the inventory bridge fires when the item is used on QB-style inventories.
LB Tablet mode
While Config.LBTablet.Enabled is true, the app inside LB Tablet is the
only way to open the UI, and this export returns without doing anything.
LB Tablet exposes no export for opening a specific app from Lua, so
there is no equivalent call to forward to. Existing callers get a no-op
rather than an error, so nothing breaks when you switch modes.