update email template
This commit is contained in:
@@ -14,8 +14,7 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::create('user', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('firstname', 255)->nullable();
|
||||
$table->string('lastname', 255)->nullable();
|
||||
$table->string('fullname', 255)->nullable();
|
||||
$table->string('username', 100)->nullable();
|
||||
$table->string('email', 100)->nullable();
|
||||
$table->date('join_date')->nullable();
|
||||
|
||||
@@ -16,7 +16,8 @@ return new class extends Migration {
|
||||
Schema::create('customers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('fullname', 100)->nullable();
|
||||
$table->string('fullname', 255)->nullable();
|
||||
$table->foreignId('saler')->nullable()->constrained('user')->nullOnDelete();
|
||||
$table->string('avatar', 100)->nullable();
|
||||
$table->integer('gender')->default(0);
|
||||
$table->string('address', 100)->nullable();
|
||||
@@ -41,7 +42,7 @@ return new class extends Migration {
|
||||
$table->string('province_code', 20)->nullable();
|
||||
$table->string('ward_code', 20)->nullable();
|
||||
$table->string('remember_token', 100)->nullable();
|
||||
$table->tinyInteger('status')->default(1);
|
||||
$table->tinyInteger('status')->default(0)->comment('1: Active, 0: InActive');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('customers', function (Blueprint $table) {
|
||||
//
|
||||
$table->integer('unpaid_total')->default(0)->comment('Số nợ còn thiếu')->after('total');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('customers', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
// WHMCS addresses (address1 + address2 combined) routinely exceed the old varchar(100).
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// ponytail: raw SQL instead of Blueprint::change() to avoid adding doctrine/dbal just for this
|
||||
DB::statement('ALTER TABLE `customers` MODIFY `address` TEXT NULL');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement('ALTER TABLE `customers` MODIFY `address` VARCHAR(100) NULL');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
// Lets whmcs-style domain sync upsert idempotently against the admin_zonedns source
|
||||
// (dns_manager_servers/domain/ns), the same pattern already used for orders/invoices.
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('dns_manager_servers', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
|
||||
Schema::table('dns_manager_domain', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
|
||||
Schema::table('dns_manager_ns', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('dns_manager_servers', fn (Blueprint $t) => $t->dropColumn('legacy_id'));
|
||||
Schema::table('dns_manager_domain', fn (Blueprint $t) => $t->dropColumn('legacy_id'));
|
||||
Schema::table('dns_manager_ns', fn (Blueprint $t) => $t->dropColumn('legacy_id'));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
// Lets whmcs addon sync upsert idempotently. product_categories/products already got legacy_id
|
||||
// from earlier migrations (2026_06_15_000001_add_legacy_id_to_sync_tables and a partial run of this file).
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumn('product_categories', 'legacy_id')) {
|
||||
Schema::table('product_categories', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('products', 'legacy_id')) {
|
||||
Schema::table('products', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('product_addon', 'legacy_id')) {
|
||||
Schema::table('product_addon', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('product_addon', fn (Blueprint $t) => $t->dropColumn('legacy_id'));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('registrars', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('short_name');
|
||||
$table->string('status')->default('active');
|
||||
$table->string('website')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
DB::table('registrars')->insert([
|
||||
['name' => 'ESC', 'short_name' => 'esc', 'status' => 'active', 'website' => null, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'Nhân Hòa', 'short_name' => 'nhanhoa', 'status' => 'active', 'website' => 'https://nhanhoa.com', 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'PA', 'short_name' => 'PA', 'status' => 'active', 'website' => 'https://pavietnam.vn', 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'TenTen', 'short_name' => 'TENTEN', 'status' => 'active', 'website' => 'https://tenten.vn', 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'TinoDomain', 'short_name' => 'tinodomain', 'status' => 'active', 'website' => 'https://tino.org', 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'VDC', 'short_name' => 'vdc', 'status' => 'active', 'website' => 'https://vdc.com.vn', 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => 'Z.Com', 'short_name' => 'zcom', 'status' => 'active', 'website' => 'https://z.com', 'created_at' => now(), 'updated_at' => now()],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('registrars');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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('module_domain', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('legacy_id')->nullable()->unique()->after('id');
|
||||
$table->foreignId('registrar_id')->nullable()->after('domain_type_id')
|
||||
->constrained('registrars')->nullOnDelete();
|
||||
$table->text('note')->nullable()->after('status');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('module_domain', function (Blueprint $table) {
|
||||
$table->dropForeign(['registrar_id']);
|
||||
$table->dropColumn(['legacy_id', 'registrar_id', 'note']);
|
||||
});
|
||||
}
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?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('dns_manager_domain', function (Blueprint $table) {
|
||||
$table->foreignId('registrar_id')->nullable()->after('plesk_id')
|
||||
->constrained('registrars')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('dns_manager_domain', function (Blueprint $table) {
|
||||
$table->dropForeign(['registrar_id']);
|
||||
$table->dropColumn('registrar_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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('invoices', function (Blueprint $table) {
|
||||
$table->date('billing_period_start')->nullable()->after('due_date');
|
||||
$table->date('billing_period_end')->nullable()->after('billing_period_start');
|
||||
|
||||
// Prevents two renewal invoices being created for the same order in the same
|
||||
// billing period (e.g. concurrent cron run + customer self-renew request).
|
||||
// NULL values (non-renewal invoices) are not constrained by MySQL unique indexes.
|
||||
$table->unique(['order_id', 'invoice_type', 'billing_period_start'], 'invoices_order_type_period_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->dropUnique('invoices_order_type_period_unique');
|
||||
$table->dropColumn(['billing_period_start', 'billing_period_end']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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('transactions', function (Blueprint $table) {
|
||||
// Prevents crediting/recording the same reference twice (retried webhook,
|
||||
// concurrent bank-statement poll, retried invoice payment recordTransaction()).
|
||||
$table->unique(['customer_id', 'trans', 'type'], 'transactions_customer_trans_type_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('transactions', function (Blueprint $table) {
|
||||
$table->dropUnique('transactions_customer_trans_type_unique');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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('orders', function (Blueprint $table) {
|
||||
// Mirrors invoice.paid_at for the order's most recently paid invoice, so the
|
||||
// order stays in sync with payment state without joining to invoices to show it.
|
||||
$table->timestamp('paid_at')->nullable()->after('payment_method');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table) {
|
||||
$table->dropColumn('paid_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?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('product_categories', function (Blueprint $table) {
|
||||
// Which billing_cycle values (one_time, monthly, quarterly, semi_annually,
|
||||
// annually, biennially, triennially) are valid for products in this category —
|
||||
// e.g. domain registration is annual-only, IT support can be one_time only,
|
||||
// hosting/VPS are typically monthly..annually. Drives cycle pickers in the UI.
|
||||
$table->json('billing_cycles')->nullable()->after('type');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('product_categories', function (Blueprint $table) {
|
||||
$table->dropColumn('billing_cycles');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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', function (Blueprint $table) {
|
||||
$table->json('metadata')->nullable()->after('format')
|
||||
->comment('Danh sách variable hợp lệ (metadata.variables[]) + mail config (metadata.mail)');
|
||||
$table->json('builder_data')->nullable()->after('metadata')
|
||||
->comment('Dữ liệu project của Email Builder để mở lại chỉnh sửa');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('template', function (Blueprint $table) {
|
||||
$table->dropColumn(['metadata', 'builder_data']);
|
||||
});
|
||||
}
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const BACKFILL = [
|
||||
'Domain Order Approved' => [
|
||||
'scenario' => 'domain_order_approved',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'client_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
],
|
||||
],
|
||||
'Domain Order Rejected' => [
|
||||
'scenario' => 'domain_order_rejected',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'client_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'reason', 'label' => 'Lý do từ chối', 'type' => 'string', 'example' => 'Tên miền đã có chủ sở hữu'],
|
||||
],
|
||||
],
|
||||
'Order Created' => [
|
||||
'scenario' => 'order_created',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'client_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'order_total', 'label' => 'Tổng tiền', 'type' => 'string', 'example' => '990.000 đ'],
|
||||
['key' => 'invoice_num', 'label' => 'Mã hoá đơn', 'type' => 'string', 'example' => 'INV001'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $name => $config) {
|
||||
DB::table('template')
|
||||
->where('name', $name)
|
||||
->whereNull('scenario')
|
||||
->update([
|
||||
'scenario' => $config['scenario'],
|
||||
'metadata' => json_encode(['variables' => $config['variables']]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $name => $config) {
|
||||
DB::table('template')
|
||||
->where('name', $name)
|
||||
->where('scenario', $config['scenario'])
|
||||
->update(['scenario' => null, 'metadata' => null]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private function cardStyle(string $gradientFrom, string $gradientTo, string $headerTextColor = '#fde3d3'): string
|
||||
{
|
||||
return <<<CSS
|
||||
* { box-sizing: border-box; }
|
||||
.wrapper { max-width: 620px; margin: 30px auto; font-family: 'Segoe UI', Arial, sans-serif; }
|
||||
.card { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.08); }
|
||||
.header { background: linear-gradient(135deg, {$gradientFrom} 0%, {$gradientTo} 100%); padding: 34px 40px; text-align: center; }
|
||||
.header .icon { font-size: 44px; margin-bottom: 10px; }
|
||||
.header h1 { color: #fff; margin: 0 0 6px; font-size: 22px; font-weight: 700; }
|
||||
.header p { color: {$headerTextColor}; margin: 0; font-size: 14px; }
|
||||
.body { padding: 34px 40px; }
|
||||
.section-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: #9ca3af; margin: 24px 0 10px; }
|
||||
.section-label:first-child { margin-top: 0; }
|
||||
.info-table { width: 100%; border-collapse: collapse; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; }
|
||||
.info-table td { padding: 11px 16px; font-size: 14px; border-bottom: 1px solid #f3f4f6; }
|
||||
.info-table tr:last-child td { border-bottom: none; }
|
||||
.info-table td:first-child { color: #6b7280; width: 42%; }
|
||||
.info-table td:last-child { font-weight: 600; color: #111827; word-break: break-word; }
|
||||
.badge { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; background: #fff7ed; color: #c2410c; }
|
||||
.badge-green { background: #dcfce7; color: #15803d; }
|
||||
.notice { background: #fffbeb; border-left: 3px solid #f59e0b; padding: 12px 16px; border-radius: 0 6px 6px 0; font-size: 13px; color: #92400e; margin-top: 24px; line-height: 1.6; }
|
||||
.footer { text-align: center; padding: 20px 40px; border-top: 1px solid #f3f4f6; font-size: 12px; color: #9ca3af; }
|
||||
CSS;
|
||||
}
|
||||
|
||||
private function header(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<tr class="Header"><td style="width: 700px;" valign="top"><table align="center" width="700" bgcolor="#ffffff" style="border-collapse: collapse; width: 700px; max-width: 100%; text-align: center; background-color: #ffffff; border-top: 5px solid #f16723;" border="0" cellspacing="0" cellpadding="0"><tbody><tr><td style="padding: 0;"><a href="https://tgs.com.vn" style="display: block;" target="_blank" rel="noopener"><img src="https://res.cloudinary.com/izpast9i/image/upload/v1786499308/email_header.png" alt="Thư gửi từ Công Ty Cổ Phần Giải Pháp Công Nghệ Thế Giới Số" style="width: 100%; display: block; border: none;" width="700"></a></td></tr></tbody></table></td></tr>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private function footer(): string
|
||||
{
|
||||
return <<<'HTML'
|
||||
<tr class="Footer"><td align="center" valign="top"><table border="0" cellpadding="0" cellspacing="0" width="700" align="center" background="https://res.cloudinary.com/izpast9i/image/upload/v1786500298/email_footer.png" bgcolor="#F16723" style="border-collapse: collapse; width: 700px; background-image: url("https://res.cloudinary.com/izpast9i/image/upload/v1786500298/email_footer.png"); background-size: cover; background-position: center; background-repeat: no-repeat;"><tbody><tr><td style="padding: 40px 30px 20px 30px; text-align: center;"><strong style="font-size: 14px; line-height: 22px; color: #ffffff;">CÔNG TY CỔ PHẦN GIẢI PHÁP CÔNG NGHỆ THẾ GIỚI SỐ</strong></td></tr><tr><td style="padding: 6px 30px 16px 30px; text-align: center;"><span style="font-size: 12px; line-height: 18px; color: #ffffff;">Tel: (028) 7309.7379 - Email: <a href="mailto:support@tgs.com.vn" style="color: #ffffff; text-decoration: none;">support@tgs.com.vn</a></span></td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table>
|
||||
HTML;
|
||||
}
|
||||
|
||||
private function wrap(string $bodyHtml): string
|
||||
{
|
||||
return '<table style="border-collapse: collapse; width: 100%; background-color: #efefef;" border="0" cellspacing="0" cellpadding="0"><tbody><tr><td style="padding: 20px;" align="center" valign="top"><table style="padding-top: 20px; padding-bottom: 20px; border-collapse: collapse; background-color: #ffffff; width: 700px;" border="0" cellspacing="0" cellpadding="0"><tbody>'
|
||||
. $this->header()
|
||||
. '<tr class="NoiDungKM"><td style="width: 700px; padding: 0; background-color: #ffffff;" valign="top">' . $bodyHtml . '</td></tr>'
|
||||
. $this->footer();
|
||||
}
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$rows = [
|
||||
[
|
||||
'name' => '[Admin] Đơn hàng mới',
|
||||
'scenario' => 'admin_order_notification',
|
||||
'subject' => '[ADMIN] Đơn hàng mới #{$order_num} — {$customer_email}',
|
||||
'variables' => [
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'client_ip_row', 'label' => '[Tự động] Dòng IP đặt hàng', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm', 'type' => 'string', 'example' => 'Cloud VPS Pro'],
|
||||
['key' => 'product_type', 'label' => 'Loại dịch vụ', 'type' => 'string', 'example' => 'VPS'],
|
||||
['key' => 'billing_cycle', 'label' => 'Chu kỳ thanh toán', 'type' => 'string', 'example' => 'Hàng tháng'],
|
||||
['key' => 'total_formatted', 'label' => 'Tổng tiền', 'type' => 'string', 'example' => '990.000 VNĐ'],
|
||||
['key' => 'vm_block', 'label' => '[Tự động] Khối thông tin VPS', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#1e40af', '#1d4ed8', '#bfdbfe') . '.vm-box { background: #f0fdf4; border: 1.5px solid #86efac; border-radius: 10px; padding: 20px 24px; margin-top: 10px; } .vm-row { display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid #dcfce7; } .vm-row:last-child { border-bottom: none; } .vm-label { color: #15803d; font-size: 13px; width: 130px; flex-shrink: 0; } .vm-value { font-family: "Courier New", monospace; font-size: 14px; font-weight: 700; color: #111827; word-break: break-all; }</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">📦</div><h1>Đơn hàng mới đã thanh toán</h1><p>Đơn #{$order_num} — {$created_at}</p></div><div class="body"><div class="section-label">Thông tin khách hàng</div><table class="info-table"><tr><td>Tên khách hàng</td><td>{$customer_name}</td></tr><tr><td>Email</td><td>{$customer_email}</td></tr>{$client_ip_row}</table><div class="section-label">Thông tin đơn hàng</div><table class="info-table"><tr><td>Mã đơn hàng</td><td>#{$order_num}</td></tr><tr><td>Sản phẩm</td><td>{$product_name}</td></tr><tr><td>Loại dịch vụ</td><td>{$product_type}</td></tr><tr><td>Chu kỳ thanh toán</td><td>{$billing_cycle}</td></tr><tr><td>Tổng tiền</td><td>{$total_formatted}</td></tr><tr><td>Trạng thái</td><td><span class="badge badge-green">✅ Đã thanh toán</span></td></tr></table>{$vm_block}</div><div class="footer">© ' . date('Y') . ' {$company_name} — Hệ thống quản lý dịch vụ<br>Email này được gửi tự động. Vui lòng không trả lời.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => '[Admin] Đơn đăng ký tên miền mới',
|
||||
'scenario' => 'admin_domain_order_new',
|
||||
'subject' => '[ADMIN] Đơn đăng ký tên miền mới - {$domain_name}',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'order_total', 'label' => 'Tổng tiền', 'type' => 'string', 'example' => '350.000 VNĐ'],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
['key' => 'admin_url', 'label' => 'Link xem đơn (admin)', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/admin/orders'],
|
||||
],
|
||||
'message' => $this->wrap('<div style="padding: 25px;"><p>Có một đơn đăng ký tên miền mới vừa được đặt tại <strong>{$company_name}</strong>.</p><p><strong>Thông tin đơn hàng:</strong></p><ul><li>Tên miền: <strong>{$domain_name}</strong></li><li>Mã đơn hàng: <strong>{$order_num}</strong></li><li>Tổng tiền: <strong>{$order_total}</strong></li><li>Khách hàng: <strong>{$customer_name}</strong></li><li>Email: <strong>{$customer_email}</strong></li><li>Thời gian: <strong>{$created_at}</strong></li></ul><table cellpadding="0" cellspacing="0" role="presentation" style="margin: 16px auto;"><tbody><tr><td style="border-radius: 4px; background-color: #f16723; text-align: center;"><a href="{$admin_url}" target="_blank" rel="noopener" style="font-family: sans-serif; display: block; font-size: 16px; font-weight: 600; line-height: 100%; color: #ffffff; text-decoration: none; padding: 16px 24px;">XEM ĐƠN HÀNG</a></td></tr></tbody></table><p style="text-align: justify; color: #666666; font-size: 13px;">Lưu ý: Đây là tin nhắn tự động. Vui lòng không trả lời tin nhắn này!</p></div>'),
|
||||
],
|
||||
[
|
||||
'name' => '[Admin] Hosting account mới',
|
||||
'scenario' => 'admin_hosting_account_created',
|
||||
'subject' => '[ADMIN] Hosting account mới — {$domain_name}',
|
||||
'variables' => [
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'hosting_plan', 'label' => 'Gói hosting', 'type' => 'string', 'example' => 'Hosting Pro'],
|
||||
['key' => 'server_name', 'label' => 'Tên server', 'type' => 'string', 'example' => 'host01'],
|
||||
['key' => 'server_ip', 'label' => 'IP server', 'type' => 'string', 'example' => '1.2.3.4'],
|
||||
['key' => 'plesk_client_id', 'label' => 'Plesk Client ID', 'type' => 'string', 'example' => '123'],
|
||||
['key' => 'plesk_subscription_id', 'label' => 'Plesk Subscription ID', 'type' => 'string', 'example' => '456'],
|
||||
['key' => 'ftp_username', 'label' => 'FTP Username', 'type' => 'string', 'example' => 'ftpuser'],
|
||||
['key' => 'ftp_password_row', 'label' => '[Tự động] Dòng FTP Password', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'plesk_panel_url', 'label' => 'Plesk Panel URL', 'type' => 'url', 'example' => 'https://panel.tgs.com.vn'],
|
||||
['key' => 'plesk_login', 'label' => 'Plesk Login', 'type' => 'string', 'example' => 'admin'],
|
||||
['key' => 'plesk_password_row', 'label' => '[Tự động] Dòng Plesk Password', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#f06824', '#d9531a') . '.cred-box { background: #fff7ed; border: 1.5px solid #fdba74; border-radius: 10px; padding: 20px 24px; margin-top: 10px; } .cred-row { display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid #fed7aa; } .cred-row:last-child { border-bottom: none; } .cred-label { color: #c2410c; font-size: 13px; width: 150px; flex-shrink: 0; } .cred-value { font-family: "Courier New", monospace; font-size: 14px; font-weight: 700; color: #111827; word-break: break-all; } .note { margin-top: 16px; font-size: 12px; color: #9ca3af; }</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🖥️</div><h1>Hosting account mới được khởi tạo</h1><p>{$domain_name} — {$created_at}</p></div><div class="body"><div class="section-label">Thông tin khách hàng</div><table class="info-table"><tr><td>Tên khách hàng</td><td>{$customer_name}</td></tr><tr><td>Email</td><td>{$customer_email}</td></tr></table><div class="section-label">Thông tin dịch vụ Hosting</div><table class="info-table"><tr><td>Tên miền</td><td>{$domain_name}</td></tr><tr><td>Gói hosting</td><td>{$hosting_plan}</td></tr><tr><td>Server</td><td>{$server_name} ({$server_ip})</td></tr><tr><td>Plesk Client ID</td><td>{$plesk_client_id}</td></tr><tr><td>Plesk Subscription ID</td><td>{$plesk_subscription_id}</td></tr><tr><td>Trạng thái</td><td><span class="badge badge-green">✅ Active</span></td></tr></table><div class="section-label">Tài khoản FTP / System User (đã gửi cho khách)</div><div class="cred-box"><div class="cred-row"><span class="cred-label">👤 FTP Username</span><span class="cred-value">{$ftp_username}</span></div>{$ftp_password_row}</div><div class="section-label">Tài khoản đăng nhập Plesk Panel của khách hàng</div><div class="cred-box"><div class="cred-row"><span class="cred-label">🌐 Panel URL</span><span class="cred-value">{$plesk_panel_url}</span></div><div class="cred-row"><span class="cred-label">👤 Login</span><span class="cred-value">{$plesk_login}</span></div>{$plesk_password_row}</div><div class="note">Đây là tài khoản Plesk riêng của khách hàng (đã gửi kèm cho khách), khác với tài khoản quản trị của server dùng chung — tài khoản quản trị server không được đính kèm trong email này vì lý do bảo mật.</div></div><div class="footer">© ' . date('Y') . ' {$company_name} — Hệ thống quản lý dịch vụ<br>Email này được gửi tự động. Vui lòng không trả lời.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => '[Admin] Yêu cầu nạp tiền chờ duyệt',
|
||||
'scenario' => 'admin_manual_deposit_pending',
|
||||
'subject' => '[ADMIN] Yêu cầu nạp tiền chờ duyệt - {$transaction_code}',
|
||||
'variables' => [
|
||||
['key' => 'transaction_code', 'label' => 'Mã giao dịch', 'type' => 'string', 'example' => 'DEP001'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'customer_phone_row', 'label' => '[Tự động] Dòng số điện thoại', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'client_ip_row', 'label' => '[Tự động] Dòng IP gửi yêu cầu', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'amount_formatted', 'label' => 'Số tiền', 'type' => 'string', 'example' => '500.000 VNĐ'],
|
||||
['key' => 'proof_row', 'label' => '[Tự động] Dòng link chứng từ', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#b45309', '#f16723', '#ffedd5') . '</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🔔</div><h1>Yêu cầu nạp tiền chờ duyệt</h1><p>{$transaction_code} — {$created_at}</p></div><div class="body"><div class="section-label">Thông tin khách hàng</div><table class="info-table"><tr><td>Tên khách hàng</td><td>{$customer_name}</td></tr><tr><td>Email</td><td>{$customer_email}</td></tr>{$customer_phone_row}{$client_ip_row}</table><div class="section-label">Thông tin giao dịch</div><table class="info-table"><tr><td>Mã giao dịch</td><td>{$transaction_code}</td></tr><tr><td>Số tiền</td><td>{$amount_formatted}</td></tr><tr><td>Trạng thái</td><td><span class="badge">Đang chờ duyệt</span></td></tr>{$proof_row}</table><div class="notice">Vui lòng vào trang quản trị để kiểm tra chứng từ và duyệt hoặc từ chối giao dịch.</div></div><div class="footer">© ' . date('Y') . ' {$company_name} — Email này được gửi tự động.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => '[Admin] Ticket hỗ trợ mới',
|
||||
'scenario' => 'admin_ticket_notification',
|
||||
'subject' => '[ADMIN] Ticket mới #{$ticket_num} — {$customer_email}',
|
||||
'variables' => [
|
||||
['key' => 'ticket_num', 'label' => 'Mã ticket', 'type' => 'string', 'example' => 'TCK001'],
|
||||
['key' => 'subject_text', 'label' => 'Tiêu đề ticket', 'type' => 'string', 'example' => 'Không truy cập được VPS'],
|
||||
['key' => 'room', 'label' => 'Phòng ban', 'type' => 'string', 'example' => 'Kỹ thuật'],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'description', 'label' => 'Nội dung ticket (đã cắt bớt)', 'type' => 'string', 'example' => 'Không đăng nhập được vào VPS từ sáng nay...'],
|
||||
['key' => 'ticket_url', 'label' => 'Link xem ticket', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/tickets/TCK001'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#f06824', '#e05510', '#fde8d8') . '.desc-box { background: #fff7f3; border: 1.5px solid #fcd9c5; border-radius: 10px; padding: 16px 20px; margin-top: 10px; font-size: 14px; color: #374151; line-height: 1.6; white-space: pre-wrap; word-break: break-word; } .cta { text-align: center; margin: 28px 0 4px; } .cta a { display: inline-block; background: #f06824; color: #fff; text-decoration: none; padding: 12px 32px; border-radius: 8px; font-size: 15px; font-weight: 700; letter-spacing: 0.02em; }</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🎫</div><h1>Ticket mới cần xử lý</h1><p>Ticket #{$ticket_num}</p></div><div class="body"><div class="section-label">Thông tin ticket</div><table class="info-table"><tr><td>Mã ticket</td><td>#{$ticket_num}</td></tr><tr><td>Tiêu đề</td><td>{$subject_text}</td></tr><tr><td>Phòng ban</td><td>{$room}</td></tr></table><div class="section-label">Thông tin khách hàng</div><table class="info-table"><tr><td>Khách hàng</td><td>{$customer_name}</td></tr><tr><td>Email khách</td><td>{$customer_email}</td></tr></table><div class="section-label">Nội dung</div><div class="desc-box">{$description}</div><div class="cta"><a href="{$ticket_url}">Xem ticket</a></div></div><div class="footer">© ' . date('Y') . ' Portal Admin — Hệ thống quản lý dịch vụ<br>Email này được gửi tự động. Vui lòng không trả lời.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => '[Admin] Yêu cầu dùng thử VPS',
|
||||
'scenario' => 'admin_trial_registration_request',
|
||||
'subject' => '[ADMIN] Yêu cầu dùng thử Cloud VPS - {$customer_name}',
|
||||
'variables' => [
|
||||
['key' => 'customer_name', 'label' => 'Họ tên', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'customer_phone', 'label' => 'Số điện thoại', 'type' => 'string', 'example' => '0900000000'],
|
||||
['key' => 'cpu', 'label' => 'CPU', 'type' => 'string', 'example' => '4 core'],
|
||||
['key' => 'ram', 'label' => 'RAM', 'type' => 'string', 'example' => '8GB'],
|
||||
['key' => 'ssd', 'label' => 'SSD', 'type' => 'string', 'example' => '100GB'],
|
||||
['key' => 'os', 'label' => 'Hệ điều hành', 'type' => 'string', 'example' => 'Ubuntu 22.04'],
|
||||
['key' => 'other_row', 'label' => '[Tự động] Dòng yêu cầu khác', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#ea580c', '#f97316', '#ffedd5') . '</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🧪</div><h1>Yêu cầu dùng thử Cloud VPS</h1><p>{$created_at}</p></div><div class="body"><div class="section-label">Thông tin khách hàng</div><table class="info-table"><tr><td>Họ tên</td><td>{$customer_name}</td></tr><tr><td>Email</td><td>{$customer_email}</td></tr><tr><td>Số điện thoại</td><td>{$customer_phone}</td></tr></table><div class="section-label">Cấu hình yêu cầu</div><table class="info-table"><tr><td>CPU</td><td>{$cpu}</td></tr><tr><td>RAM</td><td>{$ram}</td></tr><tr><td>SSD</td><td>{$ssd}</td></tr><tr><td>Hệ điều hành</td><td>{$os}</td></tr>{$other_row}<tr><td>Trạng thái</td><td><span class="badge">Chờ khởi tạo</span></td></tr></table><div class="notice">Vui lòng vào trang quản trị để khởi tạo VPS dùng thử theo cấu hình khách hàng yêu cầu.</div></div><div class="footer">© ' . date('Y') . ' {$company_name} — Email này được gửi tự động.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => 'Nạp tiền thành công',
|
||||
'scenario' => 'customer_deposit_success',
|
||||
'subject' => 'V/v: Thông báo nạp tiền thành công - {$amount_formatted} VNĐ',
|
||||
'variables' => [
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'transaction_code', 'label' => 'Mã giao dịch', 'type' => 'string', 'example' => 'DEP001'],
|
||||
['key' => 'amount_formatted', 'label' => 'Số tiền nạp', 'type' => 'string', 'example' => '500.000'],
|
||||
['key' => 'credit_formatted', 'label' => 'Khuyến mãi', 'type' => 'string', 'example' => '0'],
|
||||
['key' => 'current_total_formatted', 'label' => 'Tổng số dư hiện tại', 'type' => 'string', 'example' => '1.500.000'],
|
||||
['key' => 'current_credit_formatted', 'label' => 'Số dư khuyến mãi hiện tại', 'type' => 'string', 'example' => '0'],
|
||||
['key' => 'deposit_date', 'label' => 'Thời gian nạp', 'type' => 'string', 'example' => now()->format('d/m/Y H:i:s')],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#16a34a', '#15803d', '#bbf7d0') . '</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">💰</div><h1>Nạp tiền thành công</h1><p>{$transaction_code} — {$deposit_date}</p></div><div class="body"><p>Xin chào <strong>{$customer_name}</strong>, yêu cầu nạp tiền của bạn đã được duyệt thành công.</p><table class="info-table"><tr><td>Mã giao dịch</td><td>{$transaction_code}</td></tr><tr><td>Số tiền nạp</td><td>{$amount_formatted} VNĐ</td></tr><tr><td>Khuyến mãi</td><td>{$credit_formatted} VNĐ</td></tr><tr><td>Tổng số dư hiện tại</td><td>{$current_total_formatted} VNĐ</td></tr><tr><td>Số dư khuyến mãi hiện tại</td><td>{$current_credit_formatted} VNĐ</td></tr></table><p style="color:#6b7280;font-size:13px;margin-top:20px;">Nếu bạn cần hỗ trợ thêm, vui lòng liên hệ {$support_email} hoặc {$support_phone}.</p></div><div class="footer">Email này được gửi tự động. Vui lòng không trả lời email này.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => 'Chào mừng khách hàng mới',
|
||||
'scenario' => 'customer_welcome',
|
||||
'subject' => 'Chào mừng bạn đến với {$app_name}',
|
||||
'variables' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Quý khách'],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'message' => $this->wrap('<div style="padding: 25px;"><p style="text-align: center;"><img src="https://res.cloudinary.com/izpast9i/image/upload/v1786503590/email_banner_header.png" alt="Chào mừng bạn đến với Portal Thế Giới Số" width="100%"></p><p style="font-size: 21px; font-weight: bold; text-align: center;"><strong>Chào mừng Quý Khách đến với hệ thống quản trị dịch vụ của Thế Giới Số</strong></p><p style="font-weight: bold;"><strong>Kính chào <span style="color: #f16723;">{$full_name},</span></strong></p><p style="text-align: justify;">Chào mừng Quý khách đến với <strong>{$app_name}</strong>. Tài khoản của Quý khách đã được khởi tạo thành công và sẵn sàng sử dụng.</p><table cellspacing="0" cellpadding="0" style="margin: 16px auto 16px auto;"><tbody><tr><td align="center" style="background-color: #f16723; padding: 10px 20px; border-radius: 4px;"><a href="{$login_url}" target="_blank" rel="noopener" style="text-decoration: none;"><strong style="color: #ffffff;">Đăng nhập ngay</strong></a></td></tr></tbody></table><p style="text-align: justify; color: #666666; font-size: 13px;">Email này được gửi tự động. Vui lòng không trả lời email này.</p><p style="text-align: justify;">---<br>Hotline: {$support_phone}<br>Email: {$support_email}</p></div>'),
|
||||
],
|
||||
[
|
||||
'name' => 'Đổi port Remote VPS',
|
||||
'scenario' => 'vm_port_change',
|
||||
'subject' => 'Port VPS của bạn đã được thay đổi',
|
||||
'variables' => [
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'vm-example-01'],
|
||||
['key' => 'new_port', 'label' => 'Port mới', 'type' => 'string', 'example' => '13389'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#1e40af', '#1d4ed8', '#bfdbfe') . '</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🔌</div><h1>Port Remote Desktop đã được thay đổi</h1><p>Máy chủ: {$vm_name}</p></div><div class="body"><p>Xin chào Quý khách,</p><p>Port kết nối Remote Desktop (RDP) của máy chủ ảo <strong>{$vm_name}</strong> đã được thay đổi thành công.</p><table class="info-table"><tr><td>Máy chủ</td><td>{$vm_name}</td></tr><tr><td>Port mới</td><td>{$new_port}</td></tr></table><div class="notice">Vui lòng cập nhật lại port khi kết nối Remote Desktop tới máy chủ.</div></div><div class="footer">Email này được gửi tự động. Vui lòng không trả lời email này.</div></div></div>',
|
||||
],
|
||||
[
|
||||
'name' => 'Đặt lại mật khẩu VPS thành công',
|
||||
'scenario' => 'vm_password_reset_success',
|
||||
'subject' => 'Mật khẩu VPS của bạn đã được đặt lại',
|
||||
'variables' => [
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'vm-example-01'],
|
||||
['key' => 'new_password', 'label' => 'Mật khẩu mới', 'type' => 'string', 'example' => 'NewP@ss123'],
|
||||
],
|
||||
'message' => '<style>' . $this->cardStyle('#f06824', '#d9531a') . '</style><div class="wrapper"><div class="card"><div class="header"><div class="icon">🔑</div><h1>Mật khẩu VPS đã được đặt lại</h1><p>Máy chủ: {$vm_name}</p></div><div class="body"><p>Xin chào Quý khách,</p><p>Mật khẩu đăng nhập máy chủ ảo <strong>{$vm_name}</strong> đã được đặt lại thành công. Vui lòng sử dụng mật khẩu mới bên dưới để đăng nhập:</p><table class="info-table"><tr><td>Máy chủ</td><td>{$vm_name}</td></tr><tr><td>Mật khẩu mới</td><td>{$new_password}</td></tr></table><div class="notice">Vì lý do bảo mật, vui lòng đổi lại mật khẩu ngay sau khi đăng nhập thành công.</div></div><div class="footer">Email này được gửi tự động. Vui lòng không trả lời email này.</div></div></div>',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$exists = DB::table('template')->where('scenario', $row['scenario'])->exists();
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('template')->insert([
|
||||
'type_id' => 0,
|
||||
'scenario' => $row['scenario'],
|
||||
'format' => 'html',
|
||||
'name' => $row['name'],
|
||||
'subject' => $row['subject'],
|
||||
'message' => $row['message'],
|
||||
'metadata' => json_encode(['variables' => $row['variables']]),
|
||||
'status' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('template')->whereIn('scenario', [
|
||||
'admin_order_notification',
|
||||
'admin_domain_order_new',
|
||||
'admin_hosting_account_created',
|
||||
'admin_manual_deposit_pending',
|
||||
'admin_ticket_notification',
|
||||
'admin_trial_registration_request',
|
||||
'customer_deposit_success',
|
||||
'customer_welcome',
|
||||
'vm_port_change',
|
||||
'vm_password_reset_success',
|
||||
])->delete();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Template;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
// Metadata cho 13 scenario "nhóm A" — trước đây dùng str_replace tay, chưa từng khai báo metadata.
|
||||
private const LEGACY_VARIABLES = [
|
||||
'vm_suspended' => [
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'VPS-001'],
|
||||
['key' => 'vmware_id', 'label' => 'VM ID (vCenter)', 'type' => 'string', 'example' => 'vm-12345'],
|
||||
['key' => 'suspended_at', 'label' => 'Thời gian tạm ngưng', 'type' => 'string', 'example' => '09/09/2026 10:00'],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'ticket_created' => [
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'ticket_num', 'label' => 'Mã ticket', 'type' => 'string', 'example' => 'TCK001'],
|
||||
['key' => 'subject', 'label' => 'Tiêu đề ticket', 'type' => 'string', 'example' => 'Không truy cập được VPS'],
|
||||
['key' => 'room_row', 'label' => '[Tự động] Dòng phòng ban', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'ticket_url', 'label' => 'Link xem ticket', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/tickets/TCK001'],
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'service_renewal_reminder' => [
|
||||
['key' => 'client_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'product_name', 'label' => 'Tên dịch vụ', 'type' => 'string', 'example' => 'Cloud VPS Pro'],
|
||||
['key' => 'server_name_suffix', 'label' => '[Tự động] Hậu tố tên máy chủ', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'expires_at', 'label' => 'Ngày hết hạn', 'type' => 'string', 'example' => '15/09/2026'],
|
||||
['key' => 'price_row', 'label' => '[Tự động] Dòng số tiền gia hạn', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'domain_order_received' => [
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'order_total', 'label' => 'Tổng tiền', 'type' => 'string', 'example' => '350.000 VNĐ'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => '09/09/2026 10:00'],
|
||||
['key' => 'order_url', 'label' => 'Link xem đơn hàng', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/orders/DH001'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'hosting_account_created' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'hosting_plan', 'label' => 'Gói hosting', 'type' => 'string', 'example' => 'Hosting Pro'],
|
||||
['key' => 'server_host_row', 'label' => '[Tự động] Dòng máy chủ/FTP host', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'ftp_username_row', 'label' => '[Tự động] Dòng tài khoản FTP', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'ftp_password_row', 'label' => '[Tự động] Dòng mật khẩu FTP', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'ftp_password_notice', 'label' => '[Tự động] Ghi chú bảo mật FTP', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'plesk_block', 'label' => '[Tự động] Khối tài khoản Plesk', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'manual_deposit_status' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'transaction_code', 'label' => 'Mã giao dịch', 'type' => 'string', 'example' => 'DEP001'],
|
||||
['key' => 'amount_formatted', 'label' => 'Số tiền', 'type' => 'string', 'example' => '500.000'],
|
||||
['key' => 'status_label', 'label' => 'Nhãn trạng thái', 'type' => 'string', 'example' => 'Đã duyệt'],
|
||||
['key' => 'status_color', 'label' => '[Tự động] Màu chữ trạng thái', 'type' => 'string', 'example' => '#16a34a'],
|
||||
['key' => 'status_bg', 'label' => '[Tự động] Màu nền trạng thái', 'type' => 'string', 'example' => '#dcfce7'],
|
||||
['key' => 'status_message', 'label' => 'Nội dung thông báo', 'type' => 'string', 'example' => 'Yêu cầu nạp tiền đã được duyệt.'],
|
||||
['key' => 'deposit_date', 'label' => 'Thời gian nạp', 'type' => 'string', 'example' => '09/09/2026 10:00'],
|
||||
['key' => 'processed_date_row', 'label' => '[Tự động] Dòng thời gian xử lý', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'portal_url', 'label' => 'Link trang nạp tiền', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/nap-tien'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'order_payment_success' => [
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'amount_formatted', 'label' => 'Tổng thanh toán', 'type' => 'string', 'example' => '990.000'],
|
||||
['key' => 'vm_block', 'label' => '[Tự động] Khối thông tin VPS', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'addon_block', 'label' => '[Tự động] Khối addon', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'password_reset_admin' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'email', 'label' => 'Email tài khoản', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'password', 'label' => 'Mật khẩu mới', 'type' => 'string', 'example' => 'NewP@ss123'],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'snapshot_action' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'vm-example-01'],
|
||||
['key' => 'snapshot_name', 'label' => 'Tên snapshot', 'type' => 'string', 'example' => 'snap-2026-09-09'],
|
||||
['key' => 'action_at', 'label' => 'Thời gian thao tác', 'type' => 'string', 'example' => '09/09/2026 10:00'],
|
||||
['key' => 'action_icon', 'label' => '[Tự động] Icon', 'type' => 'string', 'example' => '📸'],
|
||||
['key' => 'action_title', 'label' => '[Tự động] Tiêu đề theo hành động', 'type' => 'string', 'example' => 'Tạo Snapshot thành công'],
|
||||
['key' => 'action_desc', 'label' => '[Tự động] Mô tả theo hành động', 'type' => 'string', 'example' => 'Snapshot đã được tạo thành công'],
|
||||
['key' => 'action_status', 'label' => '[Tự động] Trạng thái theo hành động', 'type' => 'string', 'example' => 'Tạo thành công ✓'],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'verify_account_welcome' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'verify_code', 'label' => 'Mã xác thực', 'type' => 'string', 'example' => '482913'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'vm_creation_failed' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm', 'type' => 'string', 'example' => 'Cloud VPS Pro'],
|
||||
['key' => 'invoice_url', 'label' => 'Link xem hoá đơn', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/hoa-don/1'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'vm_password_changed' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'vm-example-01'],
|
||||
['key' => 'new_password_row', 'label' => '[Tự động] Dòng mật khẩu mới', 'type' => 'html', 'example' => ''],
|
||||
['key' => 'login_url', 'label' => 'Link đăng nhập', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dang-nhap'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
'vm_rebuild_failed' => [
|
||||
['key' => 'app_name', 'label' => 'Tên ứng dụng', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'full_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'vm_name', 'label' => 'Tên VPS', 'type' => 'string', 'example' => 'vm-example-01'],
|
||||
['key' => 'ip', 'label' => 'Địa chỉ IP', 'type' => 'string', 'example' => '1.2.3.4'],
|
||||
['key' => 'service_url', 'label' => 'Link xem dịch vụ', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/dich-vu'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'support_phone', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Template::query()->whereNotNull('scenario')->get()->each(function (Template $template) {
|
||||
$template->subject = $this->convert((string) $template->subject);
|
||||
$template->message = $this->convert((string) $template->message);
|
||||
|
||||
if (isset(self::LEGACY_VARIABLES[$template->scenario])) {
|
||||
$metadata = $template->metadata ?? [];
|
||||
$metadata['variables'] = self::LEGACY_VARIABLES[$template->scenario];
|
||||
$template->metadata = $metadata;
|
||||
}
|
||||
|
||||
$template->save();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Template::query()->whereNotNull('scenario')->get()->each(function (Template $template) {
|
||||
$template->subject = $this->revert((string) $template->subject);
|
||||
$template->message = $this->revert((string) $template->message);
|
||||
|
||||
if (isset(self::LEGACY_VARIABLES[$template->scenario])) {
|
||||
$metadata = $template->metadata ?? [];
|
||||
unset($metadata['variables']);
|
||||
$template->metadata = $metadata ?: null;
|
||||
}
|
||||
|
||||
$template->save();
|
||||
});
|
||||
}
|
||||
|
||||
private function convert(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\$([a-zA-Z_][a-zA-Z0-9_]*)\}/', '{{$1}}', $content) ?? $content;
|
||||
}
|
||||
|
||||
private function revert(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/', '{\$$1}', $content) ?? $content;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Template;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
// id => [scenario, variables[]]
|
||||
private const BACKFILL = [
|
||||
1 => [
|
||||
'scenario' => 'customer_signup_verify',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'link_verify', 'label' => 'Link xác thực tài khoản', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/verify/xxx'],
|
||||
],
|
||||
],
|
||||
3 => [
|
||||
'scenario' => 'hosting_information',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'client_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm', 'type' => 'string', 'example' => 'Hosting Pro'],
|
||||
['key' => 'product_billing', 'label' => 'Chu kỳ thanh toán', 'type' => 'string', 'example' => 'Hàng tháng'],
|
||||
['key' => 'product_price', 'label' => 'Giá dịch vụ', 'type' => 'string', 'example' => '150.000'],
|
||||
['key' => 'service_host', 'label' => 'IP máy chủ', 'type' => 'string', 'example' => '1.2.3.4'],
|
||||
['key' => 'service_username', 'label' => 'Username', 'type' => 'string', 'example' => 'user01'],
|
||||
['key' => 'service_password', 'label' => 'Mật khẩu dịch vụ', 'type' => 'string', 'example' => 'P@ss123'],
|
||||
['key' => 'service_due_exp', 'label' => 'Ngày hết hạn', 'type' => 'string', 'example' => '09/09/2026'],
|
||||
['key' => 'ftp_account', 'label' => 'Tài khoản FTP', 'type' => 'string', 'example' => 'ftpuser'],
|
||||
['key' => 'ftp_password', 'label' => 'Mật khẩu FTP', 'type' => 'string', 'example' => 'Ftp@123'],
|
||||
],
|
||||
],
|
||||
4 => [
|
||||
'scenario' => 'customer_password_reset',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'password', 'label' => 'Mật khẩu mới', 'type' => 'string', 'example' => 'NewP@ss123'],
|
||||
],
|
||||
],
|
||||
9 => [
|
||||
'scenario' => 'service_expiry',
|
||||
'variables' => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm', 'type' => 'string', 'example' => 'Cloud VPS Pro'],
|
||||
['key' => 'service_due_exp', 'label' => 'Ngày hết hạn', 'type' => 'string', 'example' => '09/09/2026'],
|
||||
['key' => 'service_remaining', 'label' => 'Số ngày còn lại', 'type' => 'string', 'example' => '5'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $id => $config) {
|
||||
$template = Template::find($id);
|
||||
|
||||
if (!$template) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template->scenario = $config['scenario'];
|
||||
$template->subject = $this->convert((string) $template->subject);
|
||||
$template->message = $this->convert((string) $template->message);
|
||||
$metadata = $template->metadata ?? [];
|
||||
$metadata['variables'] = $config['variables'];
|
||||
$template->metadata = $metadata;
|
||||
$template->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $id => $config) {
|
||||
$template = Template::find($id);
|
||||
|
||||
if (!$template || $template->scenario !== $config['scenario']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template->scenario = null;
|
||||
$template->subject = $this->revert((string) $template->subject);
|
||||
$template->message = $this->revert((string) $template->message);
|
||||
$metadata = $template->metadata ?? [];
|
||||
unset($metadata['variables']);
|
||||
$template->metadata = $metadata ?: null;
|
||||
$template->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function convert(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\$([a-zA-Z_][a-zA-Z0-9_]*)\}/', '{{$1}}', $content) ?? $content;
|
||||
}
|
||||
|
||||
private function revert(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/', '{\$$1}', $content) ?? $content;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Template;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
// id => variables[] (chỉ set metadata; convert {$var}->{{var}} nếu nội dung còn cú pháp cũ)
|
||||
private const BACKFILL = [
|
||||
2 => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'company_logo', 'label' => 'Logo công ty (URL)', 'type' => 'url', 'example' => ''],
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm', 'type' => 'string', 'example' => 'Cloud VPS Pro'],
|
||||
['key' => 'product_billing', 'label' => 'Chu kỳ thanh toán', 'type' => 'string', 'example' => 'Hàng tháng'],
|
||||
['key' => 'product_price', 'label' => 'Giá dịch vụ', 'type' => 'string', 'example' => '420.000'],
|
||||
['key' => 'service_host', 'label' => 'IP máy chủ', 'type' => 'string', 'example' => '203.113.x.x'],
|
||||
['key' => 'service_username', 'label' => 'Username', 'type' => 'string', 'example' => 'root'],
|
||||
['key' => 'service_port', 'label' => 'Port', 'type' => 'string', 'example' => '22'],
|
||||
['key' => 'service_password', 'label' => 'Mật khẩu', 'type' => 'string', 'example' => 'P@ssw0rd!'],
|
||||
['key' => 'os_name', 'label' => 'Hệ điều hành', 'type' => 'string', 'example' => 'Ubuntu 22.04'],
|
||||
['key' => 'service_due_exp', 'label' => 'Ngày hết hạn', 'type' => 'string', 'example' => '09/09/2026'],
|
||||
],
|
||||
6 => [
|
||||
['key' => 'company_name', 'label' => 'Tên công ty', 'type' => 'string', 'example' => 'Thế Giới Số'],
|
||||
['key' => 'domain_name', 'label' => 'Tên miền', 'type' => 'string', 'example' => 'example.vn'],
|
||||
['key' => 'order_num', 'label' => 'Mã đơn hàng', 'type' => 'string', 'example' => 'DH001'],
|
||||
['key' => 'order_total', 'label' => 'Tổng tiền', 'type' => 'string', 'example' => '350.000 VNĐ'],
|
||||
['key' => 'customer_name', 'label' => 'Tên khách hàng', 'type' => 'string', 'example' => 'Nguyễn Văn A'],
|
||||
['key' => 'customer_email', 'label' => 'Email khách hàng', 'type' => 'string', 'example' => 'a@example.com'],
|
||||
['key' => 'created_at', 'label' => 'Thời gian', 'type' => 'string', 'example' => '09/09/2026 10:00:00'],
|
||||
['key' => 'admin_url', 'label' => 'Link xem đơn (admin)', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/admin/orders'],
|
||||
],
|
||||
23 => [
|
||||
['key' => 'hero_image', 'label' => 'Ảnh banner', 'type' => 'url', 'example' => 'https://client.tgs.com.vn/index.php?rp=/images/em/368_30.4-CP.png'],
|
||||
['key' => 'holiday_name', 'label' => 'Tên ngày lễ', 'type' => 'string', 'example' => 'Tết Dương lịch'],
|
||||
['key' => 'holiday_from', 'label' => 'Ngày bắt đầu nghỉ', 'type' => 'string', 'example' => 'Thứ Năm, 01/01/2026'],
|
||||
['key' => 'holiday_to', 'label' => 'Ngày kết thúc nghỉ', 'type' => 'string', 'example' => 'Thứ Năm, 01/01/2026'],
|
||||
['key' => 'work_again', 'label' => 'Ngày làm việc trở lại', 'type' => 'string', 'example' => 'Thứ Sáu, 02/01/2026'],
|
||||
['key' => 'ticket_url', 'label' => 'Link gửi ticket', 'type' => 'url', 'example' => 'https://portal.tgs.com.vn/submitticket.php'],
|
||||
['key' => 'support_email', 'label' => 'Email hỗ trợ', 'type' => 'string', 'example' => 'support@tgs.com.vn'],
|
||||
['key' => 'hotline', 'label' => 'Hotline hỗ trợ', 'type' => 'string', 'example' => '(028) 7309.7379'],
|
||||
],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $id => $variables) {
|
||||
$template = Template::find($id);
|
||||
if (!$template) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template->subject = $this->convert((string) $template->subject);
|
||||
$template->message = $this->convert((string) $template->message);
|
||||
$metadata = $template->metadata ?? [];
|
||||
$metadata['variables'] = $variables;
|
||||
$template->metadata = $metadata;
|
||||
$template->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (self::BACKFILL as $id => $variables) {
|
||||
$template = Template::find($id);
|
||||
if (!$template) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$metadata = $template->metadata ?? [];
|
||||
unset($metadata['variables']);
|
||||
$template->metadata = $metadata ?: null;
|
||||
$template->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function convert(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\$([a-zA-Z_][a-zA-Z0-9_]*)\}/', '{{$1}}', $content) ?? $content;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user