feat(web-client-tpos): connect all remaining admin pages to real backend APIs
- BFF: Added 10 new endpoints (staff roles/schedules, orders, wallets, devices, promotions, inventory transactions, membership levels) - PosDataService: Added 14 new client methods with DTOs - Rewrote 19 admin pages from hardcoded to real API: Staff: Create, Schedule, Attendance, Payroll Finance: Overview, Revenue, Expenses, Tax Inventory: PurchaseOrders, StockTransfer, SupplierMgmt Product: MenuBuilder, ModifierGroups, PricingRules Customer: Feedback, LoyaltyProgram System: DeviceManagement, NotificationCenter, IntegrationHub
This commit is contained in:
@@ -307,6 +307,136 @@ public class BffDataController : ControllerBase
|
||||
return CreatedAtAction(nameof(GetStaff), new { }, new { id });
|
||||
}
|
||||
|
||||
// ═══ STAFF ROLES ═══
|
||||
[HttpGet("staff/roles")]
|
||||
public async Task<IActionResult> GetStaffRoles()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
var roles = await conn.QueryAsync<dynamic>("SELECT id, name FROM staff_roles ORDER BY id");
|
||||
return Ok(roles);
|
||||
}
|
||||
|
||||
// ═══ STAFF SCHEDULES ═══
|
||||
[HttpGet("staff/schedules")]
|
||||
public async Task<IActionResult> GetStaffSchedules([FromQuery] Guid? shopId = null)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("booking_service"));
|
||||
var sql = @"SELECT id, staff_id, shop_id, day_of_week, start_time, end_time FROM staff_schedules";
|
||||
if (shopId.HasValue) sql += " WHERE shop_id = @ShopId";
|
||||
sql += " ORDER BY day_of_week, start_time";
|
||||
var schedules = await conn.QueryAsync<dynamic>(sql, new { ShopId = shopId });
|
||||
|
||||
// EN: Enrich with staff names / VI: Bổ sung tên nhân viên
|
||||
await using var mConn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
var staffList = (await mConn.QueryAsync<dynamic>(
|
||||
"SELECT ms.id, ms.employee_code, ms.phone, sr.name as role FROM merchant_staff ms JOIN staff_roles sr ON ms.role_id = sr.id")).ToList();
|
||||
var staffMap = staffList.ToDictionary(s => (Guid)s.id, s => new { code = (string?)s.employee_code, role = (string)s.role, phone = (string?)s.phone });
|
||||
|
||||
var result = schedules.Select(s => new {
|
||||
s.id, s.staff_id, s.shop_id, s.day_of_week, s.start_time, s.end_time,
|
||||
employee_code = staffMap.TryGetValue((Guid)s.staff_id, out var info) ? info.code : null,
|
||||
role = info?.role, phone = info?.phone
|
||||
});
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// ═══ ORDERS SUMMARY ═══
|
||||
[HttpGet("orders")]
|
||||
public async Task<IActionResult> GetOrders([FromQuery] Guid? shopId = null)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("order_service"));
|
||||
var sql = @"SELECT o.id, o.shop_id, o.total_amount, o.status_id, o.created_at,
|
||||
os.name as status
|
||||
FROM orders o
|
||||
JOIN order_statuses os ON o.status_id = os.id";
|
||||
if (shopId.HasValue) sql += " WHERE o.shop_id = @ShopId";
|
||||
sql += " ORDER BY o.created_at DESC LIMIT 200";
|
||||
var orders = await conn.QueryAsync<dynamic>(sql, new { ShopId = shopId });
|
||||
return Ok(orders);
|
||||
}
|
||||
|
||||
// ═══ WALLET/FINANCE ═══
|
||||
[HttpGet("wallets")]
|
||||
public async Task<IActionResult> GetWallets()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("wallet_service"));
|
||||
var wallets = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT w.id, w.balance, w.currency, w.owner_id, w.created_at,
|
||||
(SELECT COALESCE(SUM(amount),0) FROM wallet_transactions wt WHERE wt.wallet_id = w.id AND wt.amount > 0) as total_income,
|
||||
(SELECT COALESCE(SUM(ABS(amount)),0) FROM wallet_transactions wt WHERE wt.wallet_id = w.id AND wt.amount < 0) as total_expense
|
||||
FROM wallets w ORDER BY w.created_at DESC");
|
||||
return Ok(wallets);
|
||||
}
|
||||
|
||||
[HttpGet("wallet/transactions")]
|
||||
public async Task<IActionResult> GetWalletTransactions([FromQuery] int limit = 50)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("wallet_service"));
|
||||
var txns = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT wt.id, wt.wallet_id, wt.amount, wt.description, wt.created_at,
|
||||
wi.name as item_name
|
||||
FROM wallet_transactions wt
|
||||
LEFT JOIN wallet_items wi ON wt.reference_id = wi.id
|
||||
ORDER BY wt.created_at DESC LIMIT @Limit",
|
||||
new { Limit = limit });
|
||||
return Ok(txns);
|
||||
}
|
||||
|
||||
// ═══ DEVICES ═══
|
||||
[HttpGet("devices")]
|
||||
public async Task<IActionResult> GetDevices()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
var devices = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT dt.id, dt.device_token, dt.platform, dt.is_active, dt.created_at,
|
||||
ms.employee_code as staff_code
|
||||
FROM device_tokens dt
|
||||
LEFT JOIN merchant_staff ms ON dt.staff_id = ms.id
|
||||
ORDER BY dt.created_at DESC");
|
||||
return Ok(devices);
|
||||
}
|
||||
|
||||
// ═══ PROMOTIONS ═══
|
||||
[HttpGet("promotions")]
|
||||
public async Task<IActionResult> GetPromotions()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("promotion_service"));
|
||||
var promos = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT c.id, c.name, c.description, c.start_date, c.end_date, c.is_active, c.discount_type, c.discount_value,
|
||||
(SELECT COUNT(*) FROM vouchers v WHERE v.campaign_id = c.id) as voucher_count,
|
||||
(SELECT COUNT(*) FROM redemptions r WHERE r.campaign_id = c.id) as redemption_count
|
||||
FROM campaigns c ORDER BY c.created_at DESC");
|
||||
return Ok(promos);
|
||||
}
|
||||
|
||||
// ═══ INVENTORY TRANSACTIONS ═══
|
||||
[HttpGet("inventory/transactions")]
|
||||
public async Task<IActionResult> GetInventoryTransactions([FromQuery] Guid? shopId = null)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("inventory_service"));
|
||||
var sql = @"SELECT it.id, it.inventory_item_id, it.quantity_change, it.reason, it.created_at,
|
||||
tt.name as transaction_type
|
||||
FROM inventory_transactions it
|
||||
JOIN transaction_types tt ON it.type_id = tt.id";
|
||||
if (shopId.HasValue)
|
||||
sql += @" JOIN inventory_items ii ON it.inventory_item_id = ii.id WHERE ii.shop_id = @ShopId";
|
||||
sql += " ORDER BY it.created_at DESC LIMIT 100";
|
||||
var txns = await conn.QueryAsync<dynamic>(sql, new { ShopId = shopId });
|
||||
return Ok(txns);
|
||||
}
|
||||
|
||||
// ═══ MEMBERSHIP LEVELS ═══
|
||||
[HttpGet("membership/levels")]
|
||||
public async Task<IActionResult> GetMembershipLevels()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("membership_service"));
|
||||
var levels = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT ld.id, ld.level, ld.name, ld.min_exp, ld.max_exp,
|
||||
(SELECT COUNT(*) FROM members m WHERE m.current_level = ld.level) as member_count
|
||||
FROM level_definitions ld ORDER BY ld.level");
|
||||
return Ok(levels);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
Reference in New Issue
Block a user