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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class () extends Migration {
public function up(): void
{
Schema::create('report_templates', function (Blueprint $table): void {
$table->id();
$table->unsignedBigInteger('company_id');
$table->string('name');
$table->string('slug');
$table->text('description')->nullable();
$table->string('template_type');
$table->boolean('is_system')->default(false);
$table->boolean('is_active')->default(true);
$table->timestamps();

$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->unique(['company_id', 'slug']);
});
}

public function down(): void
{
Schema::dropIfExists('report_templates');
}
};
61 changes: 61 additions & 0 deletions Modules/ReportBuilder/Models/ReportTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Modules\ReportBuilder\Models;

use Illuminate\Database\Eloquent\Model;
use Modules\Core\Traits\BelongsToCompany;

/**
* @property int $id
* @property int $company_id
* @property string $name
* @property string $slug
* @property string|null $description
* @property string $template_type
* @property bool $is_system
* @property bool $is_active
*/
class ReportTemplate extends Model
{
use BelongsToCompany;

protected $fillable = [
'company_id',
'name',
'slug',
'description',
'template_type',
'is_system',
'is_active',
];

protected $casts = [
'is_system' => 'boolean',
'is_active' => 'boolean',
'template_type' => 'string',
];

/**
* Check if the template can be cloned.
*/
public function isCloneable(): bool
{
return $this->is_active;
}

/**
* Check if the template is a system template.
*/
public function isSystem(): bool
{
return $this->is_system;
}

/**
* Get the file path for the template.
*/
public function getFilePath(): string
{
return "{$this->company_id}/{$this->slug}.json";
}
}
222 changes: 222 additions & 0 deletions Modules/ReportBuilder/Tests/Unit/ReportTemplateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?php

namespace Modules\ReportBuilder\Tests\Unit;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Modules\Core\Models\Company;
use Modules\ReportBuilder\Models\ReportTemplate;
use Modules\ReportBuilder\Tests\TestCase;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;

class ReportTemplateTest extends TestCase
{
use RefreshDatabase;

private Company $company;

protected function setUp(): void
{
parent::setUp();

$this->company = Company::factory()->create(['name' => 'Test Company']);
}

#[Test]
#[Group('unit')]
public function it_can_create_a_report_template(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Professional Invoice',
'slug' => 'professional_invoice',
'description' => 'A professional invoice template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->assertDatabaseHas('report_templates', [
'company_id' => $this->company->id,
'name' => 'Professional Invoice',
'slug' => 'professional_invoice',
'template_type' => 'invoice',
]);

$this->assertEquals('Professional Invoice', $template->name);
$this->assertEquals('professional_invoice', $template->slug);
$this->assertFalse($template->is_system);
$this->assertTrue($template->is_active);
}

#[Test]
#[Group('unit')]
public function it_casts_boolean_fields_correctly(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'System Template',
'slug' => 'system_template',
'template_type' => 'report',
'is_system' => 1,
'is_active' => 0,
]);

$this->assertTrue($template->is_system);
$this->assertFalse($template->is_active);
$this->assertIsBool($template->is_system);
$this->assertIsBool($template->is_active);
}

#[Test]
#[Group('unit')]
public function it_belongs_to_a_company(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Test Template',
'slug' => 'test_template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->assertInstanceOf(Company::class, $template->company);
$this->assertEquals($this->company->id, $template->company->id);
}

#[Test]
#[Group('unit')]
public function is_cloneable_returns_true_when_active(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Active Template',
'slug' => 'active_template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->assertTrue($template->isCloneable());
}

#[Test]
#[Group('unit')]
public function is_cloneable_returns_false_when_inactive(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Inactive Template',
'slug' => 'inactive_template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => false,
]);

$this->assertFalse($template->isCloneable());
}

#[Test]
#[Group('unit')]
public function is_system_returns_true_for_system_templates(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'System Template',
'slug' => 'system_template',
'template_type' => 'invoice',
'is_system' => true,
'is_active' => true,
]);

$this->assertTrue($template->isSystem());
}

#[Test]
#[Group('unit')]
public function is_system_returns_false_for_user_templates(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'User Template',
'slug' => 'user_template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->assertFalse($template->isSystem());
}

#[Test]
#[Group('unit')]
public function get_file_path_returns_correct_path(): void
{
$template = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Test Template',
'slug' => 'test_template',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$expectedPath = "{$this->company->id}/test_template.json";
$this->assertEquals($expectedPath, $template->getFilePath());
}

#[Test]
#[Group('unit')]
public function slug_must_be_unique_within_company(): void
{
ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Template 1',
'slug' => 'unique_slug',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->expectException(\Illuminate\Database\QueryException::class);

ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Template 2',
'slug' => 'unique_slug',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);
}

#[Test]
#[Group('unit')]
public function same_slug_can_exist_in_different_companies(): void
{
$company2 = Company::factory()->create(['name' => 'Company 2']);

$template1 = ReportTemplate::create([
'company_id' => $this->company->id,
'name' => 'Template 1',
'slug' => 'shared_slug',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$template2 = ReportTemplate::create([
'company_id' => $company2->id,
'name' => 'Template 2',
'slug' => 'shared_slug',
'template_type' => 'invoice',
'is_system' => false,
'is_active' => true,
]);

$this->assertEquals('shared_slug', $template1->slug);
$this->assertEquals('shared_slug', $template2->slug);
$this->assertNotEquals($template1->company_id, $template2->company_id);
}
}