diff --git a/src/api/customerSpecialPrice.ts b/src/api/customerSpecialPrice.ts new file mode 100644 index 0000000..7d74e4b --- /dev/null +++ b/src/api/customerSpecialPrice.ts @@ -0,0 +1,28 @@ +import { useAxios } from '@core/utils/useAxios' +import type { TableInput } from '@core/type/baseTable' + +export const getCustomerSpecialPricePackageTable = async (params: TableInput) => + await useAxios.post('/admin/customer_special_price_packages/table', params) + +export const getCustomerSpecialPricePackage = async (id: number) => + await useAxios.get(`/admin/customer_special_price_packages/${id}`) + +export const createCustomerSpecialPricePackage = async (payload: Record) => + await useAxios.post('/admin/customer_special_price_packages', payload) + +export const updateCustomerSpecialPricePackage = async (id: number, payload: Record) => + await useAxios.put(`/admin/customer_special_price_packages/${id}`, payload) + +export const deleteCustomerSpecialPricePackage = async (id: number) => + await useAxios.delete(`/admin/customer_special_price_packages/${id}`) + +export const toggleCustomerSpecialPricePackage = async (id: number) => + await useAxios.put(`/admin/customer_special_price_packages/${id}/toggle`) + +export const searchSpecialPriceCustomers = async (q: string) => + await useAxios.get('/admin/customer_special_price_packages/search-customers', { params: { q } }) + +export const getProductsByCategories = async (category_names: string[]) => + await useAxios.post('/admin/customer_special_price_packages/products-by-categories', { + category_names + }) diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index 39114db..1fbf065 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -202,7 +202,7 @@ import SettingValueField from './SettingValueField.vue' import SettingCard from './SettingCard.vue' import { refreshAppModeMenu } from '@/router' import { useUserStore } from '@/store/userStore' -import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings } from '@/utils/config' +import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, applyCustomerSpecialPricingFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings, setCustomerSpecialPricingEnabledFromSettings } from '@/utils/config' import { EDITABLE_SETTING_GROUPS, SYSTEM_SETTING_GROUPS, @@ -485,12 +485,14 @@ async function initConfig() { applyPriceChannelAccessFromRows(baseData.value) applySupportChatMenuEnabledFromRows(baseData.value) applyGroupSmsFromRows(baseData.value) + applyCustomerSpecialPricingFromRows(baseData.value) // در صفحهٔ عادی price_provider و show_to_users_chat و price_channel_access (can_edit=0) نیستند — از کل تنظیمات بخوان if ( !baseData.value.some((row) => row.key === 'price_provider') || !baseData.value.some((row) => row.key === 'show_to_users_chat') || !baseData.value.some((row) => row.key === 'price_channel_access') || - !baseData.value.some((row) => row.key === 'group_sms') + !baseData.value.some((row) => row.key === 'group_sms') || + !baseData.value.some((row) => row.key === 'customer_special_pricing') ) { try { const { getConfiguration } = await import('@/api/configuration') @@ -500,6 +502,7 @@ async function initConfig() { applySupportChatMenuEnabledFromRows(rows) applyPriceChannelAccessFromRows(rows) applyGroupSmsFromRows(rows) + applyCustomerSpecialPricingFromRows(rows) } catch { /* کش قبلی می‌ماند */ } @@ -572,6 +575,9 @@ function syncMenuFlagsFromSetting(key: string | undefined, value: string) { if (key === 'group_sms') { setGroupSmsEnabledFromSettings(isSwitchOn(value)) } + if (key === 'customer_special_pricing') { + setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(value)) + } } function onValueChange(row: Const, value: string) { diff --git a/src/pages/menuPages/configuration/settingGroups.ts b/src/pages/menuPages/configuration/settingGroups.ts index 3ab8183..3299546 100644 --- a/src/pages/menuPages/configuration/settingGroups.ts +++ b/src/pages/menuPages/configuration/settingGroups.ts @@ -179,7 +179,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ title: 'دسترسی مشتری به قابلیت‌ها', icon: 'vuesax-linear:people', match: (k) => - /^(enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test( + /^(enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|customer_special_pricing|tracking_systems_enabled|permision__enabled|show_to_users_chat)$/.test( k ) }, diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts index 10be518..3c18bd6 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -322,6 +322,7 @@ const KEY_TYPE_FALLBACK: Record = { sms_notif_wallet_alert: 'switch', sms_notif_support_admin: 'switch', group_sms: 'switch', + customer_special_pricing: 'switch', montzar_closed: 'switch', montzar_open_buy: 'switch', montzar_open_sell: 'switch', @@ -825,6 +826,8 @@ const KEY_HELP_FALLBACK: Record = { 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان گفتگو را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.', group_sms: 'روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.', + customer_special_pricing: + 'روشن: زیرمنوی قیمت ویژه در مشتریان و اعمال سود خرید/فروش پکیج روی مظنه و سفارش برای کاربران انتخاب‌شده. خاموش: غیرفعال. فقط از دیپلوی قابل تغییر است.', profit_gold_api_enabled: 'روشن: محاسبه سود طلا از API خارجی فعال است. خاموش: محاسبه سود از API خارجی انجام نمی‌شود (حالت پیش‌فرض اکثر نصب‌ها).', main_wallet_products: @@ -986,6 +989,7 @@ export const KEY_LABEL_FALLBACK: Record = { open_register: 'ثبت‌نام باز', sms_panel: 'پنل پیامک (کیف پول)', customer_direct_sms: 'پیامک تکی به مشتری از لیست', + customer_special_pricing: 'قیمت ویژه مشتری', charge_notif: 'پیامک شارژ ادمین', sms_notif_order_admin: 'پیامک خرید/فروش به ادمین', sms_notif_order_customer: 'پیامک خرید/فروش به مشتری', @@ -1080,6 +1084,7 @@ export const SETTING_LOCKED_KEYS = new Set([ 'fee_deduct_from_gold', 'referral_bonus_amount', 'group_sms', + 'customer_special_pricing', 'customer_direct_sms', 'enable_conditional_order', 'enable_product_funds', diff --git a/src/pages/menuPages/customers/base/components/fundsTable.vue b/src/pages/menuPages/customers/base/components/fundsTable.vue index 2844b58..40499cd 100644 --- a/src/pages/menuPages/customers/base/components/fundsTable.vue +++ b/src/pages/menuPages/customers/base/components/fundsTable.vue @@ -4,23 +4,27 @@ @@ -59,6 +63,7 @@ import { } from '@/api/user' import BasicActionSheet from '@/components/BasicActionSheet.vue' import BasicShowContext from '@/components/BasicShowContext.vue' +import { toJalali } from '@core/utils/useDataTime' const [submit, reload] = useBaseTable() const [assetFundsSubmit] = useBaseTable() @@ -106,7 +111,13 @@ const assetTxCols = [ { title: 'نوع', dataIndex: 'type_str', key: 'type_str', type: 'Text' }, { title: 'طلا', dataIndex: 'gold', key: 'gold', type: 'Number' }, { title: 'ریال', dataIndex: 'cash', key: 'cash', type: 'Number' }, - { title: 'تاریخ', dataIndex: 'created_at', key: 'created_at', type: 'Text' } + { + title: 'تاریخ', + dataIndex: 'created_at', + key: 'created_at', + type: 'DateTime', + customRender: (r: any) => (r.record?.created_at ? toJalali(r.record.created_at) : '—') + } ] const assetTxTblProps = computed(() => ({ @@ -133,8 +144,17 @@ function openAssetTx(record: any) { assetTxVisible.value = true } +const tooltipContainer = () => document.body + const fetch = () => reload() defineExpose({ fetch }) onMounted(() => {}) + + diff --git a/src/pages/menuPages/customers/base/components/piggyFundsTable.vue b/src/pages/menuPages/customers/base/components/piggyFundsTable.vue index ee6f883..63ae328 100644 --- a/src/pages/menuPages/customers/base/components/piggyFundsTable.vue +++ b/src/pages/menuPages/customers/base/components/piggyFundsTable.vue @@ -4,14 +4,16 @@ @@ -96,6 +98,15 @@ function openDetails(record: any) { detailsVisible.value = true } +const tooltipContainer = () => document.body + const fetch = () => reload() defineExpose({ fetch }) + + diff --git a/src/pages/menuPages/customers/index.vue b/src/pages/menuPages/customers/index.vue index b7368db..b730dfd 100644 --- a/src/pages/menuPages/customers/index.vue +++ b/src/pages/menuPages/customers/index.vue @@ -21,14 +21,23 @@ import { computed } from 'vue' import BasicMenu from '@/components/BasicMenu/index.vue' import { useRouterStore } from '@core/stores/router' import { get } from 'lodash' +import { customerSpecialPricingEnabledSetting, initWalletSettingsFromApi } from '@/utils/config' + +initWalletSettingsFromApi() const routerStore = useRouterStore() const menu = computed(() => { const item = routerStore.routes.find((item) => item.name === 'customers') - const children = get(item, 'children[0].children', []) as Array<{ meta?: { order?: number } }> - return [...children].sort( - (a, b) => (a.meta?.order ?? 99) - (b.meta?.order ?? 99) - ) + const children = get(item, 'children[0].children', []) as Array<{ + name?: string + meta?: { order?: number } + }> + return [...children] + .filter( + (child) => + child.name !== 'customerMain_specialPrice' || customerSpecialPricingEnabledSetting.value + ) + .sort((a, b) => (a.meta?.order ?? 99) - (b.meta?.order ?? 99)) }) diff --git a/src/pages/menuPages/customers/specialPrice/components/SpecialPriceEditorForm.vue b/src/pages/menuPages/customers/specialPrice/components/SpecialPriceEditorForm.vue new file mode 100644 index 0000000..cc049c7 --- /dev/null +++ b/src/pages/menuPages/customers/specialPrice/components/SpecialPriceEditorForm.vue @@ -0,0 +1,170 @@ + + + diff --git a/src/pages/menuPages/customers/specialPrice/components/table.vue b/src/pages/menuPages/customers/specialPrice/components/table.vue new file mode 100644 index 0000000..cd09763 --- /dev/null +++ b/src/pages/menuPages/customers/specialPrice/components/table.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/pages/menuPages/customers/specialPrice/hooks/specialPriceColumns.ts b/src/pages/menuPages/customers/specialPrice/hooks/specialPriceColumns.ts new file mode 100644 index 0000000..f94ea9d --- /dev/null +++ b/src/pages/menuPages/customers/specialPrice/hooks/specialPriceColumns.ts @@ -0,0 +1,79 @@ +import type { BaseTableColumn } from '@core/type/baseTable' +import { usePrice } from '@core/utils/usePrice' +import { toJalali } from '@core/utils/useDataTime' + +export function getSpecialPricePackageColumns(): BaseTableColumn[] { + return [ + { + title: 'ردیف', + dataIndex: 'id', + key: 'row', + filterable: false, + sortable: false, + customRender: (record: any) => record.index + 1 + }, + { + title: 'عنوان پکیج', + dataIndex: 'title', + key: 'title', + type: 'Text' + }, + { + title: 'تعداد کاربر', + dataIndex: 'customers_count', + key: 'customers_count', + type: 'Number', + filterable: false + }, + { + title: 'تعداد محصول', + dataIndex: 'products_count', + key: 'products_count', + type: 'Number', + filterable: false + }, + { + title: 'سود خرید', + dataIndex: 'profit_buy', + key: 'profit_buy', + type: 'Text', + customRender: ({ record }) => usePrice(record.profit_buy ?? 0, true, '') + }, + { + title: 'سود فروش', + dataIndex: 'profit_sell', + key: 'profit_sell', + type: 'Text', + customRender: ({ record }) => usePrice(record.profit_sell ?? 0, true, '') + }, + { + title: 'نمایش محصول', + dataIndex: 'show_product', + key: 'show_product', + type: 'Text', + customRender: ({ record }) => (record.show_product ? 'فعال' : 'مخفی') + }, + { + title: 'وضعیت', + dataIndex: 'is_active', + key: 'is_active', + type: 'Text', + customRender: ({ record }) => (record.is_active ? 'فعال' : 'غیرفعال') + }, + { + title: 'تاریخ', + dataIndex: 'updated_at', + key: 'updated_at', + type: 'DateTime', + customRender: (r: any) => (r.record?.updated_at ? toJalali(r.record.updated_at) : '—') + }, + { + title: 'عملیات', + dataIndex: 'action', + key: 'action', + slot: 'action', + filterable: false, + sortable: false + } + ] +} diff --git a/src/pages/menuPages/customers/specialPrice/index.vue b/src/pages/menuPages/customers/specialPrice/index.vue new file mode 100644 index 0000000..23986e0 --- /dev/null +++ b/src/pages/menuPages/customers/specialPrice/index.vue @@ -0,0 +1,179 @@ + + + diff --git a/src/router/routes/customers.ts b/src/router/routes/customers.ts index eb265ad..4e91366 100644 --- a/src/router/routes/customers.ts +++ b/src/router/routes/customers.ts @@ -70,6 +70,16 @@ const Customers: RouteRecordRaw = { title: 'احراز هویت', order: 3 } + }, + { + name: 'customerMain_specialPrice', + path: 'special-price', + component: () => import('@/pages/menuPages/customers/specialPrice/index.vue'), + meta: { + icon: 'vuesax-linear:dollar-circle', + title: 'قیمت ویژه', + order: 4 + } } ] } diff --git a/src/utils/config.ts b/src/utils/config.ts index 70b7925..1086738 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -58,6 +58,9 @@ export const priceTriggerEnabledSetting = ref(false) /** reactive — دکمه ارسال پیامک گروهی در صفحه پیامک‌ها */ export const groupSmsEnabledSetting = ref(false) +/** reactive — زیرمنوی قیمت ویژه مشتری */ +export const customerSpecialPricingEnabledSetting = ref(false) + /** reactive — منو و زنگوله اعلان داخل اپ */ export const appNotificationEnabledSetting = ref(true) @@ -156,6 +159,10 @@ export function setGroupSmsEnabledFromSettings(value: boolean): void { groupSmsEnabledSetting.value = value } +export function setCustomerSpecialPricingEnabledFromSettings(value: boolean): void { + customerSpecialPricingEnabledSetting.value = value +} + export function setConditionalOrderEnabledFromSettings(value: boolean): void { conditionalOrderEnabledSetting.value = value } @@ -262,6 +269,13 @@ export function applyGroupSmsFromRows(rows: Array<{ key?: string; value?: string if (row) setGroupSmsEnabledFromSettings(isSwitchOn(row.value)) } +export function applyCustomerSpecialPricingFromRows( + rows: Array<{ key?: string; value?: string | null }> +) { + const row = rows.find((item) => item.key === 'customer_special_pricing') + if (row) setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(row.value)) +} + export function applyConditionalOrderEnabledFromRows( rows: Array<{ key?: string; value?: string | null }> ) { @@ -330,6 +344,7 @@ async function bootstrapSettingsFromApi(): Promise { applyTrackingSystemsEnabledFromRows(list) applyCustomerDirectSmsFromRows(list) applyGroupSmsFromRows(list) + applyCustomerSpecialPricingFromRows(list) applyConditionalOrderEnabledFromRows(list) applyProductFundsEnabledFromRows(list) applyCustomerLevelProfitEnabledFromRows(list)