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
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,8 @@ No DTO layer — services accept arrays and return Eloquent models.
- `Str::lower($company->search_code)` is always the URL tenant parameter
- The three tenant middleware classes live at `Modules/Core/Http/Middleware/`
- Panel providers live at `Modules/Core/Providers/`, not `app/Providers/`

# LESSONS

- Never write `*/` inside a PHP docblock (e.g. glob patterns like `Header*/Detail*`) — it terminates the comment and causes a parse error.
- When running a test suite in the background, redirect FULL output to a file — never pipe through `tail`/`head`, it destroys the failure details and forces a rerun.
47 changes: 47 additions & 0 deletions Modules/Core/Console/ReportsSyncSystemCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Modules\Core\Console;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Modules\Core\Services\ReportTemplateStorage;

/**
* Copies the shipped report templates from resources/report-templates into
* the system scope of the report_templates disk. Idempotent — running it
* again simply rewrites the system copies with the shipped versions.
*/
class ReportsSyncSystemCommand extends Command
{
protected $signature = 'reports:sync-system';

protected $description = 'Sync shipped report templates into the system template storage';

public function handle(): int
{
$source = resource_path('report-templates');

if ( ! File::isDirectory($source)) {
$this->error("Source directory [{$source}] does not exist.");

return self::FAILURE;
}

$disk = Storage::disk(ReportTemplateStorage::DISK);
$synced = 0;

foreach (File::allFiles($source) as $file) {
$disk->put(
ReportTemplateStorage::SCOPE_SYSTEM . '/' . str_replace('\\', '/', $file->getRelativePathname()),
$file->getContents(),
);

$synced++;
}

$this->info("Synced {$synced} report template file(s) into system storage.");

return self::SUCCESS;
}
}
14 changes: 14 additions & 0 deletions Modules/Core/Enums/ReportBand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ enum ReportBand: string
case GROUP_HEADER = 'group_header';
case HEADER = 'header';

/**
* Get all bands in document order (header first, footer last).
*
* @return array<self>
*/
public static function ordered(): array
{
$bands = self::cases();

usort($bands, fn (self $a, self $b): int => $a->getOrder() <=> $b->getOrder());

return $bands;
}

/**
* Get the display label for the band.
*/
Expand Down
24 changes: 24 additions & 0 deletions Modules/Core/Enums/ReportBlockWidth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Modules\Core\Enums;

enum ReportBlockWidth: string
{
case ONE_THIRD = 'one_third';
case HALF = 'half';
case TWO_THIRDS = 'two_thirds';
case FULL = 'full';

/**
* Get the grid width for this block width (in 12-column grid system).
*/
public function getGridWidth(): int
{
return match ($this) {
self::ONE_THIRD => 4,
self::HALF => 6,
self::TWO_THIRDS => 8,
self::FULL => 12,
};
}
}
32 changes: 32 additions & 0 deletions Modules/Core/Enums/ReportTemplateType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Modules\Core\Enums;

use Modules\Core\Contracts\LabeledEnum;

enum ReportTemplateType: string implements LabeledEnum
{
case INVOICE = 'invoice';
case QUOTE = 'quote';

public static function values(): array
{
return array_column(self::cases(), 'value');
}

public function label(): string
{
return match ($this) {
self::INVOICE => trans('ip.invoice'),
self::QUOTE => trans('ip.quote'),
};
}

public function color(): string
{
return match ($this) {
self::INVOICE => 'success',
self::QUOTE => 'info',
};
}
}
18 changes: 18 additions & 0 deletions Modules/Core/Filament/Admin/Pages/ReportBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Modules\Core\Filament\Admin\Pages;

use Modules\Core\Filament\Pages\Reports\BaseReportBuilderPage;

class ReportBuilder extends BaseReportBuilderPage
{
public function managesSystemScope(): bool
{
return true;
}

public function listPage(): string
{
return ReportTemplates::class;
}
}
18 changes: 18 additions & 0 deletions Modules/Core/Filament/Admin/Pages/ReportTemplates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Modules\Core\Filament\Admin\Pages;

use Modules\Core\Filament\Pages\Reports\BaseReportTemplatesPage;

class ReportTemplates extends BaseReportTemplatesPage
{
public function managesSystemScope(): bool
{
return true;
}

public function builderPage(): string
{
return ReportBuilder::class;
}
}
27 changes: 27 additions & 0 deletions Modules/Core/Filament/Company/Pages/ReportBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Modules\Core\Filament\Company\Pages;

use Modules\Core\Enums\UserRole;
use Modules\Core\Filament\Pages\Reports\BaseReportBuilderPage;

class ReportBuilder extends BaseReportBuilderPage
{
public static function canAccess(): bool
{
return auth()->user()?->hasAnyRole([
...UserRole::elevated(),
UserRole::CUSTOMER_ADMIN->value,
]) ?? false;
}

public function managesSystemScope(): bool
{
return false;
}

public function listPage(): string
{
return ReportTemplates::class;
}
}
27 changes: 27 additions & 0 deletions Modules/Core/Filament/Company/Pages/ReportTemplates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Modules\Core\Filament\Company\Pages;

use Modules\Core\Enums\UserRole;
use Modules\Core\Filament\Pages\Reports\BaseReportTemplatesPage;

class ReportTemplates extends BaseReportTemplatesPage
{
public static function canAccess(): bool
{
return auth()->user()?->hasAnyRole([
...UserRole::elevated(),
UserRole::CUSTOMER_ADMIN->value,
]) ?? false;
}

public function managesSystemScope(): bool
{
return false;
}

public function builderPage(): string
{
return ReportBuilder::class;
}
}
Loading
Loading