Skip to main content

staff-service

Catalog entry. Full bundle: services/staff-service/

Cluster: Workforce & Operations · Pattern: Conformist to iam-service and tenant-service; OHS publisher for housekeeping-service, maintenance-service, reservation-service.

1. Purpose

Owns operational HR for hospitality: who is employed, what they do, when they work, and whether they are on the floor right now. Provides the capacity signal that downstream task assignment (housekeeping, maintenance, front desk) depends on. Explicitly lightweight — payroll, performance reviews, complex compensation, and full HRIS are out of scope and will not be added without an ADR amendment.

It does not own:

  • User authentication, sessions, or password/MFA — that is iam-service.
  • User-tenant membership or platform role — that is tenant-service.
  • Property structure, room inventory, or operating hours — that is property-service.
  • Task assignment logic itself — housekeeping-service and maintenance-service consume our capacity events and decide.
  • Money. No payroll, no rates, no payslips. Period.

2. Bounded Context

PropertyValue
Bounded contextStaff & Shifts
Domain classSupporting
Strategic patternConformist to iam-service (User) and tenant-service (Membership); OHS publisher for assignment services
Ubiquitous languageStaff · Position · Department · Shift · Pattern · Assignment · Clock · Break · Leave · Certification · On-Call · Hand-off

3. Aggregates Owned

AggregatePrefixNotes
Staffstf_Hospitality-specific extension of iam.User; multi-property capable
Positionpos_Job role (front desk, housekeeper, GM, maintenance, F&B, security, …)
Departmentdpt_Org grouping per property; many positions per department
ShiftPatternshp_Recurring weekly/bi-weekly template (e.g., "Front Desk Morning")
Shiftshf_Concrete time window for a property/date with a position requirement
ShiftAssignmentsha_Many-to-many between Shift and Staff; supports primary/standby
ClockEntryclk_Append-only punch (in / out / break_start / break_end) with source attribution
LeaveRequestlvr_Sick / vacation / unpaid; light approval flow
StaffSkillskl_Language, equipment, soft skill — used by AI suggestion + housekeeping matching
StaffCertificationcrt_Food handling, first aid, fire safety; carries expiry → .expired.v1 on TTL
HandoffNotehno_Append-only between-shift notes scoped to a position/department

Holds + outbox + inbox are persistence-only and not aggregates.

4. Top Responsibilities

  1. Staff CRUD with hospitality attributes (position, languages, certifications, emergency contact, employment dates, status).
  2. Allow staff records without personal email (PIN-only clock-in, manager email as fallback).
  3. Define recurring shift patterns and generate concrete shifts for a date window per property.
  4. Manual + AI-suggested shift assignment with conflict detection (double-shift, leave collision, multi-property at-once).
  5. Clock in/out + break tracking from the front-desk Electron desktop (PIN-based) or via manager override on mobile.
  6. Light leave request + approval workflow.
  7. Publish capacity signals (housekeepers active now, technicians on duty) for downstream assigners.
  8. On-call/standby roster with promotion-to-active when a no-show is detected.
  9. Multi-property staff (same tenant) with at-most-one active shift across all properties.
  10. Hand-off notes between shifts (read by next position holder via Electron).
  11. Basic attendance reports (per staff, per period, per position).
  12. Emit staffing_gap_detected proactively when a shift starts within N minutes with no clocked-in primary.

5. Top REST Endpoints

VerbPathPurpose
POST/api/v1/staffCreate a staff record (with or without email)
GET/api/v1/staff?propertyId=…&position=…&status=activeList + filter staff for a property
POST/api/v1/shifts/generateGenerate concrete shifts from a pattern + window
POST/api/v1/shifts/{id}/assignmentsAssign staff to a shift (primary or standby)
POST/api/v1/clock/punchRecord clock in/out/break (PIN or JWT)
POST/api/v1/leave-requestsSubmit a leave request
POST/api/v1/leave-requests/{id}:approveApprove / reject (manager+)
GET/api/v1/capacity?propertyId=…&position=…&at=2026-04-22T15:00Active staff snapshot for downstream assigners

Full surface in API_CONTRACTS.md.

6. Top Events Published

SubjectWhen
melmastoon.staff.created.v1New staff record
melmastoon.staff.updated.v1Profile attributes mutate
melmastoon.staff.position_changed.v1Position re-assignment (audited)
melmastoon.staff.terminated.v1Employment ends; triggers IAM session revocation
melmastoon.staff.reactivated.v1Re-hire of terminated staff
melmastoon.staff.shift.scheduled.v1Concrete shift created from a pattern
melmastoon.staff.shift.assigned.v1Staff added to a shift
melmastoon.staff.shift.swapped.v1Two assignments swap staff atomically
melmastoon.staff.shift.cancelled.v1Shift removed before start
melmastoon.staff.shift.started.v1First primary clocks in
melmastoon.staff.shift.ended.v1Last primary clocks out (or auto-close at +N min)
melmastoon.staff.clock.in.v1 / .out.v1Punch persisted
melmastoon.staff.clock.break_started.v1 / .break_ended.v1Break punch persisted
melmastoon.staff.leave.requested.v1 / .approved.v1 / .rejected.v1 / .cancelled.v1Leave lifecycle
melmastoon.staff.certification.added.v1 / .expired.v1Cert added or TTL elapsed
melmastoon.staff.shift.staffing_gap_detected.v1Pre-shift gap warning
melmastoon.staff.handoff.note_added.v1New hand-off note

Full payloads in EVENT_SCHEMAS.md.

7. Top Events Consumed

SubjectProducerReaction
melmastoon.iam.user.registered.v1iam-serviceAuto-link to a pending Staff record if invitation accepted
melmastoon.tenant.membership.created.v1tenant-serviceProvision a default Staff shell if user_type=staff
melmastoon.tenant.membership.removed.v1tenant-serviceCascade-terminate any active staff for that user
melmastoon.ai.suggestion.shift_optimization.v1ai-orchestratorMaterialize advisory schedule; never auto-applied
melmastoon.property.deactivated.v1property-serviceCancel future shifts at the property

8. Storage

LayerEngineNotes
Primary DBCloud SQL PostgreSQL 16 (regional HA)Schema staff; tenant_id RLS on every table
CacheMemorystore Redis 7PIN attempt counter, current-capacity snapshot, idempotency index
Sync stateFirestorePer-device cursors (read by sync-service)
SecretsSecret ManagerPIN HMAC pepper
CryptoCloud KMS (regional)PIN HMAC, emergency-contact PII envelope key
EventsPub/SubTopics melmastoon.staff.*.v1, retention 7 d
Cold exportBigQueryAttendance facts, certification expiry funnel

9. Multi-Tenancy

  • Shared schema + RLS (per ADR-0002). Every table carries tenant_id and a USING (tenant_id = current_setting('app.tenant_id')) policy.
  • Multi-property within a tenant is a first-class concept: Staff has home_property_id plus a many-to-many staff_property_access table.
  • Staff at multiple properties simultaneously is forbidden by the clock.in invariant — see DOMAIN_MODEL §4 I-Clock-1.

10. Hotel-Specific Concerns

  • Informal staffing. A Staff record can be marked employment_type = 'temporary' | 'seasonal' | 'family_help'; onboarding reduces to ID + position + property + (optional) PIN.
  • No personal email. email is nullable; if absent, manager_email_for_notifications is required and a staff_code (human-readable, e.g., GM-DOH-FD-014) is auto-generated.
  • PIN clock-in via Electron. Front-desk desktop accepts a 6-digit PIN bound to staff_id + property_id; PIN is HMAC'd at rest, lockout after 5 failures, see SECURITY_MODEL §4.
  • Multilingual UI. All response titles are localized via Accept-Language; canonical labels for Position and Department ship with en, ps, fa, ar translations.
  • Offline clock-in. Electron queues punches locally and ships them via the sync push; conflict policy is append_only — see SYNC_CONTRACT §2.

11. Edge Cases (representative)

#ScenarioResolution
1Clock-in without a scheduled shiftAllowed with manager_override flag; emits both .clock.in.v1 and .shift.staffing_gap_detected.v1 for the period
2Double-shift conflict on same staff/propertyReject with MELMASTOON.STAFF.SHIFT_CONFLICT (HTTP 409)
3Leave request collides with already-assigned shiftApproval requires explicit forceUnassign=true; otherwise 409
4Two properties try to clock-in same staff at onceSecond punch rejected MELMASTOON.STAFF.MULTI_PROPERTY_ACTIVE
5Termination of an actively-clocked-in staffAuto-emit .clock.out.v1, then .terminated.v1; IAM session revoke fires
6Staff has no personal devicePIN clock-in only; manager email receives notifications
7Offline clock-in via Electron after >24 h offlinePunch accepted with source=offline_replay; out-of-window punches flagged
8Certification expires mid-shift.certification.expired.v1 published; shift continues; gap warning issued

Full deviation register in SERVICE_RISK_REGISTER.md.