Offer

English only

Concept

In Pluvo, the enrollment system is built around three connected entities:

Offer (the registration offering)
└── OfferEvent (a specific entry point, e.g. start date or cohort)
    ├── OfferEventDate (a scheduled session within the event)
    ├── OfferEventDate (another session)
    └── Enrollments (users registered for this event)

Offer is the overarching registration opportunity — think of it as a catalog entry. It has a title, description, and visibility settings, but users don't register for an Offer directly.

OfferEvent is what users actually enroll in. Each OfferEvent represents a specific instance — for example, "Starting Moment September" or "Online Cohort Q4". An OfferEvent has:

  • A type (MEETING, TRAINING, ELEARNING, COURSE, SCORM, or VIDEO)
  • Linked content — which trainings, courses, or videos the participant gets access to upon enrollment
  • Capacity settings — max participants, waiting list, closing date
  • Session dates — one or more OfferEventDate entries with start/end times

OfferEventDate represents a single scheduled session within an event — a workshop day, an online meeting, etc. Each date has a start time, end time, optional location, and optional trainers.

Example scenario

Offer: "Project Management Training"
├── OfferEvent: "Spring Cohort 2024" (type: TRAINING)
│   ├── Date: March 15, 09:00–17:00 (Workshop Day 1)
│   ├── Date: March 22, 09:00–17:00 (Workshop Day 2)
│   └── Linked content: Training "PM Fundamentals"
└── OfferEvent: "Fall Cohort 2024" (type: TRAINING)
    ├── Date: October 10, 09:00–17:00
    ├── Date: October 17, 09:00–17:00
    └── Linked content: Training "PM Fundamentals"

Simple vs. detailed offers: hasDetailPage

hasDetailPage: false (default)hasDetailPage: true
Intended forSimple offers with one eventOffers with multiple events/cohorts
UI behaviorShown as a single item in the catalog. Clicking opens the offer directly in a drawer.Shown with a detail page listing all events. Users pick an event to enroll in.
Event titlesEvent title is not prominently shown — the offer title is used.Each event has its own visible title and subtitle.

Creating a simple offer

mutation {
  createOffer(
    offer: {
      title: "Fire Safety E-learning"
      ref: "offer-fire-safety"
      hasDetailPage: false
    }
  ) {
    offer { id }
  }
}

Then add the event:

mutation {
  createOfferEvent(
    offerRef: "offer-fire-safety"
    offerEvent: {
      type: ELEARNING
      title: "Fire Safety E-learning"
      accessCourses: ["uuid-of-course"]
      publish: true
    }
  ) {
    offerEvent { id title }
  }
}

Creating a detailed offer

mutation {
  createOffer(
    offer: {
      title: "Project Management Program"
      description: "Choose your preferred starting moment."
      ref: "offer-pm"
      hasDetailPage: true
    }
  ) {
    offer { id }
  }
}

Use Cases

Get single offer

query {
  offer(id: "uuid-here") {
    id
    title
    ref
    description
    isActive
    offerEvents {
      id
      title
      type
      enrolledCount
      spotsAvailable
      dates {
        startDate
        endDate
      }
    }
  }
}

List all offers

query {
  offers {
    id
    title
    ref
    isActive
  }
}

Create offer with event and dates

Step 1: Create the offer

mutation {
  createOffer(
    offer: {
      title: "Project Management Training"
      description: "Register for our PM training program."
      ref: "offer-pm-2024"
    }
  ) {
    offer {
      id
      title
      ref
    }
  }
}

Step 2: Add an event with dates

mutation {
  createOfferEvent(
    offerRef: "offer-pm-2024"
    offerEvent: {
      type: TRAINING
      title: "Spring Cohort 2024"
      ref: "pm-spring-2024"
      accessTrainings: ["uuid-of-training"]
      maxSubscriptions: 20
      waitingList: true
      dates: [
        {
          startDate: "2024-03-15T09:00:00Z"
          endDate: "2024-03-15T17:00:00Z"
        },
        {
          startDate: "2024-03-22T09:00:00Z"
          endDate: "2024-03-22T17:00:00Z"
        }
      ]
    }
  ) {
    offerEvent {
      id
      title
      ref
      dates { id startDate endDate }
    }
  }
}

Step 3: Enroll users (see Enrollments page)

mutation {
  createOfferEventEnrollments(
    offerEventRef: "pm-spring-2024"
    userRefs: ["EMP-001", "EMP-002"]
    sendMail: true
  ) {
    id
    user { name }
    status
  }
}

Update offer

mutation {
  updateOffer(
    ref: "offer-pm-2024"
    offer: {
      title: "Updated title"
    }
  ) {
    offer { id title }
  }
}

Delete offer

Unlike most entities, offers can be permanently deleted (not just archived).
mutation {
  deleteOffer(id: "uuid-here") {
    ok
  }
}

GraphQL Definitions

Type: Offer

type Offer {
  id: ID!
  ref: String
  title: String!
  description: String
  isActive: Boolean
  archivedAt: DateTime
  createdAt: DateTime
  updatedAt: DateTime
  offerEvents: [OfferEvent!]
  type: OfferType
  hasDetailPage: Boolean
}

Type: OfferEvent

type OfferEvent {
  id: ID!
  ref: String
  title: String
  type: OfferEventType!
  dates: [OfferEventDate!]
  maxSubscriptions: Int
  minSubscriptions: Int
  enrolledCount: Int
  spotsAvailable: Int
  waitingList: Boolean
  closingDate: DateTime
  price: Decimal
  cancelled: Boolean
  publish: Boolean
  trainers: [User!]
  accessTrainings: [Training!]
  accessCourses: [Course!]
  certificateId: UUID
  scoreThreshold: Float
}

Type: OfferEventDate

type OfferEventDate {
  id: ID!
  ref: String
  startDate: DateTime!
  endDate: DateTime!
  location: String
  webinarLink: String
  trainers: [User!]
}

Enum: OfferEventType

enum OfferEventType {
  MEETING
  TRAINING
  ELEARNING
  COURSE
  SCORM
  VIDEO
}

Queries

offer(id: UUID, ref: String): Offer
offers(q: String, types: [OfferType!]): [Offer!]!

Mutations

createOffer(offer: CreateOfferInput!): CreateOffer
updateOffer(id: UUID, ref: String, offer: UpdateOfferInput!): UpdateOffer
deleteOffer(id: UUID!): DeleteOffer
copyOffer(id: UUID!): CopyOffer

createOfferEvent(offerId: UUID, offerRef: String, offerEvent: CreateOfferEventInput!): CreateOfferEvent
updateOfferEvent(id: UUID, ref: String, offerEvent: UpdateOfferEventInput!): UpdateOfferEvent
deleteOfferEvent(id: UUID!): DeleteOfferEvent
copyOfferEvent(id: UUID!): CopyOfferEvent

Notes

  • An Offer is not directly registrable — enrollment happens through OfferEvents.
  • OfferEvent.type is required when creating — it determines the kind of content.
  • Content is linked via accessTrainings, accessCourses, etc.
  • Session dates (OfferEventDate) are created inline via the dates field on createOfferEvent.
  • Both id and ref work for identification in all queries and mutations.
  • Use copyOffer to duplicate an offer with all its events — useful for recurring programs.
  • deleteOffer and deleteOfferEvent permanently remove the entity.
Sluit melding