> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shdevelopment.org/llms.txt
> Use this file to discover all available pages before exploring further.

# sh-society: Job Society & Business Management for RedM

> Install, configure, and use sh-society for RedM society ledgers, payroll, employees, billing, stores, storage, blips, advertising, job center hiring, crafting, and Discord logging.

`sh-society` is a RedM business and job society management resource for VORP and RSG servers. It gives each configured job a society panel for employees, ranks, permissions, payroll, ledger money, invoices, storage, storefronts, advertisements, map blips, themes, and optional Discord webhook settings. Staff can manage all societies from an admin panel, while players can use public job center hiring and bill payment windows.

## Requirements

| Requirement                           | Notes                                                                                                                                                            |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| RedM artifact with `cerulean` support | The resource uses `fx_version 'cerulean'`, `game 'rdr3'`, and Lua 5.4.                                                                                           |
| `oxmysql`                             | Required by `server/database.lua` and loaded through `@oxmysql/lib/MySQL.lua`.                                                                                   |
| VORP or RSG framework                 | Auto-detects `vorp_core`, `rsg-core`, or `rsg-core-main`, or you can force the framework in `Config.Framework`.                                                  |
| Inventory resource                    | VORP item, weapon, store, stash, and crafting item operations expect `vorp_inventory`. RSG inventory operations support `rsg-inventory` or `rsg-inventory-main`. |
| Optional `ox_target`                  | Only required when `Config.Mode = 'target'`. If it is not started, target mode falls back to drawtext visuals.                                                   |
| Optional `rsg-multijob`               | Used by the RSG bridge when available to remove the old job during job changes.                                                                                  |

## Installation

<Steps>
  <Step title="Place the resource">
    Copy the folder into your server resources directory and keep the folder name as `sh-society`.
  </Step>

  <Step title="Start dependencies first">
    Ensure `oxmysql`, your framework, and your inventory resource before `sh-society`.

    ```cfg theme={null}
    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure sh-society
    ```

    For RSG:

    ```cfg theme={null}
    ensure oxmysql
    ensure rsg-core
    ensure rsg-inventory
    ensure sh-society
    ```
  </Step>

  <Step title="Install the database tables">
    Import `sql/install.sql` into your database before first use. The script also creates and migrates its tables at startup when `oxmysql` is ready, but importing the SQL file first makes setup failures easier to spot.
  </Step>

  <Step title="Configure the resource">
    Edit `config.lua` for framework mode, locale, interaction mode, commands, admin access, payroll, storage, stores, job center, Discord webhooks, and theme presets. Edit `crafting_config.lua` if you want society crafting.
  </Step>

  <Step title="Grant admin access">
    If ACE admin checks are enabled, grant the configured ACE permission:

    ```cfg theme={null}
    add_ace group.admin sh.society.admin allow
    ```
  </Step>

  <Step title="Restart and create societies">
    Restart the resource, open `/socadmin` as an authorized admin, create a society for each framework job, then test `/society` with a character assigned to that job.
  </Step>
</Steps>

<Warning>
  Society job names must match your framework job names exactly. A player can only open the normal society panel when their current job has a matching society row.
</Warning>

## Framework Support

```lua theme={null}
Config.Framework = 'auto' -- auto | vorp | rsg
```

`auto` checks started resources in this order:

| Detected resource             | Framework mode |
| ----------------------------- | -------------- |
| `vorp_core`                   | `vorp`         |
| `rsg-core` or `rsg-core-main` | `rsg`          |

The bridge handles character IDs, player names, groups, job changes, money, inventory items, stash access, and item transfers for supported framework and inventory combinations.

## Commands

| Command                  | Config key                     | Description                                                                                     |
| ------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------- |
| `/society`               | `Config.Commands.Society`      | Opens the current job's society panel. Accepts an optional society id argument for direct open. |
| `/socadmin`              | `Config.Commands.SocietyAdmin` | Opens the staff admin panel for creating, updating, and deleting societies.                     |
| `/bills`                 | `Config.Commands.Bills`        | Opens the player's outstanding bill window.                                                     |
| `/bills pay <invoiceId>` | `Config.Commands.Bills`        | Pays a specific open invoice from cash.                                                         |
| `/invoice`               | `Config.Commands.Invoice`      | Opens the nearby-player invoice window for employees with invoice permission.                   |

## Core Configuration

```lua theme={null}
Config.Debug = false
Config.Locale = 'en'      -- en | es | de | fr
Config.Framework = 'auto' -- auto | vorp | rsg
Config.Mode = 'ingame'    -- drawtext | ingame | target
```

Interaction modes:

| Mode       | Behavior                                                                      |
| ---------- | ----------------------------------------------------------------------------- |
| `drawtext` | Draws markers and 3D text prompts.                                            |
| `ingame`   | Uses native RedM prompt groups. This is the default.                          |
| `target`   | Uses `ox_target` sphere zones when the configured target resource is started. |

When using target mode:

```lua theme={null}
Config.Target = {
    Resource = 'ox_target',
    Debug = false,
}
```

## Admin Access

```lua theme={null}
Config.Admin = {
    Ace = {
        Enabled = true,
        Permission = 'sh.society.admin',
    },
    UserGroups = {
        admin = true,
        god = true,
        superadmin = true,
        mod = true,
    },
}
```

Admins can create, edit, list, and delete societies. Admins also bypass society-level rank permission checks.

## Society Settings

```lua theme={null}
Config.Society = {
    MaxRanks = 15,
    MinRanks = 1,
    DefaultRankCount = 5,
    DefaultTaxPercent = 10.0,
    DefaultEntryGrade = 0,
    UnemployedJob = 'unemployed',
    HireDistance = 12.0,
    HireOfferTimeoutSeconds = 30,
}
```

Each society stores:

| Field                      | Purpose                                                                  |
| -------------------------- | ------------------------------------------------------------------------ |
| `society_id`               | Internal unique id generated from the job name unless manually supplied. |
| `label`                    | Display name in the UI.                                                  |
| `job_name`                 | Framework job name linked to the society.                                |
| `is_public`                | Allows the society to appear in the public job center.                   |
| `entry_grade`              | Rank assigned through public self-hire.                                  |
| `boss_grade`               | Rank index that automatically receives boss-level access.                |
| `payroll_interval_minutes` | Per-society payroll interval override.                                   |
| `tax_percent`              | Percent reserved into `tax_balance` when income deposits are recorded.   |

## Rank Permissions

New societies receive default ranks and permission rows automatically. Rank indexes greater than or equal to `boss_grade` are treated as boss ranks and get all permissions. Lower ranks use these individual permission flags:

| Permission                | Allows                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------ |
| `employee.storage`        | Open society storage.                                                                      |
| `employee.store`          | Use society store actions.                                                                 |
| `employee.invoice.create` | Create invoices with `/invoice` or the UI.                                                 |
| `employee.blip.view`      | View employee-facing blip data.                                                            |
| `boss.employee.hire`      | Send hire offers.                                                                          |
| `boss.employee.fire`      | Fire employees.                                                                            |
| `boss.employee.promote`   | Change employee ranks.                                                                     |
| `boss.employee.demote`    | Change employee ranks downward.                                                            |
| `boss.ranks.manage`       | Rename ranks, change rank count, and edit rank permissions.                                |
| `boss.payroll.manage`     | Set salaries and society payroll interval.                                                 |
| `boss.ledger.deposit`     | Deposit cash into the society ledger.                                                      |
| `boss.ledger.withdraw`    | Withdraw society ledger funds.                                                             |
| `boss.store.manage`       | Create stores, place markers, set props, manage stock, prices, categories, and buy orders. |
| `boss.advert.manage`      | Create, place, update, and delete advertisement boards.                                    |
| `boss.storage.manage`     | Place storage markers and crafting stations.                                               |
| `boss.blip.manage`        | Create, place, label, enable, disable, and delete society blips.                           |
| `boss.theme.manage`       | Update society UI theme colors and icon.                                                   |
| `boss.discord.manage`     | View and manage the society-level Discord webhook settings.                                |

## Features

<Accordion title="Admin Society Panel">
  Staff with admin access can create, update, and delete societies. A society links a framework job to a business label, public job center state, entry grade, boss grade, payroll interval, tax percent, theme, blips, storage data, and webhook data.
</Accordion>

<Accordion title="Employee Management">
  Bosses can hire, fire, and change ranks from the society panel. Hiring sends a target player an offer that expires after `Config.Society.HireOfferTimeoutSeconds`. The target must be within `Config.Society.HireDistance`. Firing assigns `Config.Society.UnemployedJob` at grade `0`.
</Accordion>

<Accordion title="Ranks and Permissions">
  Boss ranks are determined by `boss_grade`, while lower ranks can receive specific permissions. Rank names, salaries, permission flags, and rank count are stored per society.
</Accordion>

<Accordion title="Ledger and Taxes">
  Each society has its own ledger balance, tax reserve balance, and transaction history. Deposits marked as income reserve `tax_percent` into `tax_balance` and add the net amount to the spendable balance. Withdrawals and payroll come out of the spendable balance.
</Accordion>

<Accordion title="Payroll">
  Online society employees are paid by rank salary on each society's interval. Payroll withdraws from the society ledger first; if the ledger is short, that employee's payout is skipped. Configured public jobs from the job center are paid directly by the server and do not require a society ledger.
</Accordion>

<Accordion title="Billing and Invoices">
  Employees with `employee.invoice.create` can bill nearby players. Invoice targets are limited to nearby online characters and cannot be the issuer. Players can open `/bills` or use `/bills pay <invoiceId>`. Paid invoices deposit income into the issuing society ledger.
</Accordion>

<Accordion title="Storage">
  Bosses with `boss.storage.manage` can place a society storage marker. Employees with `employee.storage` can open the stash only while near the configured marker. Storage uses stash ids in the format `society_stash_<society_id>`.
</Accordion>

<Accordion title="Stores">
  Bosses can create in-world storefronts, place markers, pick props from `Config.Store.PropList`, add stock from inventory, set prices, set categories, and configure buy orders for player sell-back. Stores validate distance server-side before purchases or sales.
</Accordion>

<Accordion title="Advertisements">
  Bosses can create advertisement boards with title, description, image URL, enabled state, prop model, heading, and placed coordinates. Advertisement props stream to nearby players and can be viewed in-world.
</Accordion>

<Accordion title="Blips">
  Societies support public/job blips stored in `sh_society_blips`. Bosses can place, label, enable, disable, and delete blips from the panel. Clients refresh job blips periodically.
</Accordion>

<Accordion title="Public Job Center">
  The job center NPC displays configured public jobs and societies marked public. Public job switching uses a persistent per-character cooldown in `sh_society_job_cooldowns`. Config-only public jobs remove existing society employee records before applying the new framework job.
</Accordion>

<Accordion title="Crafting">
  Society crafting is configured in `crafting_config.lua` and is disabled by default. Enable `Config.Crafting.Enabled`, define recipes in `Config.CraftingRecipes[jobName]`, then place the station from the society UI. Only jobs with recipes can place or use a crafting station.
</Accordion>

<Accordion title="Discord Webhooks and Audit Logs">
  Global webhooks can route event types to separate Discord channels. Bosses can optionally configure a society-specific webhook from the UI when `Config.IngameWebhook.Enabled = true`. Audit rows are also written to `sh_society_audit`.
</Accordion>

## Payroll Configuration

```lua theme={null}
Config.Payroll = {
    Enabled = true,
    IntervalMinutes = 15,
    MinIntervalMinutes = 1,
    MaxIntervalMinutes = 240,
    MoneyType = 'cash',
}
```

Society payroll uses each rank's salary from `sh_society_ranks`. Public job payroll uses salary values from `Config.JobCenter.DefaultPublicJobs` when no real society exists for that job name.

## Billing Configuration

```lua theme={null}
Config.Billing = {
    Currency = 'cash',
    CommandPageSize = 25,
}
```

Invoice payment removes `Config.Billing.Currency` from the payer, marks the invoice paid, and deposits the amount into the society ledger as income.

## Job Center Configuration

```lua theme={null}
Config.JobCenter = {
    Enabled = true,
    Model = 'u_m_m_valtownfolk_01',
    Coords = vector4(-784.26, -1347.35, 43.75, 90.71),
    Distance = 2.2,
    CleanupRadius = 2.75,
    CooldownMs = 5000,
    SwitchCooldownHours = 1.0,
    DefaultPublicJobs = {
        {
            jobName = 'hunter',
            label = 'Wildlife Hunter',
            grade = 0,
            salary = 15,
            payrollIntervalMinutes = 15,
            description = 'Hunting wildlife for a living.',
        },
    },
}
```

Public job center entries come from two sources:

| Source                                       | Behavior                                                                                            |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Admin-created societies with `is_public = 1` | Player is hired into the society at `entry_grade`; salary comes from that society rank.             |
| `Config.JobCenter.DefaultPublicJobs`         | Player receives the framework job and grade directly; salary and payroll interval come from config. |

## Storage Configuration

```lua theme={null}
Config.Storage = {
    Enabled = true,
    DefaultMaxWeight = 2000000,
    DefaultSlots = 60,
    OpenDistance = 3.0,
    DrawDistance = 18.0,
    InteractDistance = 2.2,
}
```

Storage location, label, slots, and weight are stored per society in `storage_json`.

## Store Configuration

```lua theme={null}
Config.Store = {
    Enabled = true,
    MaxPerSociety = 2, -- 0 = unlimited
    BuyDistance = 2.2,
    OpenMode = 'ui',
    RefreshMs = 30000,
    PropStreamDistance = 70.0,
    PropList = {
        'p_cratetablemil01x',
        'p_cratetable01x',
    },
}
```

Store rows are stored in `sh_society_stores`. Store catalogs and buy-order catalogs are JSON columns, so each store can maintain its own stock, prices, categories, and buy-back rules.

## Advertisement Configuration

```lua theme={null}
Config.Advertising = {
    Enabled = true,
    MaxPerSociety = 5, -- 0 = unlimited
    RefreshMs = 30000,
    StreamDistance = 70.0,
    InteractDistance = 2.4,
    PropList = {
        'p_menuboardnbx01x',
        'p_sandwichboard01x',
    },
    DefaultImage = 'img/no-image.jpg',
}
```

## Crafting Configuration

Crafting is in `crafting_config.lua`.

```lua theme={null}
Config.Crafting = {
    Enabled = false,
    MaxQuantity = 50,
    AllowMultipleCrafts = false,
    PropStreamDistance = 70.0,
    DrawDistance = 18.0,
    InteractDistance = 2.2,
    OpenDistance = 3.0,
}
```

Example recipe:

```lua theme={null}
Config.CraftingRecipes = {
    blacksmithV = {
        {
            label = 'Nails',
            item = 'nails',
            amount = 5,
            duration = 8000,
            ingredients = {
                { item = 'iron', amount = 3 },
                { item = 'wood', amount = 1 },
            },
        },
    },
}
```

Supported recipe aliases include:

| Purpose      | Supported keys                                             |
| ------------ | ---------------------------------------------------------- |
| Inputs       | `inputs`, `ingredients`, `materials`                       |
| Outputs      | `outputs`, `output`, or top-level `item`/`name`            |
| Amounts      | `amount`, `count`, `qty`                                   |
| Quantity cap | `maxCount`, `max_count`, `max`                             |
| Duration     | `duration`, `time`, `craftTimeSec`, `timeMs`, `durationMs` |

Set `consume = false` on an ingredient when the item should be required but not removed.

## Discord Webhooks

```lua theme={null}
Config.Webhooks = {
    Enabled = true,
    Username = 'sh-society',
    AvatarURL = '',
    Default = '',
    Channels = {
        admin = '',
        boss = '',
        billing = '',
        payroll = '',
        stores = '',
        adverts = '',
        blips = '',
        employees = '',
        ranks = '',
        ledger = '',
        audit = '',
        taxes = '',
        security = '',
    },
}

Config.IngameWebhook = {
    Enabled = true,
}
```

Webhook URLs must use Discord webhook URL formats such as `https://discord.com/api/webhooks/...`. The script rate-limits webhook sending per bucket to avoid burst spam.

## Server Exports

Use these exports from other server resources:

```lua theme={null}
exports['sh-society']:CreateSocietyInvoice(targetCharId, societyId, amount, reason, metadata)
exports['sh-society']:GetCharacterInvoices(charId)
exports['sh-society']:PaySocietyInvoice(charId, invoiceId, paymentSource)
exports['sh-society']:GetSocietyLedgerBalance(societyId)
exports['sh-society']:AddSocietyLedgerEntry(societyId, amount, reason, metadata)
exports['sh-society']:WithdrawSocietyLedgerEntry(societyId, amount, reason, metadata)
exports['sh-society']:GetSocietyRanks(societyId)
exports['sh-society']:HasSocietyPermission(source, societyId, permissionKey)
```

Example:

```lua theme={null}
local ok, msg = exports['sh-society']:CreateSocietyInvoice(
    targetCharId,
    'doctor',
    50,
    'Medical treatment',
    { sourceResource = 'my-doctorjob' }
)
```

## Database Tables

| Table                         | Stores                                                                                                                     |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `sh_society_societies`        | Society definitions, public job flags, payroll/tax settings, theme, storage JSON, store JSON, blip JSON, and webhook JSON. |
| `sh_society_ranks`            | Rank names and salaries.                                                                                                   |
| `sh_society_rank_permissions` | Per-rank permission flags.                                                                                                 |
| `sh_society_employees`        | Society membership, rank, boss state, and last known player name.                                                          |
| `sh_society_ledger`           | Spendable ledger balance and reserved tax balance.                                                                         |
| `sh_society_ledger_tx`        | Ledger transaction history.                                                                                                |
| `sh_society_invoices`         | Open and paid invoices.                                                                                                    |
| `sh_society_blips`            | Society blips and enabled state.                                                                                           |
| `sh_society_stores`           | Store locations, props, stock catalog, and buy-order catalog.                                                              |
| `sh_society_adverts`          | Advertisement boards, image URLs, props, and positions.                                                                    |
| `sh_society_audit`            | Successful and denied action audit entries.                                                                                |
| `sh_society_job_cooldowns`    | Persistent job center switch cooldowns.                                                                                    |

<Warning>
  Deleting a society from the admin panel also deletes that society's ranks, rank permissions, employees, ledger, ledger transactions, blips, stores, and adverts. Invoices and audit rows are not deleted by the society delete function.
</Warning>

## Security Notes

```lua theme={null}
Config.Security = {
    EventCooldownMs = 300,
    MaxString = 256,
    MaxReason = 512,
    MaxAmount = 1000000,
}
```

The NUI API router validates action permissions, applies a per-source per-action cooldown, and logs denied actions to audit/security paths. Store, storage, crafting, advert, and invoice actions also perform server-side distance and payload checks.

## Troubleshooting

| Symptom                          | Check                                                                                                             |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Resource stops immediately       | The folder must be named `sh-society`.                                                                            |
| Database calls return empty data | Confirm `oxmysql` starts before `sh-society` and the SQL tables exist.                                            |
| `/society` says no society job   | Create a society for the character's exact framework job name.                                                    |
| `/socadmin` denies access        | Add the ACE permission or make sure the framework user group is listed in `Config.Admin.UserGroups`.              |
| Target interactions do not show  | Start the configured target resource or use `Config.Mode = 'ingame'` or `drawtext`.                               |
| Crafting cannot be placed        | Set `Config.Crafting.Enabled = true` and define recipes for that society's exact job name.                        |
| Storage fails to open            | Confirm the inventory resource is started, the storage marker is placed, and the employee has `employee.storage`. |
| Payroll skips employees          | Fund the society ledger and set rank salaries above `0`.                                                          |
