feat(web-client-tpos): Phase C-E — shop settings, top products report, enhanced placeholders
BFF Endpoints (3 new):
- GET/PUT shops/{id}/settings — shop features config, hours, days
- GET reports/top-products — bestselling products by quantity + revenue
PosDataService (3 new methods):
- GetShopSettings, UpdateShopSettings, GetTopProducts
ShopPage UI (149 lines):
- Settings tab: opening hours, business days, features config form
- Reports tab: top products table alongside revenue report
- Enhanced default placeholder for sections without DB tables
This commit is contained in:
@@ -926,25 +926,26 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (_reportProducts.Any())
|
||||
@* ─── Top products from real order_items data ─── *@
|
||||
@if (_topProducts.Any())
|
||||
{
|
||||
<div class="admin-panel" style="margin-bottom:16px;">
|
||||
<div class="admin-panel__header"><h3 style="font-size:14px;font-weight:700;margin:0;">Top sản phẩm</h3></div>
|
||||
<div class="admin-panel__header"><h3 style="font-size:14px;font-weight:700;margin:0;">Top sản phẩm bán chạy</h3></div>
|
||||
<div class="admin-panel__body" style="padding:0;">
|
||||
<table class="admin-table" style="width:100%;"><thead><tr>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">#</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Sản phẩm</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Giá</th>
|
||||
<th style="padding:12px 16px;text-align:center;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Loại</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Tên SP</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Đã bán</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;text-transform:uppercase;color:var(--admin-text-tertiary);">Doanh thu</th>
|
||||
</tr></thead><tbody>
|
||||
@{ var rank = 1; }
|
||||
@foreach (var p in _reportProducts.OrderByDescending(x => x.Price).Take(10))
|
||||
@{ var tpRank = 1; }
|
||||
@foreach (var tp in _topProducts)
|
||||
{
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:700;color:var(--admin-orange-primary);">@(rank++)</td>
|
||||
<td style="padding:12px 16px;font-weight:600;">@p.Name</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:var(--admin-orange-primary);">@FormatVND(p.Price)</td>
|
||||
<td style="padding:12px 16px;text-align:center;font-size:12px;color:var(--admin-text-tertiary);">@(p.Type ?? "—")</td>
|
||||
<td style="padding:12px 16px;font-weight:700;color:var(--admin-orange-primary);">@(tpRank++)</td>
|
||||
<td style="padding:12px 16px;font-weight:600;">@(tp.ProductName ?? "—")</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:#3B82F6;">@tp.TotalSold</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-weight:600;color:var(--admin-orange-primary);">@FormatVND(tp.TotalRevenue)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
@@ -1269,6 +1270,7 @@
|
||||
|
||||
// ═══ SETTINGS ═══
|
||||
case "settings":
|
||||
@* ─── Shop info (read-only) ─── *@
|
||||
<div class="admin-panel">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Thông tin cửa hàng</h3></div>
|
||||
<div class="admin-panel__body">
|
||||
@@ -1278,39 +1280,55 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@* ─── Opening hours + business days ─── *@
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Giờ hoạt động</h3></div>
|
||||
<div class="admin-panel__body">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Giờ mở cửa</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">08:00</div></div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Giờ đóng cửa</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">22:00</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Thuế & Phí</h3></div>
|
||||
<div class="admin-panel__body">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">VAT (%)</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">10%</div></div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Phí dịch vụ (%)</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">5%</div></div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Phí ship mặc định</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">15,000₫</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">🧾 Cài đặt hóa đơn / Bill</h3></div>
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Giờ & ngày hoạt động</h3></div>
|
||||
<div class="admin-panel__body">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;">
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Header hóa đơn</label><input type="text" value="Cảm ơn quý khách!" style="width:100%;padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;color:var(--admin-text-primary);" /></div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Footer hóa đơn</label><input type="text" value="Hẹn gặp lại!" style="width:100%;padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;color:var(--admin-text-primary);" /></div>
|
||||
<div>
|
||||
<label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Giờ mở cửa</label>
|
||||
<input type="time" value="@_settingsOpenTime" @oninput="@(e => _settingsOpenTime = e.Value?.ToString() ?? "")" style="width:100%;padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;color:var(--admin-text-primary);" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Giờ đóng cửa</label>
|
||||
<input type="time" value="@_settingsCloseTime" @oninput="@(e => _settingsCloseTime = e.Value?.ToString() ?? "")" style="width:100%;padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;color:var(--admin-text-primary);" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Khổ giấy</label><div style="padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:14px;">80mm</div></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-top:18px;"><input type="checkbox" checked style="accent-color:var(--admin-orange-primary);" /><label style="font-size:13px;">Hiển logo</label></div>
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-top:18px;"><input type="checkbox" style="accent-color:var(--admin-orange-primary);" /><label style="font-size:13px;">In QR Code</label></div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="font-size:12px;font-weight:600;display:block;margin-bottom:8px;">Ngày kinh doanh</label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||
@foreach (var (day, code) in new[] { ("T2","Mon"),("T3","Tue"),("T4","Wed"),("T5","Thu"),("T6","Fri"),("T7","Sat"),("CN","Sun") })
|
||||
{
|
||||
var isOn = _settingsOpenDays.Contains(code);
|
||||
<button type="button" @onclick="@(() => ToggleDay(code))"
|
||||
style="width:40px;height:40px;border-radius:10px;border:1px solid @(isOn ? "var(--admin-orange-primary)" : "var(--admin-border-subtle)");background:@(isOn ? "rgba(255,92,0,0.15)" : "var(--admin-bg-elevated)");color:@(isOn ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");font-size:12px;font-weight:@(isOn ? "700" : "500");cursor:pointer;">
|
||||
@day
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@* ─── Features config JSON ─── *@
|
||||
<div class="admin-panel" style="margin-top:16px;">
|
||||
<div class="admin-panel__header"><h3 class="admin-panel__title">Cấu hình tính năng (JSON)</h3></div>
|
||||
<div class="admin-panel__body">
|
||||
<textarea @bind="_settingsFeaturesConfig" rows="6"
|
||||
placeholder="{}"
|
||||
style="width:100%;padding:10px 14px;border-radius:8px;background:var(--admin-bg-elevated);border:1px solid var(--admin-border-subtle);font-size:13px;font-family:monospace;color:var(--admin-text-primary);resize:vertical;"></textarea>
|
||||
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:8px 0 0;">Nhập JSON để bật/tắt tính năng cho cửa hàng này.</p>
|
||||
</div>
|
||||
</div>
|
||||
@* ─── Save button ─── *@
|
||||
<div style="margin-top:16px;display:flex;align-items:center;gap:12px;">
|
||||
<button class="admin-btn-primary" @onclick="SaveShopSettings" style="display:inline-flex;align-items:center;gap:8px;">
|
||||
<i data-lucide="save" style="width:16px;height:16px;"></i>Lưu thiết lập
|
||||
</button>
|
||||
@if (_settingsMessage != null)
|
||||
{
|
||||
<span style="font-size:13px;color:@(_settingsSuccess ? "#22C55E" : "#EF4444");">@_settingsMessage</span>
|
||||
}
|
||||
</div>
|
||||
break;
|
||||
|
||||
// ═══ C2: RECIPES / NGUYÊN LIỆU (Café) ═══
|
||||
@@ -1713,7 +1731,12 @@
|
||||
<i data-lucide="@_sectionIcon" style="width:36px;height:36px;color:var(--admin-orange-primary);"></i>
|
||||
</div>
|
||||
<h2 style="font-size:22px;font-weight:700;margin:0 0 8px;color:var(--pos-text-primary, #FFFFFF);">@_sectionTitle</h2>
|
||||
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">@_sectionDescription</p>
|
||||
<p style="font-size:13px;color:#F59E0B;font-weight:600;margin:0 0 8px;">Tính năng đang phát triển</p>
|
||||
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0 0 20px;max-width:420px;margin-left:auto;margin-right:auto;">@_sectionDescription</p>
|
||||
<div style="display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border-radius:10px;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.25);">
|
||||
<i data-lucide="clock" style="width:14px;height:14px;color:#F59E0B;"></i>
|
||||
<span style="font-size:12px;color:#F59E0B;font-weight:600;">Sẽ ra mắt trong phiên bản tiếp theo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
@@ -1833,6 +1856,16 @@
|
||||
// Revenue report state
|
||||
private string _reportPeriod = "daily";
|
||||
private List<PosDataService.RevenueReportItem> _revenueReport = new();
|
||||
// Settings state
|
||||
private PosDataService.ShopSettingsInfo? _shopSettings;
|
||||
private string _settingsOpenTime = "";
|
||||
private string _settingsCloseTime = "";
|
||||
private string _settingsOpenDays = "";
|
||||
private string _settingsFeaturesConfig = "{}";
|
||||
private string? _settingsMessage;
|
||||
private bool _settingsSuccess;
|
||||
// Top products state
|
||||
private List<PosDataService.TopProductInfo> _topProducts = new();
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
|
||||
@@ -1919,6 +1952,11 @@
|
||||
case "reports":
|
||||
_reportOrders = await DataService.GetOrdersAsync(_shopGuid);
|
||||
_reportProducts = await DataService.GetAllProductsAsync(_shopGuid);
|
||||
_topProducts = await DataService.GetTopProductsAsync(_shopGuid);
|
||||
break;
|
||||
case "settings":
|
||||
if (_shopGuid.HasValue)
|
||||
await LoadShopSettings();
|
||||
break;
|
||||
case "promotions":
|
||||
_campaigns = await DataService.GetCampaignsAsync();
|
||||
@@ -1936,6 +1974,47 @@
|
||||
finally { IsLoading = false; }
|
||||
}
|
||||
|
||||
private async Task LoadShopSettings()
|
||||
{
|
||||
if (!_shopGuid.HasValue) return;
|
||||
try
|
||||
{
|
||||
_shopSettings = await DataService.GetShopSettingsAsync(_shopGuid.Value);
|
||||
if (_shopSettings != null)
|
||||
{
|
||||
_settingsOpenTime = _shopSettings.OpenTime ?? "";
|
||||
_settingsCloseTime = _shopSettings.CloseTime ?? "";
|
||||
_settingsOpenDays = _shopSettings.OpenDays ?? "";
|
||||
_settingsFeaturesConfig= string.IsNullOrWhiteSpace(_shopSettings.FeaturesConfig) ? "{}" : _shopSettings.FeaturesConfig;
|
||||
}
|
||||
}
|
||||
catch { /* non-fatal */ }
|
||||
}
|
||||
|
||||
private async Task SaveShopSettings()
|
||||
{
|
||||
if (!_shopGuid.HasValue) return;
|
||||
_settingsMessage = null;
|
||||
var req = new PosDataService.UpdateShopSettingsRequest(
|
||||
FeaturesConfig: _settingsFeaturesConfig,
|
||||
OpenTime: string.IsNullOrWhiteSpace(_settingsOpenTime) ? null : _settingsOpenTime,
|
||||
CloseTime: string.IsNullOrWhiteSpace(_settingsCloseTime) ? null : _settingsCloseTime,
|
||||
OpenDays: _settingsOpenDays);
|
||||
var ok = await DataService.UpdateShopSettingsAsync(_shopGuid.Value, req);
|
||||
_settingsSuccess = ok;
|
||||
_settingsMessage = ok ? "Đã lưu thiết lập thành công!" : "Lỗi khi lưu thiết lập.";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ToggleDay(string code)
|
||||
{
|
||||
var days = _settingsOpenDays.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
if (days.Contains(code)) days.Remove(code);
|
||||
else days.Add(code);
|
||||
_settingsOpenDays = string.Join(",", days);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ConfigureSection()
|
||||
{
|
||||
switch (_section)
|
||||
|
||||
@@ -437,4 +437,35 @@ public class PosDataService
|
||||
: $"api/bff/reports/revenue?period={period}";
|
||||
return await _http.GetFromJsonAsync<List<RevenueReportItem>>(url, _jsonOptions) ?? new();
|
||||
}
|
||||
|
||||
// ═══ SHOP SETTINGS ═══
|
||||
|
||||
public record ShopSettingsInfo(string? FeaturesConfig, string? OpenTime, string? CloseTime, string? OpenDays);
|
||||
public record UpdateShopSettingsRequest(string? FeaturesConfig, string? OpenTime, string? CloseTime, string? OpenDays);
|
||||
|
||||
public async Task<ShopSettingsInfo?> GetShopSettingsAsync(Guid shopId)
|
||||
{
|
||||
AttachToken();
|
||||
return await _http.GetFromJsonAsync<ShopSettingsInfo>($"api/bff/shops/{shopId}/settings", _jsonOptions);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateShopSettingsAsync(Guid shopId, UpdateShopSettingsRequest req)
|
||||
{
|
||||
AttachToken();
|
||||
var resp = await _http.PutAsJsonAsync($"api/bff/shops/{shopId}/settings", req, _jsonOptions);
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
// ═══ TOP PRODUCTS ═══
|
||||
|
||||
public record TopProductInfo(string? ProductName, long TotalSold, decimal TotalRevenue);
|
||||
|
||||
public async Task<List<TopProductInfo>> GetTopProductsAsync(Guid? shopId = null, int limit = 10)
|
||||
{
|
||||
AttachToken();
|
||||
var url = shopId.HasValue
|
||||
? $"api/bff/reports/top-products?shopId={shopId}&limit={limit}"
|
||||
: $"api/bff/reports/top-products?limit={limit}";
|
||||
return await _http.GetFromJsonAsync<List<TopProductInfo>>(url, _jsonOptions) ?? new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1406,6 +1406,100 @@ public class BffDataController : ControllerBase
|
||||
catch { return Ok(Array.Empty<object>()); }
|
||||
}
|
||||
|
||||
// ═══ C1: SHOP SETTINGS ═══
|
||||
|
||||
[HttpGet("shops/{shopId:guid}/settings")]
|
||||
public async Task<IActionResult> GetShopSettings(Guid shopId)
|
||||
{
|
||||
var merchantId = await GetCurrentMerchantIdAsync();
|
||||
if (merchantId == null) return NotFound();
|
||||
|
||||
await using var conn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
var settings = await conn.QueryFirstOrDefaultAsync<dynamic>(
|
||||
@"SELECT features_config::text as features_config,
|
||||
open_time::text as open_time,
|
||||
close_time::text as close_time,
|
||||
open_days
|
||||
FROM shops
|
||||
WHERE id = @ShopId AND merchant_id = @MerchantId AND is_deleted = false",
|
||||
new { ShopId = shopId, MerchantId = merchantId });
|
||||
|
||||
if (settings == null) return NotFound();
|
||||
return Ok(settings);
|
||||
}
|
||||
|
||||
[HttpPut("shops/{shopId:guid}/settings")]
|
||||
public async Task<IActionResult> UpdateShopSettings(Guid shopId, [FromBody] UpdateShopSettingsRequest req)
|
||||
{
|
||||
var merchantId = await GetCurrentMerchantIdAsync();
|
||||
if (merchantId == null) return Unauthorized();
|
||||
|
||||
var myShopIds = await GetMyShopIdsAsync(merchantId.Value);
|
||||
if (!myShopIds.Contains(shopId)) return Forbid();
|
||||
|
||||
await using var conn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
try
|
||||
{
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE shops
|
||||
SET features_config = @FeaturesConfig::jsonb,
|
||||
open_time = @OpenTime::time,
|
||||
close_time = @CloseTime::time,
|
||||
open_days = @OpenDays,
|
||||
updated_at = NOW()
|
||||
WHERE id = @ShopId AND id = ANY(@ShopIds)",
|
||||
new
|
||||
{
|
||||
ShopId = shopId,
|
||||
ShopIds = myShopIds.ToArray(),
|
||||
FeaturesConfig = string.IsNullOrWhiteSpace(req.FeaturesConfig) ? "{}" : req.FeaturesConfig,
|
||||
OpenTime = string.IsNullOrWhiteSpace(req.OpenTime) ? (object)DBNull.Value : req.OpenTime,
|
||||
CloseTime= string.IsNullOrWhiteSpace(req.CloseTime) ? (object)DBNull.Value : req.CloseTime,
|
||||
OpenDays = req.OpenDays
|
||||
});
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
// ═══ C2: TOP PRODUCTS ═══
|
||||
|
||||
[HttpGet("reports/top-products")]
|
||||
public async Task<IActionResult> GetTopProducts(
|
||||
[FromQuery] Guid? shopId = null,
|
||||
[FromQuery] int limit = 10)
|
||||
{
|
||||
var merchantId = await GetCurrentMerchantIdAsync();
|
||||
if (merchantId == null) return Ok(Array.Empty<object>());
|
||||
|
||||
var myShopIds = await GetMyShopIdsAsync(merchantId.Value);
|
||||
if (!myShopIds.Any()) return Ok(Array.Empty<object>());
|
||||
|
||||
if (shopId.HasValue && !myShopIds.Contains(shopId.Value))
|
||||
return Ok(Array.Empty<object>());
|
||||
|
||||
var targetShopIds = shopId.HasValue ? new List<Guid> { shopId.Value } : myShopIds;
|
||||
|
||||
await using var conn = new NpgsqlConnection(ConnStr("order_service"));
|
||||
try
|
||||
{
|
||||
var rows = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT oi.product_name,
|
||||
SUM(oi.quantity)::bigint AS total_sold,
|
||||
SUM(oi.quantity * oi.unit_price)::numeric AS total_revenue
|
||||
FROM order_items oi
|
||||
JOIN orders o ON oi.order_id = o.id
|
||||
WHERE o.shop_id = ANY(@ShopIds)
|
||||
AND o.status_id IN (3, 5)
|
||||
GROUP BY oi.product_name
|
||||
ORDER BY total_sold DESC
|
||||
LIMIT @Limit",
|
||||
new { ShopIds = targetShopIds.ToArray(), Limit = limit });
|
||||
return Ok(rows);
|
||||
}
|
||||
catch { return Ok(Array.Empty<object>()); }
|
||||
}
|
||||
|
||||
// EN: Request DTOs / VI: DTO yêu cầu
|
||||
public record CreateProductRequest(Guid ShopId, string Name, string? Description, decimal Price, string? Type, string? Sku, string? ImageUrl);
|
||||
public record CreateStaffRequest(Guid MerchantId, string? EmployeeCode, string? Phone, string? Email, string? Role);
|
||||
@@ -1417,4 +1511,6 @@ public class BffDataController : ControllerBase
|
||||
public record CreateCampaignRequest(string Name, string? Description, decimal FaceValue, int TotalVouchers, DateTime StartDate, DateTime EndDate);
|
||||
public record CreateMemberRequest(string? Gender, string? CountryCode);
|
||||
public record UpdateMemberRequest(string? Gender, string? Preferences);
|
||||
public record UpdateShopSettingsRequest(string? FeaturesConfig, string? OpenTime, string? CloseTime, string? OpenDays);
|
||||
public record TopProductItem(string ProductName, long TotalSold, decimal TotalRevenue);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user