supersam/supabase/functions/report-delivery-result/index.ts

159 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { getOrderUpdateForDeliveryInvitationAction } from "../_shared/delivery-invitations.ts";
import { requireUuid } from "../_shared/security.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 ReportBody = {
orderId?: string;
result?: "delivered" | "problem";
note?: string;
payload?: Record<string, unknown>;
};
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<ReportBody>(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-report",
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 isDelivered = body.result === "delivered";
const action = isDelivered ? "mark_delivered" : "mark_paid_storage";
const orderUpdate = getOrderUpdateForDeliveryInvitationAction(action);
const nextStatus = isDelivered ? orderUpdate?.status || "Доставлен" : "Проблема доставки";
const { error: invitationError } = await supabase
.from("delivery_invitations")
.update({
state: isDelivered ? "delivered" : "paid_storage",
...(isDelivered ? { delivered_at: new Date().toISOString() } : { paid_storage_at: new Date().toISOString() }),
})
.eq("order_id", body.orderId);
if (invitationError) {
throw invitationError;
}
const { error: updateError } = await supabase
.from("orders")
.update({
status: nextStatus,
delivery_agreement_status: isDelivered
? "Подтверждено клиентом"
: body.note || currentOrder.delivery_agreement_status || "Ошибка отправки",
})
.eq("id", body.orderId);
if (updateError) {
throw updateError;
}
const { error: historyError } = await supabase.from("order_history").insert({
order_id: body.orderId,
action: isDelivered ? "Подтверждение доставки" : "Фиксация проблемы доставки",
old_status: currentOrder.status,
new_status: isDelivered ? "Доставлен" : "Проблема доставки",
metadata: {
old_delivery_agreement_status: currentOrder.delivery_agreement_status,
new_delivery_agreement_status: isDelivered
? "Подтверждено клиентом"
: body.note || currentOrder.delivery_agreement_status || "Ошибка отправки",
payload: body.payload || {},
},
});
if (historyError) {
throw historyError;
}
await insertIntegrationEvent(supabase, {
order_id: body.orderId,
event_type: isDelivered ? "delivery_result_delivered" : "delivery_result_problem",
direction: "internal",
status: "success",
payload: {
result: body.result || null,
note: body.note || null,
payload: body.payload || {},
},
});
return jsonResponse(
{
ok: true,
orderId: body.orderId,
status: nextStatus,
deliveryAgreementStatus: isDelivered
? "Подтверждено клиентом"
: body.note || currentOrder.delivery_agreement_status || "Ошибка отправки",
workflowStatus: nextStatus,
},
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,
);
}
});