Deals Excel import/export aligned with Didar: browser-side import, new deal fields (code, probability, closedAt, creator, lostNote, referrer), Didar custom fields

This commit is contained in:
2026-10-06 16:36:04 +03:30
parent e08f1452ef
commit 2a2728e5ee
11 changed files with 1032 additions and 210 deletions
+52
View File
@@ -96,6 +96,14 @@ const pipelineId = ref('');
const ownerId = ref('');
const selectedAssigneeIds = ref<string[]>([]);
const lostReason = ref('');
const lostNote = ref('');
const dealCode = ref('');
const probability = ref('');
const sourceDetail = ref('');
const referrer = ref('');
const closedAt = ref('');
const createdByName = ref('');
const sourceRef = ref('');
const status = ref('');
const customFields = ref<Record<string, unknown>>({});
const lines = ref<LineDraft[]>([]);
@@ -251,6 +259,14 @@ async function load() {
ownerId.value = data.owner?.id ?? auth.user?.id ?? '';
selectedAssigneeIds.value = (data.assignees ?? []).map((a) => a.id);
lostReason.value = data.lostReason ?? '';
lostNote.value = data.lostNote ?? '';
dealCode.value = data.code ?? '';
probability.value = data.probability == null ? '' : String(data.probability);
sourceDetail.value = data.sourceDetail ?? '';
referrer.value = data.referrer ?? '';
closedAt.value = data.closedAt ? data.closedAt.slice(0, 10) : '';
createdByName.value = data.createdBy?.fullName ?? '';
sourceRef.value = data.importSource === 'didar' && data.sourceRef ? data.sourceRef : '';
status.value = data.status;
customFields.value = { ...(data.customFields ?? {}) };
lines.value = (data.lineItems ?? []).map((item) => ({
@@ -302,6 +318,10 @@ function payload(): CreateDealRequest {
? null
: Number(amount.value),
source: source.value || undefined,
sourceDetail: sourceDetail.value.trim() || null,
referrer: referrer.value.trim() || null,
code: dealCode.value.trim() || null,
probability: probability.value === '' ? null : Number(probability.value),
notes: notes.value || undefined,
tags: parseTags(tagsRaw.value),
isStale: health.value === 'stale',
@@ -374,6 +394,8 @@ async function submit() {
await api.patch(`/deals/${id.value}`, {
...payload(),
lostReason: lostReason.value || null,
lostNote: lostNote.value.trim() || null,
closedAt: closedAt.value || null,
});
if (stageId.value) {
await api.patch(`/deals/${id.value}/move`, { stageId: stageId.value });
@@ -679,13 +701,43 @@ onMounted(load);
<UiInput v-model="source" placeholder="مثال: وب‌سایت، معرفی، نمایشگاه…" />
</UiField>
<UiField label="شیوه آشنایی دلخواه">
<UiInput v-model="sourceDetail" placeholder="توضیح آزاد" />
</UiField>
<UiField label="معرف">
<UiInput v-model="referrer" placeholder="نام معرف" />
</UiField>
<UiField label="کد دستی معامله">
<UiInput v-model="dealCode" />
</UiField>
<UiField label="احتمال موفقیت (درصد)">
<UiInput v-model="probability" type="number" min="0" max="100" />
</UiField>
<UiField label="برچسب‌ها">
<UiInput v-model="tagsRaw" placeholder="سازمانی، فوری (با کاما جدا کنید)" />
</UiField>
<UiField v-if="!isNew" label="تاریخ انجام معامله">
<JalaliDateInput v-model="closedAt" placeholder="انتخاب تاریخ" />
</UiField>
<UiField v-if="!isNew" label="دلیل شکست (در صورت باخت معامله)" class="crm-col-span-full">
<UiInput id="lostReason" v-model="lostReason" placeholder="علت از دست رفتن معامله را بنویسید…" />
</UiField>
<UiField v-if="!isNew" label="یادداشت شکست" class="crm-col-span-full">
<UiInput v-model="lostNote" />
</UiField>
<p v-if="!isNew && (createdByName || sourceRef)" class="hint crm-col-span-full">
<span v-if="createdByName">ثبت‌کننده: {{ createdByName }}</span>
<span v-if="createdByName && sourceRef"> · </span>
<span v-if="sourceRef">کد دیدار: {{ toFaDigits(sourceRef) }}</span>
</p>
</div>
<!-- بخش محصولات / خدمات -->
+20 -1
View File
@@ -24,7 +24,22 @@ import UiModal from '@/components/ui/UiModal.vue';
import DealFormView from '@/views/DealFormView.vue';
import SavedFiltersBar from '@/components/SavedFiltersBar.vue';
import type { ImportContactsResult, PipelineBoardDto, PipelineDto } from '@mento/shared';
import { brand } from '@mento/shared';
import {
DEAL_EXCEL_ALIASES,
DEAL_EXCEL_COLUMNS,
EXCEL_CUSTOM_FIELD_PREFIX,
brand,
} from '@mento/shared';
const dealExcelFields = DEAL_EXCEL_COLUMNS.filter((c) => c.key !== '_type').map((c) => ({
key: c.key,
label: c.header,
required: c.key === 'title',
}));
const dealClientReadOptions = {
rawValues: true,
customFieldPrefix: EXCEL_CUSTOM_FIELD_PREFIX,
};
import { useAuthStore } from '@/stores/auth';
import { canManageSettings } from '@/lib/roles';
@@ -506,6 +521,10 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
<ExcelImportFlow
preview-path="/deals/import/preview"
import-path="/deals/import"
rows-import-path="/deals/import/rows"
:client-aliases="DEAL_EXCEL_ALIASES"
:client-fields="dealExcelFields"
:client-read-options="dealClientReadOptions"
:disabled="loading || busy"
@start="onImportStart"
@done="onImportDone"