134 lines
4.5 KiB
Python
Executable File
134 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
import argparse
|
||
import csv
|
||
import json
|
||
from datetime import date, datetime
|
||
|
||
|
||
def d(s: str) -> date:
|
||
return datetime.strptime(s, "%Y-%m-%d").date()
|
||
|
||
|
||
def normalize(h: str) -> str:
|
||
return (h or "").strip().lower().replace(" ", "_")
|
||
|
||
|
||
def pick(header_map, *aliases):
|
||
for a in aliases:
|
||
if a in header_map:
|
||
return header_map[a]
|
||
return None
|
||
|
||
|
||
def main():
|
||
p = argparse.ArgumentParser()
|
||
p.add_argument("--excel", required=True)
|
||
p.add_argument("--checkin", required=True)
|
||
p.add_argument("--checkout", required=True)
|
||
p.add_argument("--guests", required=True, type=int)
|
||
p.add_argument("--room")
|
||
args = p.parse_args()
|
||
|
||
checkin = d(args.checkin)
|
||
checkout = d(args.checkout)
|
||
nights = (checkout - checkin).days
|
||
if nights <= 0:
|
||
print(json.dumps({"ok": False, "error": "checkout должен быть позже checkin"}, ensure_ascii=False))
|
||
return
|
||
|
||
path_in = args.excel
|
||
rows = []
|
||
|
||
if path_in.lower().endswith('.csv'):
|
||
with open(path_in, 'r', encoding='utf-8-sig', newline='') as f:
|
||
reader = csv.reader(f)
|
||
rows = list(reader)
|
||
else:
|
||
try:
|
||
from openpyxl import load_workbook
|
||
except Exception:
|
||
print(json.dumps({"ok": False, "error": "Для .xlsx нужен openpyxl (или используй .csv)"}, ensure_ascii=False))
|
||
return
|
||
wb = load_workbook(path_in, data_only=True)
|
||
ws = wb.active
|
||
rows = list(ws.iter_rows(values_only=True))
|
||
|
||
if not rows:
|
||
print(json.dumps({"ok": False, "error": "Пустой файл с ценами"}, ensure_ascii=False))
|
||
return
|
||
|
||
header = [normalize(str(x) if x is not None else "") for x in rows[0]]
|
||
hm = {h: i for i, h in enumerate(header) if h}
|
||
|
||
c_from = pick(hm, "date_from", "from", "checkin_from", "заезд_с")
|
||
c_to = pick(hm, "date_to", "to", "checkout_to", "выезд_по")
|
||
c_gmin = pick(hm, "guests_min", "min_guests", "гостей_от")
|
||
c_gmax = pick(hm, "guests_max", "max_guests", "гостей_до")
|
||
c_ppn = pick(hm, "price_per_night", "night_price", "цена_за_ночь")
|
||
c_total = pick(hm, "total_price", "цена_итого")
|
||
c_curr = pick(hm, "currency", "валюта")
|
||
c_room = pick(hm, "room", "room_type", "номер")
|
||
c_meal = pick(hm, "meal", "питание")
|
||
|
||
required = [c_from, c_to]
|
||
if any(x is None for x in required) or (c_ppn is None and c_total is None):
|
||
print(json.dumps({
|
||
"ok": False,
|
||
"error": "Неверная структура Excel. Нужны date_from/date_to и price_per_night или total_price"
|
||
}, ensure_ascii=False))
|
||
return
|
||
|
||
matches = []
|
||
for r in rows[1:]:
|
||
if r is None:
|
||
continue
|
||
try:
|
||
rf = r[c_from]
|
||
rt = r[c_to]
|
||
if isinstance(rf, datetime):
|
||
rf = rf.date()
|
||
elif isinstance(rf, str):
|
||
rf = d(rf)
|
||
if isinstance(rt, datetime):
|
||
rt = rt.date()
|
||
elif isinstance(rt, str):
|
||
rt = d(rt)
|
||
except Exception:
|
||
continue
|
||
|
||
if not (rf <= checkin and rt >= checkout):
|
||
continue
|
||
|
||
gmin = int(r[c_gmin]) if c_gmin is not None and r[c_gmin] is not None else 1
|
||
gmax = int(r[c_gmax]) if c_gmax is not None and r[c_gmax] is not None else 99
|
||
if not (gmin <= args.guests <= gmax):
|
||
continue
|
||
|
||
room = str(r[c_room]).strip() if c_room is not None and r[c_room] is not None else "Стандарт"
|
||
if args.room and room.lower() != args.room.lower():
|
||
continue
|
||
|
||
if c_total is not None and r[c_total] is not None:
|
||
total = float(r[c_total])
|
||
else:
|
||
total = float(r[c_ppn]) * nights
|
||
|
||
matches.append({
|
||
"room": room,
|
||
"meal": str(r[c_meal]).strip() if c_meal is not None and r[c_meal] is not None else "без питания",
|
||
"currency": str(r[c_curr]).strip() if c_curr is not None and r[c_curr] is not None else "₽",
|
||
"total": round(total, 2),
|
||
"nights": nights,
|
||
})
|
||
|
||
if not matches:
|
||
print(json.dumps({"ok": True, "found": 0, "message": "Подходящих тарифов не найдено"}, ensure_ascii=False))
|
||
return
|
||
|
||
matches.sort(key=lambda x: x["total"])
|
||
print(json.dumps({"ok": True, "found": len(matches), "options": matches[:3]}, ensure_ascii=False))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|