rename: کاریز فروش → مسیر فروش, کاریز پروژه → تابلوی پروژه
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- Rename the default sales pipeline: «کاریز فروش» → «مسیر فروش»
|
||||
UPDATE "Pipeline" SET "name" = 'مسیر فروش' WHERE "name" = 'کاریز فروش';
|
||||
@@ -1231,7 +1231,7 @@ model PortalOtp {
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
/// برد کانبان برای کارهای داخلی / پسازفروش (جدا از کاریز فروش)
|
||||
/// برد کانبان برای کارهای داخلی / پسازفروش (جدا از مسیر فروش)
|
||||
model ProjectBoard {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
@@ -1775,7 +1775,7 @@ model AuditLog {
|
||||
@@index([organizationId, entityType, createdAt])
|
||||
}
|
||||
|
||||
/// فیلترهای ذخیرهشده برای لیستها و کاریزها
|
||||
/// فیلترهای ذخیرهشده برای لیستها و مسیرهای فروش
|
||||
/// ستونهای مخفی هر جدول برای هر کاربر (بهازای هر سازمان)
|
||||
model UserTablePreference {
|
||||
id String @id @default(cuid())
|
||||
|
||||
+10
-10
@@ -335,7 +335,7 @@ async function main() {
|
||||
pipeline = await prisma.pipeline.create({
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
name: 'کاریز فروش',
|
||||
name: 'مسیر فروش',
|
||||
isDefault: true,
|
||||
stages: {
|
||||
create: DEFAULT_STAGES.map((s, index) => ({
|
||||
@@ -354,13 +354,13 @@ async function main() {
|
||||
}
|
||||
|
||||
const servicesPipeline = await prisma.pipeline.findFirst({
|
||||
where: { organizationId: org.id, name: 'کاریز خدمات' },
|
||||
where: { organizationId: org.id, name: 'مسیر خدمات' },
|
||||
});
|
||||
if (!servicesPipeline) {
|
||||
await prisma.pipeline.create({
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
name: 'کاریز خدمات',
|
||||
name: 'مسیر خدمات',
|
||||
isDefault: false,
|
||||
stages: {
|
||||
create: DEFAULT_STAGES.map((s, index) => ({
|
||||
@@ -377,7 +377,7 @@ async function main() {
|
||||
console.log('Services pipeline already seeded');
|
||||
}
|
||||
|
||||
// کاریز منتظر — مطابق طراحی UI (۳ مرحله + معاملات نمونه)
|
||||
// مسیر منتظر — مطابق طراحی UI (۳ مرحله + معاملات نمونه)
|
||||
const WAITING_STAGES = [
|
||||
{ name: 'پیگیری', isWon: false, isLost: false },
|
||||
{ name: 'مذاکرات اولیه', isWon: false, isLost: false },
|
||||
@@ -483,7 +483,7 @@ async function main() {
|
||||
];
|
||||
|
||||
let waitingPipeline = await prisma.pipeline.findFirst({
|
||||
where: { organizationId: org.id, name: 'کاریز منتظر' },
|
||||
where: { organizationId: org.id, name: 'مسیر منتظر' },
|
||||
include: { stages: { orderBy: { position: 'asc' } } },
|
||||
});
|
||||
|
||||
@@ -491,7 +491,7 @@ async function main() {
|
||||
waitingPipeline = await prisma.pipeline.create({
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
name: 'کاریز منتظر',
|
||||
name: 'مسیر منتظر',
|
||||
isDefault: false,
|
||||
stages: {
|
||||
create: WAITING_STAGES.map((s, index) => ({
|
||||
@@ -504,7 +504,7 @@ async function main() {
|
||||
},
|
||||
include: { stages: { orderBy: { position: 'asc' } } },
|
||||
});
|
||||
console.log('Seeded waiting pipeline (کاریز منتظر)');
|
||||
console.log('Seeded waiting pipeline (مسیر منتظر)');
|
||||
} else {
|
||||
console.log('Waiting pipeline already seeded');
|
||||
}
|
||||
@@ -534,7 +534,7 @@ async function main() {
|
||||
expectedCloseAt: new Date(d.expectedCloseAt),
|
||||
ownerId: owner?.id,
|
||||
position: d.position,
|
||||
tags: ['دمو-کاریز'],
|
||||
tags: ['دمو-مسیر'],
|
||||
};
|
||||
}),
|
||||
});
|
||||
@@ -763,7 +763,7 @@ async function main() {
|
||||
{
|
||||
organizationId: org.id,
|
||||
authorId: owner?.id,
|
||||
body: 'در دمو نوا روی گزارش کاریز خیلی حساس بودند. نسخه چاپی PDF را آماده کنیم.',
|
||||
body: 'در دمو نوا روی گزارش مسیر فروش خیلی حساس بودند. نسخه چاپی PDF را آماده کنیم.',
|
||||
noteType: 'call_result',
|
||||
color: 'sky',
|
||||
tags: ['دمو'],
|
||||
@@ -971,7 +971,7 @@ async function main() {
|
||||
};
|
||||
|
||||
await ensure('deal_moved', {
|
||||
title: 'جابهجایی معامله در کاریز',
|
||||
title: 'جابهجایی معامله در مسیر فروش',
|
||||
body: sampleDeal
|
||||
? `«${sampleDeal.title}» به مرحله جدید منتقل شد`
|
||||
: 'یک معامله جابهجا شد',
|
||||
|
||||
@@ -677,7 +677,7 @@ export class LeadFormsService {
|
||||
}));
|
||||
|
||||
if (!pipeline?.stages.length) {
|
||||
throw new BadRequestException('کاریز فعالی برای ثبت معامله وجود ندارد');
|
||||
throw new BadRequestException('مسیر فروش فعالی برای ثبت معامله وجود ندارد');
|
||||
}
|
||||
|
||||
const stage =
|
||||
@@ -744,7 +744,7 @@ export class LeadFormsService {
|
||||
where: { id, organizationId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!found) throw new BadRequestException('کاریز انتخابشده معتبر نیست');
|
||||
if (!found) throw new BadRequestException('مسیر فروش انتخابشده معتبر نیست');
|
||||
}
|
||||
|
||||
private async assertLeadStage(
|
||||
|
||||
@@ -502,7 +502,7 @@ export class DealsExcelService {
|
||||
}
|
||||
|
||||
/**
|
||||
* مراحل پیشفرض سیستم (سرنخ، تماس اول، …) که خالی ماندهاند از کاریزهای واردشده حذف میشوند
|
||||
* مراحل پیشفرض سیستم (سرنخ، تماس اول، …) که خالی ماندهاند از مسیرهای فروش واردشده حذف میشوند
|
||||
* تا فقط مراحل خود فایل دیده شوند.
|
||||
*/
|
||||
private async removeUnusedDefaultStages(organizationId: string, pipelineIds: Set<string>) {
|
||||
@@ -634,7 +634,8 @@ export class DealsExcelService {
|
||||
status: DealStatus,
|
||||
ctx: RowContext,
|
||||
) {
|
||||
const name = pipelineName?.trim() || 'کاریز فروش';
|
||||
const rawName = pipelineName?.trim();
|
||||
const name = !rawName || rawName === 'کاریز فروش' ? 'مسیر فروش' : rawName;
|
||||
const cacheKey = name.toLowerCase();
|
||||
let pipeline = ctx.pipelines.get(cacheKey);
|
||||
if (!pipeline) {
|
||||
@@ -677,7 +678,7 @@ export class DealsExcelService {
|
||||
}
|
||||
const added = await this.pipelines.createStage(organizationId, pipeline.id, wanted);
|
||||
const stage = added.stages.find((s) => s.name === wanted) ?? added.stages.at(-1);
|
||||
if (!stage) throw new Error('کاریز مرحلهای ندارد');
|
||||
if (!stage) throw new Error('مسیر فروش مرحلهای ندارد');
|
||||
pipeline.stages = added.stages.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
@@ -688,7 +689,7 @@ export class DealsExcelService {
|
||||
}
|
||||
|
||||
const stage = byStatusExisting ?? pipeline.stages[0];
|
||||
if (!stage) throw new Error('کاریز مرحلهای ندارد');
|
||||
if (!stage) throw new Error('مسیر فروش مرحلهای ندارد');
|
||||
return { pipelineId: pipeline.id, stageId: stage.id, pipelineName: pipeline.name };
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export class DealsService {
|
||||
resolvedPipeline.stages[0];
|
||||
|
||||
if (!stage) {
|
||||
throw new BadRequestException('کاریز مرحلهای ندارد');
|
||||
throw new BadRequestException('مسیر فروش مرحلهای ندارد');
|
||||
}
|
||||
|
||||
await this.assertLinks(organizationId, actor, dto.personId, dto.companyId);
|
||||
@@ -337,7 +337,7 @@ export class DealsService {
|
||||
where: { id: dto.stageId, pipelineId: deal.pipelineId },
|
||||
});
|
||||
if (!stage) {
|
||||
throw new BadRequestException('مرحله در این کاریز معتبر نیست');
|
||||
throw new BadRequestException('مرحله در این مسیر فروش معتبر نیست');
|
||||
}
|
||||
|
||||
const status = stage.isWon ? 'won' : stage.isLost ? 'lost' : 'open';
|
||||
@@ -369,7 +369,7 @@ export class DealsService {
|
||||
organizationId,
|
||||
userId: deal.ownerId,
|
||||
type: 'deal_moved',
|
||||
title: 'جابهجایی معامله در کاریز',
|
||||
title: 'جابهجایی معامله در مسیر فروش',
|
||||
body: `«${deal.title}» به مرحله «${stage.name}» منتقل شد`,
|
||||
link: `/deals/${deal.id}`,
|
||||
entityType: 'deal',
|
||||
|
||||
@@ -81,7 +81,7 @@ export class PipelinesService {
|
||||
const created = await this.prisma.pipeline.create({
|
||||
data: {
|
||||
organizationId,
|
||||
name: 'کاریز فروش',
|
||||
name: 'مسیر فروش',
|
||||
isDefault: true,
|
||||
stages: {
|
||||
create: DEFAULT_STAGES.map((s, index) => ({
|
||||
@@ -389,7 +389,7 @@ export class PipelinesService {
|
||||
where: { id: pipelineId, organizationId },
|
||||
include: { stages: { orderBy: { position: 'asc' } } },
|
||||
});
|
||||
if (!pipeline) throw new NotFoundException('کاریز پیدا نشد');
|
||||
if (!pipeline) throw new NotFoundException('مسیر فروش پیدا نشد');
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ const CASE_COLUMNS = [
|
||||
{ header: 'نام خانوادگی شخص کارت', key: 'personName', width: 22 },
|
||||
{ header: 'کد دیدار شخص', key: 'personCode', width: 14 },
|
||||
{ header: 'موبایل شخص کارت', key: 'personMobile', width: 16 },
|
||||
{ header: 'کاریز کارت', key: 'boardName', width: 20 },
|
||||
{ header: 'مرحله کاریز کارت', key: 'stageName', width: 18 },
|
||||
{ header: 'تابلوی پروژه کارت', key: 'boardName', width: 20 },
|
||||
{ header: 'مرحله تابلوی پروژه کارت', key: 'stageName', width: 18 },
|
||||
{ header: 'تاریخ انجام کارت', key: 'doneDate', width: 16 },
|
||||
{ header: 'برچسب', key: 'tag', width: 16 },
|
||||
];
|
||||
@@ -53,6 +53,9 @@ const CASE_ALIASES: Record<string, string> = {
|
||||
نامخانوادگیشخصکارت: 'personName',
|
||||
کددیدارشخص: 'personCode',
|
||||
موبایلشخصکارت: 'personMobile',
|
||||
تابلویپروژهکارت: 'boardName',
|
||||
مرحلهتابلویپروژهکارت: 'stageName',
|
||||
// سرستونهای قدیمی (قبل از تغییر نام کاریز → تابلوی پروژه)
|
||||
کاریزکارت: 'boardName',
|
||||
مرحلهکاریزکارت: 'stageName',
|
||||
تاریخانجامکارت: 'doneDate',
|
||||
|
||||
@@ -49,7 +49,7 @@ const cols = computed<Col[]>(() => {
|
||||
|
||||
<HomePanelEmpty
|
||||
v-if="!cols.length"
|
||||
title="هنوز معاملهای در کاریز نیست"
|
||||
title="هنوز معاملهای در مسیر فروش نیست"
|
||||
description="اولین معامله را بسازید تا جریان فروش اینجا دیده شود."
|
||||
action-label="معامله جدید"
|
||||
:action-to="{ name: 'deal-new' }"
|
||||
|
||||
@@ -18,7 +18,7 @@ withDefaults(
|
||||
{
|
||||
canCreate: false,
|
||||
icon: 'briefcase',
|
||||
createOptionLabel: 'کاریز جدید…',
|
||||
createOptionLabel: 'مسیر فروش جدید…',
|
||||
showAmountFilters: true,
|
||||
},
|
||||
);
|
||||
@@ -34,7 +34,7 @@ const amountMin = defineModel<string>('amountMin', { default: '' });
|
||||
const amountMax = defineModel<string>('amountMax', { default: '' });
|
||||
|
||||
const viewOptions = [
|
||||
{ value: 'kanban' as const, label: 'نمای کاریز', icon: 'grid' as const },
|
||||
{ value: 'kanban' as const, label: 'نمای ستونی', icon: 'grid' as const },
|
||||
{ value: 'list' as const, label: 'نمای لیست', icon: 'list' as const },
|
||||
];
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
tint: 'sky',
|
||||
shortcuts: [
|
||||
{ to: '/leads', title: 'فهرست لیدها', icon: 'users', module: 'leads' },
|
||||
{ to: '/leads/board', title: 'کاریز لیدها', icon: 'filter', module: 'leads' },
|
||||
{ to: '/leads/board', title: 'مسیر لیدها', icon: 'filter', module: 'leads' },
|
||||
],
|
||||
cards: [
|
||||
{
|
||||
@@ -121,8 +121,8 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
},
|
||||
{
|
||||
to: '/leads/board',
|
||||
title: 'کاریز لیدها',
|
||||
desc: 'نمای کانبان و حرکت بین مراحل',
|
||||
title: 'مسیر لیدها',
|
||||
desc: 'نمای ستونی و حرکت بین مراحل',
|
||||
icon: 'filter',
|
||||
tint: 'violet',
|
||||
module: 'leads',
|
||||
@@ -130,7 +130,7 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
{
|
||||
to: '/settings/lead-stages',
|
||||
title: 'مراحل و وضعیتها',
|
||||
desc: 'تعریف وضعیتها و ستونهای کاریز لید',
|
||||
desc: 'تعریف وضعیتها و ستونهای مسیر لید',
|
||||
icon: 'layers',
|
||||
tint: 'peach',
|
||||
admin: true,
|
||||
@@ -160,7 +160,7 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
cards: [
|
||||
{
|
||||
to: '/pipeline',
|
||||
title: 'کاریز فروش',
|
||||
title: 'مسیر فروش',
|
||||
desc: 'معاملات و مراحل فروش',
|
||||
icon: 'filter',
|
||||
tint: 'mint',
|
||||
@@ -329,7 +329,7 @@ export const NAV_HUBS: NavHubDef[] = [
|
||||
cards: [
|
||||
{
|
||||
to: '/projects',
|
||||
title: 'کاریز پروژه',
|
||||
title: 'تابلوی پروژه',
|
||||
desc: 'میز کار تیمی: صف، انجام، بازبینی و ارجاع کارت',
|
||||
icon: 'flag',
|
||||
tint: 'violet',
|
||||
|
||||
@@ -125,7 +125,7 @@ export function currencyUnitLabel(currency: string): string {
|
||||
return currency;
|
||||
}
|
||||
|
||||
/** نگاشت برد API به مدل UI کاریز */
|
||||
/** نگاشت برد API به مدل UI مسیر فروش */
|
||||
export function mapBoardToStages(
|
||||
board: {
|
||||
stages: Array<{
|
||||
|
||||
@@ -435,7 +435,7 @@ onMounted(load);
|
||||
>
|
||||
<template #lead>
|
||||
<span class="crumb">
|
||||
<RouterLink :to="{ name: 'pipeline' }">کاریز فروش</RouterLink>
|
||||
<RouterLink :to="{ name: 'pipeline' }">مسیر فروش</RouterLink>
|
||||
<span>›</span>
|
||||
<strong>{{ pageTitle }}</strong>
|
||||
</span>
|
||||
@@ -678,13 +678,13 @@ onMounted(load);
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
|
||||
<UiField label="کاریز فروش">
|
||||
<UiField label="مسیر فروش">
|
||||
<UiSelect :model-value="pipelineId" @update:model-value="onPipelineChange($event)">
|
||||
<option v-for="p in pipelines" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
</UiSelect>
|
||||
</UiField>
|
||||
|
||||
<UiField label="مرحله کاریز" required>
|
||||
<UiField label="مرحله" required>
|
||||
<UiSelect v-model="stageId" required>
|
||||
<option v-for="s in stages" :key="s.id" :value="s.id">{{ s.name }}</option>
|
||||
</UiSelect>
|
||||
|
||||
@@ -33,7 +33,7 @@ async function load() {
|
||||
error.value =
|
||||
axios.isAxiosError(e) && !e.response
|
||||
? 'اتصال به API برقرار نشد.'
|
||||
: 'بارگذاری کاریز لیدها ناموفق بود.';
|
||||
: 'بارگذاری مسیر لیدها ناموفق بود.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ onMounted(load);
|
||||
|
||||
<template>
|
||||
<section class="page">
|
||||
<UiPageHeader title="کاریز لیدها" lead="کشیدن و رها کردن برای تغییر وضعیت">
|
||||
<UiPageHeader title="مسیر لیدها" lead="کشیدن و رها کردن برای تغییر وضعیت">
|
||||
<template #actions>
|
||||
<RouterLink :to="{ name: 'leads' }" class="link-btn">
|
||||
<UiButton variant="ghost" size="sm">فهرست لیدها</UiButton>
|
||||
@@ -166,8 +166,8 @@ onMounted(load);
|
||||
|
||||
<UiEmptyState
|
||||
v-else-if="!loading"
|
||||
title="مرحلهای برای کاریز نیست."
|
||||
description="در تنظیمات مراحل لید، نمایش روی کاریز را فعال کنید."
|
||||
title="مرحلهای برای نمایش در مسیر لیدها نیست."
|
||||
description="در تنظیمات مراحل لید، «نمایش روی مسیر» را فعال کنید."
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -594,7 +594,7 @@ onMounted(load);
|
||||
<input v-model="createDeal" type="checkbox" />
|
||||
</label>
|
||||
</div>
|
||||
<label v-if="createDeal" class="field pipeline-field">کاریز مقصد
|
||||
<label v-if="createDeal" class="field pipeline-field">مسیر فروش مقصد
|
||||
<select v-model="pipelineId">
|
||||
<option value="">پیشفرض سازمان</option>
|
||||
<option v-for="p in pipelines" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
|
||||
@@ -170,7 +170,7 @@ onMounted(load);
|
||||
<section class="crm-page">
|
||||
<UiPageHeader
|
||||
title="مراحل لید"
|
||||
lead="تعریف وضعیتهای کاریز لیدها و برچسبهای تبدیل/از دسترفته"
|
||||
lead="تعریف وضعیتهای مسیر لیدها و برچسبهای تبدیل/از دسترفته"
|
||||
/>
|
||||
|
||||
<p v-if="okMsg" class="ok">{{ okMsg }}</p>
|
||||
@@ -194,7 +194,7 @@ onMounted(load);
|
||||
</UiField>
|
||||
<label class="check">
|
||||
<input v-model="showOnBoard" type="checkbox" />
|
||||
نمایش روی کاریز
|
||||
نمایش روی مسیر
|
||||
</label>
|
||||
<label class="check">
|
||||
<input v-model="isConverted" type="checkbox" />
|
||||
@@ -227,7 +227,7 @@ onMounted(load);
|
||||
</UiField>
|
||||
<label class="check">
|
||||
<input v-model="editShowOnBoard" type="checkbox" />
|
||||
نمایش روی کاریز
|
||||
نمایش روی مسیر
|
||||
</label>
|
||||
<label class="check">
|
||||
<input v-model="editConverted" type="checkbox" />
|
||||
@@ -253,7 +253,7 @@ onMounted(load);
|
||||
<div class="info">
|
||||
<strong>{{ stage.name }}</strong>
|
||||
<span class="muted">
|
||||
{{ stage.showOnBoard ? 'روی کاریز' : 'مخفی از کاریز' }}
|
||||
{{ stage.showOnBoard ? 'روی مسیر' : 'مخفی از مسیر' }}
|
||||
<template v-if="stage.leadCount != null">
|
||||
· {{ toFaDigits(stage.leadCount) }} لید
|
||||
</template>
|
||||
|
||||
@@ -1230,11 +1230,11 @@ onBeforeUnmount(() => {
|
||||
</ul>
|
||||
</div>
|
||||
<TableColumnsMenu table-key="leads" align="end" />
|
||||
<div class="view-toggle" title="نمای لیست / کاریز">
|
||||
<div class="view-toggle" title="نمای لیست / مسیر">
|
||||
<button type="button" class="view-btn on" title="لیست">
|
||||
<UiIcon name="list" :size="16" />
|
||||
</button>
|
||||
<RouterLink class="view-btn" :to="{ name: 'leads-board' }" title="کاریز">
|
||||
<RouterLink class="view-btn" :to="{ name: 'leads-board' }" title="مسیر لیدها">
|
||||
<UiIcon name="grid" :size="16" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -870,7 +870,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<ProfileEmptyState
|
||||
v-if="dealsEmpty"
|
||||
title="هنوز معاملهای ثبت نشده!"
|
||||
description="اولین معامله این مشتری را در کاریز ثبت کنید یا یک فعالیت پیگیری بسازید."
|
||||
description="اولین معامله این مشتری را در مسیر فروش ثبت کنید یا یک فعالیت پیگیری بسازید."
|
||||
primary-label="افزودن معامله"
|
||||
:primary-to="{ name: 'deal-new', query: { personId: id } }"
|
||||
secondary-label="افزودن اولین فعالیت"
|
||||
|
||||
@@ -171,7 +171,7 @@ async function load() {
|
||||
if (axios.isAxiosError(e) && !e.response) {
|
||||
error.value = 'اتصال به API برقرار نشد.';
|
||||
} else {
|
||||
error.value = 'بارگذاری کاریز ناموفق بود.';
|
||||
error.value = 'بارگذاری مسیر فروش ناموفق بود.';
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -189,7 +189,7 @@ async function createPipeline() {
|
||||
showCreate.value = false;
|
||||
await router.replace({ name: 'pipeline', query: { pipelineId: data.id } });
|
||||
} catch {
|
||||
error.value = 'ساخت کاریز ناموفق بود.';
|
||||
error.value = 'ساخت مسیر فروش ناموفق بود.';
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
@@ -451,7 +451,7 @@ function onImportStart() {
|
||||
async function onImportDone(data: ImportContactsResult) {
|
||||
importMsg.value = `وارد شد: ${data.created} · تکراری رد شد: ${data.skipped}${
|
||||
data.errors.length ? ` · خطا: ${data.errors.length}` : ''
|
||||
}${data.boards?.length ? ` · کاریز: ${data.boards.join('، ')}` : ''}`;
|
||||
}${data.boards?.length ? ` · مسیر فروش: ${data.boards.join('، ')}` : ''}`;
|
||||
if (data.errors.length) {
|
||||
error.value = data.errors
|
||||
.slice(0, 3)
|
||||
@@ -504,7 +504,7 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
|
||||
<section class="page" dir="rtl">
|
||||
<UiBusyOverlay :open="pageBusy" :message="busyMessage" />
|
||||
<PipelineToolbar
|
||||
pipeline-name="کاریز فروش"
|
||||
pipeline-name="مسیر فروش"
|
||||
:pipeline-options="pipelineOptions"
|
||||
v-model:selected-pipeline-id="selectedPipelineId"
|
||||
v-model:view-mode="viewMode"
|
||||
@@ -546,7 +546,7 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<p v-if="importMsg" class="ok">{{ importMsg }}</p>
|
||||
<p v-else-if="loading && !stages.length" class="muted">در حال بارگذاری کاریز…</p>
|
||||
<p v-else-if="loading && !stages.length" class="muted">در حال بارگذاری مسیر فروش…</p>
|
||||
|
||||
<PipelineList
|
||||
v-if="viewMode === 'list' && (!loading || stages.length)"
|
||||
@@ -602,15 +602,15 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
|
||||
|
||||
<UiModal
|
||||
:open="showCreate && canCreatePipeline"
|
||||
title="کاریز جدید"
|
||||
title="مسیر فروش جدید"
|
||||
@close="showCreate = false"
|
||||
>
|
||||
<form class="create-form" @submit.prevent="createPipeline">
|
||||
<label class="field">
|
||||
<span>نام کاریز</span>
|
||||
<span>نام مسیر فروش</span>
|
||||
<UiInput
|
||||
v-model="newName"
|
||||
placeholder="نام کاریز جدید…"
|
||||
placeholder="نام مسیر فروش جدید…"
|
||||
:maxlength="120"
|
||||
required
|
||||
:minlength="2"
|
||||
|
||||
@@ -647,7 +647,7 @@ watch(
|
||||
<ProfileEmptyState
|
||||
v-if="dealsEmpty"
|
||||
title="هنوز معاملهای ثبت نشده!"
|
||||
description="اولین معامله را در کاریز ثبت کنید یا یک فعالیت پیگیری بسازید."
|
||||
description="اولین معامله را در مسیر فروش ثبت کنید یا یک فعالیت پیگیری بسازید."
|
||||
primary-label="افزودن معامله"
|
||||
:primary-to="{ name: 'deal-new' }"
|
||||
secondary-label="افزودن اولین فعالیت"
|
||||
|
||||
@@ -146,7 +146,7 @@ async function load() {
|
||||
if (axios.isAxiosError(e) && !e.response) {
|
||||
error.value = 'اتصال به API برقرار نشد.';
|
||||
} else {
|
||||
error.value = 'بارگذاری برد پروژه ناموفق بود.';
|
||||
error.value = 'بارگذاری تابلوی پروژه ناموفق بود.';
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -164,7 +164,7 @@ async function createBoard() {
|
||||
showCreate.value = false;
|
||||
await router.replace({ name: 'projects', query: { boardId: data.id } });
|
||||
} catch {
|
||||
error.value = 'ساخت برد ناموفق بود.';
|
||||
error.value = 'ساخت تابلو ناموفق بود.';
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ function onImportStart() {
|
||||
async function onImportDone(data: ImportContactsResult) {
|
||||
importMsg.value = `وارد شد: ${data.created} · تکراری رد شد: ${data.skipped}${
|
||||
data.errors.length ? ` · خطا: ${data.errors.length}` : ''
|
||||
}${data.boards?.length ? ` · برد: ${data.boards.join('، ')}` : ''}`;
|
||||
}${data.boards?.length ? ` · تابلو: ${data.boards.join('، ')}` : ''}`;
|
||||
if (data.errors.length) {
|
||||
error.value = data.errors
|
||||
.slice(0, 3)
|
||||
@@ -468,9 +468,9 @@ onBeforeUnmount(() => {
|
||||
<section class="page" dir="rtl">
|
||||
<UiBusyOverlay :open="pageBusy" :message="busyMessage" />
|
||||
<PipelineToolbar
|
||||
pipeline-name="کاریز پروژه — کارهای تیمی"
|
||||
pipeline-name="تابلوی پروژه — کارهای تیمی"
|
||||
icon="grid"
|
||||
create-option-label="برد جدید…"
|
||||
create-option-label="تابلوی جدید…"
|
||||
:pipeline-options="boardOptions"
|
||||
v-model:selected-pipeline-id="selectedBoardId"
|
||||
v-model:view-mode="viewMode"
|
||||
@@ -505,7 +505,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<p v-if="importMsg" class="ok">{{ importMsg }}</p>
|
||||
<p v-else-if="loading && !stages.length" class="muted">در حال بارگذاری برد…</p>
|
||||
<p v-else-if="loading && !stages.length" class="muted">در حال بارگذاری تابلو…</p>
|
||||
|
||||
<PipelineList
|
||||
v-if="viewMode === 'list' && (!loading || stages.length)"
|
||||
@@ -564,15 +564,15 @@ onBeforeUnmount(() => {
|
||||
|
||||
<UiModal
|
||||
:open="showCreate && canCreateBoard"
|
||||
title="برد جدید"
|
||||
title="تابلوی جدید"
|
||||
@close="showCreate = false"
|
||||
>
|
||||
<form class="create-form" @submit.prevent="createBoard">
|
||||
<label class="field">
|
||||
<span>نام برد</span>
|
||||
<span>نام تابلو</span>
|
||||
<UiInput
|
||||
v-model="newName"
|
||||
placeholder="نام برد جدید…"
|
||||
placeholder="نام تابلوی جدید…"
|
||||
:maxlength="120"
|
||||
required
|
||||
:minlength="2"
|
||||
|
||||
@@ -1404,7 +1404,7 @@ const activityMax = computed(() =>
|
||||
|
||||
<!-- Deep analytics (BI phase 1) -->
|
||||
<div class="deep-grid">
|
||||
<UiCard class="chart-card" title="تبدیل مراحل کاریز">
|
||||
<UiCard class="chart-card" title="تبدیل مراحل مسیر فروش">
|
||||
<p class="deep-hint">
|
||||
سهم هر مرحله از معاملات باز و نرخ عبور نسبت به مرحله قبل. برای دیدن پروندهها روی مرحله کلیک کنید.
|
||||
</p>
|
||||
@@ -1435,16 +1435,16 @@ const activityMax = computed(() =>
|
||||
/>
|
||||
</div>
|
||||
<div class="funnel-row-meta">
|
||||
<small>سهم کاریز: {{ toFaDigits(s.shareOfPipelinePercent) }}٪</small>
|
||||
<small>سهم از مسیر: {{ toFaDigits(s.shareOfPipelinePercent) }}٪</small>
|
||||
<small v-if="s.conversionFromPrevPercent != null">
|
||||
تبدیل از قبل: {{ toFaDigits(s.conversionFromPrevPercent) }}٪
|
||||
</small>
|
||||
<small v-else>شروع کاریز</small>
|
||||
<small v-else>شروع مسیر</small>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty-msg">معامله بازی در کاریز نیست.</p>
|
||||
<p v-else class="empty-msg">معامله بازی در مسیر فروش نیست.</p>
|
||||
</UiCard>
|
||||
|
||||
<UiCard class="chart-card" title="مقایسه با دوره قبل">
|
||||
|
||||
@@ -152,7 +152,7 @@ const forecastChartOption = computed<UiChartOption>(() => {
|
||||
grid: { left: '3%', right: '4%', bottom: '8%', top: '8%', containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['هدف دوره', 'تحققیافته', 'کاریز باز', 'پیشبینی'],
|
||||
data: ['هدف دوره', 'تحققیافته', 'معاملات باز', 'پیشبینی'],
|
||||
axisLine: { lineStyle: { color: '#cbd5e1' } },
|
||||
axisLabel: { color: '#64748b', fontFamily: 'IRANSans' },
|
||||
},
|
||||
@@ -721,7 +721,7 @@ onMounted(() => {
|
||||
<h3>خلاصه پیشبینی</h3>
|
||||
<div class="sum-row"><span>هدف دوره</span><strong>{{ formatMoney(forecast?.targetAmount || 0) }}</strong></div>
|
||||
<div class="sum-row"><span>تحقق (برنده)</span><strong>{{ formatMoney(forecast?.wonAmount || 0) }}</strong></div>
|
||||
<div class="sum-row"><span>کاریز باز</span><strong>{{ formatMoney(forecast?.openPipelineAmount || 0) }}</strong></div>
|
||||
<div class="sum-row"><span>معاملات باز</span><strong>{{ formatMoney(forecast?.openPipelineAmount || 0) }}</strong></div>
|
||||
<div class="sum-row payable"><span>پیشبینی درآمد</span><strong>{{ formatMoney(forecast?.forecastAmount || 0) }}</strong></div>
|
||||
<div class="sum-row"><span>مشتریان جدید</span><strong>{{ toFaDigits(forecast?.newCustomersCount || 0) }}</strong></div>
|
||||
<div class="sum-row"><span>معاملات جدید</span><strong>{{ toFaDigits(forecast?.newDealsCount || 0) }}</strong></div>
|
||||
|
||||
Reference in New Issue
Block a user