first commit
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped

This commit is contained in:
soroush.asadi
2026-05-31 11:06:24 +03:30
parent 51e422272d
commit 345ae0a4b5
69 changed files with 11964 additions and 152 deletions
@@ -0,0 +1,41 @@
namespace Meezi.Core.Authorization;
/// <summary>
/// Capabilities a café employee can be granted. These are the single source of
/// truth for authorization — controllers check a <see cref="Permission"/> rather
/// than hard-coding role names, so the role→capability mapping lives in exactly
/// one place (<see cref="RolePermissions"/>).
/// </summary>
public enum Permission
{
// Café-level administration (Owner only)
ManageCafeSettings,
ManageBilling,
ManageBranches,
// Management (Owner + Manager)
ManageStaff,
ManageMenu,
ManageInventory,
ManageExpenses,
ManageTaxes,
ManageCoupons,
ManageReservations,
ManageTables,
ViewReports,
ReviewLeave,
ManageSalaries,
ManagePrintSettings,
// Front-of-house operations
ProcessOrders,
HandlePayments,
OperateRegister,
ManageQueue,
// Kitchen
ViewKitchen,
// Delivery
HandleDelivery,
}
@@ -0,0 +1,76 @@
using Meezi.Core.Enums;
namespace Meezi.Core.Authorization;
/// <summary>
/// The authoritative role→capability matrix. Change what a role can do here and
/// every controller that calls <c>EnsurePermission</c> updates automatically.
/// </summary>
public static class RolePermissions
{
private static readonly IReadOnlyDictionary<EmployeeRole, HashSet<Permission>> Matrix =
new Dictionary<EmployeeRole, HashSet<Permission>>
{
[EmployeeRole.Owner] = AllPermissions(),
[EmployeeRole.Manager] = new()
{
Permission.ManageStaff,
Permission.ManageMenu,
Permission.ManageInventory,
Permission.ManageExpenses,
Permission.ManageTaxes,
Permission.ManageCoupons,
Permission.ManageReservations,
Permission.ManageTables,
Permission.ViewReports,
Permission.ReviewLeave,
Permission.ManageSalaries,
Permission.ManagePrintSettings,
Permission.ProcessOrders,
Permission.HandlePayments,
Permission.OperateRegister,
Permission.ManageQueue,
Permission.ViewKitchen,
Permission.HandleDelivery,
},
[EmployeeRole.Cashier] = new()
{
Permission.ProcessOrders,
Permission.HandlePayments,
Permission.OperateRegister,
Permission.ManageQueue,
Permission.ManageReservations,
},
[EmployeeRole.Waiter] = new()
{
Permission.ProcessOrders,
Permission.ManageReservations,
Permission.ManageQueue,
},
[EmployeeRole.Chef] = new()
{
Permission.ViewKitchen,
},
[EmployeeRole.Delivery] = new()
{
Permission.HandleDelivery,
},
};
public static bool Has(EmployeeRole role, Permission permission) =>
Matrix.TryGetValue(role, out var set) && set.Contains(permission);
public static IReadOnlySet<Permission> For(EmployeeRole role) =>
Matrix.TryGetValue(role, out var set) ? set : new HashSet<Permission>();
/// <summary>True for roles that administer the whole café across all branches.</summary>
public static bool IsCafeWide(EmployeeRole role) => role == EmployeeRole.Owner;
private static HashSet<Permission> AllPermissions() =>
new(Enum.GetValues<Permission>());
}
+34
View File
@@ -0,0 +1,34 @@
namespace Meezi.Core.Entities;
/// <summary>
/// Immutable record of a sensitive POS / management action. Written by
/// <c>IAuditLogService</c> and never updated. Branch-scoped so the strict
/// branch isolation filter applies (café-wide sessions see all).
/// </summary>
public class AuditLog : TenantEntity
{
/// <summary>High-level grouping, e.g. "Order", "Payment", "Register", "Staff".</summary>
public string Category { get; set; } = string.Empty;
/// <summary>Specific action, e.g. "OrderCancelled", "ItemVoided", "PaymentRecorded".</summary>
public string Action { get; set; } = string.Empty;
/// <summary>The entity acted upon, e.g. "Order", "Shift".</summary>
public string? EntityType { get; set; }
/// <summary>Id of the affected entity.</summary>
public string? EntityId { get; set; }
public string? BranchId { get; set; }
/// <summary>Employee who performed the action (null for system/automated).</summary>
public string? ActorId { get; set; }
public string? ActorName { get; set; }
public string? ActorRole { get; set; }
/// <summary>Human-readable one-line summary (already localized at write time or neutral).</summary>
public string Summary { get; set; } = string.Empty;
/// <summary>Optional structured payload (before/after, amounts, reason) as JSON.</summary>
public string? DetailsJson { get; set; }
}
+3
View File
@@ -39,4 +39,7 @@ public class Branch : TenantEntity
public ICollection<Table> Tables { get; set; } = [];
public ICollection<Order> Orders { get; set; } = [];
public ICollection<Employee> Staff { get; set; } = [];
/// <summary>Per-branch role assignments scoped to this branch.</summary>
public ICollection<EmployeeBranchRole> StaffRoles { get; set; } = [];
}
+3
View File
@@ -19,4 +19,7 @@ public class Employee : TenantEntity
public ICollection<Attendance> Attendances { get; set; } = [];
public ICollection<EmployeeSchedule> Schedules { get; set; } = [];
public ICollection<LeaveRequest> LeaveRequests { get; set; } = [];
/// <summary>Per-branch role assignments (multi-branch staff). Owners are café-wide and may have none.</summary>
public ICollection<EmployeeBranchRole> BranchRoles { get; set; } = [];
}
@@ -0,0 +1,19 @@
using Meezi.Core.Enums;
namespace Meezi.Core.Entities;
/// <summary>
/// Per-branch role assignment for an employee. An employee row is scoped to one café
/// (a "membership"); this join lets that same employee hold a different
/// <see cref="EmployeeRole"/> in each branch they work at.
/// Owners remain café-wide via <see cref="Employee.Role"/> and need no rows here.
/// </summary>
public class EmployeeBranchRole : TenantEntity
{
public string EmployeeId { get; set; } = string.Empty;
public string BranchId { get; set; } = string.Empty;
public EmployeeRole Role { get; set; }
public Employee Employee { get; set; } = null!;
public Branch Branch { get; set; } = null!;
}
+6
View File
@@ -34,6 +34,12 @@ public class Order : TenantEntity
/// <summary>JSON snapshot: driver, address, delivery ETA, etc.</summary>
public string? DeliveryMetaJson { get; set; }
/// <summary>Reason captured when the order was cancelled (POS audit / accountability).</summary>
public string? CancelReason { get; set; }
/// <summary>Employee who cancelled the order (null for system/automated).</summary>
public string? CancelledByEmployeeId { get; set; }
public DateTime? CancelledAt { get; set; }
public Cafe Cafe { get; set; } = null!;
public Branch? Branch { get; set; }
public Table? Table { get; set; }