Add customer multi-level referral profit editor and related settings UI.

Expose enable_customer_level_profit in config and let admins edit per-customer level percents from the customers table/mobile sheets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-10 16:48:53 +03:30
co-authored by Cursor
parent 4dde172ed7
commit de97d7caa6
12 changed files with 252 additions and 21 deletions
@@ -0,0 +1,144 @@
<template>
<BasicModal
v-model:visible="visibleProxy"
:width="isMobile ? '94vw' : '32rem'"
title="سود چندسطحی معرف"
ok-text="ذخیره"
:loading="loading"
@ok="submit"
@close="close"
>
<div class="flex flex-col gap-3">
<p class="m-0 text-sm" style="color: var(--admin-text-secondary)">
مشتری:
<strong class="text-[var(--admin-text)]">{{ customer?.name || '—' }}</strong>
</p>
<p class="m-0 text-xs" style="color: var(--admin-text-secondary)">
درصد از کارمزد سفارش زیرمجموعه‌ها. سطح ۱ یعنی معرف مستقیم، سطح ۲ معرفِ معرف، و همین‌طور الی آخر.
</p>
<div class="flex flex-col gap-2">
<div
v-for="(item, index) in levels"
:key="index"
class="flex flex-wrap items-end gap-2"
>
<BasicInput
v-model:value="item.level"
type="number"
class="min-w-28 flex-1"
:addon-after="Number(item.level) === 0 ? 'سطح (جدید)' : 'سطح'"
/>
<BasicInput
v-model:value="item.present"
type="number"
class="min-w-28 flex-1"
addon-after="درصد سود"
/>
<BaseButton size="small" type="default" danger @click="removeLevel(index)">
حذف
</BaseButton>
</div>
</div>
<BaseButton type="dashed" class="!w-full" icon="vuesax-linear:add" @click="addLevel">
افزودن سطح
</BaseButton>
</div>
</BasicModal>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { cloneDeep } from 'lodash'
import { message } from 'ant-design-vue'
import BasicModal from '@/components/BasicModal.vue'
import BasicInput from '@/components/BasicInput.vue'
import { updateCustomer } from '@/api/customer'
import type { Customer, ProfitByLevel } from '@/model/customer'
import { isMobile } from '@core/utils/is'
const props = defineProps<{
visible: boolean
customer: Customer | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}>()
const loading = ref(false)
const levels = ref<ProfitByLevel[]>([])
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
function normalizeLevels(raw: unknown): ProfitByLevel[] {
if (!Array.isArray(raw)) return []
return raw
.map((row) => {
if (Array.isArray(row)) {
return { level: Number(row[0] ?? 0), present: Number(row[1] ?? 0) }
}
const item = row as ProfitByLevel
return {
level: Number(item?.level ?? 0),
present: Number(item?.present ?? 0),
}
})
.filter((row) => Number.isFinite(row.level) && Number.isFinite(row.present))
}
watch(
() => [props.visible, props.customer] as const,
([visible]) => {
if (!visible) return
const fromCustomer = normalizeLevels(props.customer?.profit_amount_by_lvl)
levels.value = fromCustomer.length
? cloneDeep(fromCustomer)
: [{ level: 1, present: 0 }]
},
)
function addLevel() {
const nextLevel =
levels.value.reduce((max, row) => Math.max(max, Number(row.level) || 0), 0) + 1
levels.value.push({ level: nextLevel, present: 0 })
}
function removeLevel(index: number) {
levels.value.splice(index, 1)
}
function close() {
visibleProxy.value = false
}
async function submit() {
if (!props.customer?.id) return
const payload = levels.value
.map((row) => ({
level: Number(row.level) || 0,
present: Number(row.present) || 0,
}))
.filter((row) => row.level > 0 && row.present > 0)
try {
loading.value = true
await updateCustomer(props.customer.id, {
profit_amount_by_lvl: payload,
} as Customer)
message.success('درصدهای سود سطح ذخیره شد.')
emit('success')
close()
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
</script>
@@ -30,6 +30,7 @@
@confirmUser="forwardAndClose('confirmUser', $event)"
@sendSms="forwardAndClose('sendSms', $event)"
@editAccounting="forwardAndClose('editAccounting', $event)"
@levelProfit="forwardAndClose('levelProfit', $event)"
/>
</template>
@@ -61,6 +62,7 @@ const emit = defineEmits<{
(e: 'confirmUser', item: Customer): void
(e: 'sendSms', item: Customer): void
(e: 'editAccounting', item: Customer): void
(e: 'levelProfit', item: Customer): void
}>()
const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet<Customer>()
@@ -75,6 +77,7 @@ type SheetAction =
| 'confirmUser'
| 'sendSms'
| 'editAccounting'
| 'levelProfit'
function forward(action: SheetAction, item: Customer) {
emit(action, item)
@@ -53,6 +53,15 @@
<BaseButton size="small" type="default" icon="vuesax-linear:edit" @click="closeAndEmit('edit', row)">
ویرایش
</BaseButton>
<BaseButton
v-if="customerLevelProfitEnabled"
size="small"
type="default"
icon="vuesax-linear:hierarchy-2"
@click="closeAndEmit('levelProfit', row)"
>
سود چندسطحی
</BaseButton>
<BaseButton size="small" type="default" icon="vuesax-linear:archive-tick" @click="closeAndEmit('archive', row)">
بایگانی
</BaseButton>
@@ -121,6 +130,7 @@ import CustomerWalletBalancePanel from '@/components/customer/CustomerWalletBala
import {
accountingProviderSetting,
customerDirectSmsEnabledSetting,
customerLevelProfitEnabledSetting,
initAccountingSettingsFromApi
} from '@/utils/config'
import { displayAccountingCode } from '@/utils/accountingCode'
@@ -142,10 +152,12 @@ const emit = defineEmits<{
(e: 'confirmUser', item: Customer): void
(e: 'sendSms', item: Customer): void
(e: 'editAccounting', item: Customer): void
(e: 'levelProfit', item: Customer): void
}>()
const chargeTargets = ref<ChargeTarget[]>([])
const customerDirectSmsEnabled = customerDirectSmsEnabledSetting
const customerLevelProfitEnabled = customerLevelProfitEnabledSetting
const accountingCodeLabel = computed(() => {
const provider = accountingProviderSetting.value
@@ -108,6 +108,15 @@
<template #title><span>ویرایش</span></template>
<Icon icon="vuesax-linear:edit" color="#4572E9" />
</a-tooltip>
<a-tooltip
v-if="customerLevelProfitEnabled"
placement="bottom"
class="cursor-pointer"
@click="openLevelProfit(record)"
>
<template #title><span>سود چندسطحی معرف</span></template>
<Icon icon="vuesax-linear:hierarchy-2" color="#ca8a04" />
</a-tooltip>
<a-tooltip
:key="record.trade_active"
placement="bottom"
@@ -157,11 +166,18 @@
@confirmUser="openConfirmUser"
@sendSms="openDirectSms"
@editAccounting="openAccountingCodeEdit"
@levelProfit="openLevelProfit"
/>
</div>
<CustomerDirectSmsModal v-model:visible="directSmsVisible" :customer="directSmsCustomer" />
<CustomerLevelProfitModal
v-model:visible="levelProfitVisible"
:customer="levelProfitCustomer"
@success="fetch"
/>
<CustomerAccountingCodeModal
v-model:visible="accountingCodeVisible"
:customer="accountingCodeCustomer"
@@ -234,6 +250,7 @@ import {
import { buildCustomerColumns } from '@/pages/menuPages/customers/base/hooks/customerColumns'
import CustomerMobileCardList from '@/pages/menuPages/customers/base/components/CustomerMobileCardList.vue'
import CustomerDirectSmsModal from '@/pages/menuPages/customers/base/components/CustomerDirectSmsModal.vue'
import CustomerLevelProfitModal from '@/pages/menuPages/customers/base/components/CustomerLevelProfitModal.vue'
import CustomerAccountingCodeModal from '@/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
import useBaseTable from '@core/components/BaseTable/hooks/useBaseTable'
@@ -251,6 +268,7 @@ import {
pargarEnabledSetting,
accountingProviderSetting,
customerDirectSmsEnabledSetting,
customerLevelProfitEnabledSetting,
initAccountingSettingsFromApi,
initWalletSettingsFromApi
} from '@/utils/config'
@@ -272,6 +290,9 @@ const pargarRegisterVisible = ref(false)
const pargarCustomer = ref<Customer>()
const pargarEnabled = pargarEnabledSetting
const customerDirectSmsEnabled = customerDirectSmsEnabledSetting
const customerLevelProfitEnabled = customerLevelProfitEnabledSetting
const levelProfitVisible = ref(false)
const levelProfitCustomer = ref<Customer | null>(null)
const accountingCodeVisible = ref(false)
const accountingCodeCustomer = ref<Customer | null>(null)
const accountingEnabled = computed(() => {
@@ -329,6 +350,11 @@ function openDirectSms(user: Customer) {
directSmsVisible.value = true
}
function openLevelProfit(user: Customer) {
levelProfitCustomer.value = user
levelProfitVisible.value = true
}
async function changeCustomerActivationFunction(id: number) {
try {
loading.value = true