diff --git a/app/resources/js/components/sales/SalesProspectDialog.vue b/app/resources/js/components/sales/SalesProspectDialog.vue new file mode 100644 index 00000000..49dc9609 --- /dev/null +++ b/app/resources/js/components/sales/SalesProspectDialog.vue @@ -0,0 +1,135 @@ + + + + + + {{ prospect.firm_name }} + {{ stageMeta(prospect.stage).title }} · {{ prospect.city }} + + + Телефон: {{ prospect.phone ?? '—' }} + + Сайт: + {{ prospect.site }} + — + + ИНН: {{ prospect.inn }} + E-mail: {{ prospect.registered_email }} + След. созвон: {{ prospect.next_call_at }} + Причина: {{ prospect.reason }} + + + + + + + + {{ error }} + + + + Закрыть + Сохранить + + + + diff --git a/app/tests/Frontend/SalesProspectDialog.spec.ts b/app/tests/Frontend/SalesProspectDialog.spec.ts new file mode 100644 index 00000000..9b544c56 --- /dev/null +++ b/app/tests/Frontend/SalesProspectDialog.spec.ts @@ -0,0 +1,77 @@ +import { mount } from '@vue/test-utils'; +import { createVuetify } from 'vuetify'; +import { describe, expect, it } from 'vitest'; +import SalesProspectDialog from '../../resources/js/components/sales/SalesProspectDialog.vue'; +import type { SalesProspect } from '../../resources/js/api/sales'; + +const vuetify = createVuetify(); + +function prospect(overrides: Partial = {}): SalesProspect { + return { + id: 1, + sales_user_id: 1, + stage: 'new', + firm_name: 'ООО Тест', + city: 'Ростов', + phone: '+7800', + site: 'test.ru', + inn: '6161', + rating_label: 'горячая', + payload: {}, + next_call_at: null, + reason: null, + registered_email: null, + notes: null, + ...overrides, + }; +} + +interface DialogVm { + action: string; + reason: string; + nextCallAt: string; + siteHref: string | null; + availableActions: { value: string; title: string }[]; + submit: () => void; +} + +function factory(p: SalesProspect) { + return mount(SalesProspectDialog, { + props: { modelValue: true, prospect: p }, + global: { plugins: [vuetify] }, + }); +} + +describe('SalesProspectDialog', () => { + it('siteHref добавляет https к домену', () => { + const w = factory(prospect()); + expect((w.vm as unknown as DialogVm).siteHref).toBe('https://test.ru'); + }); + + it('для stage=user действие «Отказ» недоступно', () => { + const w = factory(prospect({ stage: 'user' })); + const values = (w.vm as unknown as DialogVm).availableActions.map((a) => a.value); + expect(values).not.toContain('rejected'); + }); + + it('для обычной стадии «Отказ» доступен', () => { + const w = factory(prospect({ stage: 'negotiation' })); + const values = (w.vm as unknown as DialogVm).availableActions.map((a) => a.value); + expect(values).toContain('rejected'); + }); + + it('«Переговоры» без даты — submit не эмитит save', () => { + const w = factory(prospect()); + (w.vm as unknown as DialogVm).action = 'negotiation'; + (w.vm as unknown as DialogVm).submit(); + expect(w.emitted('save')).toBeFalsy(); + }); + + it('«Отказ» с причиной — эмитит save с action=rejected', () => { + const w = factory(prospect({ stage: 'negotiation' })); + (w.vm as unknown as DialogVm).action = 'rejected'; + (w.vm as unknown as DialogVm).reason = 'дорого'; + (w.vm as unknown as DialogVm).submit(); + expect(w.emitted('save')?.[0]?.[0]).toMatchObject({ action: 'rejected', reason: 'дорого' }); + }); +}); diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index 79b2ff08..63b8b652 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-07-15T13:36:04.742Z +Last updated: 2026-07-15T13:38:18.903Z | Контролёр | Состояние | Детали | |---|---|---| @@ -112,9 +112,9 @@ Episodes since last run: 542 / threshold: 10 | PID | Имя | CPU-время | Возраст | |---|---|---|---| -| 3488 | MsMpEng | 6.91ч | 0.0ч | -| 9756 | Code | 3.18ч | 0.0ч | -| 1320 | svchost | 1.33ч | 1328341.6ч | +| 3488 | MsMpEng | 6.92ч | NaNч | +| 9756 | Code | 3.19ч | NaNч | +| 1320 | svchost | 1.33ч | NaNч | ⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.