feat: move brand name to one config and redesign the accounting card
اپ: نام برند در همهی صفحهها و پیامها یکسان شده و از یک تنظیم مرکزی خوانده میشود. اپ: کارت حسابداری در صفحهی مشتری بازطراحی شد؛ ماندهها و وضعیت لینک واضحتر نمایش داده میشوند. پنل: کارت حسابداری در پروندهی مشتری بازطراحی شد؛ ماندهها، آخرین ارسال و فرم لینک دستی واضحتر شدهاند. پنل: ستون «کد» در جدولها بهطور پیشفرض پنهان است و از انتخاب ستونها قابل نمایش است. پنل: تغییر نمایش ستونها در جدولها بلافاصله اعمال میشود. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
5d2a1efb0c
commit
532ceee189
@@ -101,18 +101,39 @@
|
||||
key="slider"
|
||||
tab="اسلایدر موبایل"
|
||||
>
|
||||
<div v-for="(s, index) in sliders" :key="index" class="slide">
|
||||
<h6 class="mobile-c">اسلاید {{ index + 1 }}</h6>
|
||||
<div class="slider-toolbar">
|
||||
<span class="slider-count">{{ sliders.length }} اسلاید</span>
|
||||
<div class="row">
|
||||
<ImageUploadInput v-model="s.image" purpose="slider" placeholder="تصویر اسلاید" />
|
||||
<a-input v-model:value="s.link" placeholder="لینک" />
|
||||
<a-button danger @click="sliders.splice(index, 1)">حذف</a-button>
|
||||
<a-button @click="addSlide">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
افزودن اسلاید
|
||||
</a-button>
|
||||
<a-button type="primary" @click="saveSliders">ذخیره اسلایدر</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<a-button @click="sliders.push({ image: '', link: '' })">افزودن اسلاید</a-button>
|
||||
<a-button type="primary" @click="saveSliders">ذخیره اسلایدر</a-button>
|
||||
<a-empty v-if="sliders.length === 0" description="اسلایدی ثبت نشده است" />
|
||||
|
||||
<div v-else class="slider-grid">
|
||||
<div v-for="(s, index) in sliders" :key="index" class="slide-card">
|
||||
<div class="slide-card__head">
|
||||
<span class="slide-card__title">اسلاید {{ index + 1 }}</span>
|
||||
<a-popconfirm
|
||||
ok-text="بله"
|
||||
cancel-text="خیر"
|
||||
title="این اسلاید حذف شود؟"
|
||||
@confirm="removeSlide(index)"
|
||||
>
|
||||
<a-button type="text" danger size="small" title="حذف">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
<div class="slide-card__media">
|
||||
<ImageUploadInput v-model="s.image" purpose="slider" placeholder="تصویر اسلاید" />
|
||||
</div>
|
||||
<a-input v-model:value="s.link" placeholder="لینک" />
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
@@ -173,7 +194,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import { useDeleteHandler } from '../../composables/useDeleteHandler';
|
||||
import api from '../../api/client';
|
||||
@@ -280,6 +301,14 @@ async function saveTags() {
|
||||
}
|
||||
|
||||
// ── slider
|
||||
function addSlide() {
|
||||
sliders.value.push({ image: '', link: '' });
|
||||
}
|
||||
|
||||
function removeSlide(index: number) {
|
||||
sliders.value.splice(index, 1);
|
||||
}
|
||||
|
||||
// Gated server-side by its own `custom.config.statics.slider` permission (not
|
||||
// the broad `config.statics` that guards the rest of this screen) — see
|
||||
// admin-misc.controller.ts. Uses the dedicated `const/slider` route rather
|
||||
@@ -368,14 +397,62 @@ onMounted(async () => {
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.slide {
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
.slider-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mobile-c {
|
||||
margin: 0 0 0.5rem;
|
||||
.slider-count {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
/* one card per slide; min() keeps a card no wider than a narrow phone column */
|
||||
.slider-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.slide-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.slide-card__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.slide-card__title {
|
||||
font-weight: 600;
|
||||
}
|
||||
/* Fixed-height media box: the empty upload tile, the uploading spinner and the
|
||||
saved thumbnail all fill it, so the card keeps the same height when a photo
|
||||
is added or removed (no jump). The shared upload tile is 104px square by
|
||||
default; here it becomes a full-width banner preview. */
|
||||
.slide-card__media {
|
||||
height: 140px;
|
||||
}
|
||||
.slide-card__media :deep(.image-upload),
|
||||
.slide-card__media :deep(.ant-upload-wrapper),
|
||||
.slide-card__media :deep(.ant-upload-list),
|
||||
.slide-card__media :deep(.ant-upload-list-item-container),
|
||||
.slide-card__media :deep(.ant-upload.ant-upload-select) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.slide-card__media :deep(.ant-upload-wrapper) {
|
||||
display: block;
|
||||
}
|
||||
.slide-card__media :deep(.ant-upload-list-item-thumbnail img) {
|
||||
object-fit: cover;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
@@ -387,12 +464,6 @@ onMounted(async () => {
|
||||
flex: 1 1 140px;
|
||||
max-width: none !important;
|
||||
}
|
||||
.slide {
|
||||
padding: 10px;
|
||||
}
|
||||
.slide .row > * {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
</div>
|
||||
<div class="hint mb-4">
|
||||
اسناد سفارش همیشه «موقت» ثبت میشوند و تا حسابدار نهایی نکند روی ماندهی حسابداری اثر ندارند.
|
||||
سقف اعتبار سفارش همچنان با دفتر داخلی منتزر چک میشود.
|
||||
سقف اعتبار سفارش همچنان با دفتر داخلی {{ brand.name }} چک میشود.
|
||||
</div>
|
||||
<a-button type="primary" @click="save">ذخیره</a-button>
|
||||
</a-tab-pane>
|
||||
@@ -129,17 +129,17 @@
|
||||
<a-collapse ghost class="mb-4">
|
||||
<a-collapse-panel key="guide" header="راهنمای این تب: نگاشت چیست و چطور پر میشود؟">
|
||||
<p>
|
||||
در این صفحه «منتزر» یعنی همین سامانه: <b>اپ مشتری</b> (جایی که مشتری ثبتنام، احراز هویت و سفارش میکند و ماندهاش را میبیند) و
|
||||
در این صفحه «{{ brand.name }}» یعنی همین سامانه: <b>اپ مشتری</b> (جایی که مشتری ثبتنام، احراز هویت و سفارش میکند و ماندهاش را میبیند) و
|
||||
<b>پنل مدیریت</b> (همین پنل، جایی که محصول و دستهبندی تعریف میشود). «حسابداری» یعنی نرمافزار حسابداری مغازه (تهحساب یا زرین).
|
||||
</p>
|
||||
<p>
|
||||
منتزر و حسابداری هر کدام گروهبندی خودشان را دارند. این تب میگوید هر چیز در منتزر با کدام گروه در حسابداری متناظر است؛
|
||||
{{ brand.name }} و حسابداری هر کدام گروهبندی خودشان را دارند. این تب میگوید هر چیز در {{ brand.name }} با کدام گروه در حسابداری متناظر است؛
|
||||
خودِ مشتریها و محصولها اینجا وصل نمیشوند (آنها در تب «لینکها» دیده میشوند).
|
||||
</p>
|
||||
<ol>
|
||||
<li>تنظیمات تب «اتصال» را ذخیره کنید، بعد «دریافت گروهها از حسابداری» را بزنید تا فهرستها از حسابداری پر شوند.</li>
|
||||
<li>
|
||||
<b>گروههای حسابی که مشتری منتزر حساب میشوند:</b> در حسابداری صندوق، کارمند و سرمایهگذار هم «حساب» هستند. فقط گروههایی را انتخاب کنید که
|
||||
<b>گروههای حسابی که مشتری {{ brand.name }} حساب میشوند:</b> در حسابداری صندوق، کارمند و سرمایهگذار هم «حساب» هستند. فقط گروههایی را انتخاب کنید که
|
||||
واقعاً مشتریاند؛ سینک مشتری فقط حسابهای این گروهها را با شماره موبایل به کاربران اپ مشتری وصل میکند. اگر خالی بماند سینک مشتری اجرا نمیشود.
|
||||
</li>
|
||||
<li>
|
||||
@@ -147,12 +147,12 @@
|
||||
اگر تعیین نشود، ساخت حساب در «لاگ ارسال» با وضعیت «نیاز به بررسی» میماند.
|
||||
</li>
|
||||
<li>
|
||||
<b>عیار پایه:</b> عیار وزنهایی که در منتزر ثبت میشود (وزن محصولها و سفارشها؛ معمولاً ۷۵۰ = ۱۸ عیار). در سند سفارش، وزن با همین عیار به
|
||||
<b>عیار پایه:</b> عیار وزنهایی که در {{ brand.name }} ثبت میشود (وزن محصولها و سفارشها؛ معمولاً ۷۵۰ = ۱۸ عیار). در سند سفارش، وزن با همین عیار به
|
||||
حسابداری میرود؛ در تهحساب اجرت هم روی آن اعمال میشود (مثلاً ۷۵۰ با اجرت ۱۰٪ میشود ۸۲۵).
|
||||
</li>
|
||||
<li v-if="form.provider === 'zarrin'">
|
||||
<b>کارساخته (فقط زرین):</b> وقتی در پنل مدیریت منتزر محصول جدیدی تعریف شود، برایش یک «کارساخته» در زرین ساخته میشود. «نوع» تعیین میکند
|
||||
گرمی است یا تعدادی، و «گروه ساخته» جای آن در زرین است. در جدول پایین برای هر دستهبندی محصولِ منتزر میتوانید گروه جدا بدهید؛ دستهای که
|
||||
<b>کارساخته (فقط زرین):</b> وقتی در پنل مدیریت {{ brand.name }} محصول جدیدی تعریف شود، برایش یک «کارساخته» در زرین ساخته میشود. «نوع» تعیین میکند
|
||||
گرمی است یا تعدادی، و «گروه ساخته» جای آن در زرین است. در جدول پایین برای هر دستهبندی محصولِ {{ brand.name }} میتوانید گروه جدا بدهید؛ دستهای که
|
||||
«پیشفرض» بماند در «گروه ساختهی پیشفرض» میرود. اگر نوع یا گروه خالی باشد، ساخت کارساخته در «لاگ ارسال» منتظر بررسی میماند.
|
||||
</li>
|
||||
</ol>
|
||||
@@ -167,10 +167,10 @@
|
||||
</a-space>
|
||||
<a-form layout="vertical">
|
||||
<a-divider orientation="right">حساب مشتریها</a-divider>
|
||||
<a-alert type="info" show-icon class="mb-4" message="این بخش تعیین میکند کدام حسابهای حسابداری به کاربران اپ مشتری منتزر وصل شوند">
|
||||
<a-alert type="info" show-icon class="mb-4" :message="`این بخش تعیین میکند کدام حسابهای حسابداری به کاربران اپ مشتری ${brand.name} وصل شوند`">
|
||||
<template #description>
|
||||
<div>
|
||||
هر حسابی که در گروههای انتخابشده باشد و موبایلش با یک کاربر اپ مشتری منتزر یکی باشد، به آن کاربر وصل میشود و ماندهاش در اپ مشتری
|
||||
هر حسابی که در گروههای انتخابشده باشد و موبایلش با یک کاربر اپ مشتری {{ brand.name }} یکی باشد، به آن کاربر وصل میشود و ماندهاش در اپ مشتری
|
||||
نمایش داده میشود. صندوق، کارمند و سرمایهگذار را انتخاب نکنید.
|
||||
</div>
|
||||
<div>
|
||||
@@ -180,7 +180,7 @@
|
||||
</template>
|
||||
</a-alert>
|
||||
<div class="grid-2">
|
||||
<a-form-item label="گروههای حسابی که مشتری منتزر حساب میشوند" extra="فقط گروههایی که مشتریاند؛ خالی = سینک مشتری اجرا نمیشود">
|
||||
<a-form-item :label="`گروههای حسابی که مشتری ${brand.name} حساب میشوند`" extra="فقط گروههایی که مشتریاند؛ خالی = سینک مشتری اجرا نمیشود">
|
||||
<a-select v-model:value="importGroupKeys" mode="multiple" :options="customerGroupOptions" allow-clear placeholder="هیچ گروهی انتخاب نشده" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
@@ -189,32 +189,32 @@
|
||||
>
|
||||
<a-select v-model:value="newCustomerGroupKey" :options="customerGroupOptions" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="عیار پایهی وزنهای منتزر" extra="عیار وزن محصولها و سفارشها در منتزر؛ ۷۵۰ یعنی ۱۸ عیار">
|
||||
<a-form-item :label="`عیار پایهی وزنهای ${brand.name}`" :extra="`عیار وزن محصولها و سفارشها در ${brand.name}؛ ۷۵۰ یعنی ۱۸ عیار`">
|
||||
<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="هر محصولی که در پنل مدیریت منتزر (بخش محصولات) تعریف میکنید، در زرین یک «کارساخته» میشود">
|
||||
<a-alert type="info" show-icon class="mb-4" :message="`هر محصولی که در پنل مدیریت ${brand.name} (بخش محصولات) تعریف میکنید، در زرین یک «کارساخته» میشود`">
|
||||
<template #description>
|
||||
<div>
|
||||
زرین برای ساختن کارساخته دو چیز لازم دارد: <b>نوع</b> (گرمی یا تعدادی) و <b>گروه ساخته</b> (کارساخته در کدام پوشهی زرین قرار بگیرد).
|
||||
این بخش این دو را بهجای شما انتخاب میکند تا لازم نباشد برای هر محصول جداگانه در زرین کاری انجام دهید.
|
||||
</div>
|
||||
<div>
|
||||
ستون «دستهبندی محصول در منتزر» همان دستهبندیهایی است که در پنل مدیریت برای محصولات تعریف کردهاید. مثال: اگر روبهروی «انگشتر»
|
||||
گروه «انگشتر» زرین را بگذارید، هر محصول تازه در دستهی انگشتر منتزر، در گروه انگشترِ زرین ساخته میشود.
|
||||
ستون «دستهبندی محصول در {{ brand.name }}» همان دستهبندیهایی است که در پنل مدیریت برای محصولات تعریف کردهاید. مثال: اگر روبهروی «انگشتر»
|
||||
گروه «انگشتر» زرین را بگذارید، هر محصول تازه در دستهی انگشتر {{ brand.name }}، در گروه انگشترِ زرین ساخته میشود.
|
||||
دستههایی که «پیشفرض» بمانند در «گروه ساختهی پیشفرض» میروند.
|
||||
</div>
|
||||
<div>
|
||||
اگر نوع یا گروه خالی باشد، محصول در منتزر ساخته میشود ولی ساخت کارساخته در «لاگ ارسال» منتظر میماند تا آن را تعیین کنید و «تلاش دوباره» بزنید.
|
||||
اگر نوع یا گروه خالی باشد، محصول در {{ brand.name }} ساخته میشود ولی ساخت کارساخته در «لاگ ارسال» منتظر میماند تا آن را تعیین کنید و «تلاش دوباره» بزنید.
|
||||
محصولهایی که از قبل به کارساخته وصل شدهاند تغییری نمیکنند.
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
<div class="grid-2">
|
||||
<a-form-item label="نوع کارساختهی جدید (گرمی/تعدادی…)" extra="برای محصول تازهای که در پنل مدیریت منتزر تعریف میشود">
|
||||
<a-form-item label="نوع کارساختهی جدید (گرمی/تعدادی…)" :extra="`برای محصول تازهای که در پنل مدیریت ${brand.name} تعریف میشود`">
|
||||
<a-select v-model:value="newTypeKey" :options="typeOptions" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="گروه ساختهی پیشفرض" extra="برای دستههایی که در جدول زیر «پیشفرض» ماندهاند">
|
||||
@@ -222,7 +222,7 @@
|
||||
</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="`دستهبندی محصول در ${brand.name}`" 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="پیشفرض" />
|
||||
@@ -343,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="`شناسه در ${brand.name}`" data-index="entityId" />
|
||||
<a-table-column title="منشأ">
|
||||
<template #default="{ record }">{{ ORIGIN[record.origin] ?? record.origin }}</template>
|
||||
</a-table-column>
|
||||
@@ -396,6 +396,7 @@ import moment from 'moment-jalaali';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import api from '../../api/client';
|
||||
import AccountingConnection from '../../components/accounting/AccountingConnection.vue';
|
||||
import { brand } from '../../brand';
|
||||
|
||||
type ProviderName = 'tahesab' | 'zarrin';
|
||||
interface Group {
|
||||
@@ -438,7 +439,7 @@ const ISSUE: Record<string, string> = {
|
||||
const ORIGIN: Record<string, string> = {
|
||||
LINKED: 'جفت با موبایل/نام',
|
||||
IMPORTED: 'وارد شده از حسابداری',
|
||||
PUSHED: 'ساخته شده از منتزر',
|
||||
PUSHED: `ساخته شده از ${brand.name}`,
|
||||
MANUAL: 'دستی',
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user