Files
portal-api/app/Models/Customer.php
T
baodev0andClaude Opus 5.5 5de62b9083 fix(customer): flush customer repository cache on model writes
Writes that bypass the cached repository (e.g. client preferences)
left stale customers in cache, so admin sending a marketing template
got 422 although the customer had opted back in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:39:21 +07:00

280 lines
6.1 KiB
PHP

<?php
namespace App\Models;
use App\Services\Cache\CacheServiceInterface;
use App\Traits\LogsActivity;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\DB;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Customer extends Authenticatable implements JWTSubject
{
use HasFactory, HasUuids, SoftDeletes, LogsActivity;
protected $table = 'customers';
protected static function booted(): void
{
$flushRepositoryCache = fn () => DB::afterCommit(
fn () => app(CacheServiceInterface::class)->clearByModule('customer')
);
static::saved($flushRepositoryCache);
static::deleted($flushRepositoryCache);
static::restored($flushRepositoryCache);
}
/**
* Chỉ định cột sẽ nhận UUID.
* Mặc định HasUuids dùng cột 'id', mình đổi sang 'uuid'.
*/
public function uniqueIds()
{
return ['uuid'];
}
/**
* Điểm tích luỹ cộng cho một khoản nạp: 1 credit / 100 VNĐ.
* Trước đây khớp sao kê tự động cộng credit bằng nguyên số tiền còn duyệt tay cộng
* amount/100, cùng một khoản ra hai kết quả lệch nhau 100 lần — nay đi chung một chỗ.
*/
public static function creditFor(int $amount): int
{
return (int) floor(max(0, $amount) / 100);
}
protected $fillable = [
'fullname',
'username',
'avatar',
'address',
'state',
'city',
'postcode',
'country',
'company',
'phone',
'email',
'email_verified_at',
'email_verify',
'email_verify_code',
'email_verify_code_expired_at',
'email_hash',
'email_marketing',
'receive_deposit',
'identity_number',
'front_identity_number',
'back_identity_number',
'password',
'note',
'type',
'credit',
'total',
'total_pending',
'remember_token',
'status',
'gender',
'birthday',
'province_code',
'ward_code',
];
/**
* Appends for API
*/
protected $appends = [
'point',
'gender_label',
];
protected $casts = [
'gender' => \App\Enums\Client\ClientGender::class,
'email_verified_at' => 'datetime',
'two_factor_enabled' => 'boolean',
'birthday' => 'date',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Quan hệ với ModuleHosting (Hosting).
*/
public function hosting()
{
return $this->hasMany(ModuleHosting::class);
}
/**
* Quan hệ với ModuleDomain (Domain).
*/
public function domain()
{
return $this->hasMany(ModuleDomain::class);
}
/**
* Quan hệ với ModuleVmware (VPS).
*/
public function vps()
{
return $this->hasMany(ModuleVmware::class);
}
/**
* Quan hệ với Service (tbl.services).
*/
public function service()
{
return $this->hasMany(Service::class);
}
/**
* Quan hệ với Invoice.
*/
public function invoice()
{
return $this->hasMany(Invoice::class);
}
/**
* Quan hệ với Ticket.
*/
public function ticket()
{
return $this->hasMany(Ticket::class);
}
/**
* Quan hệ với Mail.
*/
public function mail()
{
return $this->hasMany(Mail::class);
}
/**
* Quan hệ với File.
*/
public function file()
{
return $this->hasMany(File::class, 'customer_id');
}
/**
* Quan hệ với Order.
*/
public function order()
{
return $this->hasMany(Order::class);
}
/**
* Quan hệ với Transaction.
*/
public function transaction()
{
return $this->hasMany(Transaction::class);
}
/**
* Quan hệ với InvoiceItem.
*/
public function invoiceItem()
{
return $this->hasMany(InvoiceItem::class);
}
/**
* Quan hệ với Log.
*/
public function log()
{
return $this->hasMany(Log::class);
}
public function acceptsMarketingEmail(): bool
{
return (bool) $this->email_marketing;
}
public function getReceiveDepositBoolAttribute()
{
return (bool) $this->receive_deposit;
}
public function getPointAttribute()
{
return (string) floor($this->total / 100);
}
public function getNameAttribute()
{
return $this->fullname;
}
public function getCmndBeforeAttribute()
{
return $this->front_identity_number ?? '';
}
public function getCmndAfterAttribute()
{
return $this->back_identity_number ?? '';
}
public function getCmndAttribute()
{
return $this->identity_number;
}
public function getOffMailMarketingAttribute()
{
return $this->email_marketing;
}
public function province()
{
return $this->belongsTo(Province::class, 'province_code', 'code');
}
public function ward()
{
return $this->belongsTo(Ward::class, 'ward_code', 'code');
}
public function getGenderLabelAttribute()
{
return $this->gender?->label() ?? 'Khác';
}
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return ['pv' => $this->passwordVersion()];
}
/**
* Dấu vân tay của hash mật khẩu, nhúng vào JWT. Đổi/đặt lại mật khẩu → giá trị đổi →
* mọi access token cũ bị ApiAuthCheck từ chối (không cần blacklist).
*/
public function passwordVersion(): string
{
return substr(hash('sha256', (string) $this->password), 0, 16);
}
}