fix: stop SMS UI for terminal delivery statuses (picked_up/delivered/cancelled)
- SmsStatusCard: show "SMS не отправляется" when delivery is terminal, hide countdown + restart buttons
- SmsCampaignPanel fmtAgreement: check terminal status FIRST → "✅ Самовывоз завершён" instead of "Самовывоз (неполно)"
- update_delivery_status RPC: reset notification_status→completed on terminal transition
- Add completed to notification_status check constraint
- SW v60→v61
This commit is contained in:
parent
43d9116c51
commit
eabf30bceb
|
|
@ -1,8 +1,8 @@
|
||||||
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
||||||
|
|
||||||
if (!isLocalhost) {
|
if (!isLocalhost) {
|
||||||
const STATIC_CACHE = "construction-delivery-static-v60";
|
const STATIC_CACHE = "construction-delivery-static-v61";
|
||||||
const RUNTIME_CACHE = "construction-delivery-runtime-v60";
|
const RUNTIME_CACHE = "construction-delivery-runtime-v61";
|
||||||
const APP_SHELL_URLS = ["/", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
const APP_SHELL_URLS = ["/", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ const NOTIF_LABELS = {
|
||||||
"manual_required": "🔧 Ручное управление",
|
"manual_required": "🔧 Ручное управление",
|
||||||
"paid_storage_sending": "📡 Отправляется…",
|
"paid_storage_sending": "📡 Отправляется…",
|
||||||
"paid_storage_sent": "✓ Платное хранение: отправлено",
|
"paid_storage_sent": "✓ Платное хранение: отправлено",
|
||||||
|
"completed": "✅ Завершено",
|
||||||
"confirmed": "✅ Согласовано клиентом",
|
"confirmed": "✅ Согласовано клиентом",
|
||||||
"address_required": "⚠ Требуется адрес",
|
"address_required": "⚠ Требуется адрес",
|
||||||
"draft": "Черновик",
|
"draft": "Черновик",
|
||||||
|
|
@ -103,6 +104,7 @@ const NOTIF_TONES = {
|
||||||
"manual_required": "warning",
|
"manual_required": "warning",
|
||||||
"paid_storage_sending": "info",
|
"paid_storage_sending": "info",
|
||||||
"paid_storage_sent": "accent",
|
"paid_storage_sent": "accent",
|
||||||
|
"completed": "accent",
|
||||||
"confirmed": "accent",
|
"confirmed": "accent",
|
||||||
"address_required": "danger",
|
"address_required": "danger",
|
||||||
"draft": "neutral",
|
"draft": "neutral",
|
||||||
|
|
@ -209,7 +211,18 @@ const fmtAgreement = (g) => {
|
||||||
if (!g) return { text: "—", tone: "neutral", icon: "⏳" };
|
if (!g) return { text: "—", tone: "neutral", icon: "⏳" };
|
||||||
const ds = g.delivery_status;
|
const ds = g.delivery_status;
|
||||||
|
|
||||||
// If client already chose date/time — show it regardless of delivery_status
|
// Terminal states first — delivery already happened or cancelled
|
||||||
|
if (ds === "picked_up") {
|
||||||
|
return { text: "✅ Самовывоз завершён", tone: "accent", icon: "✅" };
|
||||||
|
}
|
||||||
|
if (ds === "delivered") {
|
||||||
|
return { text: "✅ Доставлено", tone: "accent", icon: "✅" };
|
||||||
|
}
|
||||||
|
if (ds === "cancelled") {
|
||||||
|
return { text: "❌ Отменён", tone: "danger", icon: "❌" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If client already chose date/time — show it
|
||||||
if (g.delivery_type === "pickup" || g.pickup_date) {
|
if (g.delivery_type === "pickup" || g.pickup_date) {
|
||||||
const date = g.pickup_date ? new Date(g.pickup_date).toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" }) : "";
|
const date = g.pickup_date ? new Date(g.pickup_date).toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" }) : "";
|
||||||
const slot = g.pickup_time_slot || "";
|
const slot = g.pickup_time_slot || "";
|
||||||
|
|
@ -236,9 +249,6 @@ const fmtAgreement = (g) => {
|
||||||
if (ds === "paid_storage") {
|
if (ds === "paid_storage") {
|
||||||
return { text: "📦 Платное хранение", tone: "warning", icon: "📦" };
|
return { text: "📦 Платное хранение", tone: "warning", icon: "📦" };
|
||||||
}
|
}
|
||||||
if (ds === "cancelled") {
|
|
||||||
return { text: "❌ Отменён", tone: "danger", icon: "❌" };
|
|
||||||
}
|
|
||||||
if (ds === "problem") {
|
if (ds === "problem") {
|
||||||
return { text: "⚠ Проблема", tone: "danger", icon: "⚠" };
|
return { text: "⚠ Проблема", tone: "danger", icon: "⚠" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const NOTIF_LABELS = {
|
||||||
manual_required: "Требуется ручное управление",
|
manual_required: "Требуется ручное управление",
|
||||||
paid_storage_sending: "Отправляется…",
|
paid_storage_sending: "Отправляется…",
|
||||||
paid_storage_sent: "Платное хранение: отправлено",
|
paid_storage_sent: "Платное хранение: отправлено",
|
||||||
|
completed: "✅ Завершено",
|
||||||
draft: "Черновик",
|
draft: "Черновик",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ const NOTIF_TONES = {
|
||||||
manual_required: "warning",
|
manual_required: "warning",
|
||||||
paid_storage_sending: "info",
|
paid_storage_sending: "info",
|
||||||
paid_storage_sent: "accent",
|
paid_storage_sent: "accent",
|
||||||
|
completed: "accent",
|
||||||
draft: "neutral",
|
draft: "neutral",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,6 +108,7 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
const canManage = ["mega_admin", "admin"].includes(userRole);
|
const canManage = ["mega_admin", "admin"].includes(userRole);
|
||||||
|
|
||||||
const notifStatus = order.notificationStatus || order.notification_status || "not_started";
|
const notifStatus = order.notificationStatus || order.notification_status || "not_started";
|
||||||
|
const deliveryStatus = order.deliveryStatus || order.delivery_status || "";
|
||||||
const nextCheck = order.nextNotificationCheckAt || order.next_notification_check_at;
|
const nextCheck = order.nextNotificationCheckAt || order.next_notification_check_at;
|
||||||
const firstSmsAt = order.firstSmsSentAt || order.first_sms_sent_at;
|
const firstSmsAt = order.firstSmsSentAt || order.first_sms_sent_at;
|
||||||
const secondSmsAt = order.secondSmsSentAt || order.second_sms_sent_at;
|
const secondSmsAt = order.secondSmsSentAt || order.second_sms_sent_at;
|
||||||
|
|
@ -113,6 +116,14 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
const smsAttempts = order.smsAttempts ?? order.sms_attempts ?? 0;
|
const smsAttempts = order.smsAttempts ?? order.sms_attempts ?? 0;
|
||||||
const lastError = order.lastSmsError || order.last_sms_error;
|
const lastError = order.lastSmsError || order.last_sms_error;
|
||||||
|
|
||||||
|
// Terminal delivery states — SMS flow should be fully stopped
|
||||||
|
const TERMINAL_STATUSES = ["picked_up", "delivered", "cancelled"];
|
||||||
|
const isTerminal = TERMINAL_STATUSES.includes(deliveryStatus);
|
||||||
|
const terminalLabel = deliveryStatus === "picked_up" ? "Самовывоз завершён"
|
||||||
|
: deliveryStatus === "delivered" ? "Доставка завершена"
|
||||||
|
: deliveryStatus === "cancelled" ? "Заказ отменён"
|
||||||
|
: "";
|
||||||
|
|
||||||
// Restart: reset this group's notification_status to link_ready
|
// Restart: reset this group's notification_status to link_ready
|
||||||
const handleRestart = async () => {
|
const handleRestart = async () => {
|
||||||
if (!order?.id) return;
|
if (!order?.id) return;
|
||||||
|
|
@ -205,11 +216,13 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
|
|
||||||
{/* 2nd SMS */}
|
{/* 2nd SMS */}
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasSecondSms ? "bg-[#22c55e]" : notifStatus === "second_sms_sending" ? "bg-[var(--color-accent)]" : notifStatus === "first_sms_sent" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
|
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasSecondSms ? "bg-[#22c55e]" : isTerminal ? "bg-[var(--color-border)]" : notifStatus === "second_sms_sending" ? "bg-[var(--color-accent)]" : notifStatus === "first_sms_sent" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="text-[var(--color-text)]">2-е SMS</div>
|
<div className="text-[var(--color-text)]">2-е SMS</div>
|
||||||
{hasSecondSms ? (
|
{hasSecondSms ? (
|
||||||
<div className="text-[var(--color-text-muted)]">{fmtTime(secondSmsAt)} ✓ доставлено</div>
|
<div className="text-[var(--color-text-muted)]">{fmtTime(secondSmsAt)} ✓ доставлено</div>
|
||||||
|
) : isTerminal ? (
|
||||||
|
<div className="text-[var(--color-text-muted)]">— {terminalLabel}, SMS не отправляется</div>
|
||||||
) : notifStatus === "second_sms_sending" && hasSmsSent ? (
|
) : notifStatus === "second_sms_sending" && hasSmsSent ? (
|
||||||
<div className="text-[var(--color-text-muted)]">{fmtTime(smsSentAt)} · отправлено, ждём подтверждения…</div>
|
<div className="text-[var(--color-text-muted)]">{fmtTime(smsSentAt)} · отправлено, ждём подтверждения…</div>
|
||||||
) : notifStatus === "first_sms_sent" && countdown ? (
|
) : notifStatus === "first_sms_sent" && countdown ? (
|
||||||
|
|
@ -225,7 +238,7 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Next check countdown */}
|
{/* Next check countdown */}
|
||||||
{countdown && notifStatus !== "second_sms_sent" && (
|
{countdown && notifStatus !== "second_sms_sent" && !isTerminal && (
|
||||||
<div className="flex items-center gap-2 rounded-lg bg-[var(--color-surface-strong)] px-2 py-1.5">
|
<div className="flex items-center gap-2 rounded-lg bg-[var(--color-surface-strong)] px-2 py-1.5">
|
||||||
<span className="text-[var(--color-text-muted)]">⏱ Следующая проверка:</span>
|
<span className="text-[var(--color-text-muted)]">⏱ Следующая проверка:</span>
|
||||||
<span className="font-mono text-[var(--color-accent)]">{countdown}</span>
|
<span className="font-mono text-[var(--color-accent)]">{countdown}</span>
|
||||||
|
|
@ -268,8 +281,8 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Restart buttons */}
|
{/* Restart buttons — hidden when delivery is terminal (picked_up/delivered/cancelled) */}
|
||||||
{canManage && (
|
{canManage && !isTerminal && (
|
||||||
<div className="mt-3 flex gap-2 border-t border-[var(--color-border)] pt-3">
|
<div className="mt-3 flex gap-2 border-t border-[var(--color-border)] pt-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue