added bannig when amount is under 0
This commit is contained in:
@@ -64,7 +64,9 @@
|
|||||||
<h3 class="basic-action-sheet__title">{{ title }}</h3>
|
<h3 class="basic-action-sheet__title">{{ title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="basic-action-sheet__content">
|
<div class="basic-action-sheet__content">
|
||||||
<slot />
|
<a-spin :spinning="loading">
|
||||||
|
<slot />
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showButton && (showCancel || showOk)"
|
v-if="showButton && (showCancel || showOk)"
|
||||||
|
|||||||
+233
-37
@@ -13,10 +13,15 @@
|
|||||||
|
|
||||||
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL';
|
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL';
|
||||||
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL';
|
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL';
|
||||||
|
const CONFIG_CACHE_KEY = 'APP_CONFIG_CACHE';
|
||||||
|
const VERSION_STORAGE_KEY = 'version';
|
||||||
|
const VERSION_UPDATE_LOCK_KEY = 'version_update_lock';
|
||||||
|
const VERSION_LAST_RELOAD_KEY = 'version_last_reload';
|
||||||
|
const AUTH_TOKEN_KEY = 'peransa_token';
|
||||||
const VERSION_RETRY_ROUNDS = 5;
|
const VERSION_RETRY_ROUNDS = 5;
|
||||||
const CONFIG_RETRY_ROUNDS = 5;
|
const CONFIG_RETRY_ROUNDS = 5;
|
||||||
|
|
||||||
const CONFIG_TIMEOUT = 12000;
|
const CONFIG_TIMEOUT = 12000;
|
||||||
|
|
||||||
let configTimeoutId;
|
let configTimeoutId;
|
||||||
let mainScriptLoaded = false;
|
let mainScriptLoaded = false;
|
||||||
|
|
||||||
@@ -48,33 +53,175 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortVersionUrls(urls) {
|
function sortUrls(urls, successKey) {
|
||||||
const lastSuccessUrl = localStorage.getItem(VERSION_SUCCESS_URL_KEY);
|
const lastSuccessUrl = localStorage.getItem(successKey);
|
||||||
|
if (!lastSuccessUrl) return urls.slice();
|
||||||
if (!lastSuccessUrl) {
|
|
||||||
return urls.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [lastSuccessUrl].concat(urls.filter((url) => url !== lastSuccessUrl));
|
return [lastSuccessUrl].concat(urls.filter((url) => url !== lastSuccessUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortConfigUrls(urls) {
|
|
||||||
const lastSuccessUrl = localStorage.getItem(CONFIG_SUCCESS_URL_KEY);
|
|
||||||
|
|
||||||
return [...urls].sort((a, b) => {
|
|
||||||
if (a === lastSuccessUrl) return -1;
|
|
||||||
if (b === lastSuccessUrl) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearStaleSuccessUrl(key, url) {
|
function clearStaleSuccessUrl(key, url) {
|
||||||
if (localStorage.getItem(key) === url) {
|
if (localStorage.getItem(key) === url) {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchJson(url, timeout = 3500) {
|
function normalizeVersion(value) {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
return String(value).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readUserVersion(user) {
|
||||||
|
if (!user || typeof user !== 'object' || Array.isArray(user)) return '';
|
||||||
|
const version = user.version ?? user.value;
|
||||||
|
return version === undefined || version === null ? '' : String(version).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeVersionResponse(response) {
|
||||||
|
if (typeof response === 'string' || typeof response === 'number') {
|
||||||
|
return String(response).trim();
|
||||||
|
}
|
||||||
|
if (!response || typeof response !== 'object') return '';
|
||||||
|
|
||||||
|
if (typeof response.data === 'string' || typeof response.data === 'number') {
|
||||||
|
return String(response.data).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const directVersion =
|
||||||
|
response.version ??
|
||||||
|
response.value ??
|
||||||
|
(response.data && typeof response.data === 'object'
|
||||||
|
? response.data.version ?? response.data.value
|
||||||
|
: undefined);
|
||||||
|
|
||||||
|
if (directVersion !== undefined && directVersion !== null) {
|
||||||
|
return String(directVersion).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return readUserVersion(response.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCachedAppConfig() {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(CONFIG_CACHE_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
const payload = JSON.parse(raw);
|
||||||
|
if (!payload || typeof payload !== 'object' || !payload.config) return null;
|
||||||
|
return payload.config;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to read cached get_user:', error);
|
||||||
|
localStorage.removeItem(CONFIG_CACHE_KEY);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeCachedAppConfig(config) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
CONFIG_CACHE_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
cached_at: Date.now(),
|
||||||
|
config: config
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to write get_user cache:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStoredAppVersion() {
|
||||||
|
return localStorage.getItem(VERSION_STORAGE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeStoredAppVersion(version) {
|
||||||
|
localStorage.setItem(VERSION_STORAGE_KEY, normalizeVersion(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldUpdateForVersion(serverVersion) {
|
||||||
|
const normalized = normalizeVersion(serverVersion);
|
||||||
|
if (!normalized) return false;
|
||||||
|
|
||||||
|
const stored = getStoredAppVersion();
|
||||||
|
if (!stored) {
|
||||||
|
writeStoredAppVersion(normalized);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stored !== normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canStartVersionUpdate(serverVersion) {
|
||||||
|
const version = normalizeVersion(serverVersion);
|
||||||
|
if (!version) return false;
|
||||||
|
|
||||||
|
if (sessionStorage.getItem(VERSION_LAST_RELOAD_KEY) === version) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawLock = sessionStorage.getItem(VERSION_UPDATE_LOCK_KEY);
|
||||||
|
if (rawLock) {
|
||||||
|
try {
|
||||||
|
const lock = JSON.parse(rawLock);
|
||||||
|
const isSameVersion = lock.version === version;
|
||||||
|
const isFreshLock = Date.now() - lock.started_at < 60_000;
|
||||||
|
if (isSameVersion && isFreshLock) return false;
|
||||||
|
} catch {
|
||||||
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startVersionUpdateLock(serverVersion) {
|
||||||
|
sessionStorage.setItem(
|
||||||
|
VERSION_UPDATE_LOCK_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
version: normalizeVersion(serverVersion),
|
||||||
|
started_at: Date.now()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishVersionUpdateBeforeReload(serverVersion) {
|
||||||
|
const version = normalizeVersion(serverVersion);
|
||||||
|
writeStoredAppVersion(version);
|
||||||
|
sessionStorage.setItem(VERSION_LAST_RELOAD_KEY, version);
|
||||||
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelVersionUpdateLock() {
|
||||||
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preserveAuthToken() {
|
||||||
|
return localStorage.getItem(AUTH_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreAuthToken(token) {
|
||||||
|
if (token) {
|
||||||
|
localStorage.setItem(AUTH_TOKEN_KEY, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearBootstrapCachesKeepToken() {
|
||||||
|
const token = preserveAuthToken();
|
||||||
|
localStorage.removeItem(CONFIG_CACHE_KEY);
|
||||||
|
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY);
|
||||||
|
localStorage.removeItem(VERSION_SUCCESS_URL_KEY);
|
||||||
|
restoreAuthToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hardReloadApp() {
|
||||||
|
const refreshUrl =
|
||||||
|
window.location.origin +
|
||||||
|
window.location.pathname +
|
||||||
|
'?__app_refresh=' +
|
||||||
|
Date.now() +
|
||||||
|
(window.location.hash || '');
|
||||||
|
window.location.replace(refreshUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJson(url, timeout) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = window.setTimeout(() => controller.abort(), timeout);
|
const timeoutId = window.setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
@@ -105,8 +252,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadVersion(rounds = VERSION_RETRY_ROUNDS) {
|
async function loadVersion(rounds) {
|
||||||
const urls = sortVersionUrls(getVersionUrls());
|
rounds = rounds || VERSION_RETRY_ROUNDS;
|
||||||
|
const urls = sortUrls(getVersionUrls(), VERSION_SUCCESS_URL_KEY);
|
||||||
|
|
||||||
for (let round = 1; round <= rounds; round++) {
|
for (let round = 1; round <= rounds; round++) {
|
||||||
for (let index = 0; index < urls.length; index++) {
|
for (let index = 0; index < urls.length; index++) {
|
||||||
@@ -114,10 +262,14 @@
|
|||||||
const timeout = index === urls.length - 1 ? 6000 : 3500;
|
const timeout = index === urls.length - 1 ? 6000 : 3500;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchJson(url, timeout);
|
const data = await fetchJson(url, timeout);
|
||||||
|
const version = normalizeVersionResponse(data);
|
||||||
|
if (!version) {
|
||||||
|
throw new Error('Version response is empty');
|
||||||
|
}
|
||||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url);
|
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url);
|
||||||
console.log('Version loaded from:', url);
|
console.log('Version loaded from:', url, version);
|
||||||
return;
|
return version;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clearStaleSuccessUrl(VERSION_SUCCESS_URL_KEY, url);
|
clearStaleSuccessUrl(VERSION_SUCCESS_URL_KEY, url);
|
||||||
console.warn('Version failed from', url, 'round:', round, error);
|
console.warn('Version failed from', url, 'round:', round, error);
|
||||||
@@ -126,10 +278,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.warn('Version check failed from all endpoints');
|
console.warn('Version check failed from all endpoints');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchConfig(rounds = CONFIG_RETRY_ROUNDS) {
|
async function fetchConfig(rounds) {
|
||||||
const urls = sortConfigUrls(getConfigUrls());
|
rounds = rounds || CONFIG_RETRY_ROUNDS;
|
||||||
|
const urls = sortUrls(getConfigUrls(), CONFIG_SUCCESS_URL_KEY);
|
||||||
|
|
||||||
for (let round = 1; round <= rounds; round++) {
|
for (let round = 1; round <= rounds; round++) {
|
||||||
for (let index = 0; index < urls.length; index++) {
|
for (let index = 0; index < urls.length; index++) {
|
||||||
@@ -144,7 +298,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url);
|
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url);
|
||||||
return { config, url };
|
writeCachedAppConfig(config);
|
||||||
|
return { config: config, url: url };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clearStaleSuccessUrl(CONFIG_SUCCESS_URL_KEY, url);
|
clearStaleSuccessUrl(CONFIG_SUCCESS_URL_KEY, url);
|
||||||
console.warn('Config failed from', url, 'round:', round, error);
|
console.warn('Config failed from', url, 'round:', round, error);
|
||||||
@@ -156,14 +311,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyConfig(config) {
|
function applyConfig(config) {
|
||||||
window.__APP_CONFIG__ = config;
|
window.__APP_CONFIG__ = config || {};
|
||||||
window.__CONFIG_READY__ = true;
|
window.__CONFIG_READY__ = true;
|
||||||
|
|
||||||
if (config?.user?.name_fa) {
|
if (config && config.user && config.user.name_fa) {
|
||||||
document.title = 'پنل مدیریت ' + '-' + config.user.name_fa;
|
document.title = 'پنل مدیریت ' + '-' + config.user.name_fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config?.user?.avatar) {
|
if (config && config.user && config.user.avatar) {
|
||||||
const faviconUrl = config.user.avatar;
|
const faviconUrl = config.user.avatar;
|
||||||
const existingIcons = document.querySelectorAll('link[rel*="icon"]');
|
const existingIcons = document.querySelectorAll('link[rel*="icon"]');
|
||||||
existingIcons.forEach((icon) => icon.remove());
|
existingIcons.forEach((icon) => icon.remove());
|
||||||
@@ -195,19 +350,59 @@
|
|||||||
loadMainScript();
|
loadMainScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function applyVersionUpdate(serverVersion) {
|
||||||
|
startVersionUpdateLock(serverVersion);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { config } = await fetchConfig();
|
||||||
|
clearBootstrapCachesKeepToken();
|
||||||
|
writeCachedAppConfig(config);
|
||||||
|
applyConfig(config);
|
||||||
|
finishVersionUpdateBeforeReload(serverVersion);
|
||||||
|
console.log('App version changed — hard refresh', serverVersion);
|
||||||
|
hardReloadApp();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Version update failed:', error);
|
||||||
|
cancelVersionUpdateLock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
try {
|
try {
|
||||||
await loadVersion();
|
// ۱) کش get_user — اگر بود سریع اعمال شود
|
||||||
|
const cachedConfig = readCachedAppConfig();
|
||||||
|
if (cachedConfig) {
|
||||||
|
applyConfig(cachedConfig);
|
||||||
|
console.log('Applied cached get_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۲) get_version — اگر تغییر کرده hard refresh
|
||||||
|
const remoteVersion = await loadVersion();
|
||||||
|
if (remoteVersion && shouldUpdateForVersion(remoteVersion) && canStartVersionUpdate(remoteVersion)) {
|
||||||
|
const updating = await applyVersionUpdate(remoteVersion);
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۳) تازه کردن get_user و کش
|
||||||
const { config, url } = await fetchConfig();
|
const { config, url } = await fetchConfig();
|
||||||
applyConfig(config);
|
applyConfig(config);
|
||||||
|
|
||||||
console.log('Config loaded successfully from', url, config);
|
console.log('Config loaded successfully from', url, config);
|
||||||
finishConfigLoad(config);
|
finishConfigLoad(config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Config loader: Could not fetch config from any endpoint');
|
console.warn('Config loader: Could not fetch config from any endpoint');
|
||||||
console.warn('Error details:', error.message);
|
console.warn('Error details:', error && error.message ? error.message : error);
|
||||||
console.warn('App will continue with empty config.');
|
console.warn('App will continue with empty/cached config.');
|
||||||
|
|
||||||
|
const cachedConfig = readCachedAppConfig();
|
||||||
|
if (cachedConfig) {
|
||||||
|
applyConfig(cachedConfig);
|
||||||
|
finishConfigLoad(cachedConfig, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.__APP_CONFIG__ = {};
|
window.__APP_CONFIG__ = {};
|
||||||
window.__CONFIG_READY__ = true;
|
window.__CONFIG_READY__ = true;
|
||||||
@@ -229,12 +424,13 @@
|
|||||||
|
|
||||||
configTimeoutId = setTimeout(function() {
|
configTimeoutId = setTimeout(function() {
|
||||||
if (!mainScriptLoaded) {
|
if (!mainScriptLoaded) {
|
||||||
console.warn('Config loader: Timeout reached. Loading main.ts with empty config.');
|
console.warn('Config loader: Timeout reached. Loading main.ts with empty/cached config.');
|
||||||
window.__APP_CONFIG__ = window.__APP_CONFIG__ || {};
|
const cachedConfig = readCachedAppConfig();
|
||||||
|
window.__APP_CONFIG__ = cachedConfig || window.__APP_CONFIG__ || {};
|
||||||
window.__CONFIG_READY__ = true;
|
window.__CONFIG_READY__ = true;
|
||||||
|
|
||||||
if (configResolve) {
|
if (configResolve) {
|
||||||
configResolve({});
|
configResolve(window.__APP_CONFIG__);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMainScript();
|
loadMainScript();
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
id: 'features',
|
id: 'features',
|
||||||
title: 'قابلیتهای سیستم',
|
title: 'قابلیتهای سیستم',
|
||||||
icon: 'vuesax-linear:setting-2',
|
icon: 'vuesax-linear:setting-2',
|
||||||
match: (k) => /^(product_|main_wallet_products|profit_gold)/.test(k)
|
match: (k) => /^(product_|main_wallet_products|require_zero_main_gold_before_other_buy|profit_gold)/.test(k)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'misc',
|
id: 'misc',
|
||||||
@@ -119,7 +119,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [
|
|||||||
title: 'عمومی و برند',
|
title: 'عمومی و برند',
|
||||||
icon: 'vuesax-linear:home-2',
|
icon: 'vuesax-linear:home-2',
|
||||||
match: (k) =>
|
match: (k) =>
|
||||||
/^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel)/.test(
|
/^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel|main_wallet_products|require_zero_main_gold_before_other_buy)/.test(
|
||||||
k
|
k
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ const KEY_TYPE_FALLBACK: Record<string, SettingFieldType> = {
|
|||||||
text_about: 'longtext',
|
text_about: 'longtext',
|
||||||
fee_service_charge_percentage: 'number',
|
fee_service_charge_percentage: 'number',
|
||||||
fee_deduct_from_gold: 'switch',
|
fee_deduct_from_gold: 'switch',
|
||||||
|
require_zero_main_gold_before_other_buy: 'switch',
|
||||||
fee_single: 'price',
|
fee_single: 'price',
|
||||||
buy_fee: 'price',
|
buy_fee: 'price',
|
||||||
sell_fee: 'price',
|
sell_fee: 'price',
|
||||||
@@ -550,6 +551,8 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
|||||||
'درصد کارمزد هر معامله از مبلغ سفارش (اعشاری؛ مثلاً ۰٫۰۰۵ یعنی نیم در هزار).',
|
'درصد کارمزد هر معامله از مبلغ سفارش (اعشاری؛ مثلاً ۰٫۰۰۵ یعنی نیم در هزار).',
|
||||||
fee_deduct_from_gold:
|
fee_deduct_from_gold:
|
||||||
'این کلید دیگر روی محاسبه اثری ندارد. مبنای کارمزد اکنون فیلدی است که مشتری وارد میکند: اگر «مبلغ ریالی» را وارد کند (مثلاً دقیقاً ۱۰۰ میلیون ریال) همان مبلغ معامله میشود و کارمزد از طلا کسر میشود؛ اگر «وزن» را وارد کند (مثلاً دقیقاً ۲ گرم) همان وزن معامله میشود و کارمزد از ریال کسر میشود. این قانون برای خرید و فروش یکسان است.',
|
'این کلید دیگر روی محاسبه اثری ندارد. مبنای کارمزد اکنون فیلدی است که مشتری وارد میکند: اگر «مبلغ ریالی» را وارد کند (مثلاً دقیقاً ۱۰۰ میلیون ریال) همان مبلغ معامله میشود و کارمزد از طلا کسر میشود؛ اگر «وزن» را وارد کند (مثلاً دقیقاً ۲ گرم) همان وزن معامله میشود و کارمزد از ریال کسر میشود. این قانون برای خرید و فروش یکسان است.',
|
||||||
|
require_zero_main_gold_before_other_buy:
|
||||||
|
'روشن: اگر مانده طلای کیف اصلی صفر یا زیر صفر باشد (بدهی)، مشتری ابتدا باید آبشده/مانده کیف اصلی را صفر کند و بعد میتواند محصول دیگر بخرد. خاموش: بدون این محدودیت. معامله خودِ محصولات کیف اصلی همیشه مجاز است.',
|
||||||
fee_free:
|
fee_free:
|
||||||
'تعداد اولین سفارشهای هر کاربر که از کارمزد معاف میشوند. صفر یعنی غیرفعال.',
|
'تعداد اولین سفارشهای هر کاربر که از کارمزد معاف میشوند. صفر یعنی غیرفعال.',
|
||||||
min_withdraw:
|
min_withdraw:
|
||||||
@@ -778,6 +781,7 @@ export const KEY_LABEL_FALLBACK: Record<string, string> = {
|
|||||||
sell_fee: 'کسر قیمت فروش',
|
sell_fee: 'کسر قیمت فروش',
|
||||||
fee_service_charge_percentage: 'درصد کارمزد معامله',
|
fee_service_charge_percentage: 'درصد کارمزد معامله',
|
||||||
fee_deduct_from_gold: 'اعمال مبلغ دقیق در معامله مشتری',
|
fee_deduct_from_gold: 'اعمال مبلغ دقیق در معامله مشتری',
|
||||||
|
require_zero_main_gold_before_other_buy: 'الزام صفر بودن کیف اصلی قبل از خرید محصول دیگر',
|
||||||
fee_free: 'معافیت کارمزد سفارش اول',
|
fee_free: 'معافیت کارمزد سفارش اول',
|
||||||
min_withdraw: 'حداقل مبلغ برداشت',
|
min_withdraw: 'حداقل مبلغ برداشت',
|
||||||
min_deposit: 'حداقل مبلغ واریز کیف پول',
|
min_deposit: 'حداقل مبلغ واریز کیف پول',
|
||||||
@@ -860,6 +864,7 @@ export const SETTING_LOCKED_KEYS = new Set([
|
|||||||
'sms_panel',
|
'sms_panel',
|
||||||
'logo_login', 'logo_register', 'title_pwa', 'title_fav',
|
'logo_login', 'logo_register', 'title_pwa', 'title_fav',
|
||||||
'main_product', 'main_wallet_products', 'metal_type', 'main_metal',
|
'main_product', 'main_wallet_products', 'metal_type', 'main_metal',
|
||||||
|
'require_zero_main_gold_before_other_buy',
|
||||||
'montify_name',
|
'montify_name',
|
||||||
'montzar_base_url', 'montzar_closed',
|
'montzar_base_url', 'montzar_closed',
|
||||||
'montzar_diff', 'montzar_last_product_log', 'montzar_live_price', 'montzar_mazaneh',
|
'montzar_diff', 'montzar_last_product_log', 'montzar_live_price', 'montzar_mazaneh',
|
||||||
|
|||||||
@@ -293,73 +293,88 @@ async function setProductIndexImage(record: Record<string, any>, productId?: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function edit(record: Product) {
|
async function edit(record: Product) {
|
||||||
await loadCategorySelectItems()
|
submitError.value = ''
|
||||||
addUserVisible.value = true
|
|
||||||
editId = record.id
|
editId = record.id
|
||||||
let productData = cloneDeep(record) as Record<string, any>
|
addUserVisible.value = true
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const detail = unwrapApiRecord(await getCustomerById(record.id))
|
let productData = cloneDeep(record) as Record<string, any>
|
||||||
if (detail) productData = { ...productData, ...detail }
|
|
||||||
} catch {
|
|
||||||
/* use table row data */
|
|
||||||
}
|
|
||||||
|
|
||||||
methodOption.value = productData.app_price
|
const [, detailResult] = await Promise.all([
|
||||||
? '3'
|
loadCategorySelectItems(),
|
||||||
: (productData.reading_method ? '2' : '1')
|
getCustomerById(record.id)
|
||||||
meltedPriceOption.value = resolveMeltedPriceSource(productData)
|
.then((res) => unwrapApiRecord(res))
|
||||||
if (usesMeltedPriceEditor(productData)) {
|
.catch(() => null),
|
||||||
methodOption.value = '1'
|
refreshLivePrices(),
|
||||||
if (
|
])
|
||||||
!isMeltedTradeGroup(productData.group_type)
|
|
||||||
&& !productData.reading_method
|
if (detailResult) {
|
||||||
&& Number(productData.direct_price) !== 1
|
productData = { ...productData, ...detailResult }
|
||||||
&& Number(productData.price) > 0
|
|
||||||
) {
|
|
||||||
meltedPriceOption.value = 'fixed'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
methodOption.value = productData.app_price
|
||||||
|
? '3'
|
||||||
|
: (productData.reading_method ? '2' : '1')
|
||||||
|
meltedPriceOption.value = resolveMeltedPriceSource(productData)
|
||||||
|
if (usesMeltedPriceEditor(productData)) {
|
||||||
|
methodOption.value = '1'
|
||||||
|
if (
|
||||||
|
!isMeltedTradeGroup(productData.group_type)
|
||||||
|
&& !productData.reading_method
|
||||||
|
&& Number(productData.direct_price) !== 1
|
||||||
|
&& Number(productData.price) > 0
|
||||||
|
) {
|
||||||
|
meltedPriceOption.value = 'fixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
weightOption.value = Number(productData.amount) > 0 ? '1' : '2'
|
||||||
|
if (weightOption.value === '2' && !isMeltedTradeGroup(productData.group_type)) {
|
||||||
|
productData.unit = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = productData.auto_open_at
|
||||||
|
? dayjs(productData.auto_open_at, 'HH:mm:ss')
|
||||||
|
: dayjs('00:00:00', 'HH:mm:ss')
|
||||||
|
const close = productData.auto_close_at
|
||||||
|
? dayjs(productData.auto_close_at, 'HH:mm:ss')
|
||||||
|
: dayjs('00:00:00', 'HH:mm:ss')
|
||||||
|
|
||||||
|
form.value = normalizeProductOrderLimitForm({
|
||||||
|
...productData,
|
||||||
|
show_in_assets: productData.show_in_assets ?? false,
|
||||||
|
trade_by_quantity: productData.trade_by_quantity ?? false,
|
||||||
|
app_price: Boolean(productData.app_price),
|
||||||
|
override_trading_hours: productData.override_trading_hours ?? false,
|
||||||
|
has_physical_delivery: productData.has_physical_delivery ?? false,
|
||||||
|
min_physical_delivery_quantity:
|
||||||
|
productData.min_physical_delivery_quantity ??
|
||||||
|
(productData as { min_physical_delivery_grams?: number | null }).min_physical_delivery_grams ??
|
||||||
|
undefined,
|
||||||
|
cash_fee_buy: parseCashFeeValue(productData.cash_fee_buy),
|
||||||
|
cash_fee_sell: parseCashFeeValue(productData.cash_fee_sell),
|
||||||
|
cash_fee_buy_is_percent: normalizeCashFeeFlags(productData.cash_fee_buy_is_percent),
|
||||||
|
cash_fee_sell_is_percent: normalizeCashFeeFlags(productData.cash_fee_sell_is_percent),
|
||||||
|
price_offset_percent: productData.price_offset_percent ?? 0,
|
||||||
|
direct_price: productData.direct_price ?? 0,
|
||||||
|
auto_open_at: start.isValid() ? start : dayjs('00:00:00', 'HH:mm:ss'),
|
||||||
|
auto_close_at: close.isValid() ? close : dayjs('00:00:00', 'HH:mm:ss')
|
||||||
|
})
|
||||||
|
|
||||||
|
await setProductIndexImage(productData, record.id)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
message.error('خطا در دریافت اطلاعات محصول')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
weightOption.value = Number(productData.amount) > 0 ? '1' : '2'
|
|
||||||
if (weightOption.value === '2' && !isMeltedTradeGroup(productData.group_type)) {
|
|
||||||
productData.unit = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = productData.auto_open_at
|
|
||||||
? dayjs(productData.auto_open_at, 'HH:mm:ss')
|
|
||||||
: dayjs('00:00:00', 'HH:mm:ss')
|
|
||||||
const close = productData.auto_close_at
|
|
||||||
? dayjs(productData.auto_close_at, 'HH:mm:ss')
|
|
||||||
: dayjs('00:00:00', 'HH:mm:ss')
|
|
||||||
|
|
||||||
form.value = normalizeProductOrderLimitForm({
|
|
||||||
...productData,
|
|
||||||
show_in_assets: productData.show_in_assets ?? false,
|
|
||||||
trade_by_quantity: productData.trade_by_quantity ?? false,
|
|
||||||
app_price: Boolean(productData.app_price),
|
|
||||||
override_trading_hours: productData.override_trading_hours ?? false,
|
|
||||||
has_physical_delivery: productData.has_physical_delivery ?? false,
|
|
||||||
min_physical_delivery_quantity:
|
|
||||||
productData.min_physical_delivery_quantity ??
|
|
||||||
(productData as { min_physical_delivery_grams?: number | null }).min_physical_delivery_grams ??
|
|
||||||
undefined,
|
|
||||||
cash_fee_buy: parseCashFeeValue(productData.cash_fee_buy),
|
|
||||||
cash_fee_sell: parseCashFeeValue(productData.cash_fee_sell),
|
|
||||||
cash_fee_buy_is_percent: normalizeCashFeeFlags(productData.cash_fee_buy_is_percent),
|
|
||||||
cash_fee_sell_is_percent: normalizeCashFeeFlags(productData.cash_fee_sell_is_percent),
|
|
||||||
price_offset_percent: productData.price_offset_percent ?? 0,
|
|
||||||
direct_price: productData.direct_price ?? 0,
|
|
||||||
auto_open_at: start.isValid() ? start : dayjs('00:00:00', 'HH:mm:ss'),
|
|
||||||
auto_close_at: close.isValid() ? close : dayjs('00:00:00', 'HH:mm:ss')
|
|
||||||
})
|
|
||||||
|
|
||||||
await setProductIndexImage(productData, record.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeProductModal() {
|
function closeProductModal() {
|
||||||
addUserVisible.value = false
|
addUserVisible.value = false
|
||||||
editId = undefined
|
editId = undefined
|
||||||
submitError.value = ''
|
submitError.value = ''
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add() {
|
async function add() {
|
||||||
@@ -367,7 +382,12 @@ async function add() {
|
|||||||
submitError.value = ''
|
submitError.value = ''
|
||||||
addUserVisible.value = true
|
addUserVisible.value = true
|
||||||
resetData()
|
resetData()
|
||||||
await loadCategorySelectItems()
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await Promise.all([loadCategorySelectItems(), refreshLivePrices()])
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetData() {
|
function resetData() {
|
||||||
|
|||||||
Reference in New Issue
Block a user