package api import ( "context" "fmt" "time" "dana/internal/bitrix" ) // bitrixSourceInfo — сколько записей и номеров забрали из Битрикс24 для «Отчёта по каналам». type bitrixSourceInfo struct { Records int `json:"records"` // лидов и контактов обработано всего Phones int `json:"phones"` // из них с номером телефона, попавших в отчёт Skipped int `json:"skipped"` // лидов с источником другой клиники (портал общий) или служебным — не учтены } // bitrixChannelBySource — источники лидов DANA (crm.status.list, ENTITY_ID=SOURCE) → канал отчёта. // Белый список осознанно: в этом Битриксе есть вторая компания («Очевидно») со своими источниками — // звонки/заявки не из этого списка в отчёт не попадают, даже если выглядят похоже. var bitrixChannelBySource = map[string]string{ "132": "2гис", "137": "2гис", // 137 — тот же номер, но через WhatsApp "134": "сайт", "136": "сайт", "112": "сайт", "135": "яндекс", "138": "яндекс", "143": "продокторов", "142": "продокторов", "144": "сберздоровье", "145": "zoon", "146": "напоправку", "147": "докту", "154": "инстаграмм", "155": "инстаграмм", "156": "инстаграмм", "157": "инстаграмм", "158": "инстаграмм", "20": "инстаграмм", "23": "инстаграмм", "26": "инстаграмм", "44|PACT_INSTAGRAM_BUSINESS_CONNECTOR_101559": "инстаграмм", "14|PACT_WHATSAPP_CONNECTOR_66018": "инстаграмм", // WhatsApp на 047-00-55: заявки приходят из инстаграма "10|PACT_INSTAGRAM_BUSINESS_CONNECTOR_66018": "инстаграмм", "STORE": "баннер", "166": "баннер", "159": "маршрутки", "160": "тв", "148": "google", "149": "google", "161": "радио", "162": "полиграфия", "140": "ролик", "119": "max", "27": "telegram", "WZ0bdb58c8-907e-4632-84b0-81a30d250588": "вк", "133|VK": "вк", } // bitrixGeneralSources — общая линия DANA без привязки к рекламному каналу: идёт в «все номера», // как колл-центр без атрибуции (так же, как необработанные линии АТС). var bitrixGeneralSources = map[string]bool{ "113": true, // звонки на мегафоновский 047-00-55 } // bitrixMatchOpts — выгрузка номеров из Битрикс24 для «Отчёта по каналам» за период [from, to): // лиды раскладываются по каналам через SOURCE_ID (белый список выше), контакты (у них источника // нет) идут в «все номера». Период обязателен — без него пришлось бы тянуть всю историю CRM. func (a *API) bitrixMatchOpts(ctx context.Context, clinic *clinicDef, from, to time.Time, onProgress func(phase string, done, total int)) (matchOpts, error) { if !a.bitrix.Configured() { return matchOpts{}, fmt.Errorf("Битрикс24 не подключён: нет BITRIX_WEBHOOK_URL") } phase := func(name string) bitrix.Progress { if onProgress == nil { return nil } return func(done, total int) { onProgress(name, done, total) } } leads, err := a.bitrix.Leads(ctx, from, to, phase("leads")) if err != nil { return matchOpts{}, fmt.Errorf("Не удалось загрузить лиды из Битрикс24: %v", err) } var contacts []bitrix.Record if clinic.bitrixContacts { if contacts, err = a.bitrix.Contacts(ctx, from, to, phase("contacts")); err != nil { return matchOpts{}, fmt.Errorf("Не удалось загрузить контакты из Битрикс24: %v", err) } } lists := map[string][]string{} phones, skipped := 0, 0 for _, l := range leads { switch { case clinic.bitrixSources[l.SourceID] != "": ch := clinic.bitrixSources[l.SourceID] lists[ch] = append(lists[ch], l.Phones...) phones += len(l.Phones) case clinic.bitrixGeneral[l.SourceID]: lists["все номера"] = append(lists["все номера"], l.Phones...) phones += len(l.Phones) default: skipped++ // источник другой клиники либо служебный (CALL, WEBFORM…) — не учитываем } } for _, c := range contacts { lists["все номера"] = append(lists["все номера"], c.Phones...) phones += len(c.Phones) } if phones == 0 { return matchOpts{}, fmt.Errorf("В Битрикс24 не нашлось ни одного номера %s за период — проверьте права вебхука", clinic.Title) } return matchOpts{pbxLists: lists, bitrixInfo: &bitrixSourceInfo{ Records: len(leads) + len(contacts), Phones: phones, Skipped: skipped, }}, nil } // mergeChannelLists объединяет номера каналов из двух источников (например, АТС и Битрикс24) // в один — списки одноимённых каналов складываются, а не перезаписываются. func mergeChannelLists(dst, src map[string][]string) map[string][]string { if dst == nil { dst = map[string][]string{} } for k, v := range src { dst[k] = append(dst[k], v...) } return dst }