diff --git a/src/api/goldCredit.ts b/src/api/goldCredit.ts new file mode 100644 index 0000000..082cece --- /dev/null +++ b/src/api/goldCredit.ts @@ -0,0 +1,111 @@ +import useAxios from '@core/utils/useAxios' +import type { TableInput } from '@core/type/baseTable' + +export interface GoldCreditForm { + customer_id?: number + gold_amount?: number + months?: number + interest_type?: 'none' | 'percent' | 'fixed_gram' + interest_value?: number + delivery_method?: 'auto' | 'manual' | 'in_person' + notes?: string + has_check?: boolean + has_contract?: boolean + check_note?: string + contract_note?: string + penalty_type?: 'rial' | 'gold' + penalty_amount?: number + grace_hours?: number +} + +export interface GoldCreditInstallment { + id: number + installment_number: number + due_date: string + amount_soot: number + principal_gold: number + interest_gold: number + total_gold: number + paid_gold: number + paid_rial: number + status: 'pending' | 'paid' | 'partial' | 'overdue' | 'prepaid' + paid_at?: string + penalty_days?: number + penalty_rial?: number + penalty_gold?: number +} + +export interface GoldCredit { + id: number + customer_id: number + gold_amount: number + months: number + interest_type: 'none' | 'percent' | 'fixed_gram' + interest_value: number + delivery_method: 'auto' | 'manual' | 'in_person' + status: string + source: string + total_interest_gold: number + total_payable_gold: number + paid_gold: number + paid_rial: number + notes?: string + has_check?: boolean + has_contract?: boolean + check_note?: string + contract_note?: string + penalty_type?: 'rial' | 'gold' + penalty_amount?: number + grace_hours?: number + customer?: { id: number; name: string; mobile: string } + installments?: GoldCreditInstallment[] +} + +export interface GoldCreditRequest { + id: number + customer_id: number + requested_gold: number + requested_months?: number + note?: string + status: 'pending' | 'approved' | 'rejected' + admin_note?: string + customer?: { id: number; name: string; mobile: string } +} + +export const createGoldCredit = async (data: GoldCreditForm) => + await useAxios.post('/admin/gold_credit', data) + +export const updateGoldCredit = async (id: number, data: Partial & { status?: string }) => + await useAxios.put(`/admin/gold_credit/${id}`, data) + +export const previewGoldCredit = async (data: GoldCreditForm) => + await useAxios.post<{ installments: GoldCreditInstallment[]; total_payable_gold: number }>( + '/admin/gold_credit/preview', + data + ) + +export const getGoldCreditTable = async (params: TableInput) => + await useAxios.post('/admin/gold_credit/table', params) + +export const getGoldCreditRequestsTable = async (params: TableInput) => + await useAxios.post('/admin/gold_credit/requests/table', params) + +export const getCustomerGoldCreditInstallments = async (customerId: number) => + await useAxios.post<{ rows: GoldCreditInstallment[]; credit: GoldCredit | null }>( + `/admin/customers/${customerId}/gold_credit/installments`, + {} + ) + +export const approveGoldCreditRequest = async (id: number, data: GoldCreditForm) => + await useAxios.post(`/admin/gold_credit/requests/${id}/approve`, data) + +export const rejectGoldCreditRequest = async (id: number, adminNote?: string) => + await useAxios.post(`/admin/gold_credit/requests/${id}/reject`, { admin_note: adminNote }) + +export const payGoldCreditInstallments = async (data: { + gold_credit_id: number + installment_ids: number[] + pay_method: 'gold' | 'rial' | 'mixed' + gold_amount?: number + rial_amount?: number +}) => await useAxios.post('/admin/gold_credit/installments/pay', data) diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index 12d0c5e..b1d192b 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -209,7 +209,7 @@ import SettingValueField from './SettingValueField.vue' import SettingCard from './SettingCard.vue' import { refreshAppModeMenu } from '@/router' import { useUserStore } from '@/store/userStore' -import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyAutomationEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, applyCustomerSpecialPricingFromRows, applyCustomerDirectSmsFromRows, applyCustomerLevelProfitEnabledFromRows, applySuperAdminAccessFlagsFromItems, invalidateSettingsBootstrap, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setAutomationEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings, setCustomerSpecialPricingEnabledFromSettings } from '@/utils/config' +import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyAutomationEnabledFromRows, applyProductFundsEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, applyPriceChannelAccessFromRows, applySupportChatMenuEnabledFromRows, applyGroupSmsFromRows, applyCustomerSpecialPricingFromRows, applyGoldCreditEnabledFromRows, applyCustomerDirectSmsFromRows, applyCustomerLevelProfitEnabledFromRows, applySuperAdminAccessFlagsFromItems, invalidateSettingsBootstrap, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setAutomationEnabledFromSettings, setProductFundsEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setPriceChannelAccessFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings, setGroupSmsEnabledFromSettings, setCustomerSpecialPricingEnabledFromSettings, setGoldCreditEnabledFromSettings } from '@/utils/config' import { EDITABLE_SETTING_GROUPS, SYSTEM_SETTING_GROUPS, @@ -498,13 +498,12 @@ async function pullSuperAdminAndApplyFlags(): Promise<{ ok: boolean message?: string count?: number + meta?: { channel?: string; origin?: string; domain?: string } + items?: Record }> { invalidateSettingsBootstrap() const res = await refreshSuperAdminSettings() - if (res?.items && typeof res.items === 'object') { - applySuperAdminAccessFlagsFromItems(res.items) - } - // get_settings کامل — فلگ منو و کارت‌ها با مقدار زنده هم‌تراز شوند + // get_settings کامل — فلگ‌های غیرسوپرادمین و قیمت try { const { getConfiguration } = await import('@/api/configuration') const all = await getConfiguration() @@ -513,23 +512,50 @@ async function pullSuperAdminAndApplyFlags(): Promise<{ applyPriceProviderFromRows(rows) applyPriceChannelAccessFromRows(rows) } catch { - /* فلگ از items می‌ماند */ + /* در ادامه از items همگام استفاده می‌شود */ + } + // items پاسخ refresh منبع حقیقت است — آخر اعمال شود تا get_settings کهنه/دامنهٔ اشتباه فلگ را خاموش نکند + if (res?.items && typeof res.items === 'object') { + applySuperAdminAccessFlagsFromItems(res.items) } return { ok: res?.ok !== false, message: res?.message, - count: res?.count + count: res?.count, + meta: res?.meta, + items: res?.items } } +function syncSuccessMessage(res: { + message?: string + count?: number + meta?: { channel?: string; origin?: string; domain?: string } + items?: Record +}): string { + const base = res?.message || `همگام شد (${res?.count ?? 0} کلید دسترسی از سوپرادمین)` + const domain = String(res?.meta?.domain || '').trim() + const csp = res?.items && typeof res.items === 'object' && !Array.isArray(res.items) + ? String((res.items as Record).customer_special_pricing ?? '') + : '' + const bits = [base] + if (domain) bits.push(`دامنه: ${domain}`) + if (csp !== '') bits.push(`قیمت ویژه: ${csp}`) + return bits.join(' — ') +} + async function refreshFromSuperAdmin() { if (refreshingSuperAdmin.value) return refreshingSuperAdmin.value = true try { const res = await pullSuperAdminAndApplyFlags() await initConfig() + // دوباره items را بعد از initConfig (که ممکن است get_settings ناقص بخواند) اعمال کن + if (res?.items && typeof res.items === 'object') { + applySuperAdminAccessFlagsFromItems(res.items) + } refreshMenuAfterStoreChange() - message.success(res?.message || `همگام شد (${res?.count ?? 0} کلید دسترسی از سوپرادمین)`) + message.success(syncSuccessMessage(res)) } catch (e: any) { console.error(e) const msg = e?.response?.data?.message || e?.message || 'همگام‌سازی از سوپرادمین ناموفق بود' @@ -546,8 +572,11 @@ async function onRefreshClick() { try { const res = await pullSuperAdminAndApplyFlags() await initConfig() + if (res?.items && typeof res.items === 'object') { + applySuperAdminAccessFlagsFromItems(res.items) + } refreshMenuAfterStoreChange() - message.success(res?.message || 'بروزرسانی شد') + message.success(syncSuccessMessage(res)) } catch (e: any) { console.error(e) const msg = e?.response?.data?.message || e?.message || 'همگام‌سازی از سوپرادمین ناموفق بود' @@ -580,6 +609,7 @@ async function initConfig() { applySupportChatMenuEnabledFromRows(baseData.value) applyGroupSmsFromRows(baseData.value) applyCustomerSpecialPricingFromRows(baseData.value) + applyGoldCreditEnabledFromRows(baseData.value) // صفحهٔ عادی price_provider ندارد (can_edit=0) — همیشه از get_settings کامل بخوان if ( !isSystemMode.value || @@ -587,7 +617,8 @@ async function initConfig() { !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 === 'customer_special_pricing') + !baseData.value.some((row) => row.key === 'customer_special_pricing') || + !baseData.value.some((row) => row.key === 'enable_gold_credit') ) { try { const { getConfiguration } = await import('@/api/configuration') @@ -598,6 +629,7 @@ async function initConfig() { applyPriceChannelAccessFromRows(rows) applyGroupSmsFromRows(rows) applyCustomerSpecialPricingFromRows(rows) + applyGoldCreditEnabledFromRows(rows) } catch { /* کش قبلی می‌ماند */ } @@ -677,6 +709,9 @@ function syncMenuFlagsFromSetting(key: string | undefined, value: string) { if (key === 'customer_special_pricing') { setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(value)) } + if (key === 'enable_gold_credit') { + setGoldCreditEnabledFromSettings(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 76bbe04..59b3fd5 100644 --- a/src/pages/menuPages/configuration/settingGroups.ts +++ b/src/pages/menuPages/configuration/settingGroups.ts @@ -45,6 +45,12 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [ icon: 'vuesax-linear:lock', match: (k) => /^(min_|minimum_|balance_report_exclude)/.test(k) }, + { + id: 'gold_credit', + title: 'اعتبار طلایی', + icon: 'vuesax-linear:medal-star', + match: (k) => k.startsWith('gold_credit_') + }, { id: 'price_source', title: 'منبع قیمت و تابلو', @@ -152,7 +158,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ icon: 'vuesax-linear:people', // فقط فلگ‌های مالک سوپرادمین — banner/tracking/نمایش محصول در «تنظیمات» عادی‌اند match: (k) => - /^(enable_automation|enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|customer_special_pricing|permision__enabled|show_to_users_chat)$/.test( + /^(enable_automation|enable_conditional_order|enable_product_funds|has_page_price_trigger|has_page_notification|has_page_gift_card|enable_customer_level_profit|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|customer_special_pricing|enable_gold_credit|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 4d652c7..adc96a4 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -194,6 +194,7 @@ export function isCodeManagedSettingKey(key?: string | null): boolean { export const SUPERADMIN_OWNED_SETTING_KEYS = new Set([ 'customer_direct_sms', 'customer_special_pricing', + 'enable_gold_credit', 'enable_automation', 'enable_conditional_order', 'enable_customer_level_profit', @@ -366,6 +367,22 @@ const KEY_TYPE_FALLBACK: Record = { sms_notif_support_admin: 'switch', group_sms: 'switch', customer_special_pricing: 'switch', + enable_gold_credit: 'switch', + gold_credit_overdue_sms_enabled: 'switch', + gold_credit_admin_sms_enabled: 'switch', + gold_credit_upcoming_sms_enabled: 'switch', + gold_credit_pay_gold_enabled: 'switch', + gold_credit_pay_rial_enabled: 'switch', + gold_credit_pay_mixed_enabled: 'switch', + gold_credit_penalty_amount: 'number', + gold_credit_grace_hours: 'number', + gold_credit_default_months: 'number', + gold_credit_min_balance_gram: 'number', + gold_credit_overdue_sms_text: 'longtext', + gold_credit_info_text: 'longtext', + gold_credit_request_success_text: 'longtext', + gold_credit_tiers: 'longtext', + gold_credit_penalty_type: 'select', montzar_closed: 'switch', montzar_open_buy: 'switch', montzar_open_sell: 'switch', @@ -443,6 +460,10 @@ const SELECT_LABELS_BY_KEY: Record> = { tahesab: 'ته‌حساب', zarin: 'زرین' }, + gold_credit_penalty_type: { + rial: 'ریالی', + gold: 'طلایی', + }, support_messenger: { bale: 'بله', telegram: 'تلگرام', @@ -462,6 +483,7 @@ const SELECT_LABELS_BY_KEY: Record> = { const SELECT_OPTIONS_BY_KEY: Record = { accounting_provider: 'none,pargar,tahesab,zarin', + gold_credit_penalty_type: 'rial,gold', price_provider: 'montzar,zaarbox,main_product', payping_gateway_name: 'sepal,', external_payment_gateway: 'zarinpall,zibal,payping,sepal', @@ -883,6 +905,8 @@ const KEY_HELP_FALLBACK: Record = { 'روشن: وقتی مشتری اولین پیام خوانده‌نشده را در چت پشتیبانی می‌فرستد و ادمین همان گفتگو را باز و visible ندارد، پیامک به ادمین‌های دارای «ارسال پیامک» می‌رود. خاموش: اعلان پیامک چت نمی‌رود.', group_sms: 'روشن: دکمه ارسال پیامک گروهی در پنل ادمین فعال است (ارسال ۱۰تایی). خاموش: ارسال پیامک گروهی غیرفعال است. فقط از تنظیمات پیشرفته.', + enable_gold_credit: + 'روشن: زیرمنوی اعتبار در مشتریان و صفحه اعتبار در اپ مشتری فعال می‌شود. خاموش: غیرفعال. فقط از سوپرادمین قابل تغییر است.', customer_special_pricing: 'روشن: زیرمنوی قیمت ویژه در مشتریان و اعمال سود خرید/فروش پکیج روی مظنه و سفارش برای کاربران انتخاب‌شده. خاموش: غیرفعال. فقط از دیپلوی قابل تغییر است.', profit_gold_api_enabled: @@ -1049,6 +1073,22 @@ export const KEY_LABEL_FALLBACK: Record = { open_register: 'ثبت‌نام باز', sms_panel: 'پنل پیامک (کیف پول)', customer_direct_sms: 'پیامک تکی به مشتری از لیست', + enable_gold_credit: 'اعتبار طلایی', + gold_credit_penalty_type: 'نوع جریمه اعتبار طلایی', + gold_credit_penalty_amount: 'مبلغ جریمه روزانه اعتبار طلایی', + gold_credit_grace_hours: 'مهلت پرداخت بعد از سررسید (ساعت)', + gold_credit_default_months: 'تعداد ماه پیش‌فرض اعتبار طلایی', + gold_credit_min_balance_gram: 'حداقل موجودی طلا برای درخواست (گرم)', + gold_credit_overdue_sms_enabled: 'SMS هشدار سررسید اعتبار طلایی', + gold_credit_admin_sms_enabled: 'پیامک ادمین اعتبار طلایی', + gold_credit_upcoming_sms_enabled: 'یادآوری سررسید قسط اعتبار', + gold_credit_pay_gold_enabled: 'پرداخت اقساط با طلا', + gold_credit_pay_rial_enabled: 'پرداخت اقساط با ریال', + gold_credit_pay_mixed_enabled: 'پرداخت اقساط ترکیبی', + gold_credit_overdue_sms_text: 'متن SMS سررسید اعتبار طلایی', + gold_credit_info_text: 'متن راهنمای اعتبار طلایی', + gold_credit_request_success_text: 'متن پس از ثبت درخواست اعتبار', + gold_credit_tiers: 'سطوح اعتبار طلایی (JSON)', customer_special_pricing: 'قیمت ویژه مشتری', charge_notif: 'پیامک شارژ ادمین', sms_notif_order_admin: 'پیامک خرید/فروش به ادمین', @@ -1157,6 +1197,7 @@ export const SETTING_LOCKED_KEYS = new Set([ 'referral_bonus_amount', 'group_sms', 'customer_special_pricing', + 'enable_gold_credit', 'customer_direct_sms', 'enable_automation', 'enable_conditional_order', diff --git a/src/pages/menuPages/customers/base/components/goldCreditDetailModal.vue b/src/pages/menuPages/customers/base/components/goldCreditDetailModal.vue new file mode 100644 index 0000000..362b542 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/goldCreditDetailModal.vue @@ -0,0 +1,199 @@ + + + diff --git a/src/pages/menuPages/customers/base/components/goldCreditInstallmentsTable.vue b/src/pages/menuPages/customers/base/components/goldCreditInstallmentsTable.vue new file mode 100644 index 0000000..51b0105 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/goldCreditInstallmentsTable.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/pages/menuPages/customers/base/components/goldCreditModal.vue b/src/pages/menuPages/customers/base/components/goldCreditModal.vue new file mode 100644 index 0000000..c21e0ef --- /dev/null +++ b/src/pages/menuPages/customers/base/components/goldCreditModal.vue @@ -0,0 +1,335 @@ + + + diff --git a/src/pages/menuPages/customers/base/components/goldCreditSettingsModal.vue b/src/pages/menuPages/customers/base/components/goldCreditSettingsModal.vue new file mode 100644 index 0000000..751a5d5 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/goldCreditSettingsModal.vue @@ -0,0 +1,164 @@ + + + diff --git a/src/pages/menuPages/customers/base/hooks/goldCreditColumns.tsx b/src/pages/menuPages/customers/base/hooks/goldCreditColumns.tsx new file mode 100644 index 0000000..fd91679 --- /dev/null +++ b/src/pages/menuPages/customers/base/hooks/goldCreditColumns.tsx @@ -0,0 +1,185 @@ +import type { BaseTableColumn } from '@core/type/baseTable' +import CustomTag from '@/components/CustomTag.vue' +import { toJalaliNoTime } from '@core/utils/useDataTime' + +const statusStyles: Record = { + pending: { text: 'در انتظار', textColor: '#c2410c', bgColor: '#ffedd5' }, + paid: { text: 'پرداخت شده', textColor: '#15803d', bgColor: '#dcfce7' }, + prepaid: { text: 'پیش‌پرداخت', textColor: '#1d4ed8', bgColor: '#dbeafe' }, + overdue: { text: 'معوق', textColor: '#b91c1c', bgColor: '#fee2e2' }, + partial: { text: 'ناقص', textColor: '#a16207', bgColor: '#fef9c3' }, +} + +export const cols: BaseTableColumn[] = [ + { + title: 'ردیف', + dataIndex: 'installment_number', + key: 'installment_number', + filterable: false, + sortable: false, + }, + { + title: 'مبلغ (سوت)', + dataIndex: 'amount_soot', + key: 'amount_soot', + type: 'Number', + customRender: (record: any) => `${record.record.amount_soot?.toLocaleString('fa-IR')} سوت`, + }, + { + title: 'تاریخ سررسید', + dataIndex: 'due_date', + key: 'due_date', + customRender: (record: any) => toJalaliNoTime(record.record.due_date), + }, + { + title: 'وضعیت', + dataIndex: 'status', + key: 'status', + customRender: (record: any) => { + const s = statusStyles[record.record.status] ?? { + text: record.record.status, + textColor: '#374151', + bgColor: '#f3f4f6', + } + return ( + + {s.text} + + ) + }, + }, + { + title: 'تاریخ پرداخت', + dataIndex: 'paid_at', + key: 'paid_at', + customRender: (record: any) => + record.record.paid_at ? toJalaliNoTime(record.record.paid_at) : '-', + }, + { + title: 'جریمه', + dataIndex: 'penalty_days', + key: 'penalty_days', + customRender: (record: any) => { + const r = record.record + const days = r.penalty_days || 0 + if (!days && !(r.penalty_rial > 0) && !(r.penalty_gold > 0)) return '-' + const parts: string[] = [] + if (days) parts.push(`${days} روز`) + if (r.penalty_rial > 0) parts.push(`${Number(r.penalty_rial).toLocaleString('fa-IR')} ریال`) + if (r.penalty_gold > 0) parts.push(`${Number(r.penalty_gold).toFixed(4)} گرم`) + return parts.join(' / ') + }, + }, +] + +export const creditCols: BaseTableColumn[] = [ + { + title: 'ردیف', + dataIndex: 'id', + key: 'id', + filterable: false, + sortable: false, + customRender: (record: any) => record.index + 1, + }, + { + title: 'نام مشتری', + dataIndex: 'customer.name', + key: 'customer.name', + type: 'Text', + }, + { + title: 'موبایل', + dataIndex: 'customer.mobile', + key: 'customer.mobile', + type: 'Text', + }, + { + title: 'مقدار (گرم)', + dataIndex: 'gold_amount', + key: 'gold_amount', + type: 'Number', + }, + { + title: 'تعداد ماه', + dataIndex: 'months', + key: 'months', + type: 'Number', + }, + { + title: 'وضعیت', + dataIndex: 'status', + key: 'status', + customRender: (record: any) => { + const map: Record = { + active: 'فعال', + completed: 'تسویه شده', + cancelled: 'لغو شده', + pending: 'در انتظار', + } + const styles: Record = { + active: { textColor: '#15803d', bgColor: '#dcfce7' }, + completed: { textColor: '#1d4ed8', bgColor: '#dbeafe' }, + cancelled: { textColor: '#b91c1c', bgColor: '#fee2e2' }, + pending: { textColor: '#c2410c', bgColor: '#ffedd5' }, + } + const st = record.record.status + const style = styles[st] ?? { textColor: '#374151', bgColor: '#f3f4f6' } + return ( + + {map[st] ?? st} + + ) + }, + }, +] + +export const requestCols: BaseTableColumn[] = [ + { + title: 'ردیف', + dataIndex: 'id', + key: 'id', + filterable: false, + sortable: false, + customRender: (record: any) => record.index + 1, + }, + { + title: 'نام مشتری', + dataIndex: 'customer.name', + key: 'customer.name', + type: 'Text', + }, + { + title: 'موبایل', + dataIndex: 'customer.mobile', + key: 'customer.mobile', + type: 'Text', + }, + { + title: 'مقدار درخواست (گرم)', + dataIndex: 'requested_gold', + key: 'requested_gold', + type: 'Number', + }, + { + title: 'وضعیت', + dataIndex: 'status', + key: 'status', + customRender: (record: any) => { + const map: Record = { + pending: { text: 'در انتظار', textColor: '#c2410c', bgColor: '#ffedd5' }, + approved: { text: 'تأیید شده', textColor: '#15803d', bgColor: '#dcfce7' }, + rejected: { text: 'رد شده', textColor: '#b91c1c', bgColor: '#fee2e2' }, + } + const s = map[record.record.status] ?? { + text: record.record.status, + textColor: '#374151', + bgColor: '#f3f4f6', + } + return ( + + {s.text} + + ) + }, + }, +] diff --git a/src/pages/menuPages/customers/goldCredit/active/index.vue b/src/pages/menuPages/customers/goldCredit/active/index.vue new file mode 100644 index 0000000..d19bf24 --- /dev/null +++ b/src/pages/menuPages/customers/goldCredit/active/index.vue @@ -0,0 +1,175 @@ + + + diff --git a/src/pages/menuPages/customers/goldCredit/index.vue b/src/pages/menuPages/customers/goldCredit/index.vue new file mode 100644 index 0000000..f7c40f0 --- /dev/null +++ b/src/pages/menuPages/customers/goldCredit/index.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/pages/menuPages/customers/goldCredit/requests/index.vue b/src/pages/menuPages/customers/goldCredit/requests/index.vue new file mode 100644 index 0000000..3dd69bf --- /dev/null +++ b/src/pages/menuPages/customers/goldCredit/requests/index.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/pages/menuPages/customers/index.vue b/src/pages/menuPages/customers/index.vue index b730dfd..5b5657c 100644 --- a/src/pages/menuPages/customers/index.vue +++ b/src/pages/menuPages/customers/index.vue @@ -21,7 +21,7 @@ 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' +import { customerSpecialPricingEnabledSetting, goldCreditEnabledSetting, initWalletSettingsFromApi } from '@/utils/config' initWalletSettingsFromApi() @@ -33,10 +33,11 @@ const menu = computed(() => { meta?: { order?: number } }> return [...children] - .filter( - (child) => - child.name !== 'customerMain_specialPrice' || customerSpecialPricingEnabledSetting.value - ) + .filter((child) => { + if (child.name === 'customerMain_specialPrice') return customerSpecialPricingEnabledSetting.value + if (child.name === 'customerMain_goldCredit') return goldCreditEnabledSetting.value + return true + }) .sort((a, b) => (a.meta?.order ?? 99) - (b.meta?.order ?? 99)) }) diff --git a/src/router/routes/customers.ts b/src/router/routes/customers.ts index 4e91366..828c119 100644 --- a/src/router/routes/customers.ts +++ b/src/router/routes/customers.ts @@ -80,6 +80,16 @@ const Customers: RouteRecordRaw = { title: 'قیمت ویژه', order: 4 } + }, + { + name: 'customerMain_goldCredit', + path: 'gold-credit', + component: () => import('@/pages/menuPages/customers/goldCredit/index.vue'), + meta: { + icon: 'vuesax-linear:medal-star', + title: 'اعتبار', + order: 5 + } } ] } diff --git a/src/utils/config.ts b/src/utils/config.ts index e963bc3..45f393b 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -64,6 +64,9 @@ export const groupSmsEnabledSetting = ref(false) /** reactive — زیرمنوی قیمت ویژه مشتری */ export const customerSpecialPricingEnabledSetting = ref(false) +/** reactive — زیرمنوی اعتبار طلایی در مشتریان */ +export const goldCreditEnabledSetting = ref(false) + /** reactive — منو و زنگوله اعلان داخل اپ */ export const appNotificationEnabledSetting = ref(true) @@ -87,8 +90,11 @@ let pargarEnabledFromSettings: boolean | null = null let customerDirectSmsFromSettings: boolean | null = null let accountingProviderFromSettings: AccountingProvider | null = null -function isSwitchOn(value: string | undefined | null): boolean { - const v = (value ?? '').trim().toLowerCase() +function isSwitchOn(value: string | number | boolean | undefined | null): boolean { + if (value === true || value === 1) return true + const v = String(value ?? '') + .trim() + .toLowerCase() return v === 'true' || v === '1' || v === 'yes' || v === 'activate' } @@ -166,6 +172,10 @@ export function setCustomerSpecialPricingEnabledFromSettings(value: boolean): vo customerSpecialPricingEnabledSetting.value = value } +export function setGoldCreditEnabledFromSettings(value: boolean): void { + goldCreditEnabledSetting.value = value +} + export function setConditionalOrderEnabledFromSettings(value: boolean): void { conditionalOrderEnabledSetting.value = value } @@ -283,13 +293,20 @@ export function applyCustomerSpecialPricingFromRows( if (row) setCustomerSpecialPricingEnabledFromSettings(isSwitchOn(row.value)) } +export function applyGoldCreditEnabledFromRows( + rows: Array<{ key?: string; value?: string | null }> +) { + const row = rows.find((item) => item.key === 'enable_gold_credit') + if (row) setGoldCreditEnabledFromSettings(isSwitchOn(row.value)) +} + /** بعد از همگام‌سازی سوپرادمین — bootstrap بعدی دوباره از API می‌خواند */ export function invalidateSettingsBootstrap(): void { settingsBootstrapped = false } /** - * اعمال مستقیم ۱۴ دسترسی سوپرادمین روی فلگ‌های منو/UI + * اعمال مستقیم ۱۵ دسترسی سوپرادمین روی فلگ‌های منو/UI * (پاسخ refresh-superadmin.items یا ردیف‌های get_settings). */ export function applySuperAdminAccessFlagsFromItems( @@ -301,6 +318,7 @@ export function applySuperAdminAccessFlagsFromItems( applyCustomerDirectSmsFromRows(rows) applyGroupSmsFromRows(rows) applyCustomerSpecialPricingFromRows(rows) + applyGoldCreditEnabledFromRows(rows) applyConditionalOrderEnabledFromRows(rows) applyAutomationEnabledFromRows(rows) applyProductFundsEnabledFromRows(rows) @@ -386,6 +404,7 @@ async function bootstrapSettingsFromApi(): Promise { applyCustomerDirectSmsFromRows(list) applyGroupSmsFromRows(list) applyCustomerSpecialPricingFromRows(list) + applyGoldCreditEnabledFromRows(list) applyConditionalOrderEnabledFromRows(list) applyAutomationEnabledFromRows(list) applyProductFundsEnabledFromRows(list)