fix(treasury): scope finance access and hide it when the module is off

Accounts can be edited, including the opening balance, and finance sections and accounts can be granted on a role or on one user. The finance menu stays hidden when the organization module is disabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-04 10:02:31 +03:30
co-authored by Cursor
parent 14f4e9d7d8
commit 1ccbefa7a3
26 changed files with 895 additions and 135 deletions
+105 -1
View File
@@ -14,6 +14,7 @@ import {
DATA_ACCESS_SECTIONS,
ROLE_PERMISSION_ITEMS,
type DataAccessPolicy,
type FinancialAccountDto,
type InviteUserRequest,
type OrganizationDto,
type OrgModuleKey,
@@ -74,7 +75,16 @@ const PERMISSION_MODULES: Partial<Record<RolePermissionKey, OrgModuleKey[]>> = {
conversations: ['conversations'],
conversations_groups: ['conversations'],
treasury: ['treasury'],
treasury_dashboard: ['treasury'],
treasury_incomes: ['treasury'],
treasury_expenses: ['treasury'],
treasury_receivables: ['treasury'],
treasury_accounts: ['treasury'],
treasury_payroll: ['treasury'],
treasury_recurring: ['treasury'],
treasury_reports: ['treasury'],
treasury_audit: ['treasury'],
treasury_categories: ['treasury'],
behavior: ['behavior'],
};
@@ -139,6 +149,10 @@ const roleFormDesc = ref('');
const roleFormColor = ref<RoleColor>('teal');
const roleFormParentId = ref('');
const roleFormPerms = ref<RolePermissionKey[]>(ROLE_PERMISSION_ITEMS.map((p) => p.key));
const roleFormAccountIds = ref<string[]>([]);
const roleFormOwnAccounts = ref(false);
const roleFormAccounts = ref<Array<{ id: string; name: string; responsibleName: string }>>([]);
const roleAccountQuery = ref('');
const deleteOpen = ref(false);
const deleteSaving = ref(false);
@@ -616,7 +630,11 @@ function openCreateRole(parentId?: string | null) {
? [...parent.permissions]
: visibleRolePermissions.value.map((p) => p.key);
roleFormDataAccess.value = clonePolicy(parent?.dataAccessPolicy);
roleFormAccountIds.value = [...(parent?.treasuryAccountIds ?? [])];
roleFormOwnAccounts.value = Boolean(parent?.treasuryOwnAccounts);
roleAccountQuery.value = '';
roleFormOpen.value = true;
void loadRoleAccounts();
}
function openCreateChildRole() {
@@ -636,7 +654,40 @@ function openEditRole() {
roleFormParentId.value = role.systemKey ? '' : (role.parentId ?? '');
roleFormPerms.value = [...role.permissions];
roleFormDataAccess.value = clonePolicy(role.dataAccessPolicy);
roleFormAccountIds.value = [...(role.treasuryAccountIds ?? [])];
roleFormOwnAccounts.value = Boolean(role.treasuryOwnAccounts);
roleAccountQuery.value = '';
roleFormOpen.value = true;
void loadRoleAccounts();
}
function isTreasuryDetail(key: RolePermissionKey) {
return key.startsWith('treasury_');
}
const filteredRoleAccounts = computed(() => {
const query = roleAccountQuery.value.trim();
if (!query) return roleFormAccounts.value;
return roleFormAccounts.value.filter((account) => account.name.includes(query));
});
async function loadRoleAccounts() {
try {
const { data } = await api.get<FinancialAccountDto[]>('/treasury/accounts');
roleFormAccounts.value = data.map((account) => ({
id: account.id,
name: account.name,
responsibleName: account.responsibleName?.trim() ?? '',
}));
} catch {
roleFormAccounts.value = [];
}
}
function toggleRoleAccount(id: string) {
roleFormAccountIds.value = roleFormAccountIds.value.includes(id)
? roleFormAccountIds.value.filter((item) => item !== id)
: [...roleFormAccountIds.value, id];
}
function permOn(key: RolePermissionKey) {
@@ -644,6 +695,14 @@ function permOn(key: RolePermissionKey) {
}
function toggleFormPerm(key: RolePermissionKey) {
if (key === 'treasury' && permOn('treasury')) {
roleFormPerms.value = roleFormPerms.value.filter(
(item) => item !== 'treasury' && !item.startsWith('treasury_'),
);
roleFormAccountIds.value = [];
roleFormOwnAccounts.value = false;
return;
}
roleFormPerms.value = permOn(key)
? roleFormPerms.value.filter((p) => p !== key)
: [...roleFormPerms.value, key];
@@ -667,12 +726,16 @@ async function submitRoleForm() {
permissions: RolePermissionKey[];
dataAccessPolicy?: OrgRoleDto['dataAccessPolicy'];
parentId?: string | null;
treasuryAccountIds?: string[];
treasuryOwnAccounts?: boolean;
} = {
name: roleFormName.value.trim(),
description: roleFormDesc.value.trim(),
color: roleFormColor.value,
permissions: roleFormPerms.value,
dataAccessPolicy: roleFormDataAccess.value,
treasuryAccountIds: roleFormAccountIds.value,
treasuryOwnAccounts: roleFormOwnAccounts.value,
};
if (!editing?.systemKey) {
body.parentId = roleFormParentId.value || null;
@@ -1491,11 +1554,48 @@ onUnmounted(() => {
</label>
<fieldset class="perm-set">
<legend>مجوزهای دسترسی</legend>
<label v-for="perm in visibleRolePermissions" :key="perm.key" class="perm-row">
<p class="hint">
تیک مدیریت مالی در را باز می‌کند. بخش‌های ریز جدا انتخاب می‌شوند و با روشن کردن مدیریت مالی خودبه‌خود تیک نمی‌خورند.
تا وقتی هیچ بخش ریزی ذخیره نشده، همه بخش‌ها به‌جز حقوق باز می‌مانند. با ذخیره حتی یک بخش، فقط همان‌ها می‌مانند. حقوق جداست.
</p>
<label
v-for="perm in visibleRolePermissions"
v-show="!isTreasuryDetail(perm.key) || permOn('treasury')"
:key="perm.key"
class="perm-row"
:class="{ 'perm-sub': isTreasuryDetail(perm.key) }"
>
<input type="checkbox" :checked="permOn(perm.key)" @change="toggleFormPerm(perm.key)" />
{{ perm.label }}
</label>
</fieldset>
<fieldset v-if="permOn('treasury')" class="perm-set">
<legend>دسترسی حساب‌ها</legend>
<p class="hint">
این بخش جدا از تیک بخش‌های مالی است. می‌توانید حساب‌هایی را بدهید که خود عضو مسئول آن‌هاست،
و جدا از آن حساب‌های دیگر را هم انتخاب کنید. اگر هیچ‌کدام انتخاب نشود، همه حساب‌ها باز می‌ماند.
</p>
<label class="perm-row">
<input
type="checkbox"
:checked="roleFormOwnAccounts"
@change="roleFormOwnAccounts = !roleFormOwnAccounts"
/>
حساب‌هایی که خودش مسئول آن‌هاست
</label>
<p class="hint">حساب‌های دیگر</p>
<input v-model="roleAccountQuery" placeholder="جستجوی حساب" />
<label v-for="account in filteredRoleAccounts" :key="account.id" class="perm-row">
<input
type="checkbox"
:checked="roleFormAccountIds.includes(account.id)"
@change="toggleRoleAccount(account.id)"
/>
{{ account.name }}
<template v-if="account.responsibleName"> — مسئول: {{ account.responsibleName }}</template>
</label>
<p v-if="!roleFormAccounts.length" class="hint">حسابی برای انتخاب نیست.</p>
</fieldset>
<fieldset class="perm-set">
<legend>محدوده داده</legend>
<DataAccessPolicyPanel
@@ -2381,6 +2481,10 @@ textarea {
color: #0f172a;
}
.perm-sub {
padding-inline-start: 1.4rem;
}
.sheet-handle,
.sheet-backdrop {
display: none;