Saltar al contenido principal

Employee Module Documentation

Overview

The Employee module manages business staff members, separating "system user" (Firebase authentication) from "employee" (business entity). This enables transaction attribution, commission tracking, PIN-based manager approvals, and role-specific permissions for staff who may or may not have system access.

Architecture

Hexagonal Structure

apps/backend/src/employees/
├── employees.module.ts # Module registration
├── domain/
│ └── employees-repository.domain.ts # Repository interface (port) + EMPLOYEES_REPOSITORY token
├── application/
│ └── employees.service.ts # Business logic & use cases
├── infrastructure/
│ └── employees.repository.ts # Kysely implementation (adapter)
└── interfaces/
├── employees.controller.ts # REST endpoints (adapter)
├── dtos/
│ ├── create-employee.dto.ts # Create validation + Swagger schema
│ ├── update-employee.dto.ts # Update validation + Swagger schema
│ ├── set-approval-pin.dto.ts # PIN set validation
│ └── verify-pin.dto.ts # PIN verify validation
└── query/
└── paginate-employees.query.ts # Pagination/filter/sort query (OffsetPaginationMixin + SortMixin)

Dependency Injection

The module uses Symbol-based DI tokens for proper hexagonal boundaries:

  • EMPLOYEES_REPOSITORY Symbol defined in domain layer
  • Service injects via @Inject(EMPLOYEES_REPOSITORY) with IEmployeesRepository interface type
  • Module registers: { provide: EMPLOYEES_REPOSITORY, useClass: EmployeesRepository }

Guards & Permissions

  • @UseGuards(RolesGuard) — enforces RBAC
  • @PermissionResource(PolicyResource.Employee) — class-level resource binding
  • @PermissionAction(PolicyAction.Create/Read/Update/Delete) — per-endpoint action

Database Schema

Table: employee

SectionFields
Identityid, businessId, locationId, userId
IdentificationemployeeNumber, fullName, email, phone
Job Infoposition, department, employmentType, hireDate, terminationDate
ApprovalapprovalPinHash, canApproveReturns/Discounts/Voids, canOverridePrices
CompensationcommissionRate, hourlyRate, salaryAmount, tipPoolShare
RestaurantserverNumber, canTakeOrders, canCloseTables, maxTableCapacity
RetailsalesTargetMonthly, canProcessReturns
StatusisActive, isManager, notes
AuditcreatedAt, createdBy, updatedAt, updatedBy

Key Indexes

  • (businessId) — business scoping
  • (businessId, isActive) — active employee queries
  • (businessId, position) — position filtering
  • (businessId, employeeNumber) UNIQUE — employee number per business
  • (businessId, userId) UNIQUE — one employee per user per business

API Endpoints

All endpoints require Bearer authentication and businessId context.

CRUD

MethodEndpointDescription
POST/employeesCreate employee (businessId + createdBy in body)
GET/employees?businessId=<id>Paginated list with filters and sorting
GET/employees/:id?businessId=<id>Get by ID
PATCH/employees/:idUpdate (businessId + updatedBy in body)
DELETE/employees/:id?businessId=<id>Soft delete (sets isActive=false)

Lookups

MethodEndpointDescription
GET/employees/active?businessId=<id>All active employees
GET/employees/managers?businessId=<id>All active managers
GET/employees/by-number/:number?businessId=<id>Find by employee number

PIN Management

MethodEndpointDescription
POST/employees/:id/set-pin?businessId=<id>Set 4-6 digit approval PIN
POST/employees/:id/verify-pin?businessId=<id>Verify PIN → { valid: boolean }
DELETE/employees/:id/pin?businessId=<id>Remove PIN

Pagination Parameters

ParamTypeDefaultDescription
sizenumber10Page size
pagenumber1Page number
orderBystringfullNameSort field: fullName, hireDate, position, employeeNumber
orderstringascSort direction: asc, desc
searchstringSearch across fullName, email, employeeNumber
isActivebooleanFilter by active status
positionstringFilter by position
departmentstringFilter by department
locationIdUUIDFilter by location
isManagerbooleanFilter by manager flag

Usage Examples

Creating an Employee

// Via service (internal)
const employee = await employeesService.createEmployee({
businessId,
fullName: "John Doe",
employeeNumber: "EMP-001",
position: "Server",
canTakeOrders: true,
isActive: true,
createdBy: userId,
});

// Via API
POST /employees
{
"businessId": "...",
"fullName": "John Doe",
"employeeNumber": "EMP-001",
"position": "Server",
"canTakeOrders": true,
"createdBy": "..."
}

Setting Approval PIN

await employeesService.setApprovalPin(employeeId, businessId, userId, "1234");
const isValid = await employeesService.verifyPin(employeeId, businessId, "1234");

Integration with Other Modules

The EmployeesService is exported and used by:

ModuleUsage
Restaurant Ordersorder.waiter_id → validates active + canTakeOrders
Retail Salessale.salesperson_id, sale.approved_by_employee_id
Cash Registersession.opened_by_employee_id, session.closed_by_employee_id
DiscountsEmployee approval for discount applications
Store CreditEmployee context resolution
Data ImportBulk employee upsert

Backward-Compatible Methods

For cross-module consumers, the service exposes convenience methods with simpler signatures:

  • findById(id) — throws NotFoundException (no businessId required)
  • findByUserId(businessId, userId) — returns null if not linked
  • findByEmployeeNumber(businessId, number) — throws NotFoundException
  • findAll(businessId, options) — legacy paginated list
  • create(businessId, userId, data) — legacy create
  • update(id, userId, data) — legacy update

Security

PIN Management

  • Algorithm: bcrypt (cost factor 12)
  • Storage: Only hashed in approvalPinHash column
  • Format: 4-6 digits (validated via regex)
  • Logging: All operations logged (raw PINs never logged)

Permission Flags

FlagPurpose
canApproveReturnsApprove return requests
canApproveDiscountsApprove discount applications
canApproveVoidsApprove void transactions
canOverridePricesOverride item prices
canTakeOrdersTake restaurant orders
canCloseTablesClose restaurant tables
canProcessReturnsProcess retail returns

Multi-Tenancy

  • All queries scoped by businessId
  • Unique constraints per-business (employeeNumber, userId)
  • One employee record per user per business
  • Foreign keys use ON DELETE SET NULL to preserve transaction history

Design Decisions

  1. Soft deletes only — employees are never physically deleted to preserve transaction attribution
  2. Symbol-based DI — ensures proper hexagonal boundaries between layers
  3. Backward-compatible methods — external consumers use simpler signatures while the controller uses the standard pattern
  4. Business-scoped repository — all mutations require businessId for multi-tenancy safety

Version: 2.0.0 Last Updated: 2026-03-24 Status: Production Ready