feat(permissions): carve out dedicated payments resource
Payments (deposit / balance / refund records on an interest) used to
share `invoices.record_payment`, which forces a port that doesn't
issue invoices at all to still navigate the invoicing permission
group to grant its sales reps payment-recording rights. Splitting
the resource lets admins gate the two surfaces independently.
The new resource has three actions:
- view — gates the UI affordance (API reads still go through
`interests.view`)
- record — POST / PATCH a payment
- delete — DELETE a payment record
Seed maps updated for all six system roles; existing role rows +
per-user permission overrides are backfilled by migration 0064 so
upgrades don't silently lose access. Two call sites (POST /interests/
[id]/payments, PATCH /payments/[id]) → payments.record; one
(DELETE /payments/[id]) → payments.delete. The PermissionGates on the
payments-section UI swap to the new keys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ const DEFAULT_PERMISSIONS: Record<string, Record<string, boolean>> = {
|
||||
record_payment: false,
|
||||
export: false,
|
||||
},
|
||||
payments: { view: false, record: false, delete: false },
|
||||
files: { view: false, upload: false, edit: false, delete: false, manage_folders: false },
|
||||
email: { view: false, send: false, configure_account: false },
|
||||
reminders: {
|
||||
@@ -105,6 +106,7 @@ const GROUP_LABELS: Record<string, string> = {
|
||||
documents: 'Documents',
|
||||
expenses: 'Expenses',
|
||||
invoices: 'Invoices',
|
||||
payments: 'Payments',
|
||||
files: 'Files',
|
||||
email: 'Email',
|
||||
reminders: 'Reminders',
|
||||
|
||||
@@ -36,6 +36,7 @@ const GROUP_LABELS: Record<string, string> = {
|
||||
documents: 'Documents',
|
||||
expenses: 'Expenses',
|
||||
invoices: 'Invoices',
|
||||
payments: 'Payments',
|
||||
files: 'Files',
|
||||
email: 'Email',
|
||||
reminders: 'Reminders',
|
||||
@@ -78,6 +79,7 @@ const PERMISSION_LEAVES: Record<string, string[]> = {
|
||||
],
|
||||
expenses: ['view', 'create', 'edit', 'delete', 'export', 'scan_receipt'],
|
||||
invoices: ['view', 'create', 'edit', 'delete', 'send', 'record_payment', 'export'],
|
||||
payments: ['view', 'record', 'delete'],
|
||||
files: ['view', 'upload', 'edit', 'delete', 'manage_folders'],
|
||||
email: ['view', 'send', 'configure_account'],
|
||||
reminders: ['view_own', 'view_all', 'create', 'edit_own', 'edit_all', 'assign_others'],
|
||||
|
||||
@@ -128,7 +128,7 @@ export function PaymentsSection({
|
||||
that.
|
||||
</p>
|
||||
</div>
|
||||
<PermissionGate resource="invoices" action="record_payment">
|
||||
<PermissionGate resource="payments" action="record">
|
||||
<Button size="sm" className="h-8 px-3 text-xs" onClick={() => setRecordOpen(true)}>
|
||||
<Plus className="size-3.5" aria-hidden />
|
||||
Record payment
|
||||
@@ -184,7 +184,7 @@ export function PaymentsSection({
|
||||
<Receipt className="size-3 text-emerald-600" aria-hidden />
|
||||
) : null}
|
||||
</div>
|
||||
<PermissionGate resource="invoices" action="record_payment">
|
||||
<PermissionGate resource="payments" action="delete">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Delete payment record"
|
||||
|
||||
Reference in New Issue
Block a user