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.

137 lines
3.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { formatPercentDisplay, roundHalfUp } from "@/lib/math";
import { prisma } from "@/lib/prisma";
export type MarkupType = "percent" | "fixed";
export interface MarkupRule {
type: MarkupType;
percent: number;
fixedAmount: number;
}
export const DEFAULT_MARKUP_RULE: MarkupRule = {
type: "percent",
percent: 0,
fixedAmount: 0,
};
export interface MarkupResult {
markupAmount: number;
finalTotal: number;
}
/** 加价公式:百分比仅 raw_freight 参与固定金额为每档固定叠加GD-6 扩展) */
export function applyMarkup(
rawFreight: number,
rawTotal: number,
rule: MarkupRule | number,
): MarkupResult {
const normalized =
typeof rule === "number"
? { type: "percent" as const, percent: rule, fixedAmount: 0 }
: rule;
const markupAmount =
normalized.type === "fixed"
? roundHalfUp(normalized.fixedAmount, 2)
: roundHalfUp(rawFreight * (normalized.percent / 100), 2);
const finalTotal = roundHalfUp(rawTotal + markupAmount, 2);
return { markupAmount, finalTotal };
}
/** 读取客户加价规则;未配置默认 0%BR-6.3 */
export async function getMarkupRule(customerId: string): Promise<MarkupRule> {
const config = await prisma.markupConfig.findFirst({
where: { customerId, isDeleted: false },
select: {
markupType: true,
markupPercent: true,
markupFixedAmount: true,
},
});
if (!config) {
return DEFAULT_MARKUP_RULE;
}
if (config.markupType === "fixed") {
return {
type: "fixed",
percent: 0,
fixedAmount: Number(config.markupFixedAmount ?? 0),
};
}
return {
type: "percent",
percent: Number(config.markupPercent),
fixedAmount: 0,
};
}
/** @deprecated 请使用 getMarkupRule保留兼容旧调用 */
export async function getMarkupPercent(customerId: string): Promise<number> {
const rule = await getMarkupRule(customerId);
return rule.type === "percent" ? rule.percent : 0;
}
export interface RawQuoteTier {
service_level: string;
rate_option: string;
carrier: string;
transit_days: string;
transit_description: string;
raw_freight: number;
surcharges: number;
raw_total: number;
}
export interface MarkedUpQuoteTier extends RawQuoteTier {
markup_percent: number;
markup_amount: number;
final_total: number;
breakdown: Array<{ item: string; amount: number }>;
}
function markupBreakdownLabel(rule: MarkupRule): string {
if (rule.type === "fixed") {
return `运营加价(固定 ${rule.fixedAmount.toFixed(2)} USD)`;
}
return `运营加价(${formatPercentDisplay(rule.percent)})`;
}
/** 对单档报价应用加价并生成 breakdown */
export function applyMarkupToTier(
tier: RawQuoteTier,
rule: MarkupRule | number,
): MarkedUpQuoteTier {
const normalized =
typeof rule === "number"
? { type: "percent" as const, percent: rule, fixedAmount: 0 }
: rule;
const { markupAmount, finalTotal } = applyMarkup(
tier.raw_freight,
tier.raw_total,
normalized,
);
return {
...tier,
markup_percent: normalized.type === "percent" ? normalized.percent : 0,
markup_amount: markupAmount,
final_total: finalTotal,
breakdown: [
{ item: "运费", amount: tier.raw_freight },
{ item: "附加费", amount: tier.surcharges },
{
item: markupBreakdownLabel(normalized),
amount: markupAmount,
},
],
};
}
/** 对多档原始报价批量加价 */
export function applyMarkupToQuotes(
quotes: RawQuoteTier[],
rule: MarkupRule | number,
): MarkedUpQuoteTier[] {
return quotes.map((tier) => applyMarkupToTier(tier, rule));
}