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
@@ -90,6 +90,7 @@
<select asp-for="CompanyRole" class="form-select">
<option value="Viewer">Viewer (Read-only)</option>
<option value="Worker">Worker</option>
<option value="Accountant">Accountant</option>
<option value="Manager">Manager</option>
<option value="CompanyAdmin">Company Admin</option>
</select>
@@ -215,6 +216,19 @@
<label asp-for="CanViewReports" class="form-check-label">Can View Reports</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input asp-for="CanManageBills" class="form-check-input permission-checkbox" />
<label asp-for="CanManageBills" class="form-check-label">Can Manage Bills &amp; AP</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input asp-for="CanManageAccounting" class="form-check-input permission-checkbox" />
<label asp-for="CanManageAccounting" class="form-check-label">Can Manage Accounting</label>
<div class="form-text text-muted small">Chart of accounts, bank reconciliations, journal entries</div>
</div>
</div>
</div>
<div class="d-flex gap-2 justify-content-end">
@@ -249,26 +263,30 @@
const adminAlert = document.getElementById('companyAdminAlert');
const isSuperAdmin = @((ViewBag.IsSuperAdmin as bool? ?? false) ? "true" : "false");
const accountantDefaults = ['CanManageInvoices', 'CanViewReports', 'CanManageVendors', 'CanManageBills', 'CanManageAccounting'];
function updatePermissionState() {
const isCompanyAdmin = roleSelect.value === 'CompanyAdmin';
const role = roleSelect.value;
const isCompanyAdmin = role === 'CompanyAdmin';
const isAccountant = role === 'Accountant';
if (isSuperAdmin) {
// SuperAdmins can always edit individual permissions
adminAlert.style.display = 'none';
permissionCheckboxes.forEach(checkbox => { checkbox.disabled = false; });
return;
}
// Show/hide alert
adminAlert.style.display = isCompanyAdmin ? 'block' : 'none';
// Check all and disable if Company Admin, otherwise enable
permissionCheckboxes.forEach(checkbox => {
if (isCompanyAdmin) {
checkbox.checked = true;
checkbox.disabled = true;
} else {
checkbox.disabled = false;
if (isAccountant) {
checkbox.checked = accountantDefaults.includes(checkbox.id);
}
}
});
}