Files
baodev0andClaude Sonnet 5.5 e1ffbf52fe fix(customer): lưu và trả địa chỉ khách hàng
Bảng customers chỉ có cột address nên address_1/address_2 từ form admin bị bỏ khi validate.
Thêm cột address_2, fillable, rule validate và trả đủ field địa chỉ trong CustomerResource.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 08:37:57 +07:00

286 lines
6.3 KiB
PHP

<?php
namespace App\Models;
use App\Services\Cache\CacheServiceInterface;
use App\Models\Concerns\Markable;
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, Markable;
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',
'address_2',
'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',
'tax_code',
'invoice_company',
'invoice_address',
'invoice_email',
];
/**
* 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);
}
}