fix(логистика): бейдж обзвона не висит на согласованной заявке
Заявку согласовали — логист проставил дату вручную (delivery_date_source=manual) или клиент подтвердил по ссылке. Планировщик такие заявки уже не обзванивает, но бейдж последнего звонка («не дозвонились», «не выбрал день», «нужен менеджер») оставался и читался как состояние заявки рядом со «Согласовано». resolveCallBadge: исходы no_answer/no_input_day/no_input_half/no_input_both/manual скрываются, если заявка согласована (есть дата доставки/самовызова, notification_status=confirmed или статус ушёл в логистику). Запреты (opt_out/excluded/city_disabled) и подтверждение по звонку робота показываются как раньше. +7 тестов, SW v136.
This commit is contained in:
parent
50c9a68e9f
commit
34463bbc4a
|
|
@ -1,7 +1,7 @@
|
|||
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
||||
|
||||
if (!isLocalhost) {
|
||||
const STATIC_CACHE = "construction-delivery-static-v135";
|
||||
const STATIC_CACHE = "construction-delivery-static-v136";
|
||||
const RUNTIME_CACHE = "construction-delivery-runtime-v119";
|
||||
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
* «Не звонили» показываем только по заказам, которые ещё ждут решения
|
||||
* (pending_confirmation / manual_confirmation_required): в уже доставленных
|
||||
* заказах такая плашка — просто шум.
|
||||
*
|
||||
* То же правило действует для исходов «клиент не согласовал» (не дозвонились,
|
||||
* не выбрал день/время, нужен менеджер): если заявку уже согласовали — логист
|
||||
* проставил дату вручную или клиент подтвердил по ссылке — бейдж обзвона
|
||||
* скрываем. Иначе рядом со «Согласовано» висело «📅 не выбрал день», то есть
|
||||
* итог старого звонка читался как состояние заявки.
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
|
|
@ -18,6 +24,20 @@ import { AUTOCALL_STATUS_META } from "../../services/orderGroupViews";
|
|||
/** Статусы, при которых заказ ещё ждёт решения клиента. */
|
||||
const AWAITING_DELIVERY_STATUSES = ["pending_confirmation", "manual_confirmation_required"];
|
||||
|
||||
/** Статусы доставки: решение уже принято, заказ пошёл в логистику. */
|
||||
const AGREED_DELIVERY_STATUSES = [
|
||||
"agreed",
|
||||
"driver_assigned",
|
||||
"loaded",
|
||||
"on_route",
|
||||
"delivered",
|
||||
"pickup",
|
||||
"picked_up",
|
||||
];
|
||||
|
||||
/** Исходы звонка «клиент не согласовал» — след истории, а не состояние заявки. */
|
||||
const HISTORICAL_CALL_STATUSES = ["no_answer", "no_input_day", "no_input_half", "no_input_both", "manual"];
|
||||
|
||||
const STYLES = {
|
||||
confirmed: "bg-[rgba(18,128,92,0.14)] text-[var(--color-accent)]",
|
||||
no_answer: "bg-[rgba(239,68,68,0.14)] text-[var(--color-danger)]",
|
||||
|
|
@ -51,12 +71,32 @@ export const isAwaitingDecision = (group) => {
|
|||
return AWAITING_DELIVERY_STATUSES.includes(status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Согласована ли заявка — тогда роботу по ней звонить нечего и бейдж обзвона
|
||||
* не показываем. Признаки те же, по которым планировщик
|
||||
* (scripts/autocall_scheduler.py) исключает заявку из обзвона: есть дата
|
||||
* доставки или самовывоза, клиент подтвердил по ссылке (notification_status)
|
||||
* либо заказ уже ушёл в логистику.
|
||||
*/
|
||||
export const isCallingStopped = (group) => {
|
||||
if (!group) return false;
|
||||
const notification = String(group.notificationStatus || group.notification_status || "").toLowerCase();
|
||||
if (notification === "confirmed") return true;
|
||||
if (group.deliveryDate || group.delivery_date || group.pickupDate || group.pickup_date) return true;
|
||||
const status = String(group.deliveryStatus || group.delivery_status || "").toLowerCase();
|
||||
return AGREED_DELIVERY_STATUSES.includes(status);
|
||||
};
|
||||
|
||||
/** Что показать в строке: null — показывать нечего. */
|
||||
export const resolveCallBadge = (group) => {
|
||||
if (!group) return null;
|
||||
const status = group.autocallStatus || group.autocall_status || "not_called";
|
||||
// Запрет и исключение важны всегда, независимо от стадии заказа.
|
||||
if (status === "not_called" && !isAwaitingDecision(group)) return null;
|
||||
if (status === "not_called" && (!isAwaitingDecision(group) || isCallingStopped(group))) return null;
|
||||
// Заявку уже согласовали — исход последнего звонка «не дозвонились / не выбрал
|
||||
// день / нужен менеджер» стал историей: рядом со «Согласовано» он противоречит
|
||||
// статусу заявки.
|
||||
if (HISTORICAL_CALL_STATUSES.includes(status) && isCallingStopped(group)) return null;
|
||||
const meta = AUTOCALL_STATUS_META[status];
|
||||
if (!meta) return null;
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { CallStatusBadge, isAwaitingDecision, resolveCallBadge } from "./CallStatusBadge";
|
||||
import { CallStatusBadge, isAwaitingDecision, isCallingStopped, resolveCallBadge } from "./CallStatusBadge";
|
||||
import { AUTOCALL_STATUS_META, AUTOCALL_STATUS_OPTIONS } from "../../services/orderGroupViews";
|
||||
|
||||
const group = (over = {}) => ({ id: "g1", deliveryStatus: "pending_confirmation", ...over });
|
||||
|
|
@ -76,6 +76,73 @@ describe("resolveCallBadge — что видно в строке", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("заявка уже согласована — бейджа обзвона нет", () => {
|
||||
it("дату поставил логист вручную: «не выбрал день» больше не висит", () => {
|
||||
expect(resolveCallBadge(group({
|
||||
autocallStatus: "no_input_day",
|
||||
deliveryStatus: "agreed",
|
||||
deliveryDate: "2026-10-08",
|
||||
deliveryDateSource: "manual",
|
||||
notificationStatus: "confirmed",
|
||||
}))).toBeNull();
|
||||
});
|
||||
|
||||
it("клиент подтвердил по ссылке — исходы «не дозвонились» и «нужен менеджер» тоже скрыты", () => {
|
||||
const over = { notificationStatus: "confirmed", deliveryStatus: "pending_confirmation" };
|
||||
expect(resolveCallBadge(group({ ...over, autocallStatus: "no_answer" }))).toBeNull();
|
||||
expect(resolveCallBadge(group({ ...over, autocallStatus: "manual" }))).toBeNull();
|
||||
expect(resolveCallBadge(group({ ...over, autocallStatus: "no_input_half" }))).toBeNull();
|
||||
expect(resolveCallBadge(group({ ...over, autocallStatus: "no_input_both" }))).toBeNull();
|
||||
});
|
||||
|
||||
it("заказ ушёл в логистику (доставлен, самовывоз, в пути) — плашка не висит", () => {
|
||||
for (const deliveryStatus of ["delivered", "picked_up", "on_route", "pickup"]) {
|
||||
expect(resolveCallBadge(group({ autocallStatus: "no_answer", deliveryStatus }))).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("дата есть, а статус ещё «ждёт подтверждения» — это не повод показывать «день не выбрал»", () => {
|
||||
expect(resolveCallBadge(group({
|
||||
autocallStatus: "no_input_day",
|
||||
deliveryStatus: "pending_confirmation",
|
||||
deliveryDate: "2026-10-08",
|
||||
}))).toBeNull();
|
||||
});
|
||||
|
||||
it("не согласовано — подписи остаются на месте", () => {
|
||||
const badge = resolveCallBadge(group({ autocallStatus: "no_input_day", deliveryStatus: "pending_confirmation" }));
|
||||
expect(badge.label).toContain("не выбрал день");
|
||||
});
|
||||
|
||||
it("запрет и исключение важнее согласования — их не скрываем", () => {
|
||||
const agreed = { deliveryStatus: "delivered", deliveryDate: "2026-10-01" };
|
||||
expect(resolveCallBadge(group({ ...agreed, autocallStatus: "opt_out" })).label).toContain("не звонить");
|
||||
expect(resolveCallBadge(group({ ...agreed, autocallStatus: "excluded" })).label).toContain("исключение");
|
||||
expect(resolveCallBadge(group({ ...agreed, autocallStatus: "city_disabled" })).label).toContain("не звоним");
|
||||
});
|
||||
|
||||
it("согласие по звонку робота видно и после отгрузки — оно не противоречит статусу", () => {
|
||||
const badge = resolveCallBadge(group({ autocallStatus: "confirmed", deliveryStatus: "delivered" }));
|
||||
expect(badge.label).toContain("согласовано");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isCallingStopped", () => {
|
||||
it("согласование видно по дате, подтверждению клиента или уходу в логистику", () => {
|
||||
expect(isCallingStopped({ deliveryDate: "2026-10-08" })).toBe(true);
|
||||
expect(isCallingStopped({ pickupDate: "2026-10-08" })).toBe(true);
|
||||
expect(isCallingStopped({ notificationStatus: "confirmed" })).toBe(true);
|
||||
expect(isCallingStopped({ delivery_status: "driver_assigned" })).toBe(true);
|
||||
expect(isCallingStopped({ deliveryStatus: "agreed" })).toBe(true);
|
||||
});
|
||||
|
||||
it("пустая и необработанная заявка не считается согласованной", () => {
|
||||
expect(isCallingStopped(null)).toBe(false);
|
||||
expect(isCallingStopped(group({}))).toBe(false);
|
||||
expect(isCallingStopped(group({ deliveryStatus: "pending_confirmation", notificationStatus: "link_ready" }))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAwaitingDecision", () => {
|
||||
it("ждёт решения только ожидающий подтверждения заказ", () => {
|
||||
expect(isAwaitingDecision({ deliveryStatus: "pending_confirmation" })).toBe(true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue