fix moshtari code

This commit is contained in:
2026-08-27 14:37:39 +03:30
parent 52057805ff
commit da5b113406
5 changed files with 108 additions and 39 deletions
+38 -30
View File
@@ -6,6 +6,8 @@ import {
LEGACY_SLIDER_BANNERS_CONST_KEY, LEGACY_SLIDER_BANNERS_CONST_KEY,
LEGACY_THEME_BANNERS_CONST_KEY, LEGACY_THEME_BANNERS_CONST_KEY,
emptyAppBanners, emptyAppBanners,
isThemeBannersEmpty,
mergeAppBannersFromLegacy,
parseAppBanners, parseAppBanners,
serializeAppBanners, serializeAppBanners,
type AppBannersValue, type AppBannersValue,
@@ -51,6 +53,8 @@ export type AppBannersConstState = {
/** فقط وقتی رکورد واقعاً با key=app_banners است؛ وگرنه null تا create شود */ /** فقط وقتی رکورد واقعاً با key=app_banners است؛ وگرنه null تا create شود */
id: number | null id: number | null
data: AppBannersValue data: AppBannersValue
/** از settings_profile_banners پر شده — بهتر است یک‌بار ذخیره شود */
healedFromLegacy?: boolean
} }
async function loadLegacySliderInto(merged: AppBannersValue): Promise<void> { async function loadLegacySliderInto(merged: AppBannersValue): Promise<void> {
@@ -67,9 +71,22 @@ async function loadLegacySliderInto(merged: AppBannersValue): Promise<void> {
} }
} }
async function loadLegacyThemeBanners(): Promise<AppBannersValue> {
try {
const themeRes = await useAxios.get<Const | Const[]>(
`/api/const/key/${LEGACY_THEME_BANNERS_CONST_KEY}`
)
const themeRow = pickConstRow(themeRes)
if (!themeRow) return emptyAppBanners()
return parseAppBanners(themeRow.value)
} catch {
return emptyAppBanners()
}
}
/** /**
* یک API واحد برای همهٔ بنرها. * یک API واحد برای همهٔ بنرها.
* اگر `app_banners` نبود یا اسلایدر خالی بود، از کلیدهای قدیمی migrate می‌کند (فقط خواندن). * اگر `app_banners` نبود یا اسلات‌های تم خالی بود، از settings_profile_banners پر می‌کند.
*/ */
export async function loadAppBannersConst(): Promise<AppBannersConstState> { export async function loadAppBannersConst(): Promise<AppBannersConstState> {
let appRow: Const | null = null let appRow: Const | null = null
@@ -81,44 +98,36 @@ export async function loadAppBannersConst(): Promise<AppBannersConstState> {
} }
if (appRow) { if (appRow) {
const data = parseAppBanners(appRow.value) let data = parseAppBanners(appRow.value)
// رکورد جدید خالی: اسلایدر را از header_banner پر کن let healedFromLegacy = false
if (!data.slider.length) {
await loadLegacySliderInto(data) // رکورد پوچ app_banners روی URLهای واقعی legacy سایه نیندازد
if (isThemeBannersEmpty(data) || !data.slider.length) {
const before = serializeAppBanners(data)
if (isThemeBannersEmpty(data)) {
const legacyTheme = await loadLegacyThemeBanners()
data = mergeAppBannersFromLegacy(data, legacyTheme)
}
if (!data.slider.length) {
await loadLegacySliderInto(data)
}
healedFromLegacy = serializeAppBanners(data) !== before
} }
return { return {
id: appRow.id ?? appRow.const_id ?? null, id: appRow.id ?? appRow.const_id ?? null,
data, data,
healedFromLegacy,
} }
} }
const merged = emptyAppBanners() const merged = emptyAppBanners()
const legacyTheme = await loadLegacyThemeBanners()
try { Object.assign(merged, mergeAppBannersFromLegacy(merged, legacyTheme))
const themeRes = await useAxios.get<Const | Const[]>(
`/api/const/key/${LEGACY_THEME_BANNERS_CONST_KEY}`
)
const themeRow = pickConstRow(themeRes)
if (themeRow) {
const themes = parseAppBanners(themeRow.value)
Object.assign(merged, {
homeAsset: themes.homeAsset,
piggyCreate: themes.piggyCreate,
productsHero: themes.productsHero?.length ? themes.productsHero : merged.productsHero,
referral: themes.referral,
share: themes.share,
invite: themes.invite,
slider: themes.slider?.length ? themes.slider : merged.slider,
})
}
} catch {
// ignore
}
await loadLegacySliderInto(merged) await loadLegacySliderInto(merged)
// id=null → اولین ذخیره، رکورد جدید با key=app_banners می‌سازد const hasLegacy = !isThemeBannersEmpty(merged) || merged.slider.length > 0
return { id: null, data: merged } return { id: null, data: merged, healedFromLegacy: hasLegacy }
} }
async function syncLegacySliderConst(slides: IBenefits[]): Promise<void> { async function syncLegacySliderConst(slides: IBenefits[]): Promise<void> {
@@ -177,7 +186,6 @@ export async function saveAppBannersConst(
nextId = created?.id ?? created?.const_id ?? null nextId = created?.id ?? created?.const_id ?? null
} }
// اپ مشتری هنوز header_banner می‌خواند — اسلایدر را همگام نگه دار
try { try {
await syncLegacySliderConst(data.slider ?? []) await syncLegacySliderConst(data.slider ?? [])
} catch (error) { } catch (error) {
+1
View File
@@ -82,6 +82,7 @@ async function load(force = false) {
constId.value = res.id constId.value = res.id
applyLoadedData(res.data) applyLoadedData(res.data)
loaded.value = true loaded.value = true
// عمداً auto-save نمی‌کنیم: URLهای legacy ممکن است ۴۰۴ باشند و app_banners را خراب کنند
} catch (error) { } catch (error) {
if (rev !== localRevision) { if (rev !== localRevision) {
return return
+37
View File
@@ -74,6 +74,43 @@ function pairHasImage(pair: ThemeImagePair): boolean {
return Boolean(pair.light || pair.dark) return Boolean(pair.light || pair.dark)
} }
/** همهٔ اسلات‌های تم خالی‌اند (رکورد app_banners پوچ که روی legacy سایه انداخته) */
export function isThemeBannersEmpty(data: AppBannersValue): boolean {
return (
!pairHasImage(data.homeAsset) &&
!pairHasImage(data.piggyCreate) &&
!(data.productsHero ?? []).some(pairHasImage) &&
!pairHasImage(data.referral) &&
!pairHasImage(data.share) &&
!pairHasImage(data.invite)
)
}
function mergePair(primary: ThemeImagePair, legacy: ThemeImagePair): ThemeImagePair {
return {
light: primary.light || legacy.light,
dark: primary.dark || legacy.dark,
}
}
/** اسلات خالی primary را از legacy پر می‌کند؛ اسلایدر/محصولات اگر آرایه خالی باشد از legacy می‌آید */
export function mergeAppBannersFromLegacy(
primary: AppBannersValue,
legacy: AppBannersValue,
): AppBannersValue {
return {
slider: primary.slider.length ? primary.slider : legacy.slider,
homeAsset: mergePair(primary.homeAsset, legacy.homeAsset),
piggyCreate: mergePair(primary.piggyCreate, legacy.piggyCreate),
productsHero: (primary.productsHero ?? []).some(pairHasImage)
? primary.productsHero
: legacy.productsHero ?? [],
referral: mergePair(primary.referral, legacy.referral),
share: mergePair(primary.share, legacy.share),
invite: mergePair(primary.invite, legacy.invite),
}
}
/** آرایهٔ جدید یا سه اسلات قدیمی productsHero1/2/3 */ /** آرایهٔ جدید یا سه اسلات قدیمی productsHero1/2/3 */
export function normalizeProductsHero(parsed: Record<string, unknown>): ThemeImagePair[] { export function normalizeProductsHero(parsed: Record<string, unknown>): ThemeImagePair[] {
if (Array.isArray(parsed.productsHero)) { if (Array.isArray(parsed.productsHero)) {
@@ -239,7 +239,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import { onMounted, ref, computed } from 'vue' import { onMounted, ref, computed, watch } from 'vue'
import { import {
changeCustomerActivation, changeCustomerActivation,
changeCustomerArchive, changeCustomerArchive,
@@ -323,10 +323,15 @@ function rebuildColumns(metals = catalogMetals.value) {
}) })
} }
watch(accountingProviderSetting, () => {
rebuildColumns()
})
const tblProps = ref<any>({ const tblProps = ref<any>({
columns: buildCustomerColumns([], { accountingProvider: accountingProviderSetting.value }), columns: buildCustomerColumns([], { accountingProvider: accountingProviderSetting.value }),
api: async (params: TableInput) => { api: async (params: TableInput) => {
await initAccountingSettingsFromApi() await initAccountingSettingsFromApi()
rebuildColumns(catalogMetals.value)
const data = await getCustomerTable(params) const data = await getCustomerTable(params)
const metals = data?.catalogMetals ?? [] const metals = data?.catalogMetals ?? []
rebuildColumns(metals) rebuildColumns(metals)
@@ -341,8 +346,11 @@ const fetch = async () => {
defineExpose({ fetch }) defineExpose({ fetch })
onMounted(() => { onMounted(() => {
void initAccountingSettingsFromApi() void (async () => {
void initWalletSettingsFromApi() await initAccountingSettingsFromApi()
rebuildColumns()
await initWalletSettingsFromApi()
})()
}) })
function openDirectSms(user: Customer) { function openDirectSms(user: Customer) {
@@ -15,12 +15,16 @@ export type CustomerColumnOptions = {
function accountingCodeColumn(provider: AccountingProvider | undefined): BaseTableColumn | null { function accountingCodeColumn(provider: AccountingProvider | undefined): BaseTableColumn | null {
if (provider === 'pargar' || provider === 'tahesab' || provider === 'zarin') { if (provider === 'pargar' || provider === 'tahesab' || provider === 'zarin') {
const field = provider === 'pargar' ? 'pargar_account_id' : 'moshtari_code'
return { return {
title: 'کد حسابداری', title: 'کد حسابداری',
dataIndex: provider === 'pargar' ? 'pargar_account_id' : 'moshtari_code', dataIndex: field,
key: 'accounting_code', key: 'accounting_code',
filterField: field,
sortField: field,
type: 'Text', type: 'Text',
filterable: false, filterable: true,
sortable: true,
slot: 'accountingCode' slot: 'accountingCode'
} }
} }
@@ -74,6 +78,8 @@ export function buildCustomerColumns(
dataIndex: 'wallet.cash', dataIndex: 'wallet.cash',
key: 'wallet.cash', key: 'wallet.cash',
type: 'Number', type: 'Number',
filterable: false,
sortable: false,
customRender(data) { customRender(data) {
return usePrice(getCashBalance(data.record?.wallet, { walletCash: data.record?.wallet_cash }), true, '') return usePrice(getCashBalance(data.record?.wallet, { walletCash: data.record?.wallet_cash }), true, '')
} }
@@ -116,18 +122,18 @@ export function buildCustomerColumns(
key: 'last_seen_at', key: 'last_seen_at',
type: 'DateTime', type: 'DateTime',
width: 130, width: 130,
filterable: false, filterable: true,
sortable: true, sortable: true,
customRender: ({ record }) => <CustomerPresenceCell lastSeenAt={record.last_seen_at} /> customRender: ({ record }) => <CustomerPresenceCell lastSeenAt={record.last_seen_at} />
}, },
{ {
title: 'وضعیت', title: 'وضعیت حساب',
dataIndex: 'active', dataIndex: 'active',
key: 'status', key: 'status',
type: 'Boolean', type: 'Boolean',
width: 132, width: 132,
filterable: false, filterable: true,
sortable: false, sortable: true,
customRender: ({ record }) => { customRender: ({ record }) => {
const userOk = Boolean(record.active) const userOk = Boolean(record.active)
const tradeOk = Boolean(record.trade_active) const tradeOk = Boolean(record.trade_active)
@@ -151,6 +157,15 @@ export function buildCustomerColumns(
) )
} }
}, },
{
title: 'وضعیت سفارش',
dataIndex: 'trade_active',
key: 'trade_active',
type: 'Boolean',
filterable: true,
sortable: true,
show: false
},
{ {
title: 'عملیات', title: 'عملیات',
dataIndex: 'action', dataIndex: 'action',