Adding a task generated from the account balance to products
This commit is contained in:
@@ -23,6 +23,7 @@ import type {
|
||||
} from '@mento/shared';
|
||||
|
||||
const CATEGORIES = [
|
||||
'کارساخته',
|
||||
'طلا و جواهر',
|
||||
'سکه',
|
||||
'خدمات',
|
||||
|
||||
@@ -56,12 +56,35 @@ type SyncResult = {
|
||||
};
|
||||
const syncResults = ref<SyncResult[]>([]);
|
||||
|
||||
const productSync = reactive({
|
||||
connectionId: '',
|
||||
q: '',
|
||||
});
|
||||
const productSyncBusy = ref(false);
|
||||
type ProductSyncResult = {
|
||||
name: string;
|
||||
ayar: number | null;
|
||||
vaznSabet: number | null;
|
||||
lastSanad: string;
|
||||
alreadyImported?: boolean;
|
||||
productId?: string | null;
|
||||
productName?: string | null;
|
||||
};
|
||||
const productSyncResults = ref<ProductSyncResult[]>([]);
|
||||
const productCategoryLabel = ref('کارساخته');
|
||||
|
||||
function pendingImportCodes() {
|
||||
return syncResults.value
|
||||
.filter((r) => r.moshtariCode != null && !r.alreadyLinked)
|
||||
.map((r) => r.moshtariCode as number);
|
||||
}
|
||||
|
||||
function pendingProductNames() {
|
||||
return productSyncResults.value
|
||||
.filter((r) => !r.alreadyImported)
|
||||
.map((r) => r.name);
|
||||
}
|
||||
|
||||
function apiErr(e: unknown, fallback: string) {
|
||||
if (!axios.isAxiosError(e)) return fallback;
|
||||
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
||||
@@ -117,6 +140,7 @@ async function load() {
|
||||
items.value = data;
|
||||
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;
|
||||
} catch (e) {
|
||||
error.value = axios.isAxiosError(e)
|
||||
? String(
|
||||
@@ -381,6 +405,107 @@ async function importBulk(mode: 'results' | 'range') {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchProducts() {
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
if (!productSync.connectionId) {
|
||||
error.value = 'ابتدا اتصال را برای کارساخته انتخاب کنید.';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
productSyncBusy.value = true;
|
||||
const body: Record<string, unknown> = {
|
||||
connectionId: productSync.connectionId,
|
||||
};
|
||||
if (productSync.q.trim()) body.q = productSync.q.trim();
|
||||
const { data } = await api.post<{
|
||||
results: ProductSyncResult[];
|
||||
categoryLabel?: string;
|
||||
}>('/integrations/tahesab/sync/products/search', body, { timeout: 60_000 });
|
||||
productSyncResults.value = data.results ?? [];
|
||||
if (data.categoryLabel) productCategoryLabel.value = data.categoryLabel;
|
||||
if (!productSyncResults.value.length) {
|
||||
ok.value = 'کارساختهای یافت نشد.';
|
||||
} else {
|
||||
const linked = productSyncResults.value.filter((r) => r.alreadyImported).length;
|
||||
ok.value =
|
||||
`${productSyncResults.value.length} ${productCategoryLabel.value} پیدا شد` +
|
||||
(linked ? ` (${linked} قبلاً وارد شده)` : '');
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = apiErr(e, 'دریافت لیست کارساخته ناموفق');
|
||||
} finally {
|
||||
productSyncBusy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function importProductOne(name: string) {
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
try {
|
||||
productSyncBusy.value = true;
|
||||
const { data } = await api.post<{ productId: string }>(
|
||||
'/integrations/tahesab/sync/products/import',
|
||||
{ connectionId: productSync.connectionId, name },
|
||||
);
|
||||
ok.value = `وارد شد — در محصولات با شناسه ${data.productId}`;
|
||||
const row = productSyncResults.value.find((r) => r.name === name);
|
||||
if (row) {
|
||||
row.alreadyImported = true;
|
||||
row.productId = data.productId;
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = apiErr(e, 'وارد کردن محصول ناموفق');
|
||||
} finally {
|
||||
productSyncBusy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function importProductsBulk(mode: 'pending' | 'all') {
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
if (!productSync.connectionId) {
|
||||
error.value = 'ابتدا اتصال را انتخاب کنید.';
|
||||
return;
|
||||
}
|
||||
const body: Record<string, unknown> = { connectionId: productSync.connectionId };
|
||||
if (mode === 'pending') {
|
||||
const names = pendingProductNames();
|
||||
if (!names.length) {
|
||||
error.value = `${productCategoryLabel.value} جدیدی برای وارد کردن نیست.`;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!confirm(
|
||||
`${names.length} ${productCategoryLabel.value} بهعنوان محصول در ${brand.nameFa} ساخته میشود. ادامه؟`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
body.names = names;
|
||||
} else if (
|
||||
!confirm(
|
||||
`همهٔ نامهای ${productCategoryLabel.value} از تهحساب وارد محصولات ${brand.nameFa} میشوند. ادامه؟`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
productSyncBusy.value = true;
|
||||
const { data } = await api.post<{
|
||||
imported: number;
|
||||
skipped: number;
|
||||
failed: number;
|
||||
}>('/integrations/tahesab/sync/products/import-bulk', body, { timeout: 120_000 });
|
||||
ok.value = `وارد شد: ${data.imported} · قبلاً بود: ${data.skipped} · ناموفق: ${data.failed}`;
|
||||
await searchProducts();
|
||||
} catch (e) {
|
||||
error.value = apiErr(e, 'وارد کردن دستهای محصولات ناموفق');
|
||||
} finally {
|
||||
productSyncBusy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
@@ -636,6 +761,106 @@ onMounted(load);
|
||||
</div>
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<UiCard title="وارد کردن کارساخته از تهحساب به محصولات" class="section-card">
|
||||
<p class="sync-desc">
|
||||
نامهای کارساختهٔ تهحساب (لیست <span class="font-mono" dir="ltr">DoListNameKarsakhte</span>)
|
||||
را بهعنوان محصول با دستهٔ «{{ productCategoryLabel }}» وارد کنید.
|
||||
موجودی همچنان از تهحساب است؛ اینجا فقط کاتالوگ برای معامله/فاکتور ساخته میشود.
|
||||
</p>
|
||||
|
||||
<div class="crm-form">
|
||||
<UiField label="اتصال مورد نظر" required>
|
||||
<UiSelect v-model="productSync.connectionId">
|
||||
<option disabled value="">انتخاب اتصال</option>
|
||||
<option v-for="row in items" :key="row.id" :value="row.id">
|
||||
{{ row.label }} ({{ row.dbName }})
|
||||
</option>
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
|
||||
<div class="sync-sub-section">
|
||||
<h4 class="sync-subhead">لیست و جستجو</h4>
|
||||
<div class="crm-form-grid-2">
|
||||
<UiInput
|
||||
v-model="productSync.q"
|
||||
placeholder="جستجو نام یا عیار…"
|
||||
@keydown.enter.prevent="searchProducts"
|
||||
/>
|
||||
<div class="sync-buttons" style="margin-top: 0">
|
||||
<UiButton
|
||||
variant="secondary"
|
||||
:disabled="!productSync.connectionId || productSyncBusy"
|
||||
@click="searchProducts"
|
||||
>
|
||||
دریافت لیست
|
||||
</UiButton>
|
||||
<UiButton
|
||||
:disabled="!productSync.connectionId || productSyncBusy || !pendingProductNames().length"
|
||||
@click="importProductsBulk('pending')"
|
||||
>
|
||||
ورود نتایج جدید ({{ pendingProductNames().length }})
|
||||
</UiButton>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
:disabled="!productSync.connectionId || productSyncBusy"
|
||||
@click="importProductsBulk('all')"
|
||||
>
|
||||
ورود همه
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="productSyncResults.length" class="sync-results-box">
|
||||
<h4 class="sync-results-title">
|
||||
نتایج ({{ productSyncResults.length }}) — دسته: {{ productCategoryLabel }}
|
||||
</h4>
|
||||
<div class="crm-table-container">
|
||||
<table class="crm-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>نام</th>
|
||||
<th>عیار</th>
|
||||
<th>آخرین سند</th>
|
||||
<th>وضعیت</th>
|
||||
<th>عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in productSyncResults" :key="r.name">
|
||||
<td class="font-bold">{{ r.name }}</td>
|
||||
<td class="font-mono" dir="ltr">{{ r.ayar ?? '—' }}</td>
|
||||
<td>{{ r.lastSanad || '—' }}</td>
|
||||
<td>
|
||||
<UiBadge v-if="r.alreadyImported" variant="success">
|
||||
وارد شده{{ r.productName ? ` (${r.productName})` : '' }}
|
||||
</UiBadge>
|
||||
<UiBadge v-else variant="neutral">آماده ورود</UiBadge>
|
||||
</td>
|
||||
<td>
|
||||
<UiButton
|
||||
v-if="!r.alreadyImported"
|
||||
size="sm"
|
||||
:disabled="productSyncBusy"
|
||||
@click="importProductOne(r.name)"
|
||||
>
|
||||
وارد کردن
|
||||
</UiButton>
|
||||
<RouterLink
|
||||
v-else-if="r.productId"
|
||||
:to="{ name: 'product-edit', params: { id: r.productId } }"
|
||||
>
|
||||
<UiButton variant="ghost" size="sm">مشاهده محصول</UiButton>
|
||||
</RouterLink>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiCard>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user