feat(access): keep a single owner per organization

The person who registered stays the only owner. Full day-to-day access is granted through the manager role instead of a second owner.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-10 14:59:32 +03:30
co-authored by Cursor
parent 932dc3f1c1
commit 25a24772fc
7 changed files with 56 additions and 13 deletions
+4 -4
View File
@@ -138,7 +138,7 @@ const editPassword = ref('');
const editRoleLabel = ref('');
const editRoleId = ref('');
const editInitialRoleId = ref('');
/** مدیر نمی‌تواند نقش مالک را عوض کند؛ آن موقع فقط برچسب نقش نشان داده می‌شود */
/** نقش مالک قفل است؛ مالک همان ثبت‌نام‌کننده می‌ماند */
const editRoleLocked = ref(false);
const roleFormOpen = ref(false);
@@ -253,7 +253,7 @@ const parentRoleOptions = computed(() => {
});
const inviteableRoles = computed(() =>
roles.value.filter((r) => r.systemKey !== 'owner' || auth.user?.role === 'owner'),
roles.value.filter((r) => r.systemKey !== 'owner'),
);
function roleTreeLabel(role: OrgRoleDto) {
@@ -482,7 +482,7 @@ function openEditUser(user: OrgUserDto) {
editRoleLabel.value = user.orgRoleName || '—';
editRoleId.value = user.orgRoleId ?? '';
editInitialRoleId.value = user.orgRoleId ?? '';
editRoleLocked.value = user.role === 'owner' && auth.user?.role !== 'owner';
editRoleLocked.value = user.role === 'owner';
editOpen.value = true;
error.value = '';
ok.value = '';
@@ -1517,7 +1517,7 @@ onUnmounted(() => {
</option>
</select>
</label>
<p v-else class="hint">نقش فعلی: {{ editRoleLabel }} — نقش مالک را فقط مالک می‌تواند تغییر دهد.</p>
<p v-else class="hint">نقش فعلی: {{ editRoleLabel }} — مالک همان کسی است که ثبت‌نام کرده و عوض نمی‌شود. برای دسترسی کامل نقش مدیر را بدهید.</p>
</form>
<template #footer>
<button type="button" class="btn ghost" @click="editOpen = false">انصراف</button>
@@ -51,7 +51,8 @@ async function loadOwners() {
const { data } = await platformApi.get<PlatformOrganizationDto>(
`/platform/organizations/${orgId.value}`,
);
owners.value = (data.members ?? []).filter((member) => member.role === 'owner');
const ownersInOrder = (data.members ?? []).filter((member) => member.role === 'owner');
owners.value = ownersInOrder.slice(0, 1);
} catch (e) {
error.value = apiError(e, 'بارگذاری اشتراک محیط ناموفق بود');
} finally {
@@ -554,7 +554,8 @@ onMounted(() => {
<div class="member-actions">
<select
:value="member.role"
:disabled="busyId === `role-${member.membershipId}`"
:disabled="member.role === 'owner' || busyId === `role-${member.membershipId}`"
:title="member.role === 'owner' ? 'مالک همان ثبت‌نام‌کننده است و عوض نمی‌شود' : undefined"
@change="
setMembershipRole(
member.membershipId,
@@ -562,7 +563,11 @@ onMounted(() => {
)
"
>
<option v-for="role in USER_ROLES" :key="role" :value="role">
<option
v-for="role in USER_ROLES.filter((role) => role !== 'owner' || member.role === 'owner')"
:key="role"
:value="role"
>
{{ USER_ROLE_LABELS[role] }}
</option>
</select>
+7 -2
View File
@@ -247,7 +247,8 @@ onMounted(() => {
<div class="member-actions">
<select
:value="mem.role"
:disabled="busyId === `role-${mem.membershipId}`"
:disabled="mem.role === 'owner' || busyId === `role-${mem.membershipId}`"
:title="mem.role === 'owner' ? 'مالک همان ثبت‌نام‌کننده است و عوض نمی‌شود' : undefined"
@change="
setMembershipRole(
user.id,
@@ -256,7 +257,11 @@ onMounted(() => {
)
"
>
<option v-for="role in USER_ROLES" :key="role" :value="role">
<option
v-for="role in USER_ROLES.filter((role) => role !== 'owner' || mem.role === 'owner')"
:key="role"
:value="role"
>
{{ USER_ROLE_LABELS[role] }}
</option>
</select>