289 lines
7.9 KiB
JavaScript
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,
|
|
});
|