diff --git a/public/service-worker.js b/public/service-worker.js index cb6d884..adbfca9 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-v118"; - const RUNTIME_CACHE = "construction-delivery-runtime-v118"; + const STATIC_CACHE = "construction-delivery-static-v119"; + const RUNTIME_CACHE = "construction-delivery-runtime-v119"; 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 1b75414..9a56f73 100644 --- a/scripts/sms_first_campaign.py +++ b/scripts/sms_first_campaign.py @@ -68,10 +68,16 @@ LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} # Шаблоны живут в админке (sms_campaign_settings): # sms_text_template — текст для ДОСТАВКИ # sms_text_template_pickup — текст для САМОВЫВОЗА -# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. +# Подстановки: {link}/{ссылка} — ссылка согласования, +# {order}/{номер}/{номер_доставки} — номер доставки (010796), +# {code}/{код}/{код_выдачи} — код выдачи (7598). +# Пустой шаблон → DEFAULT_TEXT_*. DEFAULT_TEXT_DELIVERY = "Ваш заказ готов. Согласуйте дату доставки по ссылке: {link}" -DEFAULT_TEXT_PICKUP = "Ваш заказ готов. Согласуйте дату самовывоза по ссылке: {link}" +DEFAULT_TEXT_PICKUP = ( + "Ваш заказ готов. Номер доставки {order}, код выдачи {code}. " + "Согласуйте дату самовывоза по ссылке: {link}" +) DEFAULT_TEXT_NOTIFICATION = "Ваш заказ готов." @@ -79,6 +85,35 @@ def is_pickup_group(group): return (group.get("delivery_type") or "delivery").strip().lower() == "pickup" +def first_order_digits(value): + """Номер доставки из order_numbers («ТП\\-010722» → «010722»).""" + items = value if isinstance(value, (list, tuple)) else [value] + for item in items: + digits = "".join(ch for ch in str(item or "").replace("\\", "") if ch.isdigit()) + if digits: + return digits + return "" + + +def apply_sms_placeholders(text, group, link): + """Подстановка переменных шаблона в текст SMS.""" + if not text: + return text + + order = first_order_digits(group.get("order_numbers")) + code = "".join(ch for ch in str(group.get("pickup_code") or "") if ch.isdigit()) + replacements = ( + ("{link}", link), ("{ссылка}", link), + ("{номер_доставки}", order), ("{номер}", order), ("{order}", order), + ("{код_выдачи}", code), ("{код}", code), ("{code}", code), + ) + + result = text + for token, value in replacements: + result = result.replace(token, value) + return result.strip() + + def build_sms_text(group, settings): """Текст SMS по типу отгрузки: самовывоз → pickup-шаблон, доставка → обычный. @@ -99,13 +134,13 @@ def build_sms_text(group, settings): if mode == "notification": if profile_text: - return profile_text + return apply_sms_placeholders(profile_text, group, link) # уведомление без ссылки: шаблон годится, только если в нём нет {link} if template and "{link}" not in template: - return template - return DEFAULT_TEXT_NOTIFICATION + return apply_sms_placeholders(template, group, link) + return apply_sms_placeholders(DEFAULT_TEXT_NOTIFICATION, group, link) - return template.replace("{link}", link).strip() + return apply_sms_placeholders(template, group, link) # ─── Логирование ───────────────────────────────────────────────────────────── @@ -227,6 +262,7 @@ def get_groups_to_send(conn, max_attempts): SELECT og.id, og.group_key, og.customer_name, og.customer_phone, og.customer_phone_normalized, og.delivery_link, og.notification_status, og.delivery_type, og.town, og.sms_attempts, + og.order_numbers, og.pickup_code, -- Профиль города (fallback на дефолтные значения если нет записи) COALESCE(scp.delivery_mode, 'link') as delivery_mode, COALESCE(scp.delivery_text, '') as delivery_text, diff --git a/scripts/sms_paid_storage_campaign.py b/scripts/sms_paid_storage_campaign.py index 64ba9da..877de44 100644 --- a/scripts/sms_paid_storage_campaign.py +++ b/scripts/sms_paid_storage_campaign.py @@ -55,18 +55,50 @@ LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} # Шаблоны живут в админке (sms_campaign_settings): # sms_text_template — текст для ДОСТАВКИ # sms_text_template_pickup — текст для САМОВЫВОЗА -# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. +# Подстановки: {link}/{ссылка} — ссылка согласования, +# {order}/{номер}/{номер_доставки} — номер доставки (010796), +# {code}/{код}/{код_выдачи} — код выдачи (7598). +# Пустой шаблон → DEFAULT_TEXT_*. DEFAULT_TEXT_DELIVERY = ( "Ваш заказ переведён на платное хранение. Стоимость: 300 ₽/день. " "Заберите заказ или согласуйте доставку: {link}" ) DEFAULT_TEXT_PICKUP = ( - "Ваш заказ переведён на платное хранение. Стоимость: 300 ₽/день. " - "Заберите заказ со склада или согласуйте дату самовывоза: {link}" + "Заказ переведён на платное хранение. Номер доставки {order}, код выдачи {code}. " + "Заберите со склада или согласуйте дату самовывоза: {link}" ) +def first_order_digits(value): + """Номер доставки из order_numbers («ТП\\-010722» → «010722»).""" + items = value if isinstance(value, (list, tuple)) else [value] + for item in items: + digits = "".join(ch for ch in str(item or "").replace("\\", "") if ch.isdigit()) + if digits: + return digits + return "" + + +def apply_sms_placeholders(text, group, link): + """Подстановка переменных шаблона в текст SMS.""" + if not text: + return text + + order = first_order_digits(group.get("order_numbers")) + code = "".join(ch for ch in str(group.get("pickup_code") or "") if ch.isdigit()) + replacements = ( + ("{link}", link), ("{ссылка}", link), + ("{номер_доставки}", order), ("{номер}", order), ("{order}", order), + ("{код_выдачи}", code), ("{код}", code), ("{code}", code), + ) + + result = text + for token, value in replacements: + result = result.replace(token, value) + return result.strip() + + def build_sms_text(group, settings): """Текст SMS о платном хранении по типу отгрузки.""" link = group.get("delivery_link") or "" @@ -76,7 +108,7 @@ def build_sms_text(group, settings): ) or "").strip() if not template: template = DEFAULT_TEXT_PICKUP if pickup else DEFAULT_TEXT_DELIVERY - return template.replace("{link}", link).strip() + return apply_sms_placeholders(template, group, link) # ─── Логирование ───────────────────────────────────────────────────────────── @@ -222,7 +254,8 @@ 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.delivery_type, og.town + og.paid_storage_at, og.delivery_type, og.town, + og.order_numbers, og.pickup_code FROM order_groups og LEFT JOIN sms_city_profiles scp ON scp.city = og.town WHERE og.delivery_status = 'paid_storage' diff --git a/scripts/sms_second_campaign.py b/scripts/sms_second_campaign.py index 8fa3a0c..8efee5b 100644 --- a/scripts/sms_second_campaign.py +++ b/scripts/sms_second_campaign.py @@ -67,10 +67,45 @@ LIMIT_ERROR_CODES = {"131", "132", "230", "231", "232"} # Шаблоны живут в админке (sms_campaign_settings): # sms_text_template — текст для ДОСТАВКИ # sms_text_template_pickup — текст для САМОВЫВОЗА -# {link} подставляется ссылкой согласования. Пустой шаблон → DEFAULT_TEXT_*. +# Подстановки: {link}/{ссылка} — ссылка согласования, +# {order}/{номер}/{номер_доставки} — номер доставки (010796), +# {code}/{код}/{код_выдачи} — код выдачи (7598). +# Пустой шаблон → DEFAULT_TEXT_*. DEFAULT_TEXT_DELIVERY = "Ваш заказ готов к доставке. Выберите дату доставки по ссылке: {link}" -DEFAULT_TEXT_PICKUP = "Ваш заказ готов к самовывозу. Выберите дату самовывоза по ссылке: {link}" +DEFAULT_TEXT_PICKUP = ( + "Напоминаем: номер доставки {order}, код выдачи {code}. " + "Выберите дату самовывоза по ссылке: {link}" +) + + +def first_order_digits(value): + """Номер доставки из order_numbers («ТП\\-010722» → «010722»).""" + items = value if isinstance(value, (list, tuple)) else [value] + for item in items: + digits = "".join(ch for ch in str(item or "").replace("\\", "") if ch.isdigit()) + if digits: + return digits + return "" + + +def apply_sms_placeholders(text, group, link): + """Подстановка переменных шаблона в текст SMS.""" + if not text: + return text + + order = first_order_digits(group.get("order_numbers")) + code = "".join(ch for ch in str(group.get("pickup_code") or "") if ch.isdigit()) + replacements = ( + ("{link}", link), ("{ссылка}", link), + ("{номер_доставки}", order), ("{номер}", order), ("{order}", order), + ("{код_выдачи}", code), ("{код}", code), ("{code}", code), + ) + + result = text + for token, value in replacements: + result = result.replace(token, value) + return result.strip() def build_sms_text(group, settings): @@ -82,7 +117,7 @@ def build_sms_text(group, settings): ) or "").strip() if not template: template = DEFAULT_TEXT_PICKUP if pickup else DEFAULT_TEXT_DELIVERY - return template.replace("{link}", link).strip() + return apply_sms_placeholders(template, group, link) # ─── Логирование ───────────────────────────────────────────────────────────── @@ -237,7 +272,7 @@ def get_groups_to_send(conn, max_attempts): 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.delivery_type, og.town + og.delivery_type, og.town, og.order_numbers, og.pickup_code FROM order_groups og LEFT JOIN sms_city_profiles scp ON scp.city = og.town WHERE og.delivery_status = 'pending_confirmation' diff --git a/src/components/admin/SmsCampaignPanel.jsx b/src/components/admin/SmsCampaignPanel.jsx index 8a80c86..77d54c4 100644 --- a/src/components/admin/SmsCampaignPanel.jsx +++ b/src/components/admin/SmsCampaignPanel.jsx @@ -24,13 +24,23 @@ const HAS_SETTINGS = ["first_sms", "second_sms", "manual", "paid_storage"]; // ── SMS text templates (доставка / самовывоз) ──────────────────────────────── const SMS_LINK_PREVIEW = "https://dost.supersamsev.ru/d/XXXXXX"; +// Образцы значений для предпросмотра (как будет выглядеть SMS у клиента) +const SMS_PREVIEW_VALUES = [ + ["{номер_доставки}", "010796"], ["{номер}", "010796"], ["{order}", "010796"], + ["{код_выдачи}", "7598"], ["{код}", "7598"], ["{code}", "7598"], + ["{ссылка}", SMS_LINK_PREVIEW], ["{link}", SMS_LINK_PREVIEW], +]; -const renderSmsPreview = (template, emptyLabel) => { - const text = (template || "").trim(); - if (!text) return emptyLabel; - return text.replace("{link}", SMS_LINK_PREVIEW); +const smsPreviewText = (template) => { + let text = (template || "").trim(); + SMS_PREVIEW_VALUES.forEach(([token, value]) => { + text = text.split(token).join(value); + }); + return text; }; +const renderSmsPreview = (template, emptyLabel) => smsPreviewText(template) || emptyLabel; + // ── Days of week ───────────────────────────────────────────────────────────── const DAYS = [ { num: 1, short: "Пн" }, @@ -512,7 +522,7 @@ export const SmsCampaignPanel = () => { placeholder="Ваш заказ готов. Согласуйте дату доставки по ссылке: {link}" />
- Текущая длина: {(settings.sms_text_template || "").replace("{link}", SMS_LINK_PREVIEW).length} символов + Текущая длина: {smsPreviewText(settings.sms_text_template).length} символов
@@ -525,12 +535,15 @@ export const SmsCampaignPanel = () => { placeholder="Ваш заказ готов. Согласуйте дату самовывоза по ссылке: {link}" />
- Текущая длина: {(settings.sms_text_template_pickup || "").replace("{link}", SMS_LINK_PREVIEW).length} символов + Текущая длина: {smsPreviewText(settings.sms_text_template_pickup).length} символов
- Текст выбирается по типу отгрузки: самовывоз → правый шаблон, доставка → левый. {"{link}"} заменяется ссылкой согласования. + Текст выбирается по типу отгрузки: самовывоз → правый шаблон, доставка → левый. +
+ Переменные: {"{link}"} — ссылка согласования, {"{order}"} — номер доставки (можно {"{номер}"}), {"{code}"} — код выдачи (можно {"{код}"}). +
📦 Доставка: {renderSmsPreview(settings.sms_text_template, "— пусто, уйдёт стандартный текст кампании")}
🏪 Самовывоз: {renderSmsPreview(settings.sms_text_template_pickup, "— пусто, уйдёт стандартный текст кампании")}
Если у города в «SMS по городам» задан свой текст в режиме «Уведомление» — уходит он, и тогда шаблон применяется только если в нём нет {"{link}"}.
diff --git a/src/components/client/DeliveryChoiceFlow.test.jsx b/src/components/client/DeliveryChoiceFlow.test.jsx index 0e29011..98fffc5 100644 --- a/src/components/client/DeliveryChoiceFlow.test.jsx +++ b/src/components/client/DeliveryChoiceFlow.test.jsx @@ -84,7 +84,7 @@ describe("DeliveryChoiceFlow", () => { />, ); - expect(markup).toContain("Счет CD-240032"); + expect(markup).toContain("Доставка CD-240032"); expect(markup).not.toContain("Александр Савин"); }); }); diff --git a/src/components/client/invitationReference.js b/src/components/client/invitationReference.js index 13413b1..e64216c 100644 --- a/src/components/client/invitationReference.js +++ b/src/components/client/invitationReference.js @@ -20,20 +20,42 @@ const getOrderItemNames = (invitation) => { .filter(Boolean); }; +// Обмен с 1С экранирует дефис («ТП\-010796») — клиенту показываем «ТП-010796». +const unescapeOncDashes = (value) => String(value || "").replace(/\\/g, ""); + export const getInvitationReferenceLabel = (invitation) => { - const invoiceNumbers = [...new Set(getOrderItemNames(invitation))]; + const invoiceNumbers = [...new Set(getOrderItemNames(invitation))].map(unescapeOncDashes); if (invoiceNumbers.length === 1) { - return `Счет ${invoiceNumbers[0]}`; + return `Доставка ${invoiceNumbers[0]}`; } if (invoiceNumbers.length > 1) { - return `Счета: ${invoiceNumbers.join(", ")}`; + return `Доставки: ${invoiceNumbers.join(", ")}`; } - const orderNumber = typeof invitation?.orderNumber === "string" ? invitation.orderNumber.trim() : ""; + const orderNumber = typeof invitation?.orderNumber === "string" + ? unescapeOncDashes(invitation.orderNumber.trim()) + : ""; if (orderNumber) { - return `Счет ${orderNumber}`; + return `Доставка ${orderNumber}`; } - return "Счет —"; + return "Доставка —"; +}; + +/** + * Номер доставки для клиента: «ТП\-010796» → «010796». + * Сначала берём номер заказа из состава, затем orderNumber/orderNumbers приглашения. + */ +export const getDeliveryNumberDigits = (invitation) => { + const fromItems = getOrderItemNames(invitation)[0] || ""; + const fromOrderNumbers = Array.isArray(invitation?.orderNumbers) + ? invitation.orderNumbers[0] || "" + : ""; + const fromOrderNumber = typeof invitation?.orderNumber === "string" + ? invitation.orderNumber + : ""; + const source = fromItems || fromOrderNumbers || fromOrderNumber; + + return unescapeOncDashes(source).replace(/[^0-9]/g, ""); }; diff --git a/src/pages/ClientDeliveryPage.jsx b/src/pages/ClientDeliveryPage.jsx index 545840c..c2daa3f 100644 --- a/src/pages/ClientDeliveryPage.jsx +++ b/src/pages/ClientDeliveryPage.jsx @@ -4,7 +4,7 @@ import { DeliveryChoiceFlow } from "../components/client/DeliveryChoiceFlow"; import { DeliverySlotsPicker } from "../components/client/DeliverySlotsPicker"; import { PickupSlotsPicker } from "../components/client/PickupSlotsPicker"; import { OrderCompositionPanel } from "../components/client/OrderCompositionPanel"; -import { getInvitationReferenceLabel } from "../components/client/invitationReference"; +import { getDeliveryNumberDigits, getInvitationReferenceLabel } from "../components/client/invitationReference"; import { DeliveryStateNotice } from "../components/client/DeliveryStateNotice"; import { PushSubscriptionBanner } from "../components/client/PushSubscriptionBanner"; import { PwaInstallCard } from "../components/client/PwaInstallCard"; @@ -299,13 +299,25 @@ export const buildSelectedSlotFromInvitation = (invitation, slots = []) => { }; }; -export const getClientDeliveryHeroDescription = (isActiveState, isChoiceSaved) => { +export const getClientDeliveryHeroDescription = ( + isActiveState, + isChoiceSaved, + deliveryType = "delivery", +) => { if (isChoiceSaved) { return ""; } - return isActiveState - ? "Вам предложены варианты доставки. Выберите удобную дату и время." + const isPickup = deliveryType === "pickup"; + + if (isActiveState) { + return isPickup + ? "Вам предложены варианты самовывоза. Выберите удобную дату и время." + : "Вам предложены варианты доставки. Выберите удобную дату и время."; + } + + return isPickup + ? "По этому заказу согласование самовывоза завершено или передано логисту." : "По этому заказу согласование доставки завершено или передано логисту."; }; @@ -399,6 +411,10 @@ export const ClientDeliveryPage = () => { const invitationState = invitation?.state || "awaiting_choice"; const isActiveState = ["awaiting_choice", "opened", "reminder_sent"].includes(invitationState); + // Тип отгрузки приходит из приглашения: для самовывоза страница говорит «самовывоз». + const orderIsPickup = invitation?.deliveryType === "pickup"; + const deliveryNumberDigits = getDeliveryNumberDigits(invitation); + const orderTypeEyebrow = orderIsPickup ? "Самовывоз заказа" : "Доставка заказа"; const invitationSelectedSlot = isActiveState ? null : buildSelectedSlotFromInvitation(invitation, slots); @@ -407,7 +423,11 @@ export const ClientDeliveryPage = () => { const savedChoiceLabel = effectiveSelectedSlot ? `${formatDeliveryDate(effectiveSelectedSlot.date)} / ${effectiveSelectedSlot.time}` : ""; - const heroDescription = getClientDeliveryHeroDescription(isActiveState, isChoiceSaved); + const heroDescription = getClientDeliveryHeroDescription( + isActiveState, + isChoiceSaved, + invitation?.deliveryType, + ); const handleSaveChoice = async () => { if (!token) { @@ -558,8 +578,10 @@ export const ClientDeliveryPage = () => {
{!isChoiceSaved ? ( -

Доставка заказа

-

Согласование доставки

+

{orderTypeEyebrow}

+

+ {orderIsPickup ? "Согласование самовывоза" : "Согласование доставки"} +

{heroDescription ? (

{heroDescription} @@ -591,7 +613,10 @@ export const ClientDeliveryPage = () => {

{(invitation?.pickupCode || invitation?.pickup_code) ? (
-

Код выдачи

+ {deliveryNumberDigits ? ( +

Ваш номер доставки {deliveryNumberDigits}

+ ) : null} +

Ваш код выдачи

{invitation.pickupCode || invitation.pickup_code}

Покажите этот код при получении заказа

@@ -694,7 +719,10 @@ export const ClientDeliveryPage = () => { {activeTab === TAB_PICKUP && (invitation?.pickupCode || invitation?.pickup_code) ? (
-

Код выдачи

+ {deliveryNumberDigits ? ( +

Ваш номер доставки {deliveryNumberDigits}

+ ) : null} +

Ваш код выдачи

{invitation.pickupCode || invitation.pickup_code}

Покажите этот код при получении заказа

diff --git a/src/pages/ClientDeliveryPage.test.js b/src/pages/ClientDeliveryPage.test.js index 8b7ab68..5206058 100644 --- a/src/pages/ClientDeliveryPage.test.js +++ b/src/pages/ClientDeliveryPage.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { getInvitationReferenceLabel } from "../components/client/invitationReference"; +import { getDeliveryNumberDigits, getInvitationReferenceLabel } from "../components/client/invitationReference"; import { buildDeliveryConfirmationPayload, buildSelectedSlotFromInvitation, @@ -176,7 +176,16 @@ describe("ClientDeliveryPage helpers", () => { { name: "СФ Т\\ЕА-28700", quantity: "" }, ], }), - ).toBe("Счета: СФ Т\\ЕА-28687, СФ Т\\ЕА-28700"); + ).toBe("Доставки: СФ ТЕА-28687, СФ ТЕА-28700"); + }); + + it("берёт номер доставки из состава заказа: «ТП\\-010796» → «010796»", () => { + expect( + getDeliveryNumberDigits({ orderItems: [{ name: "ТП\\-010796", quantity: "" }] }), + ).toBe("010796"); + expect(getDeliveryNumberDigits({ orderNumbers: ["ТП\\-010557", "ТП\\-010599"] })).toBe("010557"); + expect(getDeliveryNumberDigits({ orderNumber: "9788324113|28.09.2026" })).toBe("978832411328092026"); + expect(getDeliveryNumberDigits({})).toBe(""); }); it("hides the hero helper text after the client saves the choice", () => { @@ -189,6 +198,16 @@ describe("ClientDeliveryPage helpers", () => { ); }); + it("говорит про самовывоз, если отгрузка самовывозом", () => { + expect(getClientDeliveryHeroDescription(true, false, "pickup")).toBe( + "Вам предложены варианты самовывоза. Выберите удобную дату и время.", + ); + expect(getClientDeliveryHeroDescription(false, false, "pickup")).toBe( + "По этому заказу согласование самовывоза завершено или передано логисту.", + ); + expect(getClientDeliveryHeroDescription(true, true, "pickup")).toBe(""); + }); + it("skips Sunday when generating fallback delivery slots from Friday", () => { // 2026-04-17 is Friday → next workday = Saturday 18, then Monday 20 (skip Sunday 19) const slots = groupSlotsFromInvitation( diff --git a/supabase/functions/_shared/delivery-invitations.ts b/supabase/functions/_shared/delivery-invitations.ts index 1637dc0..33186eb 100644 --- a/supabase/functions/_shared/delivery-invitations.ts +++ b/supabase/functions/_shared/delivery-invitations.ts @@ -242,6 +242,8 @@ export type OrderGroupInvitationSource = { delivery_status?: string | null; delivery_link?: string | null; source_orders?: unknown[] | null; + delivery_type?: string | null; + pickup_code?: string | null; }; export const isInvitationExpired = (invitation: DeliveryInvitationRecord, now = new Date()) => { if (invitation.revoked_at) { @@ -325,6 +327,11 @@ export const buildPublicOrderGroupInvitationView = ( customerName: maskCustomerName(customerName), customerPhone: maskPhoneNumber(customerPhone), orderItems, + // Тип отгрузки и код выдачи — клиентская страница показывает «самовывоз» + // и номер доставки с кодом выдачи по этим полям. + deliveryType: group.delivery_type || null, + pickupCode: group.pickup_code || null, + orderNumbers, availableSlots: invitation.available_slots || [], deliveryDate: invitation.delivery_date || null, deliveryTime: invitation.delivery_time || null, diff --git a/supabase/sms_pickup_code_placeholders.sql b/supabase/sms_pickup_code_placeholders.sql new file mode 100644 index 0000000..0f7ccc2 --- /dev/null +++ b/supabase/sms_pickup_code_placeholders.sql @@ -0,0 +1,33 @@ +-- Код выдачи и номер доставки в SMS для самовывоза +-- 2026-09-28 +-- +-- Переменные шаблона (sms_text_template / sms_text_template_pickup): +-- {link} / {ссылка} — ссылка согласования +-- {order} / {номер} — номер доставки без префикса (010796) +-- {code} / {код} — код выдачи (7598) +-- Подстановку делает скрипт кампании (apply_sms_placeholders). + +UPDATE sms_campaign_settings SET + sms_text_template_pickup = 'Ваш заказ готов. Номер доставки {order}, код выдачи {code}. Согласуйте дату самовывоза по ссылке: {link}' +WHERE campaign_type = 'first_sms'; + +UPDATE sms_campaign_settings SET + sms_text_template_pickup = 'Напоминаем: номер доставки {order}, код выдачи {code}. Выберите дату самовывоза по ссылке: {link}' +WHERE campaign_type = 'second_sms'; + +UPDATE sms_campaign_settings SET + sms_text_template_pickup = 'Заказ переведён на платное хранение. Номер доставки {order}, код выдачи {code}. Заберите со склада или согласуйте дату самовывоза: {link}' +WHERE campaign_type = 'paid_storage'; + +-- Текст самовывоза для городов в режиме «Уведомление» (сейчас Ялта): +-- переменные работают и в профиле города. +UPDATE sms_city_profiles SET + pickup_text = 'Ваш заказ готов. Номер доставки {order}, код выдачи {code}. Заберите со склада.', + updated_at = NOW() +WHERE pickup_text IS NOT NULL + AND pickup_text <> '' + AND pickup_text NOT LIKE '%{code}%'; + +-- Проверка: +-- SELECT campaign_type, sms_text_template_pickup FROM sms_campaign_settings ORDER BY 1; +-- SELECT city, pickup_text FROM sms_city_profiles;