fix(autopodbor): разворот глубина 1 по умолчанию + массовое удаление предложений

Разворот по телефонам 2ГИС и Яндекс больше не уходит цепочкой «до талого»:
по умолчанию берём только фирмы с ЛИЧНЫМ номером фирмы — глубина 1. Именно
неограниченная цепочка уводила в чужие фирмы, давая мусорные сайты в
предложениях холдинга. Многошаговый разворот сохранён за параметром maxDepth,
по умолчанию 1. Изолировано: работает только под флагом phone_reverse, основной
пул сбора не затрагивается. Бэкенд автоподбора 430 из 430.

Фронт: на вкладке «Предложения» добавлена кнопка массового удаления выбранных
источников — bulkDeleteProps через removeSource, покрыта тестами.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-05 15:33:31 +03:00
parent 4e2129227c
commit dda18d7cd6
2 changed files with 45 additions and 1 deletions
@@ -205,6 +205,24 @@ async function bulkMoveToWork() {
}
}
// ——— массовое удаление предложений (вкладка «Предложения») ———
// Предложения ещё не в работе (проекта нет, лиды не заказаны), поэтому удаляем сразу,
// без grace-модалки. Удалённые попадают в «недавно удалённые» и восстановимы (restoreDeleted).
async function bulkDeleteProps() {
if (busy.value || nav.ctx.competitorId === null) return;
busy.value = true;
try {
for (const s of selProps.value) {
await store.removeSource(nav.ctx.competitorId, s.id);
}
selected.value = [];
await reload();
flash('Предложения удалены');
} finally {
busy.value = false;
}
}
// ——— массовые действия (работа) ———
const selWork = computed(() => inWork.value.filter((s) => selected.value.includes(s.id)));
const selAllHaveProj = computed(() => selWork.value.length > 0 && selWork.value.every((s) => s.project));
@@ -691,7 +709,7 @@ onMounted(async () => {
}
});
defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, toggle, openEdit, openCollect, openAdd, openCreate, openBulkCreate, doCreate, toggleProject, bulkWork, bulkMoveToWork, moveToWork, sourceValue });
defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, toggle, openEdit, openCollect, openAdd, openCreate, openBulkCreate, doCreate, toggleProject, bulkWork, bulkMoveToWork, bulkDeleteProps, moveToWork, sourceValue });
</script>
<template>
@@ -862,6 +880,7 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
<span>Выбрано предложений: <b>{{ selected.length }}</b> перенесём в источники разом</span>
<div class="ld-bulkbar__acts">
<button class="ld-btn gray sm" @click="clearSel">Снять выбор</button>
<button class="ld-btn danger sm" :disabled="busy" @click="bulkDeleteProps"> Удалить</button>
<button class="ld-btn primary sm" :disabled="busy" @click="bulkMoveToWork">Перенести выбранные в источники </button>
</div>
</div>
@@ -75,4 +75,29 @@ describe('FieldCompetitorScreen — массовые действия на вк
expect(spy).toHaveBeenCalledWith(10, 2, 'field');
expect(spy).toHaveBeenCalledTimes(2);
});
it('в панели массовых действий предложений есть кнопка «Удалить»', async () => {
const { wrapper } = await mountWithProposals();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.switchTab('sugg');
vm.selected = [1, 2];
await wrapper.vm.$nextTick();
const delBtn = wrapper.findAll('.ld-bulkbar button').find((b) => b.text().includes('Удалить'));
expect(delBtn).toBeTruthy();
});
it('bulkDeleteProps удаляет каждый выбранный источник через removeSource', async () => {
const { wrapper, store } = await mountWithProposals();
const spy = vi.spyOn(store, 'removeSource').mockResolvedValue();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
vm.switchTab('sugg');
vm.selected = [1, 2];
await wrapper.vm.$nextTick();
await vm.bulkDeleteProps();
expect(spy).toHaveBeenCalledWith(10, 1);
expect(spy).toHaveBeenCalledWith(10, 2);
expect(spy).toHaveBeenCalledTimes(2);
});
});