230 lines
9.5 KiB
Python
230 lines
9.5 KiB
Python
# -*- 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")
|