feat: Add IT Support catalog and related migrations, seeders, and email templates

- Created migration to add 'group_label' to 'product_type_fields' table.
- Implemented ItSupportCatalogSeeder to populate IT Support features and tiers.
- Added ProductCategorySeeder for initial product categories.
- Developed ProductMockupSeeder for mockup products in various categories.
- Introduced ProductTypeFieldSeeder to define fields for different product types.
- Created email templates for VPS service information and service renewal reminders.
This commit is contained in:
baodev0
2026-08-26 16:19:38 +07:00
parent 382b7c0ef8
commit c917e3bf5e
47 changed files with 2992 additions and 1759 deletions
@@ -4,12 +4,16 @@ namespace App\Services\Api\Client;
use App\Http\Services\Telegram;
use App\Mail\Admin\TrialRegistrationRequest as TrialRegistrationRequestMail;
use App\Mail\Customer\PasswordResetByAdmin;
use App\Mail\Customer\VerifyAccount as VerifyAccountMail;
use App\Mail\Customer\Welcome as WelcomeMail;
use App\Models\Customer;
use App\Models\RefreshToken;
use App\Models\Setting;
use App\Repositories\Api\Client\ClientRepositoryInterface;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
@@ -299,4 +303,77 @@ class ClientAuthService
$hash = hash('sha256', $rawToken);
RefreshToken::where('token', $hash)->delete();
}
/**
* Cấp mật khẩu mới ngẫu nhiên khi khách hàng quên mật khẩu. Không tiết lộ
* email có tồn tại hay không — trả về null nếu không tìm thấy tài khoản.
*/
public function resetForgottenPassword(string $email): ?Customer
{
$customer = Customer::whereRaw('LOWER(email) = ?', [strtolower(trim($email))])->first();
if (!$customer) {
return null;
}
$newPassword = Str::password(16);
DB::transaction(function () use ($customer, $newPassword) {
$customer->password = Hash::make($newPassword);
$customer->save();
Mail::to($customer->email)->later(now()->addSeconds(5), new PasswordResetByAdmin([
'fullname' => $customer->fullname,
'email' => $customer->email,
'password' => $newPassword,
]));
});
return $customer;
}
/**
* Cập nhật thông tin cá nhân. Trả về old/dirty values để caller ghi activity log.
*/
public function updateProfile(Customer $client, array $data): array
{
$oldValues = array_intersect_key($client->toArray(), $data);
$client->fill($data);
$dirty = $client->getDirty();
$client->save();
return ['client' => $client, 'old' => array_intersect_key($oldValues, $dirty), 'dirty' => $dirty];
}
/**
* Cập nhật CMT/CCCD (số + 2 ảnh đã được lưu sẵn trên storage bởi caller).
*/
public function updateCccd(Customer $client, string $number, string $frontUrl, string $backUrl): array
{
$oldValues = $client->only(['identity_number', 'front_identity_number', 'back_identity_number']);
$client->identity_number = $number;
$client->front_identity_number = $frontUrl;
$client->back_identity_number = $backUrl;
$dirty = $client->getDirty();
$client->save();
return ['client' => $client, 'old' => array_intersect_key($oldValues, $dirty), 'dirty' => $dirty];
}
/**
* Đổi mật khẩu. Ném ValidationException nếu mật khẩu hiện tại không đúng.
*/
public function changePassword(Customer $client, string $currentPassword, string $newPassword): void
{
if (!Hash::check($currentPassword, $client->password)) {
throw ValidationException::withMessages([
'current_password' => ['Mật khẩu hiện tại không chính xác'],
]);
}
$client->password = Hash::make($newPassword);
$client->save();
}
}