194 lines
13 KiB
Python
194 lines
13 KiB
Python
# -*- 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")
|
||
for slug, name in NEW_SECTIONS:
|
||
o.write("INSERT INTO price_sections (slug,name,sort,published) VALUES (%s,%s,100,true) "
|
||
"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,true 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))
|