update
This commit is contained in:
+20
-1
@@ -24,6 +24,16 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
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',
|
||||
@@ -291,6 +301,15 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
|
||||
public function getJWTCustomClaims()
|
||||
{
|
||||
return [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailNotificationTemplate extends Model
|
||||
{
|
||||
protected $table = 'email_notification_templates';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'type',
|
||||
'subject',
|
||||
'header_color',
|
||||
'header_title',
|
||||
'blocks',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'blocks' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/** Default blocks by template type */
|
||||
public static function defaultBlocks(string $type = 'invoice_notification'): array
|
||||
{
|
||||
if ($type === 'vm_credentials') {
|
||||
return [
|
||||
['id' => 'greeting', 'type' => 'greeting', 'label' => 'Lời chào', 'enabled' => true, 'order' => 1, 'config' => []],
|
||||
['id' => 'vm_credentials', 'type' => 'vm_credentials', 'label' => 'Thông tin truy cập VPS','enabled' => true, 'order' => 2, 'config' => [
|
||||
'fields' => ['service_host', 'service_username', 'service_port', 'service_password', 'os_name'],
|
||||
]],
|
||||
['id' => 'product_info', 'type' => 'product_info', 'label' => 'Thông tin sản phẩm', 'enabled' => true, 'order' => 3, 'config' => [
|
||||
'fields' => ['product_name', 'product_billing', 'product_price', 'service_due_exp'],
|
||||
]],
|
||||
['id' => 'custom_text', 'type' => 'custom_text', 'label' => 'Lời nhắn', 'enabled' => true, 'order' => 4, 'config' => [
|
||||
'content' => 'Nếu bạn có bất kỳ thắc mắc nào, vui lòng liên hệ với chúng tôi để được hỗ trợ.',
|
||||
]],
|
||||
['id' => 'footer', 'type' => 'footer', 'label' => 'Footer', 'enabled' => true, 'order' => 5, 'config' => []],
|
||||
];
|
||||
}
|
||||
|
||||
// invoice_notification (default)
|
||||
return [
|
||||
['id' => 'greeting', 'type' => 'greeting', 'label' => 'Lời chào', 'enabled' => true, 'order' => 1, 'config' => []],
|
||||
['id' => 'invoice_info', 'type' => 'invoice_info', 'label' => 'Thông tin hóa đơn', 'enabled' => true, 'order' => 2, 'config' => [
|
||||
'fields' => ['invoice_number', 'created_at', 'due_date', 'total', 'status'],
|
||||
]],
|
||||
['id' => 'customer_info','type' => 'customer_info','label' => 'Thông tin khách hàng', 'enabled' => false, 'order' => 3, 'config' => [
|
||||
'fields' => ['name', 'email'],
|
||||
]],
|
||||
['id' => 'order_items', 'type' => 'order_items', 'label' => 'Chi tiết sản phẩm', 'enabled' => true, 'order' => 4, 'config' => []],
|
||||
['id' => 'attachments', 'type' => 'attachments', 'label' => 'File đính kèm', 'enabled' => true, 'order' => 5, 'config' => []],
|
||||
['id' => 'custom_text', 'type' => 'custom_text', 'label' => 'Lời nhắn', 'enabled' => true, 'order' => 6, 'config' => [
|
||||
'content' => 'Nếu bạn có bất kỳ thắc mắc nào, vui lòng liên hệ với chúng tôi để được hỗ trợ.',
|
||||
]],
|
||||
['id' => 'footer', 'type' => 'footer', 'label' => 'Footer', 'enabled' => true, 'order' => 7, 'config' => []],
|
||||
];
|
||||
}
|
||||
|
||||
/** Field definitions exposed per block type */
|
||||
public static function blockFieldDefinitions(): array
|
||||
{
|
||||
return [
|
||||
'invoice_info' => [
|
||||
['key' => 'invoice_number', 'label' => 'Số hóa đơn'],
|
||||
['key' => 'created_at', 'label' => 'Ngày tạo'],
|
||||
['key' => 'due_date', 'label' => 'Hạn thanh toán'],
|
||||
['key' => 'total', 'label' => 'Tổng tiền'],
|
||||
['key' => 'sub_total', 'label' => 'Tiền dịch vụ'],
|
||||
['key' => 'tax', 'label' => 'Thuế'],
|
||||
['key' => 'status', 'label' => 'Trạng thái'],
|
||||
['key' => 'payment_method', 'label' => 'Phương thức TT'],
|
||||
],
|
||||
'customer_info' => [
|
||||
['key' => 'name', 'label' => 'Tên khách hàng'],
|
||||
['key' => 'email', 'label' => 'Email'],
|
||||
['key' => 'address', 'label' => 'Địa chỉ'],
|
||||
],
|
||||
'vm_credentials' => [
|
||||
['key' => 'service_host', 'label' => 'Địa chỉ IP'],
|
||||
['key' => 'service_username', 'label' => 'Username'],
|
||||
['key' => 'service_port', 'label' => 'Port'],
|
||||
['key' => 'service_password', 'label' => 'Mật khẩu'],
|
||||
['key' => 'os_name', 'label' => 'Hệ điều hành'],
|
||||
],
|
||||
'product_info' => [
|
||||
['key' => 'product_name', 'label' => 'Tên sản phẩm'],
|
||||
['key' => 'product_billing', 'label' => 'Chu kỳ thanh toán'],
|
||||
['key' => 'product_price', 'label' => 'Giá dịch vụ'],
|
||||
['key' => 'service_due_exp', 'label' => 'Ngày hết hạn'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -58,15 +58,13 @@ class Order extends Model
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* orders.status là tinyInteger (OrderStatus), không phải chuỗi — bản cũ match 'pending'/
|
||||
* 'completed'… nên mọi giá trị đều rơi vào default và hiển thị ra "0", "1", "2".
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
'pending' => 'Chờ xử lý',
|
||||
'processing' => 'Đang xử lý',
|
||||
'completed' => 'Hoàn tất',
|
||||
'cancelled' => 'Đã hủy',
|
||||
default => ucfirst($this->status),
|
||||
};
|
||||
return \App\Enums\Order\OrderStatus::tryFrom((int) $this->status)?->label() ?? 'Không xác định';
|
||||
}
|
||||
|
||||
public function payment()
|
||||
|
||||
+12
-15
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Support\EmailTemplateShell;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -81,7 +82,6 @@ class Template extends Model
|
||||
'vm_creation_failed' => 'Khởi tạo VPS thất bại',
|
||||
'vm_password_changed' => 'Đổi mật khẩu VPS',
|
||||
'vm_rebuild_failed' => 'Rebuild VPS thất bại',
|
||||
'holiday_notice' => 'Thông báo nghỉ lễ',
|
||||
'domain_order_approved' => 'Tên miền được duyệt',
|
||||
'domain_order_rejected' => 'Tên miền bị từ chối',
|
||||
'order_created' => 'Xác nhận đơn hàng',
|
||||
@@ -98,8 +98,11 @@ class Template extends Model
|
||||
'customer_signup_verify' => 'Xác thực đăng ký tài khoản',
|
||||
'hosting_information' => 'Thông tin truy cập Hosting',
|
||||
'customer_password_reset' => 'Khôi phục mật khẩu khách hàng',
|
||||
'customer_password_reset_link' => 'Link đặt lại mật khẩu khách hàng',
|
||||
'service_expiry' => 'Dịch vụ sắp hết hạn',
|
||||
'customer_payment' => 'Thông báo thanh toán khách hàng',
|
||||
'invoice_payment_reminder' => 'Nhắc thanh toán hoá đơn',
|
||||
'invoice_notification' => 'Thông báo hoá đơn / gia hạn cước',
|
||||
];
|
||||
|
||||
public function invoiceTemplate()
|
||||
@@ -109,26 +112,20 @@ class Template extends Model
|
||||
|
||||
/**
|
||||
* Wrap $contentHtml with the admin-editable "layout" template (scenario=layout),
|
||||
* replacing its {{$content}} placeholder. Falls back to the static blade
|
||||
* template-camp wrapper when no active layout template is configured.
|
||||
* replacing its {{$content}} placeholder.
|
||||
*
|
||||
* Không còn fallback về blade: mọi email đều render từ tbl.template. Thiếu layout thì
|
||||
* dùng khung header/footer chuẩn trong code thay vì gửi ra nội dung trần.
|
||||
*/
|
||||
public static function renderWithLayout(?string $subject, string $contentHtml): string
|
||||
{
|
||||
$layout = self::where('scenario', 'layout')->where('status', 1)->first();
|
||||
|
||||
if (!$layout || !$layout->message) {
|
||||
return view('emails.customer.template-camp', [
|
||||
'content' => $contentHtml,
|
||||
'emailSubject' => $subject,
|
||||
])->render();
|
||||
}
|
||||
$body = $layout && $layout->message
|
||||
? str_replace('{{$content}}', $contentHtml, $layout->message)
|
||||
: EmailTemplateShell::wrap(EmailTemplateShell::body($contentHtml));
|
||||
|
||||
$body = str_replace('{{$content}}', $contentHtml, $layout->message);
|
||||
|
||||
return view('emails.customer.template-envelope', [
|
||||
'body' => $body,
|
||||
'emailSubject' => $subject,
|
||||
])->render();
|
||||
return EmailTemplateShell::document($subject, $body);
|
||||
}
|
||||
|
||||
public function type()
|
||||
|
||||
@@ -19,6 +19,7 @@ class Transaction extends Model
|
||||
'public_code',
|
||||
'trans',
|
||||
'amount',
|
||||
'received_amount',
|
||||
'customer_id',
|
||||
'user_id',
|
||||
'note',
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ class User extends Authenticatable implements JWTSubject
|
||||
|
||||
public function getNameAttribute(): string
|
||||
{
|
||||
return trim($this->firstname . ' ' . $this->lastname);
|
||||
return trim((string) $this->fullname);
|
||||
}
|
||||
|
||||
public function staffOnline()
|
||||
|
||||
Reference in New Issue
Block a user