feat: email gửi tay/nhắc gia hạn, gói hosting, sửa dữ liệu sản phẩm
- Gửi email theo template cho 1 khách, tab lịch sử email, nhắc gia hạn dịch vụ hàng loạt - Mail lưu from/bcc/sent_by; mẫu MARKETING và mẫu cũ trong TemplateSeeder - Gói hosting: giới hạn, trạng thái, lấy plan Plesk qua CLI gateway - Backfill payment_method và ngày đơn hàng còn thiếu - Bổ sung field CPU/RAM/Disk/Băng thông cho loại sản phẩm vps và cloud Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8651dde6b1
commit
30898ea46f
@@ -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('mail', function (Blueprint $table) {
|
||||
$table->string('from_address', 255)->nullable()->after('subject');
|
||||
$table->string('bcc', 1000)->nullable()->after('cc');
|
||||
$table->unsignedBigInteger('sent_by')->nullable()->index()->after('customer_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('mail', function (Blueprint $table) {
|
||||
$table->dropIndex(['sent_by']);
|
||||
$table->dropColumn(['from_address', 'bcc', 'sent_by']);
|
||||
});
|
||||
}
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?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('service_plans', function (Blueprint $table) {
|
||||
$table->text('description')->nullable()->after('title');
|
||||
$table->unsignedInteger('bandwidth')->nullable()->after('domain');
|
||||
$table->unsignedInteger('databases')->nullable()->after('bandwidth');
|
||||
$table->unsignedInteger('mailboxes')->nullable()->after('databases');
|
||||
$table->unsignedInteger('mailbox_quota')->nullable()->after('mailboxes');
|
||||
$table->unsignedInteger('ftp_accounts')->nullable()->after('mailbox_quota');
|
||||
$table->unsignedInteger('subdomains')->nullable()->after('ftp_accounts');
|
||||
$table->unsignedInteger('domain_aliases')->nullable()->after('subdomains');
|
||||
$table->json('features')->nullable()->after('domain_aliases');
|
||||
$table->boolean('is_active')->default(true)->after('features');
|
||||
$table->unsignedInteger('sort_order')->default(0)->after('is_active');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('service_plans', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'description', 'bandwidth', 'databases', 'mailboxes', 'mailbox_quota',
|
||||
'ftp_accounts', 'subdomains', 'domain_aliases', 'features', 'is_active', 'sort_order',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('orders')
|
||||
->whereNull('payment_method')
|
||||
->whereExists(function ($query) {
|
||||
$query->selectRaw('1')
|
||||
->from('invoices')
|
||||
->whereColumn('invoices.order_id', 'orders.id')
|
||||
->whereNotNull('invoices.payment_method');
|
||||
})
|
||||
->update([
|
||||
'payment_method' => DB::raw('(SELECT invoices.payment_method FROM invoices WHERE invoices.order_id = orders.id AND invoices.payment_method IS NOT NULL ORDER BY invoices.id ASC LIMIT 1)'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Preserve recovered payment data. The application remains compatible
|
||||
// because orders.payment_method is still the original nullable string.
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('orders')
|
||||
->whereNull('date')
|
||||
->whereNotNull('created_at')
|
||||
->update(['date' => DB::raw('DATE(created_at)')]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Preserve recovered order dates. The date column remains nullable and
|
||||
// compatible with older application versions after a code rollback.
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
private const TYPES = ['vps', 'cloud'];
|
||||
|
||||
private const FIELDS = [
|
||||
['key' => 'cpu', 'label' => 'CPU (vCPU)', 'sort_order' => 1],
|
||||
['key' => 'ram', 'label' => 'RAM (GB)', 'sort_order' => 2],
|
||||
['key' => 'disk', 'label' => 'Disk (GB)', 'sort_order' => 3],
|
||||
['key' => 'bandwidth', 'label' => 'Băng thông (GB)', 'sort_order' => 4],
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
foreach (self::TYPES as $type) {
|
||||
foreach (self::FIELDS as $field) {
|
||||
$exists = DB::table('product_type_fields')
|
||||
->where('product_type', $type)
|
||||
->where('key', $field['key'])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('product_type_fields')->insert($field + [
|
||||
'product_type' => $type,
|
||||
'field_type' => 'number',
|
||||
'is_required' => false,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user