diff --git a/components.d.ts b/components.d.ts index 3d0dca9..50acf2e 100644 --- a/components.d.ts +++ b/components.d.ts @@ -95,5 +95,6 @@ declare module '@vue/runtime-core' { ThemeMenuSection: typeof import('./src/core/components/ThemeMenuSection.vue')['default'] ThemeToggle: typeof import('./src/core/components/ThemeToggle.vue')['default'] UploadPic: typeof import('./src/components/UploadPic.vue')['default'] + ZarinRetryModal: typeof import('./src/components/ZarinRetryModal.vue')['default'] } } diff --git a/src/api/appNotifications.ts b/src/api/appNotifications.ts new file mode 100644 index 0000000..d5ec3f0 --- /dev/null +++ b/src/api/appNotifications.ts @@ -0,0 +1,18 @@ +import type { TableInput } from '@core/type/baseTable' +import useAxios from '@core/utils/useAxios' +import type { AppNotification } from '@/model/appNotifications' + +export const getAppNotificationsTable = async (params: TableInput) => + await useAxios.post>('/admin/app_notifications/table', params) + +export const createAppNotification = async (payload: Partial) => + await useAxios.post('/admin/app_notifications', payload) + +export const updateAppNotification = async (id: number, payload: Partial) => + await useAxios.put(`/admin/app_notifications/${id}`, payload) + +export const toggleAppNotificationActive = async (id: number) => + await useAxios.put<{ is_active: boolean }>(`/admin/app_notifications/${id}/toggle`) + +export const deleteAppNotification = async (id: number) => + await useAxios.delete(`/admin/app_notifications/${id}`) diff --git a/src/api/zarin.ts b/src/api/zarin.ts new file mode 100644 index 0000000..4e4fbee --- /dev/null +++ b/src/api/zarin.ts @@ -0,0 +1,93 @@ +import useAxios from '@core/utils/useAxios' +import type { TableInput } from '@core/type/baseTable' + +export interface ZarinRecord { + id: number + customer_id?: number + amount?: number + desc?: string + type?: string + sync_status?: 'pending' | 'success' | 'failed' + sync_log?: string | null + zarin_id?: string | null + data?: string | null + res?: string | null + created_at?: string + customer?: { + id?: number + name?: string + mobile?: string + moshtari_code?: string | number | null + } +} + +export const getZarinTable = async (params: TableInput) => + await useAxios.post('/admin/zarin/table', params) + +export const getZarinDetail = async (id: number) => + await useAxios.get(`/admin/zarin/${id}`) + +export const retryZarinSanad = async (id: number) => + await useAxios.post<{ ok: boolean; message: string; zarin?: ZarinRecord }>(`/admin/zarin/${id}/retry`) + +export const registerCustomerInZarin = async (customerId: number) => + await useAxios.post<{ ok: boolean; message: string; moshtari_code?: string | number }>( + `/admin/customer/${customerId}/zarin/register` + ) + +export interface PanelOnlyCustomer { + id: number + name: string + mobile: string + created_at: string +} + +export interface ZarinOnlyCustomer { + code: number + name: string + tel: string + gid: number | null + group: string +} + +export interface CustomerSyncStatus { + panel_total: number + panel_registered: number + panel_only: number + zarin_total: number + zarin_only_count: number + panel_only_list: { + data: PanelOnlyCustomer[] + current_page: number + last_page: number + total: number + } + zarin_only_list: { + data: ZarinOnlyCustomer[] + current_page: number + last_page: number + total: number + } +} + +export const getZarinCustomerSyncStatus = async (params?: { + search?: string + page?: number + zarin_page?: number + include_zarin?: number | boolean +}) => await useAxios.get('/admin/zarin/customer-sync/status', { params }) + +export interface SyncPanelResult { + ok: boolean + success: number + failed: number + errors: { name: string; mobile: string; error: string }[] +} + +export const syncPanelToZarin = async () => + await useAxios.post('/admin/zarin/customer-sync/panel-to-zarin') + +export const importFromZarin = async (customers: ZarinOnlyCustomer[]) => + await useAxios.post('/admin/zarin/customer-sync/import-from-zarin', { + customers, + }) diff --git a/src/components/ZarinRetryModal.vue b/src/components/ZarinRetryModal.vue new file mode 100644 index 0000000..dfa4dde --- /dev/null +++ b/src/components/ZarinRetryModal.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/model/appNotifications.ts b/src/model/appNotifications.ts new file mode 100644 index 0000000..18bf27d --- /dev/null +++ b/src/model/appNotifications.ts @@ -0,0 +1,17 @@ +export interface AppNotification { + id?: number + title: string + body: string + severity: 'info' | 'success' | 'warning' | 'danger' + is_active: boolean + created_by?: number | null + created_at?: string + updated_at?: string +} + +export const APP_NOTIFICATION_SEVERITIES = [ + { value: 'info', label: 'اطلاعات (info)' }, + { value: 'success', label: 'موفق (success)' }, + { value: 'warning', label: 'هشدار (warning)' }, + { value: 'danger', label: 'مهم / خطر (danger)' }, +] as const diff --git a/src/pages/menuPages/accounting/components/ZarinCustomerSync.vue b/src/pages/menuPages/accounting/components/ZarinCustomerSync.vue new file mode 100644 index 0000000..1c8cb57 --- /dev/null +++ b/src/pages/menuPages/accounting/components/ZarinCustomerSync.vue @@ -0,0 +1,863 @@ + + + + + diff --git a/src/pages/menuPages/accounting/components/ZarinMobileCardList.vue b/src/pages/menuPages/accounting/components/ZarinMobileCardList.vue new file mode 100644 index 0000000..e0ef0de --- /dev/null +++ b/src/pages/menuPages/accounting/components/ZarinMobileCardList.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/pages/menuPages/accounting/components/ZarinMobileDetailSheet.vue b/src/pages/menuPages/accounting/components/ZarinMobileDetailSheet.vue new file mode 100644 index 0000000..68f8ba8 --- /dev/null +++ b/src/pages/menuPages/accounting/components/ZarinMobileDetailSheet.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/pages/menuPages/accounting/components/ZarinMobileListItem.vue b/src/pages/menuPages/accounting/components/ZarinMobileListItem.vue new file mode 100644 index 0000000..6133a96 --- /dev/null +++ b/src/pages/menuPages/accounting/components/ZarinMobileListItem.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/pages/menuPages/accounting/components/zarinTable.vue b/src/pages/menuPages/accounting/components/zarinTable.vue new file mode 100644 index 0000000..7542f7d --- /dev/null +++ b/src/pages/menuPages/accounting/components/zarinTable.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/pages/menuPages/accounting/index.vue b/src/pages/menuPages/accounting/index.vue index 22465f4..7d411ea 100644 --- a/src/pages/menuPages/accounting/index.vue +++ b/src/pages/menuPages/accounting/index.vue @@ -3,18 +3,22 @@
+ + + +
@@ -147,6 +151,8 @@ import { ref } from 'vue' import tbl from './components/table.vue' import tahesabTbl from './components/tahesabTable.vue' import customerSync from './components/TahesabCustomerSync.vue' +import zarinTbl from './components/zarinTable.vue' +import zarinCustomerSync from './components/ZarinCustomerSync.vue' import pargarTbl from './components/pargarTable.vue' import pargarCustomerSync from './components/PargarCustomerSync.vue' import type { FormInstance } from 'ant-design-vue' @@ -166,13 +172,14 @@ import type { last } from '@/model/price' import { createOrder } from '@/api/order' import { getCashBalance, getGoldBalance } from '@/components/mobile/utils/wallet' import { metalLabel } from '@/utils/adminMetalLabels' -import { initAccountingSettingsFromApi, tahesabEnabledSetting, pargarEnabledSetting } from '@/utils/config' +import { initAccountingSettingsFromApi, tahesabEnabledSetting, zarinEnabledSetting, pargarEnabledSetting } from '@/utils/config' //loading const loading = ref(false) const submitError = ref('') -const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'pargar' | 'pargar-customer-sync'>('reports') +const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'zarin' | 'zarin-customer-sync' | 'pargar' | 'pargar-customer-sync'>('reports') const tahesabEnabled = tahesabEnabledSetting +const zarinEnabled = zarinEnabledSetting const pargarEnabled = pargarEnabledSetting //define store diff --git a/src/pages/menuPages/accounting/utils/zarinStatus.ts b/src/pages/menuPages/accounting/utils/zarinStatus.ts new file mode 100644 index 0000000..cabeff7 --- /dev/null +++ b/src/pages/menuPages/accounting/utils/zarinStatus.ts @@ -0,0 +1,12 @@ +export function zarinStatusLabel(status?: string, zarinId?: string | null): string { + if (zarinId) return 'موفق' + if (status === 'failed') return 'ناموفق' + if (status === 'success') return 'موفق' + // pending = ردیف محلی ساخته شده ولی هنوز در نرم‌افزار زرین ثبت نشده (نه «در صف») + return 'ارسال نشده' +} + +export function canRetryZarin(record: { zarin_id?: string | null; sync_status?: string }): boolean { + return !record.zarin_id || record.sync_status === 'failed' +} + diff --git a/src/pages/menuPages/appNotifications/components/AppNotificationEditorFormBody.vue b/src/pages/menuPages/appNotifications/components/AppNotificationEditorFormBody.vue new file mode 100644 index 0000000..3d8a587 --- /dev/null +++ b/src/pages/menuPages/appNotifications/components/AppNotificationEditorFormBody.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/pages/menuPages/appNotifications/components/table.vue b/src/pages/menuPages/appNotifications/components/table.vue new file mode 100644 index 0000000..0ee8748 --- /dev/null +++ b/src/pages/menuPages/appNotifications/components/table.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/pages/menuPages/appNotifications/hooks/appNotificationsColumns.tsx b/src/pages/menuPages/appNotifications/hooks/appNotificationsColumns.tsx new file mode 100644 index 0000000..1ef9ff4 --- /dev/null +++ b/src/pages/menuPages/appNotifications/hooks/appNotificationsColumns.tsx @@ -0,0 +1,63 @@ +import type { BaseTableColumn } from '@core/type/baseTable' +import { toJalali } from '@core/utils/useDataTime' +import CustomTag from '@/components/CustomTag.vue' + +const severityLabel: Record = { + info: 'اطلاعات', + success: 'موفق', + warning: 'هشدار', + danger: 'مهم', +} + +const severityColor: Record = { + info: 'blue', + success: 'green', + warning: 'orange', + danger: 'red', +} + +export const cols: BaseTableColumn[] = [ + { + title: 'ردیف', + dataIndex: 'id', + key: 'id', + customRender: (record: any) => record.index + 1, + }, + { + title: 'عنوان', + dataIndex: 'title', + key: 'title', + type: 'Text', + }, + { + title: 'اهمیت', + dataIndex: 'severity', + key: 'severity', + customRender: ({ record }) => ( + + {severityLabel[record.severity] || record.severity} + + ), + }, + { + title: 'وضعیت', + dataIndex: 'is_active', + key: 'is_active', + customRender: ({ record }) => ( + + {record.is_active ? 'فعال' : 'غیرفعال'} + + ), + }, + { + title: 'تاریخ', + dataIndex: 'created_at', + key: 'created_at', + customRender: ({ record }) => (record.created_at ? toJalali(record.created_at) : '-'), + }, + { + title: 'عملیات', + dataIndex: 'action', + key: 'action', + }, +] diff --git a/src/pages/menuPages/appNotifications/index.vue b/src/pages/menuPages/appNotifications/index.vue new file mode 100644 index 0000000..fa04225 --- /dev/null +++ b/src/pages/menuPages/appNotifications/index.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index 9f8cf55..9a2f7a5 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -174,7 +174,7 @@ import SettingValueField from './SettingValueField.vue' import SettingCard from './SettingCard.vue' import { refreshAppModeMenu } from '@/router' import { useUserStore } from '@/store/userStore' -import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyPriceProviderFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setPriceProviderFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config' +import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, applyConditionalOrderEnabledFromRows, applyPriceTriggerEnabledFromRows, applyAppNotificationEnabledFromRows, applyPriceProviderFromRows, setBannerEnabledFromSettings, setConditionalOrderEnabledFromSettings, setPriceTriggerEnabledFromSettings, setAppNotificationEnabledFromSettings, setPriceProviderFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config' import { EDITABLE_SETTING_GROUPS, SYSTEM_SETTING_GROUPS, @@ -423,6 +423,8 @@ async function initConfig() { applyBannerEnabledFromRows(baseData.value) applyTrackingSystemsEnabledFromRows(baseData.value) applyConditionalOrderEnabledFromRows(baseData.value) + applyPriceTriggerEnabledFromRows(baseData.value) + applyAppNotificationEnabledFromRows(baseData.value) applyPriceProviderFromRows(baseData.value) // در صفحهٔ عادی price_provider (can_edit=0) نیست — از کل تنظیمات بخوان if (!baseData.value.some((row) => row.key === 'price_provider')) { @@ -559,6 +561,12 @@ async function saveSettingRow(row: Const) { if (row.key === 'enable_conditional_order') { setConditionalOrderEnabledFromSettings(isSwitchOn(value)) } + if (row.key === 'has_page_price_trigger') { + setPriceTriggerEnabledFromSettings(isSwitchOn(value)) + } + if (row.key === 'has_page_notification') { + setAppNotificationEnabledFromSettings(isSwitchOn(value)) + } if (row.key === 'price_provider') { setPriceProviderFromSettings(value) } diff --git a/src/pages/menuPages/configuration/settingGroups.ts b/src/pages/menuPages/configuration/settingGroups.ts index f791806..e0808c0 100644 --- a/src/pages/menuPages/configuration/settingGroups.ts +++ b/src/pages/menuPages/configuration/settingGroups.ts @@ -126,7 +126,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ title: 'دسترسی مشتری به قابلیت‌ها', icon: 'vuesax-linear:people', match: (k) => - /^(enable_conditional_order|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled)$/.test( + /^(enable_conditional_order|has_page_price_trigger|has_page_notification|banner_enabled|jibit_enabled|customer_direct_sms|has_page_earn_money|group_sms|tracking_systems_enabled|permision__enabled)$/.test( k ) }, @@ -172,15 +172,28 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ match: (k) => k.startsWith('payping_') }, { - id: 'accounting', - title: 'حسابداری', + id: 'accounting_provider', + title: 'انتخاب سیستم حسابداری', icon: 'vuesax-linear:receipt-2', - match: (k) => - k === 'accounting_provider' - || k.startsWith('tahesab_') - || k.startsWith('pargar_') - || k === 'send_to_tahesab' - || k === 'send_to_pargar' + match: (k) => k === 'accounting_provider' + }, + { + id: 'accounting_zarin', + title: 'حسابداری زرین', + icon: 'vuesax-linear:card-pos', + match: (k) => k.startsWith('zarin_') + }, + { + id: 'accounting_tahesab', + title: 'حسابداری ته‌حساب', + icon: 'vuesax-linear:calculator', + match: (k) => k.startsWith('tahesab_') || k === 'send_to_tahesab' + }, + { + id: 'accounting_pargar', + title: 'حسابداری پرگار', + icon: 'vuesax-linear:document-text', + match: (k) => k.startsWith('pargar_') || k === 'send_to_pargar' }, { id: 'price_channel', diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts index e9740da..9d3e99a 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -243,6 +243,8 @@ const KEY_TYPE_FALLBACK: Record = { fee_free: 'number', fee_conditional_order: 'price', enable_conditional_order: 'switch', + has_page_price_trigger: 'switch', + has_page_notification: 'switch', min_withdraw: 'price', min_deposit: 'price', min_first_buy: 'price', @@ -305,6 +307,12 @@ const KEY_TYPE_FALLBACK: Record = { tahesab_token: 'text', tahesab_db: 'text', tahesab_bank_account: 'text', + zarin_api: 'text', + zarin_token: 'text', + zarin_noe_kar: 'number', + zarin_gorohe_kar: 'number', + zarin_name_g: 'text', + zarin_uid_prefix: 'text', pargar_username: 'text', pargar_password: 'text', pargar_api_token: 'text', @@ -342,7 +350,8 @@ const SELECT_LABELS_BY_KEY: Record> = { accounting_provider: { none: 'هیچکدام', pargar: 'پرگار', - tahesab: 'ته‌حساب' + tahesab: 'ته‌حساب', + zarin: 'زرین' }, support_messenger: { bale: 'بله', @@ -362,7 +371,7 @@ const SELECT_LABELS_BY_KEY: Record> = { } const SELECT_OPTIONS_BY_KEY: Record = { - accounting_provider: 'none,pargar,tahesab', + accounting_provider: 'none,pargar,tahesab,zarin', price_provider: 'montzar,zaarbox,main_product', payping_gateway_name: 'sepal,', unit: 'گرم,کیلوگرم', @@ -567,6 +576,10 @@ const KEY_HELP_FALLBACK: Record = { 'مبلغ ریالی که با هر بار ثبت تریگر سفارش شرطی از کیف پول ریالی مشتری کم می‌شود. صفر = رایگان.', enable_conditional_order: 'روشن: قابلیت سفارش شرطی سراسری فعال است. خاموش: منو و ارزیابی اجرا نمی‌شود. فقط از صفحه دیپلوی قابل تغییر است.', + has_page_price_trigger: + 'روشن: منوی هشدار قیمت در اپ مشتری نمایش داده می‌شود. خاموش: منو و صفحه مخفی است. فقط از صفحه دیپلوی قابل تغییر است.', + has_page_notification: + 'روشن: زنگوله اعلان در اپ مشتری و منوی ثبت اعلان در ادمین فعال است. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.', min_withdraw: 'حداقل مبلغ درخواست برداشت (ریال). درخواست‌های کمتر پذیرفته نمی‌شوند.', min_deposit: @@ -724,7 +737,7 @@ const KEY_HELP_FALLBACK: Record = { send_to_tahesab: 'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.', accounting_provider: - 'سیستم حسابداری متصل: پرگار (API ورودی)، ته‌حساب (ارسال خودکار)، یا هیچکدام.', + 'سیستم حسابداری متصل: پرگار، ته‌حساب، زرین، یا هیچکدام.', send_to_pargar: 'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.', pargar_username: @@ -747,6 +760,18 @@ const KEY_HELP_FALLBACK: Record = { 'نام پایگاه داده در ته‌حساب که سند به آن ارسال می‌شود.', tahesab_bank_account: 'نام یا کد حساب بانکی در سند واریز/برداشت ته‌حساب.', + zarin_api: + 'آدرس پایه وب‌سرویس زرین (بدون /ZarrinService/accounting).', + zarin_token: + 'توکن مکمل API زرین که در نرم‌افزار زرین تعریف می‌شود.', + zarin_noe_kar: + 'کد noeKar هنگام ساخت کار ساخته در زرین.', + zarin_gorohe_kar: + 'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. اگر چند پنل یک زرین دارند، برای هر پنل گروه کار متفاوت بگذارید.', + zarin_name_g: + 'نام گروه حساب مشتریان در زرین (nameG). اگر چند پنل یک توکن دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند.', + zarin_uid_prefix: + 'پیشوند عددی uid برای جلوگیری از تداخل بین چند پنل روی یک زرین. فقط عدد (مثلاً ۱۱).', montify_name: 'نام فروشگاه که در درگاه پرداخت و رسید بانکی به مشتری نشان داده می‌شود.', app_url: @@ -799,6 +824,8 @@ export const KEY_LABEL_FALLBACK: Record = { fee_free: 'معافیت کارمزد سفارش اول', fee_conditional_order: 'کارمزد سفارش شرطی', enable_conditional_order: 'سفارش شرطی', + has_page_price_trigger: 'هشدار قیمت', + has_page_notification: 'اعلان داخل اپ', jibit_enabled: 'احراز هویت جیبیت', jibit_verification: 'تأیید کارت بانکی با جیبیت', register_jibit_verify: 'تأیید کدملی/موبایل در ثبت‌نام', @@ -842,6 +869,12 @@ export const KEY_LABEL_FALLBACK: Record = { accounting_provider: 'سیستم حسابداری', send_to_tahesab: 'ارسال خودکار به ته‌حساب (قدیمی)', send_to_pargar: 'ارسال خودکار به پرگار (قدیمی)', + zarin_api: 'آدرس API زرین', + zarin_token: 'توکن زرین', + zarin_noe_kar: 'نوع کار پیش‌فرض زرین', + zarin_gorohe_kar: 'گروه کار پیش‌فرض زرین', + zarin_name_g: 'نام گروه حساب مشتریان زرین', + zarin_uid_prefix: 'پیشوند عددی uid زرین', support_messengers: 'پیام‌رسان‌های پشتیبانی', tracking_systems: 'سایت‌های ری‌گیری', tracking_systems_enabled: 'نمایش سایت‌های ری‌گیری', @@ -881,6 +914,8 @@ export const SETTING_LOCKED_KEYS = new Set([ 'group_sms', 'customer_direct_sms', 'enable_conditional_order', + 'has_page_price_trigger', + 'has_page_notification', 'jibit_enabled', 'has_page_earn_money', 'is_store', @@ -901,6 +936,7 @@ export const SETTING_LOCKED_KEYS = new Set([ 'send_to_tahesab', 'send_to_pargar', 'accounting_provider', 'service_charge_percentage', 'sse_path', 'tahesab_bank_account', 'tahesab_db', 'tahesab_host', 'tahesab_port', 'tahesab_token', + 'zarin_api', 'zarin_token', 'zarin_noe_kar', 'zarin_gorohe_kar', 'zarin_name_g', 'zarin_uid_prefix', 'tracking_systems', 'tracking_systems_enabled', 'pargar_api_token', 'pargar_password', 'pargar_username', 'pargar_item_id_rial', 'pargar_item_id_gold', diff --git a/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue b/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue index a04cf63..48d620e 100644 --- a/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue +++ b/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue @@ -62,7 +62,7 @@ const visibleProxy = computed({ function currentCodeOf(customer: Customer | null): string { if (!customer) return '' if (props.provider === 'pargar') return displayAccountingCode(customer.pargar_account_id) - if (props.provider === 'tahesab') return displayAccountingCode(customer.moshtari_code) + if (props.provider === 'tahesab' || props.provider === 'zarin') return displayAccountingCode(customer.moshtari_code) return '' } @@ -81,7 +81,7 @@ function close() { async function submit() { if (!props.customer?.id) return - if (props.provider !== 'pargar' && props.provider !== 'tahesab') { + if (props.provider !== 'pargar' && props.provider !== 'tahesab' && props.provider !== 'zarin') { message.error('حسابداری فعال نیست.') return } diff --git a/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue b/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue index 5ccef72..7df30ce 100644 --- a/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue +++ b/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue @@ -149,14 +149,14 @@ const customerDirectSmsEnabled = customerDirectSmsEnabledSetting const accountingCodeLabel = computed(() => { const provider = accountingProviderSetting.value - if (provider === 'pargar' || provider === 'tahesab') return 'کد حسابداری' + if (provider === 'pargar' || provider === 'tahesab' || provider === 'zarin') return 'کد حسابداری' return null }) function accountingCodeOf(row: Customer) { const provider = accountingProviderSetting.value if (provider === 'pargar') return displayAccountingCode(row.pargar_account_id) - if (provider === 'tahesab') return displayAccountingCode(row.moshtari_code) + if (provider === 'tahesab' || provider === 'zarin') return displayAccountingCode(row.moshtari_code) return '-' } diff --git a/src/pages/menuPages/customers/base/components/table.vue b/src/pages/menuPages/customers/base/components/table.vue index b301a4b..2fc5034 100644 --- a/src/pages/menuPages/customers/base/components/table.vue +++ b/src/pages/menuPages/customers/base/components/table.vue @@ -73,6 +73,15 @@ + + + + + () const tahesabRegisterVisible = ref(false) const tahesabCustomer = ref() const tahesabEnabled = tahesabEnabledSetting +const zarinRegisterVisible = ref(false) +const zarinCustomer = ref() +const zarinEnabled = zarinEnabledSetting const pargarRegisterVisible = ref(false) const pargarCustomer = ref() const pargarEnabled = pargarEnabledSetting @@ -254,13 +276,13 @@ const accountingCodeVisible = ref(false) const accountingCodeCustomer = ref(null) const accountingEnabled = computed(() => { const provider = accountingProviderSetting.value - return provider === 'pargar' || provider === 'tahesab' + return provider === 'pargar' || provider === 'tahesab' || provider === 'zarin' }) function accountingCodeOf(record: Customer) { const provider = accountingProviderSetting.value if (provider === 'pargar') return displayAccountingCode(record.pargar_account_id) - if (provider === 'tahesab') return displayAccountingCode(record.moshtari_code) + if (provider === 'tahesab' || provider === 'zarin') return displayAccountingCode(record.moshtari_code) return '-' } @@ -330,6 +352,11 @@ function openTahesabRegister(user: Customer) { tahesabRegisterVisible.value = true } +function openZarinRegister(user: Customer) { + zarinCustomer.value = user + zarinRegisterVisible.value = true +} + function openPargarRegister(user: Customer) { pargarCustomer.value = user pargarRegisterVisible.value = true diff --git a/src/pages/menuPages/customers/base/hooks/customerColumns.tsx b/src/pages/menuPages/customers/base/hooks/customerColumns.tsx index f90c0b4..8e2a698 100644 --- a/src/pages/menuPages/customers/base/hooks/customerColumns.tsx +++ b/src/pages/menuPages/customers/base/hooks/customerColumns.tsx @@ -14,7 +14,7 @@ export type CustomerColumnOptions = { } function accountingCodeColumn(provider: AccountingProvider | undefined): BaseTableColumn | null { - if (provider === 'pargar' || provider === 'tahesab') { + if (provider === 'pargar' || provider === 'tahesab' || provider === 'zarin') { return { title: 'کد حسابداری', dataIndex: provider === 'pargar' ? 'pargar_account_id' : 'moshtari_code', diff --git a/src/pages/menuPages/customers/base/index.vue b/src/pages/menuPages/customers/base/index.vue index b483696..5591ded 100644 --- a/src/pages/menuPages/customers/base/index.vue +++ b/src/pages/menuPages/customers/base/index.vue @@ -609,7 +609,7 @@ const userInfo = ref() const accountingCodeVisible = computed(() => { const provider = accountingProviderSetting.value - return provider === 'pargar' || provider === 'tahesab' + return provider === 'pargar' || provider === 'tahesab' || provider === 'zarin' }) const conditionalOrderEnabled = computed(() => conditionalOrderEnabledSetting.value) @@ -631,7 +631,7 @@ function onConditionalOrderUpdated(payload: { const accountingCodeValue = computed(() => { const provider = accountingProviderSetting.value if (provider === 'pargar') return displayAccountingCode(userInfo.value?.pargar_account_id) - if (provider === 'tahesab') return displayAccountingCode(userInfo.value?.moshtari_code) + if (provider === 'tahesab' || provider === 'zarin') return displayAccountingCode(userInfo.value?.moshtari_code) return '-' }) diff --git a/src/pages/menuPages/customers/online/index.vue b/src/pages/menuPages/customers/online/index.vue index 6372404..7901ce8 100644 --- a/src/pages/menuPages/customers/online/index.vue +++ b/src/pages/menuPages/customers/online/index.vue @@ -81,6 +81,15 @@ + + + + + () const tahesabRegisterVisible = ref(false) const tahesabCustomer = ref() const tahesabEnabled = tahesabEnabledSetting +const zarinRegisterVisible = ref(false) +const zarinCustomer = ref() +const zarinEnabled = zarinEnabledSetting const pargarRegisterVisible = ref(false) const pargarCustomer = ref() const pargarEnabled = pargarEnabledSetting @@ -330,7 +352,7 @@ const customerDirectSmsEnabled = customerDirectSmsEnabledSetting const accountingEnabled = computed(() => { const provider = accountingProviderSetting.value - return provider === 'pargar' || provider === 'tahesab' + return provider === 'pargar' || provider === 'tahesab' || provider === 'zarin' }) const editVisible = ref(false) @@ -404,6 +426,11 @@ function openTahesabRegister(row: Customer) { tahesabRegisterVisible.value = true } +function openZarinRegister(row: Customer) { + zarinCustomer.value = row + zarinRegisterVisible.value = true +} + function openPargarRegister(row: Customer) { pargarCustomer.value = row pargarRegisterVisible.value = true diff --git a/src/pages/menuPages/hesabKetab/index.vue b/src/pages/menuPages/hesabKetab/index.vue index 3f720da..cc847de 100644 --- a/src/pages/menuPages/hesabKetab/index.vue +++ b/src/pages/menuPages/hesabKetab/index.vue @@ -24,7 +24,7 @@ import BasicMenu from '@/components/BasicMenu/index.vue' import { useRouterStore } from '@core/stores/router' import { get } from 'lodash' import type { RouteRecordRaw } from 'vue-router' -import { conditionalOrderEnabledSetting, initWalletSettingsFromApi } from '@/utils/config' +import { conditionalOrderEnabledSetting, initWalletSettingsFromApi, priceTriggerEnabledSetting } from '@/utils/config' const routerStore = useRouterStore() const route = useRoute() @@ -39,6 +39,7 @@ const menu = computed(() => { return children .filter((child) => !child.meta?.hideMenu) .filter((child) => child.name !== 'conditionalOrders' || conditionalOrderEnabledSetting.value) + .filter((child) => child.name !== 'priceTriggers' || priceTriggerEnabledSetting.value) .sort((a, b) => (Number(a.meta?.order) || 99) - (Number(b.meta?.order) || 99)) }) diff --git a/src/pages/menuPages/setting/index.vue b/src/pages/menuPages/setting/index.vue index 3ba000a..cff14c3 100644 --- a/src/pages/menuPages/setting/index.vue +++ b/src/pages/menuPages/setting/index.vue @@ -26,7 +26,7 @@ import { useRouterStore } from '@core/stores/router' import { useUserStore } from '@/store/userStore' import { isDeployAdmin } from '@/utils/deployAdmin' import { hasSmsPanelAccess, isSmsBillingEnabled } from '@/utils/smsPanelAccess' -import { bannerEnabledSetting, trackingSystemsEnabledSetting } from '@/utils/config' +import { bannerEnabledSetting, trackingSystemsEnabledSetting, appNotificationEnabledSetting } from '@/utils/config' import { get } from 'lodash' import type { RouteRecordRaw } from 'vue-router' @@ -39,6 +39,7 @@ const menu = computed(() => { return children .filter((child) => !child.meta?.hideMenu) .filter((child) => child.name !== 'banners' || bannerEnabledSetting.value) + .filter((child) => child.name !== 'appNotifications' || appNotificationEnabledSetting.value) .filter((child) => child.name !== 'captureSitesMain' || trackingSystemsEnabledSetting.value) .filter( (child) => diff --git a/src/pages/menuPages/withdraw/components/table.vue b/src/pages/menuPages/withdraw/components/table.vue index 402e614..04db995 100644 --- a/src/pages/menuPages/withdraw/components/table.vue +++ b/src/pages/menuPages/withdraw/components/table.vue @@ -82,6 +82,14 @@ + + + + @@ -103,6 +111,13 @@ :tahesab-id="selectedTahesabId" @success="fetch" /> + @@ -127,7 +142,8 @@ import { needsWeightAssignment } from '@/pages/menuPages/withdraw/utils/withdrawDisplay' import TahesabRetryModal from '@/components/TahesabRetryModal.vue' -import { initAccountingSettingsFromApi, tahesabEnabledSetting } from '@/utils/config' +import ZarinRetryModal from '@/components/ZarinRetryModal.vue' +import { initAccountingSettingsFromApi, tahesabEnabledSetting, zarinEnabledSetting } from '@/utils/config' const props = withDefaults( defineProps<{ @@ -143,6 +159,9 @@ const mobileListRef = ref Promise }>(null) const tahesabRetryVisible = ref(false) const selectedTahesabId = ref() const tahesabEnabled = tahesabEnabledSetting +const zarinRetryVisible = ref(false) +const selectedZarinId = ref() +const zarinEnabled = zarinEnabledSetting /*const select = (val) => { val.sts === 1 ? return ['1'] : ['2'] }*/ @@ -201,6 +220,12 @@ function openTahesabRetry(record: { tahesab_failed_id?: number }) { selectedTahesabId.value = record.tahesab_failed_id tahesabRetryVisible.value = true } + +function openZarinRetry(record: { zarin_failed_id?: number }) { + if (!record.zarin_failed_id) return + selectedZarinId.value = record.zarin_failed_id + zarinRetryVisible.value = true +} // const spinTable = (val: boolean) => { // // console.log('sss') // table?.value?.loading.value = val diff --git a/src/router/routes/hesabKetab.ts b/src/router/routes/hesabKetab.ts index b4cd7e1..4f46f38 100644 --- a/src/router/routes/hesabKetab.ts +++ b/src/router/routes/hesabKetab.ts @@ -1,5 +1,5 @@ import type { RouteRecordRaw } from 'vue-router' -import { conditionalOrderEnabledSetting } from '@/utils/config' +import { conditionalOrderEnabledSetting, priceTriggerEnabledSetting } from '@/utils/config' const HesabKetab: RouteRecordRaw = { name: 'hesabKetab', @@ -88,6 +88,11 @@ const HesabKetab: RouteRecordRaw = { icon: 'vuesax-linear:notification-bing', title: 'هشدار قیمت', order: 6 + }, + beforeEnter() { + if (!priceTriggerEnabledSetting.value) { + return { name: 'accountsMain' } + } } }, { diff --git a/src/router/routes/setting.ts b/src/router/routes/setting.ts index 22eefd8..3d1e2c1 100644 --- a/src/router/routes/setting.ts +++ b/src/router/routes/setting.ts @@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router' import { isDeployAdmin } from '@/utils/deployAdmin' import { hasSmsPanelAccess } from '@/utils/smsPanelAccess' import { useUserStore } from '@/store/userStore' -import { bannerEnabledSetting, trackingSystemsEnabledSetting } from '@/utils/config' +import { bannerEnabledSetting, trackingSystemsEnabledSetting, appNotificationEnabledSetting } from '@/utils/config' const Setting: RouteRecordRaw = { name: 'settings', @@ -109,6 +109,21 @@ const Setting: RouteRecordRaw = { } } }, + { + name: 'appNotifications', + path: 'app-notifications', + component: () => import('@/pages/menuPages/appNotifications/index.vue'), + meta: { + icon: 'vuesax-linear:notification-bing', + title: 'اعلان‌ها', + order: 4.5 + }, + beforeEnter() { + if (!appNotificationEnabledSetting.value) { + return { name: 'configuration' } + } + } + }, { name: 'smsMain', path: 'sms', diff --git a/src/utils/config.ts b/src/utils/config.ts index b4242ca..14c5598 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,7 +1,7 @@ import { ref } from 'vue' import { setCachedMainMetal } from '@/utils/metalLabels' -export type AccountingProvider = 'none' | 'pargar' | 'tahesab' +export type AccountingProvider = 'none' | 'pargar' | 'tahesab' | 'zarin' export function getConfigApp(key: string) { const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {} @@ -34,6 +34,9 @@ export const trackingSystemsEnabledSetting = ref(false) /** reactive — نمایش UI ته‌حساب در حسابداری و ... */ export const tahesabEnabledSetting = ref(false) +/** reactive — نمایش UI زرین در حسابداری و ... */ +export const zarinEnabledSetting = ref(false) + /** reactive — نمایش UI پرگار در حسابداری و ... */ export const pargarEnabledSetting = ref(false) @@ -43,12 +46,19 @@ export const customerDirectSmsEnabledSetting = ref(false) /** reactive — منو و UI سفارش شرطی (ادمین + کنترل‌های مشتری) */ export const conditionalOrderEnabledSetting = ref(false) +/** reactive — منو و UI هشدار قیمت */ +export const priceTriggerEnabledSetting = ref(false) + +/** reactive — منو و زنگوله اعلان داخل اپ */ +export const appNotificationEnabledSetting = ref(false) + /** reactive — منبع قیمت فعال (montzar / zaarbox / main_product) */ export const priceProviderSetting = ref('') export const accountingProviderSetting = ref('none') let tahesabEnabledFromSettings: boolean | null = null +let zarinEnabledFromSettings: boolean | null = null let pargarEnabledFromSettings: boolean | null = null let customerDirectSmsFromSettings: boolean | null = null let accountingProviderFromSettings: AccountingProvider | null = null @@ -60,7 +70,7 @@ function isSwitchOn(value: string | undefined | null): boolean { function normalizeAccountingProvider(value: string | undefined | null): AccountingProvider { const v = (value ?? '').trim().toLowerCase() - if (v === 'pargar' || v === 'tahesab') return v + if (v === 'pargar' || v === 'tahesab' || v === 'zarin') return v return 'none' } @@ -88,6 +98,7 @@ export function applyAccountingProviderFromRows( accountingProviderFromSettings = provider accountingProviderSetting.value = provider setTahesabEnabledFromSettings(provider === 'tahesab') + setZarinEnabledFromSettings(provider === 'zarin') setPargarEnabledFromSettings(provider === 'pargar') } @@ -108,6 +119,11 @@ export function setTahesabEnabledFromSettings(value: boolean): void { tahesabEnabledSetting.value = value } +export function setZarinEnabledFromSettings(value: boolean): void { + zarinEnabledFromSettings = value + zarinEnabledSetting.value = value +} + export function setPargarEnabledFromSettings(value: boolean): void { pargarEnabledFromSettings = value pargarEnabledSetting.value = value @@ -122,6 +138,14 @@ export function setConditionalOrderEnabledFromSettings(value: boolean): void { conditionalOrderEnabledSetting.value = value } +export function setPriceTriggerEnabledFromSettings(value: boolean): void { + priceTriggerEnabledSetting.value = value +} + +export function setAppNotificationEnabledFromSettings(value: boolean): void { + appNotificationEnabledSetting.value = value +} + export function setPriceProviderFromSettings(value?: string | null): void { priceProviderSetting.value = (value ?? '').trim().toLowerCase() } @@ -150,6 +174,20 @@ export function applyConditionalOrderEnabledFromRows( setConditionalOrderEnabledFromSettings(row ? isSwitchOn(row.value) : false) } +export function applyPriceTriggerEnabledFromRows( + rows: Array<{ key?: string; value?: string | null }> +) { + const row = rows.find((item) => item.key === 'has_page_price_trigger') + setPriceTriggerEnabledFromSettings(row ? isSwitchOn(row.value) : false) +} + +export function applyAppNotificationEnabledFromRows( + rows: Array<{ key?: string; value?: string | null }> +) { + const row = rows.find((item) => item.key === 'has_page_notification') + setAppNotificationEnabledFromSettings(row ? isSwitchOn(row.value) : false) +} + export function applyPriceProviderFromRows( rows: Array<{ key?: string; value?: string | null }> ) { @@ -176,6 +214,8 @@ async function bootstrapSettingsFromApi(): Promise { applyTrackingSystemsEnabledFromRows(list) applyCustomerDirectSmsFromRows(list) applyConditionalOrderEnabledFromRows(list) + applyPriceTriggerEnabledFromRows(list) + applyAppNotificationEnabledFromRows(list) applyPriceProviderFromRows(list) setCachedMainMetal(mainMetalRow?.value) const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet') @@ -207,6 +247,10 @@ export function isTahesabEnabled(): boolean { return tahesabEnabledFromSettings === true } +export function isZarinEnabled(): boolean { + return zarinEnabledFromSettings === true +} + export function isPargarEnabled(): boolean { return pargarEnabledFromSettings === true }