@@ -183,7 +184,7 @@ else if (SubSection == "rooms")
var borderColor = room.Status switch { "available" => "rgba(139,92,246,0.3)", "occupied" => "rgba(239,68,68,0.3)", "reserved" => "rgba(245,158,11,0.3)", _ => "rgba(107,107,111,0.3)" };
var statusColor = room.Status switch { "available" => "#8B5CF6", "occupied" => "#EF4444", "reserved" => "#F59E0B", _ => "#6B6B6F" };
var statusText = room.Status switch { "available" => "Trống", "occupied" => "Đang hát", "reserved" => "Đã đặt", "cleaning" => "Dọn dẹp", _ => room.Status };
- var roomType = (room.Zone ?? "").ToLower() switch { var z when z.Contains("vip") => ("VIP", "#F59E0B", 200000m), var z when z.Contains("party") => ("Party", "#EC4899", 350000m), _ => ("Standard", "#8B5CF6", 120000m) };
+ var roomType = (room.Zone ?? "").ToLower() switch { var z when z.Contains("vip") => ("VIP", "#F59E0B"), var z when z.Contains("party") => ("Party", "#EC4899"), _ => ("Standard", "#8B5CF6") };
@@ -195,7 +196,7 @@ else if (SubSection == "rooms")
Phòng @room.TableNumber
@(room.Zone ?? "Standard") • @room.Capacity chỗ
-
@ShopHelpers.FormatVND(roomType.Item3)/giờ
+
@ShopHelpers.FormatVND(room.HourlyRate)/giờ
@statusText
@@ -204,7 +205,7 @@ else if (SubSection == "rooms")
{
var elapsed = DateTime.UtcNow - (room.StartedAt ?? DateTime.UtcNow);
var hours = Math.Max(1, (int)Math.Ceiling(elapsed.TotalHours));
- var bill = hours * roomType.Item3;
+ var bill = hours * room.HourlyRate;
@room.GuestCount khách • Bắt đầu @(room.StartedAt?.ToString("HH:mm") ?? "—")
@@ -349,6 +350,7 @@ else if (SubSection == "zones")
private string _newTableNumber = "";
private int _newTableCapacity = 4;
private string _newTableZone = "";
+ private decimal _newHourlyRate;
private string? _tableFormMessage;
private bool _tableFormSuccess;
// Zone form state
@@ -431,6 +433,7 @@ else if (SubSection == "zones")
_newTableNumber = table.TableNumber;
_newTableCapacity = table.Capacity;
_newTableZone = table.Zone ?? "";
+ _newHourlyRate = table.HourlyRate;
_tableFormMessage = null;
_showTableForm = true;
}
@@ -444,9 +447,9 @@ else if (SubSection == "zones")
}
try
{
- await DataService.CreateTableAsync(new PosDataService.CreateTableRequest(ShopId, _newTableNumber, _newTableCapacity, _newTableZone));
+ await DataService.CreateTableAsync(new PosDataService.CreateTableRequest(ShopId, _newTableNumber, _newTableCapacity, _newTableZone, _newHourlyRate > 0 ? _newHourlyRate : null));
_tableFormMessage = $"Đã thêm bàn '{_newTableNumber}' thành công!"; _tableFormSuccess = true;
- _newTableNumber = ""; _newTableCapacity = 4; _newTableZone = "";
+ _newTableNumber = ""; _newTableCapacity = 4; _newTableZone = ""; _newHourlyRate = 0;
if (ShopId != Guid.Empty) _tables = await DataService.GetTablesAsync(ShopId);
}
catch (Exception ex) { _tableFormMessage = $"Lỗi: {ex.Message}"; _tableFormSuccess = false; }
@@ -461,7 +464,7 @@ else if (SubSection == "zones")
}
try
{
- await DataService.UpdateTableAsync(_editingTableId.Value, new PosDataService.CreateTableRequest(ShopId, _newTableNumber, _newTableCapacity, _newTableZone));
+ await DataService.UpdateTableAsync(_editingTableId.Value, new PosDataService.CreateTableRequest(ShopId, _newTableNumber, _newTableCapacity, _newTableZone, _newHourlyRate > 0 ? _newHourlyRate : null));
_tableFormMessage = $"Đã cập nhật bàn '{_newTableNumber}' thành công!"; _tableFormSuccess = true;
_editingTableId = null;
if (ShopId != Guid.Empty) _tables = await DataService.GetTablesAsync(ShopId);
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor
index 0522e08b..5b7c8781 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Pos/Karaoke/KaraokeDesktop.razor
@@ -143,16 +143,28 @@
+
+ Giá/giờ
+ @FormatPrice(_room?.HourlyRate ?? 0)
+
Thời gian
@_elapsed.ToString(@"h\:mm") giờ
+
+ Tiền phòng
+ @FormatPrice(RoomCost)
+
@* ═══ ACTION BUTTONS / NÚT HÀNH ĐỘNG ═══ *@
@@ -130,13 +138,17 @@
+
+
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/en-US.json b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/en-US.json
index dd8d5ebc..0061b472 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/en-US.json
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/en-US.json
@@ -420,9 +420,20 @@
"Dashboard_CreateStoreNow": "Create Store Now",
"Dashboard_QuickActions": "Quick Actions",
"Dashboard_QA_CreateStore": "Create new store",
- "Dashboard_QA_SystemSettings": "System Settings",
+ "Dashboard_QA_ManageStaff": "Manage Staff",
"Dashboard_QA_Permissions": "Permissions",
+ "Dashboard_QA_Devices": "Device Management",
+ "Dashboard_QA_Integrations": "Third-party Integrations",
+ "Dashboard_QA_AuditLog": "Activity Log",
+ "Dashboard_QA_SystemSettings": "System Settings",
"Dashboard_SystemStatus": "System Status",
+ "Dashboard_RefreshStatus": "Refresh",
+ "Dashboard_CheckingServices": "Checking services...",
+ "Dashboard_LastChecked": "Last checked at",
+ "Dashboard_FinishSetup": "Finish Setup",
+ "Dashboard_Stats_Orders": "orders",
+ "Dashboard_Stats_Staff": "staff",
+ "Dashboard_Stats_Products": "products",
"Dashboard_Status_Open": "Open",
"Dashboard_Status_Setup": "Setting up"
}
\ No newline at end of file
diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/vi-VN.json b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/vi-VN.json
index ed42fc94..7c143558 100644
--- a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/vi-VN.json
+++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/vi-VN.json
@@ -420,9 +420,20 @@
"Dashboard_CreateStoreNow": "Tạo cửa hàng ngay",
"Dashboard_QuickActions": "Thao tác nhanh",
"Dashboard_QA_CreateStore": "Tạo cửa hàng mới",
- "Dashboard_QA_SystemSettings": "Cài đặt hệ thống",
+ "Dashboard_QA_ManageStaff": "Quản lý nhân viên",
"Dashboard_QA_Permissions": "Phân quyền",
+ "Dashboard_QA_Devices": "Quản lý thiết bị",
+ "Dashboard_QA_Integrations": "Tích hợp bên thứ ba",
+ "Dashboard_QA_AuditLog": "Nhật ký hoạt động",
+ "Dashboard_QA_SystemSettings": "Cài đặt hệ thống",
"Dashboard_SystemStatus": "Trạng thái hệ thống",
+ "Dashboard_RefreshStatus": "Làm mới",
+ "Dashboard_CheckingServices": "Đang kiểm tra dịch vụ...",
+ "Dashboard_LastChecked": "Cập nhật lúc",
+ "Dashboard_FinishSetup": "Hoàn thành thiết lập",
+ "Dashboard_Stats_Orders": "đơn",
+ "Dashboard_Stats_Staff": "NV",
+ "Dashboard_Stats_Products": "SP",
"Dashboard_Status_Open": "Đang mở",
"Dashboard_Status_Setup": "Thiết lập"
}
\ No newline at end of file
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 277b68b9..047e709f 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
@@ -67,6 +67,14 @@ public class ShopController : ControllerBase
public Task
GetShopStats() =>
_merchant.GetAsync("/api/v1/shops/stats").ProxyAsync();
+ ///
+ /// EN: Publish a shop (draft → active).
+ /// VI: Xuất bản cửa hàng (draft → active).
+ ///
+ [HttpPost("shops/{shopId:guid}/publish")]
+ public Task PublishShop(Guid shopId) =>
+ _merchant.PostAsync($"/api/v1/shops/{shopId}/publish", 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.
diff --git a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommand.cs b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommand.cs
index 5462cf5a..bd897a89 100644
--- a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommand.cs
+++ b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommand.cs
@@ -13,7 +13,8 @@ public record CreateTableCommand(
Guid ShopId,
string TableNumber,
int Capacity,
- string? Zone = null
+ string? Zone = null,
+ decimal? HourlyRate = null
) : IRequest;
///
diff --git a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommandHandler.cs b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommandHandler.cs
index 86cc8b70..5abbb7f4 100644
--- a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommandHandler.cs
+++ b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/CreateTableCommandHandler.cs
@@ -48,6 +48,9 @@ public class CreateTableCommandHandler : IRequestHandler 0)
+ table.SetHourlyRate(request.HourlyRate.Value);
+
await _tableRepository.AddAsync(table, cancellationToken);
await _tableRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
diff --git a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommand.cs b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommand.cs
index 8226d178..65d54d95 100644
--- a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommand.cs
+++ b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommand.cs
@@ -14,5 +14,6 @@ public record UpdateTableCommand(
int? Capacity = null,
string? Zone = null,
int? PositionX = null,
- int? PositionY = null
+ int? PositionY = null,
+ decimal? HourlyRate = null
) : IRequest;
diff --git a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommandHandler.cs b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommandHandler.cs
index 8c966def..7f7be78a 100644
--- a/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommandHandler.cs
+++ b/services/fnb-engine-net/src/FnbEngine.API/Application/Commands/UpdateTableCommandHandler.cs
@@ -34,6 +34,9 @@ public class UpdateTableCommandHandler : IRequestHandler { Success = true, Data = result });
@@ -158,7 +160,8 @@ public record CreateTableRequest(
Guid ShopId,
string TableNumber,
int Capacity,
- string? Zone = null);
+ string? Zone = null,
+ decimal? HourlyRate = null);
///
/// EN: Request to update table details.
@@ -168,7 +171,8 @@ public record UpdateTableRequest(
int? Capacity = null,
string? Zone = null,
int? PositionX = null,
- int? PositionY = null);
+ int? PositionY = null,
+ decimal? HourlyRate = null);
///
/// EN: Request to change table status.
diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/TableAggregate/Table.cs b/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/TableAggregate/Table.cs
index d2cb488e..9980d706 100644
--- a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/TableAggregate/Table.cs
+++ b/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/TableAggregate/Table.cs
@@ -20,6 +20,7 @@ public class Table : Entity, IAggregateRoot
private int? _positionX;
private int? _positionY;
private string? _qrToken;
+ private decimal _hourlyRate;
private DateTime _createdAt;
private DateTime? _updatedAt;
@@ -27,11 +28,12 @@ public class Table : Entity, IAggregateRoot
public string TableNumber => _tableNumber;
public int Capacity => _capacity;
public string? Zone => _zone;
- public TableStatus Status => _status;
+ public TableStatus Status => _status ?? Enumeration.FromValue(StatusId);
public int StatusId { get; private set; }
public int? PositionX => _positionX;
public int? PositionY => _positionY;
public string? QrToken => _qrToken;
+ public decimal HourlyRate => _hourlyRate;
public DateTime CreatedAt => _createdAt;
public DateTime? UpdatedAt => _updatedAt;
@@ -58,8 +60,14 @@ public class Table : Entity, IAggregateRoot
_createdAt = DateTime.UtcNow;
}
+ private void EnsureStatusLoaded()
+ {
+ _status ??= Enumeration.FromValue(StatusId);
+ }
+
public void MarkAsOccupied()
{
+ EnsureStatusLoaded();
if (_status != TableStatus.Available && _status != TableStatus.Reserved)
throw new DomainException($"Cannot occupy table with status {_status.Name}");
@@ -70,6 +78,7 @@ public class Table : Entity, IAggregateRoot
public void MarkAsAvailable()
{
+ EnsureStatusLoaded();
_status = TableStatus.Available;
StatusId = TableStatus.Available.Id;
_updatedAt = DateTime.UtcNow;
@@ -77,11 +86,18 @@ public class Table : Entity, IAggregateRoot
public void MarkAsCleaning()
{
+ EnsureStatusLoaded();
_status = TableStatus.Cleaning;
StatusId = TableStatus.Cleaning.Id;
_updatedAt = DateTime.UtcNow;
}
+ public void SetHourlyRate(decimal rate)
+ {
+ _hourlyRate = rate;
+ _updatedAt = DateTime.UtcNow;
+ }
+
public void SetPosition(int x, int y)
{
_positionX = x;
diff --git a/services/fnb-engine-net/src/FnbEngine.Infrastructure/EntityConfigurations/TableEntityTypeConfiguration.cs b/services/fnb-engine-net/src/FnbEngine.Infrastructure/EntityConfigurations/TableEntityTypeConfiguration.cs
index fab5d0f1..f6bbe104 100644
--- a/services/fnb-engine-net/src/FnbEngine.Infrastructure/EntityConfigurations/TableEntityTypeConfiguration.cs
+++ b/services/fnb-engine-net/src/FnbEngine.Infrastructure/EntityConfigurations/TableEntityTypeConfiguration.cs
@@ -58,6 +58,12 @@ public class TableEntityTypeConfiguration : IEntityTypeConfiguration
.HasColumnName("qr_token")
.HasMaxLength(64);
+ builder.Property(t => t.HourlyRate)
+ .HasField("_hourlyRate")
+ .HasColumnName("hourly_rate")
+ .HasPrecision(18, 2)
+ .HasDefaultValue(0m);
+
builder.Property(t => t.CreatedAt)
.HasField("_createdAt")
.HasColumnName("created_at")