feat: hồ sơ khách (ngày sinh, tỉnh/phường theo mã), gộp menu + nút tắt, thông báo theo uuid

- PUT v1/client/update: gender theo enum ClientGender (0 Nam/1 Nữ), thêm
  birthday (cột mới), province_code/ward_code validate theo bảng provinces/wards
  (phường phải thuộc tỉnh). Migration nạp dữ liệu hành chính 2025 từ
  province_ward_import.sql (chỉ bảng trống), API GET v1/provinces[/{code}/wards].
- CustomerObserver/OrderObserver log giá trị cũ bằng getRawOriginal: getOriginal
  trả enum object, nối chuỗi gây 500 khi khách đổi giới tính.
- ClientResource: các cờ trả boolean, CCCD chỉ trả cccd_updated (không lộ ảnh/số).
- GET v1/setting/menu trả cả items + quick_links, bỏ v1/setting/quick-links.
- Thông báo admin/khách tra theo uuid thay vì id tự tăng.
- Chặn gửi template marketing cho khách đã tắt nhận email marketing.
- Xoá ProvinceWardSeeder và Customer::$updatable không dùng.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-13 16:03:18 +07:00
co-authored by Claude Opus 5.5
parent 12d5b4951c
commit bf78f3b810
30 changed files with 461 additions and 183 deletions
+8 -8
View File
@@ -13,6 +13,7 @@ use App\Http\Controllers\Api\Service\SnapshotController;
use App\Http\Controllers\Api\Service\VmController;
use App\Http\Controllers\Api\Service\VmRebuildController;
use App\Http\Controllers\Api\Admin\Domain\InetDomainController;
use App\Http\Controllers\Api\Client\AdministrativeDivisionController;
use App\Http\Controllers\Api\Client\BankController as ClientBankController;
use App\Http\Controllers\Api\Client\Docs\PostCategoryController as ClientPostCategoryController;
use App\Http\Controllers\Api\Client\Docs\PostController as ClientPostController;
@@ -39,12 +40,6 @@ Route::prefix('v1')
Route::post('forgot-password', 'forgotPassword')->middleware(['throttle:client-auth', 'turnstile'])->name('forgot-password');
Route::post('reset-password', 'resetPassword')->middleware(['throttle:client-auth', 'turnstile'])->name('reset-password');
Route::get('reset-password/{token}', 'showResetToken')->middleware('throttle:client-auth')->name('reset-password.show');
// Ảnh CCCD nằm trên disk private, chỉ truy cập qua URL ký (ClientResource cấp, hết hạn 60 phút)
Route::get('cccd/{customer}/{side}', 'cccdImage')
->whereNumber('customer')
->whereIn('side', ['front', 'back'])
->middleware('signed:relative')
->name('cccd');
Route::post('impersonate/exchange', 'impersonateExchange')->name('impersonate.exchange');
Route::post('login/two-factor', 'verifyLoginTwoFactor')->middleware('throttle:client-auth')->name('login.two-factor');
Route::post('login/two-factor/resend', 'resendLoginTwoFactor')->middleware('throttle:client-auth')->name('login.two-factor.resend');
@@ -151,7 +146,8 @@ Route::prefix('v1')
->controller(NotificationController::class)
->group(function () {
Route::get('/', fn () => app(NotificationController::class)->index(request(), 'client'));
Route::patch('{id}/read', fn ($id) => app(NotificationController::class)->markRead(request(), 'client', (int) $id));
Route::get('{uuid}', fn ($uuid) => app(NotificationController::class)->show(request(), 'client', $uuid))->whereUuid('uuid');
Route::patch('{uuid}/read', fn ($uuid) => app(NotificationController::class)->markRead(request(), 'client', $uuid))->whereUuid('uuid');
Route::patch('read-all', fn () => app(NotificationController::class)->markAllRead(request(), 'client'));
});
@@ -207,7 +203,6 @@ Route::prefix('v1')
Route::get('email', 'getEmailSetting')->name('email');
Route::get('turnstile', 'getTurnstileSetting')->name('turnstile');
Route::get('menu', 'getMenu')->name('menu');
Route::get('quick-links', 'getQuickLinks')->name('quick-links');
Route::get('popups', 'getPopups')->name('popups');
});
});
@@ -215,6 +210,11 @@ Route::prefix('v1')
// Public bank list for deposit form
Route::get('banks', [ClientBankController::class, 'index'])->name('banks.list');
Route::controller(AdministrativeDivisionController::class)->group(function () {
Route::get('provinces', 'provinces')->name('provinces');
Route::get('provinces/{province}/wards', 'wards')->where('province', '[0-9]+')->name('provinces.wards');
});
Route::prefix('post-categories')
->controller(ClientPostCategoryController::class)
->as('client.docs.categories.')