diff --git a/CTO_REPORT_SHOP_DELETE.md b/CTO_REPORT_SHOP_DELETE.md
new file mode 100644
index 00000000..484cc8be
--- /dev/null
+++ b/CTO_REPORT_SHOP_DELETE.md
@@ -0,0 +1,117 @@
+# CTO Report — Missing Shop Delete/Deactivate UI
+
+> Date: 2026-03-14
+> Reporter: QA Team (Automated Chrome Testing)
+> Priority: P1 (Functional Gap)
+> Status: OPEN
+
+---
+
+## Issue Summary
+
+Trang **Thiết lập cửa hàng** (`/admin/shop/{shopId}/settings`) không có chức năng xóa, đóng, hoặc vô hiệu hóa cửa hàng. Admin tạo shop mới nhưng không thể xóa/đóng shop từ UI.
+
+**URL kiểm tra**: `http://localhost:3001/admin/shop/0d25b74e-f855-4fba-9ca4-baa2c89e4811/settings`
+
+---
+
+## Current State
+
+### UI (ShopSettings.razor) — Thiếu hoàn toàn
+Trang Thiết lập chỉ có 3 section:
+1. **Thông tin cửa hàng** — Tên, Ngành hàng (read-only)
+2. **Giờ & ngày hoạt động** — Giờ mở/đóng, ngày kinh doanh
+3. **Tính năng cửa hàng** — 6 toggles (tồn kho, bàn, bếp, đặt lịch, vận chuyển, giao hàng)
+
+**Không có**: Nút xóa, đóng, vô hiệu hóa, hoặc lưu trữ cửa hàng.
+
+### Backend (merchant-service-net) — Đã có đầy đủ
+API endpoints đã implement và có handler + validation:
+
+| Action | Endpoint | Domain Method | Status Transition |
+|--------|----------|---------------|-------------------|
+| Vô hiệu hóa | `POST /api/v1/shops/{shopId}/deactivate` | `Shop.SetInactive()` | Active → Inactive (reversible) |
+| Đóng cửa hàng | `POST /api/v1/shops/{shopId}/close` | `Shop.Close()` | Any → Closed (permanent) |
+| Xóa mềm | — (no controller endpoint) | `Shop.Delete()` | Sets `_isDeleted = true` |
+
+**Authorization**: Handlers validate merchant ownership via JWT claims trước khi thực hiện.
+
+**Domain Events**: `ShopClosedDomainEvent` được raise khi đóng shop.
+
+**Shop Status Enum**: `Draft` → `Active` → `Inactive` (reversible) → `Closed` (permanent)
+
+### Files liên quan
+
+| Layer | File | Notes |
+|-------|------|-------|
+| UI | `Pages/Admin/Shop/ShopSettings.razor` | Thiếu delete/deactivate UI |
+| UI | `Pages/Admin/Shop/ShopPage.razor` | Chỉ có edit, không có delete |
+| API | `ShopsController.cs` (line 211-229) | Endpoints `/deactivate`, `/close` đã có |
+| Handler | `ShopStatusCommandHandlers.cs` | `SetShopInactiveCommandHandler`, `CloseShopCommandHandler` |
+| Domain | `Shop.cs` (line 298-319, 407-414) | `SetInactive()`, `Close()`, `Delete()` methods |
+| Domain | `ShopStatus.cs` | Status enum: Draft, Active, Inactive, Closed |
+
+---
+
+## Impact
+
+- **User Experience**: Admin không thể quản lý lifecycle cửa hàng (tạo → vô hiệu hóa → đóng/xóa)
+- **Data Hygiene**: Shop test/demo không thể xóa, gây lộn xộn trong danh sách shop
+- **Business Logic**: Merchant tạo shop mới cho mùa kinh doanh (ví dụ pop-up store) nhưng không thể đóng khi hết mùa
+
+---
+
+## Recommended Implementation
+
+### Option A: Thêm "Danger Zone" vào ShopSettings.razor (Recommended)
+
+Thêm section cuối trang Thiết lập, tương tự GitHub repository settings:
+
+```
+┌─ Vùng nguy hiểm ──────────────────────────────────────┐
+│ │
+│ Tạm ngưng cửa hàng [Tạm ngưng] │
+│ Shop sẽ không hiển thị trên POS. │
+│ Có thể kích hoạt lại. │
+│ │
+│ ───────────────────────────────────────────────────── │
+│ │
+│ Đóng cửa hàng vĩnh viễn [Đóng shop] │
+│ Không thể hoàn tác. Tất cả dữ liệu │
+│ sẽ được lưu trữ nhưng shop sẽ bị khóa. │
+│ │
+│ ───────────────────────────────────────────────────── │
+│ │
+│ Xóa cửa hàng [Xóa shop] │
+│ Xóa mềm — shop sẽ không hiển thị │
+│ nhưng dữ liệu vẫn được giữ trong DB. │
+│ │
+└─────────────────────────────────────────────────────────┘
+```
+
+### UX Requirements
+1. **Confirmation dialog** (MudDialog) với nhập tên shop để xác nhận
+2. **Phân quyền**: Chỉ Owner/Admin mới thấy Danger Zone
+3. **Cascading effect notice**: Thông báo impact lên staff, orders, inventory
+4. **"Tạm ngưng"** có nút **"Kích hoạt lại"** khi shop đang Inactive
+
+### API Calls (BFF)
+- Cần thêm BFF proxy endpoints trong `WebClientTpos.Server`:
+ - `POST /api/bff/shops/{shopId}/deactivate`
+ - `POST /api/bff/shops/{shopId}/close`
+- Forward đến `MerchantService` endpoints đã có
+
+### Effort Estimate
+- **Frontend**: ~2-3 hours (Danger Zone UI + confirmation dialogs)
+- **BFF**: ~30 minutes (proxy endpoints)
+- **Backend**: 0 (đã có đầy đủ)
+- **Testing**: ~1 hour (E2E verify)
+
+---
+
+## Priority Justification
+
+**P1 (High)** vì:
+- Backend đã implement đầy đủ → chỉ thiếu UI, effort thấp
+- Đây là chức năng quản lý cơ bản mà mọi merchant platform cần có
+- Ảnh hưởng trực tiếp đến trải nghiệm admin khi quản lý nhiều shop
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/CloseShopConfirmDialog.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/CloseShopConfirmDialog.razor
new file mode 100644
index 00000000..ac138c3c
--- /dev/null
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/CloseShopConfirmDialog.razor
@@ -0,0 +1,49 @@
+@* EN: Dialog requiring the user to type the shop name to confirm permanent closure. *@
+@* VI: Dialog yêu cầu người dùng nhập tên cửa hàng để xác nhận đóng vĩnh viễn. *@
+
+
+
+
+ Thao tác này không thể hoàn tác. Cửa hàng sẽ bị khóa vĩnh viễn.
+
+
+ Để xác nhận, vui lòng nhập tên cửa hàng @ShopName vào ô bên dưới:
+
+
+
+
+ Hủy
+
+ Đóng cửa hàng vĩnh viễn
+
+
+
+
+@code {
+ [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
+
+ ///
+ /// EN: The shop name the user must type to confirm.
+ /// VI: Tên cửa hàng mà người dùng phải nhập để xác nhận.
+ ///
+ [Parameter] public string ShopName { get; set; } = "";
+
+ private string _confirmText = "";
+
+ ///
+ /// EN: Check if the typed text matches the shop name (case-insensitive).
+ /// VI: Kiểm tra xem text nhập vào có khớp tên shop không (không phân biệt hoa thường).
+ ///
+ private bool IsConfirmValid =>
+ !string.IsNullOrWhiteSpace(_confirmText) &&
+ string.Equals(_confirmText.Trim(), ShopName.Trim(), StringComparison.OrdinalIgnoreCase);
+
+ private void Cancel() => MudDialog.Cancel();
+ private void Confirm() => MudDialog.Close(DialogResult.Ok(true));
+}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSettings.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSettings.razor
index 9877b098..d6a679ab 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSettings.razor
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/Shop/ShopSettings.razor
@@ -1,6 +1,10 @@
@using WebClientTpos.Client.Services
@using WebClientTpos.Client.Pages.Admin.Shop
@inject PosDataService DataService
+@inject MerchantApiService MerchantApi
+@inject IDialogService DialogService
+@inject ISnackbar Snackbar
+@inject NavigationManager NavigationManager
@* ─── Shop info (read-only) ─── *@
@@ -74,6 +78,54 @@
}
+@* ─── EN: Danger Zone — shop lifecycle actions (deactivate, close) ─── *@
+@* ─── VI: Vùng nguy hiểm — thao tác vòng đời cửa hàng (tạm ngưng, đóng) ─── *@
+
+
+ Vùng nguy hiểm
+
+
+ @* EN: Deactivate shop — temporarily disable *@
+ @* VI: Tạm ngưng cửa hàng — vô hiệu hóa tạm thời *@
+
+
+ Tạm ngưng cửa hàng
+
+ Cửa hàng sẽ không hiển thị trên POS. Có thể kích hoạt lại sau.
+
+
+
+ @if (_isDangerActionRunning && _dangerAction == "deactivate")
+ {
+
+ }
+ Tạm ngưng
+
+
+
+ @* EN: Close shop permanently — irreversible *@
+ @* VI: Đóng cửa hàng vĩnh viễn — không thể hoàn tác *@
+
+
+ Đóng cửa hàng vĩnh viễn
+
+ Không thể hoàn tác. Tất cả dữ liệu sẽ được lưu trữ nhưng cửa hàng sẽ bị khóa vĩnh viễn.
+
+
+
+ @if (_isDangerActionRunning && _dangerAction == "close")
+ {
+
+ }
+ Đóng vĩnh viễn
+
+
+
+
@code {
[Parameter] public Guid ShopId { get; set; }
[Parameter] public string? ShopName { get; set; }
@@ -149,4 +201,109 @@
else _settingsOpenDays.Add(code);
StateHasChanged();
}
+
+ // EN: Danger Zone state
+ // VI: Trạng thái Vùng nguy hiểm
+ private bool _isDangerActionRunning;
+ private string? _dangerAction;
+
+ ///
+ /// EN: Show deactivate confirmation dialog and call API.
+ /// VI: Hiển thị dialog xác nhận tạm ngưng và gọi API.
+ ///
+ private async Task OnDeactivateShop()
+ {
+ if (ShopId == Guid.Empty) return;
+
+ var confirmed = await DialogService.ShowMessageBox(
+ "Tạm ngưng cửa hàng",
+ $"Bạn có chắc muốn tạm ngưng cửa hàng {_shopName ?? "này"}?",
+ yesText: "Tạm ngưng", cancelText: "Hủy");
+
+ if (confirmed != true) return;
+
+ _isDangerActionRunning = true;
+ _dangerAction = "deactivate";
+ StateHasChanged();
+
+ try
+ {
+ var ok = await MerchantApi.DeactivateShopAsync(ShopId);
+ if (ok)
+ {
+ Snackbar.Add("Đã tạm ngưng cửa hàng thành công.", Severity.Success);
+ NavigationManager.NavigateTo("/admin");
+ }
+ else
+ {
+ Snackbar.Add("Không thể tạm ngưng cửa hàng. Vui lòng thử lại.", Severity.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"Lỗi: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _isDangerActionRunning = false;
+ _dangerAction = null;
+ StateHasChanged();
+ }
+ }
+
+ ///
+ /// EN: Show close confirmation dialog (requires typing shop name) and call API.
+ /// VI: Hiển thị dialog xác nhận đóng (yêu cầu nhập tên shop) và gọi API.
+ ///
+ private async Task OnCloseShop()
+ {
+ if (ShopId == Guid.Empty) return;
+
+ var displayName = _shopName ?? "cửa hàng";
+
+ // EN: Custom inline dialog requiring the user to type the shop name to confirm.
+ // VI: Dialog tùy chỉnh yêu cầu người dùng nhập tên cửa hàng để xác nhận.
+ var parameters = new DialogParameters
+ {
+ { x => x.ShopName, displayName }
+ };
+ var options = new DialogOptions
+ {
+ CloseButton = true,
+ MaxWidth = MaxWidth.Small,
+ FullWidth = true
+ };
+ var dialog = await DialogService.ShowAsync("Đóng cửa hàng vĩnh viễn", parameters, options);
+ var result = await dialog.Result;
+
+ if (result == null || result.Canceled) return;
+
+ _isDangerActionRunning = true;
+ _dangerAction = "close";
+ StateHasChanged();
+
+ try
+ {
+ var ok = await MerchantApi.CloseShopAsync(ShopId);
+ if (ok)
+ {
+ Snackbar.Add("Đã đóng cửa hàng vĩnh viễn.", Severity.Success);
+ NavigationManager.NavigateTo("/admin");
+ }
+ else
+ {
+ Snackbar.Add("Không thể đóng cửa hàng. Vui lòng thử lại.", Severity.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"Lỗi: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _isDangerActionRunning = false;
+ _dangerAction = null;
+ StateHasChanged();
+ }
+ }
}
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/MerchantApiService.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/MerchantApiService.cs
index 1e6e2903..d4d181ec 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/MerchantApiService.cs
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Services/MerchantApiService.cs
@@ -99,6 +99,42 @@ public class MerchantApiService
}
}
+ ///
+ /// EN: Deactivate a shop (temporarily disable operations).
+ /// VI: Tạm ngưng hoạt động shop (vô hiệu hóa tạm thời).
+ ///
+ public async Task DeactivateShopAsync(Guid shopId)
+ {
+ try
+ {
+ await AttachTokenAsync();
+ var response = await _http.PostAsync($"/api/bff/shops/{shopId}/deactivate", null);
+ return response.IsSuccessStatusCode;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// EN: Permanently close a shop.
+ /// VI: Đóng shop vĩnh viễn.
+ ///
+ public async Task CloseShopAsync(Guid shopId)
+ {
+ try
+ {
+ await AttachTokenAsync();
+ var response = await _http.PostAsync($"/api/bff/shops/{shopId}/close", null);
+ return response.IsSuccessStatusCode;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
///
/// EN: Attach Bearer token to HttpClient for authorized requests.
/// VI: Gắn Bearer token vào HttpClient cho các request cần xác thực.
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/ShopController.cs b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/ShopController.cs
index 0a45d16a..4a488bcf 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/ShopController.cs
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Server/Controllers/ShopController.cs
@@ -211,6 +211,22 @@ public class ShopController : ControllerBase
public Task PublishShop(Guid shopId) =>
_merchant.PostAsync($"/api/v1/shops/{shopId}/publish", null).ProxyAsync();
+ ///
+ /// EN: Deactivate a shop (set inactive, can be reactivated later).
+ /// VI: Tạm ngưng hoạt động cửa hàng (có thể kích hoạt lại sau).
+ ///
+ [HttpPost("shops/{shopId:guid}/deactivate")]
+ public Task DeactivateShop(Guid shopId) =>
+ _merchant.PostAsync($"/api/v1/shops/{shopId}/deactivate", null).ProxyAsync();
+
+ ///
+ /// EN: Close a shop permanently (irreversible).
+ /// VI: Đóng cửa hàng vĩnh viễn (không thể hoàn tác).
+ ///
+ [HttpPost("shops/{shopId:guid}/close")]
+ public Task CloseShop(Guid shopId) =>
+ _merchant.PostAsync($"/api/v1/shops/{shopId}/close", null).ProxyAsync();
+
///
/// EN: Get device tokens registered for this merchant's staff.
/// VI: Lấy danh sách device token đã đăng ký cho nhân viên của merchant.