feat: add shop lifecycle management UI — deactivate & close shop
- Add "Danger Zone" section to ShopSettings with deactivate/close actions
- CloseShopConfirmDialog: type shop name to confirm (GitHub-style)
- BFF: proxy endpoints POST /shops/{id}/deactivate and /close
- MerchantApiService: DeactivateShopAsync(), CloseShopAsync()
- CTO report documenting the gap and implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
CTO_REPORT_SHOP_DELETE.md
Normal file
117
CTO_REPORT_SHOP_DELETE.md
Normal file
@@ -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
|
||||||
@@ -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. *@
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<DialogContent>
|
||||||
|
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined" Dense="true" Class="mb-4">
|
||||||
|
Thao tác này không thể hoàn tác. Cửa hàng sẽ bị khóa vĩnh viễn.
|
||||||
|
</MudAlert>
|
||||||
|
<MudText Typo="Typo.body2" Class="mb-3">
|
||||||
|
Để xác nhận, vui lòng nhập tên cửa hàng <strong>@ShopName</strong> vào ô bên dưới:
|
||||||
|
</MudText>
|
||||||
|
<MudTextField @bind-Value="_confirmText"
|
||||||
|
Label="Nhập tên cửa hàng"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Immediate="true"
|
||||||
|
FullWidth="true"
|
||||||
|
Placeholder="@ShopName" />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel" Variant="Variant.Text" Style="text-transform: none;">Hủy</MudButton>
|
||||||
|
<MudButton OnClick="Confirm" Variant="Variant.Filled" Color="Color.Error"
|
||||||
|
Disabled="@(!IsConfirmValid)" Style="text-transform: none;">
|
||||||
|
Đóng cửa hàng vĩnh viễn
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[Parameter] public string ShopName { get; set; } = "";
|
||||||
|
|
||||||
|
private string _confirmText = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
@using WebClientTpos.Client.Services
|
@using WebClientTpos.Client.Services
|
||||||
@using WebClientTpos.Client.Pages.Admin.Shop
|
@using WebClientTpos.Client.Pages.Admin.Shop
|
||||||
@inject PosDataService DataService
|
@inject PosDataService DataService
|
||||||
|
@inject MerchantApiService MerchantApi
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@* ─── Shop info (read-only) ─── *@
|
@* ─── Shop info (read-only) ─── *@
|
||||||
<div class="admin-panel">
|
<div class="admin-panel">
|
||||||
@@ -74,6 +78,54 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* ─── 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) ─── *@
|
||||||
|
<MudPaper Class="mt-6 pa-0" Style="border: 1px solid #f44336; border-radius: 8px; background: rgba(244,67,54,0.04); overflow: hidden;">
|
||||||
|
<div style="padding: 16px 20px; border-bottom: 1px solid rgba(244,67,54,0.2);">
|
||||||
|
<MudText Typo="Typo.h6" Style="color: #f44336; font-size: 16px; font-weight: 700;">Vùng nguy hiểm</MudText>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* EN: Deactivate shop — temporarily disable *@
|
||||||
|
@* VI: Tạm ngưng cửa hàng — vô hiệu hóa tạm thời *@
|
||||||
|
<div style="padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid rgba(244,67,54,0.12);">
|
||||||
|
<div>
|
||||||
|
<MudText Typo="Typo.subtitle1" Style="font-weight: 600; font-size: 14px;">Tạm ngưng cửa hàng</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Style="color: var(--admin-text-tertiary, #999); font-size: 13px; margin-top: 2px;">
|
||||||
|
Cửa hàng sẽ không hiển thị trên POS. Có thể kích hoạt lại sau.
|
||||||
|
</MudText>
|
||||||
|
</div>
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Warning" Size="Size.Small"
|
||||||
|
OnClick="OnDeactivateShop" Disabled="_isDangerActionRunning"
|
||||||
|
Style="min-width: 120px; text-transform: none;">
|
||||||
|
@if (_isDangerActionRunning && _dangerAction == "deactivate")
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" Style="width:16px;height:16px;" />
|
||||||
|
}
|
||||||
|
Tạm ngưng
|
||||||
|
</MudButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* EN: Close shop permanently — irreversible *@
|
||||||
|
@* VI: Đóng cửa hàng vĩnh viễn — không thể hoàn tác *@
|
||||||
|
<div style="padding: 16px 20px; display: flex; align-items: center; justify-content: space-between;">
|
||||||
|
<div>
|
||||||
|
<MudText Typo="Typo.subtitle1" Style="font-weight: 600; font-size: 14px;">Đóng cửa hàng vĩnh viễn</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Style="color: var(--admin-text-tertiary, #999); font-size: 13px; margin-top: 2px;">
|
||||||
|
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.
|
||||||
|
</MudText>
|
||||||
|
</div>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Error" Size="Size.Small"
|
||||||
|
OnClick="OnCloseShop" Disabled="_isDangerActionRunning"
|
||||||
|
Style="min-width: 120px; text-transform: none;">
|
||||||
|
@if (_isDangerActionRunning && _dangerAction == "close")
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" Style="width:16px;height:16px;" />
|
||||||
|
}
|
||||||
|
Đóng vĩnh viễn
|
||||||
|
</MudButton>
|
||||||
|
</div>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter] public Guid ShopId { get; set; }
|
[Parameter] public Guid ShopId { get; set; }
|
||||||
[Parameter] public string? ShopName { get; set; }
|
[Parameter] public string? ShopName { get; set; }
|
||||||
@@ -149,4 +201,109 @@
|
|||||||
else _settingsOpenDays.Add(code);
|
else _settingsOpenDays.Add(code);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EN: Danger Zone state
|
||||||
|
// VI: Trạng thái Vùng nguy hiểm
|
||||||
|
private bool _isDangerActionRunning;
|
||||||
|
private string? _dangerAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<CloseShopConfirmDialog>
|
||||||
|
{
|
||||||
|
{ x => x.ShopName, displayName }
|
||||||
|
};
|
||||||
|
var options = new DialogOptions
|
||||||
|
{
|
||||||
|
CloseButton = true,
|
||||||
|
MaxWidth = MaxWidth.Small,
|
||||||
|
FullWidth = true
|
||||||
|
};
|
||||||
|
var dialog = await DialogService.ShowAsync<CloseShopConfirmDialog>("Đó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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,42 @@ public class MerchantApiService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> DeactivateShopAsync(Guid shopId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await AttachTokenAsync();
|
||||||
|
var response = await _http.PostAsync($"/api/bff/shops/{shopId}/deactivate", null);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EN: Permanently close a shop.
|
||||||
|
/// VI: Đóng shop vĩnh viễn.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> CloseShopAsync(Guid shopId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await AttachTokenAsync();
|
||||||
|
var response = await _http.PostAsync($"/api/bff/shops/{shopId}/close", null);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EN: Attach Bearer token to HttpClient for authorized requests.
|
/// 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.
|
/// VI: Gắn Bearer token vào HttpClient cho các request cần xác thực.
|
||||||
|
|||||||
@@ -211,6 +211,22 @@ public class ShopController : ControllerBase
|
|||||||
public Task<IActionResult> PublishShop(Guid shopId) =>
|
public Task<IActionResult> PublishShop(Guid shopId) =>
|
||||||
_merchant.PostAsync($"/api/v1/shops/{shopId}/publish", null).ProxyAsync();
|
_merchant.PostAsync($"/api/v1/shops/{shopId}/publish", null).ProxyAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("shops/{shopId:guid}/deactivate")]
|
||||||
|
public Task<IActionResult> DeactivateShop(Guid shopId) =>
|
||||||
|
_merchant.PostAsync($"/api/v1/shops/{shopId}/deactivate", null).ProxyAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EN: Close a shop permanently (irreversible).
|
||||||
|
/// VI: Đóng cửa hàng vĩnh viễn (không thể hoàn tác).
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("shops/{shopId:guid}/close")]
|
||||||
|
public Task<IActionResult> CloseShop(Guid shopId) =>
|
||||||
|
_merchant.PostAsync($"/api/v1/shops/{shopId}/close", null).ProxyAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EN: Get device tokens registered for this merchant's staff.
|
/// 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.
|
/// VI: Lấy danh sách device token đã đăng ký cho nhân viên của merchant.
|
||||||
|
|||||||
Reference in New Issue
Block a user