You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
4.7 KiB

import { parseServiceAuth } from "@/lib/api/auth-context";
import { prisma } from "@/lib/prisma";
import { fail, ok } from "@/lib/response";
import { AuthError } from "@/modules/auth/errors";
import { recordSecurityEvent } from "@/modules/audit/service";
import { serializeQuoteDetail } from "@/modules/quote/quote-serializer";
import { toMsRefineHoldPublic } from "@/lib/constants/ms-refine-hold";
import { readMsRefineHoldByQuoteId } from "@/lib/mothership/refine-hold-store";
import { readMsCheckoutStatus, toMsCheckoutPublic } from "@/lib/mothership/ms-checkout-store";
import {
readFlockCheckoutStatus,
toFlockCheckoutPublic,
} from "@/lib/flock/flock-checkout-store";
import {
readFlockPricingOptions,
toFlockPricingOptionsPublic,
} from "@/lib/flock/flock-pricing-options-store";
import {
toFlockQuoteHoldPublic,
shouldAutoDeclineFlockHold,
} from "@/lib/constants/flock-quote-hold";
import {
readFlockHoldCarriers,
readFlockHoldFlexibility,
readFlockQuoteHoldByQuoteId,
} from "@/lib/flock/flock-quote-hold-store";
import { declineFlockQuoteHold } from "@/modules/flock/hold-service";
type RouteContext = {
params: Promise<{ quote_id: string }>;
};
export async function GET(request: Request, context: RouteContext) {
let auth;
try {
auth = await parseServiceAuth(request);
} catch (error) {
if (error instanceof AuthError) {
return fail(error.code, error.message, error.httpStatus);
}
throw error;
}
const { quote_id: quoteId } = await context.params;
const businessCustomerId = new URL(request.url).searchParams
.get("business_customer_id")
?.trim();
const record = await prisma.quoteRecord.findFirst({
where: { quoteId, isDeleted: false },
});
if (!record) {
return fail("QUOTE_NOT_FOUND", "报价不存在", 404);
}
if (record.customerId !== auth.customerId) {
void recordSecurityEvent(
"FORBIDDEN_ACCESS",
auth.customerId,
quoteId,
{
attempted_quote_id: quoteId,
owner_customer_id: record.customerId,
},
);
return fail("FORBIDDEN", "无权访问该报价", 403);
}
if (
businessCustomerId &&
(record.businessCustomerId ?? "") !== businessCustomerId
) {
return fail("FORBIDDEN", "无权访问该业务客户报价", 403);
}
const detail = serializeQuoteDetail(record);
const hold = await readMsRefineHoldByQuoteId(quoteId);
if (hold && hold.customer_id === auth.customerId) {
// 填写中不自动放弃;仅在仍可用时透出 hold
const pub = toMsRefineHoldPublic(hold);
if (pub.available || hold.status === "refining" || hold.status === "filling") {
detail.refine_hold = {
...pub,
available:
hold.status === "refining" || hold.status === "filling"
? true
: pub.available,
};
}
}
const checkout = await readMsCheckoutStatus(quoteId);
if (checkout) {
detail.ms_checkout = toMsCheckoutPublic(checkout);
}
const flockCheckout = await readFlockCheckoutStatus(quoteId);
if (flockCheckout) {
detail.flock_checkout = toFlockCheckoutPublic(flockCheckout);
}
const flockPricing = await readFlockPricingOptions(quoteId);
if (flockPricing) {
detail.flock_pricing_options = toFlockPricingOptionsPublic(flockPricing);
}
const flockHold = await readFlockQuoteHoldByQuoteId(quoteId);
if (flockHold && flockHold.customer_id === auth.customerId) {
if (shouldAutoDeclineFlockHold(flockHold)) {
await declineFlockQuoteHold({
customerId: auth.customerId,
quoteId,
sessionId: flockHold.quote_session_id,
}).catch(() => undefined);
} else if (
(flockHold.status === "filling" ||
flockHold.status === "checking_out") &&
flockHold.total_deadline_ms <= Date.now()
) {
await declineFlockQuoteHold({
customerId: auth.customerId,
quoteId,
sessionId: flockHold.quote_session_id,
}).catch(() => undefined);
} else {
const pub = toFlockQuoteHoldPublic(flockHold);
if (
pub.available ||
flockHold.status === "filling" ||
flockHold.status === "checking_out"
) {
detail.flock_hold = {
...pub,
available:
flockHold.status === "filling" ||
flockHold.status === "checking_out"
? true
: pub.available,
};
}
}
}
const flockFlex = await readFlockHoldFlexibility(quoteId);
if (flockFlex && Object.keys(flockFlex).length > 0) {
detail.flock_flexibility_by_tier = flockFlex;
}
const flockCarriers = await readFlockHoldCarriers(quoteId);
if (flockCarriers && Object.keys(flockCarriers).length > 0) {
detail.flock_carriers_by_tier = flockCarriers;
}
return ok(detail);
}