Troubleshooting
Known gotchas and how to work around them.
The creator command does nothing
Config.CreatorCommand silently no-ops for non-admins and for console (source == 0) — there is no feedback message. If the command seems to "not exist", check Framework.GetIsFrameworkAdmin(source) for that player rather than assuming a config problem.
Shops/blips don't appear on join
Shop peds and blips only spawn after a 2-second delay on resource start, and require the legacy-shops:getShopList server callback to already be registered (which needs oxmysql/db readiness). If it's not ready yet, no peds/blips appear until the next legacy-shops:syncShops broadcast.
Blips imported from SQL never show
legacy_shops.blip_enabled comes back from oxmysql as a real boolean, not 1/0. The migration code checks for 1, true, or '1' explicitly — a naive == 1 comparison makes every migrated shop's blip never appear.
Job-locked shops or licensed items always refuse
Player job/grade comes from Framework.GetPlayerJobData(src), which returns jobName/gradeRank — not name/grade. Reading the wrong field names returns nil for everyone, and access checks fail closed (shop won't open, or item purchase is refused). Licences are read from Framework.GetPlayerMetadata(src, 'licences') (note the British spelling) — any other key/spelling makes licence checks always fail.
Alternate-currency purchases always say "insufficient funds"
Currencies like black_money are inventory items, checked via Inventory.GetItemsByName, not Framework accounts. If your legacy-lib bridge doesn't implement that item-balance lookup correctly, balances silently read as 0 and every alternate-currency purchase fails.
A preset edit didn't update a shop
A shop only follows preset edits if it was placed from that preset (shop.preset_id set at creation). Shops created before that link existed are matched to a preset by exact name equality at boot (backfillPresetLinks()) — if the names don't match exactly, the link is never made and preset edits do nothing for that shop.
Also note: editing a preset overwrites the item catalogue of every linked shop (only stock counts are preserved, matched by item_name) — any price/item edits made directly on those shops are lost.
Shop's job lock can't be changed from the UI
There is no groups/job-lock field in the creator form. groups is set only from the linked preset at creation time, or from data.groups when there's no preset. legacy-shops:updateShop never touches groups or preset_id — the only UI path to change a shop's job lock after creation is through its linked preset.
No default preset templates exist
server/presets.lua must load before the boot sequence runs seedDefaultPresets() and must define a global ShopPresets table. If it fails to load or is missing, a warning is printed but boot continues — you simply get no default templates.
Deleting a shop/category also deleted more than expected
legacy_shops_categories and legacy_shops_items use ON DELETE CASCADE. Deleting a shop or category is destructive with no soft-delete — all dependent categories/items are silently wiped. legacy_shops_history has no such foreign key, so historical purchase records can reference a shop_id that no longer exists.
Presets and coords are JSON, not relational/vec3
legacy_shops.coords is a JSON blob, not separate x/y/z columns — external tooling querying the table directly must parse JSON. legacy_shops_presets.categories/items are TEXT JSON blobs with no foreign-key relation to the live shop tables, so they can drift out of sync with actual shops built from them. Similarly, ShopPresetLocations in server/presets.lua uses plain {x,y,z} tables, not vec3() — don't refactor this without also updating how server.lua reads it.
Stale creator/store data after a live config change
OpenStore/OpenCreator cache legacy-shops:getInitData's result client-side and never invalidate it. If locale, theme, admin flag, or preset lists change at runtime, connected clients keep the stale copy until they reconnect or the resource restarts.
legacy-shops:stockUpdated doesn't refresh an open store
The client-side handler for this event is currently an empty stub. Don't rely on it to live-refresh stock/price in an already-open store UI.
