diff --git a/src/api/consts.ts b/src/api/consts.ts index 532f7ce..4f718d5 100644 --- a/src/api/consts.ts +++ b/src/api/consts.ts @@ -21,7 +21,7 @@ export const createAddConst = async (obj: Const) => await useAxios.post(` export const deleteConst = async (id: number) => await useAxios.delete(`/api/const/${id}`) export const updateBannerConst = async (obj: Const) => - await useAxios.put(`/admin/const/2`, obj) + await useAxios.put(`/admin/const/${obj.id}`, obj) export const getHeaderBannerConst = async () => - await useAxios.get>('/api/const/key/header_banner') \ No newline at end of file + await useAxios.get('/api/const/key/header_banner') \ No newline at end of file diff --git a/src/api/pargar.ts b/src/api/pargar.ts new file mode 100644 index 0000000..16e7efd --- /dev/null +++ b/src/api/pargar.ts @@ -0,0 +1,92 @@ +import useAxios from '@core/utils/useAxios' +import type { TableInput } from '@core/type/baseTable' + +export interface PargarRecord { + id: number + customer_id?: number + amount?: number + desc?: string + type?: string + sync_status?: 'pending' | 'success' | 'failed' + sync_log?: string | null + pargar_ref?: string | null + data?: string | null + res?: string | null + created_at?: string + customer?: { + id?: number + name?: string + mobile?: string + pargar_account_id?: string | number | null + } +} + +export const getPargarTable = async (params: TableInput) => + await useAxios.post('/admin/pargar/table', params) + +export const getPargarDetail = async (id: number) => + await useAxios.get(`/admin/pargar/${id}`) + +export const retryPargarSanad = async (id: number) => + await useAxios.post<{ ok: boolean; message: string; pargar?: PargarRecord }>(`/admin/pargar/${id}/retry`) + +export const registerCustomerInPargar = async (customerId: number) => + await useAxios.post<{ ok: boolean; message: string; pargar_account_id?: string | number }>( + `/admin/customer/${customerId}/pargar/register` + ) + +export interface PanelOnlyCustomer { + id: number + name: string + mobile: string + created_at: string +} + +export interface PargarOnlyCustomer { + code: number + name: string + tel: string + gid: number | null + group: string +} + +export interface CustomerSyncStatus { + panel_total: number + panel_registered: number + panel_only: number + pargar_total: number + pargar_only_count: number + panel_only_list: { + data: PanelOnlyCustomer[] + current_page: number + last_page: number + total: number + } + pargar_only_list: { + data: PargarOnlyCustomer[] + current_page: number + last_page: number + total: number + } +} + +export const getCustomerSyncStatus = async (params?: { + search?: string + page?: number + pargar_page?: number +}) => await useAxios.get('/admin/pargar/customer-sync/status', { params }) + +export interface SyncPanelResult { + ok: boolean + success: number + failed: number + errors: { name: string; mobile: string; error: string }[] +} + +export const syncPanelToPargar = async () => + await useAxios.post('/admin/pargar/customer-sync/panel-to-pargar') + +export const importFromPargar = async (customers: PargarOnlyCustomer[]) => + await useAxios.post('/admin/pargar/customer-sync/import-from-pargar', { + customers, + }) diff --git a/src/components/PargarRetryModal.vue b/src/components/PargarRetryModal.vue new file mode 100644 index 0000000..fe09432 --- /dev/null +++ b/src/components/PargarRetryModal.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/model/customer.ts b/src/model/customer.ts index e232b88..a3fe420 100644 --- a/src/model/customer.ts +++ b/src/model/customer.ts @@ -37,5 +37,6 @@ export interface Customer { type_str: string gold: number moshtari_code?: string | number | null + pargar_account_id?: string | number | null // print_name: string } diff --git a/src/pages/menuPages/accounting/components/PargarCustomerSync.vue b/src/pages/menuPages/accounting/components/PargarCustomerSync.vue new file mode 100644 index 0000000..ab99f4a --- /dev/null +++ b/src/pages/menuPages/accounting/components/PargarCustomerSync.vue @@ -0,0 +1,863 @@ + + + + + diff --git a/src/pages/menuPages/accounting/components/PargarMobileCardList.vue b/src/pages/menuPages/accounting/components/PargarMobileCardList.vue new file mode 100644 index 0000000..74eec9b --- /dev/null +++ b/src/pages/menuPages/accounting/components/PargarMobileCardList.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/pages/menuPages/accounting/components/PargarMobileDetailSheet.vue b/src/pages/menuPages/accounting/components/PargarMobileDetailSheet.vue new file mode 100644 index 0000000..f69f1d8 --- /dev/null +++ b/src/pages/menuPages/accounting/components/PargarMobileDetailSheet.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/pages/menuPages/accounting/components/PargarMobileListItem.vue b/src/pages/menuPages/accounting/components/PargarMobileListItem.vue new file mode 100644 index 0000000..5761441 --- /dev/null +++ b/src/pages/menuPages/accounting/components/PargarMobileListItem.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/pages/menuPages/accounting/components/pargarTable.vue b/src/pages/menuPages/accounting/components/pargarTable.vue new file mode 100644 index 0000000..f668848 --- /dev/null +++ b/src/pages/menuPages/accounting/components/pargarTable.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/pages/menuPages/accounting/index.vue b/src/pages/menuPages/accounting/index.vue index fa7b92c..e7b5f93 100644 --- a/src/pages/menuPages/accounting/index.vue +++ b/src/pages/menuPages/accounting/index.vue @@ -3,16 +3,20 @@
+ + + +
(false) -const activeTab = ref<'reports' | 'tahesab' | 'customer-sync'>('reports') +const activeTab = ref<'reports' | 'tahesab' | 'customer-sync' | 'pargar' | 'pargar-customer-sync'>('reports') const tahesabEnabled = tahesabEnabledSetting +const pargarEnabled = pargarEnabledSetting //define store const store = useStaticApiListsStore() @@ -181,7 +188,7 @@ async function getCustomers() { onMounted(() => { getCustomers() - void initTahesabSettingFromApi() + void initAccountingSettingsFromApi() }) //form diff --git a/src/pages/menuPages/accounting/utils/pargarStatus.ts b/src/pages/menuPages/accounting/utils/pargarStatus.ts new file mode 100644 index 0000000..21d5a63 --- /dev/null +++ b/src/pages/menuPages/accounting/utils/pargarStatus.ts @@ -0,0 +1,12 @@ +export function pargarStatusLabel(status?: string, pargarId?: string | null): string { + if (pargarId) return 'موفق' + if (status === 'failed') return 'ناموفق' + if (status === 'success') return 'موفق' + // pending = ردیف محلی ساخته شده ولی هنوز در نرم‌افزار پرگار ثبت نشده (نه «در صف») + return 'ارسال نشده' +} + +export function canRetryPargar(record: { pargar_ref?: string | null; sync_status?: string }): boolean { + return !record.pargar_ref || record.sync_status === 'failed' +} + diff --git a/src/pages/menuPages/banners/index.vue b/src/pages/menuPages/banners/index.vue index 0931706..007b4ea 100644 --- a/src/pages/menuPages/banners/index.vue +++ b/src/pages/menuPages/banners/index.vue @@ -111,26 +111,32 @@ function handelRemoveFile(index: number, size: string) { } async function handleSubmit() { + if (!id) { + message.error('شناسه بنر یافت نشد؛ صفحه را رفرش کنید') + return + } + try { await updateBannerConst({ - id: 14, + id, value: JSON.stringify(benefitsList.value), - key: "header_banner" + key: 'header_banner', }) - message.success("اطلاعات با موفقیت ذخیره شد") + message.success('اطلاعات با موفقیت ذخیره شد') } catch (error) { - message.error("خطا در ذخیره اطلاعات") + message.error('خطا در ذخیره اطلاعات') } } onMounted(async () => { try { const res = await getHeaderBannerConst() - - if (res?.length > 0) { - id = res[0].id || res[0].const_id || null - benefitsList.value = parseBannerList(res[0].value) + if (!res) { + return } + + id = res.id ?? res.const_id ?? null + benefitsList.value = parseBannerList(res.value) } catch (error) { console.error('خطا در دریافت بنرها:', error) } @@ -151,4 +157,4 @@ onMounted(async () => { overflow-y: auto; box-sizing: border-box; } - \ No newline at end of file + diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index ef088cf..c70e68f 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -323,7 +323,7 @@ const visibleSettingGroups = computed(() => const defaultOpenGroupId = computed(() => { const visibleIds = visibleSettingGroups.value.map((g) => g.id) const preferred = isSystemMode.value - ? ['chart', 'pricing', 'payping', 'tahesab', 'store', 'general'] + ? ['chart', 'pricing', 'payping', 'accounting', 'store', 'general'] : ['general', 'content', 'pricing', 'price_source', 'referral'] const fromPreferred = preferred.find((id) => visibleIds.includes(id)) return fromPreferred ?? visibleIds[0] diff --git a/src/pages/menuPages/configuration/settingGroups.ts b/src/pages/menuPages/configuration/settingGroups.ts index 4570eaf..9f92503 100644 --- a/src/pages/menuPages/configuration/settingGroups.ts +++ b/src/pages/menuPages/configuration/settingGroups.ts @@ -142,10 +142,15 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ match: (k) => k.startsWith('payping_') }, { - id: 'tahesab', - title: 'ته‌حساب', + id: 'accounting', + title: 'حسابداری', icon: 'vuesax-linear:receipt-2', - match: (k) => k.startsWith('tahesab_') || k === 'send_to_tahesab' + match: (k) => + k === 'accounting_provider' + || k.startsWith('tahesab_') + || k.startsWith('pargar_') + || k === 'send_to_tahesab' + || k === 'send_to_pargar' }, { id: 'infra', diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts index eb2ddf8..f6970f7 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -115,6 +115,7 @@ const KEY_TYPE_FALLBACK: Record = { endDay: 'time', show_last_price: 'switch', send_to_tahesab: 'switch', + accounting_provider: 'select', profit_gold_api_enabled: 'switch', payping_round_toman: 'switch', payping_strict_failed_withdraw: 'switch', @@ -136,6 +137,9 @@ const KEY_TYPE_FALLBACK: Record = { tahesab_token: 'text', tahesab_db: 'text', tahesab_bank_account: 'text', + pargar_username: 'text', + pargar_password: 'text', + pargar_api_token: 'text', support_messenger_id: 'text', support_messenger_text: 'longtext', invite_link_text: 'longtext', @@ -145,6 +149,11 @@ const KEY_TYPE_FALLBACK: Record = { } const SELECT_LABELS_BY_KEY: Record> = { + accounting_provider: { + none: 'هیچکدام', + pargar: 'پرگار', + tahesab: 'ته‌حساب' + }, support_messenger: { bale: 'بله', telegram: 'تلگرام', @@ -156,6 +165,7 @@ const SELECT_LABELS_BY_KEY: Record> = { } const SELECT_OPTIONS_BY_KEY: Record = { + accounting_provider: 'none,pargar,tahesab', price_provider: 'montzar,zaarbox,main_product', payping_gateway_name: 'sepal,', unit: 'گرم,کیلوگرم', @@ -400,7 +410,17 @@ const KEY_HELP_FALLBACK: Record = { payping_strict_failed_withdraw: 'روشن: در صورت برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام می‌شود.', send_to_tahesab: - 'روشن: واریز و برداشت مشتریان به‌صورت خودکار به ته‌حساب ارسال می‌شود.', + 'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.', + accounting_provider: + 'سیستم حسابداری متصل: پرگار (API ورودی)، ته‌حساب (ارسال خودکار)، یا هیچکدام.', + send_to_pargar: + 'تنظیم قدیمی — از «سیستم حسابداری» استفاده کنید.', + pargar_username: + 'نام کاربری که نرم‌افزار پرگار برای login به /pargar/login استفاده می‌کند.', + pargar_password: + 'رمز عبور login API پرگار — در پنل ثبت می‌شود و به پرگار داده می‌شود.', + pargar_api_token: + 'توکن ثابت اختیاری برای احراز API پرگار (جایگزین توکن login).', tahesab_host: 'آدرس سرور نرم‌افزار ته‌حساب در شبکه.', tahesab_port: @@ -450,7 +470,9 @@ export const KEY_LABEL_FALLBACK: Record = { montzar_open_sell: 'فروش از همکار', external_order_api_enabled: 'API سفارش خارجی', payping_fin_sync_enabled: 'همگام‌سازی با حسابداری مالی', - send_to_tahesab: 'ارسال خودکار به ته‌حساب', + accounting_provider: 'سیستم حسابداری', + send_to_tahesab: 'ارسال خودکار به ته‌حساب (قدیمی)', + send_to_pargar: 'ارسال خودکار به پرگار (قدیمی)', support_messenger: 'پیام‌رسان پشتیبانی', deposit_sms_support_number: 'شماره پشتیبانی واریز' } @@ -477,9 +499,10 @@ export const SETTING_LOCKED_KEYS = new Set([ 'payping_gateway_items', 'payping_gateway_name', 'payping_round_toman', 'payping_start_url', 'payping_strict_failed_withdraw', 'payping_verify_url', 'price_extreme_date', 'price_provider', 'profit_gold_api_enabled', - 'send_to_tahesab', 'service_charge_percentage', + 'send_to_tahesab', 'send_to_pargar', 'accounting_provider', 'service_charge_percentage', 'sse_path', 'tahesab_bank_account', 'tahesab_db', 'tahesab_host', 'tahesab_port', 'tahesab_token', + 'pargar_api_token', 'pargar_password', 'pargar_username', 'telegram_bot_token', 'unit', 'up_price', 'down_price', diff --git a/src/pages/menuPages/customers/base/components/table.vue b/src/pages/menuPages/customers/base/components/table.vue index 1b84506..e03acbd 100644 --- a/src/pages/menuPages/customers/base/components/table.vue +++ b/src/pages/menuPages/customers/base/components/table.vue @@ -66,6 +66,15 @@ + + + + @@ -148,6 +157,14 @@ :customer-name="tahesabCustomer?.name" @success="fetch" /> + @@ -173,7 +190,8 @@ import { MODAL_ABOVE_ACTION_SHEET_Z_INDEX } from '@/constants/overlayZIndex' import { isMobile } from '@core/utils/is' import { metalLabel } from '@/utils/adminMetalLabels' import TahesabRetryModal from '@/components/TahesabRetryModal.vue' -import { tahesabEnabledSetting, initTahesabSettingFromApi } from '@/utils/config' +import PargarRetryModal from '@/components/PargarRetryModal.vue' +import { tahesabEnabledSetting, pargarEnabledSetting, initAccountingSettingsFromApi } from '@/utils/config' const [submit, reload] = useBaseTable() const emits = defineEmits(['delete', 'edit', 'add', 'info', 'chargeMoney', 'chargeGold']) @@ -184,6 +202,9 @@ const confirmInfo = ref() const tahesabRegisterVisible = ref(false) const tahesabCustomer = ref() const tahesabEnabled = tahesabEnabledSetting +const pargarRegisterVisible = ref(false) +const pargarCustomer = ref() +const pargarEnabled = pargarEnabledSetting const mobileListRef = ref Promise }>(null) const tblProps = ref({ @@ -203,7 +224,7 @@ const fetch = async () => { defineExpose({ fetch }) onMounted(() => { - void initTahesabSettingFromApi() + void initAccountingSettingsFromApi() }) async function changeCustomerActivationFunction(id: number) { @@ -229,6 +250,11 @@ function openTahesabRegister(user: Customer) { tahesabRegisterVisible.value = true } +function openPargarRegister(user: Customer) { + pargarCustomer.value = user + pargarRegisterVisible.value = true +} + function handleArchive(customer: Customer) { Modal.confirm({ title: 'آیا مشتری را بایگانی می کنید؟', diff --git a/src/pages/menuPages/withdraw/components/table.vue b/src/pages/menuPages/withdraw/components/table.vue index dce0702..402e614 100644 --- a/src/pages/menuPages/withdraw/components/table.vue +++ b/src/pages/menuPages/withdraw/components/table.vue @@ -127,7 +127,7 @@ import { needsWeightAssignment } from '@/pages/menuPages/withdraw/utils/withdrawDisplay' import TahesabRetryModal from '@/components/TahesabRetryModal.vue' -import { initTahesabSettingFromApi, tahesabEnabledSetting } from '@/utils/config' +import { initAccountingSettingsFromApi, tahesabEnabledSetting } from '@/utils/config' const props = withDefaults( defineProps<{ @@ -211,7 +211,7 @@ defineExpose({ }) onMounted(() => { - void initTahesabSettingFromApi() + void initAccountingSettingsFromApi() }) import { status } from '@/pages/menuPages/withdraw/information/hooks/status' diff --git a/src/utils/config.ts b/src/utils/config.ts index 6ff70b9..cdbeef9 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,6 +1,8 @@ import { ref } from 'vue' import { setCachedMainMetal } from '@/utils/metalLabels' +export type AccountingProvider = 'none' | 'pargar' | 'tahesab' + export function getConfigApp(key: string) { const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {} @@ -19,13 +21,53 @@ export const bannerEnabledSetting = ref(false) /** reactive — نمایش UI ته‌حsاب در حسابداری و ... */ export const tahesabEnabledSetting = ref(false) +/** reactive — نمایش UI پرگar در حسابداری و ... */ +export const pargarEnabledSetting = ref(false) + +export const accountingProviderSetting = ref('none') + let tahesabEnabledFromSettings: boolean | null = null +let pargarEnabledFromSettings: boolean | null = null +let accountingProviderFromSettings: AccountingProvider | null = null function isSwitchOn(value: string | undefined | null): boolean { const v = (value ?? '').trim().toLowerCase() return v === 'true' || v === '1' || v === 'yes' } +function normalizeAccountingProvider(value: string | undefined | null): AccountingProvider { + const v = (value ?? '').trim().toLowerCase() + if (v === 'pargar' || v === 'tahesab') return v + return 'none' +} + +function resolveAccountingProviderFromRows( + list: Array<{ key?: string; value?: string | null }> +): AccountingProvider { + const row = list.find((item) => item.key === 'accounting_provider') + if (row?.value !== undefined && row?.value !== null && String(row.value).trim() !== '') { + return normalizeAccountingProvider(row.value) + } + + const pargarLegacy = list.find((item) => item.key === 'send_to_pargar') + if (pargarLegacy && isSwitchOn(pargarLegacy.value)) return 'pargar' + + const tahesabLegacy = list.find((item) => item.key === 'send_to_tahesab') + if (tahesabLegacy && isSwitchOn(tahesabLegacy.value)) return 'tahesab' + + return 'none' +} + +export function applyAccountingProviderFromRows( + list: Array<{ key?: string; value?: string | null }> +): void { + const provider = resolveAccountingProviderFromRows(list) + accountingProviderFromSettings = provider + accountingProviderSetting.value = provider + setTahesabEnabledFromSettings(provider === 'tahesab') + setPargarEnabledFromSettings(provider === 'pargar') +} + export function setStoreModeFromSettings(value: boolean): void { storeModeFromSettings = value } @@ -39,6 +81,11 @@ export function setTahesabEnabledFromSettings(value: boolean): void { tahesabEnabledSetting.value = value } +export function setPargarEnabledFromSettings(value: boolean): void { + pargarEnabledFromSettings = value + pargarEnabledSetting.value = value +} + export function applyBannerEnabledFromRows(rows: Array<{ key?: string; value?: string | null }>) { const row = rows.find((item) => item.key === 'banner_enabled') setBannerEnabledFromSettings(row ? isSwitchOn(row.value) : false) @@ -55,9 +102,8 @@ async function bootstrapSettingsFromApi(): Promise { const isStoreRow = list.find((item) => item.key === 'is_store') const mainWalletRow = list.find((item) => item.key === 'main_wallet_products') const mainMetalRow = list.find((item) => item.key === 'main_metal') - const tahesabRow = list.find((item) => item.key === 'send_to_tahesab') storeModeFromSettings = isStoreRow ? isSwitchOn(isStoreRow.value) : false - setTahesabEnabledFromSettings(tahesabRow ? isSwitchOn(tahesabRow.value) : false) + applyAccountingProviderFromRows(list) applyBannerEnabledFromRows(list) setCachedMainMetal(mainMetalRow?.value) const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet') @@ -65,7 +111,7 @@ async function bootstrapSettingsFromApi(): Promise { } catch { storeModeFromSettings = false setBannerEnabledFromSettings(false) - setTahesabEnabledFromSettings(false) + applyAccountingProviderFromRows([]) setCachedMainMetal(null) const { setCachedMainWalletProductIds } = await import('@/components/mobile/utils/wallet') setCachedMainWalletProductIds([]) @@ -95,10 +141,31 @@ export function isTahesabEnabled(): boolean { return tahesabEnabledFromSettings === true } -export async function initTahesabSettingFromApi(): Promise { - if (tahesabEnabledFromSettings !== null) return tahesabEnabledFromSettings === true +export function isPargarEnabled(): boolean { + return pargarEnabledFromSettings === true +} + +export function getAccountingProvider(): AccountingProvider { + return accountingProviderFromSettings ?? 'none' +} + +/** بارگذاری accounting_provider و فلگ‌های UI */ +export async function initAccountingSettingsFromApi(): Promise { + if (accountingProviderFromSettings !== null) return accountingProviderFromSettings await bootstrapSettingsFromApi() + return accountingProviderFromSettings ?? 'none' +} + +/** @deprecated از initAccountingSettingsFromApi استفاده کنید */ +export async function initTahesabSettingFromApi(): Promise { + await initAccountingSettingsFromApi() return tahesabEnabledFromSettings === true } + +/** @deprecated از initAccountingSettingsFromApi استفاده کنید */ +export async function initPargarSettingFromApi(): Promise { + await initAccountingSettingsFromApi() + return pargarEnabledFromSettings === true +}