Files
boursecu_admin/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue
T
safavimohadeseh78andCursor 29904adf02 fix(shayegan): save customer accounting codes and align the document table
Shayegan was rejected as inactive when editing moshtari_code. The document list now uses the shared table format.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 13:24:02 +03:30

219 lines
7.2 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<BasicModal
v-model:visible="visibleProxy"
:width="isMobile ? '92vw' : '28rem'"
title="ویرایش کد حسابداری"
ok-text="ذخیره"
:loading="loading"
@ok="submit"
@close="close"
>
<div class="flex flex-col gap-3">
<p class="m-0 text-sm" style="color: var(--admin-text-secondary)">
مشتری:
<strong class="text-[var(--admin-text)]">{{ customer?.name || '—' }}</strong>
</p>
<a-form layout="vertical">
<a-form-item label="کد حسابداری" class="mb-0">
<BasicInput
v-model:value="code"
placeholder="کد حسابداری را وارد کنید"
dir="ltr"
class="text-left"
/>
</a-form-item>
</a-form>
<p class="m-0 text-xs" style="color: var(--admin-text-secondary)">
برای پاک کردن کد، فیلد را خالی بگذارید و ذخیره کنید.
</p>
<p
v-if="provider === 'tahesab' || provider === 'zarin'"
class="m-0 text-xs"
style="color: var(--admin-text-secondary)"
>
قبل از ذخیره، اگر مانده اپ با حسابداری فرق داشته باشد از شما تأیید گرفته می‌شود؛ بعد اعمال می‌شود.
</p>
</div>
</BasicModal>
<TahesabMandeDecisionModal
v-model:visible="decisionVisible"
:source-label="provider === 'zarin' ? 'زرین' : 'ته‌حساب'"
:allow-push="provider === 'tahesab'"
:app-gold="decision?.app_gold"
:app-cash="decision?.app_cash"
:tahesab-gold="decision?.tahesab_gold ?? decision?.zarin_gold"
:tahesab-cash="decision?.tahesab_cash ?? decision?.zarin_cash"
:gold-delta="decision?.gold_delta"
:rial-delta="decision?.rial_delta"
:wallet-docs="decision?.wallet_docs"
:tahesab-docs="decision?.tahesab_docs"
@decide="onDecide"
/>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { message } from 'ant-design-vue'
import BasicModal from '@/components/BasicModal.vue'
import { updateCustomer } from '@/api/customer'
import {
previewCustomerWalletFromTahesab,
syncCustomerWalletFromTahesab,
type TahesabWalletSyncResult,
} from '@/api/tahesab'
import {
previewCustomerWalletFromZarin,
syncCustomerWalletFromZarin,
} from '@/api/zarin'
import type { Customer } from '@/model/customer'
import type { AccountingProvider } from '@/utils/config'
import { displayAccountingCode, normalizeAccountingCode } from '@/utils/accountingCode'
import { isMobile } from '@core/utils/is'
import TahesabMandeDecisionModal from './TahesabMandeDecisionModal.vue'
const props = defineProps<{
visible: boolean
customer: Customer | null
provider: AccountingProvider
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}>()
const loading = ref(false)
const code = ref('')
const decisionVisible = ref(false)
const decision = ref<TahesabWalletSyncResult['result'] | null>(null)
const pendingPayload = ref<{ customerId: number; body: Record<string, string>; nextCode: string } | null>(null)
const visibleProxy = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
function currentCodeOf(customer: Customer | null): string {
if (!customer) return ''
if (props.provider === 'pargar') return displayAccountingCode(customer.pargar_account_id)
if (props.provider === 'tahesab' || props.provider === 'zarin' || props.provider === 'shayegan') {
return displayAccountingCode(customer.moshtari_code)
}
return ''
}
watch(
() => [props.visible, props.customer, props.provider] as const,
([visible]) => {
if (!visible) return
const shown = currentCodeOf(props.customer)
code.value = shown === '-' ? '' : shown
}
)
function close() {
visibleProxy.value = false
}
async function saveCode(customerId: number, body: Record<string, string>) {
await updateCustomer(customerId, body as Customer)
message.success('کد حسابداری ذخیره شد.')
emit('success')
close()
}
async function applyWalletDecision(customerId: number, mode: 'overwrite' | 'keep' | 'push') {
if (props.provider === 'zarin') {
if (mode === 'push') {
message.warning('اعمال مانده اپ روی زرین در این نسخه پشتیبانی نمی‌شود')
return { ok: false, message: 'پشتیبانی نمی‌شود' }
}
return syncCustomerWalletFromZarin(customerId, {
overwrite: mode === 'overwrite',
keep: mode === 'keep',
ask_if_conflict: false,
})
}
return syncCustomerWalletFromTahesab(customerId, {
overwrite: mode === 'overwrite',
keep: mode === 'keep',
push_to_tahesab: mode === 'push',
ask_if_conflict: false,
})
}
async function onDecide(mode: 'overwrite' | 'keep' | 'push') {
const pending = pendingPayload.value
decisionVisible.value = false
if (!pending) return
try {
loading.value = true
await saveCode(pending.customerId, pending.body)
const res = await applyWalletDecision(pending.customerId, mode)
if (mode === 'keep') message.info(res.message || 'موجودی اپ حفظ شد')
else if (mode === 'push') message.success(res.message || 'مانده اپ روی ته‌حساب اعمال شد')
else message.success(res.message || 'مانده حسابداری روی کیف اعمال شد')
emit('success')
} catch (e) {
console.error(e)
} finally {
loading.value = false
pendingPayload.value = null
}
}
async function submit() {
if (!props.customer?.id) return
if (props.provider !== 'pargar' && props.provider !== 'tahesab' && props.provider !== 'zarin' && props.provider !== 'shayegan') {
message.error('حسابداری فعال نیست.')
return
}
const previousCode = normalizeAccountingCode(currentCodeOf(props.customer))
const normalized = normalizeAccountingCode(code.value)
const payload =
props.provider === 'pargar'
? { pargar_account_id: normalized }
: { moshtari_code: normalized }
const needsWalletAsk =
(props.provider === 'tahesab' || props.provider === 'zarin')
&& !!normalized
&& normalized !== previousCode
try {
loading.value = true
if (needsWalletAsk) {
try {
const preview = props.provider === 'zarin'
? await previewCustomerWalletFromZarin(props.customer.id, normalized)
: await previewCustomerWalletFromTahesab(props.customer.id, normalized)
if (preview.needs_decision || preview.result?.needs_decision) {
pendingPayload.value = {
customerId: props.customer.id,
body: payload as Record<string, string>,
nextCode: normalized,
}
decision.value = preview.result ?? null
decisionVisible.value = true
return
}
if (preview.ok === false && preview.message) {
message.warning(preview.message + ' — فقط کد ذخیره می‌شود')
}
} catch {
message.warning('مانده حسابداری خوانده نشد — فقط کد ذخیره می‌شود')
}
}
await saveCode(props.customer.id, payload as Record<string, string>)
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
</script>