feat(booking): automatically link 1C appointment to Bitrix24 lead ID and implement do_DELETE
This commit is contained in:
parent
2d803fcd28
commit
6597464ac3
@ -456,6 +456,7 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
|
||||
if path == "/appointments":
|
||||
doctor_id = req_json.get("doctor_id", "")
|
||||
bitrix_lead_id = str(req_json.get("bitrix_lead_id", "")).strip()
|
||||
branch_id = req_json.get("branch_id", "")
|
||||
patient_name = req_json.get("patient_name", "")
|
||||
patient_phone = req_json.get("patient_phone", "")
|
||||
@ -620,11 +621,23 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
Попытка Док.БИТ_ИсточникИнформации = Ист; Исключение КонецПопытки;
|
||||
КонецЕсли;
|
||||
|
||||
Если ЗначениеЗаполнено("{bitrix_lead_id}") Тогда
|
||||
Попытка
|
||||
Док.бит_Б24_СозданИзБитрикс24 = Истина;
|
||||
Док.бит_Б24_БитриксИД = "{bitrix_lead_id}";
|
||||
Док.бит_НомерКлиентаБ24 = "{bitrix_lead_id}";
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
КонецЕсли;
|
||||
|
||||
Док.Записать(РежимЗаписиДокумента.Запись);
|
||||
ID = НРег(XMLСтрока(Док.Ссылка));
|
||||
ID_B64 = Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(ID));
|
||||
|
||||
Ответ = Новый Структура;
|
||||
Ответ.Вставить("appointment_id", ID);
|
||||
Ответ.Вставить("appointment_id_b64", ID_B64);
|
||||
Ответ.Вставить("number", Док.Номер);
|
||||
Ответ.Вставить("status", "booked");
|
||||
Ответ.Вставить("start", Формат(Док.ДатаНачала, "ДФ=yyyy-MM-ddTHH:mm:ss") + "+03:00");
|
||||
Ответ.Вставить("end", Формат(Док.ДатаОкончания, "ДФ=yyyy-MM-ddTHH:mm:ss") + "+03:00");
|
||||
@ -640,12 +653,165 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
||||
КонецПопытки;
|
||||
"""
|
||||
data, err = call_1c(code)
|
||||
if isinstance(data, dict) and "appointment_id_b64" in data:
|
||||
import base64
|
||||
try:
|
||||
data["appointment_id"] = base64.b64decode(data["appointment_id_b64"]).decode("utf-8").strip()
|
||||
except Exception:
|
||||
pass
|
||||
if err or (isinstance(data, str) and data.startswith("ОШИБКА")):
|
||||
self.send_json(500, {"code": "booking_failed", "message": err or data})
|
||||
else:
|
||||
self.send_json(201, data)
|
||||
return
|
||||
|
||||
if path == "/appointments/link-bitrix":
|
||||
appointment_id = str(req_json.get("appointment_id", "")).strip()
|
||||
bitrix_lead_id = str(req_json.get("bitrix_lead_id", "")).strip()
|
||||
if not appointment_id or not bitrix_lead_id:
|
||||
self.send_json(400, {"code": "bad_request", "message": "appointment_id and bitrix_lead_id are required"})
|
||||
return
|
||||
|
||||
code = f"""
|
||||
Попытка
|
||||
ДокСсылка = Неопределено;
|
||||
Попытка
|
||||
ДокСсылка = Документы.Заявка.ПолучитьСсылку(Новый УникальныйИдентификатор("{appointment_id}"));
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Попытка
|
||||
ДокСсылка = Документы.Заявка.НайтиПоНомеру("{appointment_id}", ТекущаяДата());
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
КонецЕсли;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Запрос = Новый Запрос;
|
||||
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Документ.Заявка ГДЕ Номер = &Номер УПОРЯДОЧИТЬ ПО Дата УБЫВ";
|
||||
Запрос.УстановитьПараметр("Номер", "{appointment_id}");
|
||||
Выб = Запрос.Выполнить().Выбрать();
|
||||
Если Выб.Следующий() Тогда
|
||||
ДокСсылка = Выб.Ссылка;
|
||||
КонецЕсли;
|
||||
КонецЕсли;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Запрос = Новый Запрос;
|
||||
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 30 Ссылка ИЗ Документ.Заявка УПОРЯДОЧИТЬ ПО Дата УБЫВ";
|
||||
Выб = Запрос.Выполнить().Выбрать();
|
||||
Пока Выб.Следующий() Цикл
|
||||
СтрGUID = НРег(XMLСтрока(Выб.Ссылка));
|
||||
Если Найти("{appointment_id}", СтрGUID) > 0 ИЛИ (СтрДлина("{appointment_id}") > 20 И Найти(СтрGUID, Прав("{appointment_id}", 25)) > 0) Тогда
|
||||
ДокСсылка = Выб.Ссылка;
|
||||
Прервать;
|
||||
КонецЕсли;
|
||||
КонецЦикла;
|
||||
КонецЕсли;
|
||||
|
||||
Если ЗначениеЗаполнено(ДокСсылка) Тогда
|
||||
ДокОбъект = ДокСсылка.ПолучитьОбъект();
|
||||
Если ДокОбъект <> Неопределено Тогда
|
||||
ДокОбъект.бит_Б24_СозданИзБитрикс24 = Истина;
|
||||
ДокОбъект.бит_Б24_БитриксИД = "{bitrix_lead_id}";
|
||||
ДокОбъект.бит_НомерКлиентаБ24 = "{bitrix_lead_id}";
|
||||
ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
|
||||
Результат = "OK";
|
||||
Иначе
|
||||
Результат = "NOT_FOUND";
|
||||
КонецЕсли;
|
||||
Иначе
|
||||
Результат = "NOT_FOUND";
|
||||
КонецЕсли;
|
||||
Исключение
|
||||
Результат = "ОШИБКА: " + ОписаниеОшибки();
|
||||
КонецПопытки;
|
||||
"""
|
||||
data, err = call_1c(code)
|
||||
if err or (isinstance(data, str) and data.startswith("ОШИБКА")):
|
||||
self.send_json(500, {"code": "link_failed", "message": err or data})
|
||||
elif data == "NOT_FOUND":
|
||||
self.send_json(404, {"code": "not_found", "message": "Запись в 1С не найдена"})
|
||||
else:
|
||||
self.send_json(200, {"status": "ok", "message": "Связано успешно"})
|
||||
return
|
||||
|
||||
self.send_json(404, {"code": "not_found", "message": "Unknown endpoint"})
|
||||
|
||||
def do_DELETE(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
path = parsed.path.rstrip("/")
|
||||
|
||||
parts = path.split("/")
|
||||
if len(parts) == 3 and parts[1] == "appointments":
|
||||
appointment_id = urllib.parse.unquote(parts[2]).strip()
|
||||
code = f"""
|
||||
Попытка
|
||||
ДокСсылка = Неопределено;
|
||||
Попытка
|
||||
ДокСсылка = Документы.Заявка.ПолучитьСсылку(Новый УникальныйИдентификатор("{appointment_id}"));
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Попытка
|
||||
ДокСсылка = Документы.Заявка.НайтиПоНомеру("{appointment_id}", ТекущаяДата());
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
КонецЕсли;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Запрос = Новый Запрос;
|
||||
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Документ.Заявка ГДЕ Номер = &Номер УПОРЯДОЧИТЬ ПО Дата УБЫВ";
|
||||
Запрос.УстановитьПараметр("Номер", "{appointment_id}");
|
||||
Выб = Запрос.Выполнить().Выбрать();
|
||||
Если Выб.Следующий() Тогда
|
||||
ДокСсылка = Выб.Ссылка;
|
||||
КонецЕсли;
|
||||
КонецЕсли;
|
||||
|
||||
Если Не ЗначениеЗаполнено(ДокСсылка) ИЛИ ДокСсылка.ПолучитьОбъект() = Неопределено Тогда
|
||||
Запрос = Новый Запрос;
|
||||
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 30 Ссылка ИЗ Документ.Заявка УПОРЯДОЧИТЬ ПО Дата УБЫВ";
|
||||
Выб = Запрос.Выполнить().Выбрать();
|
||||
Пока Выб.Следующий() Цикл
|
||||
СтрGUID = НРег(XMLСтрока(Выб.Ссылка));
|
||||
Если Найти("{appointment_id}", СтрGUID) > 0 ИЛИ (СтрДлина("{appointment_id}") > 20 И Найти(СтрGUID, Прав("{appointment_id}", 25)) > 0) Тогда
|
||||
ДокСсылка = Выб.Ссылка;
|
||||
Прервать;
|
||||
КонецЕсли;
|
||||
КонецЦикла;
|
||||
КонецЕсли;
|
||||
|
||||
Если ЗначениеЗаполнено(ДокСсылка) Тогда
|
||||
ДокОбъект = ДокСсылка.ПолучитьОбъект();
|
||||
Если ДокОбъект <> Неопределено Тогда
|
||||
Попытка
|
||||
ДокОбъект.Состояние = Справочники.ВидыСостоянийЗаявок.Отменена;
|
||||
Исключение
|
||||
КонецПопытки;
|
||||
ДокОбъект.УстановитьПометкуУдаления(Истина);
|
||||
Результат = "OK";
|
||||
Иначе
|
||||
Результат = "NOT_FOUND";
|
||||
КонецЕсли;
|
||||
Иначе
|
||||
Результат = "NOT_FOUND";
|
||||
КонецЕсли;
|
||||
Исключение
|
||||
Результат = "ОШИБКА: " + ОписаниеОшибки();
|
||||
КонецПопытки;
|
||||
"""
|
||||
data, err = call_1c(code)
|
||||
if err or (isinstance(data, str) and data.startswith("ОШИБКА")):
|
||||
self.send_json(500, {"code": "cancel_failed", "message": err or data})
|
||||
elif data == "NOT_FOUND":
|
||||
self.send_json(404, {"code": "not_found", "message": "Заявка в 1С не найдена"})
|
||||
else:
|
||||
self.send_json(200, {"status": "cancelled", "message": "Запись отменена в 1С"})
|
||||
return
|
||||
|
||||
self.send_json(404, {"code": "not_found", "message": "Unknown endpoint"})
|
||||
|
||||
def run():
|
||||
|
||||
@ -188,6 +188,14 @@ func (a *API) processConfirmQueue(ctx context.Context, now time.Time) error {
|
||||
}
|
||||
it.BitrixLeadID = id
|
||||
slog.Info("booking confirm: лид создан", "appointment", it.ID, "lead", id)
|
||||
|
||||
if it.OnecID != "" {
|
||||
if err := a.onec.LinkBitrix(ctx, it.OnecID, id); err != nil {
|
||||
slog.Warn("booking confirm: не удалось связать 1С с Битриксом", "onec_id", it.OnecID, "lead", id, "err", err)
|
||||
} else {
|
||||
slog.Info("booking confirm: заявка в 1С связана с лидом Битрикса", "onec_id", it.OnecID, "lead", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
ids = append(ids, it.BitrixLeadID)
|
||||
}
|
||||
|
||||
@ -193,6 +193,18 @@ func (c *Client) Cancel(ctx context.Context, id string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) LinkBitrix(ctx context.Context, appointmentID, bitrixLeadID string) error {
|
||||
body, err := json.Marshal(map[string]string{
|
||||
"appointment_id": appointmentID,
|
||||
"bitrix_lead_id": bitrixLeadID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.do(ctx, http.MethodPost, "/appointments/link-bitrix", nil, body)
|
||||
return err
|
||||
}
|
||||
|
||||
// getList принимает ответ и в виде массива, и в виде объекта {"<key>": [...]}.
|
||||
func (c *Client) getList(ctx context.Context, path string, q url.Values, key string, out any) error {
|
||||
raw, err := c.do(ctx, http.MethodGet, path, q, nil)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user