# -*- coding: utf-8 -*- """Раскладывает прайс из 3 файлов 1С (ЦОМ/Остео/Дети) по направлениям сайта.""" import openpyxl, xlrd, io def parse_xlsx(path): wb = openpyxl.load_workbook(path, data_only=True) ws = wb[wb.sheetnames[0]] return [[('' if c is None else c) for c in row] for row in ws.iter_rows(values_only=True)] def parse_xls(path): wb = xlrd.open_workbook(path) ws = wb.sheet_by_index(0) return [[ws.cell_value(r, c) for c in range(ws.ncols)] for r in range(ws.nrows)] def extract(rows, name_col, price_col): out, section, started = [], None, False for row in rows: name = str(row[name_col]).strip() if name_col < len(row) else '' price_raw = row[price_col] if price_col < len(row) else '' if not started: if name.lower() == 'номенклатура': started = True continue if name == '' or name.lower() == 'прайс лист': continue is_price = isinstance(price_raw, (int, float)) and price_raw != '' if not is_price: section = name continue out.append((section, name, float(price_raw))) return out FILES = [ ('D:/ЦОМ.xlsx', 'xlsx', 0, 5), ('D:/Остео.xlsx', 'xlsx', 0, 5), ('D:/Дана Дети.xls', 'xls', 2, 4), ] all_items = [] for path, kind, ncol, pcol in FILES: rows = parse_xlsx(path) if kind == 'xlsx' else parse_xls(path) all_items.extend(extract(rows, ncol, pcol)) # ---------- карта: раздел 1С (lower, strip) -> slug целевого раздела прайса ---------- # существующие 22 раздела сайта используются как есть; для настоящих специальностей, # которых среди них не было, создаю новые разделы (см. NEW_SECTIONS ниже). MAP = {} def m(names, slug): for n in names: MAP[n.strip().lower()] = slug m(['анестезия'], 'anesteziya') m(['стоматология'], 'stomatologiya') m(['процедурный кабинет', 'выезд: процедурный кабинет'], 'protsedurny-kabinet') m(['дерматология и аллергология'], 'allergologiya') m(['диагностика', 'гастроскопия', 'колоноскопия', 'бронхоскопия', 'эндоскопия'], 'diagnostika') # гинекология — реальные операции/манипуляции гинекологического профиля m(['гинекология', 'лапароскопическая метропластика (пластика полеоперационного рубца на матке)', 'технические сложности оперативного вмешательства (ожирение 3ст., 3 и более лапаротомий в анамнезе, образование более 15см., множество миом матки более', 'конизация шейки матки и биопсии', 'гистерорезектоскопия', 'гистерорезектоскопия 2 ст.', 'гистерорезектоскопия 3 ст.', 'диагностическая гистероскопия', 'диагностическая лапароскопия', 'лапароскопическая миомэктомия', 'лапароскопическая миомэктомия 2кат', 'лапароскопическая миомэктомия 3кат', 'надвлагалищная ампутация матки (лапараскопия)', 'иссечение эндометриоидного очага', 'введение вмс мирена с консультацией гинеколога', 'удаление вмс', 'кольпоскопия', 'радиоволновая эксцизия ш/м врач', 'вагинопластика', 'лабиопластика', 'удаление кисты бартолиновой железы (марсупиализация)', 'вакуумная аспирация', 'удаление подкожного контрацептива "импланон"', 'лечебно - диагностическое выскабливание (сложное) врач', 'лечебно - диагностическое выскабливание врач'], 'gyne-price') # пластическая хирургия m(['пластическая хирургия', 'эндоскопический лобно-височный лифтинг', 'абдоминопластика', 'блефаропластика круговая', 'блефаропластика верхних век', 'липосакция подбородка', 'липосакция живота', 'липосакция колена', 'эндоскопическая подтяжка лба', 'смас лифтинг', 'риносептопластика', 'риносептопластика 2ст.', 'септопластика'], 'plasticheskaya_hirurgiya') # ЛОР m(['оторинолорингология', 'удаление полипа носа', 'сурдология'], 'lor') # хирургия общего профиля m(['абдоминальная хирургия', 'амбулаторная хирургия', 'бариатрия', 'гнойная хирургия', 'челюстно-лицевая хирургия', 'нейрохирургия'], 'hirurgiya-price') # детская поликлиника m(['обрезание до 6лет'], 'detskiy-price') # клиника лечения боли — блокады, иглорефлексотерапия, ЛФК, массаж, артрология m(['артрология', 'блокада при синдроме грушевидной мышцы', 'блокады триггерных точек (без стоимости препарата)', 'иглорефлексотерапия', 'иглорефлексотерапия (электронная акупунктура)', 'паравертебральная блокада', 'блокада коленного сустава', 'внутрисуставное введение препарата под контролем узи', 'физ. кабинет', 'лфк', 'массаж', 'массаж ног', 'массаж верхних конечностей взрослым', 'массаж шейно-воротниковой зоны детям', 'массаж спины (взрослым)'], 'lech_bol') # УЗИ и допплер m(['узи определение пола', 'узи гинекологии', 'узи печени + желчного пузыря', 'узи почек+мочевого пузыря+простаты', 'узи сердца', 'ультразвуковая диагностика', 'уздг сосудов', 'уздг сосудов бцс (шеи)', 'уздг сосудов органов мошонки с доплерометрией', 'узи органов малого таза трансабдоминально (та)', 'узи органов малого таза трансвагинально (тv)', 'узи беременных 1-й триместр (с1 по12 неделю включительно)', 'узи беременных 2-й триместр (с13 по 26 неделю включительно)', 'узи беременных 3-й триместр (с 27 недели и далее)', 'узи органов брюшной полости (печень,желчный пузырь,поджелудочная железа, селезенка)'], 'uzi-price') # консультации (первичные/повторные/послеоперационные) m(['первичные консультации дана клиник', 'первичные консультации специалистов', 'повторные консультации врачей', 'повторные консультации дана клиник', 'консультация врача послеоперационных пациентов'], 'consultation-price') # косметология m(['косметология', 'ультразвуковая чистка лица', 'комплексная/комбинированная чистка лица', 'prp терапия', 'удаление папиллом(ы)'], 'kosmetologiya-price') # новые узкие специальности, для которых уже есть направления услуг на сайте m(['урология'], 'urologiya-price') m(['проктология', 'вскрытие и дренирование парапроктита'], 'proktologiya-price') m(['офтальмология', 'а-скан(узи глаз.)'], 'oftalmologiya-price') m(['неврология'], 'nevrologiya-price') m(['травматология и ортопедия'], 'travmatologiya-price') # лучевая и функциональная диагностика m(['мрт'], 'mrt-price') m(['мскт'], 'msct-price') m(['рентгенография'], 'rentgen-price') m(['функциональная диагностика', 'смад (суточное мониторирование а/д)', 'холтер (суточное мониторирование сердца)', 'смад + холтер', 'экг с нагрузкой', 'выезд на дом: экг'], 'funkc-diagnostika-price') # стационар m(['стационар', 'госпитализация в дневной стационар'], 'stacionar-price') # выездное обслуживание m(['выезд (консультативный прием)', 'выезд (узи)', 'выезд (функциональная диагностика)', 'выезд (хирургия)', 'выезд доплата', 'выезд: массаж'], 'vyezd-price') # явно нехмедицинское / неотнесённое — в «Прочее» m(['услуги dana clinic', 'битрикс24', 'ситилаб', 'тесты', 'мед.книжки', 'аренда', 'биопсия', 'биопсия шейки матки', 'пункция кист молочной железы под узи контролем', 'вакцинация'], 'prochee-price') # доп. правка: пропущенный раздел m(['вульвоскопия'], 'gyne-price') NEW_SECTIONS = [ ('urologiya-price', 'Урология'), ('proktologiya-price', 'Проктология'), ('oftalmologiya-price', 'Офтальмология'), ('nevrologiya-price', 'Неврология'), ('travmatologiya-price', 'Травматология и ортопедия'), ('kosmetologiya-price', 'Косметология'), ('mrt-price', 'МРТ'), ('msct-price', 'МСКТ'), ('rentgen-price', 'Рентгенография'), ('funkc-diagnostika-price', 'Функциональная диагностика'), ('stacionar-price', 'Стационар'), ('vyezd-price', 'Выездное обслуживание'), ('prochee-price', 'Прочее'), ] def esc(s): # В PostgreSQL со standard_conforming_strings обратный слэш — обычный символ, # удваивать его нельзя: имена приезжали в базу с лишним слэшем. return "'" + str(s).replace("'", "''") + "'" unmapped = {} mapped_rows = [] for sec, name, price in all_items: key = (sec or '').strip().lower() slug = MAP.get(key) if not slug: unmapped.setdefault(sec, 0) unmapped[sec] += 1 slug = 'prochee-price' mapped_rows.append((slug, name, price)) o = io.open('seed_price_1c.sql', 'w', encoding='utf-8') o.write('BEGIN;\n') o.write("DELETE FROM price_items WHERE section_id=(SELECT id FROM price_sections WHERE slug='edinyy-svereno');\n") o.write("DELETE FROM price_sections WHERE slug='edinyy-svereno';\n") # Internal 1C nomenclature is not an approved public price list. # New sections and prices stay draft until explicitly approved by the clinic. for slug, name in NEW_SECTIONS: o.write("INSERT INTO price_sections (slug,name,sort,published) VALUES (%s,%s,100,false) " "ON CONFLICT (slug) DO NOTHING;\n" % (esc(slug), esc(name))) for i, (slug, name, price) in enumerate(mapped_rows): o.write("INSERT INTO price_items (section_id,name,price,sort,published) " "SELECT id,%s,%s,%s,false FROM price_sections WHERE slug=%s;\n" % ( esc(name), price, i + 1, esc(slug))) o.write('COMMIT;\n') o.close() print('всего строк с ценой из 3 файлов:', len(all_items)) print('к загрузке:', len(mapped_rows)) print('немаппленных разделов (ушли в Прочее), уникальных:', len(unmapped)) for k, v in sorted(unmapped.items(), key=lambda x: -x[1]): print(' %4d %s' % (v, k))