feat(tahesab): import and refresh customers at chosen hours
Each connection can set how many times a day and at which Tehran hours the server pulls Tahesab customers, including name updates for people already linked. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import type {
|
||||
UpdateTahesabConnectionRequest,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
import { formatJalaliDateTime, toFaDigits } from '@/lib/jalali';
|
||||
import {
|
||||
UiBadge,
|
||||
UiBusyOverlay,
|
||||
@@ -74,6 +74,77 @@ const syncTablePageSize = ref(50);
|
||||
const syncTablePage = ref(1);
|
||||
|
||||
const syncOnlyNew = ref(false);
|
||||
const autoCount = ref('0');
|
||||
const autoHours = ref<string[]>([]);
|
||||
const autoSyncSaving = ref(false);
|
||||
const AUTO_HOUR_PRESETS: Record<number, number[]> = {
|
||||
1: [8],
|
||||
2: [8, 14],
|
||||
3: [8, 12, 16],
|
||||
4: [8, 12, 16, 20],
|
||||
5: [8, 11, 14, 17, 20],
|
||||
6: [8, 10, 12, 14, 16, 20],
|
||||
};
|
||||
|
||||
const selectedConnection = computed(() =>
|
||||
items.value.find((row) => row.id === sync.connectionId),
|
||||
);
|
||||
|
||||
function hourLabel(hour: number) {
|
||||
return `${toFaDigits(String(hour).padStart(2, '0'))}:۰۰`;
|
||||
}
|
||||
|
||||
function fillAutoSync() {
|
||||
const hours = [...(selectedConnection.value?.customerSyncHours ?? [])]
|
||||
.sort((a, b) => a - b)
|
||||
.slice(0, 6);
|
||||
autoCount.value = String(hours.length);
|
||||
autoHours.value = hours.map(String);
|
||||
}
|
||||
|
||||
function onAutoCount(value: string) {
|
||||
const count = Math.min(6, Math.max(0, Number(value) || 0));
|
||||
const preset = AUTO_HOUR_PRESETS[count] ?? [];
|
||||
const next = autoHours.value.slice(0, count);
|
||||
while (next.length < count) {
|
||||
next.push(String(preset[next.length] ?? 8));
|
||||
}
|
||||
autoCount.value = String(count);
|
||||
autoHours.value = next;
|
||||
}
|
||||
|
||||
function setAutoHour(index: number, value: string) {
|
||||
autoHours.value[index] = value;
|
||||
}
|
||||
|
||||
async function saveAutoSync() {
|
||||
if (!sync.connectionId) return;
|
||||
const hours = autoHours.value.map((hour) => Number(hour));
|
||||
if (hours.some((hour) => !Number.isInteger(hour) || hour < 0 || hour > 23)) {
|
||||
error.value = 'ساعت باید بین ۰ و ۲۳ باشد.';
|
||||
return;
|
||||
}
|
||||
if (new Set(hours).size !== hours.length) {
|
||||
error.value = 'ساعتها نباید تکراری باشند.';
|
||||
return;
|
||||
}
|
||||
autoSyncSaving.value = true;
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
try {
|
||||
await api.patch(`/integrations/tahesab/connections/${sync.connectionId}`, {
|
||||
customerSyncHours: hours,
|
||||
});
|
||||
ok.value = hours.length
|
||||
? 'زمانبندی بروزرسانی ذخیره شد.'
|
||||
: 'بروزرسانی خودکار خاموش شد.';
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = apiErr(e, 'ذخیره زمانبندی ناموفق بود');
|
||||
} finally {
|
||||
autoSyncSaving.value = false;
|
||||
}
|
||||
}
|
||||
const syncVisibleResults = computed(() =>
|
||||
syncOnlyNew.value
|
||||
? syncResults.value.filter((r) => !r.alreadyLinked)
|
||||
@@ -86,6 +157,7 @@ const syncPagedResults = computed(() => {
|
||||
const start = (syncTablePage.value - 1) * syncTablePageSize.value;
|
||||
return syncVisibleResults.value.slice(start, start + syncTablePageSize.value);
|
||||
});
|
||||
watch(() => sync.connectionId, () => fillAutoSync());
|
||||
watch([syncOnlyNew, syncTablePageSize], () => {
|
||||
syncTablePage.value = 1;
|
||||
});
|
||||
@@ -178,6 +250,7 @@ async function load() {
|
||||
if (!editingId.value) form.isDefault = data.length === 0;
|
||||
if (!sync.connectionId && data[0]) sync.connectionId = data[0].id;
|
||||
if (!productSync.connectionId && data[0]) productSync.connectionId = data[0].id;
|
||||
fillAutoSync();
|
||||
} catch (e) {
|
||||
error.value = axios.isAxiosError(e)
|
||||
? String(
|
||||
@@ -875,6 +948,63 @@ onMounted(load);
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
|
||||
<div class="sync-sub-section">
|
||||
<h4 class="sync-subhead">بروزرسانی خودکار</h4>
|
||||
<p class="sync-sub-desc">
|
||||
تعداد دفعات در روز و ساعت هر بار را برای همین اتصال مشخص کنید. سرور در آن ساعتها، به وقت تهران،
|
||||
مشتریان تازه را وارد میکند و نام مشتریهای قبلی را هم با تهحساب تازه میکند. اگر شماره یکی باشد
|
||||
همان مشتری میماند، نام قبلی حفظ میشود و شماره به شکل ۰۹۱۲ ذخیره میشود. خاموش یعنی فقط ورود دستی.
|
||||
</p>
|
||||
<UiField label="تعداد در روز">
|
||||
<UiSelect
|
||||
:model-value="autoCount"
|
||||
:disabled="!sync.connectionId || autoSyncSaving"
|
||||
@update:model-value="onAutoCount"
|
||||
>
|
||||
<option value="0">خاموش</option>
|
||||
<option v-for="n in 6" :key="n" :value="String(n)">
|
||||
{{ toFaDigits(n) }} بار
|
||||
</option>
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
<div v-if="Number(autoCount) > 0" class="crm-form-grid-3">
|
||||
<UiField
|
||||
v-for="(_, index) in autoHours"
|
||||
:key="index"
|
||||
:label="`ساعت ${toFaDigits(index + 1)}`"
|
||||
>
|
||||
<UiSelect
|
||||
:model-value="autoHours[index]"
|
||||
:disabled="autoSyncSaving"
|
||||
@update:model-value="setAutoHour(index, $event)"
|
||||
>
|
||||
<option v-for="hour in 24" :key="hour - 1" :value="String(hour - 1)">
|
||||
{{ hourLabel(hour - 1) }}
|
||||
</option>
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
</div>
|
||||
<div class="sync-buttons">
|
||||
<UiButton
|
||||
:loading="autoSyncSaving"
|
||||
:disabled="!sync.connectionId"
|
||||
@click="saveAutoSync"
|
||||
>
|
||||
ذخیره زمانبندی
|
||||
</UiButton>
|
||||
</div>
|
||||
<p v-if="selectedConnection?.customerSyncLastRunAt" class="sync-sub-desc">
|
||||
آخرین اجرا:
|
||||
{{ formatJalaliDateTime(new Date(selectedConnection.customerSyncLastRunAt)) }}
|
||||
<template v-if="selectedConnection.customerSyncLastSummary">
|
||||
— {{ toFaDigits(selectedConnection.customerSyncLastSummary) }}
|
||||
</template>
|
||||
</p>
|
||||
<p v-if="selectedConnection?.customerSyncLastError" class="sync-sub-desc">
|
||||
آخرین خطا: {{ selectedConnection.customerSyncLastError }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="sync-sub-section">
|
||||
<h4 class="sync-subhead">۱) وارد کردن دستهای با بازه کد مشتری</h4>
|
||||
<p class="sync-sub-desc">
|
||||
|
||||
Reference in New Issue
Block a user