added authorizations in IT controller
This commit is contained in:
parent
d002954b69
commit
fa75a383ba
@ -1,5 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PSTW_CentralSystem.DBContext;
|
||||
using PSTW_CentralSystem.Models;
|
||||
|
||||
namespace PSTW_CentralSystem.Areas.IT.Controllers
|
||||
{
|
||||
@ -7,41 +11,69 @@ namespace PSTW_CentralSystem.Areas.IT.Controllers
|
||||
[Authorize]
|
||||
public class ApprovalDashboardController : Controller
|
||||
{
|
||||
public IActionResult Approval()
|
||||
private readonly CentralSystemContext _db;
|
||||
private readonly UserManager<UserModel> _userManager;
|
||||
|
||||
public ApprovalDashboardController(CentralSystemContext db, UserManager<UserModel> userManager)
|
||||
{
|
||||
_db = db;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
// ===== helpers =====
|
||||
private int GetCurrentUserId() => int.Parse(_userManager.GetUserId(User)!);
|
||||
|
||||
private async Task<bool> IsItTeamAsync(int userId) =>
|
||||
await _db.ItTeamMembers.AnyAsync(t => t.UserId == userId);
|
||||
|
||||
private async Task<bool> IsApproverInAnyFlowAsync(int userId) =>
|
||||
await _db.ItApprovalFlows.AnyAsync(f =>
|
||||
f.HodUserId == userId ||
|
||||
f.GroupItHodUserId == userId ||
|
||||
f.FinHodUserId == userId ||
|
||||
f.MgmtUserId == userId);
|
||||
|
||||
private async Task<bool> IsRequestFormManagerAsync(int userId) =>
|
||||
await _db.RequestFormManagers.AnyAsync(m => m.UserId == userId);
|
||||
|
||||
// ===== routes =====
|
||||
|
||||
// Approval is only available for approvers and IT team members
|
||||
public async Task<IActionResult> Approval()
|
||||
{
|
||||
var uid = GetCurrentUserId();
|
||||
|
||||
var isAllowed = await IsItTeamAsync(uid) || await IsApproverInAnyFlowAsync(uid);
|
||||
if (!isAllowed) return Forbid(); // or: return View("AccessDenied");
|
||||
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/Approval.cshtml
|
||||
}
|
||||
|
||||
public IActionResult Create()
|
||||
// Assignings (Admin) is only available for Request Form Managers
|
||||
public async Task<IActionResult> Admin()
|
||||
{
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/Create.cshtml
|
||||
}
|
||||
public IActionResult MyRequests()
|
||||
{
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/MyRequests.cshtml
|
||||
}
|
||||
public IActionResult Admin()
|
||||
{
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/MyRequests.cshtml
|
||||
var uid = GetCurrentUserId();
|
||||
|
||||
var isManager = await IsRequestFormManagerAsync(uid);
|
||||
if (!isManager) return Forbid(); // or: return View("AccessDenied");
|
||||
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/Admin.cshtml
|
||||
}
|
||||
|
||||
// Open to any authenticated user
|
||||
public IActionResult Create() => View(); // ~/Areas/IT/Views/ApprovalDashboard/Create.cshtml
|
||||
public IActionResult MyRequests() => View(); // ~/Areas/IT/Views/ApprovalDashboard/MyRequests.cshtml
|
||||
|
||||
// Use the same gate as Approval (reviewing a specific request)
|
||||
public IActionResult RequestReview(int statusId)
|
||||
{
|
||||
ViewBag.StatusId = statusId;
|
||||
return View(); // ~/Areas/IT/Views/ApprovalDashboard/RequestReview.cshtml
|
||||
}
|
||||
public IActionResult SectionB()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Edit()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult SectionBEdit()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
// Leave these open unless you want extra guards
|
||||
public IActionResult SectionB() => View();
|
||||
public IActionResult Edit() => View();
|
||||
public IActionResult SectionBEdit() => View();
|
||||
}
|
||||
}
|
||||
|
||||
11
Areas/IT/Models/RequestFormManager.cs
Normal file
11
Areas/IT/Models/RequestFormManager.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PSTW_CentralSystem.Areas.IT.Models
|
||||
{
|
||||
[Table("request_form_managers")]
|
||||
public class RequestFormManager
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<!-- Bootstrap Icons (kill this if you already include it in _Layout) -->
|
||||
<!-- Bootstrap Icons (remove if already added in _Layout) -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||||
|
||||
<style>
|
||||
@ -36,7 +36,7 @@
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
/* === IT Team nicer UI === */
|
||||
/* === Lists & chips (shared by IT Team & Managers) === */
|
||||
.it-card .card-header {
|
||||
background: #f8faff;
|
||||
}
|
||||
@ -110,10 +110,10 @@
|
||||
|
||||
<div id="flowApp" style="max-width:1200px; margin:auto; font-size:13px;">
|
||||
|
||||
<!-- FLOWS CARD -->
|
||||
<!-- ===================== FLOWS CARD ===================== -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="m-0"></h5>
|
||||
<h5 class="m-0">Approval Flows</h5>
|
||||
<div>
|
||||
<button class="btn btn-primary btn-sm" @@click="openCreate">New Flow</button>
|
||||
<button class="btn btn-outline-secondary btn-sm ms-2" @@click="load">Refresh</button>
|
||||
@ -140,13 +140,10 @@
|
||||
<tr v-for="f in flows" :key="f.itApprovalFlowId">
|
||||
<td>{{ f.itApprovalFlowId }}</td>
|
||||
<td class="text-start">{{ f.flowName }}</td>
|
||||
|
||||
<!-- show id + resolved name so admins aren't guessing -->
|
||||
<td>{{ f.hodUserId ? `${f.hodUserId} — ${resolveUserName(f.hodUserId)}` : '-' }}</td>
|
||||
<td>{{ f.groupItHodUserId ? `${f.groupItHodUserId} — ${resolveUserName(f.groupItHodUserId)}` : '-' }}</td>
|
||||
<td>{{ f.finHodUserId ? `${f.finHodUserId} — ${resolveUserName(f.finHodUserId)}` : '-' }}</td>
|
||||
<td>{{ f.mgmtUserId ? `${f.mgmtUserId} — ${resolveUserName(f.mgmtUserId)}` : '-' }}</td>
|
||||
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" @@click="openEdit(f)">Edit</button>
|
||||
@ -169,7 +166,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IT TEAM CARD -->
|
||||
<!-- ===================== IT TEAM CARD ===================== -->
|
||||
<div class="card mt-3 it-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0">IT Team Members</h6>
|
||||
@ -184,7 +181,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row g-3 align-items-start">
|
||||
<!-- LEFT: Search + Available -->
|
||||
<!-- LEFT -->
|
||||
<div class="col-md-7">
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||
@ -196,13 +193,11 @@
|
||||
<input type="checkbox" :value="u.id" v-model="itTeamUserIds">
|
||||
<span class="it-name">{{ u.name }}</span>
|
||||
</label>
|
||||
<div v-if="!filteredUsers.length" class="text-muted small p-2">
|
||||
No users match your search.
|
||||
</div>
|
||||
<div v-if="!filteredUsers.length" class="text-muted small p-2">No users match your search.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Selected chips -->
|
||||
<!-- RIGHT -->
|
||||
<div class="col-md-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<strong>Selected ({{ selectedUsers.length }})</strong>
|
||||
@ -225,7 +220,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FLOW MODAL -->
|
||||
<!-- ===================== REQUEST FORM MANAGERS CARD ===================== -->
|
||||
<div class="card mt-3 it-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0">Request Form Managers</h6>
|
||||
<button class="btn btn-sm btn-primary" @@click="saveFormManagers" :disabled="savingManagers">
|
||||
{{ savingManagers ? 'Saving…' : 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="text-muted mb-3">
|
||||
<small>Select users who can access Assignings/Admin features (manage request assignments, etc.).</small>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 align-items-start">
|
||||
<!-- LEFT -->
|
||||
<div class="col-md-7">
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control" placeholder="Search users by name…" v-model.trim="rfmSearch">
|
||||
</div>
|
||||
|
||||
<div class="it-list">
|
||||
<label v-for="u in filteredUsersForManagers" :key="'rfm-avail-'+u.id" class="it-item">
|
||||
<input type="checkbox" :value="u.id" v-model="rfmUserIds">
|
||||
<span class="it-name">{{ u.name }}</span>
|
||||
</label>
|
||||
<div v-if="!filteredUsersForManagers.length" class="text-muted small p-2">No users match your search.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT -->
|
||||
<div class="col-md-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<strong>Selected ({{ selectedManagers.length }})</strong>
|
||||
<button class="btn btn-link btn-sm text-decoration-none"
|
||||
@@click="rfmUserIds = []"
|
||||
:disabled="!selectedManagers.length">
|
||||
Clear all
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="it-selected">
|
||||
<span v-for="u in selectedManagers" :key="'rfm-sel-'+u.id" class="chip">
|
||||
{{ u.name }}
|
||||
<button class="chip-x" @@click="removeRfm(u.id)" aria-label="Remove">×</button>
|
||||
</span>
|
||||
<div v-if="!selectedManagers.length" class="text-muted small">Nobody selected yet.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===================== FLOW MODAL ===================== -->
|
||||
<div class="modal fade" id="flowModal" tabindex="-1" aria-hidden="true" ref="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@ -296,13 +345,19 @@
|
||||
data() {
|
||||
return {
|
||||
flows: [],
|
||||
users: [], // all users (name only for display)
|
||||
itTeamUserIds: [], // selected user IDs for IT team
|
||||
itSearch: '', // search term
|
||||
users: [], // all users for display (id + name)
|
||||
// IT Team state
|
||||
itTeamUserIds: [],
|
||||
itSearch: '',
|
||||
savingTeam: false,
|
||||
// Request Form Managers state
|
||||
rfmUserIds: [],
|
||||
rfmSearch: '',
|
||||
savingManagers: false,
|
||||
// UI state
|
||||
busy: false,
|
||||
error: null,
|
||||
saving: false,
|
||||
savingTeam: false,
|
||||
formError: null,
|
||||
form: {
|
||||
itApprovalFlowId: null,
|
||||
@ -316,6 +371,7 @@
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/* IT Team list filter */
|
||||
filteredUsers() {
|
||||
const q = (this.itSearch || '').toLowerCase();
|
||||
if (!q) return this.users;
|
||||
@ -324,9 +380,20 @@
|
||||
selectedUsers() {
|
||||
const set = new Set(this.itTeamUserIds);
|
||||
return this.users.filter(u => set.has(u.id));
|
||||
},
|
||||
/* Managers list filter */
|
||||
filteredUsersForManagers() {
|
||||
const q = (this.rfmSearch || '').toLowerCase();
|
||||
if (!q) return this.users;
|
||||
return this.users.filter(u => (u.name || '').toLowerCase().includes(q));
|
||||
},
|
||||
selectedManagers() {
|
||||
const set = new Set(this.rfmUserIds);
|
||||
return this.users.filter(u => set.has(u.id));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/* ===== Flows ===== */
|
||||
async load() {
|
||||
try {
|
||||
this.busy = true; this.error = null;
|
||||
@ -353,10 +420,6 @@
|
||||
const res = await fetch('/ItRequestAPI/users');
|
||||
this.users = await res.json();
|
||||
},
|
||||
async loadItTeam() {
|
||||
const r = await fetch('/ItRequestAPI/itTeam');
|
||||
this.itTeamUserIds = await r.json(); // array<int>
|
||||
},
|
||||
resolveUserName(id) {
|
||||
const u = this.users.find(x => x.id === id);
|
||||
return u ? u.name : '(unknown)';
|
||||
@ -439,11 +502,12 @@
|
||||
this.bsModal = new bootstrap.Modal(el);
|
||||
this.bsModal.show();
|
||||
},
|
||||
closeModal() {
|
||||
if (this.bsModal) this.bsModal.hide();
|
||||
},
|
||||
removeIt(uid) {
|
||||
this.itTeamUserIds = this.itTeamUserIds.filter(id => id !== uid);
|
||||
closeModal() { if (this.bsModal) this.bsModal.hide(); },
|
||||
|
||||
/* ===== IT Team endpoints ===== */
|
||||
async loadItTeam() {
|
||||
const r = await fetch('/ItRequestAPI/itTeam');
|
||||
this.itTeamUserIds = await r.json(); // array<int>
|
||||
},
|
||||
async saveItTeam() {
|
||||
try {
|
||||
@ -463,10 +527,46 @@
|
||||
} finally {
|
||||
this.savingTeam = false;
|
||||
}
|
||||
},
|
||||
removeIt(uid) {
|
||||
this.itTeamUserIds = this.itTeamUserIds.filter(id => id !== uid);
|
||||
},
|
||||
|
||||
/* ===== Request Form Managers endpoints ===== */
|
||||
async loadFormManagers() {
|
||||
const r = await fetch('/ItRequestAPI/formManagers');
|
||||
this.rfmUserIds = await r.json(); // array<int>
|
||||
},
|
||||
async saveFormManagers() {
|
||||
try {
|
||||
this.savingManagers = true;
|
||||
const r = await fetch('/ItRequestAPI/formManagers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ userIds: this.rfmUserIds })
|
||||
});
|
||||
if (!r.ok) {
|
||||
const j = await r.json().catch(() => ({}));
|
||||
throw new Error(j.message || `Save failed (${r.status})`);
|
||||
}
|
||||
alert('Request Form Managers updated.');
|
||||
} catch (e) {
|
||||
alert(e.message || 'Failed to update Request Form Managers.');
|
||||
} finally {
|
||||
this.savingManagers = false;
|
||||
}
|
||||
},
|
||||
removeRfm(uid) {
|
||||
this.rfmUserIds = this.rfmUserIds.filter(id => id !== uid);
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await Promise.all([this.load(), this.loadUsers(), this.loadItTeam()]);
|
||||
await Promise.all([
|
||||
this.load(),
|
||||
this.loadUsers(),
|
||||
this.loadItTeam(),
|
||||
this.loadFormManagers()
|
||||
]);
|
||||
}
|
||||
});
|
||||
flowApp.mount('#flowApp');
|
||||
|
||||
@ -765,6 +765,29 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
}
|
||||
|
||||
|
||||
// GET: /ItRequestAPI/formManagers -> returns [userId, ...]
|
||||
[HttpGet("formManagers")]
|
||||
public async Task<IActionResult> GetFormManagers() =>
|
||||
Ok(await _db.RequestFormManagers.Select(m => m.UserId).ToListAsync());
|
||||
|
||||
// POST: /ItRequestAPI/formManagers body: { userIds: [1,2,3] }
|
||||
public class ManagersDto { public List<int> UserIds { get; set; } = new(); }
|
||||
|
||||
[HttpPost("formManagers")]
|
||||
public async Task<IActionResult> SetFormManagers([FromBody] ManagersDto dto)
|
||||
{
|
||||
var all = _db.RequestFormManagers;
|
||||
_db.RequestFormManagers.RemoveRange(all);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
foreach (var uid in dto.UserIds.Distinct())
|
||||
_db.RequestFormManagers.Add(new RequestFormManager { UserId = uid });
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new { message = "Request Form Managers updated." });
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// GET: /ItRequestAPI/me
|
||||
// Pull snapshot from aspnetusers -> departments -> companies
|
||||
|
||||
@ -129,6 +129,7 @@ namespace PSTW_CentralSystem.DBContext
|
||||
public DbSet<ItRequestStatus> ItRequestStatus { get; set; }
|
||||
public DbSet<ItRequestAssetInfo> ItRequestAssetInfo { get; set; }
|
||||
public DbSet<ItTeamMember> ItTeamMembers { get; set; }
|
||||
public DbSet<RequestFormManager> RequestFormManagers { get; set; }
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user