feat: catalog read cache (api + user apps) and clearer Zarrin accounting errors

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-11 14:31:32 +03:30
co-authored by Cursor
parent 561addc676
commit 574fa1b4aa
53 changed files with 1241 additions and 401 deletions
@@ -119,42 +119,110 @@
</div>
<div class="hint mb-4">
اسناد سفارش همیشه «موقت» ثبت می‌شوند و تا حسابدار نهایی نکند روی مانده‌ی حسابداری اثر ندارند.
سقف اعتبار سفارش همچنان با دفتر داخلی اپ چک می‌شود.
سقف اعتبار سفارش همچنان با دفتر داخلی منتزر چک می‌شود.
</div>
<a-button type="primary" @click="save">ذخیره</a-button>
</a-tab-pane>
<!-- ── نگاشت ─────────────────────────────────────────── -->
<a-tab-pane key="mapping" tab="نگاشت">
<a-collapse ghost class="mb-4">
<a-collapse-panel key="guide" header="راهنمای این تب: نگاشت چیست و چطور پر می‌شود؟">
<p>
در این صفحه «منتزر» یعنی همین سامانه: <b>اپ مشتری</b> (جایی که مشتری ثبت‌نام، احراز هویت و سفارش می‌کند و مانده‌اش را می‌بیند) و
<b>پنل مدیریت</b> (همین پنل، جایی که محصول و دسته‌بندی تعریف می‌شود). «حسابداری» یعنی نرم‌افزار حسابداری مغازه (ته‌حساب یا زرین).
</p>
<p>
منتزر و حسابداری هر کدام گروه‌بندی خودشان را دارند. این تب می‌گوید هر چیز در منتزر با کدام گروه در حسابداری متناظر است؛
خودِ مشتری‌ها و محصول‌ها اینجا وصل نمی‌شوند (آن‌ها در تب «لینک‌ها» دیده می‌شوند).
</p>
<ol>
<li>تنظیمات تب «اتصال» را ذخیره کنید، بعد «دریافت گروه‌ها از حسابداری» را بزنید تا فهرست‌ها از حسابداری پر شوند.</li>
<li>
<b>گروه‌های حسابی که مشتری منتزر حساب می‌شوند:</b> در حسابداری صندوق، کارمند و سرمایه‌گذار هم «حساب» هستند. فقط گروه‌هایی را انتخاب کنید که
واقعاً مشتری‌اند؛ سینک مشتری فقط حساب‌های این گروه‌ها را با شماره موبایل به کاربران اپ مشتری وصل می‌کند. اگر خالی بماند سینک مشتری اجرا نمی‌شود.
</li>
<li>
<b>گروه مشتری جدید:</b> وقتی کاربری در اپ مشتری احراز هویت شود و در حسابداری حساب نداشته باشد، حسابش در این گروه ساخته می‌شود.
اگر تعیین نشود، ساخت حساب در «لاگ ارسال» با وضعیت «نیاز به بررسی» می‌ماند.
</li>
<li>
<b>عیار پایه:</b> عیار وزن‌هایی که در منتزر ثبت می‌شود (وزن محصول‌ها و سفارش‌ها؛ معمولاً ۷۵۰ = ۱۸ عیار). در سند سفارش، وزن با همین عیار به
حسابداری می‌رود؛ در ته‌حساب اجرت هم روی آن اعمال می‌شود (مثلاً ۷۵۰ با اجرت ۱۰٪ می‌شود ۸۲۵).
</li>
<li v-if="form.provider === 'zarrin'">
<b>کارساخته (فقط زرین):</b> وقتی در پنل مدیریت منتزر محصول جدیدی تعریف شود، برایش یک «کارساخته» در زرین ساخته می‌شود. «نوع» تعیین می‌کند
گرمی است یا تعدادی، و «گروه ساخته» جای آن در زرین است. در جدول پایین برای هر دسته‌بندی محصولِ منتزر می‌توانید گروه جدا بدهید؛ دسته‌ای که
«پیش‌فرض» بماند در «گروه ساخته‌ی پیش‌فرض» می‌رود. اگر نوع یا گروه خالی باشد، ساخت کارساخته در «لاگ ارسال» منتظر بررسی می‌ماند.
</li>
</ol>
<p class="hint">
تغییر این تنظیمات فقط روی موارد بعدی اثر دارد؛ حساب‌ها و کارساخته‌هایی که قبلاً ساخته شده‌اند جابه‌جا نمی‌شوند. در آخر «ذخیره» را بزنید.
</p>
</a-collapse-panel>
</a-collapse>
<a-space class="mb-4">
<a-button :disabled="form.provider === 'none'" :loading="optionsLoading" @click="loadOptions">دریافت گروه‌ها از حسابداری</a-button>
<span v-for="e in optionErrors" :key="e" class="warn">{{ e }}</span>
</a-space>
<a-form layout="vertical">
<a-divider orientation="right">حساب مشتری‌ها</a-divider>
<a-alert type="info" show-icon class="mb-4" message="این بخش تعیین می‌کند کدام حساب‌های حسابداری به کاربران اپ مشتری منتزر وصل شوند">
<template #description>
<div>
هر حسابی که در گروه‌های انتخاب‌شده باشد و موبایلش با یک کاربر اپ مشتری منتزر یکی باشد، به آن کاربر وصل می‌شود و مانده‌اش در اپ مشتری
نمایش داده می‌شود. صندوق، کارمند و سرمایه‌گذار را انتخاب نکنید.
</div>
<div>
مثال: «بنکدار» و «مغازه‌دار» را در فیلد اول انتخاب کنید و در فیلد دوم «مغازه‌دار» را بگذارید. در این حالت کسی که در اپ مشتری ثبت‌نام و
احراز هویت کند و در حسابداری حساب نداشته باشد، در گروه «مغازه‌دار» برایش حساب ساخته می‌شود.
</div>
</template>
</a-alert>
<div class="grid-2">
<a-form-item label="گروه‌های حسابی که وارد اپ می‌شوند (خالی = همه)">
<a-select v-model:value="importGroupKeys" mode="multiple" :options="customerGroupOptions" allow-clear placeholder="همه‌ی گروه‌ها" />
<a-form-item label="گروه‌های حسابی که مشتری منتزر حساب می‌شوند" extra="فقط گروه‌هایی که مشتری‌اند؛ خالی = سینک مشتری اجرا نمی‌شود">
<a-select v-model:value="importGroupKeys" mode="multiple" :options="customerGroupOptions" allow-clear placeholder="هیچ گروهی انتخاب نشده" />
</a-form-item>
<a-form-item label="گروه مشتری جدیدی که از اپ ساخته می‌شود">
<a-form-item
label="گروه حسابداریِ مشتری تازه‌ی اپ مشتری"
extra="کاربری که در اپ مشتری احراز هویت شده و در حسابداری حساب ندارد، در این گروه برایش حساب ساخته می‌شود"
>
<a-select v-model:value="newCustomerGroupKey" :options="customerGroupOptions" allow-clear />
</a-form-item>
<a-form-item label="عیار پایه‌ی گرم‌های اپ">
<a-form-item label="عیار پایه‌ی وزن‌های منتزر" extra="عیار وزن محصول‌ها و سفارش‌ها در منتزر؛ ۷۵۰ یعنی ۱۸ عیار">
<a-input-number v-model:value="form.mapping.karat" :min="1" :max="1000" class="w-full" />
</a-form-item>
</div>
<template v-if="form.provider === 'zarrin'">
<a-divider orientation="right">کارساخته (زرین)</a-divider>
<a-alert type="info" show-icon class="mb-4" message="هر محصولی که در پنل مدیریت منتزر (بخش محصولات) تعریف می‌کنید، در زرین یک «کارساخته» می‌شود">
<template #description>
<div>
زرین برای ساختن کارساخته دو چیز لازم دارد: <b>نوع</b> (گرمی یا تعدادی) و <b>گروه ساخته</b> (کارساخته در کدام پوشه‌ی زرین قرار بگیرد).
این بخش این دو را به‌جای شما انتخاب می‌کند تا لازم نباشد برای هر محصول جداگانه در زرین کاری انجام دهید.
</div>
<div>
ستون «دسته‌بندی محصول در منتزر» همان دسته‌بندی‌هایی است که در پنل مدیریت برای محصولات تعریف کرده‌اید. مثال: اگر روبه‌روی «انگشتر»
گروه «انگشتر» زرین را بگذارید، هر محصول تازه در دسته‌ی انگشتر منتزر، در گروه انگشترِ زرین ساخته می‌شود.
دسته‌هایی که «پیش‌فرض» بمانند در «گروه ساخته‌ی پیش‌فرض» می‌روند.
</div>
<div>
اگر نوع یا گروه خالی باشد، محصول در منتزر ساخته می‌شود ولی ساخت کارساخته در «لاگ ارسال» منتظر می‌ماند تا آن را تعیین کنید و «تلاش دوباره» بزنید.
محصول‌هایی که از قبل به کارساخته وصل شده‌اند تغییری نمی‌کنند.
</div>
</template>
</a-alert>
<div class="grid-2">
<a-form-item label="نوع کارساخته‌ی جدید (گرمی/تعدادی…)">
<a-form-item label="نوع کارساخته‌ی جدید (گرمی/تعدادی…)" extra="برای محصول تازه‌ای که در پنل مدیریت منتزر تعریف می‌شود">
<a-select v-model:value="newTypeKey" :options="typeOptions" allow-clear />
</a-form-item>
<a-form-item label="گروه ساخته‌ی پیش‌فرض">
<a-form-item label="گروه ساخته‌ی پیش‌فرض" extra="برای دسته‌هایی که در جدول زیر «پیش‌فرض» مانده‌اند">
<a-select v-model:value="newItemGroupKey" :options="itemGroupOptions" allow-clear />
</a-form-item>
</div>
<a-table :data-source="categories" :pagination="false" row-key="id" size="small" class="mb-4">
<a-table-column title="دسته‌بندی اپ" data-index="title" />
<a-table-column title="دسته‌بندی محصول در منتزر" data-index="title" />
<a-table-column title="گروه ساخته در زرین">
<template #default="{ record }">
<a-select v-model:value="categoryGroupKeys[record.id]" :options="itemGroupOptions" allow-clear class="w-full" placeholder="پیش‌فرض" />
@@ -165,6 +233,9 @@
<div v-else-if="form.provider === 'tahesab'" class="hint mb-4">
ته‌حساب در API خود ساخت کارساخته ندارد: نام کار با اولین سند سفارش در ته‌حساب ساخته می‌شود و از آن به بعد به محصول لینک می‌شود.
</div>
<div v-if="optionErrors.length" class="hint mb-4">
اگر فهرستی خالی است، یعنی دریافت آن از حسابداری خطا داده (پیام قرمز بالای صفحه). اتصال را بررسی کنید و دوباره «دریافت گروه‌ها» را بزنید.
</div>
<a-button type="primary" @click="save">ذخیره</a-button>
</a-form>
</a-tab-pane>
@@ -272,7 +343,7 @@
>
<a-table-column title="کد حسابداری" data-index="externalCode" />
<a-table-column title="نام در حسابداری" data-index="externalName" />
<a-table-column title="شناسه‌ی اپ" data-index="entityId" />
<a-table-column title="شناسه در منتزر" data-index="entityId" />
<a-table-column title="منشأ">
<template #default="{ record }">{{ ORIGIN[record.origin] ?? record.origin }}</template>
</a-table-column>
@@ -367,7 +438,7 @@ const ISSUE: Record<string, string> = {
const ORIGIN: Record<string, string> = {
LINKED: 'جفت با موبایل/نام',
IMPORTED: 'وارد شده از حسابداری',
PUSHED: 'ساخته شده از اپ',
PUSHED: 'ساخته شده از منتزر',
MANUAL: 'دستی',
};