diff --git a/apps/api/src/common/data-access.ts b/apps/api/src/common/data-access.ts index 6c10b18..475a96d 100644 --- a/apps/api/src/common/data-access.ts +++ b/apps/api/src/common/data-access.ts @@ -115,9 +115,7 @@ export function buildContactRuleWhere( if (!ids.length) return null; and.push(ids.length === 1 ? { ownerId: ids[0]! } : { ownerId: { in: ids } }); } - if (rule.groupIds?.length) { - and.push({ groupLinks: { some: { groupId: { in: rule.groupIds } } } }); - } + // groupIds دیگر فیلتر دسترسی نیست؛ دید گروه فقط از مسئول‌گروه (personViaGroupAssignee) if (rule.sources?.length) { and.push({ OR: [{ importSource: { in: rule.sources } }, { sourceRef: { in: rule.sources } }], @@ -188,20 +186,13 @@ export function customersCompanyWhere( if (!ids.length) continue; and.push(ids.length === 1 ? { ownerId: ids[0]! } : { ownerId: { in: ids } }); } - if (rule.groupIds?.length) { - and.push({ groupLinks: { some: { groupId: { in: rule.groupIds } } } }); - } + // groupIds دیگر فیلتر دسترسی نیست؛ دید گروه فقط از مسئول‌گروه (companyViaGroupAssignee) if (rule.sources?.length) { and.push({ OR: [{ importSource: { in: rule.sources } }, { sourceRef: { in: rule.sources } }], }); } - if ( - rule.moods?.length && - !rule.owners?.length && - !rule.groupIds?.length && - !rule.sources?.length - ) { + if (rule.moods?.length && !rule.owners?.length && !rule.sources?.length) { continue; } if (!and.length) continue; @@ -469,9 +460,14 @@ export function sectionOwnerIdUnion( if (rule.owners?.length) { sawOwnerRule = true; for (const id of ownerIdList(rule, ctx)) ids.add(id); - } else if (rule.groupIds?.length || rule.sources?.length || rule.moods?.length || - rule.stageIds?.length || rule.pipelineIds?.length || rule.boardIds?.length || - rule.typeIds?.length) { + } else if ( + rule.sources?.length || + rule.moods?.length || + rule.stageIds?.length || + rule.pipelineIds?.length || + rule.boardIds?.length || + rule.typeIds?.length + ) { sawNonOwnerRule = true; } } diff --git a/apps/web/src/components/DataAccessEditor.vue b/apps/web/src/components/DataAccessEditor.vue index 73571f4..05c7198 100644 --- a/apps/web/src/components/DataAccessEditor.vue +++ b/apps/web/src/components/DataAccessEditor.vue @@ -22,7 +22,6 @@ const props = withDefaults( defineProps<{ modelValue: SectionDataAccess; section?: DataAccessSection; - groups?: IdOption[]; stages?: IdOption[]; pipelines?: IdOption[]; boards?: IdOption[]; @@ -36,7 +35,6 @@ const props = withDefaults( }>(), { section: 'customers', - groups: () => [], stages: () => [], pipelines: () => [], boards: () => [], @@ -53,9 +51,9 @@ const emit = defineEmits<{ const sectionLabel = computed(() => DATA_ACCESS_SECTION_LABELS[props.section]); -const showGroups = computed(() => props.section === 'customers'); const showMoods = computed(() => props.section === 'customers'); const showImportSources = computed(() => props.section === 'customers'); +const isCustomers = computed(() => props.section === 'customers'); const showLeadSources = computed(() => props.section === 'leads'); const showNoteTypes = computed(() => props.section === 'notes'); const showTicketStatuses = computed(() => props.section === 'tickets'); @@ -119,9 +117,8 @@ const boardsLegend = computed(() => /** چند قانون قدیمی → یک فیلتر (اتحاد گزینه‌ها) برای ویرایش ساده */ function collapseRules(rules: DataAccessRule[]): DataAccessRule { if (!rules.length) return {}; - if (rules.length === 1) return { ...rules[0]! }; + if (rules.length === 1) return stripCustomerGroupIds({ ...rules[0]! }); const owners = new Set(); - const groupIds = new Set(); const sources = new Set(); const moods = new Set(); const stageIds = new Set(); @@ -130,7 +127,6 @@ function collapseRules(rules: DataAccessRule[]): DataAccessRule { const typeIds = new Set(); for (const r of rules) { for (const o of r.owners ?? []) owners.add(o); - for (const id of r.groupIds ?? []) groupIds.add(id); for (const s of r.sources ?? []) sources.add(s); for (const m of r.moods ?? []) moods.add(m); for (const id of r.stageIds ?? []) stageIds.add(id); @@ -140,7 +136,6 @@ function collapseRules(rules: DataAccessRule[]): DataAccessRule { } const out: DataAccessRule = {}; if (owners.size) out.owners = [...owners]; - if (groupIds.size) out.groupIds = [...groupIds]; if (sources.size) out.sources = [...sources]; if (moods.size) out.moods = [...moods]; if (stageIds.size) out.stageIds = [...stageIds]; @@ -150,10 +145,17 @@ function collapseRules(rules: DataAccessRule[]): DataAccessRule { return out; } +/** دسترسی گروه فقط از مسئول‌گروه؛ groupIds دیگر در فیلتر مشتریان ذخیره/نمایش نمی‌شود */ +function stripCustomerGroupIds(rule: DataAccessRule): DataAccessRule { + if (!isCustomers.value || !rule.groupIds?.length) return rule; + const { groupIds: _drop, ...rest } = rule; + return rest; +} + function ruleHasAnyFilter(rule: DataAccessRule): boolean { return Boolean( rule.owners?.length || - rule.groupIds?.length || + (!isCustomers.value && rule.groupIds?.length) || rule.sources?.length || rule.moods?.length || rule.stageIds?.length || @@ -184,10 +186,26 @@ const mode = computed({ const filter = computed(() => collapseRules(props.modelValue?.rules ?? [])); +/** پاک‌سازی groupIds قدیمی مشتریان هنگام باز شدن ویرایشگر */ +watch( + () => [props.section, props.modelValue] as const, + () => { + if (!isCustomers.value || props.modelValue?.mode !== 'rules') return; + const rules = props.modelValue.rules ?? []; + if (!rules.some((r) => r.groupIds?.length)) return; + const collapsed = collapseRules(rules); + emit('update:modelValue', { + mode: 'rules', + rules: [ruleHasAnyFilter(collapsed) ? collapsed : {}], + }); + }, + { immediate: true }, +); + function emitFilter(next: DataAccessRule) { const cleaned: DataAccessRule = {}; if (next.owners?.length) cleaned.owners = [...next.owners]; - if (next.groupIds?.length) cleaned.groupIds = [...next.groupIds]; + if (!isCustomers.value && next.groupIds?.length) cleaned.groupIds = [...next.groupIds]; if (next.sources?.length) cleaned.sources = [...next.sources]; if (next.moods?.length) cleaned.moods = [...next.moods]; if (next.stageIds?.length) cleaned.stageIds = [...next.stageIds]; @@ -210,7 +228,6 @@ function toggleOwner(kind: DataAccessOwnerKind) { } type ListKey = - | 'groupIds' | 'sources' | 'moods' | 'stageIds' @@ -293,16 +310,6 @@ const moodOptions: Choice[] = PERSON_MOODS.map((mood) => ({ const pickers = computed((): Picker[] => { const rule = filter.value; const items: Picker[] = []; - if (showGroups.value) { - items.push({ - key: 'groupIds', - legend: 'گروه‌ها', - mode: 'multiple', - placeholder: props.groups.length ? 'جستجو و انتخاب گروه…' : 'گروهی تعریف نشده', - notFound: props.groups.length ? 'موردی پیدا نشد' : 'گروهی تعریف نشده', - options: withSelected(namedOptions(props.groups), rule.groupIds), - }); - } if (showImportSources.value) { const ready = props.importSourcesReady; const failed = props.importSourcesFailed; @@ -417,7 +424,6 @@ const filterSummary = computed(() => { const parts: string[] = []; if (rule.owners?.includes('self')) parts.push(ownerSelfLabel.value); if (rule.owners?.includes('team')) parts.push('تیم‌های عضو'); - if (rule.groupIds?.length) parts.push(`${rule.groupIds.length} گروه`); if (rule.sources?.length) parts.push(`${rule.sources.length} مورد منبع/نوع`); if (rule.moods?.length) parts.push(`${rule.moods.length} حال`); if (rule.pipelineIds?.length) parts.push(`${rule.pipelineIds.length} پایپ‌لاین`); @@ -446,6 +452,9 @@ const filterSummary = computed(() => { فقط رکوردهایی دیده می‌شوند که با همهٔ فیلترهای پرشده جور باشند. در هر فهرست، چند انتخاب یعنی هر کدام کافی است. خالی یعنی بدون محدودیت. +

تنظیم قبلی چندتکه بود؛ اینجا در یک فیلتر ادغام شده. با ذخیره به همین شکل ساده ثبت می‌شود. diff --git a/apps/web/src/components/DataAccessPolicyPanel.vue b/apps/web/src/components/DataAccessPolicyPanel.vue index bd1c0ac..831e8e8 100644 --- a/apps/web/src/components/DataAccessPolicyPanel.vue +++ b/apps/web/src/components/DataAccessPolicyPanel.vue @@ -16,7 +16,6 @@ import { emptySectionDataAccess, } from '@mento/shared'; import { api } from '@/lib/api'; -import { useCustomerGroups } from '@/lib/customer-groups'; import DataAccessEditor, { type IdOption } from '@/components/DataAccessEditor.vue'; const props = defineProps<{ @@ -85,7 +84,6 @@ function setSection(key: DataAccessSection, next: SectionDataAccess) { }); } -const { groups: customerGroups, load: loadCustomerGroups } = useCustomerGroups(); const importSources = ref>([]); const importSourcesReady = ref(false); const importSourcesFailed = ref(false); @@ -142,7 +140,6 @@ async function loadTreasuryAccounts() { } async function loadOptions() { - await loadCustomerGroups().catch(() => undefined); try { const { data } = await api.get('/people/source-stats'); importSources.value = data.map((row) => ({ value: row.source, label: row.label })); @@ -362,7 +359,6 @@ const typesForActive = computed(() => { :key="active" :section="editorSection" :model-value="sectionValue(editorSection)" - :groups="customerGroups" :stages="stagesForActive" :pipelines="pipelines" :boards="boardsForActive" diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index ff415c6..8250c94 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -653,7 +653,10 @@ export const DATA_ACCESS_LEGACY_SCOPE: Record