# -*- coding: utf-8 -*- """Читает дамп Bitrix db.sql и генерирует seed.sql для новой схемы dana.""" import sqlread, io, re, html P = 'db.sql' OUT = io.open('seed.sql', 'w', encoding='utf-8') def esc(s): if s is None: return "NULL" # В PostgreSQL со standard_conforming_strings обратный слэш — обычный символ, # удваивать его нельзя: имена приезжали в базу с лишним слэшем. return "'" + str(s).replace("'", "''") + "'" def num(s): if s is None or s == '': return "NULL" m = re.search(r'-?\d+[.,]?\d*', str(s).replace(' ', '')) if not m: return "NULL" return m.group(0).replace(',', '.') def clean(s): if not s: return '' return html.unescape(s).strip() def w(line=''): OUT.write(line + '\n') # ---- собрать всё в память ---- sections = {} # (iblock, id) -> row for r in sqlread.rows(P, 'b_iblock_section'): sections[(r['IBLOCK_ID'], r['ID'])] = r elements = {} # id -> row (только нужные инфоблоки) NEED_IB = {'30', '29', '24', '50'} for r in sqlread.rows(P, 'b_iblock_element'): if r['IBLOCK_ID'] in NEED_IB: elements[r['ID']] = r # свойства: (element_id, prop_id) -> [values] props = {} NEED_PROP = { # услуги '339', '377', '360', '345', # врачи '312', '310', '327', '328', '329', '330', '331', '332', '333', '725', '325', '309', # цены '192', '195', # чек-апы '697', } for r in sqlread.rows(P, 'b_iblock_element_property'): pid = r.get('IBLOCK_PROPERTY_ID') if pid in NEED_PROP: eid = r.get('IBLOCK_ELEMENT_ID') v = r.get('VALUE') if v is None or v == '': continue props.setdefault((eid, pid), []).append(v) def prop1(eid, pid): vs = props.get((eid, pid)) return vs[0] if vs else None def propN(eid, pid): return props.get((eid, pid), []) w("-- Сгенерировано export_seed.py из бэкапа Bitrix 28.08.2026") w("BEGIN;") w() # ---------- УСЛУГИ (iblock 30) ---------- w("-- Направления (секции iblock 30)") svc_dir_code = {} # section_id -> code for (ib, sid), s in sections.items(): if ib != '30' or s.get('ACTIVE') != 'Y': continue code = s.get('CODE') or ('dir-' + sid) svc_dir_code[sid] = code w("INSERT INTO services (slug, path, name, sort, published, show_in_menu, legacy_id) " "VALUES (%s, %s, %s, %s, true, true, %s) ON CONFLICT (path) DO NOTHING;" % ( esc(code), esc(code), esc(clean(s.get('NAME'))), num(s.get('SORT')) or '100', 'NULL')) w() w("-- Услуги (элементы iblock 30)") for eid, e in elements.items(): if e['IBLOCK_ID'] != '30' or e.get('ACTIVE') != 'Y': continue sid = e.get('IBLOCK_SECTION_ID') dir_code = svc_dir_code.get(sid) if not dir_code: continue code = e.get('CODE') or ('svc-' + eid) path = dir_code + '/' + code lead = clean(e.get('PREVIEW_TEXT')) detail = e.get('DETAIL_TEXT') or '' blocks = "'[]'::jsonb" if not detail.strip() else \ "jsonb_build_array(jsonb_build_object('type','html','html'," + esc(detail) + "))" w("INSERT INTO services (parent_id, slug, path, name, lead, blocks, sort, published, legacy_id) " "SELECT p.id, %s, %s, %s, %s, %s, %s, true, %s FROM services p WHERE p.path=%s " "ON CONFLICT (path) DO NOTHING;" % ( esc(code), esc(path), esc(clean(e.get('NAME'))), esc(lead), blocks, num(e.get('SORT')) or '100', eid, esc(dir_code))) w() # ---------- СПЕЦИАЛЬНОСТИ + ВРАЧИ (iblock 29) ---------- w("-- Специальности (секции iblock 29)") spec_code = {} for (ib, sid), s in sections.items(): if ib != '29' or s.get('ACTIVE') != 'Y': continue code = s.get('CODE') or ('spec-' + sid) spec_code[sid] = code name_pl = clean(s.get('NAME')) w("INSERT INTO specialties (slug, name, name_plural, sort, published) " "VALUES (%s, %s, %s, %s, true) ON CONFLICT (slug) DO NOTHING;" % ( esc(code), esc(name_pl), esc(name_pl), num(s.get('SORT')) or '100')) w() w("-- Врачи (элементы iblock 29)") for eid, e in elements.items(): if e['IBLOCK_ID'] != '29' or e.get('ACTIVE') != 'Y': continue fio = clean(e.get('NAME')).split() last = fio[0] if fio else clean(e.get('NAME')) first = fio[1] if len(fio) > 1 else '' middle = ' '.join(fio[2:]) if len(fio) > 2 else '' code = e.get('CODE') or ('doc-' + eid) post = clean(prop1(eid, '312')) qual = clean(prop1(eid, '327')) work = clean(prop1(eid, '328')) bio = clean(prop1(eid, '329')) medflex = clean(prop1(eid, '725')) hide = prop1(eid, '333') provider = 'medflex' if medflex else 'none' # Стаж («10 лет») переводим в год начала практики от даты последней правки # карточки. Раньше текст стажа уходил в quote — цитату, а не стаж. start_year = 'NULL' m_work = re.search(r'(\d+)', work or '') ts = e.get('TIMESTAMP_X') or '' if m_work and re.match(r'\d{4}', ts) and 0 < int(m_work.group(1)) < 70: start_year = str(int(ts[:4]) - int(m_work.group(1))) w("INSERT INTO doctors (slug, last_name, first_name, middle_name, position, category, " "practice_start_year, bio, booking_provider, medflex_user, sort, published, legacy_id) " "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,true,%s) ON CONFLICT (slug) DO NOTHING;" % ( esc(code), esc(last), esc(first), esc(middle), esc(post), esc(qual), start_year, esc(bio), esc(provider), esc(medflex), num(e.get('SORT')) or '100', eid)) # специальность врача = его секция sid = e.get('IBLOCK_SECTION_ID') if sid in spec_code: w("INSERT INTO doctor_specialties (doctor_id, specialty_id, is_primary) " "SELECT d.id, sp.id, true FROM doctors d, specialties sp " "WHERE d.legacy_id=%s AND sp.slug=%s ON CONFLICT DO NOTHING;" % (eid, esc(spec_code[sid]))) # услуги врача (LINK_SERVICES=325 -> element id услуги) for sv in propN(eid, '325'): w("INSERT INTO doctor_services (doctor_id, service_id) " "SELECT d.id, s.id FROM doctors d, services s " "WHERE d.legacy_id=%s AND s.legacy_id=%s ON CONFLICT DO NOTHING;" % (eid, esc(sv))) w() # ---------- ЦЕНЫ (iblock 24) ---------- w("-- Разделы прайса (секции iblock 24)") price_sec_code = {} for (ib, sid), s in sections.items(): if ib != '24' or s.get('ACTIVE') != 'Y': continue code = s.get('CODE') or ('price-' + sid) price_sec_code[sid] = (code, s.get('IBLOCK_SECTION_ID')) w("INSERT INTO price_sections (slug, name, sort, published, legacy_id) " "VALUES (%s,%s,%s,true,%s) ON CONFLICT (slug) DO NOTHING;" % ( esc(code), esc(clean(s.get('NAME'))), num(s.get('SORT')) or '100', sid)) w("-- родители разделов прайса") for sid, (code, parent) in price_sec_code.items(): if parent and parent in price_sec_code: w("UPDATE price_sections SET parent_id=(SELECT id FROM price_sections WHERE legacy_id=%s) " "WHERE legacy_id=%s;" % (parent, sid)) w() w("-- Позиции прайса (элементы iblock 24)") for eid, e in elements.items(): if e['IBLOCK_ID'] != '24' or e.get('ACTIVE') != 'Y': continue sid = e.get('IBLOCK_SECTION_ID') if sid not in price_sec_code: continue raw = prop1(eid, '192') or prop1(eid, '195') price = num(raw) if price == 'NULL': continue old = num(prop1(eid, '193')) w("INSERT INTO price_items (section_id, name, price, sort, published, legacy_id) " "SELECT ps.id, %s, %s, %s, true, %s FROM price_sections ps WHERE ps.legacy_id=%s;" % ( esc(clean(e.get('NAME'))), price, num(e.get('SORT')) or '100', eid, sid)) w() # ---------- связь цена<->услуга через LINK_PRICES (360) на услуге ---------- w("-- Привязка позиций прайса к услугам (LINK_PRICES на услуге)") for eid, e in elements.items(): if e['IBLOCK_ID'] != '30' or e.get('ACTIVE') != 'Y': continue for pr in propN(eid, '360'): w("UPDATE price_items SET service_id=(SELECT id FROM services WHERE legacy_id=%s) " "WHERE legacy_id=%s AND service_id IS NULL;" % (eid, esc(pr))) w() # ---------- ЧЕК-АПЫ (iblock 50) ---------- w("-- Чек-апы (элементы iblock 50)") checkup_aud = {} for (ib, sid), s in sections.items(): if ib == '50': checkup_aud[sid] = 'kids' if 'етск' in (s.get('NAME') or '') else 'adults' for eid, e in elements.items(): if e['IBLOCK_ID'] != '50' or e.get('ACTIVE') != 'Y': continue code = e.get('CODE') or ('checkup-' + eid) aud = checkup_aud.get(e.get('IBLOCK_SECTION_ID'), 'adults') lead = clean(e.get('PREVIEW_TEXT')) detail = e.get('DETAIL_TEXT') or '' blocks = "'[]'::jsonb" if not detail.strip() else \ "jsonb_build_array(jsonb_build_object('type','html','html'," + esc(detail) + "))" w("INSERT INTO checkups (slug, audience, name, lead, blocks, sort, published, legacy_id) " "VALUES (%s,%s,%s,%s,%s,%s,true,%s) ON CONFLICT (slug) DO NOTHING;" % ( esc(code), esc(aud), esc(clean(e.get('NAME'))), esc(lead), blocks, num(e.get('SORT')) or '100', eid)) w() w("COMMIT;") OUT.close() print("seed.sql written")