feat(portal): lead module, WHMCS sync report/contacts/mapping, service lifecycle email templates

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-21 16:55:19 +07:00
co-authored by Claude Sonnet 5.5
parent 3c7710f1de
commit a443ec3bf6
66 changed files with 2032 additions and 124 deletions
@@ -0,0 +1,14 @@
<?php
use Database\Seeders\TemplateSeeder;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
(new TemplateSeeder())->seedServiceLifecycleTemplates();
}
public function down(): void {}
};
@@ -0,0 +1,16 @@
<?php
use App\Models\Template;
use Database\Seeders\TemplateSeeder;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Template::query()->where('name', 'like', '% — Dùng thử dịch vụ')->delete();
(new TemplateSeeder())->seedServiceLifecycleTemplates();
}
public function down(): void {}
};
@@ -0,0 +1,23 @@
<?php
use App\Models\Template;
use Database\Seeders\EmailTemplates\ServiceLifecycleTemplates;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
foreach (ServiceLifecycleTemplates::all() as $item) {
$template = Template::query()->where('name', $item['name'])->first();
if (!$template) {
continue;
}
$template->subject = $item['subject'];
$template->syncVariables($item['variables']);
$template->save();
}
}
public function down(): void {}
};
@@ -0,0 +1,18 @@
<?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::table('template', fn (Blueprint $table) => $table->string('subject', 255)->nullable()->change());
}
public function down(): void
{
Schema::table('template', fn (Blueprint $table) => $table->string('subject', 100)->nullable()->change());
}
};
@@ -0,0 +1,33 @@
<?php
use App\Models\Template;
use Database\Seeders\EmailTemplates\ServiceLifecycleTemplates;
use Database\Seeders\EmailTemplates\SubjectFormat;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
foreach (SubjectFormat::all() as $keyOrName => $subject) {
Template::query()
->where(fn ($query) => $query->where('scenario', $keyOrName)->orWhere('key', $keyOrName)->orWhere('name', $keyOrName))
->get()
->each(fn (Template $template) => $this->apply($template, $subject, []));
}
foreach (ServiceLifecycleTemplates::all() as $item) {
Template::query()->where('name', $item['name'])->get()
->each(fn (Template $template) => $this->apply($template, $item['subject'], $item['variables']));
}
}
public function down(): void {}
private function apply(Template $template, string $subject, array $variableOverrides): void
{
$template->subject = $subject;
$template->syncVariables($variableOverrides);
$template->save();
}
};
@@ -0,0 +1,26 @@
<?php
use App\Models\Template;
use App\Support\EmailTemplateShell;
use Database\Seeders\EmailTemplates\ServiceLifecycleTemplates;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$item = collect(ServiceLifecycleTemplates::all())->firstWhere('name', 'Tên miền — Cấp mới dịch vụ');
$template = Template::query()->where('name', $item['name'])->first();
if (!$template) {
return;
}
$template->subject = $item['subject'];
$template->message = EmailTemplateShell::page($item['body']);
$template->metadata = [...($template->metadata ?? []), 'variables' => []];
$template->syncVariables($item['variables']);
$template->save();
}
public function down(): void {}
};
@@ -0,0 +1,14 @@
<?php
use Database\Seeders\TemplateSeeder;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
(new TemplateSeeder())->seedServiceLifecycleTemplates();
}
public function down(): void {}
};
@@ -0,0 +1,21 @@
<?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
{
if (Schema::hasColumn('customers', 'saler')) {
return;
}
Schema::table('customers', function (Blueprint $table) {
$table->foreignId('saler')->nullable()->after('city')->constrained('user')->nullOnDelete();
});
}
public function down(): void {}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
return new class extends Migration
{
private const PERMISSIONS = ['lead.view', 'lead.create', 'lead.edit', 'lead.destroy', 'lead.manager'];
public function up(): void
{
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
foreach (self::PERMISSIONS as $name) {
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'admin']);
}
foreach (['Full Administrator', 'Administrator'] as $role) {
Role::where('name', $role)->where('guard_name', 'admin')->first()?->givePermissionTo(self::PERMISSIONS);
}
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
}
public function down(): void
{
Permission::where('guard_name', 'admin')->whereIn('name', self::PERMISSIONS)->delete();
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
}
};