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
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/shifts/preferences/me | ShiftController.getMyPreferences | Get my shift preferences |
| POST | /stores/:store_id/shifts/preferences | ShiftController.createPreference | Create preference |
| PUT | /stores/:store_id/shifts/preferences/:id | ShiftController.updatePreference | Update preference |
| DELETE | /stores/:store_id/shifts/preferences/:id | ShiftController.deletePreference | Delete preference |
| GET | /stores/:store_id/shifts/assigned | ShiftController.listAssigned | List assigned shifts |
| GET | /stores/:store_id/shifts/assigned/me | ShiftController.getMyAssigned | Get my assigned shifts |
| GET | /stores/:store_id/shifts/status | ShiftController.getStatus | Get current shift status |
| POST | /stores/:store_id/shifts/clock-in | ShiftController.clockIn | Clock in |
| POST | /stores/:store_id/shifts/clock-out | ShiftController.clockOut | Clock out |
| GET | /stores/:store_id/shifts/requests | ShiftController.listRequests | List shift requests |
| POST | /stores/:store_id/shifts/requests | ShiftController.createRequest | Create shift request |
Shifts — Store Manager+
| Method | Path | Handler | Description |
|---|---|---|---|
| POST | /stores/:store_id/shifts/assigned | ShiftController.createAssigned | Assign a shift |
| PUT | /stores/:store_id/shifts/assigned/:id | ShiftController.updateAssigned | Update assignment |
| DELETE | /stores/:store_id/shifts/assigned/:id | ShiftController.deleteAssigned | Delete assignment |
| POST | /stores/:store_id/shifts/requests/:id/review | ShiftController.reviewRequest | Approve/deny request |
Compliance — All Store Members
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/compliance/checks | ComplianceController.getChecks | List recent checks |
| GET | /stores/:store_id/compliance/checks/:check_id | ComplianceController.get | Get check detail |
| GET | /stores/:store_id/compliance/summary | ComplianceController.getSummary | Compliance stats |
| POST | /stores/:store_id/compliance/verify-age | ComplianceController.verifyAge | Perform age check |
Compliance — Store Manager+
| Method | Path | Handler | Description |
|---|---|---|---|
| POST | /stores/:store_id/compliance/override | ComplianceController.override | Override failed check |
Compliance — Store Admin
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/compliance/list | ComplianceController.listAll | List all compliance records |
Tax Config — All Store Members (Read)
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/tax-configs | TaxConfigController.list | List tax configs |
| GET | /stores/:store_id/tax-configs/active | TaxConfigController.listActive | List active configs |
| GET | /stores/:store_id/tax-configs/:config_id | TaxConfigController.get | Get single config |
Tax Config — Store Admin (Write)
| Method | Path | Handler | Description |
|---|---|---|---|
| POST | /stores/:store_id/tax-configs | TaxConfigController.create | Create tax config |
| PUT | /stores/:store_id/tax-configs/:config_id | TaxConfigController.update | Update tax config |
| DELETE | /stores/:store_id/tax-configs/:config_id | TaxConfigController.delete | Delete tax config |
Terminals — Store Admin
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/terminals | TerminalController.list | List terminals |
| GET | /stores/:store_id/terminals/:id | TerminalController.get | Get terminal |
| POST | /stores/:store_id/terminals | TerminalController.create | Create terminal |
| PUT | /stores/:store_id/terminals/:id | TerminalController.update | Update terminal |
| DELETE | /stores/:store_id/terminals/:id | TerminalController.delete | Delete terminal |
| POST | /stores/:store_id/terminals/:id/regenerate-secret | TerminalController.regenerateSecret | Regenerate terminal secret |
Terminals — All Store Members
| Method | Path | Handler | Description |
|---|---|---|---|
| POST | /stores/:store_id/terminals/:id/heartbeat | SettingsController.heartbeat | Terminal heartbeat (updates last_seen) |
Settings & Onboarding — All Store Members (Read)
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /stores/:store_id/settings | SettingsController.getSettings | Get store operational settings |
| GET | /stores/:store_id/onboarding/status | OnboardingController.getStatus | Get onboarding status |
Settings & Onboarding — Store Admin (Write)
| Method | Path | Handler | Description |
|---|---|---|---|
| PUT | /stores/:store_id/settings | SettingsController.updateSettings | Update store operational settings |
| PUT | /stores/:store_id/settings/timezone | SettingsController.updateTimezone | Update timezone |
| PUT | /stores/:store_id/settings/operating-hours | SettingsController.updateOperatingHours | Update hours |
| POST | /stores/:store_id/settings/complete-onboarding | SettingsController.completeOnboarding | Mark onboarding done |
| PUT | /stores/:store_id/onboarding/phase | OnboardingController.updatePhase | Update onboarding phase |
SQLDelight Tables & Queries
shift_preferences
findByStoreIdAndUserId(store_id, user_id)- My preferencesinsert(...),update(...),delete(preference_id)
shifts_assigned
findByStoreId(store_id, limit, offset)- All assignedfindByStoreIdAndUserId(store_id, user_id)- My assignedinsert(...),update(...),delete(assigned_id)
shifts_actual
findActiveByStoreIdAndUserId(store_id, user_id)- Current clock-inclockIn(...)- Insert with clock_in timestampclockOut(actual_id, clock_out, break_minutes)- Update clock_out
shift_requests
findByStoreId(store_id, limit, offset)- All requestsinsert(...)- Create requestreview(status, reviewed_by, reviewed_at, request_id)- Approve/deny
compliance_checks
findByStoreId(store_id, limit, offset)- List checksfindById(check_id)- Single checkgetSummary(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 configsfindActiveByStoreId(store_id)- Active onlyfindById(tax_config_id)- Single configinsert(...),update(...),delete(tax_config_id)
terminals
findByStoreId(store_id)- All terminalsfindById(terminal_id)- Single terminalinsert(...),update(...),delete(terminal_id)updateLastSeen(terminal_id)- HeartbeatupdateSecret(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
- Preferences are per-user, per-day-of-week — employees indicate when they prefer to work
- Assignments are created by managers — the actual schedule
- Clock-in creates a
shifts_actualrecord; clock-out updates it - A user can only have one active clock-in at a time (no clock_out yet)
- Shift requests (time-off, swap) are approved/denied by managers
break_minutesis recorded at clock-out time
Compliance
- Age verification checks are logged for every age-restricted sale
- Result is
passorfailbased onverified_age >= required_age - Failed checks can be overridden by managers with a reason
- Summary provides pass/fail/override rates for a date range
Tax Config
- Multiple tax configs per store (e.g., state tax, county tax, category-specific)
applies_to_categorylinks to a product category (nullable = applies to all)- Active configs are applied at transaction time
Terminals
- Terminal secrets are generated on creation (SecureRandom)
- Regenerating a secret invalidates the old one
- Heartbeat updates
last_seentimestamp — used to detect offline terminals - Terminals are per-store
Settings
- Store settings are fields on the
storestable — no separate settings table - Individual update endpoints (timezone, operating-hours) for granular updates
- Complete-onboarding sets
onboarding_completed = true
Acceptance Criteria
- Full shift lifecycle works: preference -> assignment -> clock-in -> clock-out
- Shift request creation and manager review (approve/deny) works
- Age verification creates compliance check records with pass/fail
- Compliance override records the override reason and manager
- Tax config CRUD works with category-specific configs
- Terminal CRUD works with secret generation and regeneration
- Terminal heartbeat updates
last_seen - Store settings CRUD works (general + individual endpoints)
- Onboarding phase tracking works
- All list endpoints support pagination
- Bazel build passes
- Unit tests for ShiftService, ComplianceService, TaxConfigService, TerminalService