feat(admin): let price alerts pick a product
Show the product on the form and table so each alert is tied to one item. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
export type SuperAdminSettingRow = {
|
||||
id?: number
|
||||
key?: string
|
||||
value?: string | number | null
|
||||
title_fa?: string
|
||||
description?: string
|
||||
is_active?: boolean | number | string
|
||||
type?: string
|
||||
can_edit?: number | boolean | string
|
||||
}
|
||||
|
||||
const SUPERADMIN_BASES = [
|
||||
'https://adm.montzar.ir',
|
||||
'https://adm.montech.ir',
|
||||
'https://adm.montest.ir',
|
||||
] as const
|
||||
|
||||
const SA_RETRY_ROUNDS = 3
|
||||
const SA_PROGRAM_SETTINGS_PATH = '/api/public/program-settings'
|
||||
|
||||
function resolveTenantDomain() {
|
||||
const host = String(window.location.hostname || '').trim()
|
||||
if (!host || host === 'localhost' || host === '127.0.0.1') {
|
||||
return 'k.montzar.ir'
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
function parseSettingsPayload(data: unknown): SuperAdminSettingRow[] {
|
||||
if (Array.isArray(data)) return data as SuperAdminSettingRow[]
|
||||
if (data && typeof data === 'object') {
|
||||
const obj = data as Record<string, unknown>
|
||||
if (Array.isArray(obj.settings)) return obj.settings as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.items)) return obj.items as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.data)) return obj.data as SuperAdminSettingRow[]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** کلید قفلشده در get_settings محلی — باید از سوپرادمین بیاید */
|
||||
export function isLockedCanEdit(canEdit: unknown): boolean {
|
||||
return canEdit === 0 || canEdit === false || canEdit === '0'
|
||||
}
|
||||
|
||||
async function fetchFromUrl(url: string, timeout = 5000): Promise<SuperAdminSettingRow[]> {
|
||||
const controller = new AbortController()
|
||||
const timer = window.setTimeout(() => controller.abort(), timeout)
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
signal: controller.signal,
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
const contentType = res.headers.get('content-type') || ''
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('SuperAdmin response is not JSON')
|
||||
}
|
||||
return parseSettingsPayload(await res.json())
|
||||
} finally {
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
|
||||
/** تنظیمات پیشرفته نسخه از سوپرادمین — فقط GET /api/public/program-settings?domain= */
|
||||
export async function fetchSuperAdminProgramSettings(): Promise<SuperAdminSettingRow[]> {
|
||||
const domain = encodeURIComponent(resolveTenantDomain())
|
||||
let lastError: unknown
|
||||
for (let round = 1; round <= SA_RETRY_ROUNDS; round++) {
|
||||
for (const base of SUPERADMIN_BASES) {
|
||||
try {
|
||||
return await fetchFromUrl(`${base}${SA_PROGRAM_SETTINGS_PATH}?domain=${domain}`)
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error('SuperAdmin program-settings unreachable')
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
export interface PriceTriggers{
|
||||
id?: number
|
||||
customer_id: number
|
||||
product_id?: number
|
||||
product?: { id: number; title?: string }
|
||||
target_price: number
|
||||
price_condition: string
|
||||
action: string
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="محصول" name="product_id" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.product_id"
|
||||
:items="productItems"
|
||||
key-name="id"
|
||||
value-name="label"
|
||||
placeholder="آبشده، سکه، نقره و ..."
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="شرط قیمت" name="price_condition" :rules="inputRules()">
|
||||
<BasicSelect
|
||||
v-model:value="form.price_condition"
|
||||
@@ -48,6 +58,7 @@ import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
||||
defineProps<{
|
||||
form: Partial<PriceTriggers>
|
||||
customerItems: Array<{ id: number; label: string }>
|
||||
productItems: Array<{ id: number; label: string }>
|
||||
editId?: number
|
||||
}>()
|
||||
|
||||
|
||||
@@ -39,6 +39,15 @@ export const cols: BaseTableColumn[] = [
|
||||
return name ? `${name} (${record.customer_id})` : String(record.customer_id ?? '-')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'محصول',
|
||||
dataIndex: 'product',
|
||||
key: 'product',
|
||||
type: 'Text',
|
||||
customRender: ({ record }) => {
|
||||
return record.product?.title || (record.product_id ? `محصول ${record.product_id}` : 'طلا')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'کد مشتری',
|
||||
dataIndex: 'customer_id',
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
ref="editorRef"
|
||||
:form="form"
|
||||
:customer-items="customerItems"
|
||||
:product-items="productItems"
|
||||
:edit-id="editId"
|
||||
/>
|
||||
</BasicActionSheet>
|
||||
@@ -38,6 +39,7 @@ import PriceTriggerEditorFormBody from './components/PriceTriggerEditorFormBody.
|
||||
import type { PriceTriggers } from '@/model/priceTriggers'
|
||||
import type { Customer } from '@/model/customer'
|
||||
import { getCustomersList } from '@/api/customer'
|
||||
import { getCustomersList as getProductsList } from '@/api/product'
|
||||
import {
|
||||
createPriceTrigger,
|
||||
deletePriceTrigger,
|
||||
@@ -57,9 +59,11 @@ const loading = ref(false)
|
||||
const editorVisible = ref(false)
|
||||
const editId = ref<number | undefined>()
|
||||
const customers = ref<Customer[]>([])
|
||||
const products = ref<Array<{ id: number; title?: string }>>([])
|
||||
|
||||
const defaultForm = (): Partial<PriceTriggers> => ({
|
||||
customer_id: undefined,
|
||||
product_id: undefined,
|
||||
target_price: undefined,
|
||||
price_condition: '>',
|
||||
action: 'sms',
|
||||
@@ -77,6 +81,13 @@ const customerItems = computed(() =>
|
||||
}))
|
||||
)
|
||||
|
||||
const productItems = computed(() =>
|
||||
products.value.map((p) => ({
|
||||
id: p.id,
|
||||
label: p.title || String(p.id),
|
||||
}))
|
||||
)
|
||||
|
||||
function refreshTable() {
|
||||
tableRef.value?.fetch()
|
||||
}
|
||||
@@ -96,6 +107,7 @@ function openEdit(record: PriceTriggers) {
|
||||
editId.value = record.id
|
||||
form.value = cloneDeep({
|
||||
customer_id: record.customer_id,
|
||||
product_id: record.product_id ?? record.product?.id,
|
||||
target_price: record.target_price,
|
||||
price_condition: record.price_condition,
|
||||
action: record.action,
|
||||
@@ -111,6 +123,7 @@ async function submitForm() {
|
||||
|
||||
const payload = {
|
||||
customer_id: Number(form.value.customer_id),
|
||||
product_id: Number(form.value.product_id),
|
||||
target_price: Number(form.value.target_price),
|
||||
price_condition: form.value.price_condition,
|
||||
action: form.value.action,
|
||||
@@ -156,6 +169,7 @@ function deleteItem(id: number) {
|
||||
onMounted(async () => {
|
||||
try {
|
||||
customers.value = (await getCustomersList()) ?? []
|
||||
products.value = ((await getProductsList()) as Array<{ id: number; title?: string }>) ?? []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
+2
-16
@@ -332,23 +332,9 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
||||
|
||||
try {
|
||||
const { getConfiguration } = await import('@/api/configuration')
|
||||
const { fetchSuperAdminProgramSettings, isLockedCanEdit } = await import('@/api/superadminSettings')
|
||||
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||
const localRows = await getConfiguration().catch(() => [])
|
||||
const saRows = await fetchSuperAdminProgramSettings()
|
||||
const local = Array.isArray(localRows) ? localRows : []
|
||||
const editable = local.filter((row) => !isLockedCanEdit(row?.can_edit))
|
||||
const merged = [...saRows] as typeof local
|
||||
for (const item of editable) {
|
||||
if (!item?.key) continue
|
||||
const index = merged.findIndex((row) => row.key === item.key)
|
||||
if (index >= 0) {
|
||||
merged[index] = { ...merged[index], ...item }
|
||||
} else {
|
||||
merged.push(item)
|
||||
}
|
||||
}
|
||||
const list = merged
|
||||
const rows = await getConfiguration()
|
||||
const list = Array.isArray(rows) ? rows : []
|
||||
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')
|
||||
|
||||
Reference in New Issue
Block a user