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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user