Files
unsupervised-scheduler/src/Offering/Offering.php
T
thatguygriff 36331388d1
CI / Coding Standards (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 1m0s
CI / Tests (PHP 8.1) (pull_request) Successful in 50s
CI / Tests (PHP 8.2) (pull_request) Successful in 46s
CI / Tests (PHP 8.3) (pull_request) Successful in 50s
CI / No Debug Code (pull_request) Successful in 2s
Add Offerings domain and studio-admin capabilities
Implements the offerings catalog (#1): private-lesson types and group
classes carrying pricing, billing mode (one_time/full_term), duration,
capacity, and term details. Adds the src/Offering/ domain (value object,
repository, REST endpoint, admin controller + template), the us_offerings
table, and an Offerings admin page.

Also lands the capability slice of #9: registers the us_studio_admin role
and the new capability strings (manage_instructors, manage_offerings,
manage_questions, manage_policies, manage_billing, view_all_payments,
view_own_payments, export_payments) so offering management gates correctly.

Tests: tests/Unit/Offering/ (value object + repository) and a studio-admin
case in RoleManagerTest. composer test, cs, and PHPStan level 6 all pass.

Refs #1 #9

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:33:02 -03:00

91 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Offering;
class Offering {
public const KIND_PRIVATE_LESSON = 'private_lesson';
public const KIND_GROUP_CLASS = 'group_class';
/**
* All valid offering kinds.
*
* @var list<string>
*/
public const VALID_KINDS = [ self::KIND_PRIVATE_LESSON, self::KIND_GROUP_CLASS ];
public const BILLING_ONE_TIME = 'one_time';
public const BILLING_FULL_TERM = 'full_term';
/**
* All valid billing modes.
*
* @var list<string>
*/
public const VALID_BILLING_MODES = [ self::BILLING_ONE_TIME, self::BILLING_FULL_TERM ];
public function __construct(
public readonly int $instructorId,
public readonly string $kind,
public readonly string $title,
public readonly int $priceCents = 0,
public readonly string $currency = 'CAD',
public readonly string $billingMode = self::BILLING_ONE_TIME,
public readonly ?string $description = null,
public readonly ?int $durationMinutes = null,
public readonly bool $allowWeekly = false,
public readonly ?int $capacity = null,
public readonly ?string $termStart = null,
public readonly ?string $termEnd = null,
public readonly ?string $scheduleNote = null,
public readonly bool $isActive = true,
public readonly ?int $id = null,
) {}
public static function fromRow( object $row ): self {
return new self(
instructorId: (int) $row->instructor_id,
kind: $row->kind,
title: $row->title,
priceCents: (int) $row->price_cents,
currency: $row->currency,
billingMode: $row->billing_mode,
description: $row->description,
durationMinutes: null !== $row->duration_minutes ? (int) $row->duration_minutes : null,
allowWeekly: (bool) $row->allow_weekly,
capacity: null !== $row->capacity ? (int) $row->capacity : null,
termStart: $row->term_start,
termEnd: $row->term_end,
scheduleNote: $row->schedule_note,
isActive: (bool) $row->is_active,
id: (int) $row->id,
);
}
/**
* Returns a plain array representation of the offering.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'instructor_id' => $this->instructorId,
'kind' => $this->kind,
'title' => $this->title,
'description' => $this->description,
'duration_minutes' => $this->durationMinutes,
'price_cents' => $this->priceCents,
'currency' => $this->currency,
'billing_mode' => $this->billingMode,
'allow_weekly' => $this->allowWeekly,
'capacity' => $this->capacity,
'term_start' => $this->termStart,
'term_end' => $this->termEnd,
'schedule_note' => $this->scheduleNote,
'is_active' => $this->isActive,
];
}
}