151 lines
4.6 KiB
TypeScript
151 lines
4.6 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);
|
|
}
|
|
|
|
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,
|
|
);
|
|
}
|
|
});
|