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{
|
export interface PriceTriggers{
|
||||||
id?: number
|
id?: number
|
||||||
customer_id: number
|
customer_id: number
|
||||||
|
product_id?: number
|
||||||
|
product?: { id: number; title?: string }
|
||||||
target_price: number
|
target_price: number
|
||||||
price_condition: string
|
price_condition: string
|
||||||
action: string
|
action: string
|
||||||
|
|||||||
@@ -11,6 +11,16 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</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()">
|
<a-form-item label="شرط قیمت" name="price_condition" :rules="inputRules()">
|
||||||
<BasicSelect
|
<BasicSelect
|
||||||
v-model:value="form.price_condition"
|
v-model:value="form.price_condition"
|
||||||
@@ -48,6 +58,7 @@ import { inputRules, inputRulesNumber } from '@/utils/inputRules'
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
form: Partial<PriceTriggers>
|
form: Partial<PriceTriggers>
|
||||||
customerItems: Array<{ id: number; label: string }>
|
customerItems: Array<{ id: number; label: string }>
|
||||||
|
productItems: Array<{ id: number; label: string }>
|
||||||
editId?: number
|
editId?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ export const cols: BaseTableColumn[] = [
|
|||||||
return name ? `${name} (${record.customer_id})` : String(record.customer_id ?? '-')
|
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: 'کد مشتری',
|
title: 'کد مشتری',
|
||||||
dataIndex: 'customer_id',
|
dataIndex: 'customer_id',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
ref="editorRef"
|
ref="editorRef"
|
||||||
:form="form"
|
:form="form"
|
||||||
:customer-items="customerItems"
|
:customer-items="customerItems"
|
||||||
|
:product-items="productItems"
|
||||||
:edit-id="editId"
|
:edit-id="editId"
|
||||||
/>
|
/>
|
||||||
</BasicActionSheet>
|
</BasicActionSheet>
|
||||||
@@ -38,6 +39,7 @@ import PriceTriggerEditorFormBody from './components/PriceTriggerEditorFormBody.
|
|||||||
import type { PriceTriggers } from '@/model/priceTriggers'
|
import type { PriceTriggers } from '@/model/priceTriggers'
|
||||||
import type { Customer } from '@/model/customer'
|
import type { Customer } from '@/model/customer'
|
||||||
import { getCustomersList } from '@/api/customer'
|
import { getCustomersList } from '@/api/customer'
|
||||||
|
import { getCustomersList as getProductsList } from '@/api/product'
|
||||||
import {
|
import {
|
||||||
createPriceTrigger,
|
createPriceTrigger,
|
||||||
deletePriceTrigger,
|
deletePriceTrigger,
|
||||||
@@ -57,9 +59,11 @@ const loading = ref(false)
|
|||||||
const editorVisible = ref(false)
|
const editorVisible = ref(false)
|
||||||
const editId = ref<number | undefined>()
|
const editId = ref<number | undefined>()
|
||||||
const customers = ref<Customer[]>([])
|
const customers = ref<Customer[]>([])
|
||||||
|
const products = ref<Array<{ id: number; title?: string }>>([])
|
||||||
|
|
||||||
const defaultForm = (): Partial<PriceTriggers> => ({
|
const defaultForm = (): Partial<PriceTriggers> => ({
|
||||||
customer_id: undefined,
|
customer_id: undefined,
|
||||||
|
product_id: undefined,
|
||||||
target_price: undefined,
|
target_price: undefined,
|
||||||
price_condition: '>',
|
price_condition: '>',
|
||||||
action: 'sms',
|
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() {
|
function refreshTable() {
|
||||||
tableRef.value?.fetch()
|
tableRef.value?.fetch()
|
||||||
}
|
}
|
||||||
@@ -96,6 +107,7 @@ function openEdit(record: PriceTriggers) {
|
|||||||
editId.value = record.id
|
editId.value = record.id
|
||||||
form.value = cloneDeep({
|
form.value = cloneDeep({
|
||||||
customer_id: record.customer_id,
|
customer_id: record.customer_id,
|
||||||
|
product_id: record.product_id ?? record.product?.id,
|
||||||
target_price: record.target_price,
|
target_price: record.target_price,
|
||||||
price_condition: record.price_condition,
|
price_condition: record.price_condition,
|
||||||
action: record.action,
|
action: record.action,
|
||||||
@@ -111,6 +123,7 @@ async function submitForm() {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
customer_id: Number(form.value.customer_id),
|
customer_id: Number(form.value.customer_id),
|
||||||
|
product_id: Number(form.value.product_id),
|
||||||
target_price: Number(form.value.target_price),
|
target_price: Number(form.value.target_price),
|
||||||
price_condition: form.value.price_condition,
|
price_condition: form.value.price_condition,
|
||||||
action: form.value.action,
|
action: form.value.action,
|
||||||
@@ -156,6 +169,7 @@ function deleteItem(id: number) {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
customers.value = (await getCustomersList()) ?? []
|
customers.value = (await getCustomersList()) ?? []
|
||||||
|
products.value = ((await getProductsList()) as Array<{ id: number; title?: string }>) ?? []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-16
@@ -332,23 +332,9 @@ async function bootstrapSettingsFromApi(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { getConfiguration } = await import('@/api/configuration')
|
const { getConfiguration } = await import('@/api/configuration')
|
||||||
const { fetchSuperAdminProgramSettings, isLockedCanEdit } = await import('@/api/superadminSettings')
|
|
||||||
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
const { parseMainWalletProductIds } = await import('@/components/mobile/utils/wallet')
|
||||||
const localRows = await getConfiguration().catch(() => [])
|
const rows = await getConfiguration()
|
||||||
const saRows = await fetchSuperAdminProgramSettings()
|
const list = Array.isArray(rows) ? rows : []
|
||||||
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 isStoreRow = list.find((item) => item.key === 'is_store')
|
const isStoreRow = list.find((item) => item.key === 'is_store')
|
||||||
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
const mainWalletRow = list.find((item) => item.key === 'main_wallet_products')
|
||||||
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
const mainMetalRow = list.find((item) => item.key === 'main_metal')
|
||||||
|
|||||||
Reference in New Issue
Block a user