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:
co-authored by
Claude Sonnet 5.5
parent
3c7710f1de
commit
a443ec3bf6
@@ -3,6 +3,8 @@
|
||||
namespace App\Integrations\Whmcs\Transformers;
|
||||
|
||||
use App\Integrations\Whmcs\DTOs\ClientData;
|
||||
use App\Integrations\Whmcs\Support\SyncReport;
|
||||
use App\Support\PhoneNumber;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ClientTransformer
|
||||
@@ -10,6 +12,10 @@ class ClientTransformer
|
||||
/** @var array<string, int>|null fullname => user id, cached for the process lifetime */
|
||||
private static ?array $salerIdsByName = null;
|
||||
|
||||
public function __construct(private SyncReport $report)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a raw `tblclients` row (array or stdClass) to a ClientData DTO.
|
||||
*/
|
||||
@@ -22,6 +28,8 @@ class ClientTransformer
|
||||
$fullname = $row['companyname'] ?: null;
|
||||
}
|
||||
|
||||
$this->auditMissingInfo($row, $fullname);
|
||||
|
||||
return new ClientData(
|
||||
uuid: $row['uuid'],
|
||||
fullname: $fullname,
|
||||
@@ -32,7 +40,7 @@ class ClientTransformer
|
||||
// ponytail: WHMCS repurposes the `postcode` column to store the salesperson's fullname
|
||||
saler: $this->resolveSalerId($row['postcode'] ?? null),
|
||||
country: $row['country'] ?: null,
|
||||
phone: $this->formatPhone($row['phonenumber'] ?? null),
|
||||
phone: PhoneNumber::normalize($row['phonenumber'] ?? null),
|
||||
email: $row['email'] ? strtolower($row['email']) : null,
|
||||
// ponytail: WHMCS hashes are not Laravel bcrypt, copied as-is; customer must reset password to log into portal
|
||||
password: $row['password'] ?: null,
|
||||
@@ -45,32 +53,38 @@ class ClientTransformer
|
||||
);
|
||||
}
|
||||
|
||||
private function auditMissingInfo(array $row, ?string $fullname): void
|
||||
{
|
||||
$ref = (string) ($row['email'] ?: "#{$row['id']}");
|
||||
$missing = [];
|
||||
if (! $fullname) {
|
||||
$missing[] = 'họ tên';
|
||||
}
|
||||
if (! PhoneNumber::normalize($row['phonenumber'] ?? null)) {
|
||||
$missing[] = 'số điện thoại';
|
||||
}
|
||||
if (trim(($row['address1'] ?? '') . ($row['address2'] ?? '')) === '') {
|
||||
$missing[] = 'địa chỉ';
|
||||
}
|
||||
if ($missing) {
|
||||
$this->report->note('client', $ref, $row['id'], 'thieu_thong_tin', 'Thiếu: ' . implode(', ', $missing));
|
||||
}
|
||||
if (! filter_var($row['email'] ?? '', FILTER_VALIDATE_EMAIL) || preg_match('/@(gmail|yahoo|hotmail|outlook)\.com\.\w+$/i', (string) $row['email'])) {
|
||||
$this->report->note('client', $ref, $row['id'], 'email_nghi_sai', "Email WHMCS có vẻ không hợp lệ: {$row['email']}");
|
||||
}
|
||||
if (! empty($row['postcode']) && $this->resolveSalerId($row['postcode']) === null) {
|
||||
$this->report->note('client', $ref, $row['id'], 'khong_map_duoc_sale', "Không tìm thấy nhân viên sale tên \"{$row['postcode']}\" trong bảng user.");
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveSalerId(?string $fullname): ?int
|
||||
{
|
||||
if (! $fullname) {
|
||||
return null;
|
||||
}
|
||||
|
||||
self::$salerIdsByName ??= DB::table('user')->pluck('id', 'fullname')->all();
|
||||
self::$salerIdsByName ??= DB::table('user')->get(['id', 'fullname'])->mapWithKeys(fn ($user) => [trim((string) $user->fullname) => $user->id])->all();
|
||||
|
||||
return self::$salerIdsByName[$fullname] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* "+84.93 613 68 14" -> "0936136814"; other country codes keep their digits as-is (e.g. "+62..." -> "62...").
|
||||
*/
|
||||
private function formatPhone(?string $raw): ?string
|
||||
{
|
||||
if (! $raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$digits = preg_replace('/\D+/', '', $raw);
|
||||
|
||||
if (str_starts_with($digits, '84')) {
|
||||
$digits = '0' . substr($digits, 2);
|
||||
}
|
||||
|
||||
return $digits ?: null;
|
||||
return self::$salerIdsByName[trim($fullname)] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user