feat: customer groups for people and companies with tahesab group sync and bulk register

Multiple groups per contact, legacy group text migrated, excel import/export, contacts filter and column, richer create-in-tahesab, match-first bulk export
This commit is contained in:
2026-10-21 17:16:31 +03:30
parent ce27df20ca
commit 29d99cbded
27 changed files with 1728 additions and 26 deletions
+24
View File
@@ -38,6 +38,7 @@ import type { CompanyDto, CreatePersonRequest, PersonDto, PersonMood } from '@me
import { PERSON_MOOD_LABELS, PERSON_MOODS } from '@/lib/person-mood';
import { formatLocalDateInput, toFaDigits } from '@/lib/jalali';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import { useCustomerGroups } from '@/lib/customer-groups';
import {
filterPhoneContacts,
isContactPickerSupported,
@@ -97,7 +98,13 @@ const notes = ref('');
const tagsRaw = ref('');
const companyId = ref('');
const mood = ref<PersonMood>('newcomer');
const groupIds = ref<string[]>([]);
const customFields = ref<Record<string, unknown>>({});
const { groups: customerGroups, loaded: groupsLoaded, load: loadCustomerGroups } =
useCustomerGroups();
const groupOptions = computed(() =>
customerGroups.value.map((g) => ({ value: g.id, label: g.name })),
);
const loyaltyPoints = ref(0);
const loyaltyTierName = ref('');
@@ -257,6 +264,7 @@ function fill(p: PersonDto) {
tagsRaw.value = (p.tags ?? []).join('، ');
companyId.value = p.company?.id ?? '';
mood.value = p.mood || 'newcomer';
groupIds.value = (p.groups ?? []).map((g) => g.id);
customFields.value = { ...(p.customFields ?? {}) };
loyaltyPoints.value = p.loyaltyPoints ?? 0;
loyaltyTierName.value = p.loyaltyTier?.name ?? '';
@@ -277,6 +285,7 @@ function removeAddress(index: number) {
async function load() {
loading.value = true;
error.value = '';
void loadCustomerGroups().catch(() => undefined);
try {
const companiesRes = await api.get<{ items: CompanyDto[] }>('/companies', {
params: { limit: 100 },
@@ -334,6 +343,7 @@ function payload(): CreatePersonRequest {
tags: parseTags(tagsRaw.value),
companyId: companyId.value || null,
mood: mood.value,
groupIds: groupIds.value,
customFields: customFields.value,
};
}
@@ -558,6 +568,20 @@ onMounted(load);
/>
</a-form-item>
</a-col>
<a-col :xs="24" :md="12">
<a-form-item label="گروه‌ها">
<a-select
v-model:value="groupIds"
mode="multiple"
allow-clear
option-filter-prop="label"
placeholder="بدون گروه"
:options="groupOptions"
:not-found-content="groupsLoaded ? 'گروهی تعریف نشده' : undefined"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :xs="24" :md="12">
<a-form-item label="برچسب‌ها">
<a-input