diff --git a/public/service-worker.js b/public/service-worker.js index e3a9117..7a371d5 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,8 +1,8 @@ const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1"; if (!isLocalhost) { - const STATIC_CACHE = "construction-delivery-static-v116"; - const RUNTIME_CACHE = "construction-delivery-runtime-v116"; + const STATIC_CACHE = "construction-delivery-static-v117"; + const RUNTIME_CACHE = "construction-delivery-runtime-v117"; const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"]; // Only these are treated as cacheable static assets. Everything else diff --git a/scripts/sms_first_campaign.py b/scripts/sms_first_campaign.py index 1bacd2b..1b75414 100644 --- a/scripts/sms_first_campaign.py +++ b/scripts/sms_first_campaign.py @@ -64,6 +64,49 @@ DELIVERY_ERROR_CODES = {"104", "105", "106", "107", "108", "130"} # Коды превышения лимитов (нельзя повторять отправку — заблокируют) LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} +# ─── Тексты SMS ────────────────────────────────────────────────────────────── +# Шаблоны живут в админке (sms_campaign_settings): +# sms_text_template — текст для ДОСТАВКИ +# sms_text_template_pickup — текст для САМОВЫВОЗА +# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. + +DEFAULT_TEXT_DELIVERY = "Ваш заказ готов. Согласуйте дату доставки по ссылке: {link}" +DEFAULT_TEXT_PICKUP = "Ваш заказ готов. Согласуйте дату самовывоза по ссылке: {link}" +DEFAULT_TEXT_NOTIFICATION = "Ваш заказ готов." + + +def is_pickup_group(group): + return (group.get("delivery_type") or "delivery").strip().lower() == "pickup" + + +def build_sms_text(group, settings): + """Текст SMS по типу отгрузки: самовывоз → pickup-шаблон, доставка → обычный. + + Приоритет: + 1. текст профиля города (режим «Уведомление», sms_city_profiles) — как было; + 2. шаблон кампании из админки для этого типа отгрузки; + 3. дефолт по типу отгрузки. + """ + link = group.get("delivery_link") or "" + mode = (group.get("sms_mode") or "link").strip().lower() + profile_text = (group.get("sms_text_custom") or "").strip() + pickup = is_pickup_group(group) + template = (settings.get( + "sms_text_template_pickup" if pickup else "sms_text_template" + ) or "").strip() + if not template: + template = DEFAULT_TEXT_PICKUP if pickup else DEFAULT_TEXT_DELIVERY + + if mode == "notification": + if profile_text: + return profile_text + # уведомление без ссылки: шаблон годится, только если в нём нет {link} + if template and "{link}" not in template: + return template + return DEFAULT_TEXT_NOTIFICATION + + return template.replace("{link}", link).strip() + # ─── Логирование ───────────────────────────────────────────────────────────── logging.basicConfig( @@ -369,15 +412,12 @@ def step_send_new(conn, settings): log.info(f"Group {group_id}: already has recent SMS in log, skipping") continue - # Формируем текст SMS по режиму + # Формируем текст SMS по типу отгрузки (доставка / самовывоз) и режиму + sms_text = build_sms_text(group, settings) if sms_mode == "notification": - # Уведомление без ссылки на согласование - sms_text = custom_text if custom_text else "Ваш заказ готов." - log.info(f"Group {group_id}: NOTIFICATION mode (town={town}, type={delivery_type}), no link") + log.info(f"Group {group_id}: NOTIFICATION mode (town={town}, type={delivery_type}): {sms_text!r}") else: - # Стандартный флоу — ссылка на согласование - sms_text = f"Ваш заказ готов. Согласуйте дату доставки по ссылке: {delivery_link}" - log.info(f"Group {group_id}: LINK mode (town={town}, type={delivery_type})") + log.info(f"Group {group_id}: LINK mode (town={town}, type={delivery_type}): {sms_text!r}") # ТЕСТОВЫЙ РЕЖИМ: подменяем номер на тестовый send_phone = phone diff --git a/scripts/sms_paid_storage_campaign.py b/scripts/sms_paid_storage_campaign.py index fe38ca4..64ba9da 100644 --- a/scripts/sms_paid_storage_campaign.py +++ b/scripts/sms_paid_storage_campaign.py @@ -51,6 +51,33 @@ DELIVERED_CODE = "103" DELIVERY_ERROR_CODES = {"104", "105", "106", "107", "108", "130"} LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} +# ─── Тексты SMS ────────────────────────────────────────────────────────────── +# Шаблоны живут в админке (sms_campaign_settings): +# sms_text_template — текст для ДОСТАВКИ +# sms_text_template_pickup — текст для САМОВЫВОЗА +# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. + +DEFAULT_TEXT_DELIVERY = ( + "Ваш заказ переведён на платное хранение. Стоимость: 300 ₽/день. " + "Заберите заказ или согласуйте доставку: {link}" +) +DEFAULT_TEXT_PICKUP = ( + "Ваш заказ переведён на платное хранение. Стоимость: 300 ₽/день. " + "Заберите заказ со склада или согласуйте дату самовывоза: {link}" +) + + +def build_sms_text(group, settings): + """Текст SMS о платном хранении по типу отгрузки.""" + link = group.get("delivery_link") or "" + pickup = (group.get("delivery_type") or "delivery").strip().lower() == "pickup" + template = (settings.get( + "sms_text_template_pickup" if pickup else "sms_text_template" + ) or "").strip() + if not template: + template = DEFAULT_TEXT_PICKUP if pickup else DEFAULT_TEXT_DELIVERY + return template.replace("{link}", link).strip() + # ─── Логирование ───────────────────────────────────────────────────────────── logging.basicConfig( @@ -195,7 +222,7 @@ def get_groups_to_send(conn): cur.execute(""" SELECT og.id, og.group_key, og.customer_name, og.customer_phone, og.customer_phone_normalized, og.delivery_link, og.notification_status, - og.paid_storage_at + og.paid_storage_at, og.delivery_type, og.town FROM order_groups og LEFT JOIN sms_city_profiles scp ON scp.city = og.town WHERE og.delivery_status = 'paid_storage' @@ -321,11 +348,8 @@ def step_send_new(conn, settings): log.info(f"Group {group_id}: already has recent paid_storage SMS, skipping") continue - sms_text = ( - f"Ваш заказ переведён на платное хранение. " - f"Стоимость: 300 ₽/день. " - f"Заберите заказ или согласуйте доставку: {delivery_link}" - ) + sms_text = build_sms_text(group, settings) + log.info(f"Group {group_id}: paid_storage SMS ({group.get('delivery_type') or 'delivery'}): {sms_text!r}") send_phone = phone if settings.get("test_mode", True): diff --git a/scripts/sms_second_campaign.py b/scripts/sms_second_campaign.py index 2861530..8fa3a0c 100644 --- a/scripts/sms_second_campaign.py +++ b/scripts/sms_second_campaign.py @@ -63,6 +63,27 @@ DELIVERY_ERROR_CODES = {"104", "105", "106", "107", "108", "130"} # Коды превышения лимитов (нельзя повторять отправку — заблокируют) LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} +# ─── Тексты SMS ────────────────────────────────────────────────────────────── +# Шаблоны живут в админке (sms_campaign_settings): +# sms_text_template — текст для ДОСТАВКИ +# sms_text_template_pickup — текст для САМОВЫВОЗА +# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. + +DEFAULT_TEXT_DELIVERY = "Ваш заказ готов к доставке. Выберите дату доставки по ссылке: {link}" +DEFAULT_TEXT_PICKUP = "Ваш заказ готов к самовывозу. Выберите дату самовывоза по ссылке: {link}" + + +def build_sms_text(group, settings): + """Текст второй SMS по типу отгрузки: самовывоз → pickup-шаблон, иначе обычный.""" + link = group.get("delivery_link") or "" + pickup = (group.get("delivery_type") or "delivery").strip().lower() == "pickup" + template = (settings.get( + "sms_text_template_pickup" if pickup else "sms_text_template" + ) or "").strip() + if not template: + template = DEFAULT_TEXT_PICKUP if pickup else DEFAULT_TEXT_DELIVERY + return template.replace("{link}", link).strip() + # ─── Логирование ───────────────────────────────────────────────────────────── logging.basicConfig( @@ -215,7 +236,8 @@ def get_groups_to_send(conn, max_attempts): with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute(""" SELECT og.id, og.group_key, og.customer_name, og.customer_phone, - og.customer_phone_normalized, og.delivery_link, og.notification_status + og.customer_phone_normalized, og.delivery_link, og.notification_status, + og.delivery_type, og.town FROM order_groups og LEFT JOIN sms_city_profiles scp ON scp.city = og.town WHERE og.delivery_status = 'pending_confirmation' @@ -401,7 +423,8 @@ def step_send_new(conn, settings): log.info(f"Group {group_id}: already has recent second SMS in log, skipping") continue - sms_text = f"Ваш заказ готов к доставке. Выберите дату доставки по ссылке: {delivery_link}" + sms_text = build_sms_text(group, settings) + log.info(f"Group {group_id}: second SMS ({group.get('delivery_type') or 'delivery'}), town={group.get('town') or '—'}: {sms_text!r}") # ТЕСТОВЫЙ РЕЖИМ send_phone = phone diff --git a/src/components/admin/SmsCampaignPanel.jsx b/src/components/admin/SmsCampaignPanel.jsx index e55163b..8a80c86 100644 --- a/src/components/admin/SmsCampaignPanel.jsx +++ b/src/components/admin/SmsCampaignPanel.jsx @@ -22,6 +22,15 @@ const CAMPAIGNS = [ const HAS_SETTINGS = ["first_sms", "second_sms", "manual", "paid_storage"]; +// ── SMS text templates (доставка / самовывоз) ──────────────────────────────── +const SMS_LINK_PREVIEW = "https://dost.supersamsev.ru/d/XXXXXX"; + +const renderSmsPreview = (template, emptyLabel) => { + const text = (template || "").trim(); + if (!text) return emptyLabel; + return text.replace("{link}", SMS_LINK_PREVIEW); +}; + // ── Days of week ───────────────────────────────────────────────────────────── const DAYS = [ { num: 1, short: "Пн" }, @@ -488,19 +497,43 @@ export const SmsCampaignPanel = () => { )} - {/* ── SMS text template ──────────────────────────────────────────── */} + {/* ── SMS text templates: доставка / самовывоз ───────────────────── */} {!isManualCampaign && (
📝 Текст SMS
-