Skip to content

Creating an Employee

Minimal call

from datetime import datetime
from decimal import Decimal

from sonnys_backoffice import SonnysBackofficeClient

with SonnysBackofficeClient(
    subdomain="washu",
    username="automation-bot",
    password="...",
) as client:
    result = client.create_employee(
        first_name="Jane",
        last_name="Doe",
        phone="6155551234",
        email="jane.doe@example.com",
        pos_user_id=12345,
        wage_rate=Decimal("15.50"),
        start_date=datetime(2026, 5, 1),
        available_sites=["Wash 37135"],
        permission="General User",
    )
    print(result.pos_pin)

Every parameter explained

Parameter Type Required Default Notes
first_name str yes Leading/trailing whitespace is stripped. Unicode and symbols preserved.
last_name str yes Same rules.
phone str yes 9 or 10 digits after all non-digit characters are stripped.
email str yes Must contain a valid @domain.tld.
pos_user_id int yes Must be unique per tenant. Pre-flight with is_pos_user_id_available().
pos_pin int \| None no auto-generated 5-digit int Always returned in the result — capture it!
wage_rate Decimal \| float yes Dollars per hour. Prefer Decimal to avoid floating-point drift.
overtime_wage_rate Decimal \| float \| None no wage_rate × 1.5
start_date datetime yes The form expects MM/DD/YYYY; the library formats it.
available_sites list[str] \| "all" yes Site names (not IDs). The wrapper auto-detects flat vs hierarchical tenants — see Sites, regions & districts.
permission str yes Case-insensitive. Unknown names fall back to "General User" with a warning.
departments list[str] \| None no ["Greeter"] "Greeter" is always auto-added — see below.
adp_employee_id str \| None no None ADP employee ID, if your tenant uses ADP payroll integration.
emergency_contact_name str \| None no None
emergency_contact_phone str \| None no None Same validation as phone.
requires_backoffice bool no False Creates a linked Backoffice user in the same call. See the BO section below.
backoffice_username str \| None no None Required when requires_backoffice=True.
backoffice_password str \| None no auto-generated 12 chars Always returned on BO-enabled creates.

Why "Greeter" is always present

On most Sonny's tenants, the Greeter department is tied to the tip/commission system: if an employee is NOT in Greeter, they won't appear on the pay reports for tipped commissions, even if they work a Greeter shift. The library auto-adds Greeter if you omit it, so you never end up with a "missing from tips" surprise on payday. If you genuinely want an employee without Greeter, pass departments=["Cashier"] and then edit them via the Backoffice UI — the library is deliberately opinionated here.

Wage site attribution

Sonny's wage form requires attributing each employee's wage to a specific site, even though the rate is global. The library resolves this automatically: it picks the first site from available_sites (or the first site in the tree if you passed "all") and uses that as the wage attribution site. The chosen site is returned in result.wage_site.

Uniqueness pre-flight

POS User ID, email, and phone are all unique per tenant. Before hitting create_employee, the client builds an internal employee index from /employee?limit=10000&active=all and /user/create, and checks all three — the call fails fast with DuplicateError rather than hitting the server and getting a vague form error back.

If you want to avoid the exception path entirely, use the availability helpers first:

if not client.is_pos_user_id_available(12345):
    pos_user_id = find_next_free_id(client, starting_at=12346)

Creating with a linked Backoffice user

Pass requires_backoffice=True along with backoffice_username:

result = client.create_employee(
    ...,
    requires_backoffice=True,
    backoffice_username="janedoe",
)
print(result.backoffice_password)  # auto-generated 12-char password

Milestone 1 limitation

The linked Backoffice user is created, but its permission template is not assigned automatically. You'll need to open /user in Backoffice, click the shield icon next to the new user, pick a template, and save. The returned result.warnings list contains a reminder. This is a known limitation — see Creating a Backoffice user for the full story and Milestone 2 roadmap.

The linked employee must be active at the moment of BO user creation. Since create_employee always creates the employee first and then the BO user, this is automatic for new hires. It only becomes a concern when re-linking a BO account to a previously-disabled employee — which Milestone 1 doesn't support.

The returned EmployeeCreated object

class EmployeeCreated:
    employee_id: int                  # the internal Backoffice ID
    pos_user_id: int                  # echoed from the request
    pos_pin: int                      # either the passed value or auto-generated
    first_name: str
    last_name: str
    email: str
    backoffice_user_id: int | None    # only set when requires_backoffice=True
    backoffice_username: str | None
    backoffice_password: str | None   # auto-generated if not supplied
    permission_applied: str           # the resolved template name (after fallback)
    sites_granted: list[str]          # site names, resolved from available_sites
    departments: list[str]            # with Greeter included
    wage_site: str                    # the wage attribution site name
    warnings: list[str]               # permission fallback, BO deferral, etc.

Always log or persist pos_pin and backoffice_password — Backoffice does not show them again.