price handling
This commit is contained in:
Vendored
+1
@@ -10,6 +10,7 @@ export {}
|
|||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
_index: typeof import('./src/core/components/_sample/_index.vue')['default']
|
_index: typeof import('./src/core/components/_sample/_index.vue')['default']
|
||||||
|
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||||
AButton: typeof import('ant-design-vue/es')['Button']
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
|
|||||||
@@ -31,10 +31,13 @@
|
|||||||
<AButton
|
<AButton
|
||||||
v-if="showOk"
|
v-if="showOk"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
html-type="button"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
:class="[{ 'col-span-8': showCancel }]"
|
:class="[{ 'col-span-8': showCancel }]"
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
@click="$emit('ok')"
|
@click="handleOk"
|
||||||
>
|
>
|
||||||
{{ okText }}
|
{{ okText }}
|
||||||
</AButton>
|
</AButton>
|
||||||
@@ -79,10 +82,13 @@
|
|||||||
<AButton
|
<AButton
|
||||||
v-if="showOk"
|
v-if="showOk"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
html-type="button"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
:class="[{ 'col-span-8': showCancel }]"
|
:class="[{ 'col-span-8': showCancel }]"
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
@click="$emit('ok')"
|
@click="handleOk"
|
||||||
>
|
>
|
||||||
{{ okText }}
|
{{ okText }}
|
||||||
</AButton>
|
</AButton>
|
||||||
@@ -151,6 +157,10 @@ function handleCancel() {
|
|||||||
emit('close')
|
emit('close')
|
||||||
visibleProxy.value = false
|
visibleProxy.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOk() {
|
||||||
|
emit('ok')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -26,6 +26,19 @@ const headers = {
|
|||||||
Accept: 'application/json'
|
Accept: 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatApiErrorMessage(raw: string): string {
|
||||||
|
const unknownColumn = raw.match(/Unknown column '([^']+)'/)
|
||||||
|
if (unknownColumn) {
|
||||||
|
return `ستون «${unknownColumn[1]}» در دیتابیس وجود ندارد — سرور را بهروز کنید یا با پشتیبانی تماس بگیرید`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw.length > 220) {
|
||||||
|
return `${raw.slice(0, 220)}…`
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
const _axios = axios.create({
|
const _axios = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
headers
|
headers
|
||||||
@@ -82,7 +95,12 @@ _axios.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
} else if (error.response) {
|
} else if (error.response) {
|
||||||
if (!error.config?.meta?.silent) {
|
if (!error.config?.meta?.silent) {
|
||||||
message.error('خطا در بارگذاری اطلاعات')
|
const apiMessage = error.response.data?.message
|
||||||
|
message.error(
|
||||||
|
typeof apiMessage === 'string' && apiMessage.trim()
|
||||||
|
? formatApiErrorMessage(apiMessage)
|
||||||
|
: 'خطا در بارگذاری اطلاعات',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
<Collapse class="grid grid-cols-1 gap-2" :activeKey="activeKey" @update:activeKey="$emit('update:activeKey', $event)">
|
<Collapse class="grid grid-cols-1 gap-2" :activeKey="activeKey" @update:activeKey="$emit('update:activeKey', $event)">
|
||||||
<BaseCollapse title="اطلاعات محصول" active-key="0" :actives="activeKey">
|
<BaseCollapse title="اطلاعات محصول" active-key="0" :actives="activeKey">
|
||||||
<template #content>
|
<template #content>
|
||||||
|
<a-form
|
||||||
|
:model="form"
|
||||||
|
ref="innerFormRef"
|
||||||
|
autocomplete="off"
|
||||||
|
scroll-to-first-error
|
||||||
|
class="product-editor-form"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isMeltedCategory"
|
v-if="isMeltedCategory"
|
||||||
class="md:flex flex-col gap-3 border-1 border-solid rounded-lg border-gray-200 p-3"
|
class="md:flex flex-col gap-3 border-1 border-solid rounded-lg border-gray-200 p-3"
|
||||||
@@ -46,10 +53,10 @@
|
|||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
مثبت = گرانتر، منفی = ارزانتر نسبت به قیمت مرجع.
|
مثبت = گرانتر، منفی = ارزانتر نسبت به قیمت مرجع.
|
||||||
</div>
|
</div>
|
||||||
<a-form-item label="قیمت صفر از provider" name="use_last_price_if_zero" class="!mb-0">
|
<a-form-item label="قیمت صفر از مرجع" name="use_last_price_if_zero" class="!mb-0">
|
||||||
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
||||||
<span class="mr-2 text-xs text-gray-500">
|
<span class="mr-2 text-xs text-gray-500">
|
||||||
اگر provider قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
اگر مرجع قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
||||||
</span>
|
</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,10 +73,10 @@
|
|||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
قیمت خرید/فروش از قیمت صفحه اصلی اپ محاسبه میشود. مثبت = گرانتر، منفی = ارزانتر.
|
قیمت خرید/فروش از قیمت صفحه اصلی اپ محاسبه میشود. مثبت = گرانتر، منفی = ارزانتر.
|
||||||
</div>
|
</div>
|
||||||
<a-form-item label="قیمت صفر از provider" name="use_last_price_if_zero" class="!mb-0">
|
<a-form-item label="قیمت صفر از مرجع" name="use_last_price_if_zero" class="!mb-0">
|
||||||
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
||||||
<span class="mr-2 text-xs text-gray-500">
|
<span class="mr-2 text-xs text-gray-500">
|
||||||
اگر provider قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
اگر مرجع قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
||||||
</span>
|
</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,10 +132,10 @@
|
|||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
مثبت = گرانتر، منفی = ارزانتر نسبت به قیمت اتوماتیک. مثلاً ۵ یعنی ۵٪ بالاتر از مرجع.
|
مثبت = گرانتر، منفی = ارزانتر نسبت به قیمت اتوماتیک. مثلاً ۵ یعنی ۵٪ بالاتر از مرجع.
|
||||||
</div>
|
</div>
|
||||||
<a-form-item label="قیمت صفر از provider" name="use_last_price_if_zero" class="!mb-0">
|
<a-form-item label="قیمت صفر از مرجع" name="use_last_price_if_zero" class="!mb-0">
|
||||||
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
<a-switch v-model:checked="form.use_last_price_if_zero" />
|
||||||
<span class="mr-2 text-xs text-gray-500">
|
<span class="mr-2 text-xs text-gray-500">
|
||||||
اگر provider قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
اگر مرجع قیمت نداد (صفر)، با آخرین قیمت ذخیرهشده معامله شود
|
||||||
</span>
|
</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,15 +179,20 @@
|
|||||||
<a-radio value="2">ندارد</a-radio>
|
<a-radio value="2">ندارد</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="weightOption === '1'" class="flex items-center">
|
<div v-if="weightOption === '1'" class="flex items-center md:col-span-2">
|
||||||
<span class="ml-4 ">{{ weightFieldLabel }} :</span>
|
<a-form-item
|
||||||
<BasicInput v-model:value="form.amount" type="number" />
|
class="!mb-0 w-full"
|
||||||
<div class="mt-1 mr-4" v-if="form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'">معادل {{ form.amount * 1000 }} سوت</div>
|
:label="weightFieldLabel"
|
||||||
|
name="amount"
|
||||||
|
:rules="inputRulesNumber('وزن')"
|
||||||
|
>
|
||||||
|
<BasicInput v-model:value="form.amount" type="number" />
|
||||||
|
</a-form-item>
|
||||||
|
<div class="mt-1 mr-4 shrink-0" v-if="form.amount && form.amount < 1 && 0 < form.amount && tradeWeightUnit === 'گرم'">معادل {{ form.amount * 1000 }} سوت</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-form :model="form" ref="formRef" autocomplete="off">
|
<div class="grid md:grid-cols-2 gap-4 mt-2">
|
||||||
<div class="grid md:grid-cols-2 gap-4">
|
|
||||||
<a-form-item label="عنوان" name="title" :rules="inputRules()"><BasicInput v-model:value="form.title" /></a-form-item>
|
<a-form-item label="عنوان" name="title" :rules="inputRules()"><BasicInput v-model:value="form.title" /></a-form-item>
|
||||||
<a-form-item label="دسته بندی" name="group_type" :rules="inputRules()">
|
<a-form-item label="دسته بندی" name="group_type" :rules="inputRules()">
|
||||||
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="key" />
|
<BasicSelect v-model:value="form.group_type" :items="categoryItems" valueName="value" keyName="key" />
|
||||||
@@ -207,10 +219,10 @@
|
|||||||
>
|
>
|
||||||
<BasicInput v-model:value="form.inventory" type="number" />
|
<BasicInput v-model:value="form.inventory" type="number" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="هزینه نقدی موقع خرید" name="cash_fee_buy" :rules="inputRules()">
|
<a-form-item label="هزینه نقدی موقع خرید" name="cash_fee_buy" :rules="requiredPriceRules('هزینه نقدی موقع خرید')">
|
||||||
<BasicInput v-model:value="form.cash_fee_buy" addon-before="ریال" type="price" />
|
<BasicInput v-model:value="form.cash_fee_buy" addon-before="ریال" type="price" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="هزینه نقدی موقع فروش" name="cash_fee_sell" :rules="inputRules()">
|
<a-form-item label="هزینه نقدی موقع فروش" name="cash_fee_sell" :rules="requiredPriceRules('هزینه نقدی موقع فروش')">
|
||||||
<BasicInput v-model:value="form.cash_fee_sell" addon-before="ریال" type="price" />
|
<BasicInput v-model:value="form.cash_fee_sell" addon-before="ریال" type="price" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="زمان شروع فعال سازی" name="auto_open_at">
|
<a-form-item label="زمان شروع فعال سازی" name="auto_open_at">
|
||||||
@@ -277,8 +289,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { FormInstance } from 'ant-design-vue'
|
||||||
import { Collapse, TimePicker } from 'ant-design-vue'
|
import { Collapse, TimePicker } from 'ant-design-vue'
|
||||||
import { inputRules } from '@/utils/inputRules'
|
import { inputRules, inputRulesNumber, requiredPriceRules } from '@/utils/inputRules'
|
||||||
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
||||||
import BaseCollapse from '@/components/BaseCollapse.vue'
|
import BaseCollapse from '@/components/BaseCollapse.vue'
|
||||||
import IndexImageUploader from '@/components/ProductUploader/IndexImageUploader.vue'
|
import IndexImageUploader from '@/components/ProductUploader/IndexImageUploader.vue'
|
||||||
@@ -293,9 +306,10 @@ import { resolveTradeWeightUnit, weightFieldLabelForUnit } from '@/utils/weightU
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const innerFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
form: any
|
form: any
|
||||||
formRef: any
|
|
||||||
methodOption: string
|
methodOption: string
|
||||||
weightOption: string
|
weightOption: string
|
||||||
meltedPriceOption: string
|
meltedPriceOption: string
|
||||||
@@ -392,6 +406,19 @@ defineEmits<{
|
|||||||
(e: 'deleteIndexFile'): void
|
(e: 'deleteIndexFile'): void
|
||||||
(e: 'refreshPrices'): void
|
(e: 'refreshPrices'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
async validateFields() {
|
||||||
|
if (!innerFormRef.value) {
|
||||||
|
throw {
|
||||||
|
errorFields: [{ errors: ['فرم آماده نیست — لطفاً دوباره تلاش کنید'] }],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return innerFormRef.value.validateFields()
|
||||||
|
},
|
||||||
|
resetFields: () => innerFormRef.value?.resetFields(),
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -11,14 +11,23 @@
|
|||||||
:showOk="true"
|
:showOk="true"
|
||||||
okText="ثبت"
|
okText="ثبت"
|
||||||
cancelText="انصراف"
|
cancelText="انصراف"
|
||||||
@ok="ok"
|
@ok="submitProduct"
|
||||||
@cancel="closeProductModal"
|
@cancel="closeProductModal"
|
||||||
@close="closeProductModal"
|
@close="closeProductModal"
|
||||||
>
|
>
|
||||||
<div v-if="isMobile" class="mb-3 text-base font-700">{{ `${editId ? 'ویرایش' : 'افزودن'} محصول` }}</div>
|
<div v-if="isMobile" class="mb-3 text-base font-700">{{ `${editId ? 'ویرایش' : 'افزودن'} محصول` }}</div>
|
||||||
|
<a-alert
|
||||||
|
v-if="submitError"
|
||||||
|
type="error"
|
||||||
|
:message="submitError"
|
||||||
|
show-icon
|
||||||
|
closable
|
||||||
|
class="mb-3"
|
||||||
|
@close="submitError = ''"
|
||||||
|
/>
|
||||||
<ProductEditorFormBody
|
<ProductEditorFormBody
|
||||||
|
ref="editorFormRef"
|
||||||
:form="form"
|
:form="form"
|
||||||
:formRef="formRef"
|
|
||||||
:methodOption="methodOption"
|
:methodOption="methodOption"
|
||||||
:weightOption="weightOption"
|
:weightOption="weightOption"
|
||||||
:activeKey="activeKey_"
|
:activeKey="activeKey_"
|
||||||
@@ -51,7 +60,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { FormInstance } from 'ant-design-vue'
|
|
||||||
import locale from 'ant-design-vue/es/date-picker/locale/fa_IR'
|
import locale from 'ant-design-vue/es/date-picker/locale/fa_IR'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -66,9 +74,11 @@ import BasicActionSheet from '@/components/BasicActionSheet.vue'
|
|||||||
import type { Item } from '@/model/Item'
|
import type { Item } from '@/model/Item'
|
||||||
import type { MethodePrices, Product } from '@/model/product'
|
import type { MethodePrices, Product } from '@/model/product'
|
||||||
import ProductEditorFormBody from '@/pages/menuPages/products/base/components/ProductEditorFormBody.vue'
|
import ProductEditorFormBody from '@/pages/menuPages/products/base/components/ProductEditorFormBody.vue'
|
||||||
|
import { validateProductSubmitInput } from '@/pages/menuPages/products/base/productFormValidation'
|
||||||
import tbl from './components/table.vue'
|
import tbl from './components/table.vue'
|
||||||
import { isMobile } from '@core/utils/is'
|
import { isMobile } from '@core/utils/is'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
|
import { referenceProviderTotal } from '@/utils/productEditorPrice'
|
||||||
import {
|
import {
|
||||||
applyFileToUploadList,
|
applyFileToUploadList,
|
||||||
normalizeFileResource,
|
normalizeFileResource,
|
||||||
@@ -90,6 +100,7 @@ import { resolveTradeWeightUnit } from '@/utils/weightUnit'
|
|||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const submitError = ref('')
|
||||||
const table = ref<null | { fetch: () => void }>(null)
|
const table = ref<null | { fetch: () => void }>(null)
|
||||||
const refreshTable = () => table.value?.fetch()
|
const refreshTable = () => table.value?.fetch()
|
||||||
|
|
||||||
@@ -107,7 +118,7 @@ const form = ref<Partial<Product>>({
|
|||||||
auto_open_at: dayjs('00:00:00', 'HH:mm:ss'),
|
auto_open_at: dayjs('00:00:00', 'HH:mm:ss'),
|
||||||
metal_id: 1
|
metal_id: 1
|
||||||
})
|
})
|
||||||
const formRef = ref<FormInstance>()
|
const editorFormRef = ref<InstanceType<typeof ProductEditorFormBody>>()
|
||||||
|
|
||||||
const customLocale = ref({
|
const customLocale = ref({
|
||||||
...locale,
|
...locale,
|
||||||
@@ -143,8 +154,8 @@ let pricePollTimer: ReturnType<typeof setInterval> | undefined
|
|||||||
const pricesList = computed(() =>
|
const pricesList = computed(() =>
|
||||||
pricesData.value.map((x) => ({
|
pricesData.value.map((x) => ({
|
||||||
id: `${x.key ?? x.id ?? x.name}`,
|
id: `${x.key ?? x.id ?? x.name}`,
|
||||||
name: `${x.name} (${usePrice(x.price)})`
|
name: `${x.name} (${usePrice(referenceProviderTotal(x))})`,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function fetchPrices() {
|
async function fetchPrices() {
|
||||||
@@ -329,17 +340,19 @@ async function edit(record: Product) {
|
|||||||
function closeProductModal() {
|
function closeProductModal() {
|
||||||
addUserVisible.value = false
|
addUserVisible.value = false
|
||||||
editId = undefined
|
editId = undefined
|
||||||
|
submitError.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add() {
|
async function add() {
|
||||||
editId = undefined
|
editId = undefined
|
||||||
|
submitError.value = ''
|
||||||
addUserVisible.value = true
|
addUserVisible.value = true
|
||||||
resetData()
|
resetData()
|
||||||
await loadCategorySelectItems()
|
await loadCategorySelectItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetData() {
|
function resetData() {
|
||||||
formRef.value?.resetFields()
|
editorFormRef.value?.resetFields()
|
||||||
ProductIndexImage.value = []
|
ProductIndexImage.value = []
|
||||||
methodOption.value = '1'
|
methodOption.value = '1'
|
||||||
weightOption.value = '1'
|
weightOption.value = '1'
|
||||||
@@ -349,9 +362,9 @@ function resetData() {
|
|||||||
group_type: '',
|
group_type: '',
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
percent_fee_buy: 0,
|
percent_fee_buy: 0,
|
||||||
cash_fee_buy: 0,
|
cash_fee_buy: undefined,
|
||||||
percent_fee_sell: 0,
|
percent_fee_sell: 0,
|
||||||
cash_fee_sell: 0,
|
cash_fee_sell: undefined,
|
||||||
price_offset_percent: 0,
|
price_offset_percent: 0,
|
||||||
metal_id: 1,
|
metal_id: 1,
|
||||||
direct_price: 0,
|
direct_price: 0,
|
||||||
@@ -369,29 +382,59 @@ function resetData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ok() {
|
function showSubmitError(text: string) {
|
||||||
if (!isMeltedTradeGroup(form.value.group_type ?? '')) {
|
submitError.value = text
|
||||||
if (weightOption.value === '1') {
|
message.error(text, 4)
|
||||||
const amount = Number(form.value.amount)
|
}
|
||||||
if (!Number.isFinite(amount) || amount <= 0) {
|
|
||||||
message.error('لطفاً وزن محصول را وارد کنید')
|
async function submitProduct() {
|
||||||
return
|
try {
|
||||||
}
|
await saveProduct()
|
||||||
}
|
} catch (err: unknown) {
|
||||||
|
console.error(err)
|
||||||
|
const text = err instanceof Error ? err.message : 'خطای غیرمنتظره در ثبت محصول'
|
||||||
|
showSubmitError(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveProduct() {
|
||||||
|
if (!activeKey_.value.includes('0')) {
|
||||||
|
activeKey_.value = ['0', ...activeKey_.value]
|
||||||
|
await nextTick()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const businessError = validateProductSubmitInput({
|
||||||
await formRef.value?.validateFields()
|
form: form.value as Record<string, unknown>,
|
||||||
} catch {
|
methodOption: methodOption.value,
|
||||||
|
weightOption: weightOption.value,
|
||||||
|
meltedPriceOption: meltedPriceOption.value,
|
||||||
|
isEdit: Boolean(editId),
|
||||||
|
})
|
||||||
|
if (businessError) {
|
||||||
|
showSubmitError(businessError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!editorFormRef.value) {
|
||||||
|
showSubmitError('فرم آماده نیست — لطفاً مودال را ببندید و دوباره باز کنید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await editorFormRef.value.validateFields()
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const first = (err as { errorFields?: Array<{ errors?: string[] }> })?.errorFields?.[0]?.errors?.[0]
|
||||||
|
showSubmitError(first || 'لطفاً فیلدهای الزامی را تکمیل کنید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitError.value = ''
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const payload = cloneDeep(form.value)
|
const payload = cloneDeep(form.value)
|
||||||
const limitError = validateProductOrderLimitForm(payload)
|
const limitError = validateProductOrderLimitForm(payload)
|
||||||
if (limitError) {
|
if (limitError) {
|
||||||
message.error(limitError)
|
showSubmitError(limitError)
|
||||||
loading.value = false
|
loading.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -433,6 +476,8 @@ async function ok() {
|
|||||||
|
|
||||||
if (isStoreApp() && !isMeltedTradeGroup(payload.group_type)) {
|
if (isStoreApp() && !isMeltedTradeGroup(payload.group_type)) {
|
||||||
payload.inventory = Math.max(0, Number(payload.inventory ?? 0))
|
payload.inventory = Math.max(0, Number(payload.inventory ?? 0))
|
||||||
|
} else {
|
||||||
|
delete payload.inventory
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.auto_open_at = payload.auto_open_at
|
payload.auto_open_at = payload.auto_open_at
|
||||||
@@ -463,12 +508,14 @@ async function ok() {
|
|||||||
|
|
||||||
addUserVisible.value = false
|
addUserVisible.value = false
|
||||||
editId = undefined
|
editId = undefined
|
||||||
|
submitError.value = ''
|
||||||
refreshTable()
|
refreshTable()
|
||||||
resetData()
|
resetData()
|
||||||
message.success('عملیات با موفقیت انجام شد')
|
message.success('عملیات با موفقیت انجام شد')
|
||||||
} catch (e) {
|
} catch (e: unknown) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
message.error('خطا در ثبت اطلاعات')
|
const apiMessage = (e as { response?: { data?: { message?: string } } })?.response?.data?.message
|
||||||
|
showSubmitError(apiMessage || 'خطا در ثبت اطلاعات')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { isMeltedTradeGroup } from '@/utils/productCategories'
|
||||||
|
|
||||||
|
function isBlank(value: unknown) {
|
||||||
|
return value === undefined || value === null || value === ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMissingNumber(value: unknown) {
|
||||||
|
if (isBlank(value)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = Number(value)
|
||||||
|
return !Number.isFinite(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateProductSubmitInput(input: {
|
||||||
|
form: Record<string, unknown>
|
||||||
|
methodOption: string
|
||||||
|
weightOption: string
|
||||||
|
meltedPriceOption: string
|
||||||
|
isEdit: boolean
|
||||||
|
}): string | null {
|
||||||
|
const { form, methodOption, weightOption, meltedPriceOption, isEdit } = input
|
||||||
|
const groupType = String(form.group_type ?? '')
|
||||||
|
const melted = isMeltedTradeGroup(groupType)
|
||||||
|
|
||||||
|
if (!String(form.title ?? '').trim()) {
|
||||||
|
return 'عنوان محصول الزامی است'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupType) {
|
||||||
|
return 'دستهبندی الزامی است'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(form.metal_id)) {
|
||||||
|
return 'جنس فلز الزامی است'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (melted || weightOption === '1') {
|
||||||
|
if (!String(form.unit ?? '').trim()) {
|
||||||
|
return 'واحد الزامی است'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!melted && weightOption === '1') {
|
||||||
|
const amount = Number(form.amount)
|
||||||
|
if (!Number.isFinite(amount) || amount <= 0) {
|
||||||
|
return 'وزن محصول را وارد کنید'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (melted) {
|
||||||
|
if (meltedPriceOption === 'fixed' && isMissingNumber(form.price)) {
|
||||||
|
return 'قیمت ثابت را وارد کنید'
|
||||||
|
}
|
||||||
|
if (meltedPriceOption === 'reference' && !String(form.reading_method ?? '').trim()) {
|
||||||
|
return 'مرجع قیمت را انتخاب کنید'
|
||||||
|
}
|
||||||
|
} else if (methodOption === '1') {
|
||||||
|
if (isMissingNumber(form.price)) {
|
||||||
|
return 'قیمت دستی را وارد کنید'
|
||||||
|
}
|
||||||
|
} else if (methodOption === '2') {
|
||||||
|
if (!String(form.reading_method ?? '').trim()) {
|
||||||
|
return 'مرجع قیمت را انتخاب کنید'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(form.cash_fee_buy)) {
|
||||||
|
return 'هزینه نقدی موقع خرید را وارد کنید'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(form.cash_fee_sell)) {
|
||||||
|
return 'هزینه نقدی موقع فروش را وارد کنید'
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -6,6 +6,29 @@ export function inputRules(title?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** فیلد قیمت/عدد — خالی رد میشود، صفر مجاز است */
|
||||||
|
export function requiredPriceRules(title?: string) {
|
||||||
|
const label = title || 'مورد بالا'
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
validator(_rule: unknown, value: unknown) {
|
||||||
|
if (value === undefined || value === null || String(value).trim() === '') {
|
||||||
|
return Promise.reject(`پر کردن ${label} الزامی است`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = Number(String(value).replaceAll(',', ''))
|
||||||
|
if (!Number.isFinite(num)) {
|
||||||
|
return Promise.reject('لطفاً عدد معتبر وارد کنید')
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
trigger: ['change', 'blur'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export function inputRulesNumber(title?: string) {
|
export function inputRulesNumber(title?: string) {
|
||||||
if (title) {
|
if (title) {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export function normalizeReferenceBase(entry: MethodePrices): { base: number; pr
|
|||||||
let base = Number(entry.price) || 0
|
let base = Number(entry.price) || 0
|
||||||
const profit = Number(entry.profit) || 0
|
const profit = Number(entry.profit) || 0
|
||||||
|
|
||||||
|
if (base <= 0) {
|
||||||
|
return { base: 0, profit: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.name === GOLD_DOT_REFERENCE_NAME) {
|
if (entry.name === GOLD_DOT_REFERENCE_NAME) {
|
||||||
base = base / 4.3318
|
base = base / 4.3318
|
||||||
}
|
}
|
||||||
@@ -39,6 +43,20 @@ export function adjustedProductPrice(
|
|||||||
return Math.round(reference * (1 + (Number(offsetPercent) || 0) / 100))
|
return Math.round(reference * (1 + (Number(offsetPercent) || 0) / 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** جمع مرجع — وقتی price صفر است profit نادیده گرفته میشود (مقدار قدیمی) */
|
||||||
|
export function referenceProviderTotal(entry?: Pick<MethodePrices, 'price' | 'profit'> | null): number {
|
||||||
|
if (!entry) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const price = Number(entry.price ?? 0)
|
||||||
|
if (price <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return price + Number(entry.profit ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveAppBasePrice(lastPrice?: Partial<last> & { price?: number } | null): number {
|
export function resolveAppBasePrice(lastPrice?: Partial<last> & { price?: number } | null): number {
|
||||||
if (!lastPrice) return 0
|
if (!lastPrice) return 0
|
||||||
|
|
||||||
@@ -118,7 +136,7 @@ export function computeProductEditorPricePreview(input: {
|
|||||||
: adjustedProductPrice(fallback, 0, offsetPercent)
|
: adjustedProductPrice(fallback, 0, offsetPercent)
|
||||||
return { status: 'ready', amount, source: 'آخرین قیمت ذخیرهشده' }
|
return { status: 'ready', amount, source: 'آخرین قیمت ذخیرهشده' }
|
||||||
}
|
}
|
||||||
return { status: 'empty', message: 'قیمت provider صفر است' }
|
return { status: 'empty', message: 'قیمت مرجع صفر است' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.form.reading_method) {
|
if (!input.form.reading_method) {
|
||||||
@@ -140,7 +158,7 @@ export function computeProductEditorPricePreview(input: {
|
|||||||
)
|
)
|
||||||
return fallback > 0
|
return fallback > 0
|
||||||
? { status: 'ready', amount: fallback, source: 'آخرین قیمت ذخیرهشده' }
|
? { status: 'ready', amount: fallback, source: 'آخرین قیمت ذخیرهشده' }
|
||||||
: { status: 'empty', message: 'قیمت provider صفر است' }
|
: { status: 'empty', message: 'قیمت مرجع صفر است' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.methodOption === '1') {
|
if (input.methodOption === '1') {
|
||||||
@@ -169,5 +187,5 @@ export function computeProductEditorPricePreview(input: {
|
|||||||
)
|
)
|
||||||
return fallback > 0
|
return fallback > 0
|
||||||
? { status: 'ready', amount: fallback, source: 'آخرین قیمت ذخیرهشده' }
|
? { status: 'ready', amount: fallback, source: 'آخرین قیمت ذخیرهشده' }
|
||||||
: { status: 'empty', message: 'قیمت provider صفر است' }
|
: { status: 'empty', message: 'قیمت مرجع صفر است' }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user