68428f16c6
feat(sales): карточка кандидата — девять полей владельца + правка контактных лиц
Владелец задал список полей карточки и попросил дать менеджеру записывать
контактных лиц прямо в карточке: поиск отдаёт 5-7 ПРЕДПОЛАГАЕМЫХ номеров
директора, живой из них выясняется обзвоном; а если директор перенаправил
к маркетологу — менеджер фиксирует и его, людей может быть 2-3.
- Карточка показывает РОВНО девять полей в его порядке: Юрлицо, Сайт, ИНН фирмы,
Адрес, Телефон общий, Каналы, Бюджет, Директор, Тел. директора (предполагаемый).
Убраны Оценка, Достоверность, Реклама, Запросы в Директе, ОГРН, Статус юрлица,
Личный ИНН директора, Почта директора — данные остаются в payload.
- PATCH /api/sales/prospects/{id}/contacts — список заменяется целиком, пустой
стирает всех; права как у update() (менеджер свои, начальник любые); главный
телефон карточки из поиска не трогается.
- ProspectContactsEditor.vue + utils/prospectContacts.ts — один редактор контактов
на диалог создания и карточку; диалог создания переведён на него.
- Закрывает хвост §16: у 24 живых карточек из поиска контактных лиц не было,
теперь дозаполняются руками.
Спека §18. Гейты: Pest 223/223 sales, Vitest 196 файлов / 1397 тестов,
vue-tsc чисто, Larastan 0 в своих файлах. TDD RED→GREEN на каждом шаге.
LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
83 lines
3.3 KiB
TypeScript
83 lines
3.3 KiB
TypeScript
import { mount } from '@vue/test-utils';
|
|
import { createVuetify } from 'vuetify';
|
|
import { describe, expect, it } from 'vitest';
|
|
import ProspectContactsEditor from '../../resources/js/components/sales/ProspectContactsEditor.vue';
|
|
import { cleanContacts, toForm, type ContactForm } from '../../resources/js/utils/prospectContacts';
|
|
|
|
const vuetify = createVuetify();
|
|
|
|
interface EditorVm {
|
|
rows: ContactForm[];
|
|
addContact: () => void;
|
|
removeContact: (i: number) => void;
|
|
addPhone: (i: number) => void;
|
|
removePhone: (i: number, j: number) => void;
|
|
}
|
|
|
|
function factory(modelValue: ContactForm[] = [{ name: '', position: '', phones: [''] }]) {
|
|
const w = mount(ProspectContactsEditor, { props: { modelValue }, global: { plugins: [vuetify] } });
|
|
return { w, vm: w.vm as unknown as EditorVm };
|
|
}
|
|
|
|
describe('ProspectContactsEditor', () => {
|
|
it('добавляет и убирает людей', () => {
|
|
const { vm } = factory();
|
|
expect(vm.rows).toHaveLength(1);
|
|
|
|
vm.addContact();
|
|
expect(vm.rows).toHaveLength(2);
|
|
|
|
vm.removeContact(1);
|
|
expect(vm.rows).toHaveLength(1);
|
|
});
|
|
|
|
it('последнего человека убрать нельзя — остаётся пустая строка', () => {
|
|
const { vm } = factory();
|
|
vm.removeContact(0);
|
|
expect(vm.rows).toHaveLength(1);
|
|
expect(vm.rows[0].name).toBe('');
|
|
});
|
|
|
|
it('добавляет и убирает телефоны, последний остаётся пустой строкой', () => {
|
|
const { vm } = factory();
|
|
vm.addPhone(0);
|
|
expect(vm.rows[0].phones).toHaveLength(2);
|
|
|
|
vm.removePhone(0, 1);
|
|
expect(vm.rows[0].phones).toHaveLength(1);
|
|
|
|
vm.removePhone(0, 0);
|
|
expect(vm.rows[0].phones).toEqual(['']);
|
|
});
|
|
|
|
it('изменения уходят наверх через update:modelValue', () => {
|
|
const { w, vm } = factory();
|
|
vm.addContact();
|
|
expect(w.emitted('update:modelValue')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('утилиты контактов', () => {
|
|
it('toForm: пустой список → одна пустая строка', () => {
|
|
expect(toForm([])).toEqual([{ name: '', position: '', phones: [''] }]);
|
|
expect(toForm(null)).toHaveLength(1);
|
|
});
|
|
|
|
it('toForm: сохранённые контакты разворачиваются в форму', () => {
|
|
const rows = toForm([{ name: 'Захаров', position: 'директор', phones: ['+79136518088'] }]);
|
|
expect(rows[0]).toEqual({ name: 'Захаров', position: 'директор', phones: ['+79136518088'] });
|
|
});
|
|
|
|
it('toForm: контакт без телефонов получает пустую заготовку', () => {
|
|
expect(toForm([{ name: 'Без телефона', position: null, phones: [] }])[0].phones).toEqual(['']);
|
|
});
|
|
|
|
it('cleanContacts: мусор выброшен, должность пустая → null', () => {
|
|
const out = cleanContacts([
|
|
{ name: ' ', position: '', phones: ['', ' '] },
|
|
{ name: 'Сидоров', position: ' ', phones: ['+79130000009', ''] },
|
|
]);
|
|
expect(out).toEqual([{ name: 'Сидоров', position: null, phones: ['+79130000009'] }]);
|
|
});
|
|
});
|