Add Accountant role and CanManageBills/CanManageAccounting permissions

- AppConstants: add Accountant to CompanyRoles; add CanManageBills and
  CanManageAccounting to Policies
- ApplicationUser: add CanManageBills and CanManageAccounting bool fields
- UserManagementDtos: expose new fields in all three DTOs
- ClaimsPrincipalFactory: emit ManageBills and ManageAccounting claims
- Program.cs: add CanManageBills and CanManageAccounting policies;
  update CanManageInvoices, CanViewReports, CanManagePurchaseOrders,
  and CanManageVendors to auto-pass for Accountant role
- BillsController: replace CanManageInventory with CanManageBills on
  all write actions (correct policy — bills are not inventory)
- BankReconciliationsController: replace CanManageJobs with
  CanManageAccounting on write actions
- CompanyUsersController: add Accountant to validCompanyRoles (both
  Create/Edit), legacyRole switch, and all permission assignment blocks
- Create/Edit views: add Accountant option to role dropdown; add
  CanManageBills and CanManageAccounting checkboxes; JS auto-checks
  financial permissions when Accountant role is selected
- Migration AddAccountantRolePermissions: adds columns + backfills
  CanManageBills=1 and CanManageAccounting=1 for all CompanyAdmin users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:42:53 -04:00
parent 59beba2e15
commit feff0fa73d
12 changed files with 10850 additions and 60 deletions
@@ -56,7 +56,7 @@ public class BankReconciliationsController : Controller
// ── Create ───────────────────────────────────────────────────────────────
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
[Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
public async Task<IActionResult> Create()
{
if (!AllowAccounting()) return RedirectToAction("Landing", "Reports");
@@ -65,7 +65,7 @@ public class BankReconciliationsController : Controller
}
[HttpPost]
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
[Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(BankReconciliation model)
{
@@ -171,7 +171,7 @@ public class BankReconciliationsController : Controller
/// Returns updated running totals as JSON.
/// </summary>
[HttpPost]
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
[Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ToggleCleared(
int reconId, string entityType, int entityId, bool isCleared)
@@ -207,7 +207,7 @@ public class BankReconciliationsController : Controller
/// <summary>Completes the reconciliation. Only allowed when Difference == 0.00.</summary>
[HttpPost]
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
[Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Complete(int id, decimal difference)
{
@@ -286,7 +286,7 @@ public class BankReconciliationsController : Controller
/// suggestions client-side by auto-checking the corresponding table rows.
/// </summary>
[HttpPost]
[Authorize(Policy = AppConstants.Policies.CanManageJobs)]
[Authorize(Policy = AppConstants.Policies.CanManageAccounting)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AiSuggestMatches(int reconId)
{