Skip to main content

Phase 6: Store Operations

Depends on: Phase 2 (Core Entities & Membership) New Endpoints: 40 New Files: 40


Goal

Implement store operational features: shift management (preferences, assignments, clock-in/out, requests), compliance checks (age verification), tax configuration, terminal management, store operational settings, and onboarding. This is the largest phase by file count, covering the day-to-day operational needs of a convenience store.


Endpoints

Shifts — All Store Members

MethodPathHandlerDescription
GET/stores/:store_id/shifts/preferences/meShiftController.getMyPreferencesGet my shift preferences
POST/stores/:store_id/shifts/preferencesShiftController.createPreferenceCreate preference
PUT/stores/:store_id/shifts/preferences/:idShiftController.updatePreferenceUpdate preference
DELETE/stores/:store_id/shifts/preferences/:idShiftController.deletePreferenceDelete preference
GET/stores/:store_id/shifts/assignedShiftController.listAssignedList assigned shifts
GET/stores/:store_id/shifts/assigned/meShiftController.getMyAssignedGet my assigned shifts
GET/stores/:store_id/shifts/statusShiftController.getStatusGet current shift status
POST/stores/:store_id/shifts/clock-inShiftController.clockInClock in
POST/stores/:store_id/shifts/clock-outShiftController.clockOutClock out
GET/stores/:store_id/shifts/requestsShiftController.listRequestsList shift requests
POST/stores/:store_id/shifts/requestsShiftController.createRequestCreate shift request

Shifts — Store Manager+

MethodPathHandlerDescription
POST/stores/:store_id/shifts/assignedShiftController.createAssignedAssign a shift
PUT/stores/:store_id/shifts/assigned/:idShiftController.updateAssignedUpdate assignment
DELETE/stores/:store_id/shifts/assigned/:idShiftController.deleteAssignedDelete assignment
POST/stores/:store_id/shifts/requests/:id/reviewShiftController.reviewRequestApprove/deny request

Compliance — All Store Members

MethodPathHandlerDescription
GET/stores/:store_id/compliance/checksComplianceController.getChecksList recent checks
GET/stores/:store_id/compliance/checks/:check_idComplianceController.getGet check detail
GET/stores/:store_id/compliance/summaryComplianceController.getSummaryCompliance stats
POST/stores/:store_id/compliance/verify-ageComplianceController.verifyAgePerform age check

Compliance — Store Manager+

MethodPathHandlerDescription
POST/stores/:store_id/compliance/overrideComplianceController.overrideOverride failed check

Compliance — Store Admin

MethodPathHandlerDescription
GET/stores/:store_id/compliance/listComplianceController.listAllList all compliance records

Tax Config — All Store Members (Read)

MethodPathHandlerDescription
GET/stores/:store_id/tax-configsTaxConfigController.listList tax configs
GET/stores/:store_id/tax-configs/activeTaxConfigController.listActiveList active configs
GET/stores/:store_id/tax-configs/:config_idTaxConfigController.getGet single config

Tax Config — Store Admin (Write)

MethodPathHandlerDescription
POST/stores/:store_id/tax-configsTaxConfigController.createCreate tax config
PUT/stores/:store_id/tax-configs/:config_idTaxConfigController.updateUpdate tax config
DELETE/stores/:store_id/tax-configs/:config_idTaxConfigController.deleteDelete tax config

Terminals — Store Admin

MethodPathHandlerDescription
GET/stores/:store_id/terminalsTerminalController.listList terminals
GET/stores/:store_id/terminals/:idTerminalController.getGet terminal
POST/stores/:store_id/terminalsTerminalController.createCreate terminal
PUT/stores/:store_id/terminals/:idTerminalController.updateUpdate terminal
DELETE/stores/:store_id/terminals/:idTerminalController.deleteDelete terminal
POST/stores/:store_id/terminals/:id/regenerate-secretTerminalController.regenerateSecretRegenerate terminal secret

Terminals — All Store Members

MethodPathHandlerDescription
POST/stores/:store_id/terminals/:id/heartbeatSettingsController.heartbeatTerminal heartbeat (updates last_seen)

Settings & Onboarding — All Store Members (Read)

MethodPathHandlerDescription
GET/stores/:store_id/settingsSettingsController.getSettingsGet store operational settings
GET/stores/:store_id/onboarding/statusOnboardingController.getStatusGet onboarding status

Settings & Onboarding — Store Admin (Write)

MethodPathHandlerDescription
PUT/stores/:store_id/settingsSettingsController.updateSettingsUpdate store operational settings
PUT/stores/:store_id/settings/timezoneSettingsController.updateTimezoneUpdate timezone
PUT/stores/:store_id/settings/operating-hoursSettingsController.updateOperatingHoursUpdate hours
POST/stores/:store_id/settings/complete-onboardingSettingsController.completeOnboardingMark onboarding done
PUT/stores/:store_id/onboarding/phaseOnboardingController.updatePhaseUpdate onboarding phase

SQLDelight Tables & Queries

shift_preferences

  • findByStoreIdAndUserId(store_id, user_id) - My preferences
  • insert(...), update(...), delete(preference_id)

shifts_assigned

  • findByStoreId(store_id, limit, offset) - All assigned
  • findByStoreIdAndUserId(store_id, user_id) - My assigned
  • insert(...), update(...), delete(assigned_id)

shifts_actual

  • findActiveByStoreIdAndUserId(store_id, user_id) - Current clock-in
  • clockIn(...) - Insert with clock_in timestamp
  • clockOut(actual_id, clock_out, break_minutes) - Update clock_out

shift_requests

  • findByStoreId(store_id, limit, offset) - All requests
  • insert(...) - Create request
  • review(status, reviewed_by, reviewed_at, request_id) - Approve/deny

compliance_checks

  • findByStoreId(store_id, limit, offset) - List checks
  • findById(check_id) - Single check
  • getSummary(store_id, start_date, end_date) - Aggregates (total, pass, fail, override)
  • insert(...) - Create check (age verification result)
  • override(override_reason, override_by, check_id) - Override failed check

tax_configs (gateway-authored tax configs)

  • findByStoreId(store_id) - All configs
  • findActiveByStoreId(store_id) - Active only
  • findById(tax_config_id) - Single config
  • insert(...), update(...), delete(tax_config_id)

terminals

  • findByStoreId(store_id) - All terminals
  • findById(terminal_id) - Single terminal
  • insert(...), update(...), delete(terminal_id)
  • updateLastSeen(terminal_id) - Heartbeat
  • updateSecret(terminal_secret, terminal_id) - Regenerate

Files to Create

Controllers (6)

controller/store/ShiftController.java        (15 endpoints)
controller/store/ComplianceController.java (6 endpoints)
controller/store/TaxConfigController.java (6 endpoints)
controller/store/TerminalController.java (6 endpoints)
controller/store/SettingsController.java (6 endpoints)
controller/store/OnboardingController.java (3 endpoints)

Services (6)

service/ShiftService.java
service/ComplianceService.java
service/TaxConfigService.java
service/TerminalService.java
service/SettingsService.java
service/OnboardingService.java

Repositories (4)

repository/ShiftRepository.java        -- wraps all 4 shift tables' queries
repository/ComplianceRepository.java
repository/TaxConfigRepository.java
repository/TerminalRepository.java

Request DTOs (11)

dto/request/CreateShiftPreferenceRequest.java
{ dayOfWeek: @Min(0) @Max(6) int, startTime: @NotBlank String,
endTime: @NotBlank String, priority: int }

dto/request/UpdateShiftPreferenceRequest.java
(same as create)

dto/request/CreateShiftAssignedRequest.java
{ userId: @NotNull UUID, shiftDate: @NotBlank String,
startTime: @NotBlank String, endTime: @NotBlank String, notes: String }

dto/request/UpdateShiftAssignedRequest.java
(same as create)

dto/request/CreateShiftRequestRequest.java
{ requestedDate: @NotBlank String, startTime: @NotBlank String,
endTime: @NotBlank String, notes: String }

dto/request/ReviewShiftRequestRequest.java
{ status: @NotBlank @Pattern("approved|denied") String }

dto/request/VerifyAgeRequest.java
{ productId: @NotNull UUID, checkType: @NotBlank String,
idType: String, idDob: String, idExpiry: String,
verifiedAge: Integer, requiredAge: @NotNull int }

dto/request/ComplianceOverrideRequest.java
{ checkId: @NotNull UUID, reason: @NotBlank String }

dto/request/CreateTaxConfigRequest.java
{ label: String, jurisdictionType: String, jurisdictionCode: String,
jurisdictionName: String, rateType: String, rateBps: Integer,
rateAmountMinor: Long, taxBasis: String, appliesToCategory: UUID,
priority: Integer, effectiveFrom: String, effectiveTo: String,
active: boolean }

dto/request/UpdateTaxConfigRequest.java
(same as create)

dto/request/CreateTerminalRequest.java
{ terminalName: @NotBlank String, isActive: boolean }

dto/request/UpdateTerminalRequest.java
{ terminalName: @NotBlank String, isActive: boolean }

dto/request/UpdateStoreSettingsRequest.java
{ name: @NotBlank String, addressLine1: String, addressLine2: String,
city: String, state: String, zipCode: String, phone: String,
currency: @NotBlank String }

dto/request/UpdateTaxRateRequest.java
{ taxRate: @NotNull BigDecimal }

dto/request/UpdateTimezoneRequest.java
{ timezone: @NotBlank String }

dto/request/UpdateOperatingHoursRequest.java
{ openingTime: @NotBlank String, closingTime: @NotBlank String }

dto/request/UpdateOnboardingPhaseRequest.java
{ phase: @NotBlank String, complete: boolean }

Response DTOs (9)

dto/response/ShiftPreferenceResponse.java
dto/response/ShiftAssignedResponse.java
dto/response/ShiftStatusResponse.java -- current clock-in status
dto/response/ShiftRequestResponse.java
dto/response/ComplianceCheckResponse.java
dto/response/ComplianceSummaryResponse.java -- { totalChecks, passes, fails, overrides }
dto/response/TaxConfigResponse.java
dto/response/TerminalResponse.java
dto/response/StoreSettingsResponse.java
dto/response/OnboardingStatusResponse.java

Mappers (4)

mapper/ShiftMapper.java
mapper/ComplianceMapper.java
mapper/TaxConfigMapper.java
mapper/TerminalMapper.java

Business Rules

Shifts

  1. Preferences are per-user, per-day-of-week — employees indicate when they prefer to work
  2. Assignments are created by managers — the actual schedule
  3. Clock-in creates a shifts_actual record; clock-out updates it
  4. A user can only have one active clock-in at a time (no clock_out yet)
  5. Shift requests (time-off, swap) are approved/denied by managers
  6. break_minutes is recorded at clock-out time

Compliance

  1. Age verification checks are logged for every age-restricted sale
  2. Result is pass or fail based on verified_age >= required_age
  3. Failed checks can be overridden by managers with a reason
  4. Summary provides pass/fail/override rates for a date range

Tax Config

  1. Multiple tax configs per store (e.g., state tax, county tax, category-specific)
  2. applies_to_category links to a product category (nullable = applies to all)
  3. Active configs are applied at transaction time

Terminals

  1. Terminal secrets are generated on creation (SecureRandom)
  2. Regenerating a secret invalidates the old one
  3. Heartbeat updates last_seen timestamp — used to detect offline terminals
  4. Terminals are per-store

Settings

  1. Store settings are fields on the stores table — no separate settings table
  2. Individual update endpoints (timezone, operating-hours) for granular updates
  3. Complete-onboarding sets onboarding_completed = true

Acceptance Criteria

  1. Full shift lifecycle works: preference -> assignment -> clock-in -> clock-out
  2. Shift request creation and manager review (approve/deny) works
  3. Age verification creates compliance check records with pass/fail
  4. Compliance override records the override reason and manager
  5. Tax config CRUD works with category-specific configs
  6. Terminal CRUD works with secret generation and regeneration
  7. Terminal heartbeat updates last_seen
  8. Store settings CRUD works (general + individual endpoints)
  9. Onboarding phase tracking works
  10. All list endpoints support pagination
  11. Bazel build passes
  12. Unit tests for ShiftService, ComplianceService, TaxConfigService, TerminalService