Skip to content

feat: implement Epic 5 — Manager Dashboard & Market-Day Oversight - #27

Merged
don-petry merged 3 commits into
epic1-implementationfrom
epic5-implementation
Mar 31, 2026
Merged

don-petry merged 3 commits into
epic1-implementationfrom
epic5-implementation

Conversation

@don-petry

Copy link
Copy Markdown
Contributor

Summary

  • Story 5.1: Live attendance dashboard — marketAttendance query returns roster + check-in status with summary counts
  • Story 5.2: Request vendor confirmation — requestVendorConfirmation mutation sends confirmation requests via event bus
  • Story 5.3: Manager check-in on behalf — checkIn mutation now accepts optional vendorID for manager-initiated check-ins with scope validation
  • Story 5.4: Market operational updates — publishMarketUpdate mutation + marketUpdates query with market_updates migration
  • Story 5.5: Auto-checkout at market close — autoCheckoutMarket mutation batch-checks-out all active vendors
  • 3 new domain events: MarketUpdatePublished, VendorConfirmationRequested, MarketAutoCheckoutCompleted
  • Helper functions extracted from resolvers to vendor_helpers.go (gqlgen compatibility)

Test plan

  • All existing tests continue to pass (go test ./...)
  • PublishMarketUpdate: success, non-manager forbidden, empty message validation
  • RequestVendorConfirmation: success, empty vendor IDs validation
  • AutoCheckoutMarket: success with count, zero when no active check-ins
  • MarketAttendance: returns correct counts (checked-in, pending, exception)
  • MarketUpdates: returns list
  • Manager check-in on behalf: scope-validated via checkManagerScope

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 29, 2026 19:59
@coderabbitai

coderabbitai Bot commented Mar 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ebd4f91-18d4-4c0e-a31b-a81cfafc1cf7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch epic5-implementation

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Epic 5 manager dashboard capabilities in the Go GraphQL backend, adding live attendance oversight, operational updates, confirmation requests, and auto-checkout workflows backed by new persistence and domain events.

Changes:

  • Added market_updates persistence (migration + market repository interface/model) and GraphQL query/mutation for publishing/listing updates.
  • Added manager-only attendance dashboard query (marketAttendance) plus supporting vendor repository methods for market/day check-ins.
  • Extended checkIn mutation to support manager-initiated check-in on behalf of a vendor; added new domain events and resolver tests.

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
markets-api/migrations/000015_create_market_updates.up.sql Creates market_updates table, index, and audit trigger
markets-api/migrations/000015_create_market_updates.down.sql Drops market_updates table
markets-api/internal/vendor/repository.go Adds vendor repo methods for market/day check-in reads and batch checkout
markets-api/internal/market/repository.go Adds market repo methods for market updates create/list
markets-api/internal/market/market.go Adds MarketUpdateRecord model
markets-api/internal/graph/vendor_helpers.go Moves/centralizes helper mappers used by resolvers
markets-api/internal/graph/vendor.resolvers.go Adds manager “check-in on behalf” logic
markets-api/internal/graph/vendor.resolvers_test.go Updates mocks to satisfy new vendor repo interface
markets-api/internal/graph/schema/vendor.graphqls Extends CheckInInput with optional vendorID
markets-api/internal/graph/schema/market.graphqls Adds MarketAttendance, MarketUpdate, and new query/mutation fields
markets-api/internal/graph/model/models_gen.go Regenerates gqlgen models for new schema fields/types
markets-api/internal/graph/market.resolvers.go Implements publishMarketUpdate, marketUpdates, marketAttendance, autoCheckoutMarket, requestVendorConfirmation
markets-api/internal/graph/market.resolvers_test.go Adds Epic 5 resolver tests and new vendor repo mock for those tests
markets-api/internal/graph/generated/generated.go Regenerates gqlgen executable schema and marshalling code
markets-api/internal/events/types.go Adds 3 new domain event types for Epic 5 workflows
_bmad-output/implementation-artifacts/sprint-status.yaml Updates sprint status tracking to mark Epic 5 done

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_market_updates_market_id ON market_updates(market_id);

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only index added is on market_id, but FindMarketUpdates is described as returning updates "newest first" and will likely query WHERE market_id = ... ORDER BY created_at DESC LIMIT/OFFSET. Consider adding a composite index on (market_id, created_at DESC) (or (market_id, created_at) depending on Postgres version) to avoid a sort on large markets.

Suggested change
CREATE INDEX idx_market_updates_market_id ON market_updates(market_id);
CREATE INDEX idx_market_updates_market_id_created_at_desc
ON market_updates (market_id, created_at DESC);

Copilot uses AI. Check for mistakes.
for _, re := range roster {
entry := &model.VendorAttendanceEntry{
VendorID: re.VendorID.String(),
Vendor: &model.Vendor{ID: re.VendorID.String()},

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MarketAttendance sets VendorAttendanceEntry.Vendor to &model.Vendor{ID: ...} only. As a result, clients selecting vendor fields like businessName, products, createdAt, etc. will get empty/default values instead of the real vendor profile. If the dashboard needs vendor details, consider populating the Vendor struct from the DB (likely via VendorRepo.FindVendorByUserID since roster entries use user IDs) or change VendorAttendanceEntry to expose only the vendor fields you can reliably provide without extra lookups.

Suggested change
Vendor: &model.Vendor{ID: re.VendorID.String()},

Copilot uses AI. Check for mistakes.
Comment on lines +242 to +257
if input.VendorID != nil && role == "manager" {
// Manager check-in on behalf (Story 5.3)
if err := r.checkManagerScope(ctx, domain.MarketID(input.MarketID)); err != nil {
return nil, err
}
v, err = r.VendorRepo.FindVendorByID(ctx, domain.VendorID(*input.VendorID))
if err != nil {
return nil, gqlerr.NewError(gqlerr.CodeValidationError, "vendor not found")
}
} else {
// Self check-in
v, err = r.VendorRepo.FindVendorByUserID(ctx, domain.UserID(uid))
if err != nil {
slog.Error("failed to find vendor", "error", err, "userID", uid)
return nil, gqlerr.Internal("vendor profile not found")
}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manager check-in behavior is ambiguous: if role == "manager" but input.VendorID is nil, the resolver falls back to "self check-in" and looks up a vendor profile by the manager’s userID, which will typically fail and surface as an internal error ("vendor profile not found"). Also, if a non-manager provides vendorID, it is silently ignored. Consider validating inputs explicitly: require vendorID when role is manager, and return a validation/forbidden error if vendorID is provided by a non-manager.

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +155
if ci.MarketID == marketID {
result = append(result, ci)
}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new mock FindCheckInsByMarketAndDate ignores the date parameter and returns all check-ins for the market. This can cause resolver tests (e.g., attendance counts) to pass even if the production implementation incorrectly fails to filter by date. Updating the mock to respect date will make the tests better reflect the repository contract.

Suggested change
if ci.MarketID == marketID {
result = append(result, ci)
}
if ci.MarketID != marketID {
continue
}
// If a date is provided, filter check-ins to that specific day (YYYY-MM-DD).
if date != "" {
if ci.CheckedInAt == nil || ci.CheckedInAt.Format("2006-01-02") != date {
continue
}
}
result = append(result, ci)

Copilot uses AI. Check for mistakes.
Comment on lines +839 to +844
func (m *mockVendorRepoForMarket) FindCheckInsByMarketAndDate(_ context.Context, marketID domain.MarketID, _ string) ([]*vendor.CheckInRecord, error) {
var result []*vendor.CheckInRecord
for _, ci := range m.checkIns {
if ci.MarketID == marketID {
result = append(result, ci)
}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new mock FindCheckInsByMarketAndDate ignores the date parameter and returns all check-ins for the market. This weakens the Epic 5 resolver tests because they won’t catch date-filtering bugs in attendance logic. Consider filtering by the check-in timestamp’s date (or using a test field) to honor the method contract.

Suggested change
func (m *mockVendorRepoForMarket) FindCheckInsByMarketAndDate(_ context.Context, marketID domain.MarketID, _ string) ([]*vendor.CheckInRecord, error) {
var result []*vendor.CheckInRecord
for _, ci := range m.checkIns {
if ci.MarketID == marketID {
result = append(result, ci)
}
func (m *mockVendorRepoForMarket) FindCheckInsByMarketAndDate(_ context.Context, marketID domain.MarketID, date string) ([]*vendor.CheckInRecord, error) {
var result []*vendor.CheckInRecord
// If a date is provided, parse it and filter check-ins by that calendar date.
var filterDate time.Time
var err error
if date != "" {
filterDate, err = time.Parse("2006-01-02", date)
if err != nil {
return nil, err
}
}
for _, ci := range m.checkIns {
if ci.MarketID != marketID {
continue
}
// If no date was specified, return all check-ins for the market (previous behavior).
if date == "" {
result = append(result, ci)
continue
}
// Match by calendar date of the check-in timestamp.
ciDate := ci.CheckedInAt
if ciDate.Year() == filterDate.Year() &&
ciDate.Month() == filterDate.Month() &&
ciDate.Day() == filterDate.Day() {
result = append(result, ci)
}

Copilot uses AI. Check for mistakes.
@don-petry
don-petry force-pushed the epic5-implementation branch from 67d9996 to f3cc4ba Compare March 29, 2026 20:06
@don-petry
don-petry force-pushed the epic4-implementation branch 2 times, most recently from a2243d3 to c5032c7 Compare March 29, 2026 21:03
@don-petry
don-petry force-pushed the epic5-implementation branch from f3cc4ba to 04342d2 Compare March 29, 2026 21:11
@don-petry
don-petry force-pushed the epic4-implementation branch from c5032c7 to 2b7f750 Compare March 29, 2026 22:37
@don-petry
don-petry force-pushed the epic5-implementation branch from 04342d2 to 958863d Compare March 29, 2026 22:37
@don-petry
don-petry force-pushed the epic4-implementation branch from 2b7f750 to b0e5ab7 Compare March 30, 2026 00:48
@don-petry
don-petry force-pushed the epic5-implementation branch from 958863d to 9ed73c9 Compare March 30, 2026 00:48
@don-petry
don-petry force-pushed the epic4-implementation branch from b0e5ab7 to 460e758 Compare March 30, 2026 01:00
@don-petry
don-petry force-pushed the epic5-implementation branch from 9ed73c9 to afb3307 Compare March 30, 2026 01:00
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

# Conflicts:
#	markets-api/internal/graph/vendor_helpers.go
@don-petry
don-petry force-pushed the epic4-implementation branch from 80dc7f7 to c18eaee Compare March 31, 2026 03:13
@don-petry
don-petry force-pushed the epic5-implementation branch from f70d598 to 80dc7f7 Compare March 31, 2026 03:13
Base automatically changed from epic4-implementation to epic1-implementation March 31, 2026 03:19
DJ and others added 2 commits March 30, 2026 20:23
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@don-petry
don-petry merged commit 6d56bc3 into epic1-implementation Mar 31, 2026
5 checks passed
@don-petry
don-petry deleted the epic5-implementation branch March 31, 2026 03:31
don-petry added a commit that referenced this pull request Apr 1, 2026
Merging Epic 5: Manager Dashboard & Market-Day Oversight

All CI checks pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants