diff --git a/oknardia/oknardia/urls.py b/oknardia/oknardia/urls.py index 56a10a3..b9e6f02 100644 --- a/oknardia/oknardia/urls.py +++ b/oknardia/oknardia/urls.py @@ -18,7 +18,7 @@ from django.contrib import admin from django.urls import path, re_path from django.conf.urls.static import static from oknardia.settings import * -from web import views, autocomplete_addr, user_manager, blog, diagrams, report2, catalog +from web import views, autocomplete_addr, user_manager, blog, diagrams, report2, catalog, prices urlpatterns = [ @@ -67,7 +67,9 @@ urlpatterns = [ # --- --- Каталог производителей окон re_path(r'^catalog/company[/*]$', catalog.catalog_company), re_path(r'^catalog/company/(?P\d+)-(?P\S*)[/*]$', catalog.catalog_company_detail), - + # ЦЕНОВЫЕ ПРЕДЛОЖЕНИЯ + re_path(r'^tsena-odnogo-okna/(?P\d+)x(?P\d+)mm/tip(?P\d+)[/*]$', + prices.report_one_win_price), ] diff --git a/oknardia/templates/report/report_precelist_one_flap_frame.html b/oknardia/templates/report/report_precelist_one_flap_frame.html new file mode 100755 index 0000000..c3e3617 --- /dev/null +++ b/oknardia/templates/report/report_precelist_one_flap_frame.html @@ -0,0 +1,141 @@ +{% load static %}{% load filters %}{% for CurOffer in PRICE_FRAME %} + + + {% for CurInOffer in CurOffer.DIM %} + + {% if forloop.first %} + {# красивые чекбоксы BEGIN #}
{# красивые чекбоксы END #} + + +

{{ CurOffer.MERCHANT }} – {{ CurOffer.SETS_NAME }}

+ +
■ Профиль: {{ CurOffer.PVC_NAME|safe }} ({{ CurOffer.PVC_MANUFACTURER }}) + ■ {{ CurOffer.GLAZING_NAME_B|safe }} ({{ CurOffer.GLAZING_MARK }}) + ■ Тонирование: {{ CurOffer.GLAZING_TONING }} + {% if CurOffer.PVC_SEAL %}■ Уплотнитель: {{ CurOffer.PVC_SEAL }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT %}■ Фурнитура: {{ CurOffer.SETS_IMPLEMENT|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT_R %}■ Ручки: {{ CurOffer.SETS_IMPLEMENT_R|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT_P %}■ Петли: {{ CurOffer.SETS_IMPLEMENT_P|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT_Z %}■ Запоры: {{ CurOffer.SETS_IMPLEMENT_Z|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT_O %}■ Ограничители: {{ CurOffer.SETS_IMPLEMENT_O|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_IMPLEMENT_F %}■ Фиксаторы: {{ CurOffer.SETS_IMPLEMENT_F|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_CLIMATE_CONTROL %} 3 %}class="bullet-green"{% endif %}>■ Климат-контроль: {{ CurOffer.SETS_CLIMATE_CONTROL|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_SILL %}■ Подоконник: {{ CurOffer.SETS_SILL|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_SLOPE %}■ Откос: {{ CurOffer.SETS_SLOPE|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_PANES %}■ Водоотлив: {{ CurOffer.SETS_PANES|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_UNINSTALL_INSTALL %}■ Демонтаж/монтаж: {{ CurOffer.SETS_UNINSTALL_INSTALL|capfirst|safe }} + {% endif %}{% if CurOffer.SETS_DELIVERY %}■ Доставка: {{ CurOffer.SETS_DELIVERY|capfirst|safe }}{% endif %}{% comment %} {% if CurOffer.SETS_OTHER %}■ Прочие условия: {{ CurOffer.SETS_OTHER|capfirst|safe }} + {% endif %}{% endcomment %} +
+ + {{ CurOffer.SETS_DATA_MODIFY|date:"d.M.Y" }} + + для оконого набора «{{ CurOffer.SETS_NAME }}» компании «{{ CurOffer.MERCHANT }}» состоит из:{% else %}Рейтинг не присвоен{% endif %}" + data-toggle="popover">рейтинг: {% for Star in CurOffer.SETS_RATING_STARTS %}{% if Star == 0 %}{% else %}{% endif %}{% endfor %} {% if CurOffer.SETS_RATING > -0.1 %} {{ CurOffer.SETS_RATING|stringformat:".2f" }}{% endif %} +
+ + + + + + {% endif %} + + Схема открывания: {{ CurInOffer.DESCRIPTION }} — {{ CurInOffer.WIDTH|stringformat: + + {% if forloop.first %} + + {{ CurOffer.TOTAL|stringformat:".2f"|price_format }} + {% if CurOffer.DISCOUNT < 0.1 %}—{% else %}−{{ CurOffer.DISCOUNT|stringformat:".1f" }}%{% endif %} + + Итого: {{ CurOffer.FIN_PRICE|stringformat:".2f"|price_format }}  + + + + {% if CurOffer.DIM|length == 1 %} + + +   + + + {% endif %} + + {% elif forloop.counter0 == 1 %} + + + + {% endif %} + {% if forloop.counter0 == 1 or CurOffer.DIM|length == 1 %} + + {{ CurOffer.OFFICE_NAME }}
+ +
+ {{ CurOffer.OFFICE_ADDRESS }}
{{ CurOffer.OFFICE_PHONES }}
+ Пожалуйста, скажите, что нашли цены на oknardia.ru +
+ + + {% endif %} + {% endfor %}{% endfor %} + + +
подгружаю цены на окна...
+ + + + + + Сравнить выбранные (0)

+ {% if N != "-1" %}Ещё коммерческие предложения окон  {% endif %} + \ No newline at end of file diff --git a/oknardia/templates/report/report_price-offers_for_one_window.html b/oknardia/templates/report/report_price-offers_for_one_window.html new file mode 100755 index 0000000..bcad4a7 --- /dev/null +++ b/oknardia/templates/report/report_price-offers_for_one_window.html @@ -0,0 +1,245 @@ +{% extends "base.html" %}{% load static %} +{% load filters %} + +{% block Title %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }} для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}{% endfor %} мм.{% endblock %} + +{% block Add_Body_Attribute %} style="padding-top:70px;"{% endblock %} + +{% block Date4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %} + +{% block Last4Meta %}{{ META_DATA_PUBLISH|date:"c" }}{% endblock %} + +{% block Top_JS4 %} + + {% endblock %} + +{% block Description %}Цены на типовое окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% endfor %} cм. для домов серий {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}.{% endblock %} + +{% comment %}{% block Description %}Цены на пластиковые окна для серии {{ BASE_SERIA }} ({{ APART }} квартира, {{ ADDRESS }}) :: {% for CurOffer in PRICE_FRAME %}Поставщик: {{ CurOffer.MERCHANT }}; Комплектация: {{ CurOffer.SETS_NAME }}; Цена: {{ CurOffer.FIN_PRICE }}₽ :: {% endfor %}{% endblock %}{% endcomment %} + +{% block Keywords %}цены на окно, типовое окно, {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% endfor %} cм., {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}x{{ I_WIN_DIM.iWinHight|floatformat:0 }}{% endfor %} см., стоимость окна, {% for I in SERIA_FOR_WIN %}серия {{ I.sName }}, {% endfor %}типовой проём, {{ META_KEYWORDS|default:"" }}{% endblock %} + +{% block Top_JS3%}{% endblock %} + +{% block Top_CSS1 %}{% endblock %} + +{% block Main_Content %}
+ {# #}
+
+ +

Стандартные оконные проёмы и балконные блоки

+
+
{# #} + +
+
+

Цены на окно {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:0 }}0x{{ I_WIN_DIM.iWinHight|floatformat:0 }}0{% endfor %} мм. (типовое)

+
+
+

Типовой проём {% for I_WIN_DIM in FLAP_DIM %}{{ I_WIN_DIM.iWinWidth|floatformat:1 }}x{{ I_WIN_DIM.iWinHight|floatformat:1 }}{% endfor %} cм. представлен в домах серий: {% for I in SERIA_FOR_WIN %}{% if forloop.last %} и {% elif forloop.first %}{% else %}, {% endif %}{{ I.sName }}{% endfor %}. База «Окнардии» размещено {{ NUM_TOTAL_OFFER_N_WORD }} цен для окон в такой проем (из них в архиве {{ NUM_ARCHIVE_OFFER }}). Предложено {{ NUM_FLAP_VARIATION_IN_WORD }} открывания от {{ NUM_TOTAL_FIRM_N_WORD }}.

+
+ {# Микроразмектка: названеи продукта #} +
+ +
+
{% include 'report/show_big_flap_pictures.html' %}
+
+
Наиболее частые предложения схем открывания:
+
    {% for I in LIST_FLAP_VARIATION %} + {% if not I.IMG_MINI == '' %}
  • Схема открывания {{ I.sOfferFlapConfig }} — {{ I.STR_NUM }}
  • {% endif %}{% endfor %} +
+
+
+
+
Типовые серии домой, в которых встречается данный типовой проём:
+
    {% for I in SERIA_FOR_WIN %} +
  • {{ I.sName }} — + входит в {{ I.num_variation_of_apartment }}
  • + {% endfor %}
+
+
+ +
+
+

В таблице представлены только цены поставщиков из базы «Окнардия». Клик на названии набора отобразит детальную спецификацию каждого предложения: профиль рамы и створки, схему стеклопакета, фурнитуру, элементы отлива, подоконника, откоса, системы климат-контроля) и сопутствующие услуги. Предложения выводятся блоками. Очередной блок выводится кнопкой «Ещё коммерческие предложения окон» под таблицей. Детальные технические характеристики стеклопакетов, профилей и описание сопутствующих услуг можно посмотреть и сравнить с помощью кнопки «Сравнить выбранные».

+
+ {# Микроразмектка: названеи продукта #} + +
+ + +
+ {% csrf_token %} + + + + + + + + + + + + + + {% include "report/report_precelist_one_flap_frame.html" %} + +
Поставщик окон и название набора
+ кликните чтобы отобразить описание и характеристики предложения
+
Схема
+ открывания
Стоимость,  
+ окна для типового проёма
СкидкаИтого
+ за типовое окно с учётом скидки
+

+ {# --- Баннер: НАЧАЛО --- #} +

{% include "ad/bannet-wide.html" %}
+ {# --- Баннер: конец --- #} +
+ {% include "report/report_last_user_visit.html" %} + {% include "report/report_log_user_visit.html" %} +
+
+ +{# модальное окно #} + +{% endblock %} + +{% comment %} +{% block Top_Nav_Bar %} + {# ОТЛАДКА, ГАСИМ ВЕРХНЕЕ МЕНЮ #} +{% endblock %} +{% endcomment %} + diff --git a/oknardia/web/add_func.py b/oknardia/web/add_func.py index 1ee3c5e..84706e7 100644 --- a/oknardia/web/add_func.py +++ b/oknardia/web/add_func.py @@ -84,7 +84,7 @@ def get_rating_set_for_stars(rating: float = 0.) -> list: # # # # рассчитывает дистанцию в км. между двумя геокоординатами -# def GetGeoDistance(lon1, lat1, lat2, lon2): +# def get_geo_distance(lon1, lat1, lat2, lon2): # lonA, latA, latB, lonB = map(math.radians, [lon1, lat1, lat2, lon2]) # distance = 2 * math.asin(math.sqrt(math.sin((latB - latA) / 2) ** 2 + math.cos(latA) * math.cos(latB) * math.sin( # (lonB - lonA) / 2) ** 2)) * 6371.032 # РАДИУС ЗЕМЛИ 6371.032 КМ. @@ -416,7 +416,7 @@ def make_flap_mini_pictures(path_to_img_file: str, str_flap_config: str) -> None local_h_ratio_max += j["hRatio"] if local_h_ratio_max > h_ratio_max: h_ratio_max = local_h_ratio_max - img = Image.new("RGBA", (h_ratio_max*PICT_MINWI+(h_ratio_max+1)*3, PICT_MINIH+6), (255, 255, 255, 0)) + img = Image.new("RGBA", (h_ratio_max * PICT_MINWI + (h_ratio_max + 1) * 3, PICT_MINIH + 6), (255, 255, 255, 0)) top = 0 left = 0 bottom = img.size[1] @@ -490,17 +490,38 @@ def make_flap_mini_pictures(path_to_img_file: str, str_flap_config: str) -> None return -def get_flaps_for_mini_pictures(flap_cofig: str) -> str: - image_file_name = flap_cofig - image_file_name = image_file_name.replace(">", u"G") +def get_flaps_for_mini_pictures(flap_config: str) -> str: + """ + Функция возвращает строку с именем файла мини-картинки для схемы открывания полученной в flap_config + + :param flap_config: str - строка с схемой открывания. + :return: str - строка с именем файла мини-картинки. + """ + image_file_name = flap_config.upper() + image_file_name = image_file_name.replace(">", "G") image_file_name = image_file_name.replace("<", "L") image_file_name = image_file_name.replace("|", "I") image_file_name = image_file_name.replace("[", "(") image_file_name = image_file_name.replace("]", ")") image_file_name = image_file_name.replace("/", "-") image_file_name = image_file_name.replace("\\", "-") - image_file_name = image_file_name.replace(".", "-") + u".png" + image_file_name = image_file_name.replace(".", "-") + ".png" image_file_name = f"{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}/{image_file_name}" if not os.path.isfile(f"{STATIC_BASE_PATH}/{image_file_name}"): - make_flap_mini_pictures(f"{STATIC_BASE_PATH}/{image_file_name}", flap_cofig) + make_flap_mini_pictures(f"{STATIC_BASE_PATH}/{image_file_name}", flap_config.upper()) return image_file_name + + +def get_geo_distance(lon1: float, lat1: float, lat2: float, lon2: float) -> float: + """ Функция возвращает расстояние в км. между двумя геокоординатами. + + :param lon1: float - долгота первой точки. + :param lat1: float - широта первой точки. + :param lat2: float - широта второй точки. + :param lon2: float - долгота второй точки. + :return: float - расстояние в км. между двумя геокоординатами. + """ + lon_a, lat_a, lat_b, lon_b = map(math.radians, [lon1, lat1, lat2, lon2]) + distance = 2 * math.asin(math.sqrt(math.sin((lat_b - lat_a) / 2) ** 2 + math.cos(lat_a) * math.cos(lat_b) + * math.sin((lon_b - lon_a) / 2) ** 2)) * 6371.032 # РАДИУС ЗЕМЛИ 6371.032 КМ. + return distance diff --git a/oknardia/web/prices.py b/oknardia/web/prices.py new file mode 100644 index 0000000..3e6b7a0 --- /dev/null +++ b/oknardia/web/prices.py @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- +from django.core.exceptions import ObjectDoesNotExist +from django.shortcuts import render, redirect +from django.http import HttpRequest, HttpResponse +from oknardia.models import Win_MountDim, PriceOffer +from oknardia.settings import * +from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list +from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures,\ + get_geo_distance +import django.utils.dateformat +import time +import os +import re +import pytils + + +def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_longitude: float, address_latitude: float, + frame_begin_n: int = 0, brand_id: int = 0, win_id: int = 0) -> dict: + """ Формируем выдачу цен для фрейма + + :param apartment_id: int -- ID типа квартиры, для которой получаем ценовые предложения + :param mount_dim_per_offer: int -- число различных оконых проемов в этой квартире (чтобы отсеять предложения, + в которых не представлены все проемы) + :param address_longitude: float -- долгота адреса (геокоордината), чтобы рассчитать удаленность компании + предоставившей коммерческие предложения + :param address_latitude: float -- широта адреса (геокоордината), чтобы рассчитать удаленность компании + предоставившей коммерческие предложения + :param frame_begin_n: int -- Номер записи с которой начинается фрейм с ценами (с какого предложения начинать) + :param brand_id: int -- ID бренда, если выбран бренд, то отображаем только предложения этого бренда + (нужно для виджета, где отображаются предложения только от одной компании) + если 0, то отображаем все предложения + :param win_id: int -- ID окна, если выбрано окно, то отображаем только предложения этого окна + :return: dict -- словарь данных для отображения в фрейме (цены предложений и их характеристики) + """ + # ценовая выдача + time_for_meta = 0 # время для мета-данных в HTML-кода + apartment_id = int(apartment_id) + mount_dim_per_offer = int(mount_dim_per_offer) + address_longitude = float(address_longitude) + address_latitude = float(address_latitude) + frame_begin_n = int(frame_begin_n) + brand_id = int(brand_id) + win_id = int(win_id) + add_to_sql_for_widget = "" + offer_per_frame = OFFER_PER_FRAME + if brand_id != 0: + # Это вывод для выджета. Нужны цены только по определенному поставщику + add_to_sql_for_widget = f" AND oknardia_merchantbrand.id = {brand_id} " + offer_per_frame = 1000 # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений. + if int(apartment_id) == 0 and int(win_id) != 0: + # если выводим цены только для одного проема + offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP + q_price_offer = PriceOffer.objects.raw( + f"SELECT" + f" oknardia_priceoffer.id, oknardia_priceoffer.iOfferImpressions," + f" oknardia_priceoffer.fOfferPrice, oknardia_priceoffer.dOfferModify," + f" oknardia_priceoffer.fOfferRating, oknardia_priceoffer.sOfferFlapConfig," + f" oknardia_priceoffer.iOfferViews, oknardia_priceoffer.sOfferActive," + f" oknardia_win_mountdim.sDescripion, oknardia_win_mountdim.id AS mID, " + f" oknardia_win_mountdim.bIsNearDoor, oknardia_win_mountdim.bIsDoor," + f" oknardia_win_mountdim.iWinWidth, oknardia_win_mountdim.iWinHight," + f" oknardia_setkit.id AS setID," + f" oknardia_setkit.sSetName, oknardia_setkit.dSetModify," + f" oknardia_setkit.sSetClimateControl, oknardia_setkit.sSetSill," + f" oknardia_setkit.sSetImplementAll, oknardia_setkit.sSetImplementHandles," + f" oknardia_setkit.sSetImplementHinges, oknardia_setkit.sSetImplementLatch," + f" oknardia_setkit.sSetImplementLimiter, oknardia_setkit.sSetImplementCatch," + f" oknardia_setkit.sSetPanes, oknardia_setkit.sSetSlope," + f" oknardia_setkit.sSetOtherConditions, oknardia_setkit.sSetActive," + f" oknardia_setkit.bSetDelivery, oknardia_setkit.sSetDelivery," + f" oknardia_setkit.sSetUninstallInstall, oknardia_setkit.bSetUninstallInstall," + f" oknardia_setkit.fSetRating, oknardia_setkit.iSetNumEval," + f" oknardia_setkit.iSetImpressions, oknardia_setkit.iSetViews," + f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial," + f" oknardia_merchantoffice.sOfficePhones, " + f" oknardia_merchantoffice.sOfficeDiscountMetaFormula," + f" oknardia_merchantoffice.sOfficeName, oknardia_merchantoffice.sOfficeAddress," + f" oknardia_glazing.fGlazingRating," + f" oknardia_glazing.sGlazingName, oknardia_glazing.sGlazingBriefDescription," + f" oknardia_glazing.sGlazingMark, oknardia_glazing.sGlazingToning," + f" oknardia_pvcprofiles.sProfileBriefDescription, oknardia_pvcprofiles.id AS pwc_id," + f" oknardia_pvcprofiles.sProfileReinforcement, oknardia_pvcprofiles.sProfileSealDescription," + f" oknardia_pvcprofiles.sProfileName, oknardia_pvcprofiles.sProfileColor," + f" oknardia_pvcprofiles.fProfileRating, oknardia_pvcprofiles.sProfileManufacturer," + f" oknardia_merchantbrand.sMerchantName, oknardia_merchantbrand.pMerchantLogo," + f" oknardia_merchantbrand.sMerchantMainURL, oknardia_merchantbrand.id AS brand_id," + f" 1 AS iQuantity, 0 AS fOfficeGeoCode_Longitude, 0 AS fOfficeGeoCode_Latitude " + f"FROM oknardia_priceoffer" + f" INNER JOIN oknardia_win_mountdim" + f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id" + f" INNER JOIN oknardia_setkit" + f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id" + f" INNER JOIN oknardia_ouruser" + f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id" + f" INNER JOIN oknardia_merchantoffice" + f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id" + f" INNER JOIN oknardia_glazing" + f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id" + f" INNER JOIN oknardia_pvcprofiles" + f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id" + f" INNER JOIN oknardia_merchantbrand" + f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id " + f"WHERE oknardia_priceoffer.sOfferActive IS TRUE" + f" AND oknardia_setkit.sSetActive IS TRUE " + f" AND oknardia_win_mountdim.id = {int(win_id)}" + f" {add_to_sql_for_widget} " + f"ORDER BY" + f" oknardia_priceoffer.dOfferModify DESC " + f"LIMIT {int(frame_begin_n)}, 10000;") + else: + # если выводим цены для типовой квартиры + # print("Нужно несколько окон для квартиры") + q_price_offer = PriceOffer.objects.raw( + f"SELECT" + f" oknardia_priceoffer.*," + f" oknardia_win_mountdim.*," + f" oknardia_setkit.*," + f" oknardia_merchantoffice.*," + f" oknardia_glazing.*," + f" oknardia_pvcprofiles.*," + f" oknardia_merchantbrand.*," + f" oknardia_mountdim2apartment.iQuantity," + f" oknardia_win_mountdim.id AS mID, " + f" oknardia_setkit.id AS setID," + f" (oknardia_setkit.dSetCommercialUntil > NOW()) AS bCommercial," + f" oknardia_pvcprofiles.id AS pwc_id," + f" oknardia_merchantbrand.id AS brand_id " + f"FROM oknardia_priceoffer" + f" INNER JOIN oknardia_win_mountdim" + f" ON oknardia_priceoffer.kOffer2MountDim_id = oknardia_win_mountdim.id" + f" INNER JOIN oknardia_setkit" + f" ON oknardia_priceoffer.kOffer2SetKit_id = oknardia_setkit.id" + f" INNER JOIN oknardia_ouruser" + f" ON oknardia_setkit.kSet2User_id = oknardia_ouruser.id" + f" INNER JOIN oknardia_merchantoffice" + f" ON oknardia_ouruser.kMerchantOffice_id = oknardia_merchantoffice.id" + f" INNER JOIN oknardia_glazing" + f" ON oknardia_setkit.kSet2Glazing_id = oknardia_glazing.id" + f" INNER JOIN oknardia_pvcprofiles" + f" ON oknardia_setkit.kSet2PVCprofiles_id = oknardia_pvcprofiles.id" + f" INNER JOIN oknardia_mountdim2apartment" + f" ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id" + f" INNER JOIN oknardia_merchantbrand" + f" ON oknardia_merchantoffice.kMerchantName_id = oknardia_merchantbrand.id " + f"WHERE oknardia_priceoffer.sOfferActive IS TRUE" + f" AND oknardia_mountdim2apartment.kApartment_id = {int(apartment_id)}" + f" AND oknardia_setkit.sSetActive IS TRUE {add_to_sql_for_widget} " + f"ORDER BY" + f" oknardia_setkit.dSetCreate DESC, " # Сейчас окна в наборе собираются через это + f" oknardia_win_mountdim.bIsNearDoor DESC," + f" oknardia_win_mountdim.bIsDoor DESC," + f" oknardia_win_mountdim.iWinWidth," + f" oknardia_win_mountdim.iWinHight DESC " + f"LIMIT {int(frame_begin_n)} , 10000;") + # print list(qPO) + price_frame = [] + count_mount_dim_in_offer = 0 + dim_in_offer = [] + total = 0 + cur_bullet = 0 + previous_set_id = 0 + count_mount_dim_in_frame_page = 0 + # Так как получен QuerySet начиная с frame_begin_n, то для того чтобы получить frame_begin_n следующего фрйма + # считаем не от нуля, а от старого frame_begin_n + n_begin = int(frame_begin_n) + # Проверяем есть ли папка для хранения мини-картинки конфигурации схемы открывания. + if not os.path.exists(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}"): + # создаем такую папку если её нет + os.makedirs(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}") + # print(">>>>>>>>>>>>>", apartment_id) + for i2 in q_price_offer: + n_begin += 1 + count_mount_dim_in_frame_page += 1 + # Случается, что в том или ином наборе поставщиком просчитаны не все проёмы. + # Чтобы не происходило формирование предложения (офера) из окон разных наборов делаем проверку + # является ли текущий проём в предложении из того же набора, что и предыдущий. + # Если он из другого набора, то удаляем предыдущий проём из предложения и начинаем + # формирование предложения сначала. + if count_mount_dim_in_offer == 0: + previous_set_id = i2.setID + else: + if previous_set_id != i2.setID: + # print("Сбой в наборе. Обнуляем набор") + previous_set_id = i2.setID + count_mount_dim_in_offer = 0 + total = 0 + cur_bullet = 0 + dim_in_offer.pop() + dim_in_offer = [] + # print("mID:", i2.mID, " || pID:", i2.id, " || price:", i2.fOfferPrice, " || N:", i2.iQuantity, + # " || set:", i2.sSetName, " || merchant:", i2.sOfficeName) + total += i2.fOfferPrice * i2.iQuantity + image_file = get_flaps_for_mini_pictures(i2.sOfferFlapConfig) + dim_in_offer.append({ + 'PRICE': i2.fOfferPrice, + 'FLAP': i2.sOfferFlapConfig, + 'DESCRIPTION': i2.sDescripion, + 'WIDTH': i2.iWinWidth, + 'HIGHT': i2.iWinHight, + 'ID': i2.id, + 'IMG_MINI': image_file, + 'QUANTITY': i2.iQuantity, + 'BULLET': [chr(65+cur_bullet+i) for i in range(i2.iQuantity)], + # 'BULLET': range(CurBullet, CurBullet+i2.iQuantity), + 'SUBTOTAL': i2.fOfferPrice * i2.iQuantity, + }) + cur_bullet += i2.iQuantity + count_mount_dim_in_offer += 1 + if count_mount_dim_in_offer == mount_dim_per_offer: + # print("-----------------") + # узнаем скидку через разбор формулы на метаязыке + discount = 0 + try: + meta_keys = eval(i2.sOfficeDiscountMetaFormula) + if KEY_DICSOUNT in meta_keys: + # скидки рассчитываются исходя из общей суммы + for CountVal in sorted(meta_keys[KEY_DICSOUNT]): + # print(CountVal, "::", meta_keys[KEY_DICSOUNT][CountVal]) + if float(total) > float(CountVal): + discount = meta_keys[KEY_DICSOUNT][CountVal] + # # DiscountTXT += u"!!%d!!" % Discount + # print("Значит DISCOUNT: ", Discount) + except (ValueError, TypeError): + pass + fin_price = total * (100 - discount) / 100 + # уточняем, есть ли в принципе геокоординаты? + if int(i2.fOfficeGeoCode_Longitude) != 0 and int(i2.fOfficeGeoCode_Latitude) != 0 and \ + int(address_longitude) != 0 and int(address_latitude) != 0: + # рассчитываем дистанцию между адресом дома и офиса. + distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, address_longitude, + address_latitude) + # т.к. из-за изменений в api яндекс карт поменялась местами широта-долгота и вообще, то + # порядок переменных строчной выше... На самом деле должно быть как в закоментированной + # строке ниже + # distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, address_longitude, + # address_latitude) + else: + distance = -1 + if discount > 99 or discount < 0.1: + discount_color1 = "" + discount_color2 = "" + else: + color_ratio = (discount + 0.) / 100 + discount_color1 = f"#{255 - int(color_ratio * 128): 02x}ff{255 - int(color_ratio * 128) :02x}" + discount_color2 = f"#{255-int(color_ratio * 255): 02x}ff{255 - int(color_ratio * 255) :02x}" + price_frame.append({ + 'DISTANCE': distance, + 'DIM': dim_in_offer, + 'TOTAL': total, + 'DISCOUNT': discount, + 'DISCOUNT_COLOR1': discount_color1, + 'DISCOUNT_COLOR2': discount_color2, + 'FIN_PRICE': fin_price, + 'OFFICE_NAME': i2.sOfficeName, + 'OFFICE_ADDRESS': i2.sOfficeAddress, + 'OFFICE_PHONES': i2.sOfficePhones, + 'MERCHANT': i2.sMerchantName, + 'MERCHANT_LOGO': i2.pMerchantLogo, + 'MERCHANT_URL': i2.sMerchantMainURL, + 'MERCHANT_URL_SHOT': re.sub(r"(?:^http://|^https://|/$|www\.)", "", i2.sMerchantMainURL), + 'SETS_NAME': i2.sSetName, + 'GLAZING_NAME_B': i2.sGlazingBriefDescription, + 'GLAZING_MARK': i2.sGlazingMark, + 'GLAZING_TONING': i2.sGlazingToning, + 'PVC_ID': i2.pwc_id, + 'PVC_NAME': i2.sProfileName, + 'PVC_NAME_T': pytils.translit.slugify(i2.sProfileName).lower(), + 'PVC_MANUFACTURER': i2.sProfileManufacturer, + 'PVC_MANUFACTURER_T': pytils.translit.slugify(i2.sProfileManufacturer).lower(), + 'PVC_SEAL': i2.sProfileSealDescription, + 'SETS_CLIMATE_CONTROL': i2.sSetClimateControl, + 'SETS_SILL': i2.sSetSill, + 'SETS_IMPLEMENT': i2.sSetImplementAll, + 'SETS_IMPLEMENT_R': i2.sSetImplementHandles, + 'SETS_IMPLEMENT_P': i2.sSetImplementHinges, + 'SETS_IMPLEMENT_Z': i2.sSetImplementLatch, + 'SETS_IMPLEMENT_O': i2.sSetImplementLimiter, + 'SETS_IMPLEMENT_F': i2.sSetImplementCatch, + 'SETS_PANES': i2.sSetPanes, + 'SETS_SLOPE': i2.sSetSlope, + 'SETS_DELIVERY': i2.sSetDelivery, + 'SETS_DELIVERY_B': i2.bSetDelivery, + 'SETS_OTHER': i2.sSetOtherConditions, + 'SETS_ID': i2.setID, + 'SETS_UNINSTALL_INSTALL': i2.sSetUninstallInstall, + 'SETS_UNINSTALL_INSTALL_B': i2.bSetUninstallInstall, + 'SETS_RATING': i2.fSetRating, + 'SETS_RATING_STARTS': get_rating_set_for_stars(i2.fSetRating), + 'SETS_DATA_MODIFY': i2.dOfferModify, + 'IS_COMMERCIAL': i2.bCommercial, + }) + if len(price_frame) == offer_per_frame: + break + count_mount_dim_in_offer = 0 + dim_in_offer = [] + total = 0 + cur_bullet = 0 + # узнаем дату-время самого свежего ценового предложения для размещения в META-тега + if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ + django.utils.dateformat.format(i2.dOfferModify, 'U'): + time_for_meta = i2.dOfferModify + if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \ + django.utils.dateformat.format(i2.dSetModify, 'U'): + time_for_meta = i2.dSetModify + # массив ценовых предложений (что бы предложения на одной дистанции были в случайном порядке) + # random.shuffle(PriceFrame) + # сортируем по удаленности + price_frame = sorted(price_frame, key=lambda item: item['DISTANCE']) + if len(price_frame) < offer_per_frame: + n_begin = '-1' + return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin} + + +def report_one_win_price(request: HttpRequest, win_width_mm: str = '670', win_height_mm: str = '2160', + win_id: str = '16') -> HttpResponse: + """ Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома). + + + :param request: HttpRequest -- входящий http-запрос + :param win_width_mm: str -- Ширина проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы) + :param win_height_mm: str -- Высота проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы) + :param win_id: str -- ID проема (см. таблицу oknardia_win_mountdim) + :return response: HttpResponse -- исходящий http-ответ + """ + time_start = time.time() + to_template = {} + try: + # т.к. для вызова GetFlapDim4BigPictures нужно иметь внутри queryset поле iQuantity нельзя использовать + # простой запрос (см. следующую строку). + # qWinInfo = Win_MountDim.objects.filter(id=int(win_id)) + # Придется сделать запрос немного сложнее: + q_win_info = Win_MountDim.objects.raw( + f'SELECT oknardia_win_mountdim.iWinWidth,' + f' oknardia_win_mountdim.iWinHight, oknardia_win_mountdim.iWinDepth,' + f' oknardia_win_mountdim.sFlapConfig, oknardia_win_mountdim.bIsNearDoor,' + f' oknardia_win_mountdim.bIsDoor, oknardia_win_mountdim.sDescripion,' + f' oknardia_win_mountdim.id, 0 as iQuantity ' + f'FROM oknardia_win_mountdim ' + f'WHERE oknardia_win_mountdim.id = {int(win_id)};' + ) + list_win_info = list(q_win_info) + # Если размеры типового проема не совпадают с размерами из базы, то подменяем + # на правильные и перевызываем страницу + if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \ + (list_win_info[0].iWinHight * 10 != int(win_height_mm)): + return redirect(f"/tsena-odnogo-okna/{list_win_info[0].iWinWidth * 10}x{list_win_info[0].iWinHight * 10}" + f"mm/tip{win_id}") + except (ObjectDoesNotExist, ValueError, IndexError, TypeError): + return redirect("/tsena-odnogo-okna/670x2160mm/tip16") + # все хорошо, засылаем картинку в шаблон + to_template.update(get_flaps_for_big_pictures(list_win_info)) + # получаем варианты схемы открывания (для графиков) + q_offer_flap_variation = PriceOffer.objects.raw( + f'SELECT' + f' COUNT(oknardia_priceoffer.sOfferFlapConfig) AS id,' + f' "" AS IMG_MINI,' + f' "" AS STR_NUM,' + f' oknardia_priceoffer.sOfferFlapConfig ' + f'FROM oknardia_priceoffer ' + f'WHERE oknardia_priceoffer.sOfferActive <> 0' + f' AND oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} ' + f'GROUP BY oknardia_priceoffer.sOfferFlapConfig,' + f' oknardia_priceoffer.sOfferActive,' + f' oknardia_priceoffer.kOffer2MountDim_id ' + f'ORDER BY id DESC;' + ) + list_offer_flap_variation = list(q_offer_flap_variation) + for i in range(0, len(list_offer_flap_variation)): + if i < 3: + list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1) + elif i == 3: + list_offer_flap_variation[i].STR_NUM = "остальные варианты" + continue + else: + list_offer_flap_variation[3].id += list_offer_flap_variation[i].id + continue + list_offer_flap_variation[i].IMG_MINI = get_flaps_for_mini_pictures( + list_offer_flap_variation[i].sOfferFlapConfig + ) + to_template.update({'LIST_FLAP_VARIATION': list_offer_flap_variation[:4]}) + to_template.update({'NUM_FLAP_VARIATION_IN_WORD': pytils.numeral.sum_string(len(list_offer_flap_variation), + pytils.numeral.MALE, + ("вариант схемы", + "варианта схем", + "вариантов схем"))}) + # + q = PriceOffer.objects.raw(f'SELECT' + f' COUNT(oknardia_priceoffer.kOfferFromUser_id) AS id,' + f' oknardia_priceoffer.kOfferFromUser_id,' + f' oknardia_priceoffer.kOffer2MountDim_id ' + f'FROM oknardia_priceoffer ' + f'WHERE oknardia_priceoffer.kOffer2MountDim_id = {int(win_id)} ' + f'GROUP BY oknardia_priceoffer.kOffer2MountDim_id,' + f' oknardia_priceoffer.kOfferFromUser_id;') + to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(len(list(q)), + ("компании", "компаний", "компаний"))}) + q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id)) + to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(), + ("готовый расчёт", "готовых расчёта", + "готовых расчётов"))}) + to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()}) + # + q_seria_for_win = PriceOffer.objects.raw( + f'SELECT' + f' oknardia_seria_info.sName, oknardia_seria_info.id AS id,' + f' "" AS sNameLat,' + f' COUNT(oknardia_mountdim2apartment.id) AS num_variation_of_apartment ' + f'FROM oknardia_apartment_type' + f' INNER JOIN oknardia_mountdim2apartment' + f' ON oknardia_mountdim2apartment.kApartment_id = oknardia_apartment_type.id' + f' INNER JOIN oknardia_win_mountdim' + f' ON oknardia_mountdim2apartment.kMountDim_id = oknardia_win_mountdim.id' + f' INNER JOIN oknardia_seria_info' + f' ON oknardia_apartment_type.kSeria_id = oknardia_seria_info.id ' + f'WHERE oknardia_win_mountdim.id = {int(win_id)} ' + f'GROUP BY oknardia_win_mountdim.id,' + f' oknardia_seria_info.sName,' + f' oknardia_seria_info.id ' + f'ORDER BY oknardia_seria_info.sName;' + ) + list_seria_for_win = list(q_seria_for_win) + for i in list_seria_for_win: + i.sNameLat = pytils.translit.slugify(i.sName) + i.num_variation_of_apartment = pytils.numeral.sum_string(i.num_variation_of_apartment, + pytils.numeral.MALE, + ("типовую планировку квартиры", + "типовые планировки квартир", + "типовых планировок квартир")) + to_template.update(report_price_frame(0, 1, 0, 0, 0, 0, int(win_id))) + to_template.update({ + 'SERIA_FOR_WIN': list_seria_for_win, + 'WIN_ID': int(win_id), + 'MOUNT_DIM_PER_OFFER': 1, + # получаем последние визиты клиента через куки + 'LAST_VISIT': get_last_user_visit_list(get_last_user_visit_cookies(request)[:3]), + # получаем последние визиты всех посетителей из базы + # id2log, log_visit = get_last_all_user_visit_list() + 'LOG_VISIT': get_last_all_user_visit_list(), + 'ticks': float(time.time() - time_start) + }) + return render(request, "report/report_price-offers_for_one_window.html", to_template)