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
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);
|
|
}
|