Skip to content
Merged
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
1 change: 1 addition & 0 deletions backend/app/DomainObjects/Enums/OrganizerReportTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ enum OrganizerReportTypes: string
case EVENTS_PERFORMANCE = 'events_performance';
case TAX_SUMMARY = 'tax_summary';
case CHECK_IN_SUMMARY = 'check_in_summary';
case PLATFORM_FEES = 'platform_fees';
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ abstract class OrderItemDomainObjectAbstract extends \HiEvents\DomainObjects\Abs
final public const TOTAL_SERVICE_FEE = 'total_service_fee';
final public const TAXES_AND_FEES_ROLLUP = 'taxes_and_fees_rollup';
final public const PRODUCT_TYPE = 'product_type';
final public const BUNDLE_GROUP_ID = 'bundle_group_id';
final public const IS_BUNDLE_PRIMARY = 'is_bundle_primary';

protected int $id;
protected int $order_id;
Expand All @@ -43,8 +41,6 @@ abstract class OrderItemDomainObjectAbstract extends \HiEvents\DomainObjects\Abs
protected ?float $total_service_fee = 0.0;
protected array|string|null $taxes_and_fees_rollup = null;
protected string $product_type = 'TICKET';
protected ?string $bundle_group_id = null;
protected bool $is_bundle_primary = false;

public function toArray(): array
{
Expand All @@ -64,8 +60,6 @@ public function toArray(): array
'total_service_fee' => $this->total_service_fee ?? null,
'taxes_and_fees_rollup' => $this->taxes_and_fees_rollup ?? null,
'product_type' => $this->product_type ?? null,
'bundle_group_id' => $this->bundle_group_id ?? null,
'is_bundle_primary' => $this->is_bundle_primary ?? null,
];
}

Expand Down Expand Up @@ -233,26 +227,4 @@ public function getProductType(): string
{
return $this->product_type;
}

public function setBundleGroupId(?string $bundle_group_id): self
{
$this->bundle_group_id = $bundle_group_id;
return $this;
}

public function getBundleGroupId(): ?string
{
return $this->bundle_group_id;
}

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

public function getIsBundlePrimary(): bool
{
return $this->is_bundle_primary;
}
}
224 changes: 224 additions & 0 deletions backend/app/Http/Actions/Reports/ExportOrganizerReportAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
<?php

namespace HiEvents\Http\Actions\Reports;

use HiEvents\DomainObjects\Enums\OrganizerReportTypes;
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Http\Request\Report\GetOrganizerReportRequest;
use HiEvents\Services\Application\Handlers\Reports\DTO\GetOrganizerReportDTO;
use HiEvents\Services\Application\Handlers\Reports\GetOrganizerReportHandler;
use HiEvents\Services\Domain\Report\DTO\PaginatedReportDTO;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class ExportOrganizerReportAction extends BaseAction
{
private const MAX_EXPORT_ROWS = 15000;

public function __construct(private readonly GetOrganizerReportHandler $reportHandler)
{
}

/**
* @throws ValidationException
*/
public function __invoke(GetOrganizerReportRequest $request, int $organizerId, string $reportType): StreamedResponse
{
$this->isActionAuthorized($organizerId, OrganizerDomainObject::class);

$this->validateDateRange($request);

if (!in_array($reportType, OrganizerReportTypes::valuesArray(), true)) {
throw new BadRequestHttpException(__('Invalid report type.'));
}

$reportData = $this->reportHandler->handle(
reportData: new GetOrganizerReportDTO(
organizerId: $organizerId,
reportType: OrganizerReportTypes::from($reportType),
startDate: $request->validated('start_date'),
endDate: $request->validated('end_date'),
currency: $request->validated('currency'),
eventId: $request->validated('event_id'),
page: 1,
perPage: self::MAX_EXPORT_ROWS,
),
);

$data = $reportData instanceof PaginatedReportDTO
? $reportData->data
: $reportData;

$filename = $reportType . '_' . date('Y-m-d_H-i-s') . '.csv';

return new StreamedResponse(function () use ($data, $reportType) {
$handle = fopen('php://output', 'w');

$headers = $this->getHeadersForReportType($reportType);
fputcsv($handle, $headers);

foreach ($data as $row) {
$csvRow = $this->formatRowForReportType($row, $reportType);
fputcsv($handle, $csvRow);
}

fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0',
]);
}

private function getHeadersForReportType(string $reportType): array
{
return match ($reportType) {
OrganizerReportTypes::PLATFORM_FEES->value => [
'Event',
'Payment Date',
'Order Reference',
'Amount Paid',
'Hi.Events Fee',
'VAT Rate',
'VAT on Fee',
'Total Fee',
'Currency',
'Stripe Payment ID',
],
OrganizerReportTypes::REVENUE_SUMMARY->value => [
'Date',
'Gross Sales',
'Net Revenue',
'Total Refunded',
'Total Tax',
'Total Fee',
'Order Count',
],
OrganizerReportTypes::EVENTS_PERFORMANCE->value => [
'Event ID',
'Event Name',
'Currency',
'Start Date',
'End Date',
'Status',
'Event State',
'Products Sold',
'Gross Revenue',
'Total Refunded',
'Net Revenue',
'Total Tax',
'Total Fee',
'Total Orders',
'Unique Customers',
'Page Views',
],
OrganizerReportTypes::TAX_SUMMARY->value => [
'Event ID',
'Event Name',
'Currency',
'Tax Name',
'Tax Rate',
'Total Collected',
'Order Count',
],
OrganizerReportTypes::CHECK_IN_SUMMARY->value => [
'Event ID',
'Event Name',
'Start Date',
'Total Attendees',
'Total Checked In',
'Check-in Rate (%)',
'Check-in Lists Count',
],
default => [],
};
}

private function formatRowForReportType(object $row, string $reportType): array
{
return match ($reportType) {
OrganizerReportTypes::PLATFORM_FEES->value => [
$row->event_name ?? '',
$row->payment_date ? date('Y-m-d H:i:s', strtotime($row->payment_date)) : '',
$row->order_reference ?? '',
$row->amount_paid ?? 0,
$row->fee_amount ?? 0,
$row->vat_rate !== null ? ($row->vat_rate * 100) . '%' : '',
$row->vat_amount ?? 0,
$row->total_fee ?? 0,
$row->currency ?? '',
$row->payment_intent_id ?? '',
],
OrganizerReportTypes::REVENUE_SUMMARY->value => [
$row->date ?? '',
$row->gross_sales ?? 0,
$row->net_revenue ?? 0,
$row->total_refunded ?? 0,
$row->total_tax ?? 0,
$row->total_fee ?? 0,
$row->order_count ?? 0,
],
OrganizerReportTypes::EVENTS_PERFORMANCE->value => [
$row->event_id ?? '',
$row->event_name ?? '',
$row->event_currency ?? '',
$row->start_date ?? '',
$row->end_date ?? '',
$row->status ?? '',
$row->event_state ?? '',
$row->products_sold ?? 0,
$row->gross_revenue ?? 0,
$row->total_refunded ?? 0,
$row->net_revenue ?? 0,
$row->total_tax ?? 0,
$row->total_fee ?? 0,
$row->total_orders ?? 0,
$row->unique_customers ?? 0,
$row->page_views ?? 0,
],
OrganizerReportTypes::TAX_SUMMARY->value => [
$row->event_id ?? '',
$row->event_name ?? '',
$row->event_currency ?? '',
$row->tax_name ?? '',
$row->tax_rate ? ($row->tax_rate * 100) . '%' : '',
$row->total_collected ?? 0,
$row->order_count ?? 0,
],
OrganizerReportTypes::CHECK_IN_SUMMARY->value => [
$row->event_id ?? '',
$row->event_name ?? '',
$row->start_date ?? '',
$row->total_attendees ?? 0,
$row->total_checked_in ?? 0,
$row->check_in_rate ?? 0,
$row->check_in_lists_count ?? 0,
],
default => [],
};
}

/**
* @throws ValidationException
*/
private function validateDateRange(GetOrganizerReportRequest $request): void
{
$startDate = $request->validated('start_date');
$endDate = $request->validated('end_date');

if (!$startDate || !$endDate) {
return;
}

$diffInDays = Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate));

if ($diffInDays > 370) {
throw ValidationException::withMessages(['start_date' => __('Date range must be less than 370 days.')]);
}
}
}
10 changes: 10 additions & 0 deletions backend/app/Http/Actions/Reports/GetOrganizerReportAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use HiEvents\Http\Request\Report\GetOrganizerReportRequest;
use HiEvents\Services\Application\Handlers\Reports\DTO\GetOrganizerReportDTO;
use HiEvents\Services\Application\Handlers\Reports\GetOrganizerReportHandler;
use HiEvents\Services\Domain\Report\DTO\PaginatedReportDTO;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
Expand Down Expand Up @@ -39,9 +40,18 @@ public function __invoke(GetOrganizerReportRequest $request, int $organizerId, s
startDate: $request->validated('start_date'),
endDate: $request->validated('end_date'),
currency: $request->validated('currency'),
eventId: $request->validated('event_id'),
page: (int) $request->validated('page', 1),
perPage: (int) $request->validated('per_page', 1000),
),
);

if ($reportData instanceof PaginatedReportDTO) {
return $this->jsonResponse(
data: $reportData->toArray(),
);
}

return $this->jsonResponse(
data: $reportData,
wrapInData: true,
Expand Down
3 changes: 3 additions & 0 deletions backend/app/Http/Request/Report/GetOrganizerReportRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public function rules(): array
'start_date' => 'date|before:end_date|required_with:end_date|nullable',
'end_date' => 'date|after:start_date|required_with:start_date|nullable',
'currency' => 'string|size:3|nullable',
'event_id' => 'integer|nullable',
'page' => 'integer|min:1|nullable',
'per_page' => 'integer|min:1|max:1000|nullable',
];
}
}
2 changes: 1 addition & 1 deletion backend/app/Models/StripePayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class StripePayment extends BaseModel

protected function getTimestampsEnabled(): bool
{
return false;
return true;
}

protected function getCastMap(): array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public function __construct(
public readonly ?string $startDate,
public readonly ?string $endDate,
public readonly ?string $currency,
public readonly ?int $eventId = null,
public readonly int $page = 1,
public readonly int $perPage = 1000,
)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace HiEvents\Services\Application\Handlers\Reports;

use HiEvents\Services\Application\Handlers\Reports\DTO\GetOrganizerReportDTO;
use HiEvents\Services\Domain\Report\DTO\PaginatedReportDTO;
use HiEvents\Services\Domain\Report\Factory\OrganizerReportServiceFactory;
use HiEvents\Services\Domain\Report\OrganizerReports\PlatformFeesReport;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

Expand All @@ -15,15 +17,27 @@ public function __construct(
{
}

public function handle(GetOrganizerReportDTO $reportData): Collection
public function handle(GetOrganizerReportDTO $reportData): Collection|PaginatedReportDTO
{
return $this->reportServiceFactory
->create($reportData->reportType)
->generateReport(
$reportService = $this->reportServiceFactory->create($reportData->reportType);

if ($reportService instanceof PlatformFeesReport) {
return $reportService->generateReport(
organizerId: $reportData->organizerId,
currency: $reportData->currency,
startDate: $reportData->startDate ? Carbon::parse($reportData->startDate) : null,
endDate: $reportData->endDate ? Carbon::parse($reportData->endDate) : null,
eventId: $reportData->eventId,
page: $reportData->page,
perPage: $reportData->perPage,
);
}

return $reportService->generateReport(
organizerId: $reportData->organizerId,
currency: $reportData->currency,
startDate: $reportData->startDate ? Carbon::parse($reportData->startDate) : null,
endDate: $reportData->endDate ? Carbon::parse($reportData->endDate) : null,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function generateReport(
int $organizerId,
?string $currency = null,
?Carbon $startDate = null,
?Carbon $endDate = null
?Carbon $endDate = null,
): Collection
{
$organizer = $this->organizerRepository->findById($organizerId);
Expand Down
Loading
Loading