Skip to main content

SERVICE_OVERVIEW — staff-service

Catalog: docs/03-microservices/staff-service.md

Sibling: DOMAIN_MODEL · APPLICATION_LOGIC · API_CONTRACTS · EVENT_SCHEMAS · DATA_MODEL · SYNC_CONTRACT · SECURITY_MODEL · AI_INTEGRATION · OBSERVABILITY

Strategic anchors: 02 Enterprise Architecture · 04 Event-Driven Architecture · 05 API Design · 07 Security & Tenancy · ADR-0003 Electron Offline-First

1. Purpose

staff-service is the operational HR backbone of Ghasi Melmastoon. It tracks who works where, when they are scheduled, and when they are physically on the property. Its single most important downstream signal is the capacity snapshot — the live list of staff currently on the floor, by position and department — which housekeeping-service and maintenance-service use to assign rooms and tickets, and which bff-backoffice-service uses to render the front-desk dashboard.

It is deliberately lightweight HR, not a payroll system or HRIS. Adding payroll, performance reviews, complex compensation, benefits administration, or learning management requires an ADR amendment, not a feature ticket. The hospitality target market — small-to-mid hotels and guest houses across Pashtun, Persian, Tajik, and Pakistani regions — already runs payroll out of band; what it lacks is reliable, multilingual, offline-tolerant scheduling and clock-in. That is what we provide.

2. Bounded Context

PropertyValue
Bounded contextStaff & Shifts
Domain classSupporting
Strategic patternsConformist to iam-service (User), tenant-service (Membership); OHS publisher to housekeeping-service, maintenance-service, front-desk-service, reservation-service
Ubiquitous languageStaff · Position · Department · Shift · Pattern · Assignment · Clock · Break · Leave · Certification · On-Call · Hand-off · Capacity

A Staff is not a User. A Staff is the operational projection of a person's employment within a tenant; the underlying authentication identity (and tenant membership) lives in iam-service / tenant-service. The two are connected by Staff.userId which is nullable — a staff record can exist before an invitation is accepted (see APPLICATION_LOGIC §6 CreateStaff) and a staff record can exist forever without a User if the staff member never gets a personal device (PIN-only path).

3. Aggregates Owned

AggregatePrefixBrief
Staffstf_Hospitality-specific extension of iam.User. Owns: position, department, languages, certifications (refs), emergency contact, employment dates, status, multi-property access list, clock-in PIN.
Positionpos_Tenant-defined job role (front desk, housekeeper, GM, maintenance, F&B, security, …). Localized labels.
Departmentdpt_Tenant-defined org grouping per property; many positions per department.
ShiftPatternshp_Recurring weekly/bi-weekly schedule template. Carries position requirement and headcount.
Shiftshf_Concrete time window for a property/date materialized from a pattern (or ad-hoc).
ShiftAssignmentsha_Many-to-many between Shift and Staff with role (primary / standby / on_call).
ClockEntryclk_Append-only punch (in / out / break_start / break_end) with source attribution and device binding.
LeaveRequestlvr_Sick / vacation / unpaid; lifecycle: requestedapproved / rejected / cancelled.
StaffSkillskl_Language, equipment, soft skill — used by AI suggestion + housekeeping matching. Light catalog, tenant-extensible.
StaffCertificationcrt_Food handling, first aid, fire safety, etc.; carries expires_at → emits .expired.v1 on TTL.
HandoffNotehno_Append-only between-shift notes scoped to a position/department/property.

The supporting infrastructure (outbox, inbox, idempotency, audit) is persistence-only and not part of the domain.

4. Top Responsibilities

  1. Staff CRUD with hospitality attributes — supports the informal staffing reality of target markets.
  2. No-email staff — first-class support for staff records that have only a manager email + PIN.
  3. Pattern-based scheduling — define a recurring template, generate concrete shifts for a date window per property.
  4. Manual + AI-suggested assignment — accept manual assignments, materialize AI suggestions advisory-only.
  5. Conflict detection — double-shift, leave collision, multi-property simultaneous, expired certification.
  6. Clock in / out / break — from front-desk Electron desktop (PIN) or via manager override on mobile/web.
  7. Light leave management — request + approve; collisions block until forceUnassign is set.
  8. Capacity signalGET /capacity and the staff.shift.started.v1 / .ended.v1 event stream.
  9. On-call / standby — auto-promote standby to primary if a no-show is detected.
  10. Multi-property staff — same tenant, multiple properties, but at most one active shift across all of them.
  11. Hand-off notes — append-only between-shift notes scoped per position.
  12. Staffing gap detection — proactive .staffing_gap_detected.v1 when a shift is N minutes from start with no clocked-in primary.
  13. Basic attendance reports — per staff / per period / per position (CSV + JSON).

5. Out of Scope (Explicitly)

  • Payroll, payslips, tax withholding, deductions, benefits — never. Use external payroll; export attendance via the read API or BigQuery.
  • Performance reviews, 360-feedback, OKRs, KPIs.
  • Complex compensation — no hourly rates, overtime calculation, shift differentials. We track time, not money.
  • Recruitment / ATS / onboarding workflows beyond "create record and invite".
  • Learning Management — certifications are tracked (existence + expiry), not their training delivery.
  • Authentication / sessions / MFA — owned by iam-service. We never store passwords or issue tokens.
  • Tenant role assignment — owned by tenant-service. We listen.

6. Context Map (Upstream / Downstream)

┌──────────────────────┐
│ iam-service │ user.registered.v1
│ (Generic, Conformist)│ ─────────────────────┐
└──────────────────────┘ │

┌──────────────────────┐ membership.* ┌────────────────────┐
│ tenant-service │ ──────────────────▶│ staff-service │
│ (Supporting, Conformist) │ (Supporting, │
└──────────────────────┘ │ OHS publisher) │
└────────────────────┘
│ │
┌──────────────────────────────────────────────────┘ │
│ shift.started.v1 │
│ shift.ended.v1 │
│ clock.in.v1 / .out.v1 │
│ shift.staffing_gap_detected.v1 │
│ capacity (REST) │
▼ │
┌────────────────────────┐ ┌────────────────────────┐ │
│ housekeeping-service │ │ maintenance-service │ │
│ (consumer, OHS conformist) │ (consumer, OHS conformist) │
└────────────────────────┘ └────────────────────────┘ │

┌───────────────────────────────────────────────────┘
│ certification.expired.v1, terminated.v1

┌────────────────────────────┐
│ bff-backoffice-service │ (front-desk dashboards, schedules)
└────────────────────────────┘

ai.suggestion.shift_optimization.v1

┌──────────────────────┐
│ ai-orchestrator │ (advisory schedule, never auto-applied)
└──────────────────────┘

7. Hot Read Paths

SurfaceQueryLatency budget (p95)
Front-desk Electron capacity widgetGET /capacity?propertyId&position&at80 ms (cache-hit), 250 ms (miss)
Backoffice schedule gridGET /shifts?propertyId&from&to300 ms
Housekeeping assignment loopGET /capacity?propertyId&position=housekeeper80 ms
Reports — attendance per periodGET /reports/attendance?…5 s (paginated)

8. Cost / Scale Envelope (M0 → M2)

MetricM0 (single region)M2 (multi-region active-active)
Tenants505 000
Properties / tenantup to 5up to 25
Staff / propertyup to 80up to 250
Punches / property / day~ 400~ 1 200
Peak punches / second (cluster)5200
Outbox depth normal< 20< 200
DB IOPS (avg)1004 000

Capacity events are fan-out heavy (every consumer of staff.shift.started.v1 is property-scoped) but each carries a small payload (< 1 KiB). Pub/Sub cost dominates at M2.

9. Decision Log

#DecisionWhy
1Staff is a separate aggregate, not extension of iam.UserDifferent lifecycle, different bounded context, multi-property scoping needed
2PIN-based clock-in (no biometric in M0/M1)Hardware availability and cultural fit; biometric reserved for M2 ADR
3Staff.userId is nullableReal-world hotel staff often lack personal devices/email
4Append-only clock_entries table, no updatesAudit, sync simplicity, regulatory alignment
5Multi-property staff with at-most-one active shift across allOperational sanity; physical impossibility
6AI suggestions are advisory only, never auto-appliedTrust + auditability; aligns with 02 §10 AI provenance
7Lightweight leave only (3 types, single-step approval)Scope discipline; not an HRIS
8Pattern-based scheduling, not optimizer-firstSimpler, comprehensible, AI-augmentable later

10. External Dependencies

DependencyDirectionWhyFailure response
iam-serviceinbounduser.registered.v1, session-revoke RPC on terminateInbox dedup; revoke is fire-and-forget with retry
tenant-serviceinboundmembership.created/removed.v1Inbox dedup
property-serviceinboundproperty existence, departments scopeCache + circuit breaker; degrade to last-known
ai-orchestrator-serviceinboundai.suggestion.shift_optimization.v1Drop after 7 d retention; never blocks
notification-serviceoutbound.staffing_gap_detected.v1 triggers ops alerts (Pub/Sub)Async; no synchronous coupling
Cloud SQL Postgres 16syncSystem of recordReadiness fails until DB reachable
Memorystore Redis 7syncCapacity cache, PIN attempt counter, idempotencyDegrade: skip cache, allow attempts (rate-limited via DB)
Pub/SubsyncOutbox publish + inbox subscribeOutbox retries with backoff
Cloud KMSsyncPIN HMAC pepperReadiness fails until KMS reachable
ConcernDocument
Domain types & invariantsDOMAIN_MODEL
Use cases, ports, sagasAPPLICATION_LOGIC
REST surfaceAPI_CONTRACTS
Pub/Sub schemasEVENT_SCHEMAS
DDL, RLS, indexesDATA_MODEL
Electron syncSYNC_CONTRACT
RBAC, PIN, auditSECURITY_MODEL
AI usageAI_INTEGRATION
SLIs, alerts, dashboardsOBSERVABILITY
Test pyramidTESTING_STRATEGY
Cloud Run topologyDEPLOYMENT_TOPOLOGY
Failure catalogFAILURE_MODES
Local devLOCAL_DEV_SETUP
Readiness gateSERVICE_READINESS
RisksSERVICE_RISK_REGISTER
Onboarding existing staffMIGRATION_PLAN