supersam/src/services/deliveryInvitationApi.js

289 lines
7.9 KiB
JavaScript

import {
supabase,
hasSupabaseConfig,
} from "../supabaseClient";
const SHOWCASE_TOKEN = "showcase";
const LOCAL_CLIENT_FLOW_TOKEN_PREFIX = "client-flow-";
const LOCAL_CLIENT_FLOW_TOKEN = "client-flow-1001";
const LOCAL_INVITATION_STORAGE_PREFIX = "delivery-invitation:";
const localDeliveryInvitationCache = new Map();
const formatIsoDate = (date) => date.toISOString().slice(0, 10);
const addDays = (date, days) => {
const next = new Date(date);
next.setUTCDate(next.getUTCDate() + days);
return next;
};
const cloneInvitation = (invitation) => JSON.parse(JSON.stringify(invitation));
const getLocalStorage = () => {
try {
return globalThis.localStorage || null;
} catch {
return null;
}
};
const getLocalStorageKey = (token) => `${LOCAL_INVITATION_STORAGE_PREFIX}${token}`;
const readStoredInvitation = (token) => {
const storage = getLocalStorage();
if (!storage) {
return null;
}
try {
const raw = storage.getItem(getLocalStorageKey(token));
return raw ? JSON.parse(raw) : null;
} catch {
return null;
}
};
const writeStoredInvitation = (invitation) => {
const storage = getLocalStorage();
if (!storage?.setItem) {
return;
}
try {
storage.setItem(getLocalStorageKey(invitation.token), JSON.stringify(invitation));
} catch {
// Ignore storage quota and privacy mode failures.
}
};
const isLocalClientInvitationToken = (token) =>
token === SHOWCASE_TOKEN || token?.startsWith(LOCAL_CLIENT_FLOW_TOKEN_PREFIX);
const buildLocalClientInvitation = (token = LOCAL_CLIENT_FLOW_TOKEN, now = new Date()) => {
const firstDay = formatIsoDate(addDays(now, 1));
const secondDay = formatIsoDate(addDays(now, 2));
return {
token,
orderId: "order-cd-240031",
orderNumber: "CD-240031",
customerName: "Мария Волкова",
customerPhone: "+7 978 000-12-31",
state: "awaiting_choice",
deliveryDate: firstDay,
deliveryTime: "До обеда",
orderItems: [
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
{ name: "Фурнитура Blum", quantity: "12 шт" },
{ name: "Монтажный комплект", quantity: "1 набор" },
],
availableSlots: [
`${firstDay}, До обеда`,
`${firstDay}, После обеда`,
`${secondDay}, До обеда`,
`${secondDay}, После обеда`,
],
};
};
const getCachedInvitation = (token) => {
const invitation = localDeliveryInvitationCache.get(token);
return invitation ? cloneInvitation(invitation) : null;
};
const cacheInvitation = (invitation) => {
const clonedInvitation = cloneInvitation(invitation);
localDeliveryInvitationCache.set(clonedInvitation.token, clonedInvitation);
if (isLocalClientInvitationToken(clonedInvitation.token)) {
writeStoredInvitation(clonedInvitation);
}
return getCachedInvitation(clonedInvitation.token);
};
const buildFallbackInvitation = (token) =>
token === SHOWCASE_TOKEN
? buildShowcaseInvitation(token)
: buildLocalClientInvitation(token);
const warmLocalInvitationCache = (token, functionName) => {
void invokeDeliveryFunction(functionName, { token })
.then((response) => {
if (response?.invitation) {
cacheInvitation(response.invitation);
}
})
.catch(() => undefined);
};
export const buildShowcaseInvitation = (token = SHOWCASE_TOKEN, now = new Date()) => {
const firstDay = formatIsoDate(addDays(now, 1));
const secondDay = formatIsoDate(addDays(now, 2));
return {
token,
orderId: "showcase-order",
orderNumber: "CD-CLIENT-001",
customerName: "Мария Волкова",
customerPhone: "+7 978 000-12-31",
state: "awaiting_choice",
deliveryDate: firstDay,
deliveryTime: "До обеда",
orderItems: [
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
{ name: "Фурнитура Blum", quantity: "12 шт" },
{ name: "Монтажный комплект", quantity: "1 набор" },
],
availableSlots: [
`${firstDay}, До обеда`,
`${firstDay}, После обеда`,
`${secondDay}, До обеда`,
`${secondDay}, После обеда`,
],
};
};
const invokeDeliveryFunction = async (functionName, body) => {
if (!hasSupabaseConfig || !supabase?.functions?.invoke) {
throw new Error("Supabase is not configured");
}
const { data, error } = await supabase.functions.invoke(functionName, {
body,
});
if (error) {
throw error;
}
return data;
};
const callDeliveryRpc = async (functionName, params) => {
if (!hasSupabaseConfig || !supabase?.rpc) {
throw new Error("Supabase is not configured");
}
const { data, error } = await supabase.rpc(functionName, params);
if (error) {
throw error;
}
if (data && data.ok === false) {
throw new Error(data.error || "Request failed");
}
return data;
};
export const __resetLocalDeliveryInvitationCache = () => {
localDeliveryInvitationCache.clear();
};
export const fetchDeliveryInvitation = async (token) => {
if (!token) {
throw new Error("Token is required");
}
if (isLocalClientInvitationToken(token)) {
const cachedInvitation = getCachedInvitation(token);
const storedInvitation = cachedInvitation ?? readStoredInvitation(token);
const invitation = storedInvitation
? cacheInvitation(storedInvitation)
: cacheInvitation(buildFallbackInvitation(token));
warmLocalInvitationCache(token, "get-delivery-invitation");
return invitation;
}
const cachedInvitation = getCachedInvitation(token);
if (cachedInvitation) {
return cachedInvitation;
}
try {
const response = await callDeliveryRpc("get_delivery_invitation_by_token", {
p_token: token,
});
if (response?.invitation) {
return cacheInvitation(response.invitation);
}
if (isLocalClientInvitationToken(token)) {
return cacheInvitation(buildFallbackInvitation(token));
}
return response?.invitation;
} catch (error) {
if (isLocalClientInvitationToken(token)) {
return cacheInvitation(buildFallbackInvitation(token));
}
throw error;
}
};
export const confirmDeliveryChoice = async ({ token, deliveryDate, deliveryTime, deliveryType, pickupDate, pickupTimeSlot, deliveryAddress }) => {
if (isLocalClientInvitationToken(token)) {
const baseInvitation = getCachedInvitation(token) ?? buildFallbackInvitation(token);
const invitation = cacheInvitation({
...baseInvitation,
deliveryType: deliveryType || "delivery",
...(deliveryType === "pickup" ? { pickupDate, pickupTimeSlot } : {}),
...(deliveryType === "delivery" && deliveryAddress ? { deliveryAddress, customerAddress: deliveryAddress } : {}),
deliveryDate,
deliveryTime,
state: "confirmed",
});
warmLocalInvitationCache(token, "confirm-delivery-choice");
return { ok: true, invitation };
}
return callDeliveryRpc("confirm_delivery_choice_by_token", {
p_token: token,
p_delivery_date: deliveryDate,
p_delivery_time: deliveryTime,
p_delivery_type: deliveryType || "delivery",
p_pickup_date: pickupDate || null,
p_pickup_time_slot: pickupTimeSlot || null,
p_delivery_address: deliveryAddress || null,
});
};
export const requestDeliveryLink = async ({
orderId,
orderGroupId,
orderNumber,
customerName,
customerPhone,
customerMessenger,
availableSlots,
}) =>
invokeDeliveryFunction("create-delivery-invitation", {
orderId,
orderGroupId,
orderNumber,
customerName,
customerPhone,
customerMessenger,
availableSlots,
});
export const transferDeliveryToLogistics = async ({ orderId, reason, note, targetStatus }) =>
invokeDeliveryFunction("transfer-to-logistics", {
orderId,
reason,
note,
targetStatus,
});
export const reportDeliveryResult = async ({ orderId, result, note, payload }) =>
invokeDeliveryFunction("report-delivery-result", {
orderId,
result,
note,
payload,
});