Browse docs
Wholesale Orders
Wholesale orders let an external server script manage civilian deliveries. Each order stores the requested items, the job, and the ordering character's in-game name. Your script handles payment, delivery, and the civilian interaction.
Setup
Use a sky_jobs_base build that includes the wholesale order exports and source/server/wholesale_orders.lua. In sky_jobs_base/config/config.lua, enable external orders:
Config.WholesaleShop = {
externalOrders = true
}
With this setting, submitting the wholesale basket saves a pending order. It does not charge faction funds or add goods to a player's inventory. Setting externalOrders = false restores immediate purchases with faction funds and inventory delivery.
The sky_jobs_wholesale_orders table is created automatically when Config.AutoExecuteQuery = true. If automatic migration is disabled, execute its CREATE TABLE block from sky_jobs_base/import.sql before starting the updated resource.
All calls and events on this page belong in a server script. Start sky_jobs_base before the integration resource, for example by declaring this dependency in your resource manifest:
dependency "sky_jobs_base"
The integration must authorize its own civilian actions and resolve trusted job, grade, item, and storage values before calling these exports.
Order events
Both events are local server events, handled with AddEventHandler. They are emitted after the write lock is released, so an event consumer can immediately call an order export. Failed writes do not emit events.
sky_jobs_base:wholesale:orderCreated(order)
Fires after an external order has been saved successfully. The order argument contains the requested items, the job, and the ordering character's in-game name.
AddEventHandler("sky_jobs_base:wholesale:orderCreated", function(order)
print("Order:", order.id)
print("Job:", order.job)
print("Ordered by:", order.createdBy.name)
for _, item in ipairs(order.items) do
print("Item:", item.name, "Amount:", item.amount)
end
-- order.createdBy.identifier: persistent character identifier
-- order.createdBy.source: player source at the time of ordering
end)
sky_jobs_base:wholesale:orderStatusChanged(order, previousStatus)
Fires after a successful status change. order contains the updated order, and previousStatus is the status before the change. Repeating an already-applied status does not emit another event.
AddEventHandler("sky_jobs_base:wholesale:orderStatusChanged", function(order, previousStatus)
print("Order status:", order.id, previousStatus, order.status)
-- order.civilianName, order.civilianPhone, order.rejectionReason
end)
Order payload
Both events and the order exports use this order shape:
| Field | Type | Meaning |
|---|---|---|
id | string | Generated 32-character order ID. Keep and pass this exact value to the exports. |
stationId | string | Ordering location. This can differ from the destination storage ID. |
job | string | Job that placed the order. |
items | table[] | Requested entries with name, label, amount, price, and optional ammo. |
totalPrice | number | Sum of catalogue prices; it does not confirm a payment. |
status | string | pending, accepted, rejected, or completed. |
createdBy.name | string | Ordering character's in-game name from Sky_Jobs.PlayerCache. |
createdBy.identifier | string | Persistent character identifier. |
createdBy.source | number | Player source when the order was placed. It is historical and may no longer identify an online player. |
civilianName | string | Name saved when a civilian accepts the order; initially empty. |
civilianPhone | string | Phone number saved on acceptance; initially empty. |
rejectionReason | string | Optional reason saved on rejection; initially empty. |
createdAt / updatedAt | number | Unix timestamps in seconds. |
Status exports
All three status exports return true, order on success or false, errorCode on failure. Replace REPLACE_WITH_ORDER_ID in each example with the generated order.id from an event or query.
AcceptWholesaleOrder(orderId, civilianName, civilianPhone)
AcceptWholesaleOrder(orderId, civilianName, civilianPhone) accepts a pending or rejected order. Name and phone are required non-empty strings, with maximum lengths of 120 and 40 bytes after trimming. Control characters are rejected. Pass phone numbers as strings to preserve leading zeros.
Accepting a rejected order updates the civilian contact details and clears the rejection reason. Repeating acceptance with the same contact details succeeds without another write or event. An already accepted order cannot be taken over using different contact details.
local ok, orderOrError = exports["sky_jobs_base"]:AcceptWholesaleOrder(
"REPLACE_WITH_ORDER_ID",
"Max Mustermann",
"01551234567"
)
RejectWholesaleOrder(orderId, reason?)
RejectWholesaleOrder(orderId, reason?) rejects a pending or accepted order. Supply an optional non-empty reason of at most 255 bytes after trimming, or omit it. Control characters are rejected. The last civilian contact details remain available on the order.
A rejected order can be accepted again. Repeating rejection succeeds and retains the original rejection reason.
local ok, orderOrError = exports["sky_jobs_base"]:RejectWholesaleOrder(
"REPLACE_WITH_ORDER_ID",
"The civilian changed their mind"
)
CompleteWholesaleOrder(orderId)
CompleteWholesaleOrder(orderId) marks an accepted order as completed. It updates the status only: payment and delivery remain the external script's responsibility. Repeating completion succeeds without another write or event.
local ok, orderOrError = exports["sky_jobs_base"]:CompleteWholesaleOrder(
"REPLACE_WITH_ORDER_ID"
)
Status transitions
| Current status | Allowed next status |
|---|---|
pending | accepted, rejected |
accepted | rejected, completed |
rejected | accepted |
completed | Completed orders are final. |
Read saved orders
GetWholesaleOrder(orderId)
GetWholesaleOrder(orderId) returns the saved order or nil, errorCode. Use the generated order ID from an event or list result.
local order, errorCode = exports["sky_jobs_base"]:GetWholesaleOrder(
"REPLACE_WITH_ORDER_ID"
)
GetWholesaleOrders(jobName?, status?, limit?, offset?)
GetWholesaleOrders(jobName?, status?, limit?, offset?) returns an array, including an empty array when no orders match, or nil, errorCode on failure.
| Argument | Default | Accepted value |
|---|---|---|
jobName | nil | Exact job name, or nil for all jobs. |
status | nil | pending, accepted, rejected, completed, or nil for all statuses. |
limit | 100 | Whole number from 1 to 500. |
offset | 0 | Whole number from 0 to 2147483647. |
local orders, errorCode = exports["sky_jobs_base"]:GetWholesaleOrders(
"police",
"pending",
100,
0
)
Results are sorted by createdAt descending, then by id descending. Saved orders survive resource and database reconnects. On integration startup, register the event listeners and read all pages of the statuses you need to recover. Deduplicate event and query results by order.id; creation events are not replayed after a restart.
Storage deliveries
Use the actual destination storage ID. The order's stationId identifies the ordering location. Supply a job registered for the destination and the appropriate grade context. The exports check job and station availability and storage capacity; your script remains responsible for authorizing its callers.
Keep a persistent delivery record keyed by order ID in the external script and serialize delivery attempts for that order. Only mark it completed after a confirmed successful deposit. Storage deposits and order status changes do not form a shared transaction; if a deposit succeeds but completion fails, reconcile the saved delivery record before retrying. Repeating a storage call adds the goods again.
Replace REPLACE_WITH_STORAGE_ID, the job, grade, item names, and serial numbers in the examples with your server's values. See Storage exports for the full argument reference.
AddStorageItem(stationId, jobName, grade, name, amount, metadata?)
Adds one item or weapon stack. amount must be a positive whole number, at most 2147483647. Metadata is optional. Returns true when the write succeeds or false when validation or the database write fails.
local ok = exports["sky_jobs_base"]:AddStorageItem(
"REPLACE_WITH_STORAGE_ID",
"police",
4,
"weapon_pistol",
1,
{ serial = "DELIVERY-001" }
)
For a regular item, supply its item name and omit metadata when it is not needed.
AddStorageItems(stationId, jobName, grade, items)
Adds a batch of 1–200 entries with name, a positive whole-number amount (or quantity, at most 2147483647), and optional metadata. Invalid entries reject the entire batch before writing. The batch uses one SQL statement and returns true on success or false on failure.
local ok = exports["sky_jobs_base"]:AddStorageItems(
"REPLACE_WITH_STORAGE_ID",
"police",
4,
{
{ name = "bandage", amount = 10 },
{
name = "weapon_pistol",
amount = 1,
metadata = { serial = "DELIVERY-002" }
}
}
)
For individual weapon serial numbers, use separate entries with amount = 1 and a unique metadata.serial for each weapon.
GetStorageItemCount(stationId, jobName, grade, name)
Returns the total available amount of the named item, adding together matching stacks regardless of metadata.
local count = exports["sky_jobs_base"]:GetStorageItemCount(
"REPLACE_WITH_STORAGE_ID",
"police",
4,
"bandage"
)
RemoveStorageItem(stationId, jobName, grade, name, amount)
Removes the requested amount from storage. Returns true on success or false if removal fails.
local removed = exports["sky_jobs_base"]:RemoveStorageItem(
"REPLACE_WITH_STORAGE_ID",
"police",
4,
"bandage",
5
)
Order error codes
| Code | Meaning |
|---|---|
invalid_order_id | The ID is missing or invalid. Use the generated order ID. |
order_not_found | No saved order matches the ID. |
invalid_civilian_name | The acceptance name is missing or invalid. |
invalid_civilian_phone | The acceptance phone number is missing or invalid. |
invalid_rejection_reason | A supplied rejection reason is invalid. |
already_accepted | Another set of civilian contact details already owns the accepted order. |
invalid_status_transition | The requested status change is not allowed. |
order_changed | The stored status changed before the update; reread the order. |
invalid_filters | A job, status, limit, or offset filter is invalid. |
database_error | The order could not be read or written. |
Order status exports return errors as false, errorCode; read exports return nil, errorCode. Storage exports return booleans instead of these order error codes.