supersam/volumes/functions/transfer-to-logistics/index.ts

157 lines
4.7 KiB
TypeScript

import {
getOrderUpdateForDeliveryInvitationAction,
} from "../_shared/delivery-invitations.ts";
import { createServiceClient } from "../_shared/chatbot.ts";
import { insertIntegrationEvent } from "../_shared/integration-events.ts";
import {
getCorsHeaders,
jsonResponse,
readJsonBody,
requireRateLimit,
verifyInternalRequest,
} from "../_shared/security.ts";
const MAX_BODY_BYTES = 16 * 1024;
type TransferBody = {
orderId?: string;
reason?: string;
note?: string;
targetStatus?: "Передан логисту" | "Платное хранение";
};
Deno.serve(async (request) => {
if (request.method === "OPTIONS") {
const corsHeaders = getCorsHeaders(request, "integration");
return corsHeaders ? new Response("ok", { headers: corsHeaders }) : jsonResponse({ error: "Origin not allowed" }, 403);
}
if (request.method !== "POST") {
return jsonResponse({ error: "Method not allowed" }, 405);
}
const corsHeaders = getCorsHeaders(request, "integration") || {};
try {
const { body, rawBody } = await readJsonBody<TransferBody>(request, {
maxBytes: MAX_BODY_BYTES,
});
await verifyInternalRequest(request, rawBody, { rawBody });
if (!body.orderId) {
return jsonResponse({ error: "orderId is required" }, 400, corsHeaders);
}
try {
requireUuid(body.orderId, "orderId");
} catch (e) {
return jsonResponse({ ok: false, error: (e as Error).message }, 400, corsHeaders);
}
const supabase = createServiceClient();
await requireRateLimit(supabase, {
scope: "delivery-transfer",
key: body.orderId,
maxCount: 10,
windowSeconds: 600,
blockSeconds: 1800,
});
const { data: currentOrder, error: orderError } = await supabase
.from("orders")
.select("id, status, delivery_agreement_status")
.eq("id", body.orderId)
.single();
if (orderError) {
throw orderError;
}
const targetStatus = body.targetStatus || "Передан логисту";
const action = targetStatus === "Платное хранение" ? "mark_paid_storage" : "transfer_to_logistics";
const orderUpdate = getOrderUpdateForDeliveryInvitationAction(action);
const { error: invitationError } = await supabase
.from("delivery_invitations")
.update({
state: targetStatus === "Платное хранение" ? "paid_storage" : "transferred_to_logistics",
...(targetStatus === "Платное хранение"
? { paid_storage_at: new Date().toISOString() }
: { logistics_transferred_at: new Date().toISOString() }),
})
.eq("order_id", body.orderId);
if (invitationError) {
throw invitationError;
}
const { error: updateError } = await supabase
.from("orders")
.update({
status: orderUpdate?.status,
delivery_agreement_status: orderUpdate?.deliveryAgreementStatus,
})
.eq("id", body.orderId);
if (updateError) {
throw updateError;
}
const { error: historyError } = await supabase.from("order_history").insert({
order_id: body.orderId,
action: targetStatus === "Платное хранение" ? "Перевод на платное хранение" : "Передача заказа логисту",
old_status: currentOrder.status,
new_status: orderUpdate?.status,
metadata: {
old_delivery_agreement_status: currentOrder.delivery_agreement_status,
new_delivery_agreement_status: orderUpdate?.deliveryAgreementStatus,
reason: body.reason || null,
note: body.note || null,
target_status: targetStatus,
},
});
if (historyError) {
throw historyError;
}
await insertIntegrationEvent(supabase, {
order_id: body.orderId,
event_type:
targetStatus === "Платное хранение" ? "delivery_paid_storage_requested" : "delivery_transfer_to_logistics",
direction: "internal",
status: "success",
payload: {
reason: body.reason || null,
note: body.note || null,
target_status: targetStatus,
},
});
return jsonResponse(
{
ok: true,
orderId: body.orderId,
status: orderUpdate?.status,
deliveryAgreementStatus: orderUpdate?.deliveryAgreementStatus,
},
200,
corsHeaders,
);
} catch (error) {
if (error instanceof Error && "status" in error) {
const httpError = error as { status: number; message: string };
return jsonResponse({ ok: false, error: httpError.message }, httpError.status, corsHeaders);
}
return jsonResponse(
{
ok: false,
error: error instanceof Error ? error.message : "Unexpected error",
},
500,
corsHeaders,
);
}
});