Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions backend/app/DomainObjects/Enums/Feature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace HiEvents\DomainObjects\Enums;

enum Feature: string
{
use BaseEnum;

case BRANDING_REMOVAL = 'branding_removal';
}
27 changes: 27 additions & 0 deletions backend/app/DomainObjects/EventDomainObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ class EventDomainObject extends Generated\EventDomainObjectAbstract implements I

private ?EventLocationDomainObject $eventLocation = null;

private ?bool $brandingRemoved = null;

public function setBrandingRemoved(?bool $brandingRemoved): self
{
$this->brandingRemoved = $brandingRemoved;

return $this;
}

public function getBrandingRemoved(): ?bool
{
return $this->brandingRemoved;
}

public static function getAllowedFilterFields(): array
{
return [
Expand Down Expand Up @@ -95,6 +109,19 @@ public function getProducts(): ?Collection
return $this->products;
}

public function hasPaidProducts(): bool
{
$isRevenueProducing = static fn (ProductDomainObject $product): bool => $product->isPaidType() || $product->isDonationType() || $product->isTieredType();

if ($this->products?->contains($isRevenueProducing)) {
return true;
}

return (bool) $this->productCategories
?->flatMap(fn (ProductCategoryDomainObject $category) => $category->getProducts() ?? collect())
->contains($isRevenueProducing);
}

public function setQuestions(?Collection $questions): EventDomainObject
{
$this->questions = $questions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ abstract class OrganizerConfigurationDomainObjectAbstract extends \HiEvents\Doma
final public const SINGULAR_NAME = 'organizer_configuration';
final public const PLURAL_NAME = 'organizer_configurations';
final public const ID = 'id';
final public const UPGRADES_TO_ID = 'upgrades_to_id';
final public const NAME = 'name';
final public const IS_SYSTEM_DEFAULT = 'is_system_default';
final public const APPLICATION_FEES = 'application_fees';
Expand All @@ -19,8 +20,10 @@ abstract class OrganizerConfigurationDomainObjectAbstract extends \HiEvents\Doma
final public const CREATED_AT = 'created_at';
final public const UPDATED_AT = 'updated_at';
final public const DELETED_AT = 'deleted_at';
final public const FEATURES = 'features';

protected int $id;
protected ?int $upgrades_to_id = null;
protected string $name;
protected bool $is_system_default = false;
protected array|string|null $application_fees = null;
Expand All @@ -29,11 +32,13 @@ abstract class OrganizerConfigurationDomainObjectAbstract extends \HiEvents\Doma
protected ?string $created_at = null;
protected ?string $updated_at = null;
protected ?string $deleted_at = null;
protected array|string|null $features = null;

public function toArray(): array
{
return [
'id' => $this->id ?? null,
'upgrades_to_id' => $this->upgrades_to_id ?? null,
'name' => $this->name ?? null,
'is_system_default' => $this->is_system_default ?? null,
'application_fees' => $this->application_fees ?? null,
Expand All @@ -42,6 +47,7 @@ public function toArray(): array
'created_at' => $this->created_at ?? null,
'updated_at' => $this->updated_at ?? null,
'deleted_at' => $this->deleted_at ?? null,
'features' => $this->features ?? null,
];
}

Expand All @@ -56,6 +62,17 @@ public function getId(): int
return $this->id;
}

public function setUpgradesToId(?int $upgrades_to_id): self
{
$this->upgrades_to_id = $upgrades_to_id;
return $this;
}

public function getUpgradesToId(): ?int
{
return $this->upgrades_to_id;
}

public function setName(string $name): self
{
$this->name = $name;
Expand Down Expand Up @@ -143,4 +160,15 @@ public function getDeletedAt(): ?string
{
return $this->deleted_at;
}

public function setFeatures(array|string|null $features): self
{
$this->features = $features;
return $this;
}

public function getFeatures(): array|string|null
{
return $this->features;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class OrganizerSettingDomainObjectAbstract extends \HiEvents\DomainObje
final public const DEFAULT_ALLOW_ATTENDEE_SELF_EDIT = 'default_allow_attendee_self_edit';
final public const TRACKING_PIXELS = 'tracking_pixels';
final public const TRACKING_CONSENT_ACKNOWLEDGED = 'tracking_consent_acknowledged';
final public const HIDE_BRANDING = 'hide_branding';

protected int $id;
protected int $organizer_id;
Expand All @@ -53,6 +54,7 @@ abstract class OrganizerSettingDomainObjectAbstract extends \HiEvents\DomainObje
protected bool $default_allow_attendee_self_edit = true;
protected array|string|null $tracking_pixels = null;
protected bool $tracking_consent_acknowledged = false;
protected bool $hide_branding = false;

public function toArray(): array
{
Expand All @@ -78,6 +80,7 @@ public function toArray(): array
'default_allow_attendee_self_edit' => $this->default_allow_attendee_self_edit ?? null,
'tracking_pixels' => $this->tracking_pixels ?? null,
'tracking_consent_acknowledged' => $this->tracking_consent_acknowledged ?? null,
'hide_branding' => $this->hide_branding ?? null,
];
}

Expand Down Expand Up @@ -312,4 +315,15 @@ public function getTrackingConsentAcknowledged(): bool
{
return $this->tracking_consent_acknowledged;
}

public function setHideBranding(bool $hide_branding): self
{
$this->hide_branding = $hide_branding;
return $this;
}

public function getHideBranding(): bool
{
return $this->hide_branding;
}
}
64 changes: 64 additions & 0 deletions backend/app/DomainObjects/OrganizerConfigurationDomainObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

namespace HiEvents\DomainObjects;

use HiEvents\DomainObjects\Enums\Feature;
use Illuminate\Support\Collection;

class OrganizerConfigurationDomainObject extends Generated\OrganizerConfigurationDomainObjectAbstract
{
private ?OrganizerConfigurationDomainObject $upgradesTo = null;

private ?Collection $downgradeOptions = null;

public function getFixedApplicationFee(): float
{
return $this->getApplicationFees()['fixed'] ?? config('app.default_application_fee_fixed');
Expand All @@ -18,4 +25,61 @@ public function getApplicationFeeCurrency(): string
{
return $this->getApplicationFees()['currency'] ?? 'USD';
}

public function hasFeature(Feature $feature): bool
{
return ($this->getFeaturesArray()[$feature->value] ?? false) === true;
}

public function getFeaturesArray(): array
{
$features = $this->getFeatures();

if (is_string($features)) {
return json_decode($features, true, 512, JSON_THROW_ON_ERROR) ?? [];
}

return $features ?? [];
}

public function setUpgradesTo(?OrganizerConfigurationDomainObject $upgradesTo): self
{
$this->upgradesTo = $upgradesTo;

return $this;
}

public function getUpgradesTo(): ?OrganizerConfigurationDomainObject
{
return $this->upgradesTo;
}

public function setDowngradeOptions(?Collection $downgradeOptions): self
{
$this->downgradeOptions = $downgradeOptions;

return $this;
}

public function getDowngradeOptions(): ?Collection
{
return $this->downgradeOptions;
}

public function getDowngradeTarget(): ?OrganizerConfigurationDomainObject
{
$options = $this->downgradeOptions;

if ($options === null || $options->isEmpty()) {
return null;
}

if ($options->count() === 1) {
return $options->first();
}

return $options->first(
fn (OrganizerConfigurationDomainObject $option) => $option->getIsSystemDefault()
);
}
}
7 changes: 7 additions & 0 deletions backend/app/Exceptions/BrandingRemovalNotAllowedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace HiEvents\Exceptions;

class BrandingRemovalNotAllowedException extends BaseException
{
}
5 changes: 5 additions & 0 deletions backend/app/Exceptions/PlanChangeNotAllowedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace HiEvents\Exceptions;

class PlanChangeNotAllowedException extends BaseException {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

namespace HiEvents\Http\Actions\Admin\Configurations;

use HiEvents\DomainObjects\Enums\Feature;
use HiEvents\DomainObjects\Enums\Role;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Repository\Interfaces\OrganizerConfigurationRepositoryInterface;
use HiEvents\Resources\Organizer\OrganizerConfigurationResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\Response;

class CreateConfigurationAction extends BaseAction
{
public function __construct(
private readonly OrganizerConfigurationRepositoryInterface $repository,
) {
}
) {}

public function __invoke(Request $request): JsonResponse
{
Expand All @@ -29,13 +30,22 @@ public function __invoke(Request $request): JsonResponse
'application_fees.fixed' => 'required|numeric|min:0',
'application_fees.percentage' => 'required|numeric|min:0|max:100',
'application_fees.currency' => 'sometimes|string|size:3|alpha|uppercase',
'features' => ['sometimes', 'nullable', 'array', self::validFeatureMap()],
'upgrades_to_id' => [
'sometimes',
'nullable',
'integer',
Rule::exists('organizer_configurations', 'id')->whereNull('deleted_at'),
],
'bypass_application_fees' => 'sometimes|boolean',
]);

$configuration = $this->repository->create([
'name' => $validated['name'],
'is_system_default' => false,
'application_fees' => $validated['application_fees'],
'features' => $validated['features'] ?? null,
'upgrades_to_id' => $validated['upgrades_to_id'] ?? null,
'bypass_application_fees' => $validated['bypass_application_fees'] ?? false,
]);

Expand All @@ -45,4 +55,21 @@ public function __invoke(Request $request): JsonResponse
wrapInData: true
);
}

public static function validFeatureMap(): callable
{
return static function (string $attribute, mixed $value, callable $fail) {
$knownFeatures = array_column(Feature::cases(), 'value');

foreach ((array) $value as $feature => $enabled) {
if (! in_array($feature, $knownFeatures, true)) {
$fail(__('Unknown feature: :feature', ['feature' => $feature]));
}

if (! is_bool($enabled)) {
$fail(__('Feature values must be true or false.'));
}
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Repository\Interfaces\OrganizerConfigurationRepositoryInterface;
use HiEvents\Resources\Organizer\OrganizerConfigurationResource;
use HiEvents\Services\Domain\Organizer\OrganizerPlanEntitlementService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class UpdateConfigurationAction extends BaseAction
{
public function __construct(
private readonly OrganizerConfigurationRepositoryInterface $repository,
) {
}
private readonly OrganizerPlanEntitlementService $entitlementService,
) {}

public function __invoke(Request $request, int $configurationId): JsonResponse
{
Expand All @@ -28,6 +30,14 @@ public function __invoke(Request $request, int $configurationId): JsonResponse
'application_fees.fixed' => 'required|numeric|min:0',
'application_fees.percentage' => 'required|numeric|min:0|max:100',
'application_fees.currency' => 'sometimes|string|size:3|alpha|uppercase',
'features' => ['sometimes', 'nullable', 'array', CreateConfigurationAction::validFeatureMap()],
'upgrades_to_id' => [
'sometimes',
'nullable',
'integer',
Rule::notIn([$configurationId]),
Rule::exists('organizer_configurations', 'id')->whereNull('deleted_at'),
],
'bypass_application_fees' => 'sometimes|boolean',
]);

Expand All @@ -36,10 +46,14 @@ public function __invoke(Request $request, int $configurationId): JsonResponse
attributes: [
'name' => $validated['name'],
'application_fees' => $validated['application_fees'],
'features' => $validated['features'] ?? null,
'upgrades_to_id' => $validated['upgrades_to_id'] ?? null,
'bypass_application_fees' => $validated['bypass_application_fees'] ?? false,
]
);

$this->entitlementService->syncAllOrganizersOnConfiguration($configuration);

return $this->jsonResponse(
new OrganizerConfigurationResource($configuration),
wrapInData: true
Expand Down
Loading
Loading